[clang] [CIR][CodeGen] Support DesignatedInitUpdateExpr in constant emission (PR #194238)

via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 27 13:06:20 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] [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 }



More information about the cfe-commits mailing list