https://github.com/efriedma-quic updated 
https://github.com/llvm/llvm-project/pull/196427

>From db10d77410fa485c9b47b7178cd9780961fb3c28 Mon Sep 17 00:00:00 2001
From: Eli Friedman <[email protected]>
Date: Thu, 7 May 2026 14:44:28 -0700
Subject: [PATCH 1/2] [clang] Implement constexpr DesignatedInitUpdateExpr.

DesignatedInitUpdateExpr exists to handle some obscure edge cases in C,
where the usual InitListExpr canonicalization can't be performed.
Previously, we didn't need constant evaluation for this, but C23
constexpr means we need to evaluate this before codegen.

Implementation is straightforward: just need to evaluate the two
subexperssions, in order, and skip any NoInitExprs.

Fixes #193373.
---
 clang/lib/AST/ByteCode/Compiler.cpp | 21 ++++++++++++++++++++-
 clang/lib/AST/ByteCode/Compiler.h   |  1 +
 clang/lib/AST/ExprConstant.cpp      | 18 ++++++++++++++++++
 clang/test/Sema/constexpr.c         | 14 ++++++++++++++
 4 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index 59510612d9617..d747dff081ef4 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -2193,6 +2193,13 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const 
Expr *> Inits,
              R->getField(InitIndex)->isUnnamedBitField())
         ++InitIndex;
 
+      // If this is a child of a DesignatedInitUpdateExpr, skip elements which
+      // aren't supposed to be modified.
+      if (isa<NoInitExpr>(Init)) {
+        ++InitIndex;
+        continue;
+      }
+
       if (OptPrimType T = classify(Init)) {
         const Record::Field *FieldToInit = R->getField(InitIndex);
         if (!initPrimitiveField(FieldToInit, Init, *T))
@@ -2256,6 +2263,10 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const 
Expr *> Inits,
         };
         if (!EmbedS->doForEachDataElement(Eval, ElementIndex))
           return false;
