https://github.com/AbdallahRashed updated 
https://github.com/llvm/llvm-project/pull/194238

>From ca71c0929a18e4bade46714f3fedaf927ad1302f Mon Sep 17 00:00:00 2001
From: AbdallahRashed <[email protected]>
Date: Wed, 8 Jul 2026 21:56:49 +0200
Subject: [PATCH] [CIR][CodeGen] Support DesignatedInitUpdateExpr in constant
 emission

Implement VisitDesignatedInitUpdateExpr in the ConstExprEmitter, which
handles constant aggregate updates from DesignatedInitUpdateExpr nodes.
This is used when a compound literal base is later modified by a
designated initializer (e.g., struct P g = {(struct S){1,2,3}, .s.b=9}).

The implementation works at the typed field/element level, compatible
with the new layout-based ConstRecordBuilder architecture:
- For records: decompose the base ConstRecordAttr into elements, apply
  updater overrides by CIR field index, and reassemble.
- For arrays: decompose the base ConstArrayAttr into elements, apply
  updater overrides by array index, and reassemble.

Both paths handle nested DesignatedInitUpdateExpr recursively (e.g.,
updating a field inside a nested struct inside an array).

Part of #192329
---
 clang/include/clang/CIR/MissingFeatures.h     |   1 +
 clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp  | 217 +++++++++++++++++-
 .../test/CIR/CodeGen/designated-init-update.c | 116 ++++++++++
 3 files changed, 331 insertions(+), 3 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 184cb833b3ffb..0a44865b33b1b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -205,6 +205,7 @@ struct MissingFeatures {
   static bool cleanupAfterErrorDiags() { return false; }
   static bool cleanupDeactivationScope() { return false; }
   static bool cleanupsToDeactivate() { return false; }
+  static bool constEmitterAbstractForMemory() { 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 b3651960bd2cb..40dba266e07d1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -426,6 +426,216 @@ mlir::Attribute buildRecord(ConstantEmitter &emitter, 
const APValue &val,
 }
 } // namespace ConstRecordBuilder
 
+//===----------------------------------------------------------------------===//
+//                         DesignatedInitUpdateExpr
+//===----------------------------------------------------------------------===//
+
+// Forward declaration.
+static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
+                                      CIRGenModule &cgm, QualType type,
+                                      mlir::Attribute &base,
+                                      const InitListExpr *updater);
+
+/// Apply a DesignatedInitUpdateExpr's updater InitListExpr to an existing
+/// record constant. The record's fields are decomposed, modified according to
+/// the updater, and reassembled.
+static bool updateRecord(ConstantEmitter &emitter, CIRGenModule &cgm,
+                         const RecordDecl *rd, mlir::Attribute &base,
+                         const InitListExpr *updater) {
+  CIRGenBuilderTy &builder = cgm.getBuilder();
+  const CIRGenRecordLayout &cirLayout =
+      cgm.getTypes().getCIRGenRecordLayout(rd);
+  cir::RecordType recordTy = cirLayout.getCIRType();
+
+  // TODO: Handle unions if needed.
+  if (rd->isUnion()) {
+    cgm.errorNYI("updateRecord: union type");
+    return false;
+  }
+
+  // Decompose the base record into mutable elements.
+  llvm::SmallVector<mlir::Attribute> elements;
+  if (auto recAttr = mlir::dyn_cast<cir::ConstRecordAttr>(base)) {
+    for (mlir::Attribute m : recAttr.getMembers())
+      elements.push_back(m);
+  } else if (mlir::isa<cir::ZeroAttr>(base)) {
+    // Zero-initialized base: fill with per-field zero attrs.
+    elements.resize(recordTy.getNumElements());
+    for (unsigned i = 0; i < recordTy.getNumElements(); ++i)
+      elements[i] = builder.getZeroInitAttr(recordTy.getElementType(i));
+  } else if (mlir::isa<cir::ConstVectorAttr>(base) ||
+             mlir::isa<cir::PoisonAttr>(base)) {
+    cgm.errorNYI("updateRecord: ConstVectorAttr or PoisonAttr base");
+    return false;
+  } else {
+    cgm.errorNYI("updateRecord: unsupported base attribute kind");
+    return false;
+  }
+
+  // Classic codegen uses separate FieldNo (for layout) and ElementNo (for
+  // updater inits). Unnamed bitfields are not represented in the InitListExpr,
+  // so we must not increment elementNo for them.
+  unsigned elementNo = 0;
+  for (const FieldDecl *field : rd->fields()) {
+    if (field->isUnnamedBitField())
+      continue;
+
+    if (elementNo >= updater->getNumInits())
+      break;
+
+    const Expr *init = updater->getInit(elementNo);
+    ++elementNo;
+
+    if (isa<NoInitExpr>(init))
+      continue;
+
+    if (!cirLayout.hasCIRField(field))
+      continue;
+
+    unsigned fieldIdx = cirLayout.getCIRFieldNo(field);
+
+    // When the updater contains a nested InitListExpr for a sub-aggregate,
+    // it represents additional overwriting of the current value (not a new
+    // independent constant).
+    if ((field->getType()->isArrayType() || field->getType()->isRecordType())) 
{
+      if (auto *subILE = dyn_cast<InitListExpr>(init)) {
+        if (!emitDesignatedInitUpdater(emitter, cgm, field->getType(),
+                                       elements[fieldIdx], subILE))
+          return false;
+        continue;
+      }
+      // For non-InitListExpr aggregate inits (e.g. compound literals),
+      // fall through to the regular emission below.
+    }
+
+    mlir::Attribute eltAttr =
+        emitter.tryEmitPrivateForMemory(init, field->getType());
+    if (!eltAttr)
+      return false;
+
+    if (field->isBitField()) {
+      elements[fieldIdx] = ConstRecordBuilder::setBitfieldInit(
+          cgm, cirLayout, builder, field, elements[fieldIdx], eltAttr);
+    } else {
+      elements[fieldIdx] = eltAttr;
+    }
+  }
+
+  base = builder.getConstRecordOrZeroAttr(builder.getArrayAttr(elements),
+                                          recordTy);
+  return true;
+}
+
+/// Apply a DesignatedInitUpdateExpr's updater InitListExpr to an existing
+/// array constant. Individual array elements are modified according to
+/// the updater.
+static bool updateArray(ConstantEmitter &emitter, CIRGenModule &cgm,
+                        QualType type, mlir::Attribute &base,
+                        const InitListExpr *updater) {
+  CIRGenBuilderTy &builder = cgm.getBuilder();
+  auto cat = cgm.getASTContext().getAsConstantArrayType(type);
+  if (!cat)
+    return false;
+
+  QualType elemType = cat->getElementType();
+  uint64_t numElements = cat->getZExtSize();
+
+  // Decompose the base array into mutable elements. We only store the
+  // explicitly initialized elements; any beyond this are implicitly zero
+  // and will be represented via trailing zeros in the final ConstArrayAttr.
+  llvm::SmallVector<mlir::Attribute> elements;
+
+  if (auto arrAttr = mlir::dyn_cast<cir::ConstArrayAttr>(base)) {
+    auto eltsAttr = mlir::dyn_cast<mlir::ArrayAttr>(arrAttr.getElts());
+    if (!eltsAttr) {
+      cgm.errorNYI("updateArray: string literal array base");
+      return false;
+    }
+    elements.reserve(eltsAttr.size());
+    elements.insert(elements.begin(), eltsAttr.begin(), eltsAttr.end());
+  } else if (mlir::isa<cir::ZeroAttr>(base)) {
+    // All zeros — elements stays empty, trailing zeros covers everything.
+  } else {
+    cgm.errorNYI("updateArray: unsupported base attribute kind");
+    return false;
+  }
+
+  // Apply the filler if present.
+  mlir::Attribute filler;
+  if (const Expr *fillerExpr = updater->getArrayFiller()) {
+    if (!isa<NoInitExpr>(fillerExpr)) {
+      // Classic codegen calls tryEmitAbstractForMemory here. We haven't
+      // implemented that in CIR yet.
+      assert(!cir::MissingFeatures::constEmitterAbstractForMemory());
+      filler = emitter.tryEmitPrivateForMemory(fillerExpr, elemType);
+      if (!filler)
+        return false;
+    }
+  }
+
+  // Helper to ensure elements vector is large enough for index i, filling
+  // any gaps with zero-initialized elements.
+  mlir::Type eltTy = cgm.convertType(elemType);
+  auto ensureElementAt = [&](unsigned i) {
+    if (i >= elements.size())
+      elements.resize(i + 1, builder.getZeroInitAttr(eltTy));
+  };
+
+  unsigned numElementsToUpdate = filler ? numElements : updater->getNumInits();
+  for (unsigned i = 0; i != numElementsToUpdate; ++i) {
+    const Expr *init = nullptr;
+    if (i < updater->getNumInits())
+      init = updater->getInit(i);
+
+    if (!init && filler) {
+      ensureElementAt(i);
+      elements[i] = filler;
+    } else if (!init || isa<NoInitExpr>(init)) {
+      continue;
+    } else if (auto *childILE = dyn_cast<InitListExpr>(init)) {
+      ensureElementAt(i);
+      if (!emitDesignatedInitUpdater(emitter, cgm, elemType, elements[i],
+                                     childILE))
+        return false;
+    } else {
+      mlir::Attribute val = emitter.tryEmitPrivateForMemory(init, elemType);
+      if (!val)
+        return false;
+      ensureElementAt(i);
+      elements[i] = val;
+    }
+  }
+
+  // Rebuild the array attr. Elements beyond our vector are implicitly zero
+  // via trailing_zeros in ConstArrayAttr. Trim explicit trailing zeros.
+  cir::ArrayType desiredType =
+      mlir::cast<cir::ArrayType>(cgm.convertType(type));
+  while (!elements.empty() && builder.isNullValue(elements.back()))
+    elements.pop_back();
+
+  if (elements.empty()) {
+    base = cir::ZeroAttr::get(desiredType);
+  } else {
+    base = cir::ConstArrayAttr::get(
+        desiredType, mlir::ArrayAttr::get(builder.getContext(), elements));
+  }
+  return true;
+}
+
+/// Dispatch to the record or array update path based on type.
+static bool emitDesignatedInitUpdater(ConstantEmitter &emitter,
+                                      CIRGenModule &cgm, QualType type,
+                                      mlir::Attribute &base,
+                                      const InitListExpr *updater) {
+  if (type->isRecordType())
+    return updateRecord(emitter, cgm, type->castAsRecordDecl(), base, updater);
+
+  if (type->isArrayType())
+    return updateArray(emitter, cgm, type, base, updater);
+
+  return false;
+}
+
 
