[clang] [CIR][CodeGen] Support DesignatedInitUpdateExpr in constant emission (PR #194238)
via cfe-commits
cfe-commits at lists.llvm.org
Tue May 5 10:23:40 PDT 2026
https://github.com/AbdallahRashed updated https://github.com/llvm/llvm-project/pull/194238
>From 7aa125fdba002b6a52fd59bc09d48a9b3f267690 Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Sun, 26 Apr 2026 17:04:55 +0200
Subject: [PATCH 1/5] [CIR][CodeGen] Support DesignatedInitUpdateExpr in
constant emission
---
clang/include/clang/CIR/MissingFeatures.h | 1 +
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 94 ++++++++++++++++++-
.../test/CIR/CodeGen/designated-init-update.c | 36 +++++++
3 files changed, 126 insertions(+), 5 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/designated-init-update.c
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 634d4dd83b3f8..fc87150b98eef 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -230,6 +230,7 @@ struct MissingFeatures {
static bool cleanupDeactivationScope() { return false; }
static bool cleanupWithPreservedValues() { return false; }
static bool cleanupsToDeactivate() { return false; }
+ static bool constEmitterAbstractForMemory() { return false; }
static bool constEmitterAggILE() { return false; }
static bool constEmitterArrayILE() { return false; }
static bool constEmitterVectorILE() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index a8b24fb686088..9616b970da2d4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -626,6 +626,11 @@ bool ConstRecordBuilder::applyZeroInitPadding(const ASTRecordLayout &layout,
return true;
}
+static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
+ ConstantAggregateBuilder &constant,
+ CharUnits offset, QualType type,
+ const InitListExpr *updater);
+
bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
RecordDecl *rd = ile->getType()->castAsRecordDecl();
const ASTRecordLayout &layout = cgm.getASTContext().getASTRecordLayout(rd);
@@ -679,8 +684,19 @@ bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
// a new constant to emit independently.
if (allowOverwrite &&
(field->getType()->isArrayType() || field->getType()->isRecordType())) {
- cgm.errorNYI(field->getSourceRange(), "designated init lists");
- return false;
+ if (auto *subILE = dyn_cast<InitListExpr>(init)) {
+ CharUnits fieldOffset = cgm.getASTContext().toCharUnitsFromBits(
+ layout.getFieldOffset(index));
+ if (!emitDesignatedInitUpdater(emitter, builder,
+ startOffset + fieldOffset,
+ field->getType(), subILE))
+ return false;
+ // If we split apart the field's value, try to collapse it down to a
+ // single value now.
+ builder.condense(startOffset + fieldOffset,
+ cgm.getTypes().convertTypeForMem(field->getType()));
+ continue;
+ }
}
mlir::Attribute eltInitAttr =
@@ -868,6 +884,65 @@ bool ConstRecordBuilder::updateRecord(ConstantEmitter &emitter,
.build(updater, /*allowOverwrite*/ true);
}
+static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
+ ConstantAggregateBuilder &constant,
+ CharUnits offset, QualType type,
+ const InitListExpr *updater) {
+ if (type->isRecordType())
+ return ConstRecordBuilder::updateRecord(
+ emitter, constant, offset, const_cast<InitListExpr *>(updater));
+
+ auto *cat = emitter.cgm.getASTContext().getAsConstantArrayType(type);
+ if (!cat)
+ return false;
+ QualType elemType = cat->getElementType();
+ CharUnits elemSize = emitter.cgm.getASTContext().getTypeSizeInChars(elemType);
+ mlir::Type elemTy = emitter.cgm.getTypes().convertTypeForMem(elemType);
+
+ mlir::TypedAttr fillC;
+ if (const Expr *filler = updater->getArrayFiller()) {
+ if (!isa<NoInitExpr>(filler)) {
+ // Classic codegen uses tryEmitAbstractForMemory here to track abstract
+ // state for pointer authentication. We should use that when implemented.
+ assert(!cir::MissingFeatures::constEmitterAbstractForMemory());
+ mlir::Attribute result =
+ emitter.tryEmitPrivateForMemory(filler, elemType);
+ if (!result)
+ return false;
+ fillC = mlir::cast<mlir::TypedAttr>(result);
+ }
+ }
+
+ unsigned numElementsToUpdate =
+ fillC ? cat->getZExtSize() : updater->getNumInits();
+ for (unsigned i = 0; i != numElementsToUpdate; ++i, offset += elemSize) {
+ const Expr *init = nullptr;
+ if (i < updater->getNumInits())
+ init = updater->getInit(i);
+
+ if (!init && fillC) {
+ if (!constant.add(fillC, offset, true))
+ return false;
+ } else if (!init || isa<NoInitExpr>(init)) {
+ continue;
+ } else if (const auto *childILE = dyn_cast<InitListExpr>(init)) {
+ if (!emitDesignatedInitUpdater(emitter, constant, offset, elemType,
+ childILE))
+ return false;
+ // Attempt to reduce the array element to a single constant if necessary.
+ constant.condense(offset, elemTy);
+ } else {
+ mlir::Attribute val = emitter.tryEmitPrivateForMemory(init, elemType);
+ if (!val)
+ return false;
+ if (!constant.add(mlir::cast<mlir::TypedAttr>(val), offset, true))
+ return false;
+ }
+ }
+
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// ConstExprEmitter
//===----------------------------------------------------------------------===//
@@ -1074,9 +1149,18 @@ class ConstExprEmitter
if (!c)
return {};
- cgm.errorNYI(e->getBeginLoc(),
- "ConstExprEmitter::VisitDesignatedInitUpdateExpr");
- return {};
+ ConstantAggregateBuilder constant(cgm);
+ constant.add(mlir::cast<mlir::TypedAttr>(c), CharUnits::Zero(), false);
+
+ if (!emitDesignatedInitUpdater(emitter, constant, CharUnits::Zero(),
+ destType, e->getUpdater()))
+ return {};
+
+ mlir::Type valTy = cgm.convertType(destType);
+ bool hasFlexibleArray = false;
+ if (const auto *rd = destType->getAsRecordDecl())
+ hasFlexibleArray = rd->hasFlexibleArrayMember();
+ return constant.build(valTy, hasFlexibleArray);
}
mlir::Attribute VisitCXXConstructExpr(CXXConstructExpr *e, QualType ty) {
diff --git a/clang/test/CIR/CodeGen/designated-init-update.c b/clang/test/CIR/CodeGen/designated-init-update.c
new file mode 100644
index 0000000000000..6c75d2abe1710
--- /dev/null
+++ b/clang/test/CIR/CodeGen/designated-init-update.c
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+struct S {
+ int a, b, c;
+};
+
+// Basic designated init update: start from {1, 2, 3}, override .b = 20
+struct S g1 = (struct S){1, 2, 3, .b = 20};
+
+// CIR: cir.global external @g1 = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<20> : !s32i, #cir.int<3> : !s32i}> : !rec_S
+// LLVM: @g1 = global %struct.S { i32 1, i32 20, i32 3 }
+// OGCG: @g1 = global %struct.S { i32 1, i32 20, i32 3 }
+
+// Multiple field overrides
+struct S g2 = (struct S){10, 20, 30, .a = 100, .c = 300};
+
+// CIR: cir.global external @g2 = #cir.const_record<{#cir.int<100> : !s32i, #cir.int<20> : !s32i, #cir.int<300> : !s32i}> : !rec_S
+// LLVM: @g2 = global %struct.S { i32 100, i32 20, i32 300 }
+// OGCG: @g2 = global %struct.S { i32 100, i32 20, i32 300 }
+
+// Nested struct with designated init update
+struct Outer {
+ struct S inner;
+ int x;
+};
+
+struct Outer g3 = (struct Outer){{1, 2, 3}, 4, .inner.b = 50};
+
+// CIR: cir.global external @g3 = #cir.const_record<{#cir.const_record<{#cir.int<1> : !s32i, #cir.int<50> : !s32i, #cir.int<3> : !s32i}> : !rec_S, #cir.int<4> : !s32i}> : !rec_Outer
+// LLVM: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 }
+// OGCG: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 }
>From 08c868c493cfbf2cc6f3affefdaa54a942c9a2ba Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Mon, 27 Apr 2026 23:08:53 +0200
Subject: [PATCH 2/5] [CIR][CodeGen] Implement
ConstantAggregateBuilder::split()
Implement split() for ConstRecordAttr, ConstArrayAttr, ZeroAttr, and
UndefAttr. This enables DesignatedInitUpdateExpr to work end-to-end
when the base is a compound literal (which produces an opaque expression
that Sema cannot fold in-place).
Also fix ConstRecordBuilder::build() to skip zero-init padding when in
overwrite mode, since gaps between updated fields contain valid base data
that should not be zeroed out.
Add a test case using a compound literal sub-object with a later field
override, which produces a real DesignatedInitUpdateExpr AST node and
exercises the split() path.
---
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 78 ++++++++++++++++++-
.../test/CIR/CodeGen/designated-init-update.c | 14 ++++
2 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 9616b970da2d4..fe970ac8f774c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -347,7 +347,79 @@ std::optional<size_t> ConstantAggregateBuilder::splitAt(CharUnits pos) {
/// Hint indicates the location at which we'd like to split, but may be
/// ignored.
bool ConstantAggregateBuilder::split(size_t index, CharUnits hint) {
- cgm.errorNYI("split constant at index");
+ naturalLayout = false;
+ mlir::TypedAttr c = elements[index].element;
+ CharUnits offset = elements[index].offset;
+
+ if (auto constRecord = mlir::dyn_cast<cir::ConstRecordAttr>(c)) {
+ // Expand the record into its contained member elements.
+ auto recordTy = mlir::cast<cir::RecordType>(constRecord.getType());
+ mlir::ArrayAttr members = constRecord.getMembers();
+ unsigned numMembers = members.size();
+
+ SmallVector<Element> newElems;
+ newElems.reserve(numMembers);
+ for (unsigned i = 0; i < numMembers; ++i) {
+ auto memberAttr = mlir::cast<mlir::TypedAttr>(members[i]);
+ CharUnits memberOffset =
+ offset + CharUnits::fromQuantity(
+ recordTy.getElementOffset(dataLayout.layout, i));
+ newElems.emplace_back(memberAttr, memberOffset);
+ }
+ replace(elements, index, index + 1, newElems);
+ return true;
+ }
+
+ if (auto constArray = mlir::dyn_cast<cir::ConstArrayAttr>(c)) {
+ // Expand the array into its contained elements.
+ auto arrayTy = mlir::cast<cir::ArrayType>(constArray.getType());
+ CharUnits elemSize = getSize(arrayTy.getElementType());
+
+ if (auto arrayAttr = mlir::dyn_cast<mlir::ArrayAttr>(constArray.getElts())) {
+ // Array with explicit elements (plus possible trailing zeros).
+ SmallVector<Element> newElems;
+ unsigned numExplicit = arrayAttr.size();
+ unsigned trailingZeros = constArray.getTrailingZerosNum();
+ newElems.reserve(numExplicit + trailingZeros);
+
+ for (unsigned i = 0; i < numExplicit; ++i) {
+ auto eltAttr = mlir::cast<mlir::TypedAttr>(arrayAttr[i]);
+ newElems.emplace_back(eltAttr, offset + i * elemSize);
+ }
+ // Expand trailing zeros as individual zero elements.
+ for (unsigned i = 0; i < trailingZeros; ++i) {
+ auto zeroAttr = mlir::cast<mlir::TypedAttr>(
+ cir::ZeroAttr::get(arrayTy.getElementType()));
+ newElems.emplace_back(zeroAttr, offset + (numExplicit + i) * elemSize);
+ }
+ replace(elements, index, index + 1, newElems);
+ return true;
+ }
+ // TODO: Handle StringAttr elements (char arrays) if needed.
+ return false;
+ }
+
+ if (mlir::isa<cir::ZeroAttr>(c)) {
+ // Split into two zeros at the hinted offset.
+ CharUnits elemSize = getSize(c);
+ assert(hint > offset && hint < offset + elemSize && "nothing to split");
+ // Create two byte-array padding elements to represent the two halves.
+ mlir::TypedAttr firstZero = getPadding(hint - offset);
+ mlir::TypedAttr secondZero = getPadding(offset + elemSize - hint);
+ SmallVector<Element> newElems;
+ newElems.emplace_back(firstZero, offset);
+ newElems.emplace_back(secondZero, hint);
+ replace(elements, index, index + 1, newElems);
+ return true;
+ }
+
+ if (mlir::isa<cir::UndefAttr>(c)) {
+ // Drop undef; it doesn't contribute to the final layout.
+ replace(elements, index, index + 1, SmallVector<Element>{});
+ return true;
+ }
+
+ // FIXME: We could split a cir::IntAttr if the need ever arose.
return false;
}
@@ -674,7 +746,7 @@ bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
continue;
}
- if (zeroInitPadding &&
+ if (zeroInitPadding && !allowOverwrite &&
!applyZeroInitPadding(layout, index, *field, allowOverwrite, sizeSoFar,
zeroFieldSize))
return false;
@@ -730,7 +802,7 @@ bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
}
}
- return !zeroInitPadding ||
+ return !zeroInitPadding || allowOverwrite ||
applyZeroInitPadding(layout, allowOverwrite, sizeSoFar);
}
diff --git a/clang/test/CIR/CodeGen/designated-init-update.c b/clang/test/CIR/CodeGen/designated-init-update.c
index 6c75d2abe1710..c7a495c5d8d0b 100644
--- a/clang/test/CIR/CodeGen/designated-init-update.c
+++ b/clang/test/CIR/CodeGen/designated-init-update.c
@@ -34,3 +34,17 @@ struct Outer g3 = (struct Outer){{1, 2, 3}, 4, .inner.b = 50};
// CIR: cir.global external @g3 = #cir.const_record<{#cir.const_record<{#cir.int<1> : !s32i, #cir.int<50> : !s32i, #cir.int<3> : !s32i}> : !rec_S, #cir.int<4> : !s32i}> : !rec_Outer
// LLVM: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 }
// OGCG: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 }
+
+// Compound literal as sub-object with later field override.
+// This produces a DesignatedInitUpdateExpr AST node (unlike the cases above
+// which Sema folds in-place) and exercises ConstantAggregateBuilder::split().
+struct P {
+ struct S s;
+ int x;
+};
+
+struct P g4 = { (struct S){1, 2, 3}, 4, .s.b = 9 };
+
+// CIR: cir.global external @g4 = #cir.const_record<{#cir.const_record<{#cir.int<1> : !s32i, #cir.int<9> : !s32i, #cir.int<3> : !s32i}> : !rec_S, #cir.int<4> : !s32i}> : !rec_P
+// LLVM: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
+// OGCG: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
>From 52b3f4f5eeb0f8623c1a30b33b93862b291cd5bb Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Tue, 28 Apr 2026 12:09:26 +0200
Subject: [PATCH 3/5] [CIR][CodeGen] Implement buildFrom for ArrayType
- Implement ConstantAggregateBuilder::buildFrom for cir::ArrayType (was NYI)
- Add defensive dyn_cast<TypedAttr> in DesignatedInitUpdateExpr emission
- Add array-path test case (g5) exercising split + condense + buildFrom
---
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 61 ++++++++++++++++---
.../test/CIR/CodeGen/designated-init-update.c | 11 ++++
2 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index fe970ac8f774c..3b8b362aa9d18 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -375,7 +375,8 @@ bool ConstantAggregateBuilder::split(size_t index, CharUnits hint) {
auto arrayTy = mlir::cast<cir::ArrayType>(constArray.getType());
CharUnits elemSize = getSize(arrayTy.getElementType());
- if (auto arrayAttr = mlir::dyn_cast<mlir::ArrayAttr>(constArray.getElts())) {
+ if (auto arrayAttr =
+ mlir::dyn_cast<mlir::ArrayAttr>(constArray.getElts())) {
// Array with explicit elements (plus possible trailing zeros).
SmallVector<Element> newElems;
unsigned numExplicit = arrayAttr.size();
@@ -471,9 +472,43 @@ ConstantAggregateBuilder::buildFrom(CIRGenModule &cgm, ArrayRef<Element> elems,
// If we want an array type, see if all the elements are the same type and
// appropriately spaced.
- if (mlir::isa<cir::ArrayType>(desiredTy)) {
- cgm.errorNYI("array aggregate constants");
- return {};
+ if (auto arrTy = mlir::dyn_cast<cir::ArrayType>(desiredTy)) {
+ mlir::Type eltTy = arrTy.getElementType();
+ uint64_t numElements = arrTy.getSize();
+ CharUnits eltSize = utils.getSize(eltTy);
+
+ // Check if all elements have the correct type and are at stride offsets.
+ SmallVector<mlir::Attribute> arrayElts(numElements);
+ bool canBuildArray = true;
+
+ for (const auto &[element, offset] : elems) {
+ CharUnits relOffset = offset - startOffset;
+ if (relOffset % eltSize != CharUnits::Zero() ||
+ element.getType() != eltTy) {
+ canBuildArray = false;
+ break;
+ }
+ uint64_t idx = relOffset / eltSize;
+ if (idx >= numElements) {
+ canBuildArray = false;
+ break;
+ }
+ arrayElts[idx] = element;
+ }
+
+ if (canBuildArray) {
+ // Fill gaps with zero.
+ for (uint64_t i = 0; i < numElements; ++i) {
+ if (!arrayElts[i])
+ arrayElts[i] = cir::ZeroAttr::get(eltTy);
+ }
+ CIRGenBuilderTy &builder = cgm.getBuilder();
+ return cir::ConstArrayAttr::get(
+ arrTy, mlir::ArrayAttr::get(builder.getContext(), arrayElts));
+ }
+
+ // Elements don't form a clean array layout; fall through to struct-based
+ // packing below.
}
// The size of the constant we plan to generate. This is usually just the size
@@ -769,6 +804,9 @@ bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
cgm.getTypes().convertTypeForMem(field->getType()));
continue;
}
+ // For non-InitListExpr inits (e.g. a compound literal or other
+ // expression that evaluates to the whole field value), fall through
+ // to normal emission below. This matches classic codegen behavior.
}
mlir::Attribute eltInitAttr =
@@ -981,7 +1019,9 @@ static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
emitter.tryEmitPrivateForMemory(filler, elemType);
if (!result)
return false;
- fillC = mlir::cast<mlir::TypedAttr>(result);
+ fillC = mlir::dyn_cast<mlir::TypedAttr>(result);
+ if (!fillC)
+ return false;
}
}
@@ -1007,7 +1047,10 @@ static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
mlir::Attribute val = emitter.tryEmitPrivateForMemory(init, elemType);
if (!val)
return false;
- if (!constant.add(mlir::cast<mlir::TypedAttr>(val), offset, true))
+ auto typedVal = mlir::dyn_cast<mlir::TypedAttr>(val);
+ if (!typedVal)
+ return false;
+ if (!constant.add(typedVal, offset, true))
return false;
}
}
@@ -1221,8 +1264,12 @@ class ConstExprEmitter
if (!c)
return {};
+ auto typedC = mlir::dyn_cast<mlir::TypedAttr>(c);
+ if (!typedC)
+ return {};
+
ConstantAggregateBuilder constant(cgm);
- constant.add(mlir::cast<mlir::TypedAttr>(c), CharUnits::Zero(), false);
+ constant.add(typedC, CharUnits::Zero(), false);
if (!emitDesignatedInitUpdater(emitter, constant, CharUnits::Zero(),
destType, e->getUpdater()))
diff --git a/clang/test/CIR/CodeGen/designated-init-update.c b/clang/test/CIR/CodeGen/designated-init-update.c
index c7a495c5d8d0b..30bba78834693 100644
--- a/clang/test/CIR/CodeGen/designated-init-update.c
+++ b/clang/test/CIR/CodeGen/designated-init-update.c
@@ -48,3 +48,14 @@ struct P g4 = { (struct S){1, 2, 3}, 4, .s.b = 9 };
// CIR: cir.global external @g4 = #cir.const_record<{#cir.const_record<{#cir.int<1> : !s32i, #cir.int<9> : !s32i, #cir.int<3> : !s32i}> : !rec_S, #cir.int<4> : !s32i}> : !rec_P
// LLVM: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
// OGCG: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
+
+// Array element override via compound literal — exercises the array path
+// of emitDesignatedInitUpdater and buildFrom for cir::ArrayType.
+struct Inner { int arr[4]; };
+struct ArrOuter { struct Inner in; int x; };
+
+struct ArrOuter g5 = { (struct Inner){{10, 20, 30, 40}}, 5, .in.arr[1] = 99 };
+
+// CIR: cir.global external @g5 = #cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.int<10> : !s32i, #cir.int<99> : !s32i, #cir.int<30> : !s32i, #cir.int<40> : !s32i]> : !cir.array<!s32i x 4>}> : !rec_Inner, #cir.int<5> : !s32i}> : !rec_ArrOuter
+// LLVM: @g5 = global %struct.ArrOuter { %struct.Inner { [4 x i32] [i32 10, i32 99, i32 30, i32 40] }, i32 5 }
+// OGCG: @g5 = global %struct.ArrOuter { %struct.Inner { [4 x i32] [i32 10, i32 99, i32 30, i32 40] }, i32 5 }
>From 51d8ad5ed6f9d9e7eef6e0edf8bbfc906d543db2 Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Tue, 5 May 2026 17:38:47 +0200
Subject: [PATCH 4/5] [CIR][CodeGen] Address review comments on
DesignatedInitUpdateExpr
- build()/updateRecord() now take const pointers; drop const_cast
- split(): keep trailing zeros as a single ZeroAttr; add errorNYI for
StringAttr/ConstVectorAttr/PoisonAttr
SmallVector<mlir::TypedAttr>
- Expand tests: g1-g9 cover Sema-folded, record/array/ZeroAttr splits,
2D arrays, and both sides of the emitArrayConstant threshold
---
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 59 +++++++++------
.../test/CIR/CodeGen/designated-init-update.c | 74 +++++++++++++++++--
2 files changed, 104 insertions(+), 29 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 3b8b362aa9d18..61fd61925e234 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -381,22 +381,26 @@ bool ConstantAggregateBuilder::split(size_t index, CharUnits hint) {
SmallVector<Element> newElems;
unsigned numExplicit = arrayAttr.size();
unsigned trailingZeros = constArray.getTrailingZerosNum();
- newElems.reserve(numExplicit + trailingZeros);
+ newElems.reserve(numExplicit + (trailingZeros ? 1 : 0));
for (unsigned i = 0; i < numExplicit; ++i) {
auto eltAttr = mlir::cast<mlir::TypedAttr>(arrayAttr[i]);
newElems.emplace_back(eltAttr, offset + i * elemSize);
}
- // Expand trailing zeros as individual zero elements.
- for (unsigned i = 0; i < trailingZeros; ++i) {
- auto zeroAttr = mlir::cast<mlir::TypedAttr>(
- cir::ZeroAttr::get(arrayTy.getElementType()));
- newElems.emplace_back(zeroAttr, offset + (numExplicit + i) * elemSize);
+ // Keep trailing zeros as a single ZeroAttr to avoid blowing up the
+ // element list. The ZeroAttr branch of split() can break it apart
+ // further if a later overwrite targets an element inside this range.
+ if (trailingZeros > 0) {
+ auto trailingArrayTy =
+ cir::ArrayType::get(arrayTy.getElementType(), trailingZeros);
+ auto zeroAttr =
+ mlir::cast<mlir::TypedAttr>(cir::ZeroAttr::get(trailingArrayTy));
+ newElems.emplace_back(zeroAttr, offset + numExplicit * elemSize);
}
replace(elements, index, index + 1, newElems);
return true;
}
- // TODO: Handle StringAttr elements (char arrays) if needed.
+ cgm.errorNYI("split of ConstArrayAttr with StringAttr elements");
return false;
}
@@ -420,6 +424,16 @@ bool ConstantAggregateBuilder::split(size_t index, CharUnits hint) {
return true;
}
+ if (mlir::isa<cir::ConstVectorAttr>(c)) {
+ cgm.errorNYI("split of ConstVectorAttr");
+ return false;
+ }
+
+ if (mlir::isa<cir::PoisonAttr>(c)) {
+ cgm.errorNYI("split of PoisonAttr");
+ return false;
+ }
+
// FIXME: We could split a cir::IntAttr if the need ever arose.
return false;
}
@@ -473,12 +487,14 @@ ConstantAggregateBuilder::buildFrom(CIRGenModule &cgm, ArrayRef<Element> elems,
// If we want an array type, see if all the elements are the same type and
// appropriately spaced.
if (auto arrTy = mlir::dyn_cast<cir::ArrayType>(desiredTy)) {
+ assert(!allowOversized && "oversized array emission not supported");
mlir::Type eltTy = arrTy.getElementType();
uint64_t numElements = arrTy.getSize();
CharUnits eltSize = utils.getSize(eltTy);
// Check if all elements have the correct type and are at stride offsets.
- SmallVector<mlir::Attribute> arrayElts(numElements);
+ SmallVector<mlir::TypedAttr> arrayElts;
+ arrayElts.resize(numElements);
bool canBuildArray = true;
for (const auto &[element, offset] : elems) {
@@ -497,14 +513,15 @@ ConstantAggregateBuilder::buildFrom(CIRGenModule &cgm, ArrayRef<Element> elems,
}
if (canBuildArray) {
- // Fill gaps with zero.
+ mlir::TypedAttr filler =
+ mlir::cast<mlir::TypedAttr>(cir::ZeroAttr::get(eltTy));
+ // Fill gaps with the filler (zero).
for (uint64_t i = 0; i < numElements; ++i) {
if (!arrayElts[i])
- arrayElts[i] = cir::ZeroAttr::get(eltTy);
+ arrayElts[i] = filler;
}
- CIRGenBuilderTy &builder = cgm.getBuilder();
- return cir::ConstArrayAttr::get(
- arrTy, mlir::ArrayAttr::get(builder.getContext(), arrayElts));
+ return emitArrayConstant(cgm, desiredTy, eltTy, numElements, arrayElts,
+ filler);
}
// Elements don't form a clean array layout; fall through to struct-based
@@ -605,7 +622,7 @@ class ConstRecordBuilder {
const APValue &value, QualType valTy);
static bool updateRecord(ConstantEmitter &emitter,
ConstantAggregateBuilder &constant, CharUnits offset,
- InitListExpr *updater);
+ const InitListExpr *updater);
private:
ConstRecordBuilder(ConstantEmitter &emitter,
@@ -642,7 +659,7 @@ class ConstRecordBuilder {
bool applyZeroInitPadding(const ASTRecordLayout &layout, bool allowOverwrite,
CharUnits &sizeSoFar);
- bool build(InitListExpr *ile, bool allowOverwrite);
+ bool build(const InitListExpr *ile, bool allowOverwrite);
bool build(const APValue &val, const RecordDecl *rd, bool isPrimaryBase,
const CXXRecordDecl *vTableClass, CharUnits baseOffset);
@@ -738,8 +755,8 @@ static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
CharUnits offset, QualType type,
const InitListExpr *updater);
-bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
- RecordDecl *rd = ile->getType()->castAsRecordDecl();
+bool ConstRecordBuilder::build(const InitListExpr *ile, bool allowOverwrite) {
+ const RecordDecl *rd = ile->getType()->castAsRecordDecl();
const ASTRecordLayout &layout = cgm.getASTContext().getASTRecordLayout(rd);
// Bail out if we have base classes. We could support these, but they only
@@ -767,7 +784,7 @@ bool ConstRecordBuilder::build(InitListExpr *ile, bool allowOverwrite) {
// Get the initializer. A record can include fields without initializers,
// we just use explicit null values for them.
- Expr *init = nullptr;
+ const Expr *init = nullptr;
if (elementNo < ile->getNumInits())
init = ile->getInit(elementNo++);
if (isa_and_nonnull<NoInitExpr>(init))
@@ -989,7 +1006,8 @@ mlir::Attribute ConstRecordBuilder::buildRecord(ConstantEmitter &emitter,
bool ConstRecordBuilder::updateRecord(ConstantEmitter &emitter,
ConstantAggregateBuilder &constant,
- CharUnits offset, InitListExpr *updater) {
+ CharUnits offset,
+ const InitListExpr *updater) {
return ConstRecordBuilder(emitter, constant, offset)
.build(updater, /*allowOverwrite*/ true);
}
@@ -999,8 +1017,7 @@ static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
CharUnits offset, QualType type,
const InitListExpr *updater) {
if (type->isRecordType())
- return ConstRecordBuilder::updateRecord(
- emitter, constant, offset, const_cast<InitListExpr *>(updater));
+ return ConstRecordBuilder::updateRecord(emitter, constant, offset, updater);
auto *cat = emitter.cgm.getASTContext().getAsConstantArrayType(type);
if (!cat)
diff --git a/clang/test/CIR/CodeGen/designated-init-update.c b/clang/test/CIR/CodeGen/designated-init-update.c
index 30bba78834693..4b2352f109bb2 100644
--- a/clang/test/CIR/CodeGen/designated-init-update.c
+++ b/clang/test/CIR/CodeGen/designated-init-update.c
@@ -9,21 +9,22 @@ struct S {
int a, b, c;
};
-// Basic designated init update: start from {1, 2, 3}, override .b = 20
+// Basic designated init update: start from {1, 2, 3}, override .b = 20.
+// Sema folds in place — does NOT produce a DesignatedInitUpdateExpr.
struct S g1 = (struct S){1, 2, 3, .b = 20};
// CIR: cir.global external @g1 = #cir.const_record<{#cir.int<1> : !s32i, #cir.int<20> : !s32i, #cir.int<3> : !s32i}> : !rec_S
// LLVM: @g1 = global %struct.S { i32 1, i32 20, i32 3 }
// OGCG: @g1 = global %struct.S { i32 1, i32 20, i32 3 }
-// Multiple field overrides
+// Multiple field overrides; also folded by Sema.
struct S g2 = (struct S){10, 20, 30, .a = 100, .c = 300};
// CIR: cir.global external @g2 = #cir.const_record<{#cir.int<100> : !s32i, #cir.int<20> : !s32i, #cir.int<300> : !s32i}> : !rec_S
// LLVM: @g2 = global %struct.S { i32 100, i32 20, i32 300 }
// OGCG: @g2 = global %struct.S { i32 100, i32 20, i32 300 }
-// Nested struct with designated init update
+// Nested struct with designated init update — folded by Sema.
struct Outer {
struct S inner;
int x;
@@ -35,9 +36,12 @@ struct Outer g3 = (struct Outer){{1, 2, 3}, 4, .inner.b = 50};
// LLVM: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 }
// OGCG: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 }
-// Compound literal as sub-object with later field override.
-// This produces a DesignatedInitUpdateExpr AST node (unlike the cases above
-// which Sema folds in-place) and exercises ConstantAggregateBuilder::split().
+// From here on: cases that produce a DesignatedInitUpdateExpr and exercise
+// the ConstantAggregateBuilder::split / buildFrom / emitDesignatedInitUpdater
+// paths.
+
+// g4: compound-literal sub-record + later record-field override.
+// Exercises split() of cir::ConstRecordAttr.
struct P {
struct S s;
int x;
@@ -49,8 +53,9 @@ struct P g4 = { (struct S){1, 2, 3}, 4, .s.b = 9 };
// LLVM: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
// OGCG: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
-// Array element override via compound literal — exercises the array path
-// of emitDesignatedInitUpdater and buildFrom for cir::ArrayType.
+// g5: compound-literal sub-record + later array-element override.
+// Exercises split() of cir::ConstArrayAttr (no trailing zeros) and
+// buildFrom's array path.
struct Inner { int arr[4]; };
struct ArrOuter { struct Inner in; int x; };
@@ -59,3 +64,56 @@ struct ArrOuter g5 = { (struct Inner){{10, 20, 30, 40}}, 5, .in.arr[1] = 99 };
// CIR: cir.global external @g5 = #cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.int<10> : !s32i, #cir.int<99> : !s32i, #cir.int<30> : !s32i, #cir.int<40> : !s32i]> : !cir.array<!s32i x 4>}> : !rec_Inner, #cir.int<5> : !s32i}> : !rec_ArrOuter
// LLVM: @g5 = global %struct.ArrOuter { %struct.Inner { [4 x i32] [i32 10, i32 99, i32 30, i32 40] }, i32 5 }
// OGCG: @g5 = global %struct.ArrOuter { %struct.Inner { [4 x i32] [i32 10, i32 99, i32 30, i32 40] }, i32 5 }
+
+// g6: empty initializer base for a sub-record + deep designator override.
+// The base for the inner anonymous struct is `{}`, which becomes a
+// cir::ZeroAttr; the override has to thread through the anonymous
+// struct field to reach .A.
+struct Base {
+ struct {
+ int A;
+ };
+};
+struct DerivedS {
+ struct Base B;
+};
+struct DerivedS g6 = { {}, .B.A = 42 };
+
+// CIR: cir.global external @g6 = #cir.const_record<{#cir.const_record<{#cir.const_record<{#cir.int<42> : !s32i}>
+// LLVM: @g6 = global %struct.DerivedS { %struct.Base { %struct.anon{{[0-9.]*}} { i32 42 } } }
+// OGCG: @g6 = global %struct.DerivedS { %struct.Base { %struct.anon{{[0-9.]*}} { i32 42 } } }
+
+// g7: array-of-array element override. Exercises split() of an outer
+// cir::ConstArrayAttr whose element type is itself a cir::ArrayType.
+struct M { int m[2][3]; };
+struct N { struct M mm; int x; };
+struct N g7 = { (struct M){{ {1,2,3}, {4,5,6} }}, 7, .mm.m[1][1] = 99 };
+
+// CIR: cir.global external @g7 = #cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i]> : !cir.array<!s32i x 3>, #cir.const_array<[#cir.int<4> : !s32i, #cir.int<99> : !s32i, #cir.int<6> : !s32i]> : !cir.array<!s32i x 3>]> : !cir.array<!cir.array<!s32i x 3> x 2>}> : !rec_M, #cir.int<7> : !s32i}> : !rec_N
+// LLVM: @g7 = global %struct.N { %struct.M { [2 x [3 x i32]] {{\[}}[3 x i32] [i32 1, i32 2, i32 3], [3 x i32] [i32 4, i32 99, i32 6]] }, i32 7 }
+// OGCG: @g7 = global %struct.N { %struct.M { [2 x [3 x i32]] {{\[}}[3 x i32] [i32 1, i32 2, i32 3], [3 x i32] [i32 4, i32 99, i32 6]] }, i32 7 }
+
+// g8: small trailing-zero corner. The base array {1, 2, 3} for int[10] has
+// 7 trailing zeros which is below emitArrayConstant's 8-element threshold,
+// so the result comes out as a clean cir::ConstArrayAttr.
+struct Q { int arr[10]; };
+struct R { struct Q q; int x; };
+struct R g8 = { (struct Q){{1, 2, 3}}, 5, .q.arr[7] = 99 };
+
+// CIR: cir.global external @g8 = #cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<99> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i]> : !cir.array<!s32i x 10>}> : !rec_Q, #cir.int<5> : !s32i}> : !rec_R
+// LLVM: @g8 = global %struct.R { %struct.Q { [10 x i32] [i32 1, i32 2, i32 3, i32 0, i32 0, i32 0, i32 0, i32 99, i32 0, i32 0] }, i32 5 }
+// OGCG: @g8 = global %struct.R { %struct.Q { [10 x i32] [i32 1, i32 2, i32 3, i32 0, i32 0, i32 0, i32 0, i32 99, i32 0, i32 0] }, i32 5 }
+
+// g9: large trailing-zeros corner. The base value (struct BigArrInner){{1,2,3}}
+// for int[20] has 17 trailing zeros (>= 8), so emitArrayConstant packs it
+// as a struct {i32, i32, i32, [17 x i32] zeroinitializer}. Updating
+// arr[15] requires splitting the trailing ZeroAttr (a cir::ZeroAttr over
+// an array type) via the ZeroAttr branch of split(), then condensing back.
+struct BigArrInner { int arr[20]; };
+struct BigArrOuter { struct BigArrInner in; int x; };
+struct BigArrOuter g9 =
+ { (struct BigArrInner){{1, 2, 3}}, 7, .in.arr[15] = 99 };
+
+// CIR: cir.global external @g9 = #cir.const_record<{{.*}}#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.const_array<[{{(#cir.zero : !u8i(, )?)+}}]> : !cir.array<!u8i x 48>, #cir.int<99> : !s32i,{{.*}}#cir.int<7> : !s32i{{.*}}
+// LLVM: @g9 = global { { { i32, i32, i32, [48 x i8], i32{{(, i8)+}} } }, i32 }
+// OGCG: @g9 = global %struct.BigArrOuter { %struct.BigArrInner { [20 x i32] [i32 1, i32 2, i32 3,{{( i32 0,)+}} i32 0, i32 0, i32 0, i32 0, i32 99, i32 0, i32 0, i32 0, i32 0] }, i32 7 }
>From 8c83a07fe7ea0906aa9158fe8ad6a3eda7b71139 Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Tue, 5 May 2026 19:13:15 +0200
Subject: [PATCH 5/5] Match classic codegen's behavior by calling
applyZeroInitPadding for NoInitExpr fields. This advances sizeSoFar past
skipped fields, which prevents the next field's padding fill from corrupting
the base data.
Previously, NoInitExpr fields were simply skipped without advancing
sizeSoFar, causing applyZeroInitPadding to see a false gap between
offset 0 and the next initialized field.
---
clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 11 ++++++--
.../test/CIR/CodeGen/designated-init-update.c | 28 +++++++++++++++++++
2 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index 61fd61925e234..498ea7a9f7e52 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -787,8 +787,13 @@ bool ConstRecordBuilder::build(const InitListExpr *ile, bool allowOverwrite) {
const Expr *init = nullptr;
if (elementNo < ile->getNumInits())
init = ile->getInit(elementNo++);
- if (isa_and_nonnull<NoInitExpr>(init))
+ if (isa_and_nonnull<NoInitExpr>(init)) {
+ if (zeroInitPadding &&
+ !applyZeroInitPadding(layout, index, *field, allowOverwrite,
+ sizeSoFar, zeroFieldSize))
+ return false;
continue;
+ }
// Zero-sized fields are not emitted, but their initializers may still
// prevent emission of this record as a constant.
@@ -798,7 +803,7 @@ bool ConstRecordBuilder::build(const InitListExpr *ile, bool allowOverwrite) {
continue;
}
- if (zeroInitPadding && !allowOverwrite &&
+ if (zeroInitPadding &&
!applyZeroInitPadding(layout, index, *field, allowOverwrite, sizeSoFar,
zeroFieldSize))
return false;
@@ -857,7 +862,7 @@ bool ConstRecordBuilder::build(const InitListExpr *ile, bool allowOverwrite) {
}
}
- return !zeroInitPadding || allowOverwrite ||
+ return !zeroInitPadding ||
applyZeroInitPadding(layout, allowOverwrite, sizeSoFar);
}
diff --git a/clang/test/CIR/CodeGen/designated-init-update.c b/clang/test/CIR/CodeGen/designated-init-update.c
index 4b2352f109bb2..f1f591ed1098f 100644
--- a/clang/test/CIR/CodeGen/designated-init-update.c
+++ b/clang/test/CIR/CodeGen/designated-init-update.c
@@ -109,6 +109,11 @@ struct R g8 = { (struct Q){{1, 2, 3}}, 5, .q.arr[7] = 99 };
// as a struct {i32, i32, i32, [17 x i32] zeroinitializer}. Updating
// arr[15] requires splitting the trailing ZeroAttr (a cir::ZeroAttr over
// an array type) via the ZeroAttr branch of split(), then condensing back.
+//
+// The CIR result is a packed struct of byte arrays rather than a clean
+// [20 x i32]; the LLVM output similarly diverges from classic codegen.
+// FIXME: tighten g9 once buildFrom reuses emitArrayConstant on the
+// array fallthrough path.
struct BigArrInner { int arr[20]; };
struct BigArrOuter { struct BigArrInner in; int x; };
struct BigArrOuter g9 =
@@ -117,3 +122,26 @@ struct BigArrOuter g9 =
// CIR: cir.global external @g9 = #cir.const_record<{{.*}}#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.const_array<[{{(#cir.zero : !u8i(, )?)+}}]> : !cir.array<!u8i x 48>, #cir.int<99> : !s32i,{{.*}}#cir.int<7> : !s32i{{.*}}
// LLVM: @g9 = global { { { i32, i32, i32, [48 x i8], i32{{(, i8)+}} } }, i32 }
// OGCG: @g9 = global %struct.BigArrOuter { %struct.BigArrInner { [20 x i32] [i32 1, i32 2, i32 3,{{( i32 0,)+}} i32 0, i32 0, i32 0, i32 0, i32 99, i32 0, i32 0, i32 0, i32 0] }, i32 7 }
+
+// g10: all-zero base, single override. Exercises the cir::ZeroAttr split
+// branch — the base whole-record is emitted as ZeroAttr, which split
+// breaks apart at the override offset.
+struct AllZero { int arr[12]; };
+struct AllZeroOuter { struct AllZero az; int x; };
+struct AllZeroOuter g10 = { (struct AllZero){{0}}, 7, .az.arr[5] = 99 };
+
+// CIR: cir.global external @g10 = #cir.const_record<{#cir.const_record<{#cir.const_record<{#cir.const_array<[{{(#cir.zero : !u8i(, )?)+}}]> : !cir.array<!u8i x 20>, #cir.int<99> : !s32i,{{.*}}}> : !rec_anon_struct{{[0-9]*}}}> : !rec_anon_struct{{[0-9]*}}, #cir.int<7> : !s32i}> : !rec_anon_struct{{[0-9]*}}
+// LLVM: @g10 = global { { { [20 x i8], i32{{(, i8)+}} } }, i32 }
+// OGCG: @g10 = global { { { [20 x i8], i32, [24 x i8] } }, i32 } { { { [20 x i8], i32, [24 x i8] } } { { [20 x i8], i32, [24 x i8] } { [20 x i8] zeroinitializer, i32 99, [24 x i8] zeroinitializer } }, i32 7 }
+
+// g11: very large array trailing-zero stress test. 97 trailing zeros after
+// {1,2,3} with an override at index 80. Same FIXME as g9 — CIR produces
+// a packed struct of byte arrays rather than a clean [100 x i32].
+// FIXME: tighten g11 once buildFrom reuses emitArrayConstant.
+struct Many { int arr[100]; };
+struct ManyOuter { struct Many mm; int x; };
+struct ManyOuter g11 = { (struct Many){{1, 2, 3}}, 7, .mm.arr[80] = 42 };
+
+// CIR: cir.global external @g11 = #cir.const_record<{{.*}}#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.const_array<[{{(#cir.zero : !u8i(, )?)+}}]> : !cir.array<!u8i x 308>, #cir.int<42> : !s32i,{{.*}}#cir.int<7> : !s32i{{.*}}
+// LLVM: @g11 = global { { { i32, i32, i32, [308 x i8], i32{{(, i8)+}} } }, i32 }
+// OGCG: @g11 = global {{.*}}[81 x i32]{{.*}}i32 42], [19 x i32] zeroinitializer{{.*}}i32 7{{.*}}
More information about the cfe-commits
mailing list