+      } else if (isa<NoInitExpr>(Init)) {
+        // If this is a child of a DesignatedInitUpdateExpr, skip elements 
which
+        // aren't supposed to be modified.
+        ++ElementIndex;
       } else {
         if (!this->visitArrayElemInit(ElementIndex, Init, InitT))
           return false;
@@ -2265,7 +2276,7 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr 
*> Inits,
 
     // Expand the filler expression.
     // FIXME: This should go away.
-    if (ArrayFiller) {
+    if (ArrayFiller && !isa<NoInitExpr>(ArrayFiller)) {
       for (; ElementIndex != NumElems; ++ElementIndex) {
         if (!this->visitArrayElemInit(ElementIndex, ArrayFiller, InitT))
           return false;
@@ -7633,6 +7644,14 @@ bool Compiler<Emitter>::VisitDeclRefExpr(const 
DeclRefExpr *E) {
   return this->visitDeclRef(D, E);
 }
 
+template <class Emitter>
+bool Compiler<Emitter>::VisitDesignatedInitUpdateExpr(const 
DesignatedInitUpdateExpr *E) {
+  assert(E->getType()->isRecordType());
+  if (!this->visitInitializer(E->getBase()))
+    return false;
+  return this->visitInitializer(E->getUpdater());
+}
+
 template <class Emitter> bool Compiler<Emitter>::emitCleanup() {
   for (VariableScope<Emitter> *C = VarScope; C; C = C->getParent()) {
     if (!C->destroyLocals())
diff --git a/clang/lib/AST/ByteCode/Compiler.h 
b/clang/lib/AST/ByteCode/Compiler.h
index 4a70db89dba74..45cda575b000e 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -232,6 +232,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, 
bool>,
   bool VisitCXXTypeidExpr(const CXXTypeidExpr *E);
   bool VisitObjCDictionaryLiteral(const ObjCDictionaryLiteral *E);
   bool VisitObjCArrayLiteral(const ObjCArrayLiteral *E);
+  bool VisitDesignatedInitUpdateExpr(const DesignatedInitUpdateExpr *E);
 
   // Statements.
   bool visitCompoundStmt(const CompoundStmt *S);
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 4f45fa728c605..b26afb4449484 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -11065,6 +11065,7 @@ namespace {
     bool VisitCXXParenListInitExpr(const CXXParenListInitExpr *E);
     bool VisitCXXParenListOrInitListExpr(const Expr *ExprToVisit,
                                          ArrayRef<Expr *> Args);
+    bool VisitDesignatedInitUpdateExpr(const DesignatedInitUpdateExpr *E);
   };
 }
 
@@ -11325,6 +11326,11 @@ bool 
RecordExprEvaluator::VisitCXXParenListOrInitListExpr(
     ImplicitValueInitExpr VIE(HaveInit ? Info.Ctx.IntTy : Field->getType());
     const Expr *Init = HaveInit ? Args[ElementNo++] : &VIE;
 
+    // If this is a child of a DesignatedInitUpdateExpr, skip elements which
+    // aren't supposed to be modified.
+    if (isa<NoInitExpr>(Init))
+      continue;
+
     if (Field->getType()->isIncompleteArrayType()) {
       if (auto *CAT = Info.Ctx.getAsConstantArrayType(Init->getType())) {
         if (!CAT->isZeroSize()) {
@@ -11522,6 +11528,13 @@ bool RecordExprEvaluator::VisitLambdaExpr(const 
LambdaExpr *E) {
   return Success;
 }
 
+bool RecordExprEvaluator::VisitDesignatedInitUpdateExpr(
+    const DesignatedInitUpdateExpr *E) {
+  if (!Visit(E->getBase()))
+    return false;
+  return Visit(E->getUpdater());
+}
+
 static bool EvaluateRecord(const Expr *E, const LValue &This,
                            APValue &Result, EvalInfo &Info) {
   assert(!E->isValueDependent());
@@ -15074,6 +15087,11 @@ bool 
ArrayExprEvaluator::VisitCXXParenListOrInitListExpr(
     if (Init->isValueDependent())
       return EvaluateDependentExpr(Init, Info);
 
+    // If this is a child of a DesignatedInitUpdateExpr, skip elements which
+    // aren't supposed to be modified.
+    if (isa<NoInitExpr>(Init))
+      return true;
+
     if (!EvaluateInPlace(Result.getArrayInitializedElt(ArrayIndex), Info,
                          Subobject, Init) ||
         !HandleLValueArrayAdjustment(Info, Init, Subobject,
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
index 0b8de906e1838..79f9f877af12f 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -431,3 +431,17 @@ int gh173605(int x) {
   static int justincase = justincase; // expected-error {{initializer element 
is not a compile-time constant}}
   return x;
 }
+
+
+struct designated_init_A { int a; int b; int c : 2; double d; int e[5]; };
+struct designated_init_B { struct designated_init_A a; int y; };
+constexpr struct designated_init_A designated_init_a = {1, 2};
+constexpr struct designated_init_B designated_init_b =
+  {designated_init_a, .a.a = 3, .a.c = 4, .a.d = 5.0, .a.e[1] = 6, .y = 7}; // 
expected-warning 4 {{initializer partially overrides prior initialization of 
this subobject}} // expected-note 4 {{previous initialization}}
+static_assert(designated_init_b.a.a == 3);
+static_assert(designated_init_b.a.b == 2);
+static_assert(designated_init_b.a.c == 0);
+static_assert(designated_init_b.a.d == 5.0); // expected-warning {{folding it 
to a constant is a GNU extension}}
+static_assert(designated_init_b.a.e[0] == 0); // expected-warning {{folding it 
to a constant is a GNU extension}}
+static_assert(designated_init_b.a.e[1] == 6); // expected-warning {{folding it 
to a constant is a GNU extension}}
+static_assert(designated_init_b.y == 7);

>From 6ad9894e15bca21d94516a572eaaa8bd1c002705 Mon Sep 17 00:00:00 2001
From: Eli Friedman <[email protected]>
Date: Thu, 7 May 2026 17:41:41 -0700
Subject: [PATCH 2/2] Fix format.  Fix an edge case I didn't consider properly.

---
 clang/lib/AST/ByteCode/Compiler.cpp           |  3 +-
 clang/lib/AST/ExprConstant.cpp                | 48 +++++++++++++------
 clang/test/Sema/constexpr.c                   | 16 +++----
 .../SemaCXX/cxx2c-constexpr-placement-new.cpp | 10 ++++
 4 files changed, 53 insertions(+), 24 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index d747dff081ef4..d569c230522f6 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7645,7 +7645,8 @@ bool Compiler<Emitter>::VisitDeclRefExpr(const 
DeclRefExpr *E) {
 }
 
 template <class Emitter>
-bool Compiler<Emitter>::VisitDesignatedInitUpdateExpr(const 
DesignatedInitUpdateExpr *E) {
+bool Compiler<Emitter>::VisitDesignatedInitUpdateExpr(
+    const DesignatedInitUpdateExpr *E) {
   assert(E->getType()->isRecordType());
   if (!this->visitInitializer(E->getBase()))
     return false;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index b26afb4449484..d8f02aa20cd71 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -15044,12 +15044,6 @@ bool 
ArrayExprEvaluator::VisitCXXParenListOrInitListExpr(
 
   bool Success = true;
 
-  assert((!Result.isArray() || Result.getArrayInitializedElts() == 0) &&
-         "zero-initialized array shouldn't have any initialized elts");
-  APValue Filler;
-  if (Result.isArray() && Result.hasArrayFiller())
-    Filler = Result.getArrayFiller();
-
   unsigned NumEltsToInit = Args.size();
   unsigned NumElts = CAT->getZExtSize();
 
@@ -15059,26 +15053,50 @@ bool 
ArrayExprEvaluator::VisitCXXParenListOrInitListExpr(
       MaybeElementDependentArrayFiller(ArrayFiller)) {
     NumEltsToInit = NumElts;
   } else {
+    // Add additional elements represented by EmbedExpr.
     for (auto *Init : Args) {
       if (auto *EmbedS = dyn_cast<EmbedExpr>(Init->IgnoreParenImpCasts()))
         NumEltsToInit += EmbedS->getDataElementCount() - 1;
     }
+    // If we have extra elements in the list, they will be discarded.
     if (NumEltsToInit > NumElts)
       NumEltsToInit = NumElts;
+    // If we're overwriting memory which already has an object, make sure we
+    // don't reduce the number of non-filler elements.  (It's possible to
+    // optimize this in some cases, but the logic gets really complicated.)
+    if (Result.hasValue() && NumEltsToInit < Result.getArrayInitializedElts())
+      NumEltsToInit = Result.getArrayInitializedElts();
   }
 
   LLVM_DEBUG(llvm::dbgs() << "The number of elements to initialize: "
                           << NumEltsToInit << ".\n");
 
-  Result = APValue(APValue::UninitArray(), NumEltsToInit, NumElts);
-
-  // If the array was previously zero-initialized, preserve the
-  // zero-initialized values.
-  if (Filler.hasValue()) {
-    for (unsigned I = 0, E = Result.getArrayInitializedElts(); I != E; ++I)
-      Result.getArrayInitializedElt(I) = Filler;
-    if (Result.hasArrayFiller())
-      Result.getArrayFiller() = Filler;
+  if (!Result.hasValue()) {
+    Result = APValue(APValue::UninitArray(), NumEltsToInit, NumElts);
+  } else if (Result.getArrayInitializedElts() != NumEltsToInit) {
+    // Number of inititalized elts changed. Recreate the APValue, and copy over
+    // the relevant elements.  (This is essentially just fixing the internal
+    // representation of the value, because it's tied to the number of
+    // non-filler elements.)
+    //
+    // This should be hit rarely, but there are some edge cases:
+    //
+    // - The array could be zero-initialized.
+    // - There could be a DesignatedInitListExpr.
+    // - operator new[] can be used to start the lifetime early.
+    APValue NewResult = APValue(APValue::UninitArray(), NumEltsToInit, 
NumElts);
+    // First copy existing elements.
+    unsigned NumOldElts = Result.getArrayInitializedElts();
+    for (unsigned I = 0; I < NumOldElts; ++I) {
+      NewResult.getArrayInitializedElt(I) =
+          std::move(Result.getArrayInitializedElt(I));
+    }
+    // Then copy the array filler over the remaining elements.
+    for (unsigned I = Result.getArrayInitializedElts(); I < NumEltsToInit; ++I)
+      NewResult.getArrayInitializedElt(I) = Result.getArrayFiller();
+    if (NewResult.hasArrayFiller() && Result.hasArrayFiller())
+      NewResult.getArrayFiller() = Result.getArrayFiller();
+    Result = std::move(NewResult);
   }
 
   LValue Subobject = This;
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
index 79f9f877af12f..905819ae470f8 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -435,13 +435,13 @@ int gh173605(int x) {
 
 struct designated_init_A { int a; int b; int c : 2; double d; int e[5]; };
 struct designated_init_B { struct designated_init_A a; int y; };
-constexpr struct designated_init_A designated_init_a = {1, 2};
+constexpr struct designated_init_A designated_init_a = {1, 2, 3, 4.0, 5, 6, 7, 
8, 9};
 constexpr struct designated_init_B designated_init_b =
-  {designated_init_a, .a.a = 3, .a.c = 4, .a.d = 5.0, .a.e[1] = 6, .y = 7}; // 
expected-warning 4 {{initializer partially overrides prior initialization of 
this subobject}} // expected-note 4 {{previous initialization}}
-static_assert(designated_init_b.a.a == 3);
+  {designated_init_a, .a.a = 10, .a.c = 11, .a.d = 12.0, .a.e[1] = 13, .y = 
14}; // expected-warning 4 {{initializer partially overrides prior 
initialization of this subobject}} // expected-note 4 {{previous 
initialization}}
+static_assert(designated_init_b.a.a == 10);
 static_assert(designated_init_b.a.b == 2);
-static_assert(designated_init_b.a.c == 0);
-static_assert(designated_init_b.a.d == 5.0); // expected-warning {{folding it 
to a constant is a GNU extension}}
-static_assert(designated_init_b.a.e[0] == 0); // expected-warning {{folding it 
to a constant is a GNU extension}}
-static_assert(designated_init_b.a.e[1] == 6); // expected-warning {{folding it 
to a constant is a GNU extension}}
-static_assert(designated_init_b.y == 7);
+static_assert(designated_init_b.a.c == -1);
+static_assert(designated_init_b.a.d == 12.0); // expected-warning {{folding it 
to a constant is a GNU extension}}
+static_assert(designated_init_b.a.e[0] == 5); // expected-warning {{folding it 
to a constant is a GNU extension}}
+static_assert(designated_init_b.a.e[1] == 13); // expected-warning {{folding 
it to a constant is a GNU extension}}
+static_assert(designated_init_b.y == 14);
diff --git a/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp 
b/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
index 4cf0e9ffe1d64..6ffd746e4a14d 100644
--- a/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
+++ b/clang/test/SemaCXX/cxx2c-constexpr-placement-new.cpp
@@ -154,3 +154,13 @@ namespace ModifyMutableMember {
   }
   static_assert(modify_mutable_member() == 12);
 }
+
+namespace NewDuringInit {
+  // Make sure an InitListExpr can overwrite an array initialized by
+  // placement new.
+  constexpr int f() {
+    struct X { int a, b[5]; } x = {(new(&x.b) int[5])[1]=12,15};
+    return x.b[1];
+  }
+  static_assert(f() == 0);
+}

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

Reply via email to