Skip to content

Commit

Permalink
[CIR][CodeGen] Bitfield operations (llvm#279)
Browse files Browse the repository at this point in the history
As we discussed in llvm#233, there is a desire to have CIR operations for
bit fields set/get access and do all the stuff in the `LoweringPrepare`.

There is one thing I want to discuss, that's why the PR is marked as a
draft now.
Looks like I have to introduce some redundant helpers for all these `or`
and `shift` operations: while we were in the `CodeGen` area, we used
`CIRGenBuilder` and we could easily extend it. I bet we don't want to
depend from `CodeGen` in the `LoweringPrepare`. Once it's true. what is
a good place for all this common things? As an idea, we could introduce
one more layer for builder, with no state involved - just helpers and
nothing else. But again, what is a good place for it from your point of
view?
  • Loading branch information
gitoleg authored and lanza committed Jan 29, 2024
1 parent 48572c3 commit 3cd2c56
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 129 deletions.
27 changes: 27 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -521,4 +521,31 @@ def GlobalCtorAttr : CIR_Attr<"GlobalCtor", "globalCtor"> {
];
let skipDefaultBuilders = 1;
}

def BitfieldInfoAttr : CIR_Attr<"BitfieldInfo", "bitfield_info"> {
let summary = "Represents a bit field info";
let description = [{
Holds the next information about bitfields: name, storage type, a bitfield size
and position in the storage, if the bitfield is signed or not.
}];
let parameters = (ins "StringAttr":$name,
"Type":$storage_type,
"uint64_t":$size,
"uint64_t":$offset,
"bool":$is_signed);

let assemblyFormat = "`<` struct($name, $storage_type, $size, $offset, $is_signed) `>`";

let builders = [
AttrBuilder<(ins "StringRef":$name,
"Type":$storage_type,
"uint64_t":$size,
"uint64_t":$offset,
"bool":$is_signed
), [{
return $_get($_ctxt, StringAttr::get($_ctxt, name), storage_type, size, offset, is_signed);
}]>
];
}

#endif // MLIR_CIR_DIALECT_CIR_ATTRS
148 changes: 148 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1415,6 +1415,154 @@ def VTableAddrPointOp : CIR_Op<"vtable.address_point",
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// SetBitfieldOp
//===----------------------------------------------------------------------===//

def SetBitfieldOp : CIR_Op<"set_bitfield"> {
let summary = "Set a bitfield";
let description = [{
The `cir.set_bitfield` operation provides a store-like access to
a bit field of a record.

It expects an address of a storage where to store, a type of the storage,
a value being stored, a name of a bit field, a pointer to the storage in the
base record, a size of the storage, a size the bit field, an offset
of the bit field and a sign. Returns a value being stored.

Example.
Suppose we have a struct with multiple bitfields stored in
different storages. The `cir.set_bitfield` operation sets the value
of the bitfield.
```C++
typedef struct {
int a : 4;
int b : 27;
int c : 17;
int d : 2;
int e : 15;
} S;

void store_bitfield(S& s) {
s.d = 3;
}
```

```mlir
// 'd' is in the storage with the index 1
!struct_type = !cir.struct<struct "S" {!cir.int<u, 32>, !cir.int<u, 32>, !cir.int<u, 16>} #cir.record.decl.ast>
#bfi_d = #cir.bitfield_info<name = "d", storage_type = !u32i, size = 2, offset = 17, is_signed = true>

%1 = cir.const(#cir.int<3> : !s32i) : !s32i
%2 = cir.load %0 : cir.ptr <!cir.ptr<!struct_type>>, !cir.ptr<!struct_type>
%3 = cir.get_member %2[1] {name = "d"} : !cir.ptr<!struct_type> -> !cir.ptr<!u32i>
%4 = cir.set_bitfield(#bfi_d, %3 : !cir.ptr<!u32i>, %1 : !s32i) -> !s32i
```
}];

let arguments = (ins
AnyType:$dst,
AnyType:$src,
BitfieldInfoAttr:$bitfield_info
);

let results = (outs CIR_IntType:$result);

let assemblyFormat = [{ `(`$bitfield_info`,` $dst`:`type($dst)`,`
$src`:`type($src) `)` attr-dict `->` type($result) }];

let builders = [
OpBuilder<(ins "Type":$type,
"Value":$dst,
"Type":$storage_type,
"Value":$src,
"StringRef":$name,
"unsigned":$size,
"unsigned":$offset,
"bool":$is_signed
),
[{
BitfieldInfoAttr info =
BitfieldInfoAttr::get($_builder.getContext(),
name, storage_type,
size, offset, is_signed);
build($_builder, $_state, type, dst, src, info);
}]>
];
}

