Author: Adam Smith Date: 2026-08-19T21:58:18Z New Revision: 935bfc708590c60147a79c7df145bb6e68b1d388
URL: https://github.com/llvm/llvm-project/commit/935bfc708590c60147a79c7df145bb6e68b1d388 DIFF: https://github.com/llvm/llvm-project/commit/935bfc708590c60147a79c7df145bb6e68b1d388.diff LOG: [CIR] Mark bit-field access units as their own member kinds (#216864) A bit-field access unit is only as wide as the compiler needs it to be, which can be narrower than the type the bit-fields were declared with. Nothing in the record type says so. The unit just gets `data`, or `empty` when none of its bit-fields are named, and an ordinary field gets those same marks. That makes `struct { long long x : 32; }` and `struct { unsigned x; }` look identical here. Classic CodeGen tells them apart and coerces the first to `i64`, the second to `i32`. It reads the declared type, where the bit-field still leaves user data past bit 32. The fix is to give a unit its own marks, `bitfield` and `empty_bitfield`. Nothing reads them yet, and no record's emptiness answer changes. The next PR records a zero-width bit-field, which the marks miss too, and pad-aware classification can then use both. Assisted-by: Cursor / claude-opus-5 Added: Modified: clang/include/clang/CIR/Dialect/IR/CIRTypes.h clang/include/clang/CIR/Dialect/IR/CIRTypes.td clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp clang/lib/CIR/Dialect/IR/CIRTypes.cpp clang/test/CIR/CodeGen/bitfield-union.c clang/test/CIR/CodeGen/bitfields.c clang/test/CIR/CodeGen/bitfields.cpp clang/test/CIR/CodeGen/bitfields_be.c clang/test/CIR/CodeGen/dumb-record.cpp clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp clang/test/CIR/CodeGen/mms-bitfields.c clang/test/CIR/CodeGen/no-unique-address.cpp clang/test/CIR/CodeGen/record-member-kinds.c clang/test/CIR/CodeGen/record-member-kinds.cpp clang/test/CIR/CodeGen/record-zero-init-padding.c clang/test/CIR/IR/bitfield_info.cir clang/test/CIR/IR/invalid-record-member-kinds.cir clang/test/CIR/IR/struct.cir clang/unittests/CIR/RecordMemberKindTest.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h index 9b5b3957f6453..cc1cdfe2cbe52 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h @@ -38,6 +38,22 @@ struct UnionTypeStorage; bool isValidFundamentalIntWidth(unsigned width); +/// Whether a member of this kind holds data for argument passing. +inline bool holdsDataForABI(RecordMemberKind kind) { + return kind == RecordMemberKind::Data || kind == RecordMemberKind::BitField; +} + +/// Whether a member of this kind is a bit-field access unit holding data. The +/// compiler chooses an access unit's width, so the member can be narrower than +/// the declared type of the bit-fields it holds. A true answer does not mean +/// the member holds a bit-field: a union's base subobject takes this mark when +/// any variant is an access unit, whatever its own storage type came from. A +/// unit holding only unnamed bit-fields is `empty` instead, and is told apart +/// from the rest of `empty` by occupying bytes. +inline bool isBitFieldAccessUnit(RecordMemberKind kind) { + return kind == RecordMemberKind::BitField; +} + /// Returns true if the type is a CIR sized type. /// /// Types are sized if they implement SizedTypeInterface and diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td index 2d8d7abe9b404..acbd6ad071d63 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td @@ -624,29 +624,45 @@ def CIR_VoidType : CIR_Type<"Void", "void"> { //===----------------------------------------------------------------------===// // RecordMemberKind // -// What a record member holds, for members that do not hold source data. +// What a record member holds, and whether its extent is a declared one. //===----------------------------------------------------------------------===// def CIR_RecordMemberKind : CIR_I32EnumAttr< "RecordMemberKind", "what a record member holds", [ I32EnumAttrCase<"Data", 0, "data">, I32EnumAttrCase<"Pad", 1, "pad">, - I32EnumAttrCase<"Empty", 2, "empty"> + I32EnumAttrCase<"Empty", 2, "empty">, + I32EnumAttrCase<"BitField", 3, "bitfield"> ]> { let description = [{ - Distinguishes a record member that holds source data from one that does - not. `pad` is storage the compiler inserted to place a later member at its + `pad` is storage the compiler inserted to place a later member at its required offset, and is reusable tail padding when it trails the record. - `empty` is storage the source declared that carries no data for argument - passing: an unnamed bit-field unit, or a field of a record that is empty for - the ABI. Everything else, including a vtable pointer, a base subobject, and - a bit-field unit with a named occupant, is `data`. - - A record is empty for the ABI when no member is `data`, which is vacuously - true for a record with no members. The distinction between `pad` and - `empty` is load-bearing beyond that: only `pad` is reusable, so a record - whose trailing member is an unnamed bit-field unit keeps that unit in its - data size. + + `data` and `empty` both span something whose extent the source fixed: a + field, a vtable pointer, or a base subobject. They diff er only on whether + it holds data for argument passing. + + `bitfield` is a bit-field access unit holding at least one named + bit-field. A unit can be narrower than the declared type of the bit-fields + it holds, so its extent does not answer what the source declared. Without + it, a unit holding a 32-bit bit-field of `long long` would be + indistinguishable from an `unsigned` field, and clang's x86-64 lowering + coerces two records that diff er only in that to `i64` and `i32`. + + An access unit holding only unnamed bit-fields is `empty`, since it carries + no data for argument passing. A consumer that has to recognize one can: + among `empty` members only an access unit occupies bytes, everything else + the source declared as empty for the ABI occupies none. + + A union's base subobject is the one exception to all of this: a single + member stands in for every variant, and it takes a unit mark as soon as any + variant is one, whatever its own storage type came from. + + A record is empty for the ABI when no member is `data` or `bitfield`, which + is vacuously true for a record with no members. The distinction between + `pad` and the rest is load-bearing beyond that: only `pad` is reusable, so a + record whose trailing member is an unnamed bit-field unit keeps that unit in + its data size. }]; let genSpecializedAttr = 0; @@ -700,6 +716,7 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ !rec_packed = !cir.struct<"p1" packed {data !u8i, data !u8i}> !rec_pad = !cir.struct<"p3" {data !u8i, pad !cir.array<!u8i x 3>}> !rec_empty = !cir.struct<"e" {empty !u8i}> + !rec_bits = !cir.struct<"b" {bitfield !u8i, empty !u8i}> !recursive = !cir.struct<"Node" {data !cir.ptr<!cir.struct<"Node">>}> ``` }]; @@ -805,8 +822,8 @@ def CIR_StructType : CIR_Type<"Struct", "struct", [ bool isSized() const { return isComplete(); } /// Returns the data size for this struct type. Tail padding is the - /// trailing run of pad members, so interior padding and a trailing empty - /// member stay inside the data size. + /// trailing run of pad members, so interior padding and a trailing member + /// of any other kind stay inside the data size. unsigned computeStructDataSize(const mlir::DataLayout &dataLayout) const; private: @@ -855,6 +872,7 @@ def CIR_UnionType : CIR_Type<"Union", "union", [ !u_anonymous = !cir.union<{data !s32i, data !u8i}> !u_padded = !cir.union<"U" {data !s32i, data !u8i}, padding = {!u8i}> !u_empty = !cir.union<"U" {empty !u8i}> + !u_bits = !cir.union<"U" {bitfield !u8i, empty !u8i}> ``` }]; diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp index 1ce70091d9f4b..dc06fa90f9d14 100644 --- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp @@ -148,10 +148,23 @@ struct CIRRecordLowering final { return cirGenTypes.isZeroInitializable(rd); } + /// The mark for a member, given whether it holds data for argument passing + /// and whether it is a bit-field access unit. A run of bit-fields is + /// allocated as a single storage type, and that storage is the access unit, + /// so its width is ours to choose and can be narrower than the declared type + /// of the bit-fields it holds. + static cir::RecordMemberKind makeMemberKind(bool holdsData, + bool isBitFieldAccessUnit) { + if (!holdsData) + return cir::RecordMemberKind::Empty; + return isBitFieldAccessUnit ? cir::RecordMemberKind::BitField + : cir::RecordMemberKind::Data; + } + /// The mark for a field. cir::RecordMemberKind getFieldMemberKind(const FieldDecl *fd) { - return isEmptyFieldForABI(astContext, fd) ? cir::RecordMemberKind::Empty - : cir::RecordMemberKind::Data; + return makeMemberKind(/*holdsData=*/!isEmptyFieldForABI(astContext, fd), + /*isBitFieldAccessUnit=*/fd->isBitField()); } /// The mark for a base subobject. A base contributes no ABI data when it is @@ -422,7 +435,7 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field, assert(members[storageIdx].offset == bitsToCharUnits(startBitOffset) && "storageIdx must name the current run's storage"); if (!field->isUnnamedBitField()) - members[storageIdx].memberKind = cir::RecordMemberKind::Data; + members[storageIdx].memberKind = cir::RecordMemberKind::BitField; // Bitfields get the offset of their storage but come afterward and remain // there after a stable sort. members.push_back(MemberInfo(bitsToCharUnits(startBitOffset), @@ -607,7 +620,7 @@ CIRRecordLowering::accumulateBitFields(RecordDecl::field_iterator field, makeStorageInfo(beginOffset, type, cir::RecordMemberKind::Empty)); for (; begin != bestEnd; ++begin) { if (!begin->isUnnamedBitField()) - members[storageIdx].memberKind = cir::RecordMemberKind::Data; + members[storageIdx].memberKind = cir::RecordMemberKind::BitField; if (!begin->isZeroLengthBitField()) members.push_back(MemberInfo(beginOffset, MemberInfo::InfoKind::Field, nullptr, @@ -948,9 +961,7 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) { } fieldIdxMap[field->getCanonicalDecl()] = 0; - addField(fieldType, isEmptyFieldForABI(astContext, field) - ? cir::RecordMemberKind::Empty - : cir::RecordMemberKind::Data); + addField(fieldType, getFieldMemberKind(field)); } // Compute zero-initializable status. @@ -994,12 +1005,15 @@ void CIRRecordLowering::lowerUnion(bool nonVirtualBaseType) { // the storage type and any trailing padding as ordinary fields rather than // routing padding through the union's single tail-padding slot. if (nonVirtualBaseType) { - // One member stands in for every variant, so it holds data unless no - // variant does. Computed before clearFields() drops the variant marks. - const cir::RecordMemberKind storageKind = - llvm::is_contained(getFieldKinds(), cir::RecordMemberKind::Data) - ? cir::RecordMemberKind::Data - : cir::RecordMemberKind::Empty; + // A unit mark here says the stand-in's extent is not a declared extent, not + // that its storage came from a bit-field: UnionBitAndWide in + // clang/test/CIR/CodeGen/no-unique-address.cpp takes its double as storage + // and still marks bitfield. Computed before clearFields() drops the + // variant marks. + const cir::RecordMemberKind storageKind = makeMemberKind( + /*holdsData=*/llvm::any_of(getFieldKinds(), cir::holdsDataForABI), + /*isBitFieldAccessUnit=*/llvm::any_of(getFieldKinds(), + cir::isBitFieldAccessUnit)); clearFields(); addField(storageType, storageKind); CharUnits padding = layoutSize - getSize(storageType); diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp index 4a26f970b5bc5..5ee0e493ef918 100644 --- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -172,7 +172,8 @@ verifyRecordMemberKinds(function_ref<mlir::InFlightDiagnostic()> emitError, /// The keywords that spell a member kind. A union's tail-padding slot probes /// for one of these to reject it, since that slot is not a member. -static const llvm::StringRef memberKindMarks[] = {"data", "pad", "empty"}; +static const llvm::StringRef memberKindMarks[] = {"data", "pad", "empty", + "bitfield"}; static std::optional<RecordMemberKind> parseMemberKind(mlir::AsmParser &parser) { @@ -685,9 +686,7 @@ bool RecordType::isEmptyForABI() const { // holding no data. if (isIncomplete()) return false; - return llvm::none_of(getMemberKinds(), [](RecordMemberKind kind) { - return kind == RecordMemberKind::Data; - }); + return llvm::none_of(getMemberKinds(), holdsDataForABI); } //===----------------------------------------------------------------------===// @@ -868,9 +867,9 @@ unsigned StructType::computeStructDataSize(const mlir::DataLayout &dataLayout) const { assert(isComplete() && "Cannot get layout of incomplete records"); - // Tail padding is the trailing run of pad members. An empty member stays - // inside the data size: it is storage the source declared, which a derived - // class may not reuse. + // Tail padding is the trailing run of pad members. A member of any other + // kind stays inside the data size, only pad being reusable by a derived + // class. llvm::ArrayRef<mlir::Type> members = getMembers(); llvm::ArrayRef<RecordMemberKind> kinds = getMemberKinds(); assert(kinds.size() == members.size() && diff --git a/clang/test/CIR/CodeGen/bitfield-union.c b/clang/test/CIR/CodeGen/bitfield-union.c index 2dcc81295af90..70b3a669041c0 100644 --- a/clang/test/CIR/CodeGen/bitfield-union.c +++ b/clang/test/CIR/CodeGen/bitfield-union.c @@ -11,7 +11,7 @@ typedef union { int z : 8; } demo; -// CIR: !rec_demo = !cir.union<"demo" {data !s32i, data !u8i, data !u8i}> +// CIR: !rec_demo = !cir.union<"demo" {data !s32i, bitfield !u8i, bitfield !u8i}> // LLVM: %union.demo = type { i32 } // OGCG: %union.demo = type { i32 } @@ -22,7 +22,7 @@ typedef union { int z : 2; } zero_bit; -// CIR: !rec_zero_bit = !cir.union<"zero_bit" {data !s32i, data !u8i, data !u8i}> +// CIR: !rec_zero_bit = !cir.union<"zero_bit" {data !s32i, bitfield !u8i, bitfield !u8i}> // LLVM: %union.zero_bit = type { i32 } // OGCG: %union.zero_bit = type { i32 } diff --git a/clang/test/CIR/CodeGen/bitfields.c b/clang/test/CIR/CodeGen/bitfields.c index 2ed9c6147212b..d4bd980a057d2 100644 --- a/clang/test/CIR/CodeGen/bitfields.c +++ b/clang/test/CIR/CodeGen/bitfields.c @@ -12,7 +12,7 @@ typedef struct { unsigned still_more_bits : 7; } A; -// CIR-DAG: !rec_A = !cir.struct<"A" packed {data !s8i, data !s8i, data !s8i, data !u16i, pad !cir.array<!u8i x 3>}> +// CIR-DAG: !rec_A = !cir.struct<"A" packed {data !s8i, data !s8i, data !s8i, bitfield !u16i, pad !cir.array<!u8i x 3>}> // CIR-DAG: #bfi_more_bits = #cir.bitfield_info<name = "more_bits", storage_type = !u16i, size = 4, offset = 3, is_signed = false> // LLVM-DAG: %struct.A = type <{ i8, i8, i8, i16, [3 x i8] }> // OGCG-DAG: %struct.A = type <{ i8, i8, i8, i16, [3 x i8] }> @@ -23,7 +23,7 @@ typedef struct { int c; } D; -// CIR-DAG: !rec_D = !cir.struct<"D" {data !u16i, data !s32i}> +// CIR-DAG: !rec_D = !cir.struct<"D" {bitfield !u16i, data !s32i}> // LLVM-DAG: %struct.D = type { i16, i32 } // OGCG-DAG: %struct.D = type { i16, i32 } @@ -36,7 +36,7 @@ typedef struct { unsigned f; // type other than int above, not a bitfield } S; // CIR-DAG: #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u64i, size = 17, offset = 32, is_signed = true> -// CIR-DAG: !rec_S = !cir.struct<"S" {data !u64i, data !u16i, data !u32i}> +// CIR-DAG: !rec_S = !cir.struct<"S" {bitfield !u64i, bitfield !u16i, data !u32i}> // LLVM-DAG: %struct.S = type { i64, i16, i32 } // OGCG-DAG: %struct.S = type { i64, i16, i32 } @@ -45,7 +45,7 @@ typedef struct { unsigned b; } T; -// CIR-DAG: !rec_T = !cir.struct<"T" {data !u8i, data !u32i}> +// CIR-DAG: !rec_T = !cir.struct<"T" {bitfield !u8i, data !u32i}> // LLVM-DAG: %struct.T = type { i8, i32 } // OGCG-DAG: %struct.T = type { i8, i32 } @@ -67,7 +67,7 @@ typedef struct { int l: 14; } U; -// CIR-DAG: !rec_U = !cir.struct<"U" packed {data !s8i, data !s8i, data !s8i, data !u8i, data !u64i}> +// CIR-DAG: !rec_U = !cir.struct<"U" packed {data !s8i, data !s8i, data !s8i, bitfield !u8i, bitfield !u64i}> // LLVM-DAG: %struct.U = type <{ i8, i8, i8, i8, i64 }> // OGCG-DAG: %struct.U = type <{ i8, i8, i8, i8, i64 }> @@ -77,7 +77,7 @@ typedef struct{ int c: 30; } Clip; -// CIR-DAG: !rec_Clip = !cir.struct<"Clip" {data !cir.array<!u8i x 3>, data !s8i, data !u32i}> +// CIR-DAG: !rec_Clip = !cir.struct<"Clip" {bitfield !cir.array<!u8i x 3>, data !s8i, bitfield !u32i}> // LLVM-DAG: %struct.Clip = type { [3 x i8], i8, i32 } // OGCG-DAG: %struct.Clip = type { [3 x i8], i8, i32 } diff --git a/clang/test/CIR/CodeGen/bitfields.cpp b/clang/test/CIR/CodeGen/bitfields.cpp index 7dce9bf29cd4a..8aec3cf99a66a 100644 --- a/clang/test/CIR/CodeGen/bitfields.cpp +++ b/clang/test/CIR/CodeGen/bitfields.cpp @@ -15,7 +15,7 @@ typedef struct { int e : 15; unsigned f; // type other than int above, not a bitfield } S; -// CIR-DAG: !rec_S = !cir.struct<"S" {data !u64i, data !u16i, data !u32i}> +// CIR-DAG: !rec_S = !cir.struct<"S" {bitfield !u64i, bitfield !u16i, data !u32i}> // CIR-DAG: #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u64i, size = 17, offset = 32, is_signed = true> // LLVM-DAG: %struct.S = type { i64, i16, i32 } // OGCG-DAG: %struct.S = type { i64, i16, i32 } @@ -25,7 +25,7 @@ typedef struct { unsigned b; } T; -// CIR-DAG: !rec_T = !cir.struct<"T" {data !u8i, data !u32i}> +// CIR-DAG: !rec_T = !cir.struct<"T" {bitfield !u8i, data !u32i}> // LLVM-DAG: %struct.T = type { i8, i32 } // OGCG-DAG: %struct.T = type { i8, i32 } diff --git a/clang/test/CIR/CodeGen/bitfields_be.c b/clang/test/CIR/CodeGen/bitfields_be.c index 3a40a65a26192..0ed234a362068 100644 --- a/clang/test/CIR/CodeGen/bitfields_be.c +++ b/clang/test/CIR/CodeGen/bitfields_be.c @@ -11,7 +11,7 @@ typedef struct { int c : 17; } S; -// CIR: !rec_S = !cir.struct<"S" {data !u32i}> +// CIR: !rec_S = !cir.struct<"S" {bitfield !u32i}> // LLVM: %struct.S = type { i32 } // OGCG: %struct.S = type { i32 } void def() { diff --git a/clang/test/CIR/CodeGen/dumb-record.cpp b/clang/test/CIR/CodeGen/dumb-record.cpp index 10870124bd019..0d8c898415108 100644 --- a/clang/test/CIR/CodeGen/dumb-record.cpp +++ b/clang/test/CIR/CodeGen/dumb-record.cpp @@ -30,8 +30,8 @@ struct BitfieldsInOrder { } bitfield_order; // CHECK: Layout: <CIRecordLayout -// CHECK: CIR Type:!cir.struct<"BitfieldsInOrder" {data !cir.int<s, 8>, data !cir.int<u, 8>, data !cir.int<u, 32>}> -// CHECK: NonVirtualBaseCIRType:!cir.struct<"BitfieldsInOrder" {data !cir.int<s, 8>, data !cir.int<u, 8>, data !cir.int<u, 32>}> +// CHECK: CIR Type:!cir.struct<"BitfieldsInOrder" {data !cir.int<s, 8>, bitfield !cir.int<u, 8>, bitfield !cir.int<u, 32>}> +// CHECK: NonVirtualBaseCIRType:!cir.struct<"BitfieldsInOrder" {data !cir.int<s, 8>, bitfield !cir.int<u, 8>, bitfield !cir.int<u, 32>}> // CHECK: IsZeroInitializable:1 // CHECK: BitFields:[ // CHECK-NEXT: <CIRBitFieldInfo name:bit offset:0 size:8 isSigned:0 storageSize:8 storageOffset:1 volatileOffset:0 volatileStorageSize:0 volatileStorageOffset:0> diff --git a/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp b/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp index db97c97342ec8..7b9db8fd5358d 100644 --- a/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp +++ b/clang/test/CIR/CodeGen/finegrain-bitfield-access.cpp @@ -13,7 +13,7 @@ struct S1 { unsigned f5:8; }; -// CIR-DAG: !rec_S1 = !cir.struct<"S1" {data !u8i, data !u8i, data !u16i}> +// CIR-DAG: !rec_S1 = !cir.struct<"S1" {bitfield !u8i, bitfield !u8i, bitfield !u16i}> // LLVM-DAG: %struct.S1 = type { i8, i8, i16 } // OGCG-DAG: %struct.S1 = type { i8, i8, i16 } @@ -23,7 +23,7 @@ struct S2 { unsigned long f3:6; }; -// CIR-DAG: !rec_S2 = !cir.struct<"S2" {data !u16i, data !u16i, data !u8i, pad !cir.array<!u8i x 3>}> +// CIR-DAG: !rec_S2 = !cir.struct<"S2" {bitfield !u16i, bitfield !u16i, bitfield !u8i, pad !cir.array<!u8i x 3>}> // LLVM-DAG: %struct.S2 = type { i16, i16, i8, [3 x i8] } // OGCG-DAG: %struct.S2 = type { i16, i16, i8, [3 x i8] } @@ -33,7 +33,7 @@ struct S3 { unsigned long f3:32; }; -// CIR-DAG: !rec_S3 = !cir.struct<"S3" {data !u32i, data !u32i}> +// CIR-DAG: !rec_S3 = !cir.struct<"S3" {bitfield !u32i, bitfield !u32i}> // LLVM-DAG: %struct.S3 = type { i32, i32 } // OGCG-DAG: %struct.S3 = type { i32, i32 } diff --git a/clang/test/CIR/CodeGen/mms-bitfields.c b/clang/test/CIR/CodeGen/mms-bitfields.c index 861164f4beeba..f9832f55e5405 100644 --- a/clang/test/CIR/CodeGen/mms-bitfields.c +++ b/clang/test/CIR/CodeGen/mms-bitfields.c @@ -10,7 +10,7 @@ struct s1 { long long f64 : 30; } s1; -// CIR-DAG: !rec_s1 = !cir.struct<"s1" {data !s32i, data !s64i}> +// CIR-DAG: !rec_s1 = !cir.struct<"s1" {bitfield !s32i, bitfield !s64i}> // LLVM-DAG: %struct.s1 = type { i32, i64 } // OGCG-DAG: %struct.s1 = type { i32, i64 } @@ -20,7 +20,7 @@ struct s2 { int c : 30; } Clip; -// CIR-DAG: !rec_s2 = !cir.struct<"s2" {data !s32i, data !s8i, data !s32i}> +// CIR-DAG: !rec_s2 = !cir.struct<"s2" {bitfield !s32i, data !s8i, bitfield !s32i}> // LLVM-DAG: %struct.s2 = type { i32, i8, i32 } // OGCG-DAG: %struct.s2 = type { i32, i8, i32 } @@ -30,7 +30,7 @@ struct s3 { int c : 14; } zero_bit; -// CIR-DAG: !rec_s3 = !cir.struct<"s3" {data !s32i, data !s32i}> +// CIR-DAG: !rec_s3 = !cir.struct<"s3" {bitfield !s32i, bitfield !s32i}> // LLVM-DAG: %struct.s3 = type { i32, i32 } // OGCG-DAG: %struct.s3 = type { i32, i32 } @@ -45,7 +45,7 @@ struct Inner { #pragma pack (pop) -// CIR-DAG: !rec_Inner = !cir.struct<"Inner" {data !u32i, data !u32i}> +// CIR-DAG: !rec_Inner = !cir.struct<"Inner" {bitfield !u32i, bitfield !u32i}> // LLVM-DAG: %struct.Inner = type { i32, i32 } // OGCG-DAG: %struct.Inner = type { i32, i32 } @@ -67,7 +67,7 @@ union HEADER { #pragma pack(pop) -// CIR-DAG: !rec_A = !cir.struct<"A" {data !s32i, data !s32i, data !s32i}> +// CIR-DAG: !rec_A = !cir.struct<"A" {bitfield !s32i, bitfield !s32i, bitfield !s32i}> // CIR-DAG: !rec_HEADER = !cir.union<"HEADER" {data !rec_A}> // LLVM-DAG: %struct.A = type { i32, i32, i32 } // LLVM-DAG: %union.HEADER = type { %struct.A } diff --git a/clang/test/CIR/CodeGen/no-unique-address.cpp b/clang/test/CIR/CodeGen/no-unique-address.cpp index 54514d5f18b20..b2d1f845fdc48 100644 --- a/clang/test/CIR/CodeGen/no-unique-address.cpp +++ b/clang/test/CIR/CodeGen/no-unique-address.cpp @@ -62,6 +62,12 @@ struct Outer { // LLVM-DAG: @ozd = {{(dso_local )?}}global %struct.OuterZeroData zeroinitializer, align 4 // LLVM-DAG: %struct.OuterAllEmpty = type { i8 } // LLVM-DAG: @oae = {{(dso_local )?}}global %struct.OuterAllEmpty zeroinitializer, align 1 +// LLVM-DAG: %struct.OuterUnionBitPad = type { %struct.UnionBitAndWide.base, i8, [6 x i8] } +// LLVM-DAG: %struct.UnionBitAndWide.base = type <{ double, i8 }> +// LLVM-DAG: %struct.OuterAllEmptyBits = type { %struct.UnionAllEmptyBits.base, i8, [4 x i8] } +// LLVM-DAG: %struct.UnionAllEmptyBits.base = type { [3 x i8] } +// LLVM-DAG: @oubp = {{(dso_local )?}}global %struct.OuterUnionBitPad zeroinitializer, align 8 +// LLVM-DAG: @oaeb = {{(dso_local )?}}global %struct.OuterAllEmptyBits zeroinitializer, align 8 // OGCG-DAG: %struct.OuterUnion = type { %union.UnionForNUA, i32 } // OGCG-DAG: %union.UnionForNUA = type { i64 } // OGCG-DAG: %struct.OuterFinal = type { %struct.FinalForNUA, i8 } @@ -82,6 +88,12 @@ struct Outer { // OGCG-DAG: @ozd = {{(dso_local )?}}global %struct.OuterZeroData zeroinitializer, align 4 // OGCG-DAG: %struct.OuterAllEmpty = type { i8 } // OGCG-DAG: @oae = {{(dso_local )?}}global %struct.OuterAllEmpty zeroinitializer, align 1 +// OGCG-DAG: %struct.OuterUnionBitPad = type { %union.UnionBitAndWide.base, i8, [6 x i8] } +// OGCG-DAG: %union.UnionBitAndWide.base = type <{ double, i8 }> +// OGCG-DAG: %struct.OuterAllEmptyBits = type { %union.UnionAllEmptyBits.base, i8, [4 x i8] } +// OGCG-DAG: %union.UnionAllEmptyBits.base = type { [3 x i8] } +// OGCG-DAG: @oubp = {{(dso_local )?}}global %struct.OuterUnionBitPad zeroinitializer, align 8 +// OGCG-DAG: @oaeb = {{(dso_local )?}}global %struct.OuterAllEmptyBits zeroinitializer, align 8 // LLVM-LABEL: define {{.*}} void @_ZN5OuterC2ERK6Middlec( // LLVM: %[[GEP:.*]] = getelementptr inbounds nuw %struct.Outer, ptr %{{.+}}, i32 0, i32 0 @@ -184,6 +196,76 @@ struct OuterUnionPadAfterStorage { OuterUnionPadAfterStorage oupas; +// The stand-in member takes a bit-field unit mark as soon as any variant is a +// unit, even where the storage type comes from a variant that is not one. +struct WideTail { + WideTail(); + +private: + double d; + char c; +}; + +union UnionBitAndWide { + UnionBitAndWide(); + [[no_unique_address]] WideTail w; + unsigned b : 3; + double d; +}; + +struct OuterUnionBitPad { + [[no_unique_address]] UnionBitAndWide u; + bool tail; +}; + +OuterUnionBitPad oubp; + +// No variant holds data and one is a unit, so the stand-in is an empty unit. +struct alignas(8) EmptyBitsTail { + EmptyBitsTail(); + +private: + int : 24; +}; + +union UnionAllEmptyBits { + UnionAllEmptyBits(); + [[no_unique_address]] EmptyBitsTail e; + unsigned : 3; +}; + +struct OuterAllEmptyBits { + [[no_unique_address]] UnionAllEmptyBits u; + bool tail; +}; + +OuterAllEmptyBits oaeb; + +// The only variant holding data is a unit, so the stand-in holds data because +// of a unit mark rather than a data mark. +union OnlyBitData { + OnlyBitData(); + [[no_unique_address]] EmptyBitsTail e; + unsigned b : 3; +}; + +struct OuterOnlyBitData { + [[no_unique_address]] OnlyBitData u; + bool tail; +}; + +OuterOnlyBitData oobd; + +// CIR-NUA-DAG: !rec_OnlyBitData2Ebase = !cir.struct<"OnlyBitData.base" {bitfield !cir.array<!u8i x 3>}> +// CIR-NUA-DAG: !rec_OuterOnlyBitData = !cir.struct<"OuterOnlyBitData" {data !rec_OnlyBitData2Ebase, data !cir.bool, pad !cir.array<!u8i x 4>}> +// CIR-NUA-DAG: cir.global external @oobd = #cir.zero : !rec_OuterOnlyBitData +// CIR-NUA-DAG: !rec_UnionBitAndWide2Ebase = !cir.struct<"UnionBitAndWide.base" packed {bitfield !cir.double, pad !u8i}> +// CIR-NUA-DAG: !rec_OuterUnionBitPad = !cir.struct<"OuterUnionBitPad" {data !rec_UnionBitAndWide2Ebase, data !cir.bool, pad !cir.array<!u8i x 6>}> +// CIR-NUA-DAG: !rec_UnionAllEmptyBits2Ebase = !cir.struct<"UnionAllEmptyBits.base" {empty !cir.array<!u8i x 3>}> +// CIR-NUA-DAG: !rec_OuterAllEmptyBits = !cir.struct<"OuterAllEmptyBits" {empty !rec_UnionAllEmptyBits2Ebase, data !cir.bool, pad !cir.array<!u8i x 4>}> +// CIR-NUA-DAG: cir.global external @oubp = #cir.zero : !rec_OuterUnionBitPad +// CIR-NUA-DAG: cir.global external @oaeb = #cir.zero : !rec_OuterAllEmptyBits + // CIR-NUA-DAG: !rec_FinalForNUA = !cir.struct<"FinalForNUA" {data !s32i, data !s8i}> // CIR-NUA-DAG: !rec_UnionForNUA = !cir.union<"UnionForNUA" {data !s32i, data !s64i}> // CIR-NUA-DAG: !rec_OuterFinal = !cir.struct<"OuterFinal" {data !rec_FinalForNUA, data !s8i}> diff --git a/clang/test/CIR/CodeGen/record-member-kinds.c b/clang/test/CIR/CodeGen/record-member-kinds.c index 1e46153862a49..831b68f23c4f9 100644 --- a/clang/test/CIR/CodeGen/record-member-kinds.c +++ b/clang/test/CIR/CodeGen/record-member-kinds.c @@ -40,37 +40,42 @@ struct UnnamedBitOnly { int : 8; }; struct UnnamedBitThenField { int : 8; int f; }; // CIR-DAG: !rec_UnnamedBitThenField = !cir.struct<"UnnamedBitThenField" {empty !u8i, data !s32i}> +// The trailing unit is narrower than its bit-field's declared type and is not +// pad, so it stays in the data size. +struct NamedFieldThenUnnamedBit { char c; int : 24; }; +// CIR-DAG: !rec_NamedFieldThenUnnamedBit = !cir.struct<"NamedFieldThenUnnamedBit" {data !s8i, empty !cir.array<!u8i x 3>}> + // The discrete ms_struct path allocates a unit per formal type. A unit whose // only occupant is unnamed holds no data. struct MsOnlyUnnamed { int : 3; } __attribute__((ms_struct)); // CIR-DAG: !rec_MsOnlyUnnamed = !cir.struct<"MsOnlyUnnamed" {empty !s32i}> struct MsNamedThenUnnamed { int a : 3; int : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsNamedThenUnnamed = !cir.struct<"MsNamedThenUnnamed" {data !s32i}> +// CIR-DAG: !rec_MsNamedThenUnnamed = !cir.struct<"MsNamedThenUnnamed" {bitfield !s32i}> struct MsUnnamedThenNamed { int : 3; int b : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsUnnamedThenNamed = !cir.struct<"MsUnnamedThenNamed" {data !s32i}> +// CIR-DAG: !rec_MsUnnamedThenNamed = !cir.struct<"MsUnnamedThenNamed" {bitfield !s32i}> // A diff ering formal type starts a new unit, so this record carries one unit of // each kind. struct MsMixed { int a : 3; char : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsMixed = !cir.struct<"MsMixed" {data !s32i, empty !s8i}> +// CIR-DAG: !rec_MsMixed = !cir.struct<"MsMixed" {bitfield !s32i, empty !s8i}> struct MsEmptyFirst { char : 3; int a : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsEmptyFirst = !cir.struct<"MsEmptyFirst" {empty !s8i, data !s32i}> +// CIR-DAG: !rec_MsEmptyFirst = !cir.struct<"MsEmptyFirst" {empty !s8i, bitfield !s32i}> struct MsEmptyMiddle { int a : 3; char : 3; short b : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsEmptyMiddle = !cir.struct<"MsEmptyMiddle" {data !s32i, empty !s8i, data !s16i}> +// CIR-DAG: !rec_MsEmptyMiddle = !cir.struct<"MsEmptyMiddle" {bitfield !s32i, empty !s8i, bitfield !s16i}> // A zero-width bit-field ends the run here too, so the unit after it is a // fresh one that has to be marked on its own. struct MsZeroWidthSplit { int a : 3; int : 0; int : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsZeroWidthSplit = !cir.struct<"MsZeroWidthSplit" {data !s32i, empty !s32i}> +// CIR-DAG: !rec_MsZeroWidthSplit = !cir.struct<"MsZeroWidthSplit" {bitfield !s32i, empty !s32i}> struct MsZeroWidthSplit2 { int : 3; int : 0; int b : 3; } __attribute__((ms_struct)); -// CIR-DAG: !rec_MsZeroWidthSplit2 = !cir.struct<"MsZeroWidthSplit2" {empty !s32i, data !s32i}> +// CIR-DAG: !rec_MsZeroWidthSplit2 = !cir.struct<"MsZeroWidthSplit2" {empty !s32i, bitfield !s32i}> union UnnamedBitUnion { int : 8; }; // CIR-DAG: !rec_UnnamedBitUnion = !cir.union<"UnnamedBitUnion" {empty !u8i}> @@ -78,6 +83,20 @@ union UnnamedBitUnion { int : 8; }; union ContainsEmptyUnion { struct E e; }; // CIR-DAG: !rec_ContainsEmptyUnion = !cir.union<"ContainsEmptyUnion" {empty !rec_E}> +// The two pairs that follow are byte-identical apart from their marks, in a +// struct and in a union alike. +struct BitWideUnit { long long x : 32; } __attribute__((aligned(16))); +// CIR-DAG: !rec_BitWideUnit = !cir.struct<"BitWideUnit" {bitfield !u32i, pad !cir.array<!u8i x 12>}> + +struct UIntOverAligned { unsigned x; } __attribute__((aligned(16))); +// CIR-DAG: !rec_UIntOverAligned = !cir.struct<"UIntOverAligned" {data !u32i, pad !cir.array<!u8i x 12>}> + +union BitUnit { unsigned a : 1; unsigned b : 1; }; +// CIR-DAG: !rec_BitUnit = !cir.union<"BitUnit" {bitfield !u8i, bitfield !u8i}, padding = {!cir.array<!u8i x 3>}> + +union UCharOverAligned { unsigned char c, d; } __attribute__((aligned(4))); +// CIR-DAG: !rec_UCharOverAligned = !cir.union<"UCharOverAligned" {data !u8i, data !u8i}, padding = {!cir.array<!u8i x 3>}> + struct AlignedTail { char c; int i __attribute__((aligned(8))); }; // CIR-DAG: !rec_AlignedTail = !cir.struct<"AlignedTail" {data !s8i, pad !cir.array<!u8i x 7>, data !s32i, pad !cir.array<!u8i x 4>}> // LLVM-DAG: %struct.AlignedTail = type { i8, [7 x i8], i32, [4 x i8] } @@ -92,7 +111,10 @@ void useTypes(struct ContainsEmpty *a, struct ContainsEmptyAndInt *b, struct MsMixed *l, struct MsEmptyFirst *m, struct MsEmptyMiddle *n, struct MsZeroWidthSplit *o, struct MsZeroWidthSplit2 *p, union UnnamedBitUnion *q, - union ContainsEmptyUnion *r) {} + union ContainsEmptyUnion *r, + struct NamedFieldThenUnnamedBit *s, struct BitWideUnit *t, + struct UIntOverAligned *u, union BitUnit *v, + union UCharOverAligned *w) {} struct AlignedTail gAlignedTail; diff --git a/clang/test/CIR/CodeGen/record-member-kinds.cpp b/clang/test/CIR/CodeGen/record-member-kinds.cpp index 98be5496e7040..379f756912985 100644 --- a/clang/test/CIR/CodeGen/record-member-kinds.cpp +++ b/clang/test/CIR/CodeGen/record-member-kinds.cpp @@ -69,29 +69,29 @@ struct OnlyUnnamedBit { int : 24; }; // A unit with a named occupant holds data, whichever order the occupants come // in, and however the storage is spelled. struct NamedClipped { int i; int j : 24; }; -// CIR-DAG: !rec_NamedClipped = !cir.struct<"NamedClipped" {data !s32i, data !u32i}> +// CIR-DAG: !rec_NamedClipped = !cir.struct<"NamedClipped" {data !s32i, bitfield !u32i}> struct NamedFirst { int a : 8; int : 16; }; -// CIR-DAG: !rec_NamedFirst = !cir.struct<"NamedFirst" {data !u32i}> +// CIR-DAG: !rec_NamedFirst = !cir.struct<"NamedFirst" {bitfield !u32i}> struct UnnamedFirst { int : 16; int a : 8; }; -// CIR-DAG: !rec_UnnamedFirst = !cir.struct<"UnnamedFirst" {data !u32i}> +// CIR-DAG: !rec_UnnamedFirst = !cir.struct<"UnnamedFirst" {bitfield !u32i}> // A zero-length bit-field separates one span into two units, and a record can // carry a data unit and an empty unit at once, in either order. struct SpanMixed { int a : 3; int : 0; int : 3; }; -// CIR-DAG: !rec_SpanMixed = !cir.struct<"SpanMixed" {data !u8i, pad !cir.array<!u8i x 3>, empty !u8i, pad !cir.array<!u8i x 3>}> +// CIR-DAG: !rec_SpanMixed = !cir.struct<"SpanMixed" {bitfield !u8i, pad !cir.array<!u8i x 3>, empty !u8i, pad !cir.array<!u8i x 3>}> struct SpanEmptyFirst { int : 3; int : 0; int b : 3; }; -// CIR-DAG: !rec_SpanEmptyFirst = !cir.struct<"SpanEmptyFirst" {empty !u8i, pad !cir.array<!u8i x 3>, data !u8i, pad !cir.array<!u8i x 3>}> +// CIR-DAG: !rec_SpanEmptyFirst = !cir.struct<"SpanEmptyFirst" {empty !u8i, pad !cir.array<!u8i x 3>, bitfield !u8i, pad !cir.array<!u8i x 3>}> // One unit covering both of these would need more than one register, so they // split into two units that are marked independently. struct WideSpanMixed { unsigned long a : 64; unsigned long : 64; }; -// CIR-DAG: !rec_WideSpanMixed = !cir.struct<"WideSpanMixed" {data !u64i, empty !u64i}> +// CIR-DAG: !rec_WideSpanMixed = !cir.struct<"WideSpanMixed" {bitfield !u64i, empty !u64i}> struct WideSpanEmptyFirst { unsigned long : 64; unsigned long b : 64; }; -// CIR-DAG: !rec_WideSpanEmptyFirst = !cir.struct<"WideSpanEmptyFirst" {empty !u64i, data !u64i}> +// CIR-DAG: !rec_WideSpanEmptyFirst = !cir.struct<"WideSpanEmptyFirst" {empty !u64i, bitfield !u64i}> struct WideSpanAllEmpty { unsigned long : 64; unsigned long : 64; }; // CIR-DAG: !rec_WideSpanAllEmpty = !cir.struct<"WideSpanAllEmpty" {empty !u64i, empty !u64i}> @@ -112,13 +112,15 @@ struct NearlyEmptyVBase { virtual ~NearlyEmptyVBase(); }; struct HasNearlyEmptyVBase : virtual NearlyEmptyVBase { int i; }; // CIR-DAG: !rec_HasNearlyEmptyVBase = !cir.struct<"HasNearlyEmptyVBase" packed {data !rec_NearlyEmptyVBase, data !s32i, pad !cir.array<!u8i x 4>}> -// Both marks appear on one record: the byte array is storage the source -// declared for its unnamed bit-field, while the byte after it is inserted by -// the compiler. The storage keeps its mark in the base subobject type. +// Only the pad member is reusable, so the unit stays in the base subobject type +// while the byte after it does not. A named unit stays the same way. struct Clipped { Clipped(const Clipped &); int i; int : 24; }; // CIR-DAG: !rec_Clipped = !cir.struct<"Clipped" packed {data !s32i, empty !cir.array<!u8i x 3>, pad !u8i}> // CIR-DAG: !rec_Clipped2Ebase = !cir.struct<"Clipped.base" packed {data !s32i, empty !cir.array<!u8i x 3>}> +struct NamedClippedTail { NamedClippedTail(const NamedClippedTail &); int i; int j : 24; }; +// CIR-DAG: !rec_NamedClippedTail = !cir.struct<"NamedClippedTail" packed {data !s32i, bitfield !cir.array<!u8i x 3>, pad !u8i}> + struct DerivedClipped : Clipped { char c; }; // CIR-DAG: !rec_DerivedClipped = !cir.struct<"DerivedClipped" {data !rec_Clipped2Ebase, data !s8i}> // LLVM-DAG: %struct.Clipped.base = type <{ i32, [3 x i8] }> @@ -132,7 +134,7 @@ void useTypes(HoldsEmpty *, NuaEmpty *, NuaEmptyUnion *, NuaPolyUnion *, NamedFirst *, UnnamedFirst *, SpanMixed *, SpanEmptyFirst *, WideSpanMixed *, WideSpanEmptyFirst *, WideSpanAllEmpty *, UnnamedBitUnion *, NoMemberUnion *, Pod *, NearlyEmptyVBase *, - HasNearlyEmptyVBase *, Clipped *) {} + HasNearlyEmptyVBase *, Clipped *, NamedClippedTail *) {} Empty gEmpty; AlignasTail gAlignasTail; diff --git a/clang/test/CIR/CodeGen/record-zero-init-padding.c b/clang/test/CIR/CodeGen/record-zero-init-padding.c index 8137f00194271..97ea5087c3746 100644 --- a/clang/test/CIR/CodeGen/record-zero-init-padding.c +++ b/clang/test/CIR/CodeGen/record-zero-init-padding.c @@ -35,7 +35,7 @@ void test_zero_init_padding(void) { } // Type definitions for anonymous structs with padding -// CIR-DAG: !rec_bitfield_with_padding = !cir.struct<"bitfield_with_padding" {data !u8i, data !s32i}> +// CIR-DAG: !rec_bitfield_with_padding = !cir.struct<"bitfield_with_padding" {bitfield !u8i, data !s32i}> // CIR-DAG: !rec_multiple_padding = !cir.struct<"multiple_padding" {data !s8i, data !s16i, data !s64i}> // CIR-DAG: !rec_padding_after_field = !cir.struct<"padding_after_field" {data !s8i, data !s32i}> // CIR-DAG: !rec_tail_padding = !cir.struct<"tail_padding" {data !s32i, data !s8i}> diff --git a/clang/test/CIR/IR/bitfield_info.cir b/clang/test/CIR/IR/bitfield_info.cir index 43c2ad6e75491..37f07c2f912f9 100644 --- a/clang/test/CIR/IR/bitfield_info.cir +++ b/clang/test/CIR/IR/bitfield_info.cir @@ -4,7 +4,7 @@ !u32i = !cir.int<u, 32> -!rec_S = !cir.struct<"S" {data !u32i}> +!rec_S = !cir.struct<"S" {bitfield !u32i}> #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u32i, size = 17, offset = 15, is_signed = true> // CHECK: #bfi_c = #cir.bitfield_info<name = "c", storage_type = !u32i, size = 17, offset = 15, is_signed = true> diff --git a/clang/test/CIR/IR/invalid-record-member-kinds.cir b/clang/test/CIR/IR/invalid-record-member-kinds.cir index 9524b6ea93d70..77e0e00c7664f 100644 --- a/clang/test/CIR/IR/invalid-record-member-kinds.cir +++ b/clang/test/CIR/IR/invalid-record-member-kinds.cir @@ -53,6 +53,15 @@ module {} // ----- +!u8i = !cir.int<u, 8> +!s32i = !cir.int<s, 32> +// expected-error @below {{a union's tail padding takes no kind mark}} +!rec_U = !cir.union<"U" {data !s32i}, padding = {bitfield !cir.array<!u8i x 4>}> + +module {} + +// ----- + !u8i = !cir.int<u, 8> !s32i = !cir.int<s, 32> // expected-error @below {{a union member cannot be marked pad}} diff --git a/clang/test/CIR/IR/struct.cir b/clang/test/CIR/IR/struct.cir index cf22833e211b4..0103da3982b95 100644 --- a/clang/test/CIR/IR/struct.cir +++ b/clang/test/CIR/IR/struct.cir @@ -37,6 +37,9 @@ !rec_P7 = !cir.struct<"P7" packed {data !u8i, pad !u8i}> !rec_P8 = !cir.struct<"P8" {data !u16i, pad !u8i, pad !u8i}> +// Bit-field access units +!rec_P9 = !cir.struct<"P9" {bitfield !u8i, empty !u8i}> + // CHECK-DAG: !rec_P1 = !cir.struct<"P1" packed {data !s32i, data !s32i}> // CHECK-DAG: !rec_P2 = !cir.struct<"P2" {data !u8i, pad !u8i, data !u16i, data !u32i}> // CHECK-DAG: !rec_P3 = !cir.struct<"P3" packed {data !u8i, pad !u8i, data !u16i, data !u32i}> @@ -45,6 +48,7 @@ // CHECK-DAG: !rec_P6 = !cir.struct<"P6" {data !u32i, empty !cir.array<!u8i x 3>, pad !u8i}> // CHECK-DAG: !rec_P7 = !cir.struct<"P7" packed {data !u8i, pad !u8i}> // CHECK-DAG: !rec_P8 = !cir.struct<"P8" {data !u16i, pad !u8i, pad !u8i}> +// CHECK-DAG: !rec_P9 = !cir.struct<"P9" {bitfield !u8i, empty !u8i}> // Records with identical member types, spelled apart by their kinds. !rec_M1 = !cir.struct<"M1" {data !u8i, pad !u8i}> @@ -63,6 +67,9 @@ !rec_anon_u_plain = !cir.union<{data !s32i, data !u8i}> !rec_U3 = !cir.union<"U3" {empty !u8i}> !rec_U4 = !cir.union<"U4" {data !s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}> +// A unit-marked union with a tail-padding slot, which is the shape CIRGen emits +// for a union of bit-fields. +!rec_U5 = !cir.union<"U5" {bitfield !u8i, empty !u8i}, padding = {!cir.array<!u8i x 3>}> // CHECK-DAG: !rec_U1 = !cir.union<"U1" {data !s32i, data !u8i}, padding = {!u8i}> // CHECK-DAG: !rec_U2 = !cir.union<"U2" packed {data !s32i}, padding = {!cir.array<!u8i x 4>}> @@ -70,6 +77,7 @@ // CHECK-DAG: !cir.union<{data !s32i, data !u8i}> // CHECK-DAG: !rec_U3 = !cir.union<"U3" {empty !u8i}> // CHECK-DAG: !rec_U4 = !cir.union<"U4" {data !s32i, empty !u8i}, padding = {!cir.array<!u8i x 4>}> +// CHECK-DAG: !rec_U5 = !cir.union<"U5" {bitfield !u8i, empty !u8i}, padding = {!cir.array<!u8i x 3>}> // Complete a previously incomplete record @@ -127,7 +135,9 @@ module { %arg21: !rec_P7, %arg22: !rec_anon_u_empty, %arg23: !rec_anon_u_plain, - %arg24: !rec_P8) { + %arg24: !rec_P8, + %arg25: !rec_P9, + %arg26: !rec_U5) { cir.return } diff --git a/clang/unittests/CIR/RecordMemberKindTest.cpp b/clang/unittests/CIR/RecordMemberKindTest.cpp index 9bbaf39c2ca17..d266078eb9129 100644 --- a/clang/unittests/CIR/RecordMemberKindTest.cpp +++ b/clang/unittests/CIR/RecordMemberKindTest.cpp @@ -73,12 +73,20 @@ TEST_F(RecordMemberKindTest, EmptyForTheABIWhenNoMemberHoldsData) { EXPECT_FALSE(makeStruct("dp", {u8, u8}, {RecordMemberKind::Data, RecordMemberKind::Pad}) .isEmptyForABI()); + // A unit with a named occupant holds data the same way a field does. + EXPECT_FALSE( + makeStruct("b1", {u8}, {RecordMemberKind::BitField}).isEmptyForABI()); + EXPECT_FALSE(makeStruct("be", {u8, u8}, + {RecordMemberKind::BitField, RecordMemberKind::Empty}) + .isEmptyForABI()); } TEST_F(RecordMemberKindTest, PaddedFollowsThePadKinds) { IntType u8 = getU8(); EXPECT_FALSE(makeStruct("d", {u8}, {RecordMemberKind::Data}).getPadded()); EXPECT_FALSE(makeStruct("e", {u8}, {RecordMemberKind::Empty}).getPadded()); + // Bit-field storage is declared, so an access unit is not padding. + EXPECT_FALSE(makeStruct("b", {u8}, {RecordMemberKind::BitField}).getPadded()); EXPECT_TRUE(makeStruct("p", {u8}, {RecordMemberKind::Pad}).getPadded()); // Interior padding counts too, not just a trailing run. EXPECT_TRUE(makeStruct("dpd", {u8, u8, u8}, @@ -156,6 +164,13 @@ TEST_F(RecordMemberKindTest, RejectsPadOnAUnionMember) { /*packed=*/false, /*padding=*/mlir::Type{}, llvm::ArrayRef<RecordMemberKind>(empty))); EXPECT_EQ(diags.count, 1u); + // A union variant can be a bit-field access unit, which is not padding. + llvm::SmallVector<RecordMemberKind> bitField{RecordMemberKind::BitField}; + EXPECT_TRUE( + UnionType::getChecked(getLoc(), &context, membersRef, + /*packed=*/false, /*padding=*/mlir::Type{}, + llvm::ArrayRef<RecordMemberKind>(bitField))); + EXPECT_EQ(diags.count, 1u); } TEST_F(RecordMemberKindTest, AnIncompleteRecordIsNotEmptyForTheABI) { @@ -192,6 +207,12 @@ TEST_F(RecordMemberKindTest, KindsTakePartInAnonymousTypeIdentity) { // Kinds are provenance rather than layout. EXPECT_TRUE(kindsPad.isLayoutIdentical(kindsEmpty)); + auto kindsBitField = + StructType::get(&context, {u8, u8}, /*packed=*/false, /*is_class=*/false, + {RecordMemberKind::BitField, RecordMemberKind::Pad}); + EXPECT_NE(kindsPad, kindsBitField); + EXPECT_TRUE(kindsPad.isLayoutIdentical(kindsBitField)); + llvm::SmallVector<mlir::Type> unionMembers{u8, u8}; llvm::SmallVector<RecordMemberKind> unionEmpty{RecordMemberKind::Data, RecordMemberKind::Empty}; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