//===----------------------------------------------------------------------===//
 //                             ConstExprEmitter
 
//===----------------------------------------------------------------------===//
@@ -637,9 +847,10 @@ class ConstExprEmitter
     if (!c)
       return {};
 
-    cgm.errorNYI(e->getBeginLoc(),
-                 "ConstExprEmitter::VisitDesignatedInitUpdateExpr");
-    return {};
+    if (!emitDesignatedInitUpdater(emitter, cgm, destType, c, e->getUpdater()))
+      return {};
+
+    return c;
   }
 
   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..aef405271db71
--- /dev/null
+++ b/clang/test/CIR/CodeGen/designated-init-update.c
@@ -0,0 +1,116 @@
+// 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-prefixes=LLVM,ALL --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefixes=OGCG,ALL --input-file=%t.ll %s
+
+struct S {
+  int a, b, c;
+};
+
+// 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
+// ALL: @g1 = global %struct.S { i32 1, i32 20, i32 3 }
+
+// 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
+// ALL: @g2 = global %struct.S { i32 100, i32 20, i32 300 }
+
+// Nested struct, folded by Sema.
+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
+// ALL: @g3 = global %struct.Outer { %struct.S { i32 1, i32 50, i32 3 }, i32 4 
}
+
+// From here on: cases that produce a DesignatedInitUpdateExpr.
+
+// g4: compound-literal sub-record + later record-field override.
+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
+// ALL: @g4 = global %struct.P { %struct.S { i32 1, i32 9, i32 3 }, i32 4 }
+
+// g5: compound-literal sub-record + later array-element override.
+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
+// ALL: @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
+// through an anonymous struct.
+struct Base {
+  struct {
+    int A;
+  };
+};
+struct Derived {
+  struct Base B;
+};
+struct Derived g6 = { {}, .B.A = 42 };
+// CIR: cir.global external @g6 = 
#cir.const_record<{#cir.const_record<{#cir.const_record<{#cir.int<42> : !s32i}> 
: !rec_anon{{.*}}}> : !rec_Base}> : !rec_Derived
+// ALL: @g6 = global %struct.Derived { %struct.Base { %struct.anon{{.*}} { i32 
42 } } }
+
+// g7: array-of-array element override. Exercises nested array update.
+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
+// ALL: @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: trailing zeros + single override in a 10-element array.
+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], 
trailing_zeros> : !cir.array<!s32i x 10>}> : !rec_Q, #cir.int<5> : !s32i}> : 
!rec_R
+// ALL: @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 (20-element array, override at [15]).
+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.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<0> : !s32i, 
#cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : 
!s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, 
#cir.int<99> : !s32i], trailing_zeros> : !cir.array<!s32i x 20>}> : 
!rec_BigArrInner, #cir.int<7> : !s32i}> : !rec_BigArrOuter
+// ALL: @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.
+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_array<[#cir.int<0> : !s32i, 
#cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : 
!s32i, #cir.int<99> : !s32i], trailing_zeros> : !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 }
+// Note: OGCG produces a byte-packed struct for all-zero bases; our typed
+// array output is better. Only verify the values are present.
+// OGCG: @g10 = global { { { [20 x i8], i32, [24 x i8] } }, i32 }
+
+// g11: very large array (100 elements), override at index 80.
+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.const_record<{#cir.const_array<[#cir.int<1> : !s32i, 
#cir.int<2> : !s32i, #cir.int<3> : !s32i{{.*}}#cir.int<42> : !s32i], 
trailing_zeros> : !cir.array<!s32i x 100>}> : !rec_Many, #cir.int<7> : !s32i}> 
: !rec_ManyOuter
+// LLVM: @g11 = global %struct.ManyOuter { %struct.Many { [100 x i32] [i32 1, 
i32 2, i32 3,{{.*}} i32 42,{{.*}}] }, i32 7 }
+// Note: OGCG produces a packed struct <{[81 x i32], [19 x i32]}>; our
+// typed [100 x i32] array output is better.
+// OGCG: @g11 = global { { <{ [81 x i32], [19 x i32] }> }, i32 }
+
+// g12: unnamed bitfield - tests that elementNo skips unnamed bitfields
+// correctly (they are not represented in the InitListExpr).
+struct WithBitfield {
+  int a;
+  int : 16;
+  int b;
+  int c;
+};
+struct BFOuter { struct WithBitfield wbf; int x; };
+struct BFOuter g12 = { (struct WithBitfield){1, 2, 3}, 4, .wbf.b = 99 };
+// LLVM: @g12 = global %struct.BFOuter { %struct.WithBitfield { i32 1, i16 0, 
i32 99, i32 3 }, i32 4 }
+// OGCG: @g12 = global { { i32, [4 x i8], i32, i32 }, i32 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to