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 1/2] [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 >From abd057526afcd71378113032c5984736c864c8a3 Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Mon, 31 Aug 2026 06:09:34 -0700 Subject: [PATCH 2/2] [clang] Add a flag to init-list node to specifiy whether it's initializes an object weith default-mem-init Signed-off-by: yronglin <[email protected]> --- clang/include/clang/AST/Expr.h | 24 ++++++++++ clang/include/clang/AST/ExprCXX.h | 8 ++++ clang/include/clang/AST/Stmt.h | 5 ++ clang/lib/AST/ByteCode/Compiler.cpp | 57 ++++++----------------- clang/lib/AST/ByteCode/Compiler.h | 6 --- clang/lib/AST/Expr.cpp | 9 ++++ clang/lib/Sema/SemaInit.cpp | 7 +++ clang/lib/Serialization/ASTReaderStmt.cpp | 3 ++ clang/lib/Serialization/ASTWriterStmt.cpp | 2 + 9 files changed, 73 insertions(+), 48 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 535086a6c2aa3..2699a08d6ef18 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -479,6 +479,24 @@ class Expr : public ValueStmt { /// an aspect of the value-kind type system. bool refersToBitField() const { return getObjectKind() == OK_BitField; } + /// Whether this is a list-initialization (an \c InitListExpr or a + /// \c CXXParenListInitExpr) that initializes an object using a default + /// member initializer, that is, one of whose initializers is a + /// \c CXXDefaultInitExpr. + /// + /// Within such an initializer, \c this denotes the object that this + /// list-initialization initializes, not the instance pointer of the + /// enclosing member function: + /// + /// \code + /// struct S { int x; int y = this->x; }; + /// int foo() { return S{10}.y; } // `this` denotes the `S{10}` object + /// \endcode + /// + /// Sema sets this when it fills the list in, so that consumers do not each + /// have to re-derive which list-initializations establish such an object. + bool initializesObjectWithDefaultMemberInit() const; + /// If this expression refers to a bit-field, retrieve the /// declaration of that bit-field. /// @@ -5380,6 +5398,7 @@ class InitListExpr : public Expr { explicit InitListExpr(EmptyShell Empty) : Expr(InitListExprClass, Empty), AltForm(nullptr, true) { InitListExprBits.IsExplicit = false; + InitListExprBits.InitializesObjectWithDefaultMemberInit = false; } unsigned getNumInits() const { return InitExprs.size(); } @@ -5494,6 +5513,11 @@ class InitListExpr : public Expr { // locations). Implicit InitListExpr's are created by the semantic analyzer. bool isExplicit() const { return InitListExprBits.IsExplicit; } + /// See Expr::initializesObjectWithDefaultMemberInit(). + void setInitializesObjectWithDefaultMemberInit(bool V = true) { + InitListExprBits.InitializesObjectWithDefaultMemberInit = V; + } + /// Is this an initializer for an array of characters, initialized by a string /// literal or an @encode? bool isStringLiteralInit() const; diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index bb790b1100e7f..9e6956a9d703d 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -5193,9 +5193,12 @@ class CXXParenListInitExpr final friend class TrailingObjects; friend class ASTStmtReader; friend class ASTStmtWriter; + friend class Expr; unsigned NumExprs; unsigned NumUserSpecifiedExprs; + /// See Expr::initializesObjectWithDefaultMemberInit(). + bool InitializesObjectWithDefaultMemberInit = false; SourceLocation InitLoc, LParenLoc, RParenLoc; llvm::PointerUnion<Expr *, FieldDecl *> ArrayFillerOrUnionFieldInit; @@ -5229,6 +5232,11 @@ class CXXParenListInitExpr final void updateDependence() { setDependence(computeDependence(this)); } + /// See Expr::initializesObjectWithDefaultMemberInit(). + void setInitializesObjectWithDefaultMemberInit(bool V = true) { + InitializesObjectWithDefaultMemberInit = V; + } + MutableArrayRef<Expr *> getInitExprs() { return getTrailingObjects(NumExprs); } diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 5d27ded64082d..8f8ee9ec2350c 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -670,6 +670,7 @@ class alignas(void *) Stmt { class InitListExprBitfields { friend class ASTStmtReader; + friend class Expr; friend class InitListExpr; LLVM_PREFERRED_TYPE(ExprBitfields) @@ -682,6 +683,10 @@ class alignas(void *) Stmt { // Whether this list is explicitly written in the source (with braces). LLVM_PREFERRED_TYPE(bool) unsigned IsExplicit : 1; + /// Whether this list initializes an object using a default member + /// initializer. See Expr::initializesObjectWithDefaultMemberInit(). + LLVM_PREFERRED_TYPE(bool) + unsigned InitializesObjectWithDefaultMemberInit : 1; }; class ParenListExprBitfields { diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index cf4945a3f2ff6..ab0b0ea590539 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -50,22 +50,6 @@ 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: @@ -2328,6 +2312,21 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, // Handle discarding first. if (DiscardResult) { + // A default member initializer may read subobjects initialized earlier in + // this same list, so `this` has to denote something and those subobjects + // have to actually be written. Materialize a result object and initialize + // into it instead of discarding each initializer on its own. + if (E->initializesObjectWithDefaultMemberInit()) { + UnsignedOrNone LocalIndex = + allocateLocal(E, QualType(), ScopeKind::FullExpression); + if (!LocalIndex) + return false; + InitLinkScope<Emitter> ResultScope(this, InitLink::Temp(*LocalIndex)); + if (!this->emitGetPtrLocal(*LocalIndex, E)) + return false; + return this->visitInitializerPop(E); + } + for (const Expr *Init : Inits) { if (!this->discard(Init)) return false; @@ -3493,9 +3492,6 @@ 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); } @@ -4916,30 +4912,7 @@ 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); diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 6e22f57749e43..f34809cd0f14c 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -321,12 +321,6 @@ 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/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index 6ce0a29aa3bd7..a1720596ea315 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -2443,6 +2443,7 @@ InitListExpr::InitListExpr(const ASTContext &C, SourceLocation lbraceloc, sawArrayRangeDesignator(false); InitExprs.insert(C, InitExprs.end(), initExprs.begin(), initExprs.end()); InitListExprBits.IsExplicit = isExplicit; + InitListExprBits.InitializesObjectWithDefaultMemberInit = false; setDependence(computeDependence(this)); } @@ -2595,6 +2596,14 @@ static bool IsDecompositionDeclRefExpr(const Expr *E) { return isa_and_nonnull<DecompositionDecl>(Ref->getDecl()); } +bool Expr::initializesObjectWithDefaultMemberInit() const { + if (const auto *ILE = dyn_cast<InitListExpr>(this)) + return ILE->InitListExprBits.InitializesObjectWithDefaultMemberInit; + if (const auto *PLIE = dyn_cast<CXXParenListInitExpr>(this)) + return PLIE->InitializesObjectWithDefaultMemberInit; + return false; +} + bool Expr::isReadIfDiscardedInCPlusPlus11() const { // In C++11, discarded-value expressions of a certain form are special, // according to [expr]p10: diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 48ce51863c2c0..db52b77d9989a 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -835,6 +835,9 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field, return; } SemaRef.checkInitializerLifetime(MemberEntity, DIE.get()); + // Record that `this` within the default member initializer denotes the + // object this list initializes. + ILE->setInitializesObjectWithDefaultMemberInit(); if (Init < NumInits) ILE->setInit(Init, DIE.get()); else { @@ -5967,6 +5970,7 @@ static void TryOrBuildParenListInitialization( QualType ResultType; Expr *ArrayFiller = nullptr; FieldDecl *InitializedFieldInUnion = nullptr; + bool UsesDefaultMemberInit = false; auto HandleInitializedEntity = [&](const InitializedEntity &SubEntity, const InitializationKind &SubKind, @@ -6158,6 +6162,7 @@ static void TryOrBuildParenListInitialization( return; S.checkInitializerLifetime(SubEntity, DIE.get()); InitExprs.push_back(DIE.get()); + UsesDefaultMemberInit = true; } } else { // C++ [dcl.init]p17.6.2.2 @@ -6211,6 +6216,8 @@ static void TryOrBuildParenListInitialization( CPLIE->setArrayFiller(ArrayFiller); if (InitializedFieldInUnion) CPLIE->setInitializedFieldInUnion(InitializedFieldInUnion); + if (UsesDefaultMemberInit) + CPLIE->setInitializesObjectWithDefaultMemberInit(); *Result = CPLIE; S.Diag(Kind.getLocation(), diag::warn_cxx17_compat_aggregate_init_paren_list) diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 92c555dc427b3..a60de794596ee 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -1281,6 +1281,8 @@ void ASTStmtReader::VisitInitListExpr(InitListExpr *E) { E->updateInit(Record.getContext(), I, Record.readSubExpr()); } E->InitListExprBits.IsExplicit = Record.readBool(); + E->InitListExprBits.InitializesObjectWithDefaultMemberInit = + Record.readBool(); } void ASTStmtReader::VisitDesignatedInitExpr(DesignatedInitExpr *E) { @@ -2402,6 +2404,7 @@ void ASTStmtReader::VisitCXXParenListInitExpr(CXXParenListInitExpr *E) { E->setInitializedFieldInUnion(readDeclAs<FieldDecl>()); } } + E->InitializesObjectWithDefaultMemberInit = Record.readBool(); E->updateDependence(); } diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 513c832c38ffd..0d5bd86fb70d6 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -1289,6 +1289,7 @@ void ASTStmtWriter::VisitInitListExpr(InitListExpr *E) { Record.AddStmt(E->getInit(I)); } Record.writeBool(E->isExplicit()); + Record.writeBool(E->initializesObjectWithDefaultMemberInit()); Code = serialization::EXPR_INIT_LIST; } @@ -2474,6 +2475,7 @@ void ASTStmtWriter::VisitCXXParenListInitExpr(CXXParenListInitExpr *E) { else Record.AddDeclRef(UnionField); } + Record.writeBool(E->initializesObjectWithDefaultMemberInit()); Code = serialization::EXPR_CXX_PAREN_LIST_INIT; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
