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
