Skip to content
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

[CIR][CodeGen] Const structs with bitfields #412

Merged
merged 21 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 18 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
1 change: 1 addition & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def IntAttr : CIR_Attr<"Int", "int", [TypedAttrInterface]> {
int64_t getSInt() const { return getValue().getSExtValue(); }
uint64_t getUInt() const { return getValue().getZExtValue(); }
bool isNullValue() const { return getValue() == 0; }
uint64_t getBitWidth() const { return getType().cast<IntType>().getWidth(); }
}];
let genVerifyDecl = 1;
let hasCustomAssemblyFormat = 1;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ class StructType
uint64_t getPreferredAlignment(const DataLayout &dataLayout,
DataLayoutEntryListRef params) const;

bool isLayoutIdentical(const StructType &other);

// Utilities for lazily computing and cacheing data layout info.
private:
mutable Type largestMember{};
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CIR/CodeGen/CIRDataLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class CIRDataLayout {
mlir::DataLayout layout;

CIRDataLayout(mlir::ModuleOp modOp);
bool isBigEndian() { return bigEndian; }
bool isBigEndian() const { return bigEndian; }

// `useABI` is `true` if not using prefered alignment.
unsigned getAlignment(mlir::Type ty, bool useABI) const {
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,22 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
return type;
}

mlir::cir::StructType
getCompleteStructType(mlir::ArrayAttr fields, bool packed = false,
llvm::StringRef name = "",
const clang::RecordDecl *ast = nullptr) {
llvm::SmallVector<mlir::Type, 8> members;
for (auto &attr : fields) {
const auto typedAttr = attr.dyn_cast<mlir::TypedAttr>();
members.push_back(typedAttr.getType());
}

if (name.empty())
return getAnonStructTy(members, packed, ast);
else
return getCompleteStructTy(members, name, packed, ast);
}

mlir::cir::ArrayType getArrayType(mlir::Type eltType, unsigned size) {
return mlir::cir::ArrayType::get(getContext(), eltType, size);
}
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -698,8 +698,10 @@ static LValue buildGlobalVarDeclLValue(CIRGenFunction &CGF, const Expr *E,

auto V = CGF.CGM.getAddrOfGlobalVar(VD);
auto RealVarTy = CGF.getTypes().convertTypeForMem(VD->getType());
// TODO(cir): do we need this for CIR?
// V = EmitBitCastOfLValueToProperType(CGF, V, RealVarTy);
auto realPtrTy = CGF.getBuilder().getPointerTo(RealVarTy);
if (realPtrTy != V.getType())
V = CGF.getBuilder().createBitcast(V.getLoc(), V, realPtrTy);

CharUnits Alignment = CGF.getContext().getDeclAlign(VD);
Address Addr(V, RealVarTy, Alignment);
// Emit reference to the private copy of the variable if it is an OpenMP
Expand Down
196 changes: 183 additions & 13 deletions clang/lib/CIR/CodeGen/CIRGenExprConst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ struct ConstantAggregateBuilderUtils {
return getSize(C.getType());
}

mlir::Attribute getPadding(CharUnits PadSize) const {
llvm_unreachable("NYI");
mlir::TypedAttr getPadding(CharUnits size) const {
auto eltTy = CGM.UCharTy;
auto arSize = size.getQuantity();
auto &bld = CGM.getBuilder();
SmallVector<mlir::Attribute, 4> elts(arSize, bld.getZeroAttr(eltTy));
return bld.getConstArray(mlir::ArrayAttr::get(bld.getContext(), elts),
bld.getArrayType(eltTy, arSize));
}

mlir::Attribute getZeroes(CharUnits ZeroSize) const {
Expand Down Expand Up @@ -185,7 +190,111 @@ bool ConstantAggregateBuilder::add(mlir::Attribute A, CharUnits Offset,

bool ConstantAggregateBuilder::addBits(llvm::APInt Bits, uint64_t OffsetInBits,
bool AllowOverwrite) {
llvm_unreachable("NYI");
const ASTContext &Context = CGM.getASTContext();
const uint64_t CharWidth = CGM.getASTContext().getCharWidth();
auto charTy = CGM.getBuilder().getUIntNTy(CharWidth);
// Offset of where we want the first bit to go within the bits of the
// current char.
unsigned OffsetWithinChar = OffsetInBits % CharWidth;

// We split bit-fields up into individual bytes. Walk over the bytes and
// update them.
for (CharUnits OffsetInChars =
Context.toCharUnitsFromBits(OffsetInBits - OffsetWithinChar);
/**/; ++OffsetInChars) {
// Number of bits we want to fill in this char.
unsigned WantedBits =
std::min((uint64_t)Bits.getBitWidth(), CharWidth - OffsetWithinChar);

// Get a char containing the bits we want in the right places. The other
// bits have unspecified values.
llvm::APInt BitsThisChar = Bits;
if (BitsThisChar.getBitWidth() < CharWidth)
BitsThisChar = BitsThisChar.zext(CharWidth);
if (CGM.getDataLayout().isBigEndian()) {
// Figure out how much to shift by. We may need to left-shift if we have
// less than one byte of Bits left.
int Shift = Bits.getBitWidth() - CharWidth + OffsetWithinChar;
if (Shift > 0)
BitsThisChar.lshrInPlace(Shift);
else if (Shift < 0)
BitsThisChar = BitsThisChar.shl(-Shift);
} else {
BitsThisChar = BitsThisChar.shl(OffsetWithinChar);
}
if (BitsThisChar.getBitWidth() > CharWidth)
BitsThisChar = BitsThisChar.trunc(CharWidth);

if (WantedBits == CharWidth) {
// Got a full byte: just add it directly.
add(mlir::cir::IntAttr::get(charTy, BitsThisChar), OffsetInChars,
AllowOverwrite);
} else {
// Partial byte: update the existing integer if there is one. If we
// can't split out a 1-CharUnit range to update, then we can't add
// these bits and fail the entire constant emission.
std::optional<size_t> FirstElemToUpdate = splitAt(OffsetInChars);
if (!FirstElemToUpdate)
return false;
std::optional<size_t> LastElemToUpdate =
splitAt(OffsetInChars + CharUnits::One());
if (!LastElemToUpdate)
return false;
assert(*LastElemToUpdate - *FirstElemToUpdate < 2 &&
"should have at most one element covering one byte");

// Figure out which bits we want and discard the rest.
llvm::APInt UpdateMask(CharWidth, 0);
if (CGM.getDataLayout().isBigEndian())
UpdateMask.setBits(CharWidth - OffsetWithinChar - WantedBits,
CharWidth - OffsetWithinChar);
else
UpdateMask.setBits(OffsetWithinChar, OffsetWithinChar + WantedBits);
BitsThisChar &= UpdateMask;
bool isNull = false;
if (*FirstElemToUpdate < Elems.size()) {
auto firstEltToUpdate =
dyn_cast<mlir::cir::IntAttr>(Elems[*FirstElemToUpdate]);
isNull = firstEltToUpdate && firstEltToUpdate.isNullValue();
}

if (*FirstElemToUpdate == *LastElemToUpdate || isNull) {
// All existing bits are either zero or undef.
add(CGM.getBuilder().getAttr<mlir::cir::IntAttr>(charTy, BitsThisChar),
OffsetInChars, /*AllowOverwrite*/ true);
} else {
mlir::cir::IntAttr CI =
dyn_cast<mlir::cir::IntAttr>(Elems[*FirstElemToUpdate]);
// In order to perform a partial update, we need the existing bitwise
// value, which we can only extract for a constant int.
// auto *CI = dyn_cast<llvm::ConstantInt>(ToUpdate);
if (!CI)
return false;
// Because this is a 1-CharUnit range, the constant occupying it must
// be exactly one CharUnit wide.
assert(CI.getBitWidth() == CharWidth && "splitAt failed");
assert((!(CI.getValue() & UpdateMask) || AllowOverwrite) &&
"unexpectedly overwriting bitfield");
BitsThisChar |= (CI.getValue() & ~UpdateMask);
Elems[*FirstElemToUpdate] =
CGM.getBuilder().getAttr<mlir::cir::IntAttr>(charTy, BitsThisChar);
}
}

// Stop if we've added all the bits.
if (WantedBits == Bits.getBitWidth())
break;

// Remove the consumed bits from Bits.
if (!CGM.getDataLayout().isBigEndian())
Bits.lshrInPlace(WantedBits);
Bits = Bits.trunc(Bits.getBitWidth() - WantedBits);

// The remanining bits go at the start of the following bytes.
OffsetWithinChar = 0;
}

return true;
}

/// Returns a position within Elems and Offsets such that all elements
Expand Down Expand Up @@ -235,6 +344,7 @@ mlir::Attribute ConstantAggregateBuilder::buildFrom(

if (Elems.empty())
return {};
auto Offset = [&](size_t I) { return Offsets[I] - StartOffset; };

// If we want an array type, see if all the elements are the same type and
// appropriately spaced.
Expand Down Expand Up @@ -275,14 +385,44 @@ mlir::Attribute ConstantAggregateBuilder::buildFrom(
// as a non-packed struct and do so opportunistically if possible.
llvm::SmallVector<mlir::Attribute, 32> PackedElems;
if (!NaturalLayout) {
llvm_unreachable("NYI");
CharUnits SizeSoFar = CharUnits::Zero();
for (size_t I = 0; I != Elems.size(); ++I) {
mlir::TypedAttr C = Elems[I].dyn_cast<mlir::TypedAttr>();
assert(C && "expected typed attribute");

CharUnits Align = Utils.getAlignment(C);
CharUnits NaturalOffset = SizeSoFar.alignTo(Align);
CharUnits DesiredOffset = Offset(I);
assert(DesiredOffset >= SizeSoFar && "elements out of order");

if (DesiredOffset != NaturalOffset)
Packed = true;
if (DesiredOffset != SizeSoFar)
PackedElems.push_back(Utils.getPadding(DesiredOffset - SizeSoFar));
PackedElems.push_back(Elems[I]);
SizeSoFar = DesiredOffset + Utils.getSize(C);
}
// If we're using the packed layout, pad it out to the desired size if
// necessary.
if (Packed) {
assert(SizeSoFar <= DesiredSize &&
"requested size is too small for contents");

if (SizeSoFar < DesiredSize)
PackedElems.push_back(Utils.getPadding(DesiredSize - SizeSoFar));
}
}

// TODO(cir): emit a #cir.zero if all elements are null values.
auto &builder = CGM.getBuilder();
auto arrAttr = mlir::ArrayAttr::get(builder.getContext(),
Packed ? PackedElems : UnpackedElems);
return builder.getConstStructOrZeroAttr(arrAttr, Packed, DesiredTy);
auto strType = builder.getCompleteStructType(arrAttr, Packed);

if (auto desired = dyn_cast<mlir::cir::StructType>(DesiredTy))
if (desired.isLayoutIdentical(strType))
strType = desired;

return builder.getConstStructOrZeroAttr(arrAttr, Packed, strType);
}

void ConstantAggregateBuilder::condense(CharUnits Offset,
Expand Down Expand Up @@ -352,7 +492,7 @@ class ConstStructBuilder {
bool AllowOverwrite = false);

bool AppendBitField(const FieldDecl *Field, uint64_t FieldOffset,
mlir::IntegerAttr InitExpr, bool AllowOverwrite = false);
mlir::cir::IntAttr InitExpr, bool AllowOverwrite = false);

bool Build(InitListExpr *ILE, bool AllowOverwrite);
bool Build(const APValue &Val, const RecordDecl *RD, bool IsPrimaryBase,
Expand All @@ -379,9 +519,26 @@ bool ConstStructBuilder::AppendBytes(CharUnits FieldOffsetInChars,

bool ConstStructBuilder::AppendBitField(const FieldDecl *Field,
uint64_t FieldOffset,
mlir::IntegerAttr CI,
mlir::cir::IntAttr CI,
bool AllowOverwrite) {
llvm_unreachable("NYI");
const auto &RL = CGM.getTypes().getCIRGenRecordLayout(Field->getParent());
const auto &Info = RL.getBitFieldInfo(Field);
llvm::APInt FieldValue = CI.getValue();

// Promote the size of FieldValue if necessary
// FIXME: This should never occur, but currently it can because initializer
// constants are cast to bool, and because clang is not enforcing bitfield
// width limits.
if (Info.Size > FieldValue.getBitWidth())
FieldValue = FieldValue.zext(Info.Size);

// Truncate the size of FieldValue to the bit field size.
if (Info.Size < FieldValue.getBitWidth())
FieldValue = FieldValue.trunc(Info.Size);

return Builder.addBits(FieldValue,
CGM.getASTContext().toBits(StartOffset) + FieldOffset,
AllowOverwrite);
}

static bool EmitDesignatedInitUpdater(ConstantEmitter &Emitter,
Expand Down Expand Up @@ -512,7 +669,16 @@ bool ConstStructBuilder::Build(InitListExpr *ILE, bool AllowOverwrite) {
if (Field->hasAttr<NoUniqueAddressAttr>())
AllowOverwrite = true;
} else {
llvm_unreachable("NYI");
// Otherwise we have a bitfield.
if (auto constInt = dyn_cast<mlir::cir::IntAttr>(EltInit)) {
if (!AppendBitField(Field, Layout.getFieldOffset(FieldNo), constInt,
AllowOverwrite))
return false;
} else {
// We are trying to initialize a bitfield with a non-trivial constant,
// this must require run-time code.
return false;
}
}
}

Expand Down Expand Up @@ -983,9 +1149,13 @@ buildArrayConstant(CIRGenModule &CGM, mlir::Type DesiredType,
ArrayBound));
}

// We have mixed types. Use a packed struct.
assert(0 && "NYE");
return {};
SmallVector<mlir::Attribute, 4> Eles;
Eles.reserve(Elements.size());
for (auto const &Element : Elements)
Eles.push_back(Element);

auto arrAttr = mlir::ArrayAttr::get(builder.getContext(), Eles);
return builder.getAnonConstStruct(arrAttr, false);
}

} // end anonymous namespace.
Expand Down
7 changes: 3 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,11 +653,10 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef MangledName, mlir::Type Ty,
// TODO(cir): LLVM codegen makes sure the result is of the correct type
// by issuing a address space cast.

// TODO(cir):
// (In LLVM codgen, if global is requested for a definition, we always need
// to create a new global, otherwise return a bitcast.)
// (If global is requested for a definition, we always need to create a new
// global, not just return a bitcast.)
if (!IsForDefinition)
assert(0 && "not implemented");
return Entry;
}

// TODO(cir): auto DAddrSpace = GetGlobalVarAddressSpace(D);
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,16 @@ void StructType::complete(ArrayRef<Type> members, bool packed,
llvm_unreachable("failed to complete struct");
}

bool StructType::isLayoutIdentical(const StructType &other) {
if (getImpl() == other.getImpl())
return true;

if (getPacked() != other.getPacked())
return false;

return getMembers() == other.getMembers();
}

//===----------------------------------------------------------------------===//
// Data Layout information for types
//===----------------------------------------------------------------------===//
Expand Down
47 changes: 47 additions & 0 deletions clang/test/CIR/CodeGen/const-bitfields.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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

struct T {
int X : 5;
int Y : 6;
int Z : 9;
int W;
};

struct Inner {
unsigned a : 1;
unsigned b : 1;
unsigned c : 1;
unsigned d : 30;
};

// CHECK: !ty_22T22 = !cir.struct<struct "T" {!cir.int<u, 32>, !cir.int<s, 32>} #cir.record.decl.ast>
// CHECK: !ty_anon_struct = !cir.struct<struct {!cir.int<u, 8>, !cir.int<u, 8>, !cir.int<u, 8>, !cir.int<s, 32>}>
// CHECK: #bfi_Z = #cir.bitfield_info<name = "Z", storage_type = !u32i, size = 9, offset = 11, is_signed = true>
// CHECK: !ty_anon_struct1 = !cir.struct<struct {!cir.int<u, 8>, !cir.array<!cir.int<u, 8> x 3>, !cir.int<u, 8>, !cir.int<u, 8>, !cir.int<u, 8>, !cir.int<u, 8>}>

struct T GV = { 1, 5, 256, 42 };
// CHECK: cir.global external @GV = #cir.const_struct<{#cir.int<161> : !u8i, #cir.int<0> : !u8i, #cir.int<8> : !u8i, #cir.int<42> : !s32i}> : !ty_anon_struct

// check padding is used (const array of zeros)
struct Inner var = { 1, 0, 1, 21};
// CHECK: cir.global external @var = #cir.const_struct<{#cir.int<5> : !u8i, #cir.const_array<[#cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i]> : !cir.array<!u8i x 3>, #cir.int<21> : !u8i, #cir.int<0> : !u8i, #cir.int<0> : !u8i, #cir.int<0> : !u8i}> : !ty_anon_struct1


// CHECK: cir.func {{.*@getZ()}}
// CHECK: %1 = cir.get_global @GV : cir.ptr <!ty_anon_struct>
// CHECK: %2 = cir.cast(bitcast, %1 : !cir.ptr<!ty_anon_struct>), !cir.ptr<!ty_22T22>
// CHECK: %3 = cir.cast(bitcast, %2 : !cir.ptr<!ty_22T22>), !cir.ptr<!u32i>
// CHECK: %4 = cir.get_bitfield(#bfi_Z, %3 : !cir.ptr<!u32i>) -> !s32i
int getZ() {
return GV.Z;
}

// check the type used is the type of T struct for plain field
// CHECK: cir.func {{.*@getW()}}
// CHECK: %1 = cir.get_global @GV : cir.ptr <!ty_anon_struct>
// CHECK: %2 = cir.cast(bitcast, %1 : !cir.ptr<!ty_anon_struct>), !cir.ptr<!ty_22T22>
// CHECK: %3 = cir.get_member %2[1] {name = "W"} : !cir.ptr<!ty_22T22> -> !cir.ptr<!s32i>
int getW() {
return GV.W;
}

Loading