From 3cd2c5686fbf7737c01ed734174f588e777f91cd Mon Sep 17 00:00:00 2001 From: gitoleg Date: Thu, 30 Nov 2023 21:30:37 +0300 Subject: [PATCH] [CIR][CodeGen] Bitfield operations (#279) As we discussed in #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? --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 27 ++++ clang/include/clang/CIR/Dialect/IR/CIROps.td | 148 +++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenBuilder.h | 21 +++ clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 147 +++++------------ clang/lib/CIR/CodeGen/CIRGenRecordLayout.h | 3 + .../CIR/CodeGen/CIRRecordLayoutBuilder.cpp | 3 +- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 4 + .../Dialect/Transforms/LoweringPrepare.cpp | 150 ++++++++++++++++-- clang/test/CIR/CodeGen/bitfield-ops.c | 33 ++++ 9 files changed, 407 insertions(+), 129 deletions(-) create mode 100644 clang/test/CIR/CodeGen/bitfield-ops.c diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index c3e551f08632..f385051d254b 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -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 diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index d41ea3ec1178..f63904f3e6b4 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -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, !cir.int, !cir.int} #cir.record.decl.ast> + #bfi_d = #cir.bitfield_info + + %1 = cir.const(#cir.int<3> : !s32i) : !s32i + %2 = cir.load %0 : cir.ptr >, !cir.ptr + %3 = cir.get_member %2[1] {name = "d"} : !cir.ptr -> !cir.ptr + %4 = cir.set_bitfield(#bfi_d, %3 : !cir.ptr, %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, !cir.int, !cir.int} #cir.record.decl.ast> + #bfi_d = #cir.bitfield_info + + %2 = cir.load %0 : cir.ptr >, !cir.ptr + %3 = cir.get_member %2[1] {name = "d"} : !cir.ptr -> !cir.ptr + %4 = cir.get_bitfield(#bfi_d, %3 : !cir.ptr) -> !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 //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h index 9ef98bc88754..f3c896edbdd4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h +++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h @@ -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(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( + 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, diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 5763806a78ce..a6a0a67a01d9 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -222,14 +222,14 @@ static bool isAAPCS(const TargetInfo &TargetInfo) { return TargetInfo.getABI().starts_with("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 = @@ -268,7 +268,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); @@ -278,7 +277,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); @@ -400,7 +398,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); @@ -609,42 +607,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().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(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(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) { @@ -669,79 +645,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().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, @@ -2451,9 +2376,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); } diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h index 0a686181db61..fc198776511e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h +++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h @@ -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() {} diff --git a/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp index 5bedcf5b221c..effad38412a5 100644 --- a/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp +++ b/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp @@ -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; diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index fa91f7c46978..8f031d611712 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -77,6 +77,10 @@ struct CIROpAsmDialectInterface : public OpAsmDialectInterface { os << (boolAttr.getValue() ? "true" : "false"); return AliasResult::FinalAlias; } + if (auto bitfield = attr.dyn_cast()) { + os << "bfi_" << bitfield.getName().str(); + return AliasResult::FinalAlias; + } return AliasResult::NoAlias; } diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 8ec0d76226fb..92a7137e8e40 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -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(callOp.getCallableForCallee()); if (!sym) return nullptr; - return dyn_cast_or_null( + return dyn_cast_or_null( SymbolTable::lookupNearestSymbolFrom(callOp, sym)); } namespace { + struct LoweringPreparePass : public LoweringPrepareBase { 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 { }; } // 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(SymbolTable::lookupNearestSymbolFrom( + GlobalOp g = + dyn_cast_or_null(SymbolTable::lookupNearestSymbolFrom( theModule, StringAttr::get(theModule->getContext(), name))); if (!g) { g = builder.create(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(SymbolTable::lookupNearestSymbolFrom( + FuncOp f = + dyn_cast_or_null(SymbolTable::lookupNearestSymbolFrom( theModule, StringAttr::get(theModule->getContext(), name))); if (!f) { f = builder.create(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(*dtorFunc.getAst()) && "Expected a dtor call"); @@ -297,10 +302,117 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { builder.create(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().getWidth(); + auto offset = info.getOffset(); + auto resultTy = op.getType(); + auto addr = op.getAddr(); + auto loc = addr.getLoc(); + mlir::Value val = + builder.create(loc, storageType, op.getAddr()); + auto valWidth = val.getType().cast().getWidth(); + + if (info.getIsSigned()) { + assert(static_cast(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(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().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().getWidth(); + + mlir::Value maskedVal = srcVal; + + if (storageSize != size) { + assert(storageSize > size && "Invalid bitfield size."); + + mlir::Value val = + builder.create(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(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(op)) { - lowerGlobalOp(globalOp); - return; + if (auto getGlobal = dyn_cast(op)) { + lowerGlobalOp(getGlobal); + } else if (auto getBitfield = dyn_cast(op)) { + lowerGetBitfieldOp(getBitfield); + } else if (auto setBitfield = dyn_cast(op)) { + lowerSetBitfieldOp(setBitfield); } } @@ -315,6 +427,10 @@ void LoweringPreparePass::runOnOperation() { op->walk([&](Operation *op) { if (isa(op)) opsToTransform.push_back(op); + if (isa(op)) + opsToTransform.push_back(op); + if (isa(op)) + opsToTransform.push_back(op); }); for (auto *o : opsToTransform) { diff --git a/clang/test/CIR/CodeGen/bitfield-ops.c b/clang/test/CIR/CodeGen/bitfield-ops.c new file mode 100644 index 000000000000..5fb5f6824c3e --- /dev/null +++ b/clang/test/CIR/CodeGen/bitfield-ops.c @@ -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, !cir.int, !cir.int, !cir.int} #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 +// CHECK: #bfi_e = #cir.bitfield_info + +// CHECK: cir.func {{.*@store_field}} +// CHECK: [[TMP0:%.*]] = cir.alloca !ty_22S22, cir.ptr , ["s"] +// CHECK: [[TMP1:%.*]] = cir.const(#cir.int<3> : !s32i) : !s32i +// CHECK: [[TMP2:%.*]] = cir.get_member [[TMP0]][2] {name = "e"} : !cir.ptr -> !cir.ptr +// CHECK: [[TMP3:%.*]] = cir.set_bitfield(#bfi_e, [[TMP2]] : !cir.ptr, [[TMP1]] : !s32i) -> !s32i +void store_field() { + S s; + s.e = 3; +} + +// CHECK: cir.func {{.*@load_field}} +// CHECK: [[TMP0:%.*]] = cir.alloca !cir.ptr, cir.ptr >, ["s", init] +// CHECK: [[TMP1:%.*]] = cir.load [[TMP0]] : cir.ptr >, !cir.ptr +// CHECK: [[TMP2:%.*]] = cir.get_member [[TMP1]][1] {name = "d"} : !cir.ptr -> !cir.ptr +// CHECK: [[TMP3:%.*]] = cir.get_bitfield(#bfi_d, [[TMP2]] : !cir.ptr) -> !s32i +int load_field(S* s) { + return s->d; +} \ No newline at end of file