//===----------------------------------------------------------------------===//
// GetBitfieldOp
//===----------------------------------------------------------------------===//

def GetBitfieldOp : CIR_Op<"get_bitfield"> {
let summary = "Get a bitfield";
let description = [{
The `cir.get_bitfield` operation provides a load-like access to
a bit field of a record.

It expects a name if a bit field, a pointer to a storage in the
base record, a type of the storage, a name of the bitfield,
a size the bit field, an offset of the bit field and a sign.

Example:
Suppose we have a struct with multiple bitfields stored in
different storages. The `cir.get_bitfield` operation gets the value
of the bitfield
```C++
typedef struct {
int a : 4;
int b : 27;
int c : 17;
int d : 2;
int e : 15;
} S;

int load_bitfield(S& s) {
return s.d;
}
```

```mlir
// 'd' is in the storage with the index 1
!struct_type = !cir.struct<struct "S" {!cir.int<u, 32>, !cir.int<u, 32>, !cir.int<u, 16>} #cir.record.decl.ast>
#bfi_d = #cir.bitfield_info<name = "d", storage_type = !u32i, size = 2, offset = 17, is_signed = true>

%2 = cir.load %0 : cir.ptr <!cir.ptr<!struct_type>>, !cir.ptr<!struct_type>
%3 = cir.get_member %2[1] {name = "d"} : !cir.ptr<!struct_type> -> !cir.ptr<!u32i>
%4 = cir.get_bitfield(#bfi_d, %3 : !cir.ptr<!u32i>) -> !s32i
```
}];

let arguments = (ins
AnyType:$addr,
BitfieldInfoAttr:$bitfield_info
);

let results = (outs CIR_IntType:$result);

let assemblyFormat = [{ `(`$bitfield_info `,` $addr attr-dict `:`
type($addr) `)` `->` type($result) }];

let builders = [
OpBuilder<(ins "Type":$type,
"Value":$addr,
"Type":$storage_type,
"StringRef":$name,
"unsigned":$size,
"unsigned":$offset,
"bool":$is_signed
),
[{
BitfieldInfoAttr info =
BitfieldInfoAttr::get($_builder.getContext(),
name, storage_type,
size, offset, is_signed);
build($_builder, $_state, type, addr, info);
}]>
];
}

//===----------------------------------------------------------------------===//
// GetMemberOp
//===----------------------------------------------------------------------===//
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_CLANG_LIB_CIR_CIRGENBUILDER_H

#include "Address.h"
#include "CIRGenRecordLayout.h"
#include "CIRDataLayout.h"
#include "CIRGenTypeCache.h"
#include "UnimplementedFeatureGuarding.h"
Expand Down Expand Up @@ -661,6 +662,26 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
global.getLoc(), getPointerTo(global.getSymType()), global.getName());
}

mlir::Value createGetBitfield(mlir::Location loc, mlir::Type resultType,
mlir::Value addr, mlir::Type storageType,
const CIRGenBitFieldInfo &info,
bool useVolatile) {
auto offset = useVolatile ? info.VolatileOffset : info.Offset;
return create<mlir::cir::GetBitfieldOp>(loc, resultType, addr, storageType,
info.Name, info.Size,
offset, info.IsSigned);
}

mlir::Value createSetBitfield(mlir::Location loc, mlir::Type resultType,
mlir::Value dstAddr, mlir::Type storageType,
mlir::Value src, const CIRGenBitFieldInfo &info,
bool useVolatile) {
auto offset = useVolatile ? info.VolatileOffset : info.Offset;
return create<mlir::cir::SetBitfieldOp>(
loc, resultType, dstAddr, storageType, src, info.Name,
info.Size, offset, info.IsSigned);
}

/// Create a pointer to a record member.
mlir::Value createGetMember(mlir::Location loc, mlir::Type result,
mlir::Value base, llvm::StringRef name,
Expand Down
Loading

0 comments on commit 3cd2c56

Please sign in to comment.