Skip to content

[CIR][CodeGen] Bitfield operations #279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Nov 30, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
@@ -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
@@ -1416,6 +1416,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
//===----------------------------------------------------------------------===//
21 changes: 21 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
@@ -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"
@@ -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,
147 changes: 36 additions & 111 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
@@ -220,14 +220,14 @@ static bool isAAPCS(const TargetInfo &TargetInfo) {
return TargetInfo.getABI().startswith("aapcs");
}

Address CIRGenFunction::getAddrOfBitFieldStorage(LValue base,
Address CIRGenFunction::getAddrOfBitFieldStorage(LValue base,
const FieldDecl *field,
unsigned index,
unsigned size) {
if (index == 0)
return base.getAddress();

auto loc = getLoc(field->getLocation());
auto loc = getLoc(field->getLocation());
auto fieldType = builder.getUIntNTy(size);

auto fieldPtr =
@@ -266,7 +266,6 @@ LValue CIRGenFunction::buildLValueForBitField(LValue base,

const unsigned SS = useVolatile ? info.VolatileStorageSize : info.StorageSize;
Address Addr = getAddrOfBitFieldStorage(base, field, Idx, SS);

// Get the access type.
mlir::Type FieldIntTy = builder.getUIntNTy(SS);

@@ -276,7 +275,6 @@ LValue CIRGenFunction::buildLValueForBitField(LValue base,

QualType fieldType =
field->getType().withCVRQualifiers(base.getVRQualifiers());

assert(!UnimplementedFeature::tbaa() && "NYI TBAA for bit fields");
LValueBaseInfo FieldBaseInfo(BaseInfo.getAlignmentSource());
return LValue::MakeBitfield(Addr, info, fieldType, FieldBaseInfo);
@@ -398,7 +396,7 @@ LValue CIRGenFunction::buildLValueForFieldInitialization(

auto& layout = CGM.getTypes().getCIRGenRecordLayout(Field->getParent());
unsigned FieldIndex = layout.getCIRFieldNo(Field);

Address V = buildAddrOfFieldStorage(*this, Base.getAddress(), Field,
FieldName, FieldIndex);

@@ -607,42 +605,20 @@ RValue CIRGenFunction::buildLoadOfLValue(LValue LV, SourceLocation Loc) {

RValue CIRGenFunction::buildLoadOfBitfieldLValue(LValue LV,
SourceLocation Loc) {
const CIRGenBitFieldInfo &Info = LV.getBitFieldInfo();
const CIRGenBitFieldInfo &info = LV.getBitFieldInfo();

// Get the output type.
mlir::Type ResLTy = convertType(LV.getType());
Address Ptr = LV.getBitFieldAddress();
mlir::Value Val = builder.createLoad(getLoc(Loc), Ptr);
auto ValWidth = Val.getType().cast<IntType>().getWidth();

bool UseVolatile = LV.isVolatileQualified() &&
Info.VolatileStorageSize != 0 && isAAPCS(CGM.getTarget());
const unsigned Offset = UseVolatile ? Info.VolatileOffset : Info.Offset;
const unsigned StorageSize =
UseVolatile ? Info.VolatileStorageSize : Info.StorageSize;

if (Info.IsSigned) {
assert(static_cast<unsigned>(Offset + Info.Size) <= StorageSize);

mlir::Type typ = builder.getSIntNTy(ValWidth);
Val = builder.createIntCast(Val, typ);

unsigned HighBits = StorageSize - Offset - Info.Size;
if (HighBits)
Val = builder.createShiftLeft(Val, HighBits);
if (Offset + HighBits)
Val = builder.createShiftRight(Val, Offset + HighBits);
} else {
if (Offset)
Val = builder.createShiftRight(Val, Offset);
mlir::Type resLTy = convertType(LV.getType());
Address ptr = LV.getBitFieldAddress();

if (static_cast<unsigned>(Offset) + Info.Size < StorageSize)
Val = builder.createAnd(Val,
llvm::APInt::getLowBitsSet(ValWidth, Info.Size));
}
Val = builder.createIntCast(Val, ResLTy);
assert(!UnimplementedFeature::emitScalarRangeCheck() && "NYI");
return RValue::get(Val);
bool useVolatile = LV.isVolatileQualified() &&
info.VolatileStorageSize != 0 && isAAPCS(CGM.getTarget());

auto field =
builder.createGetBitfield(getLoc(Loc), resLTy, ptr.getPointer(),
ptr.getElementType(), info, useVolatile);
assert(!UnimplementedFeature::emitScalarRangeCheck() && "NYI");
return RValue::get(field);
}

void CIRGenFunction::buildStoreThroughLValue(RValue Src, LValue Dst) {
@@ -667,79 +643,28 @@ void CIRGenFunction::buildStoreThroughLValue(RValue Src, LValue Dst) {

void CIRGenFunction::buildStoreThroughBitfieldLValue(RValue Src, LValue Dst,
mlir::Value &Result) {
const CIRGenBitFieldInfo &Info = Dst.getBitFieldInfo();
mlir::Type ResLTy = getTypes().convertTypeForMem(Dst.getType());
Address Ptr = Dst.getBitFieldAddress();

// Get the source value, truncated to the width of the bit-field.
mlir::Value SrcVal = Src.getScalarVal();

// Cast the source to the storage type and shift it into place.
SrcVal = builder.createIntCast(SrcVal, Ptr.getElementType());
auto SrcWidth = SrcVal.getType().cast<IntType>().getWidth();
mlir::Value MaskedVal = SrcVal;

const bool UseVolatile =
// According to the AACPS:
// When a volatile bit-field is written, and its container does not overlap
// with any non-bit-field member, its container must be read exactly once
// and written exactly once using the access width appropriate to the type
// of the container. The two accesses are not atomic.
if (Dst.isVolatileQualified() && isAAPCS(CGM.getTarget()) &&
CGM.getCodeGenOpts().ForceAAPCSBitfieldLoad)
llvm_unreachable("volatile bit-field is not implemented for the AACPS");

const CIRGenBitFieldInfo &info = Dst.getBitFieldInfo();
mlir::Type resLTy = getTypes().convertTypeForMem(Dst.getType());
Address ptr = Dst.getBitFieldAddress();

const bool useVolatile =
CGM.getCodeGenOpts().AAPCSBitfieldWidth && Dst.isVolatileQualified() &&
Info.VolatileStorageSize != 0 && isAAPCS(CGM.getTarget());
const unsigned StorageSize =
UseVolatile ? Info.VolatileStorageSize : Info.StorageSize;
const unsigned Offset = UseVolatile ? Info.VolatileOffset : Info.Offset;
// See if there are other bits in the bitfield's storage we'll need to load
// and mask together with source before storing.
if (StorageSize != Info.Size) {
assert(StorageSize > Info.Size && "Invalid bitfield size.");

mlir::Value Val = buildLoadOfScalar(Dst, Dst.getPointer().getLoc());

// Mask the source value as needed.
if (!hasBooleanRepresentation(Dst.getType()))
SrcVal = builder.createAnd(
SrcVal, llvm::APInt::getLowBitsSet(SrcWidth, Info.Size));

MaskedVal = SrcVal;
if (Offset)
SrcVal = builder.createShiftLeft(SrcVal, Offset);

// Mask out the original value.
Val = builder.createAnd(
Val, ~llvm::APInt::getBitsSet(SrcWidth, Offset, Offset + Info.Size));
info.VolatileStorageSize != 0 && isAAPCS(CGM.getTarget());

// Or together the unchanged values and the source value.
SrcVal = builder.createOr(Val, SrcVal);
mlir::Value dstAddr = Dst.getAddress().getPointer();

} else {
// According to the AACPS:
// When a volatile bit-field is written, and its container does not overlap
// with any non-bit-field member, its container must be read exactly once
// and written exactly once using the access width appropriate to the type
// of the container. The two accesses are not atomic.
if (Dst.isVolatileQualified() && isAAPCS(CGM.getTarget()) &&
CGM.getCodeGenOpts().ForceAAPCSBitfieldLoad)
llvm_unreachable("volatile bit-field is not implemented for the AACPS");
}

// Write the new value back out.
// TODO: constant matrix type, volatile, no init, non temporal, TBAA
buildStoreOfScalar(SrcVal, Ptr, Dst.isVolatileQualified(), Dst.getType(),
Dst.getBaseInfo(), false, false);

// Return the new value of the bit-field.
mlir::Value ResultVal = MaskedVal;
ResultVal = builder.createIntCast(ResultVal, ResLTy);

// Sign extend the value if needed.
if (Info.IsSigned) {
assert(Info.Size <= StorageSize);
unsigned HighBits = StorageSize - Info.Size;

if (HighBits) {
ResultVal = builder.createShiftLeft(ResultVal, HighBits);
ResultVal = builder.createShiftRight(ResultVal, HighBits);
}
}

Result = buildFromMemory(ResultVal, Dst.getType());
Result = builder.createSetBitfield(dstAddr.getLoc(), resLTy, dstAddr,
ptr.getElementType(), Src.getScalarVal(),
info, useVolatile);
}

static LValue buildGlobalVarDeclLValue(CIRGenFunction &CGF, const Expr *E,
@@ -2455,9 +2380,9 @@ mlir::Value CIRGenFunction::buildLoadOfScalar(Address Addr, bool Volatile,
if (isNontemporal) {
llvm_unreachable("NYI");
}
assert(!UnimplementedFeature::tbaa() && "NYI");
assert(!UnimplementedFeature::emitScalarRangeCheck() && "NYI");

assert(!UnimplementedFeature::tbaa() && "NYI");
assert(!UnimplementedFeature::emitScalarRangeCheck() && "NYI");

return buildFromMemory(Load, Ty);
}
3 changes: 3 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
Original file line number Diff line number Diff line change
@@ -91,6 +91,9 @@ struct CIRGenBitFieldInfo {
/// The offset of the bitfield storage from the start of the struct.
clang::CharUnits VolatileStorageOffset;

/// The name of a bitfield
llvm::StringRef Name;

CIRGenBitFieldInfo()
: Offset(), Size(), IsSigned(), StorageSize(), VolatileOffset(),
VolatileStorageSize() {}
3 changes: 2 additions & 1 deletion clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp
Original file line number Diff line number Diff line change
@@ -227,7 +227,8 @@ void CIRRecordLowering::setBitFieldInfo(const FieldDecl *FD,
(unsigned)(getFieldBitOffset(FD) - astContext.toBits(StartOffset));
Info.Size = FD->getBitWidthValue(astContext);
Info.StorageSize = getSizeInBits(StorageType).getQuantity();
Info.StorageOffset = StartOffset;
Info.StorageOffset = StartOffset;
Info.Name = FD->getName();

if (Info.Size > Info.StorageSize)
Info.Size = Info.StorageSize;
4 changes: 4 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
@@ -78,6 +78,10 @@ struct CIROpAsmDialectInterface : public OpAsmDialectInterface {
os << (boolAttr.getValue() ? "true" : "false");
return AliasResult::FinalAlias;
}
if (auto bitfield = attr.dyn_cast<mlir::cir::BitfieldInfoAttr>()) {
os << "bfi_" << bitfield.getName().str();
return AliasResult::FinalAlias;
}

return AliasResult::NoAlias;
}
150 changes: 133 additions & 17 deletions clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Mangle.h"
#include "clang/Basic/Module.h"
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/Passes.h"
#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"
@@ -23,8 +24,9 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"

using cir::CIRBaseBuilderTy;
using namespace mlir;
using namespace cir;
using namespace mlir::cir;

static SmallString<128> getTransformedFileName(ModuleOp theModule) {
SmallString<128> FileName;
@@ -47,36 +49,39 @@ static SmallString<128> getTransformedFileName(ModuleOp theModule) {
}

/// Return the FuncOp called by `callOp`.
static cir::FuncOp getCalledFunction(cir::CallOp callOp) {
static FuncOp getCalledFunction(CallOp callOp) {
SymbolRefAttr sym =
llvm::dyn_cast_if_present<SymbolRefAttr>(callOp.getCallableForCallee());
if (!sym)
return nullptr;
return dyn_cast_or_null<cir::FuncOp>(
return dyn_cast_or_null<FuncOp>(
SymbolTable::lookupNearestSymbolFrom(callOp, sym));
}

namespace {

struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
LoweringPreparePass() = default;
void runOnOperation() override;

void runOnOp(Operation *op);
void lowerGlobalOp(GlobalOp op);
void lowerGetBitfieldOp(GetBitfieldOp op);
void lowerSetBitfieldOp(SetBitfieldOp op);

/// Build the function that initializes the specified global
cir::FuncOp buildCXXGlobalVarDeclInitFunc(GlobalOp op);
FuncOp buildCXXGlobalVarDeclInitFunc(GlobalOp op);

/// Build a module init function that calls all the dynamic initializers.
void buildCXXGlobalInitFunc();

cir::FuncOp
FuncOp
buildRuntimeFunction(mlir::OpBuilder &builder, llvm::StringRef name,
mlir::Location loc, mlir::cir::FuncType type,
mlir::cir::GlobalLinkageKind linkage =
mlir::cir::GlobalLinkageKind::ExternalLinkage);

cir::GlobalOp
GlobalOp
buildRuntimeVariable(mlir::OpBuilder &Builder, llvm::StringRef Name,
mlir::Location Loc, mlir::Type type,
mlir::cir::GlobalLinkageKind Linkage =
@@ -98,11 +103,11 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
};
} // namespace

cir::GlobalOp LoweringPreparePass::buildRuntimeVariable(
GlobalOp LoweringPreparePass::buildRuntimeVariable(
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
mlir::Type type, mlir::cir::GlobalLinkageKind linkage) {
cir::GlobalOp g =
dyn_cast_or_null<cir::GlobalOp>(SymbolTable::lookupNearestSymbolFrom(
GlobalOp g =
dyn_cast_or_null<GlobalOp>(SymbolTable::lookupNearestSymbolFrom(
theModule, StringAttr::get(theModule->getContext(), name)));
if (!g) {
g = builder.create<mlir::cir::GlobalOp>(loc, name, type);
@@ -114,11 +119,11 @@ cir::GlobalOp LoweringPreparePass::buildRuntimeVariable(
return g;
}

cir::FuncOp LoweringPreparePass::buildRuntimeFunction(
FuncOp LoweringPreparePass::buildRuntimeFunction(
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
mlir::cir::FuncType type, mlir::cir::GlobalLinkageKind linkage) {
cir::FuncOp f =
dyn_cast_or_null<cir::FuncOp>(SymbolTable::lookupNearestSymbolFrom(
FuncOp f =
dyn_cast_or_null<FuncOp>(SymbolTable::lookupNearestSymbolFrom(
theModule, StringAttr::get(theModule->getContext(), name)));
if (!f) {
f = builder.create<mlir::cir::FuncOp>(loc, name, type);
@@ -133,7 +138,7 @@ cir::FuncOp LoweringPreparePass::buildRuntimeFunction(
return f;
}

cir::FuncOp LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(GlobalOp op) {
FuncOp LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(GlobalOp op) {
SmallString<256> fnName;
{
llvm::raw_svector_ostream Out(fnName);
@@ -177,7 +182,7 @@ cir::FuncOp LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(GlobalOp op) {
break;
}
assert(dtorCall && "Expected a dtor call");
cir::FuncOp dtorFunc = getCalledFunction(dtorCall);
FuncOp dtorFunc = getCalledFunction(dtorCall);
assert(dtorFunc &&
mlir::isa<ASTCXXDestructorDeclInterface>(*dtorFunc.getAst()) &&
"Expected a dtor call");
@@ -297,10 +302,117 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() {
builder.create<ReturnOp>(f.getLoc());
}

void LoweringPreparePass::lowerGetBitfieldOp(GetBitfieldOp op) {
CIRBaseBuilderTy builder(getContext());
builder.setInsertionPointAfter(op.getOperation());

auto info = op.getBitfieldInfo();
auto size = info.getSize();
auto storageType = info.getStorageType();
auto storageSize = storageType.cast<IntType>().getWidth();
auto offset = info.getOffset();
auto resultTy = op.getType();
auto addr = op.getAddr();
auto loc = addr.getLoc();
mlir::Value val =
builder.create<mlir::cir::LoadOp>(loc, storageType, op.getAddr());
auto valWidth = val.getType().cast<IntType>().getWidth();

if (info.getIsSigned()) {
assert(static_cast<unsigned>(offset + size) <= storageSize);
mlir::Type typ =
mlir::cir::IntType::get(builder.getContext(), valWidth, true);

val = builder.createIntCast(val, typ);

unsigned highBits = storageSize - offset - size;
if (highBits)
val = builder.createShiftLeft(val, highBits);
if (offset + highBits)
val = builder.createShiftRight(val, offset + highBits);
} else {
if (offset)
val = builder.createShiftRight(val, offset);

if (static_cast<unsigned>(offset) + size < storageSize)
val = builder.createAnd(val, llvm::APInt::getLowBitsSet(valWidth, size));
}
val = builder.createIntCast(val, resultTy);

op.replaceAllUsesWith(val);
op.erase();
}

void LoweringPreparePass::lowerSetBitfieldOp(SetBitfieldOp op) {
CIRBaseBuilderTy builder(getContext());
builder.setInsertionPointAfter(op.getOperation());

auto srcVal = op.getSrc();
auto addr = op.getDst();
auto info = op.getBitfieldInfo();
auto size = info.getSize();
auto storageType = info.getStorageType();
auto storageSize = storageType.cast<IntType>().getWidth();
auto offset = info.getOffset();
auto resultTy = op.getType();
auto loc = addr.getLoc();

// Get the source value, truncated to the width of the bit-field.
srcVal = builder.createIntCast(op.getSrc(), storageType);
auto srcWidth = srcVal.getType().cast<IntType>().getWidth();

mlir::Value maskedVal = srcVal;

if (storageSize != size) {
assert(storageSize > size && "Invalid bitfield size.");

mlir::Value val =
builder.create<mlir::cir::LoadOp>(loc, storageType, addr);

srcVal =
builder.createAnd(srcVal, llvm::APInt::getLowBitsSet(srcWidth, size));

maskedVal = srcVal;
if (offset)
srcVal = builder.createShiftLeft(srcVal, offset);

// Mask out the original value.
val = builder.createAnd(val,
~llvm::APInt::getBitsSet(srcWidth, offset, offset + size));

// Or together the unchanged values and the source value.
srcVal = builder.createOr(val, srcVal);
}

builder.create<mlir::cir::StoreOp>(loc, srcVal, addr);

if (!op->getUses().empty()) {
mlir::Value resultVal = maskedVal;
resultVal = builder.createIntCast(resultVal, resultTy);

if (info.getIsSigned()) {
assert(size <= storageSize);
unsigned highBits = storageSize - size;

if (highBits) {
resultVal = builder.createShiftLeft(resultVal, highBits);
resultVal = builder.createShiftRight(resultVal, highBits);
}
}

op.replaceAllUsesWith(resultVal);
}

op.erase();
}

void LoweringPreparePass::runOnOp(Operation *op) {
if (GlobalOp globalOp = cast<GlobalOp>(op)) {
lowerGlobalOp(globalOp);
return;
if (auto getGlobal = dyn_cast<GlobalOp>(op)) {
lowerGlobalOp(getGlobal);
} else if (auto getBitfield = dyn_cast<GetBitfieldOp>(op)) {
lowerGetBitfieldOp(getBitfield);
} else if (auto setBitfield = dyn_cast<SetBitfieldOp>(op)) {
lowerSetBitfieldOp(setBitfield);
}
}

@@ -315,6 +427,10 @@ void LoweringPreparePass::runOnOperation() {
op->walk([&](Operation *op) {
if (isa<GlobalOp>(op))
opsToTransform.push_back(op);
if (isa<GetBitfieldOp>(op))
opsToTransform.push_back(op);
if (isa<SetBitfieldOp>(op))
opsToTransform.push_back(op);
});

for (auto *o : opsToTransform) {
33 changes: 33 additions & 0 deletions clang/test/CIR/CodeGen/bitfield-ops.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir-enable -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o - 2>&1 | FileCheck %s

// CHECK: !ty_22S22 = !cir.struct<struct "S" {!cir.int<u, 32>, !cir.int<u, 32>, !cir.int<u, 16>, !cir.int<u, 32>} #cir.record.decl.ast>
typedef struct {
int a : 4;
int b : 27;
int c : 17;
int d : 2;
int e : 15;
unsigned f;
} S;

// CHECK: #bfi_d = #cir.bitfield_info<name = "d", storage_type = !u32i, size = 2, offset = 17, is_signed = true>
// CHECK: #bfi_e = #cir.bitfield_info<name = "e", storage_type = !u16i, size = 15, offset = 0, is_signed = true>

// CHECK: cir.func {{.*@store_field}}
// CHECK: [[TMP0:%.*]] = cir.alloca !ty_22S22, cir.ptr <!ty_22S22>, ["s"]
// CHECK: [[TMP1:%.*]] = cir.const(#cir.int<3> : !s32i) : !s32i
// CHECK: [[TMP2:%.*]] = cir.get_member [[TMP0]][2] {name = "e"} : !cir.ptr<!ty_22S22> -> !cir.ptr<!u16i>
// CHECK: [[TMP3:%.*]] = cir.set_bitfield(#bfi_e, [[TMP2]] : !cir.ptr<!u16i>, [[TMP1]] : !s32i) -> !s32i
void store_field() {
S s;
s.e = 3;
}

// CHECK: cir.func {{.*@load_field}}
// CHECK: [[TMP0:%.*]] = cir.alloca !cir.ptr<!ty_22S22>, cir.ptr <!cir.ptr<!ty_22S22>>, ["s", init]
// CHECK: [[TMP1:%.*]] = cir.load [[TMP0]] : cir.ptr <!cir.ptr<!ty_22S22>>, !cir.ptr<!ty_22S22>
// CHECK: [[TMP2:%.*]] = cir.get_member [[TMP1]][1] {name = "d"} : !cir.ptr<!ty_22S22> -> !cir.ptr<!u32i>
// CHECK: [[TMP3:%.*]] = cir.get_bitfield(#bfi_d, [[TMP2]] : !cir.ptr<!u32i>) -> !s32i
int load_field(S* s) {
return s->d;
}