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

via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 28 03:12:06 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/3] [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/3] [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 3afef05d4ae1a44e7d084fc771883a85501d0a22 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/3] [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  | 58 +++++++++++++++++--
 .../test/CIR/CodeGen/designated-init-update.c | 11 ++++
 2 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
index fe970ac8f774c..d0f9551224495 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp
@@ -471,9 +471,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 +803,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 +1018,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 +1046,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 +1263,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 }



More information about the cfe-commits mailing list