Skip to content

[clang][bytecode] Activate primitive fields before initializing them #149963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 50 additions & 39 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CXXRecordDecl>(FD->getParent());
return !CXXRD || CXXRD->hasTrivialDefaultConstructor();
}

static bool refersToUnion(const Expr *E) {
for (;;) {
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl());
FD && FD->getParent()->isUnion() && hasTrivialDefaultCtorParent(FD))
return true;
E = ME->getBase();
continue;
}

if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
E = ASE->getBase()->IgnoreImplicit();
continue;
}

if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E);
ICE && (ICE->getCastKind() == CK_NoOp ||
ICE->getCastKind() == CK_DerivedToBase ||
ICE->getCastKind() == CK_UncheckedDerivedToBase)) {
E = ICE->getSubExpr();
continue;
}

break;
}
return false;
}

static std::optional<bool> getBoolValue(const Expr *E) {
if (const auto *CE = dyn_cast_if_present<ConstantExpr>(E);
CE && CE->hasAPValueResult() &&
Expand Down Expand Up @@ -5408,6 +5373,53 @@ bool Compiler<Emitter>::maybeEmitDeferredVarInit(const VarDecl *VD) {
return true;
}

static bool hasTrivialDefaultCtorParent(const FieldDecl *FD) {
assert(FD);
assert(FD->getParent()->isUnion());
const auto *CXXRD = dyn_cast<CXXRecordDecl>(FD->getParent());
return !CXXRD || CXXRD->hasTrivialDefaultConstructor();
}

template <class Emitter> bool Compiler<Emitter>::refersToUnion(const Expr *E) {
for (;;) {
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl());
FD && FD->getParent()->isUnion() && hasTrivialDefaultCtorParent(FD))
return true;
E = ME->getBase();
continue;
}

if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
E = ASE->getBase()->IgnoreImplicit();
continue;
}

if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E);
ICE && (ICE->getCastKind() == CK_NoOp ||
ICE->getCastKind() == CK_DerivedToBase ||
ICE->getCastKind() == CK_UncheckedDerivedToBase)) {
E = ICE->getSubExpr();
continue;
}

if (const auto *This = dyn_cast<CXXThisExpr>(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<CXXConstructorDecl>(CompilingFunction))
return Ctor->getParent() == ThisRecord;
return false;
}

break;
}
return false;
}

template <class Emitter>
bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS,
bool EvaluateConditionDecl) {
Expand Down Expand Up @@ -5940,16 +5952,15 @@ bool Compiler<Emitter>::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
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool checkLiteralType(const Expr *E);
bool maybeEmitDeferredVarInit(const VarDecl *VD);

bool refersToUnion(const Expr *E);

protected:
/// Variable to storage mapping.
llvm::DenseMap<const ValueDecl *, Scope::Local> Locals;
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/AST/ByteCode/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PrimType Name, class T = typename PrimConv<Name>::T>
bool StoreActivate(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ByteCode/Opcodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ def StoreBitFieldActivate : StoreBitFieldOpcode {}
def StoreBitFieldActivatePop : StoreBitFieldOpcode {}

def Activate : Opcode {}
def ActivateThisField : Opcode { let Args = [ArgUint32]; }

// [Pointer, Value] -> []
def Init : StoreOpcode {}
Expand Down
17 changes: 14 additions & 3 deletions clang/test/AST/ByteCode/unions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down Expand Up @@ -916,6 +915,18 @@ namespace NonTrivialCtor {

}

namespace PrimitiveFieldInitActivates {
/// The initializer of a needs the field to be active _before_ it's visited.
template<int> 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 {
Expand Down