https://github.com/AbdallahRashed updated https://github.com/llvm/llvm-project/pull/194238
>From f9fd96c1102038851f51a199a259dfaf1b39f14a Mon Sep 17 00:00:00 2001 From: AbdallahRashed <[email protected]> Date: Sun, 26 Apr 2026 17:04:55 +0200 Subject: [PATCH 1/6] [CIR][CodeGen] Support DesignatedInitUpdateExpr in constant emission --- clang/include/clang/CIR/MissingFeatures.h | 2 + clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 94 ++++++++++++++++++- .../test/CIR/CodeGen/designated-init-update.c | 36 +++++++ 3 files changed, 127 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 56ee2f4101a99..2fce80bac0a7b 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -207,6 +207,8 @@ struct MissingFeatures { static bool cleanupAfterErrorDiags() { return false; } static bool cleanupDeactivationScope() { 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; } static bool constantFoldSwitchStatement() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index c6346542b4b44..444f2fd945bb6 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 2055a9d4bd97fe56ee9225a9566e0a3638cb71d9 Mon Sep 17 00:00:00 2001 From: AbdallahRashed <[email protected]> Date: Mon, 27 Apr 2026 23:08:53 +0200 Subject: [PATCH 2/6] [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 444f2fd945bb6..43f4830065eb2 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 228087e36df59fef909c754eae59ac900d9e8711 Mon Sep 17 00:00:00 2001 From: AbdallahRashed <[email protected]> Date: Tue, 28 Apr 2026 12:09:26 +0200 Subject: [PATCH 3/6] [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 43f4830065eb2..bbd0c9fec0ef4 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 9f2e9f2ce79ccd01017aa4d931cce5400dbec2d1 Mon Sep 17 00:00:00 2001 From: AbdallahRashed <[email protected]> Date: Tue, 5 May 2026 17:38:47 +0200 Subject: [PATCH 4/6] [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 bbd0c9fec0ef4..93337dbc2e3bb 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 a8811ca98e0149c67fff829a33e760b22f170621 Mon Sep 17 00:00:00 2001 From: AbdallahRashed <[email protected]> Date: Tue, 5 May 2026 19:13:15 +0200 Subject: [PATCH 5/6] 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 93337dbc2e3bb..a1621da49840c 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{{.*}} >From f7b0f40e55f7ad5d75de4cbf0640d081249b719c Mon Sep 17 00:00:00 2001 From: AbdallahRashed <[email protected]> Date: Sat, 30 May 2026 17:49:28 +0200 Subject: [PATCH 6/6] [CIR][CodeGen] Preserve array type info when splitting ZeroAttr in constant emission When ConstantAggregateBuilder splits a ZeroAttr that wraps a CIR array type at an element-aligned boundary, produce typed sub-array ZeroAttrs instead of falling back to byte-level padding. This allows buildFrom to reconstruct a clean [N x T] array constant for designated init updates on large arrays with trailing zeros. Also teach buildFrom's array reconstruction path to recognize multi-slot ZeroAttr elements and skip them (filled with per-element zeros), and emit ZeroAttr from computePadding so downstream code can match on it. --- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 62 ++++++++++++++++--- .../test/CIR/CodeGen/designated-init-update.c | 31 ++++------ .../test/CIR/CodeGen/paren-list-agg-init.cpp | 4 +- clang/test/CIR/CodeGen/union-agg-init.c | 2 +- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index a1621da49840c..c93fb1d02baf2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -48,12 +48,9 @@ class ConstExprEmitter; static mlir::TypedAttr computePadding(CIRGenModule &cgm, CharUnits size) { mlir::Type eltTy = cgm.uCharTy; clang::CharUnits::QuantityType arSize = size.getQuantity(); - CIRGenBuilderTy &bld = cgm.getBuilder(); - if (size > CharUnits::One()) { - SmallVector<mlir::Attribute> elts(arSize, cir::ZeroAttr::get(eltTy)); - return bld.getConstArray(mlir::ArrayAttr::get(bld.getContext(), elts), - cir::ArrayType::get(eltTy, arSize)); - } + if (size > CharUnits::One()) + return mlir::cast<mlir::TypedAttr>( + cir::ZeroAttr::get(cir::ArrayType::get(eltTy, arSize))); return cir::ZeroAttr::get(eltTy); } @@ -408,6 +405,37 @@ bool ConstantAggregateBuilder::split(size_t index, CharUnits hint) { // Split into two zeros at the hinted offset. CharUnits elemSize = getSize(c); assert(hint > offset && hint < offset + elemSize && "nothing to split"); + + // If the ZeroAttr wraps a CIR array type and the split point aligns to + // element boundaries, produce typed ZeroAttr sub-arrays. This preserves + // array layout information so that buildFrom can later reconstruct a + // proper [N x T] array constant instead of falling back to a packed struct. + if (auto arrTy = mlir::dyn_cast<cir::ArrayType>(c.getType())) { + CharUnits eltSize = getSize(arrTy.getElementType()); + CharUnits firstLen = hint - offset; + CharUnits secondLen = offset + elemSize - hint; + if (firstLen % eltSize == CharUnits::Zero() && + secondLen % eltSize == CharUnits::Zero()) { + uint64_t firstCount = firstLen / eltSize; + uint64_t secondCount = secondLen / eltSize; + SmallVector<Element> newElems; + if (firstCount > 0) { + auto firstTy = + cir::ArrayType::get(arrTy.getElementType(), firstCount); + newElems.emplace_back( + mlir::cast<mlir::TypedAttr>(cir::ZeroAttr::get(firstTy)), offset); + } + if (secondCount > 0) { + auto secondTy = + cir::ArrayType::get(arrTy.getElementType(), secondCount); + newElems.emplace_back( + mlir::cast<mlir::TypedAttr>(cir::ZeroAttr::get(secondTy)), hint); + } + replace(elements, index, index + 1, newElems); + return true; + } + } + // Create two byte-array padding elements to represent the two halves. mlir::TypedAttr firstZero = getPadding(hint - offset); mlir::TypedAttr secondZero = getPadding(offset + elemSize - hint); @@ -499,12 +527,30 @@ ConstantAggregateBuilder::buildFrom(CIRGenModule &cgm, ArrayRef<Element> elems, for (const auto &[element, offset] : elems) { CharUnits relOffset = offset - startOffset; - if (relOffset % eltSize != CharUnits::Zero() || - element.getType() != eltTy) { + if (relOffset % eltSize != CharUnits::Zero()) { canBuildArray = false; break; } uint64_t idx = relOffset / eltSize; + + // A ZeroAttr whose size is an exact multiple of eltSize fills a run of + // array slots with zeros. These slots stay null and will be filled with + // the zero filler below. + if (mlir::isa<cir::ZeroAttr>(element)) { + CharUnits zeroSize = utils.getSize(element); + if (zeroSize % eltSize == CharUnits::Zero()) { + uint64_t count = zeroSize / eltSize; + if (idx + count <= numElements) + continue; + } + canBuildArray = false; + break; + } + + if (element.getType() != eltTy) { + canBuildArray = false; + break; + } if (idx >= numElements) { canBuildArray = false; break; diff --git a/clang/test/CIR/CodeGen/designated-init-update.c b/clang/test/CIR/CodeGen/designated-init-update.c index f1f591ed1098f..32a96d19f4395 100644 --- a/clang/test/CIR/CodeGen/designated-init-update.c +++ b/clang/test/CIR/CodeGen/designated-init-update.c @@ -108,40 +108,35 @@ struct R g8 = { (struct Q){{1, 2, 3}}, 5, .q.arr[7] = 99 }; // 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. -// -// 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. +// an array type) via the ZeroAttr branch of split(), then condensing back +// into a clean [20 x i32] array. 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 } +// CIR: cir.global external @g9 = #cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.int<99> : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i]> : !cir.array<!s32i x 20>}> : !rec_BigArrInner, #cir.int<7> : !s32i}> : !rec_BigArrOuter +// LLVM: @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 0, i32 0, i32 0, i32 0, i32 0, i32 0, i32 0, i32 99, i32 0, i32 0, i32 0, i32 0] }, i32 7 } +// 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 0, i32 0, 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. +// breaks apart at the override offset and reconstructs as a clean array. 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 } +// CIR: cir.global external @g10 = #cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.int<99> : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i, #cir.zero : !s32i]> : !cir.array<!s32i x 12>}> : !rec_AllZero, #cir.int<7> : !s32i}> : !rec_AllZeroOuter +// LLVM: @g10 = global %struct.AllZeroOuter { %struct.AllZero { [12 x i32] [i32 0, i32 0, i32 0, i32 0, i32 0, i32 99, i32 0, i32 0, i32 0, i32 0, i32 0, i32 0] }, i32 7 } // 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. +// {1,2,3} with an override at index 80. emitArrayConstant splits at 81 +// elements (the last non-zero) producing {[81 x i32], [19 x i32] zero}. 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{{.*}} +// CIR: cir.global external @g11 = #cir.const_record<{#cir.const_record<{#cir.const_record<{#cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i,{{( #cir.zero : !s32i,)+}} #cir.int<42> : !s32i]> : !cir.array<!s32i x 81>, #cir.zero : !cir.array<!s32i x 19>}> : !rec_anon_struct}> : !rec_anon_struct{{[0-9]*}}, #cir.int<7> : !s32i}> : !rec_anon_struct{{[0-9]*}} +// LLVM: @g11 = global { { <{ [81 x i32], [19 x i32] }> }, i32 } { { <{ [81 x i32], [19 x i32] }> } { <{ [81 x i32], [19 x i32] }> <{ [81 x i32] [i32 1, i32 2, i32 3,{{( i32 0,)+}} i32 42], [19 x i32] zeroinitializer }> }, i32 7 } +// OGCG: @g11 = global { { <{ [81 x i32], [19 x i32] }> }, i32 } { { <{ [81 x i32], [19 x i32] }> } { <{ [81 x i32], [19 x i32] }> <{ [81 x i32] [i32 1, i32 2, i32 3,{{( i32 0,)+}} i32 42], [19 x i32] zeroinitializer }> }, i32 7 } diff --git a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp index a5e84669d914d..aec12a364ea6d 100644 --- a/clang/test/CIR/CodeGen/paren-list-agg-init.cpp +++ b/clang/test/CIR/CodeGen/paren-list-agg-init.cpp @@ -134,13 +134,13 @@ constexpr auto a2 = static_cast<A>('c'); // CIR-DAG: cir.global "private" constant internal dso_local @_ZL2b1 = #cir.const_record<{#cir.const_record<{#cir.int<99> : !s8i, #cir.fp<0.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.int<0> : !s32i}> : ![[STRUCT_B]] {alignment = 8 : i64} constexpr B b1(A('c')); // LLVM-DAG: [[C1:@.*c1.*]] = internal constant { [[STRUCT_A]], i32, [4 x i8], i8, double, i32 } { [[STRUCT_A]] { i8 99, double 0.000000e+00 }, i32 0, [4 x i8] {{.*}}, i8 3, double 2.000000e+00, i32 0 }, align -// CIR-DAG: cir.global "private" constant internal dso_local @_ZL2c1 = #cir.const_record<{#cir.const_record<{#cir.int<99> : !s8i, #cir.fp<0.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.int<0> : !s32i, #cir.const_array<[#cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i]> : !cir.array<!u8i x 4>, #cir.int<3> : !s8i, #cir.fp<2.000000e+00> : !cir.double, #cir.int<0> : !s32i}> +// CIR-DAG: cir.global "private" constant internal dso_local @_ZL2c1 = #cir.const_record<{#cir.const_record<{#cir.int<99> : !s8i, #cir.fp<0.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.int<0> : !s32i, #cir.zero : !cir.array<!u8i x 4>, #cir.int<3> : !s8i, #cir.fp<2.000000e+00> : !cir.double, #cir.int<0> : !s32i}> constexpr C c1(b1, a1); // LLVM-DAG: [[U1:@.*]] = internal constant {{.*}} { [[STRUCT_A]] { i8 1, double 1.000000e+00 } }, align 8 // CIR-DAG: cir.global "private" constant internal dso_local @_ZL2u1 = #cir.const_record<{#cir.const_record<{#cir.int<1> : !s8i, #cir.fp<1.000000e+00> : !cir.double}> : ![[STRUCT_A]]}> : !{{.*}}{alignment = 8 : i64} constexpr U u1(A(1, 1)); // LLVM-DAG: [[D1:@.*d1.*]] = internal constant { [[STRUCT_A]], [[STRUCT_A]], [8 x i8], [[STRUCT_A]] } { [[STRUCT_A]] { i8 2, double 2.000000e+00 }, [[STRUCT_A]] { i8 2, double 2.000000e+00 }, [8 x i8] {{.*}}, [[STRUCT_A]] zeroinitializer }, align 8 -// CIR-DAG: cir.global "private" constant internal dso_local @_ZL2d1 = #cir.const_record<{#cir.const_record<{#cir.int<2> : !s8i, #cir.fp<2.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.const_record<{#cir.int<2> : !s8i, #cir.fp<2.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.const_array<[#cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i]> : !cir.array<!u8i x 8>, #cir.zero : ![[STRUCT_A]]}> +// CIR-DAG: cir.global "private" constant internal dso_local @_ZL2d1 = #cir.const_record<{#cir.const_record<{#cir.int<2> : !s8i, #cir.fp<2.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.const_record<{#cir.int<2> : !s8i, #cir.fp<2.000000e+00> : !cir.double}> : ![[STRUCT_A]], #cir.zero : !cir.array<!u8i x 8>, #cir.zero : ![[STRUCT_A]]}> constexpr D d1(A(2, 2)); // LLVM-DAG: [[ARR1:@.*arr1.*]] = internal constant [3 x i32] [i32 1, i32 2, i32 0], align 4 // CIR-DAG: cir.global "private" constant internal dso_local @_ZL4arr1 = #cir.const_array<[#cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<0> : !s32i]> : !cir.array<!s32i x 3> {alignment = 4 : i64} diff --git a/clang/test/CIR/CodeGen/union-agg-init.c b/clang/test/CIR/CodeGen/union-agg-init.c index caabc4c7e738f..4f2ad1bc54a7e 100644 --- a/clang/test/CIR/CodeGen/union-agg-init.c +++ b/clang/test/CIR/CodeGen/union-agg-init.c @@ -55,7 +55,7 @@ struct outer ret_outer() { // CIR-LABEL: ret_outer // CIR: %[[RET_ALLOCA:.*]] = cir.alloca !rec_outer, !cir.ptr<!rec_outer>, ["__retval", init] // CIR: %[[BITCAST:.*]] = cir.cast bitcast %0 : !cir.ptr<!rec_outer> -> !cir.ptr<!{{.*}}> - // CIR: %[[RECORD:.*]] = cir.const #cir.const_record<{#cir.zero : !{{.*}}, #cir.int<1> : !s32i, #cir.const_array<[#cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i, #cir.zero : !u8i]> : !cir.array<!u8i x 4>}> + // CIR: %[[RECORD:.*]] = cir.const #cir.const_record<{#cir.zero : !{{.*}}, #cir.int<1> : !s32i, #cir.zero : !cir.array<!u8i x 4>}> // CIR: cir.store {{.*}}%[[RECORD]], %[[BITCAST]] // LLVM-LABEL: ret_outer _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
