Author: Yihan Wang Date: 2026-09-17T19:41:09+08:00 New Revision: f4f7257be543b02dd786d0d2be7b18cce4d266db
URL: https://github.com/llvm/llvm-project/commit/f4f7257be543b02dd786d0d2be7b18cce4d266db DIFF: https://github.com/llvm/llvm-project/commit/f4f7257be543b02dd786d0d2be7b18cce4d266db.diff LOG: [clang][bytecode] Give a discarded composite prvalue a result object (#219952) A default member initializer can refer to previously initialized subobjects, so `this` has to denote something while it is evaluated. A discarded composite prvalue had no result object, so the interpreter rejected code the legacy evaluator accepts: ```cpp struct A { int &x; constexpr ~A() { x = 0; } }; struct B { int &x; const A &a = A{x}; }; constexpr int f() { int x = 1; B{x}; return x; } constexpr int r = f(); // error: implicit use of 'this' pointer static_assert(r == 0); // FXIME: 'r' shouble be 0, it's a bug in clang, see https://github.com/llvm/llvm-project/issues/85601. ``` https://godbolt.org/z/rb993Ef9d This patch materialize a result object for a discarded composite prvalue and initialize into it. A discarded list of primitive type keeps discarding its initializers, as there is no object to establish there. Also let an explicit initializer nested in a default member initializer see the surrounding initializer's `this` reconstruction. Signed-off-by: yronglin <[email protected]> Co-authored-by: Timm Bäder <[email protected]> Added: Modified: clang/lib/AST/ByteCode/Compiler.cpp clang/test/AST/ByteCode/records.cpp Removed: ################################################################################ diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 569a4b1c39d46..d7bdb2f217a9a 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -268,7 +268,9 @@ template <class Emitter> class InitStackScope final { public: InitStackScope(Compiler<Emitter> *Ctx, bool Active) : Ctx(Ctx), OldValue(Ctx->InitStackActive), Active(Active) { - Ctx->InitStackActive = Active; + // An explicit initializer nested in a default member initializer still + // needs the surrounding default initializer's `this` reconstruction. + Ctx->InitStackActive = OldValue || Active; if (Active) Ctx->InitStack.push_back(InitLink::DIE()); } @@ -2316,24 +2318,38 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, return this->emitInvalid(E); } - // Handle discarding first. - if (DiscardResult) { - for (const Expr *Init : Inits) { - if (!this->discard(Init)) - return false; - } - return true; - } - - // Primitive values. + // Primitive values. A discarded one can simply discard each initializer; + // there is no object to establish. if (OptPrimType T = classify(QT)) { - assert(!DiscardResult); + if (DiscardResult) { + for (const Expr *Init : Inits) { + if (!this->discard(Init)) + return false; + } + return true; + } if (Inits.size() == 0) return this->visitZeroInitializer(*T, QT, E); assert(Inits.size() == 1); return this->delegate(Inits[0]); } + assert(!canClassify(E->getType())); + + // A composite prvalue needs somewhere to live even when it is discarded: a + // default member initializer may read subobjects initialized earlier in this + // same list, so those have to actually be written and `this` has to denote + // the object. Materialize one and initialize into it. + if (DiscardResult && !Initializing) { + UnsignedOrNone LocalIndex = allocateLocal(E); + if (!LocalIndex) + return false; + if (!this->emitGetPtrLocal(*LocalIndex, E)) + return false; + InitLinkScope<Emitter> ILS2(this, InitLink::Temp(*LocalIndex)); + return this->visitInitializerPop(E); + } + if (QT->isRecordType()) { const Record *R = getRecord(QT); diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp index 90e7c1eb6ec64..daba582f82fb5 100644 --- a/clang/test/AST/ByteCode/records.cpp +++ b/clang/test/AST/ByteCode/records.cpp @@ -1302,11 +1302,36 @@ namespace { }; constexpr int a() { int x = 1; - int f = B{x}.x; + { + B b{x}; + } + return x; + } + static_assert(a() == 0); + + constexpr int discarded() { + int x = 1; B{x}; // both-warning {{expression result unused}} + return x; + } - return 1; + /// Before the result object was allocated, this could not be evaluated at + /// all. The temporary 'B' is not destroyed until the end of the enclosing + /// full-expression, so 'x' still reads 1 here, matching legacy evaluator. + /// + /// FIXME: See https://github.com/llvm/llvm-project/issues/85601. + static_assert(discarded() == 1); + + /// A const-qualified composite result is writable while under construction. + constexpr int decrement(int &x) { + return --x; } + struct DMIConstComposite { + int a; + int b = decrement(a); + }; + constexpr DMIConstComposite c{1}; + static_assert(c.a == 0); } #endif _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
