[clang] [CIR] Correct union lowering behavior re-padding (PR #216349)
Erich Keane via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 14 17:11:46 PDT 2026
https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/216349
>From 8a309bbbe03a84d654130de6c54777602ba1a1ee Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Thu, 13 Aug 2026 14:54:35 -0700
Subject: [PATCH 1/3] [CIR] Correct union lowering behavior re-padding
Most of the code that CIR lowers to LLVM counts on the fact that our
alignnments are correct/calculated in LLVM to get our layout correctly.
This works for the most part, and unions have the storage type of the
'highest' alignment type.
However, when creating a constant, we have to convert the type of the
union to have a 'storage' type that matches the data being inserted (not
the union's storage type!). The result was that if we had a storage
type where the alignment was smaller than the actual storage type, LLVM
would mis-calculate the padding.
This patch adds the padding explicitly when we make that conversion to
get the alignment set up correctly.
Note: there is one mild IR-equivilency-regression to this patch. There
isn't really a great way to tell the difference between a
union-tail-padding needing zero-init vs undef-init in this case. This
patch chooses to make it always zero-init, which is harmless. While it
MIGHT suppress some optimizations (facts not in evidence?), it seems
like something we can figure out later if necessary.
---
clang/include/clang/CIR/LoweringHelpers.h | 8 ++
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 17 ++-
clang/lib/CIR/Lowering/LoweringHelpers.cpp | 125 +++++++++++++++---
clang/test/CIR/CodeGen/bitfields.cpp | 2 +-
clang/test/CIR/CodeGen/union-agg-init.c | 59 ++++++++-
clang/test/CIR/CodeGen/union-agg-init.cpp | 14 ++
.../CIR/CodeGen/unions-with-zero-init.cpp | 6 +-
7 files changed, 201 insertions(+), 30 deletions(-)
diff --git a/clang/include/clang/CIR/LoweringHelpers.h b/clang/include/clang/CIR/LoweringHelpers.h
index 92633c89e1369..f0f65bb317521 100644
--- a/clang/include/clang/CIR/LoweringHelpers.h
+++ b/clang/include/clang/CIR/LoweringHelpers.h
@@ -54,6 +54,14 @@ mlir::Type adjustGlobalTypeForInit(mlir::Type llvmType, mlir::Attribute init,
const mlir::TypeConverter &converter,
const mlir::DataLayout &dataLayout);
+// A version of adjustGlobalTypeForInit which records where additional padding
+// was added in the middle, so we can properly adjust field indexes.
+mlir::Type
+adjustGlobalTypeForInit(mlir::Type llvmType, mlir::Attribute init,
+ const mlir::TypeConverter &converter,
+ const mlir::DataLayout &dataLayout,
+ llvm::SmallVectorImpl<unsigned> &paddingAddedIndexes);
+
mlir::Value getConstAPInt(mlir::OpBuilder &bld, mlir::Location loc,
mlir::Type typ, const llvm::APInt &val);
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 4ae4b7693d982..7986227a2d035 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -825,15 +825,26 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstArrayAttr attr) {
mlir::Value CIRAttrToValue::visitCirAttr(cir::ConstRecordAttr constRecord) {
mlir::Type llvmTy = converter->convertType(constRecord.getType());
mlir::DataLayout dataLayout(parentOp->getParentOfType<mlir::ModuleOp>());
- llvmTy = adjustGlobalTypeForInit(llvmTy, constRecord, *converter, dataLayout);
+ llvm::SmallVector<unsigned> paddingAddedIndexes;
+ llvmTy = adjustGlobalTypeForInit(llvmTy, constRecord, *converter, dataLayout,
+ paddingAddedIndexes);
const mlir::Location loc = parentOp->getLoc();
mlir::Value result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
+ uint64_t insertIdx = 0;
+ auto paddingItr = paddingAddedIndexes.begin();
+
// Iteratively lower each constant element of the record.
for (auto [idx, elt] : llvm::enumerate(constRecord.getMembers())) {
+ if (paddingItr != paddingAddedIndexes.end() && *paddingItr == idx) {
+ ++insertIdx;
+ ++paddingItr;
+ }
+
mlir::Value init = visit(elt);
- result =
- mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
+ result = mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init,
+ insertIdx);
+ ++insertIdx;
}
return result;
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index 3b927ad759c7a..db6448ee0023f 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -416,13 +416,23 @@ static bool shouldPackFAMStruct(const mlir::DataLayout &dataLayout,
// Additionally, the struct itself could contain a struct with a FAM or a union
// that needed adjustment, so it recurses to check those. If no such type has
// been found/no adjustment needed, this returns the type unchanged.
+//
+// Additionally, a union having an active member of the not-the-largest
+// alignment can cause the need for a small padding array. We also capture the
+// original indices of the fields that had this padding prepended, so the
+// lowerConstRecordAttr can later put in a 'zero' init there.
static mlir::Type adjustGlobalStructTypeForInit(
mlir::LLVM::LLVMStructType structTy, cir::ConstRecordAttr constRecord,
- const mlir::TypeConverter &converter, const mlir::DataLayout &dataLayout) {
-
+ const mlir::TypeConverter &converter, const mlir::DataLayout &dataLayout,
+ llvm::SmallVectorImpl<unsigned> &paddingAddedIndexes) {
+ assert(paddingAddedIndexes.empty() &&
+ "Not for accumulation, just single depth");
llvm::ArrayRef<mlir::Attribute> initMembers =
constRecord.getMembers().getValue();
- llvm::SmallVector<mlir::Type> newBody{structTy.getBody()};
+ llvm::SmallVector<mlir::Type> origBody{structTy.getBody()};
+ llvm::SmallVector<mlir::Type> newBody{};
+ bool packed = structTy.isPacked();
+ uint64_t curOffset = 0;
bool changed = false;
// Recursively adjust each member. A member that is itself a union (or a
@@ -430,14 +440,45 @@ static mlir::Type adjustGlobalStructTypeForInit(
// field type, and this struct has to adopt that adjusted type so the
// enclosing insertvalue chain type-checks.
for (auto [idx, member] : llvm::enumerate(initMembers)) {
- if (idx >= newBody.size())
+ if (idx >= origBody.size())
break;
- mlir::Type adjusted =
- adjustGlobalTypeForInit(newBody[idx], member, converter, dataLayout);
- if (adjusted != newBody[idx]) {
- newBody[idx] = adjusted;
+ mlir::Type adjusted = adjustGlobalTypeForInit(
+ origBody[idx], member, converter, dataLayout);
+ unsigned adjustedAlign = dataLayout.getTypeABIAlignment(adjusted);
+
+ if (adjusted != origBody[idx]) {
+ // We're always going to 'change' the layout if it has changed, but we
+ // need to see if there is new 'padding' that won't happen automatically
+ // here based on alignment.
+ unsigned origAlign = dataLayout.getTypeABIAlignment(origBody[idx]);
+
+ uint64_t origOffset =
+ packed ? curOffset : llvm::alignTo(curOffset, origAlign);
+ uint64_t adjustedOffset =
+ packed ? curOffset : llvm::alignTo(curOffset, adjustedAlign);
+
+ if (adjustedOffset != origOffset) {
+ // If the offset would change, we have to insert padding to make up for
+ // it. This should only happen since alignment will decrease with
+ // unions, so we should be able to assume adjusted-offset < origOffset?
+ assert(adjustedOffset < origOffset);
+ // Rather than just pad the difference between the offsets, we have to
+ // fill in since the end of the last field, else we leave room thanks to
+ // alignment between this field and the padding.
+ uint64_t difference = origOffset - curOffset;
+ newBody.push_back(mlir::LLVM::LLVMArrayType::get(
+ mlir::IntegerType::get(structTy.getContext(), 8),
+ difference));
+ paddingAddedIndexes.push_back(idx);
+ curOffset = origOffset;
+ }
changed = true;
}
+ newBody.push_back(adjusted);
+
+ if (!packed)
+ curOffset = llvm::alignTo(curOffset, adjustedAlign);
+ curOffset += dataLayout.getTypeSize(adjusted).getFixedValue();
}
// CIR supports flexible-array-members in its struct types. That is, a
@@ -446,7 +487,7 @@ static mlir::Type adjustGlobalStructTypeForInit(
// these, and our verifier allows it. However, the LLVM implementation does
// NOT permit this, so we widen that trailing member to the initializer's
// array type (packing the struct if that changes the layout).
- bool packed = structTy.isPacked();
+ bool widenedFAM = false;
if (auto fam =
mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(structTy.getBody().back());
fam && fam.getNumElements() == 0) {
@@ -455,6 +496,7 @@ static mlir::Type adjustGlobalStructTypeForInit(
if (mlir::cast<cir::ArrayType>(lastInitType).getSize() != 0) {
newBody.back() = converter.convertType(lastInitType);
packed = packed || shouldPackFAMStruct(dataLayout, newBody);
+ widenedFAM = true;
changed = true;
}
}
@@ -462,6 +504,19 @@ static mlir::Type adjustGlobalStructTypeForInit(
if (!changed)
return structTy;
+ // We've likely reduced the alignment, so make sure we put padding 'behind'
+ // it. We can skip this in the FAM case, since a Flexible array member is not
+ // allowed to be initialized unless it is the 'last' element. So it doesn't
+ // need to be padded out.
+ if (!widenedFAM) {
+ uint64_t declaredSize = dataLayout.getTypeSize(structTy).getFixedValue();
+ assert(curOffset <= declaredSize && "body bigger than type?");
+ if (curOffset < declaredSize)
+ newBody.push_back(mlir::LLVM::LLVMArrayType::get(
+ mlir::IntegerType::get(structTy.getContext(), 8),
+ declaredSize - curOffset));
+ }
+
return mlir::LLVM::LLVMStructType::getLiteral(structTy.getContext(), newBody,
packed);
}
@@ -515,9 +570,11 @@ static mlir::Type adjustGlobalUnionTypeForInit(
}
// Apply various adjustments required for struct/union types.
-mlir::Type adjustGlobalTypeForInit(mlir::Type llvmType, mlir::Attribute init,
- const mlir::TypeConverter &converter,
- const mlir::DataLayout &dataLayout) {
+mlir::Type
+adjustGlobalTypeForInit(mlir::Type llvmType, mlir::Attribute init,
+ const mlir::TypeConverter &converter,
+ const mlir::DataLayout &dataLayout,
+ llvm::SmallVectorImpl<unsigned> &paddingAddedIndexes) {
// Conversions for both only happen if we have a record init.
auto constRecord = mlir::dyn_cast_if_present<cir::ConstRecordAttr>(init);
if (!constRecord)
@@ -532,16 +589,25 @@ mlir::Type adjustGlobalTypeForInit(mlir::Type llvmType, mlir::Attribute init,
// Structs can have a flexible array member, adjust that.
if (mlir::isa<cir::StructType>(constRecord.getType()))
return adjustGlobalStructTypeForInit(structTy, constRecord, converter,
- dataLayout);
+ dataLayout, paddingAddedIndexes);
if (mlir::isa<cir::UnionType>(constRecord.getType()))
return adjustGlobalUnionTypeForInit(structTy, constRecord, converter,
dataLayout);
return llvmType;
}
+mlir::Type adjustGlobalTypeForInit(mlir::Type llvmType, mlir::Attribute init,
+ const mlir::TypeConverter &converter,
+ const mlir::DataLayout &dataLayout) {
+ llvm::SmallVector<unsigned> ignoredAddedIndexes;
+ return adjustGlobalTypeForInit(llvmType, init, converter, dataLayout,
+ ignoredAddedIndexes);
+}
+
std::optional<mlir::Attribute> lowerConstRecordAttr(
cir::ConstRecordAttr constRecord, mlir::SymbolTableCollection &symbolTables,
const mlir::TypeConverter *converter, mlir::ModuleOp moduleOp) {
+
// Build one constant attribute per record member. The LLVM dialect global
// translation accepts an ArrayAttr (one element per struct field) and emits
// an llvm::ConstantStruct, so the whole initializer can be a single
@@ -558,18 +624,37 @@ std::optional<mlir::Attribute> lowerConstRecordAttr(
}
// The lowered LLVM type may have more fields than the CIR record has members
- // -- e.g. a union lowers to { active-member, [pad x i8] } (see
- // adjustGlobalTypeForInit, the single source of truth for the shape). Fill
- // any such synthesized (padding) fields with undef so this ArrayAttr has
- // exactly one entry per LLVM field, matching the type the global is declared
- // with.
+ // for a few reasons:
+ // 1- a union lowers to { active-member, [pad x i8]).
+ // 2- A struct that contains such a union can have its alignment changed too,
+ // so it needs tail padding to fill that in.
+ // 3- A struct containing a union whose initializer doesn't use the highest-aligned
+ // field will have to prepend a bit of padding, such as struct { i32, union {
+ // i64, i32 } }. Typically the union gets lowered to a struct { i64 } (as i64
+ // has the greatest alignment), but if the init causes it to be the i32(or any
+ // such smaller field) we have to prepend it with padding:
+ // struct { i32, [4 x i8], struct { i32 }}
+ // instead of (with no init):
+ // struct { i32, struct { i64 }}
+ llvm::SmallVector<unsigned> paddingAddedIndexes;
mlir::Type adjustedTy = adjustGlobalTypeForInit(
converter->convertType(constRecord.getType()), constRecord, *converter,
- mlir::DataLayout(moduleOp));
+ mlir::DataLayout(moduleOp), paddingAddedIndexes);
+
+ // This handles #3 from above. adjustGlobalTypeForInit ensures the
+ // indexes are in increasing order, so we can insert 'backwards' without
+ // causing problems.
+ for (unsigned paddedElt : llvm::reverse(paddingAddedIndexes))
+ loweredMembers.insert(loweredMembers.begin() + paddedElt,
+ mlir::LLVM::ZeroAttr::get(constRecord.getContext()));
+
+ // Any remaining difference will be the union/struct padding case. We don't
+ // have a great handle/way to tell when to zero-vs-undef init, so always
+ // zero init, as it is always safe to do so.
if (auto structTy = mlir::dyn_cast<mlir::LLVM::LLVMStructType>(adjustedTy))
while (loweredMembers.size() < structTy.getBody().size())
loweredMembers.push_back(
- mlir::LLVM::UndefAttr::get(constRecord.getContext()));
+ mlir::LLVM::ZeroAttr::get(constRecord.getContext()));
return mlir::ArrayAttr::get(constRecord.getContext(), loweredMembers);
}
diff --git a/clang/test/CIR/CodeGen/bitfields.cpp b/clang/test/CIR/CodeGen/bitfields.cpp
index f1d2561dc2e44..7dce9bf29cd4a 100644
--- a/clang/test/CIR/CodeGen/bitfields.cpp
+++ b/clang/test/CIR/CodeGen/bitfields.cpp
@@ -32,7 +32,7 @@ typedef struct {
union U { int x : 3; };
const U u = {5};
// CIR-DAG: cir.global "private" {{.*}}@_ZL1u = #cir.const_record<{#cir.int<5> : !u8i}> : !rec_U
-// LLVM-DAG: @_ZL1u = internal constant %union.U { i8 5, [3 x i8] undef }
+// LLVM-DAG: @_ZL1u = internal constant %union.U { i8 5, [3 x i8] zeroinitializer }
// OGCG-DAG: @_ZL1u = internal constant %union.U { i8 5, [3 x i8] undef }
auto use() {
return u;
diff --git a/clang/test/CIR/CodeGen/union-agg-init.c b/clang/test/CIR/CodeGen/union-agg-init.c
index ab603d24d8712..ca390d25461d6 100644
--- a/clang/test/CIR/CodeGen/union-agg-init.c
+++ b/clang/test/CIR/CodeGen/union-agg-init.c
@@ -5,15 +5,67 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s
+union PtrToIntUnion { int id; char *str; };
+struct HasPtoIU { int info; union PtrToIntUnion u; };
+struct HasPtoIU ptoIU = { 101, { 1 } };
+// CIR-DAG: cir.global external @ptoIU = #cir.const_record<{#cir.int<101> : !s32i, #cir.const_record<{#cir.int<1> : !s32i}> : !rec_PtrToIntUnion}> : !rec_HasPtoIU
+// LLVM-DAG: @ptoIU = global { i32, [4 x i8], { i32, [4 x i8] } } { i32 101, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 1, [4 x i8] zeroinitializer } }
+
+struct WithTailPadding { int info; union PtrToIntUnion u; int tail; };
+struct WithTailPadding tailPadding = { 101, { 1 }, 42 };
+// CIR-DAG: cir.global external @tailPadding = #cir.const_record<{#cir.int<101> : !s32i, #cir.const_record<{#cir.int<1> : !s32i}> : !rec_PtrToIntUnion, #cir.int<42> : !s32i}> : !rec_WithTailPadding
+// LLVM-DAG: @tailPadding = global { i32, [4 x i8], { i32, [4 x i8] }, i32, [4 x i8] } { i32 101, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 1, [4 x i8] zeroinitializer }, i32 42, [4 x i8] zeroinitializer }
+
+struct AtStart { union PtrToIntUnion u; int x; };
+struct AtStart start = { 7, 9 };
+// CIR-DAG: cir.global external @start = #cir.const_record<{#cir.const_record<{#cir.int<7> : !s32i}> : !rec_PtrToIntUnion, #cir.int<9> : !s32i}> : !rec_AtStart
+// LLVM-DAG: @start = global { { i32, [4 x i8] }, i32, [4 x i8] } { { i32, [4 x i8] } { i32 7, [4 x i8] zeroinitializer }, i32 9, [4 x i8] zeroinitializer }
+
+struct NotToplevel { char c; struct WithTailPadding inner; };
+struct NotToplevel notTop = { 'x', { 101, { 1 }, 42 } };
+// CIR-DAG: cir.global external @notTop = #cir.const_record<{#cir.int<120> : !s8i, #cir.const_record<{#cir.int<101> : !s32i, #cir.const_record<{#cir.int<1> : !s32i}> : !rec_PtrToIntUnion, #cir.int<42> : !s32i}> : !rec_WithTailPadding}> : !rec_NotToplevel
+// LLVM-DAG: @notTop = global { i8, [7 x i8], { i32, [4 x i8], { i32, [4 x i8] }, i32, [4 x i8] } } { i8 120, [7 x i8] zeroinitializer, { i32, [4 x i8], { i32, [4 x i8] }, i32, [4 x i8] } { i32 101, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 1, [4 x i8] zeroinitializer }, i32 42, [4 x i8] zeroinitializer } }
+
+struct TwoUnions {
+ int a;
+ union PtrToIntUnion u1;
+ int b;
+ union PtrToIntUnion u2;
+};
+struct TwoUnions two_unions = { 1, { 2 }, 3, { 4 } };
+// CIR-DAG: cir.global external @two_unions = #cir.const_record<{#cir.int<1> : !s32i, #cir.const_record<{#cir.int<2> : !s32i}> : !rec_PtrToIntUnion, #cir.int<3> : !s32i, #cir.const_record<{#cir.int<4> : !s32i}> : !rec_PtrToIntUnion}> : !rec_TwoUnions
+// LLVM-DAG: @two_unions = global { i32, [4 x i8], { i32, [4 x i8] }, i32, [4 x i8], { i32, [4 x i8] } } { i32 1, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 2, [4 x i8] zeroinitializer }, i32 3, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 4, [4 x i8] zeroinitializer } }
+
+struct Anon { int info; union { int id; char *str; } u; };
+struct Anon anon = { 101, 1 };
+// CIR-DAG: cir.global external @anon = #cir.const_record<{#cir.int<101> : !s32i, #cir.const_record<{#cir.int<1> : !s32i}> : !rec_anon2E0}> : !rec_Anon
+// LLVM-DAG: @anon = global { i32, [4 x i8], { i32, [4 x i8] } } { i32 101, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 1, [4 x i8] zeroinitializer } }
+
+struct Bitfields { int a : 3; int b : 4; union PtrToIntUnion u; };
+struct Bitfields bitfields = { 1, 2, { 9 } };
+// CIR-DAG: cir.global external @bitfields = #cir.const_record<{#cir.int<17> : !u8i, #cir.const_record<{#cir.int<9> : !s32i}> : !rec_PtrToIntUnion}> : !rec_Bitfields {alignment = 8 : i64} loc(#loc42)
+// LLVM-DAG: @bitfields = global { i8, [7 x i8], { i32, [4 x i8] } } { i8 17, [7 x i8] zeroinitializer, { i32, [4 x i8] } { i32 9, [4 x i8] zeroinitializer } }
+
+struct FamUnion { int n; union PtrToIntUnion u; char fam[]; };
+struct FamUnion fam_union = { 3, { 7 }, { 'a','b','c' } };
+// CIR-DAG: cir.global external @fam_union = #cir.const_record<{#cir.int<3> : !s32i, #cir.const_record<{#cir.int<7> : !s32i}> : !rec_PtrToIntUnion, #cir.const_array<[#cir.int<97> : !s8i, #cir.int<98> : !s8i, #cir.int<99> : !s8i]> : !cir.array<!s8i x 3>}> : !rec_FamUnion
+// LLVM-DAG: @fam_union = global <{ i32, [4 x i8], { i32, [4 x i8] }, [3 x i8] }> <{ i32 3, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 7, [4 x i8] zeroinitializer }, [3 x i8] c"abc" }>
+
+struct FamMoves { char c; union PtrToIntUnion u; char fam[]; };
+struct FamMoves fam_realign = { 'q', { 7 }, { 'a','b','c' } };
+// CIR-DAG: cir.global external @fam_realign = #cir.const_record<{#cir.int<113> : !s8i, #cir.const_record<{#cir.int<7> : !s32i}> : !rec_PtrToIntUnion, #cir.const_array<[#cir.int<97> : !s8i, #cir.int<98> : !s8i, #cir.int<99> : !s8i]> : !cir.array<!s8i x 3>}> : !rec_FamMoves
+// LLVM-DAG: @fam_realign = global <{ i8, [7 x i8], { i32, [4 x i8] }, [3 x i8] }> <{ i8 113, [7 x i8] zeroinitializer, { i32, [4 x i8] } { i32 7, [4 x i8] zeroinitializer }, [3 x i8] c"abc" }>
+
+
typedef union vec3 {
struct { double x, y, z; };
double component[3];
} vec3;
-// LLVMCIR: @__const.ret_outer.__retval = {{.*}}%struct.outer { %union.needs_padding zeroinitializer, i32 1 }
-// OGCG: @__const.ret_outer.o = {{.*}}{ { i32, [4 x i8] }, i32, [4 x i8] } { { i32, [4 x i8] } zeroinitializer, i32 1, [4 x i8] zeroinitializer }
+// LLVMCIR-DAG: @__const.ret_outer.__retval = {{.*}}%struct.outer { %union.needs_padding zeroinitializer, i32 1 }
+// OGCG-DAG: @__const.ret_outer.o = {{.*}}{ { i32, [4 x i8] }, i32, [4 x i8] } { { i32, [4 x i8] } zeroinitializer, i32 1, [4 x i8] zeroinitializer }
-// CIR: cir.global "private" constant cir_private @__const.ret_outer.__retval = #cir.const_record<{#cir.zero : !rec_needs_padding, #cir.int<1> : !s32i}> : !rec_outer
+// CIR-DAG: cir.global "private" constant cir_private @__const.ret_outer.__retval = #cir.const_record<{#cir.zero : !rec_needs_padding, #cir.int<1> : !s32i}> : !rec_outer
// In C mode, this does do zero padding.
vec3 ret_vec3() {
@@ -67,3 +119,4 @@ struct outer ret_outer() {
// OGCG: %[[RET:.*]] = load { i64, i32 }, ptr %[[RET_ALLOCA]]
// LLVM: ret { i64, i32 } %[[RET]]
}
+
diff --git a/clang/test/CIR/CodeGen/union-agg-init.cpp b/clang/test/CIR/CodeGen/union-agg-init.cpp
index 86c94c596572c..8ce84648577e8 100644
--- a/clang/test/CIR/CodeGen/union-agg-init.cpp
+++ b/clang/test/CIR/CodeGen/union-agg-init.cpp
@@ -5,6 +5,19 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s
+union PtrToIntUnion { int id; char *str; };
+struct Base { int b; };
+struct Derived : Base { union PtrToIntUnion u; };
+struct DerivedFromDerived : Derived { int i; };
+Derived derived = { { 1 }, { 2 } };
+// CIR: cir.global external @derived = #cir.const_record<{#cir.const_record<{#cir.int<1> : !s32i}> : !rec_Base, #cir.const_record<{#cir.int<2> : !s32i}> : !rec_PtrToIntUnion}> : !rec_Derived
+// LLVMCIR: @derived = global { %struct.Base, [4 x i8], { i32, [4 x i8] } } { %struct.Base { i32 1 }, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 2, [4 x i8] zeroinitializer } }
+// OGCG: @derived = global { i32, [4 x i8], { i32, [4 x i8] } } { i32 1, [4 x i8] undef, { i32, [4 x i8] } { i32 2, [4 x i8] undef } }, align 8
+DerivedFromDerived derivedFromDerived = { { { 1 }, { 2 } }, 3};
+// CIR: cir.global external @derivedFromDerived = #cir.const_record<{#cir.const_record<{#cir.const_record<{#cir.int<1> : !s32i}> : !rec_Base, #cir.const_record<{#cir.int<2> : !s32i}> : !rec_PtrToIntUnion}> : !rec_Derived, #cir.int<3> : !s32i, #cir.zero : !cir.array<!u8i x 4>}> : !rec_DerivedFromDerived
+// LLVMCIR: @derivedFromDerived = global <{ { %struct.Base, [4 x i8], { i32, [4 x i8] } }, i32, [4 x i8] }> <{ { %struct.Base, [4 x i8], { i32, [4 x i8] } } { %struct.Base { i32 1 }, [4 x i8] zeroinitializer, { i32, [4 x i8] } { i32 2, [4 x i8] zeroinitializer } }, i32 3, [4 x i8] zeroinitializer }>
+// OGCG: @derivedFromDerived = global { i32, [4 x i8], { i32, [4 x i8] }, i32, [4 x i8] } { i32 1, [4 x i8] undef, { i32, [4 x i8] } { i32 2, [4 x i8] undef }, i32 3, [4 x i8] undef }, align 8
+
typedef union vec3 {
struct { double x, y, z; };
double component[3];
@@ -63,3 +76,4 @@ extern "C" Trivial ret_trivial() { return {}; }
// OGCG: %[[COERCE_DIVE:.*]] = getelementptr inbounds nuw %union.Trivial, ptr %[[RET_ALLOCA]], i32 0, i32 0
// OGCG: %[[RET:.*]] = load i32, ptr %[[COERCE_DIVE]]
// LLVM: ret i32 %[[RET]]
+
diff --git a/clang/test/CIR/CodeGen/unions-with-zero-init.cpp b/clang/test/CIR/CodeGen/unions-with-zero-init.cpp
index 992925ef85c2c..9c9cdc0165a15 100644
--- a/clang/test/CIR/CodeGen/unions-with-zero-init.cpp
+++ b/clang/test/CIR/CodeGen/unions-with-zero-init.cpp
@@ -39,7 +39,7 @@
// This gets promoted to a constant, so it is up here.
// CIR-AFTER-DAG: cir.global "private" constant cir_private @__const._Z1fv.inner_a2 = #cir.const_record<{#cir.int<12> : !s32i}> : !rec_inner_aggregate2
-// LLVM-DAG: @__const._Z1fv.inner_a2 = private {{.*}}constant { i32, [4 x i8] } { i32 12, [4 x i8] undef }
+// LLVM-DAG: @__const._Z1fv.inner_a2 = private {{.*}}constant { i32, [4 x i8] } { i32 12, [4 x i8] {{.*}} }
struct S { int x; };
int S::* p = nullptr;
@@ -54,7 +54,7 @@ int S::* p = nullptr;
// LLVMCIR-DAG: @outer_a1 = global %union.outer_aggregate zeroinitializer
// OGCG-DAG: @outer_a1 = global %union.outer_aggregate { i64 -1 }
-// LLVM-DAG: @outer_a2 = global { i32, [4 x i8] } { i32 32, [4 x i8] undef }
+// LLVM-DAG: @outer_a2 = global { i32, [4 x i8] } { i32 32, [4 x i8] {{.*}} }
// LLVM-DAG: @outer_a3 = global %union.outer_aggregate3 { i64 -1 }
struct HasPtrToMember { int S::*p; }; // not zero-initializable
@@ -65,7 +65,7 @@ union U {
U u{};
// CIR-DAG: cir.global external @u = #cir.zero : !rec_U
// LLVMCIR-DAG: @u = global %union.U zeroinitializer
-// OGCG-DAG: @u = global { i32, [4 x i8] } { i32 0, [4 x i8] undef }
+// OGCG-DAG: @u = global { i32, [4 x i8] } { i32 0, [4 x i8] {{.*}} }
auto use() {
return u;
}
>From e43a1fdbd2b1180ad66c1626ce6e2e746290bc35 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Fri, 14 Aug 2026 09:23:38 -0700
Subject: [PATCH 2/3] clang-format
---
clang/lib/CIR/Lowering/LoweringHelpers.cpp | 23 +++++++++++-----------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index db6448ee0023f..3dc79b3b10d21 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -442,8 +442,8 @@ static mlir::Type adjustGlobalStructTypeForInit(
for (auto [idx, member] : llvm::enumerate(initMembers)) {
if (idx >= origBody.size())
break;
- mlir::Type adjusted = adjustGlobalTypeForInit(
- origBody[idx], member, converter, dataLayout);
+ mlir::Type adjusted =
+ adjustGlobalTypeForInit(origBody[idx], member, converter, dataLayout);
unsigned adjustedAlign = dataLayout.getTypeABIAlignment(adjusted);
if (adjusted != origBody[idx]) {
@@ -467,8 +467,7 @@ static mlir::Type adjustGlobalStructTypeForInit(
// alignment between this field and the padding.
uint64_t difference = origOffset - curOffset;
newBody.push_back(mlir::LLVM::LLVMArrayType::get(
- mlir::IntegerType::get(structTy.getContext(), 8),
- difference));
+ mlir::IntegerType::get(structTy.getContext(), 8), difference));
paddingAddedIndexes.push_back(idx);
curOffset = origOffset;
}
@@ -624,15 +623,15 @@ std::optional<mlir::Attribute> lowerConstRecordAttr(
}
// The lowered LLVM type may have more fields than the CIR record has members
- // for a few reasons:
- // 1- a union lowers to { active-member, [pad x i8]).
+ // for a few reasons:
+ // 1- a union lowers to { active-member, [pad x i8]).
// 2- A struct that contains such a union can have its alignment changed too,
// so it needs tail padding to fill that in.
- // 3- A struct containing a union whose initializer doesn't use the highest-aligned
- // field will have to prepend a bit of padding, such as struct { i32, union {
- // i64, i32 } }. Typically the union gets lowered to a struct { i64 } (as i64
- // has the greatest alignment), but if the init causes it to be the i32(or any
- // such smaller field) we have to prepend it with padding:
+ // 3- A struct containing a union whose initializer doesn't use the
+ // highest-aligned field will have to prepend a bit of padding, such as struct
+ // { i32, union { i64, i32 } }. Typically the union gets lowered to a struct
+ // { i64 } (as i64 has the greatest alignment), but if the init causes it to
+ // be the i32(or any such smaller field) we have to prepend it with padding:
// struct { i32, [4 x i8], struct { i32 }}
// instead of (with no init):
// struct { i32, struct { i64 }}
@@ -646,7 +645,7 @@ std::optional<mlir::Attribute> lowerConstRecordAttr(
// causing problems.
for (unsigned paddedElt : llvm::reverse(paddingAddedIndexes))
loweredMembers.insert(loweredMembers.begin() + paddedElt,
- mlir::LLVM::ZeroAttr::get(constRecord.getContext()));
+ mlir::LLVM::ZeroAttr::get(constRecord.getContext()));
// Any remaining difference will be the union/struct padding case. We don't
// have a great handle/way to tell when to zero-vs-undef init, so always
>From 6c8f65248b9be93a841d5102167f6f50b4dc5ea6 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Fri, 14 Aug 2026 17:11:26 -0700
Subject: [PATCH 3/3] Nits from Andy
---
clang/lib/CIR/Lowering/LoweringHelpers.cpp | 2 +-
clang/test/CIR/CodeGen/union-agg-init.cpp | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/lib/CIR/Lowering/LoweringHelpers.cpp b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
index 3dc79b3b10d21..73554852ae522 100644
--- a/clang/lib/CIR/Lowering/LoweringHelpers.cpp
+++ b/clang/lib/CIR/Lowering/LoweringHelpers.cpp
@@ -417,7 +417,7 @@ static bool shouldPackFAMStruct(const mlir::DataLayout &dataLayout,
// that needed adjustment, so it recurses to check those. If no such type has
// been found/no adjustment needed, this returns the type unchanged.
//
-// Additionally, a union having an active member of the not-the-largest
+// Additionally, a union having an active member of not-the-largest
// alignment can cause the need for a small padding array. We also capture the
// original indices of the fields that had this padding prepended, so the
// lowerConstRecordAttr can later put in a 'zero' init there.
diff --git a/clang/test/CIR/CodeGen/union-agg-init.cpp b/clang/test/CIR/CodeGen/union-agg-init.cpp
index 8ce84648577e8..f9f00530fb4ec 100644
--- a/clang/test/CIR/CodeGen/union-agg-init.cpp
+++ b/clang/test/CIR/CodeGen/union-agg-init.cpp
@@ -76,4 +76,3 @@ extern "C" Trivial ret_trivial() { return {}; }
// OGCG: %[[COERCE_DIVE:.*]] = getelementptr inbounds nuw %union.Trivial, ptr %[[RET_ALLOCA]], i32 0, i32 0
// OGCG: %[[RET:.*]] = load i32, ptr %[[COERCE_DIVE]]
// LLVM: ret i32 %[[RET]]
-
More information about the cfe-commits
mailing list