From 97bbf081aab778c9dadcc912f4a79fa0a27eed92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Tue, 22 Jul 2025 06:59:02 +0200 Subject: [PATCH] [clang][bytecode] Activate primitive fields before initializing them The initializer itself might need the field to be active. --- clang/lib/AST/ByteCode/Compiler.cpp | 89 ++++++++++++++++------------- clang/lib/AST/ByteCode/Compiler.h | 2 + clang/lib/AST/ByteCode/Interp.h | 10 ++++ clang/lib/AST/ByteCode/Opcodes.td | 1 + clang/test/AST/ByteCode/unions.cpp | 17 +++++- 5 files changed, 77 insertions(+), 42 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e760055a8d235..63ac536c2b445 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -25,41 +25,6 @@ using APSInt = llvm::APSInt; namespace clang { namespace interp { -static bool hasTrivialDefaultCtorParent(const FieldDecl *FD) { - assert(FD); - assert(FD->getParent()->isUnion()); - const auto *CXXRD = dyn_cast(FD->getParent()); - return !CXXRD || CXXRD->hasTrivialDefaultConstructor(); -} - -static bool refersToUnion(const Expr *E) { - for (;;) { - if (const auto *ME = dyn_cast(E)) { - if (const auto *FD = dyn_cast(ME->getMemberDecl()); - FD && FD->getParent()->isUnion() && hasTrivialDefaultCtorParent(FD)) - return true; - E = ME->getBase(); - continue; - } - - if (const auto *ASE = dyn_cast(E)) { - E = ASE->getBase()->IgnoreImplicit(); - continue; - } - - if (const auto *ICE = dyn_cast(E); - ICE && (ICE->getCastKind() == CK_NoOp || - ICE->getCastKind() == CK_DerivedToBase || - ICE->getCastKind() == CK_UncheckedDerivedToBase)) { - E = ICE->getSubExpr(); - continue; - } - - break; - } - return false; -} - static std::optional getBoolValue(const Expr *E) { if (const auto *CE = dyn_cast_if_present(E); CE && CE->hasAPValueResult() && @@ -5408,6 +5373,53 @@ bool Compiler::maybeEmitDeferredVarInit(const VarDecl *VD) { return true; } +static bool hasTrivialDefaultCtorParent(const FieldDecl *FD) { + assert(FD); + assert(FD->getParent()->isUnion()); + const auto *CXXRD = dyn_cast(FD->getParent()); + return !CXXRD || CXXRD->hasTrivialDefaultConstructor(); +} + +template bool Compiler::refersToUnion(const Expr *E) { + for (;;) { + if (const auto *ME = dyn_cast(E)) { + if (const auto *FD = dyn_cast(ME->getMemberDecl()); + FD && FD->getParent()->isUnion() && hasTrivialDefaultCtorParent(FD)) + return true; + E = ME->getBase(); + continue; + } + + if (const auto *ASE = dyn_cast(E)) { + E = ASE->getBase()->IgnoreImplicit(); + continue; + } + + if (const auto *ICE = dyn_cast(E); + ICE && (ICE->getCastKind() == CK_NoOp || + ICE->getCastKind() == CK_DerivedToBase || + ICE->getCastKind() == CK_UncheckedDerivedToBase)) { + E = ICE->getSubExpr(); + continue; + } + + if (const auto *This = dyn_cast(E)) { + const auto *ThisRecord = + This->getType()->getPointeeType()->getAsRecordDecl(); + if (!ThisRecord->isUnion()) + return false; + // Otherwise, always activate if we're in the ctor. + if (const auto *Ctor = + dyn_cast_if_present(CompilingFunction)) + return Ctor->getParent() == ThisRecord; + return false; + } + + break; + } + return false; +} + template bool Compiler::visitDeclStmt(const DeclStmt *DS, bool EvaluateConditionDecl) { @@ -5940,16 +5952,15 @@ bool Compiler::compileConstructor(const CXXConstructorDecl *Ctor) { return false; if (OptPrimType T = this->classify(InitExpr)) { + if (Activate && !this->emitActivateThisField(FieldOffset, InitExpr)) + return false; + if (!this->visit(InitExpr)) return false; bool BitField = F->isBitField(); - if (BitField && Activate) - return this->emitInitThisBitFieldActivate(*T, F, FieldOffset, InitExpr); if (BitField) return this->emitInitThisBitField(*T, F, FieldOffset, InitExpr); - if (Activate) - return this->emitInitThisFieldActivate(*T, FieldOffset, InitExpr); return this->emitInitThisField(*T, FieldOffset, InitExpr); } // Non-primitive case. Get a pointer to the field-to-initialize diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 503269399c757..3a263429a0e15 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -401,6 +401,8 @@ class Compiler : public ConstStmtVisitor, bool>, bool checkLiteralType(const Expr *E); bool maybeEmitDeferredVarInit(const VarDecl *VD); + bool refersToUnion(const Expr *E); + protected: /// Variable to storage mapping. llvm::DenseMap Locals; diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index b42c7665c3a35..9012442943d59 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -1983,6 +1983,16 @@ static inline bool Activate(InterpState &S, CodePtr OpPC) { return true; } +static inline bool ActivateThisField(InterpState &S, CodePtr OpPC, uint32_t I) { + if (S.checkingPotentialConstantExpression()) + return false; + + const Pointer &Ptr = S.Current->getThis(); + assert(Ptr.atField(I).canBeInitialized()); + Ptr.atField(I).activate(); + return true; +} + template ::T> bool StoreActivate(InterpState &S, CodePtr OpPC) { const T &Value = S.Stk.pop(); diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 80703ad72d954..abfed77750f87 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -510,6 +510,7 @@ def StoreBitFieldActivate : StoreBitFieldOpcode {} def StoreBitFieldActivatePop : StoreBitFieldOpcode {} def Activate : Opcode {} +def ActivateThisField : Opcode { let Args = [ArgUint32]; } // [Pointer, Value] -> [] def Init : StoreOpcode {} diff --git a/clang/test/AST/ByteCode/unions.cpp b/clang/test/AST/ByteCode/unions.cpp index ae71776353cf0..139e318e1ba68 100644 --- a/clang/test/AST/ByteCode/unions.cpp +++ b/clang/test/AST/ByteCode/unions.cpp @@ -79,10 +79,9 @@ namespace DefaultInit { constexpr U1 u1; /// OK. - constexpr int foo() { // expected-error {{never produces a constant expression}} + constexpr int foo() { U1 u; - return u.a; // both-note {{read of member 'a' of union with active member 'b'}} \ - // expected-note {{read of member 'a' of union with active member 'b'}} + return u.a; // both-note {{read of member 'a' of union with active member 'b'}} } static_assert(foo() == 42); // both-error {{not an integral constant expression}} \ // both-note {{in call to}} @@ -916,6 +915,18 @@ namespace NonTrivialCtor { } +namespace PrimitiveFieldInitActivates { + /// The initializer of a needs the field to be active _before_ it's visited. + template struct X {}; + union V { + int a, b; + constexpr V(X<0>) : a(a = 1) {} // ok + constexpr V(X<2>) : a() { b = 1; } // ok + }; + constinit V v0 = X<0>(); + constinit V v2 = X<2>(); +} + #endif namespace AddressComparison {