https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/219952
>From dac444c62ab46acea74331b31a1fdc4e1756959f Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Mon, 31 Aug 2026 04:41:14 -0700 Subject: [PATCH] [clang][bytecode] Give a discarded composite prvalue a result object Signed-off-by: yronglin <[email protected]> --- clang/lib/AST/ByteCode/Compiler.cpp | 62 ++++++++++++++++++++++++++++- clang/lib/AST/ByteCode/Compiler.h | 6 +++ clang/test/AST/ByteCode/records.cpp | 29 +++++++++++++- 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index c182639ea07f8..cf4945a3f2ff6 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -50,6 +50,22 @@ static bool isSideEffectFree(const Expr *E) { return false; } +static bool containsDefaultInitExpr(const Expr *E) { + class Finder final : public ConstDynamicRecursiveASTVisitor { + public: + Finder() { ShouldVisitImplicitCode = true; } + + bool VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *) override { + Found = true; + return true; + } + + bool Found = false; + } F; + F.TraverseStmt(E); + return F.Found; +} + /// Scope chain managing the variable lifetimes. template <class Emitter> class VariableScope { public: @@ -265,7 +281,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()); } @@ -3475,6 +3493,9 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) { LocalScope<Emitter> ES(this, ScopeKind::FullExpression); const Expr *SubExpr = E->getSubExpr(); + if (DiscardResult && this->discardNeedsResultObject(SubExpr)) + return this->discardIntoResultObject(SubExpr) && ES.destroyLocals(E); + return this->delegate(SubExpr) && ES.destroyLocals(E); } @@ -3541,8 +3562,12 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( // Non-primitive values. if (!this->emitGetPtrGlobal(*GlobalIndex, E)) return false; + if (!this->emitStartInit(E)) + return false; if (!this->visitInitializer(Inner)) return false; + if (!this->emitEndInit(E)) + return false; if (IsStatic) { assert(TempDecl); return this->emitInitGlobalTempComp(TempDecl, E); @@ -3585,7 +3610,11 @@ bool Compiler<Emitter>::VisitMaterializeTemporaryExpr( if (!this->emitGetPtrLocal(*LocalIndex, E)) return false; - return this->visitInitializer(Inner); + if (!this->emitStartInit(E)) + return false; + if (!this->visitInitializer(Inner)) + return false; + return this->emitEndInit(E); } return false; } @@ -4887,7 +4916,30 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) { return BS.destroyLocals(); } +template <class Emitter> +bool Compiler<Emitter>::discardNeedsResultObject(const Expr *E) const { + return !E->isGLValue() && !canClassify(E->getType()) && + containsDefaultInitExpr(E); +} + +template <class Emitter> +bool Compiler<Emitter>::discardIntoResultObject(const Expr *E) { + UnsignedOrNone LocalIndex = + allocateLocal(E, QualType(), ScopeKind::FullExpression); + if (!LocalIndex) + return false; + InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex)); + if (!this->emitGetPtrLocal(*LocalIndex, E)) + return false; + return this->visitInitializerPop(E); +} + template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) { + // Let an ExprWithCleanups establish its full-expression scope first; it + // allocates the result object itself. + if (!isa<ExprWithCleanups>(E) && this->discardNeedsResultObject(E)) + return this->discardIntoResultObject(E); + OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true, /*NewInitializing=*/false, /*ToLValue=*/false); return this->Visit(E); @@ -5451,8 +5503,14 @@ bool Compiler<Emitter>::visitExpr(const Expr *E, bool DestroyToplevelScope) { if (!this->emitGetPtrLocal(*LocalOffset, E)) return false; + // A const-qualified result object is writable while it is being + // initialized, just like an object evaluated through visitVarDecl(). + if (!this->emitStartInit(E)) + return false; if (!visitInitializer(E)) return false; + if (!this->emitEndInit(E)) + return false; // We are destroying the locals AFTER the Ret op. // The Ret op needs to copy the (alive) values, but the // destructors may still turn the entire expression invalid. diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index f34809cd0f14c..6e22f57749e43 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -321,6 +321,12 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool visitAsLValue(const Expr *E); /// Evaluates an expression for side effects and discards the result. bool discard(const Expr *E); + /// Whether discarding \p E still requires a result object: a composite + /// prvalue whose default member initializer may refer to previously + /// initialized subobjects, so that `this` has something to denote. + bool discardNeedsResultObject(const Expr *E) const; + /// Allocate that result object and initialize \p E into it. + bool discardIntoResultObject(const Expr *E); /// Just pass evaluation on to \p E. This leaves all the parsing flags /// intact. bool delegate(const Expr *E); diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp index 36b5cb62fe95f..e522072802308 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
