https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/148835
>From 49c3bd4e218ffeb3b8ccf4131da84fb4a70c7325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbae...@redhat.com> Date: Tue, 15 Jul 2025 13:14:57 +0200 Subject: [PATCH] [clang][bytecode] Make union activation more granular Only activate things if the syntactical structure suggests so. This adds a bunch of new opcodes to control whether to activate in stores, etc. Fixes #134789 --- clang/lib/AST/ByteCode/Compiler.cpp | 192 ++++++++++++++++++++------- clang/lib/AST/ByteCode/Compiler.h | 4 +- clang/lib/AST/ByteCode/Interp.cpp | 7 +- clang/lib/AST/ByteCode/Interp.h | 195 +++++++++++++++++++++++----- clang/lib/AST/ByteCode/Opcodes.td | 28 ++-- clang/lib/AST/ByteCode/Pointer.cpp | 11 +- clang/test/AST/ByteCode/cxx23.cpp | 19 +++ clang/test/AST/ByteCode/unions.cpp | 141 +++++++++++++++++++- 8 files changed, 494 insertions(+), 103 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index afa3b7ea7de7e..98479edc01a60 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -25,6 +25,34 @@ using APSInt = llvm::APSInt; namespace clang { namespace interp { +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()) + 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() && @@ -880,22 +908,11 @@ bool Compiler<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) { return this->VisitPointerArithBinOp(BO); } - // Assignments require us to evalute the RHS first. - if (BO->getOpcode() == BO_Assign) { - - if (!visit(RHS) || !visit(LHS)) - return false; - - // We don't support assignments in C. - if (!Ctx.getLangOpts().CPlusPlus && !this->emitInvalid(BO)) - return false; + if (BO->getOpcode() == BO_Assign) + return this->visitAssignment(LHS, RHS, BO); - if (!this->emitFlip(*LT, *RT, BO)) - return false; - } else { - if (!visit(LHS) || !visit(RHS)) - return false; - } + if (!visit(LHS) || !visit(RHS)) + return false; // For languages such as C, cast the result of one // of our comparision opcodes to T (which is usually int). @@ -946,22 +963,6 @@ bool Compiler<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) { if (BO->getType()->isFloatingType()) return Discard(this->emitDivf(getFPOptions(BO), BO)); return Discard(this->emitDiv(*T, BO)); - case BO_Assign: - if (DiscardResult) - return LHS->refersToBitField() ? this->emitStoreBitFieldPop(*T, BO) - : this->emitStorePop(*T, BO); - if (LHS->refersToBitField()) { - if (!this->emitStoreBitField(*T, BO)) - return false; - } else { - if (!this->emitStore(*T, BO)) - return false; - } - // Assignments aren't necessarily lvalues in C. - // Load from them in that case. - if (!BO->isLValue()) - return this->emitLoadPop(*T, BO); - return true; case BO_And: return Discard(this->emitBitAnd(*T, BO)); case BO_Or: @@ -1790,19 +1791,26 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, return this->delegate(Inits[0]); auto initPrimitiveField = [=](const Record::Field *FieldToInit, - const Expr *Init, PrimType T) -> bool { + const Expr *Init, PrimType T, + bool Activate = false) -> bool { InitStackScope<Emitter> ISS(this, isa<CXXDefaultInitExpr>(Init)); InitLinkScope<Emitter> ILS(this, InitLink::Field(FieldToInit->Offset)); if (!this->visit(Init)) return false; - if (FieldToInit->isBitField()) + bool BitField = FieldToInit->isBitField(); + if (BitField && Activate) + return this->emitInitBitFieldActivate(T, FieldToInit, E); + if (BitField) return this->emitInitBitField(T, FieldToInit, E); + if (Activate) + return this->emitInitFieldActivate(T, FieldToInit->Offset, E); return this->emitInitField(T, FieldToInit->Offset, E); }; auto initCompositeField = [=](const Record::Field *FieldToInit, - const Expr *Init) -> bool { + const Expr *Init, + bool Activate = false) -> bool { InitStackScope<Emitter> ISS(this, isa<CXXDefaultInitExpr>(Init)); InitLinkScope<Emitter> ILS(this, InitLink::Field(FieldToInit->Offset)); @@ -1810,6 +1818,10 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, // on the stack and recurse into visitInitializer(). if (!this->emitGetPtrField(FieldToInit->Offset, Init)) return false; + + if (Activate && !this->emitActivate(E)) + return false; + if (!this->visitInitializer(Init)) return false; return this->emitPopPtr(E); @@ -1829,10 +1841,10 @@ bool Compiler<Emitter>::visitInitList(ArrayRef<const Expr *> Inits, const Record::Field *FieldToInit = R->getField(FToInit); if (std::optional<PrimType> T = classify(Init)) { - if (!initPrimitiveField(FieldToInit, Init, *T)) + if (!initPrimitiveField(FieldToInit, Init, *T, /*Activate=*/true)) return false; } else { - if (!initCompositeField(FieldToInit, Init)) + if (!initCompositeField(FieldToInit, Init, /*Activate=*/true)) return false; } } @@ -2023,7 +2035,8 @@ bool Compiler<Emitter>::visitArrayElemInit(unsigned ElemIndex, const Expr *Init, template <class Emitter> bool Compiler<Emitter>::visitCallArgs(ArrayRef<const Expr *> Args, - const FunctionDecl *FuncDecl) { + const FunctionDecl *FuncDecl, + bool Activate) { assert(VarScope->getKind() == ScopeKind::Call); llvm::BitVector NonNullArgs = collectNonNullArgs(FuncDecl, Args); @@ -2046,6 +2059,11 @@ bool Compiler<Emitter>::visitCallArgs(ArrayRef<const Expr *> Args, return false; } + if (ArgIndex == 1 && Activate) { + if (!this->emitActivate(Arg)) + return false; + } + if (FuncDecl && NonNullArgs[ArgIndex]) { PrimType ArgT = classify(Arg).value_or(PT_Ptr); if (ArgT == PT_Ptr) { @@ -4227,10 +4245,13 @@ bool Compiler<Emitter>::visitZeroRecordInitializer(const Record *R, PrimType T = classifyPrim(D->getType()); if (!this->visitZeroInitializer(T, QT, E)) return false; + if (R->isUnion()) { + if (!this->emitInitFieldActivate(T, Field.Offset, E)) + return false; + break; + } if (!this->emitInitField(T, Field.Offset, E)) return false; - if (R->isUnion()) - break; continue; } @@ -4256,13 +4277,15 @@ bool Compiler<Emitter>::visitZeroRecordInitializer(const Record *R, } else return false; - if (!this->emitFinishInitPop(E)) - return false; - // C++11 [dcl.init]p5: If T is a (possibly cv-qualified) union type, the // object's first non-static named data member is zero-initialized - if (R->isUnion()) + if (R->isUnion()) { + if (!this->emitFinishInitActivatePop(E)) + return false; break; + } + if (!this->emitFinishInitPop(E)) + return false; } for (const Record::Base &B : R->bases()) { @@ -4325,6 +4348,61 @@ bool Compiler<Emitter>::visitZeroArrayInitializer(QualType T, const Expr *E) { return false; } +template <class Emitter> +bool Compiler<Emitter>::visitAssignment(const Expr *LHS, const Expr *RHS, + const Expr *E) { + std::optional<PrimType> T = classify(E->getType()); + + if (!T) + return false; + + if (!this->visit(RHS)) + return false; + if (!this->visit(LHS)) + return false; + + // We don't support assignments in C. + if (!Ctx.getLangOpts().CPlusPlus && !this->emitInvalid(E)) + return false; + + PrimType RHT = classifyPrim(RHS); + bool Activates = refersToUnion(LHS); + bool BitField = LHS->refersToBitField(); + + if (!this->emitFlip(PT_Ptr, RHT, E)) + return false; + + if (DiscardResult) { + if (BitField && Activates) + return this->emitStoreBitFieldActivatePop(RHT, E); + if (BitField) + return this->emitStoreBitFieldPop(RHT, E); + if (Activates) + return this->emitStoreActivatePop(RHT, E); + // Otherwise, regular non-activating store. + return this->emitStorePop(RHT, E); + } + + auto maybeLoad = [&](bool Result) -> bool { + if (!Result) + return false; + // Assignments aren't necessarily lvalues in C. + // Load from them in that case. + if (!E->isLValue()) + return this->emitLoadPop(RHT, E); + return true; + }; + + if (BitField && Activates) + return maybeLoad(this->emitStoreBitFieldActivate(RHT, E)); + if (BitField) + return maybeLoad(this->emitStoreBitField(RHT, E)); + if (Activates) + return maybeLoad(this->emitStoreActivate(RHT, E)); + // Otherwise, regular non-activating store. + return maybeLoad(this->emitStore(RHT, E)); +} + template <class Emitter> template <typename T> bool Compiler<Emitter>::emitConst(T Value, PrimType Ty, const Expr *E) { @@ -5067,7 +5145,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) { return false; } - if (!this->visitCallArgs(Args, FuncDecl)) + if (!this->visitCallArgs(Args, FuncDecl, IsAssignmentOperatorCall)) return false; // Undo the argument reversal we did earlier. @@ -5851,7 +5929,8 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { assert(!ReturnType); auto emitFieldInitializer = [&](const Record::Field *F, unsigned FieldOffset, - const Expr *InitExpr) -> bool { + const Expr *InitExpr, + bool Activate = false) -> bool { // We don't know what to do with these, so just return false. if (InitExpr->getType().isNull()) return false; @@ -5860,8 +5939,13 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { if (!this->visit(InitExpr)) return false; - if (F->isBitField()) + 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 @@ -5870,6 +5954,9 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { if (!this->emitGetPtrThisField(FieldOffset, InitExpr)) return false; + if (Activate && !this->emitActivate(InitExpr)) + return false; + if (!this->visitInitializer(InitExpr)) return false; @@ -5880,8 +5967,9 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { const Record *R = this->getRecord(RD); if (!R) return false; + bool IsUnion = R->isUnion(); - if (R->isUnion() && Ctor->isCopyOrMoveConstructor()) { + if (IsUnion && Ctor->isCopyOrMoveConstructor()) { if (R->getNumFields() == 0) return this->emitRetVoid(Ctor); // union copy and move ctors are special. @@ -5908,7 +5996,7 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { if (const FieldDecl *Member = Init->getMember()) { const Record::Field *F = R->getField(Member); - if (!emitFieldInitializer(F, F->Offset, InitExpr)) + if (!emitFieldInitializer(F, F->Offset, InitExpr, IsUnion)) return false; } else if (const Type *Base = Init->getBaseClass()) { const auto *BaseDecl = Base->getAsCXXRecordDecl(); @@ -5928,11 +6016,15 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { return false; } + if (IsUnion && !this->emitActivate(InitExpr)) + return false; + if (!this->visitInitializer(InitExpr)) return false; if (!this->emitFinishInitPop(InitExpr)) return false; } else if (const IndirectFieldDecl *IFD = Init->getIndirectMember()) { + assert(IFD->getChainingSize() >= 2); unsigned NestedFieldOffset = 0; @@ -5944,12 +6036,14 @@ bool Compiler<Emitter>::compileConstructor(const CXXConstructorDecl *Ctor) { NestedField = FieldRecord->getField(FD); assert(NestedField); + IsUnion = IsUnion || FieldRecord->isUnion(); NestedFieldOffset += NestedField->Offset; } assert(NestedField); - if (!emitFieldInitializer(NestedField, NestedFieldOffset, InitExpr)) + if (!emitFieldInitializer(NestedField, NestedFieldOffset, InitExpr, + IsUnion)) return false; // Mark all chain links as initialized. diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 05ba7460b343b..debee6726853b 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -307,7 +307,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, const Expr *E); bool visitArrayElemInit(unsigned ElemIndex, const Expr *Init, std::optional<PrimType> InitT); - bool visitCallArgs(ArrayRef<const Expr *> Args, const FunctionDecl *FuncDecl); + bool visitCallArgs(ArrayRef<const Expr *> Args, const FunctionDecl *FuncDecl, + bool Activate); /// Creates a local primitive value. unsigned allocateLocalPrimitive(DeclTy &&Decl, PrimType Ty, bool IsConst, @@ -342,6 +343,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>, bool visitZeroInitializer(PrimType T, QualType QT, const Expr *E); bool visitZeroRecordInitializer(const Record *R, const Expr *E); bool visitZeroArrayInitializer(QualType T, const Expr *E); + bool visitAssignment(const Expr *LHS, const Expr *RHS, const Expr *E); /// Emits an APSInt constant. bool emitConst(const llvm::APSInt &Value, PrimType Ty, const Expr *E); diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 457de2bed37d6..98fb8c8fcded5 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -326,7 +326,6 @@ bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, return true; assert(Ptr.inUnion()); - assert(Ptr.isField() && Ptr.getField()); Pointer U = Ptr.getBase(); Pointer C = Ptr; @@ -805,6 +804,8 @@ bool CheckStore(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { return false; if (!CheckRange(S, OpPC, Ptr, AK_Assign)) return false; + if (!CheckActive(S, OpPC, Ptr, AK_Assign)) + return false; if (!CheckGlobal(S, OpPC, Ptr)) return false; if (!CheckConst(S, OpPC, Ptr)) @@ -1500,7 +1501,6 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func, if (!CheckInvoke(S, OpPC, ThisPtr)) return cleanup(); if (!Func->isConstructor() && !Func->isDestructor() && - !Func->isCopyOrMoveOperator() && !CheckActive(S, OpPC, ThisPtr, AK_MemberCall)) return false; } @@ -1773,6 +1773,9 @@ bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E, std::optional<uint64_t> ArraySize) { const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (Ptr.inUnion() && Ptr.getBase().getRecord()->isUnion()) + Ptr.activate(); + // Similar to CheckStore(), but with the additional CheckTemporary() call and // the AccessKinds are different. if (!CheckTemporary(S, OpPC, Ptr, AK_Construct)) diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 9d17f96c97c85..6be68e4a978b5 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -1592,6 +1592,21 @@ bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) { if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(I); + assert(Field.canBeInitialized()); + Field.deref<T>() = S.Stk.pop<T>(); + Field.initialize(); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool InitThisFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) { + if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) + return false; + const Pointer &This = S.Current->getThis(); + if (!CheckThis(S, OpPC, This)) + return false; + const Pointer &Field = This.atField(I); + assert(Field.canBeInitialized()); Field.deref<T>() = S.Stk.pop<T>(); Field.activate(); Field.initialize(); @@ -1610,9 +1625,28 @@ bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F, if (!CheckThis(S, OpPC, This)) return false; const Pointer &Field = This.atField(FieldOffset); + assert(Field.canBeInitialized()); + const auto &Value = S.Stk.pop<T>(); + Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue()); + Field.initialize(); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool InitThisBitFieldActivate(InterpState &S, CodePtr OpPC, + const Record::Field *F, uint32_t FieldOffset) { + assert(F->isBitField()); + if (S.checkingPotentialConstantExpression() && S.Current->getDepth() == 0) + return false; + const Pointer &This = S.Current->getThis(); + if (!CheckThis(S, OpPC, This)) + return false; + const Pointer &Field = This.atField(FieldOffset); + assert(Field.canBeInitialized()); const auto &Value = S.Stk.pop<T>(); Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue()); Field.initialize(); + Field.activate(); return true; } @@ -1621,6 +1655,18 @@ bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F, /// 3) Pushes the value to field I of the pointer on the stack template <PrimType Name, class T = typename PrimConv<Name>::T> bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) { + const T &Value = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (!CheckRange(S, OpPC, Ptr, CSK_Field)) + return false; + const Pointer &Field = Ptr.atField(I); + Field.deref<T>() = Value; + Field.initialize(); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool InitFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) { const T &Value = S.Stk.pop<T>(); const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckRange(S, OpPC, Ptr, CSK_Field)) @@ -1638,6 +1684,32 @@ bool InitBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) { const T &Value = S.Stk.pop<T>(); const Pointer &Field = S.Stk.peek<Pointer>().atField(F->Offset); + if constexpr (needsAlloc<T>()) { + T Result = S.allocAP<T>(Value.bitWidth()); + if (T::isSigned()) + Result.copy(Value.toAPSInt() + .trunc(F->Decl->getBitWidthValue()) + .sextOrTrunc(Value.bitWidth())); + else + Result.copy(Value.toAPSInt() + .trunc(F->Decl->getBitWidthValue()) + .zextOrTrunc(Value.bitWidth())); + + Field.deref<T>() = Result; + } else { + Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue()); + } + Field.initialize(); + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool InitBitFieldActivate(InterpState &S, CodePtr OpPC, + const Record::Field *F) { + assert(F->isBitField()); + const T &Value = S.Stk.pop<T>(); + const Pointer &Field = S.Stk.peek<Pointer>().atField(F->Offset); + if constexpr (needsAlloc<T>()) { T Result = S.allocAP<T>(Value.bitWidth()); if (T::isSigned()) @@ -1695,32 +1767,6 @@ inline bool GetPtrThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { return true; } -inline bool GetPtrActiveField(InterpState &S, CodePtr OpPC, uint32_t Off) { - const Pointer &Ptr = S.Stk.pop<Pointer>(); - if (!CheckNull(S, OpPC, Ptr, CSK_Field)) - return false; - if (!CheckRange(S, OpPC, Ptr, CSK_Field)) - return false; - Pointer Field = Ptr.atField(Off); - Ptr.deactivate(); - Field.activate(); - S.Stk.push<Pointer>(std::move(Field)); - return true; -} - -inline bool GetPtrActiveThisField(InterpState &S, CodePtr OpPC, uint32_t Off) { - if (S.checkingPotentialConstantExpression()) - return false; - const Pointer &This = S.Current->getThis(); - if (!CheckThis(S, OpPC, This)) - return false; - Pointer Field = This.atField(Off); - This.deactivate(); - Field.activate(); - S.Stk.push<Pointer>(std::move(Field)); - return true; -} - inline bool GetPtrDerivedPop(InterpState &S, CodePtr OpPC, uint32_t Off, bool NullOK, const Type *TargetType) { const Pointer &Ptr = S.Stk.pop<Pointer>(); @@ -1816,6 +1862,20 @@ inline bool GetPtrThisBase(InterpState &S, CodePtr OpPC, uint32_t Off) { inline bool FinishInitPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); + if (Ptr.canBeInitialized()) + Ptr.initialize(); + return true; +} + +inline bool FinishInit(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (Ptr.canBeInitialized()) + Ptr.initialize(); + return true; +} + +inline bool FinishInitActivate(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.peek<Pointer>(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); @@ -1823,8 +1883,8 @@ inline bool FinishInitPop(InterpState &S, CodePtr OpPC) { return true; } -inline bool FinishInit(InterpState &S, CodePtr OpPC) { - const Pointer &Ptr = S.Stk.peek<Pointer>(); +inline bool FinishInitActivatePop(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.pop<Pointer>(); if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); @@ -1902,10 +1962,8 @@ bool Store(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; - if (Ptr.canBeInitialized()) { + if (Ptr.canBeInitialized()) Ptr.initialize(); - Ptr.activate(); - } Ptr.deref<T>() = Value; return true; } @@ -1916,10 +1974,46 @@ bool StorePop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (Ptr.canBeInitialized()) + Ptr.initialize(); + Ptr.deref<T>() = Value; + return true; +} + +static inline bool Activate(InterpState &S, CodePtr OpPC) { + const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (Ptr.canBeInitialized()) + Ptr.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>(); + const Pointer &Ptr = S.Stk.peek<Pointer>(); + + if (Ptr.canBeInitialized()) { + Ptr.initialize(); + Ptr.activate(); + } + + if (!CheckStore(S, OpPC, Ptr)) + return false; + Ptr.deref<T>() = Value; + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool StoreActivatePop(InterpState &S, CodePtr OpPC) { + const T &Value = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.pop<Pointer>(); + if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } + if (!CheckStore(S, OpPC, Ptr)) + return false; Ptr.deref<T>() = Value; return true; } @@ -1930,10 +2024,8 @@ bool StoreBitField(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; - if (Ptr.canBeInitialized()) { + if (Ptr.canBeInitialized()) Ptr.initialize(); - Ptr.activate(); - } if (const auto *FD = Ptr.getField()) Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue()); else @@ -1947,10 +2039,43 @@ bool StoreBitFieldPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckStore(S, OpPC, Ptr)) return false; + if (Ptr.canBeInitialized()) + Ptr.initialize(); + if (const auto *FD = Ptr.getField()) + Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue()); + else + Ptr.deref<T>() = Value; + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool StoreBitFieldActivate(InterpState &S, CodePtr OpPC) { + const T &Value = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (Ptr.canBeInitialized()) { + Ptr.initialize(); + Ptr.activate(); + } + if (!CheckStore(S, OpPC, Ptr)) + return false; + if (const auto *FD = Ptr.getField()) + Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue()); + else + Ptr.deref<T>() = Value; + return true; +} + +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool StoreBitFieldActivatePop(InterpState &S, CodePtr OpPC) { + const T &Value = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.pop<Pointer>(); + if (Ptr.canBeInitialized()) { Ptr.initialize(); Ptr.activate(); } + if (!CheckStore(S, OpPC, Ptr)) + return false; if (const auto *FD = Ptr.getField()) Ptr.deref<T>() = Value.truncate(FD->getBitWidthValue()); else @@ -1964,7 +2089,6 @@ bool Init(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.peek<Pointer>(); if (!CheckInit(S, OpPC, Ptr)) return false; - Ptr.activate(); Ptr.initialize(); new (&Ptr.deref<T>()) T(Value); return true; @@ -1976,7 +2100,6 @@ bool InitPop(InterpState &S, CodePtr OpPC) { const Pointer &Ptr = S.Stk.pop<Pointer>(); if (!CheckInit(S, OpPC, Ptr)) return false; - Ptr.activate(); Ptr.initialize(); new (&Ptr.deref<T>()) T(Value); return true; diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 57e01f7bd9da0..804853d29512e 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -315,10 +315,6 @@ def GetPtrGlobal : OffsetOpcode; // [Pointer] -> [Pointer] def GetPtrField : OffsetOpcode; def GetPtrFieldPop : OffsetOpcode; -// [Pointer] -> [Pointer] -def GetPtrActiveField : OffsetOpcode; -// [] -> [Pointer] -def GetPtrActiveThisField : OffsetOpcode; // [] -> [Pointer] def GetPtrThisField : OffsetOpcode; // [Pointer] -> [Pointer] @@ -330,9 +326,10 @@ def GetMemberPtrBasePop : Opcode { let Args = [ArgSint32]; } - def FinishInitPop : Opcode; -def FinishInit : Opcode; +def FinishInit : Opcode; +def FinishInitActivate : Opcode; +def FinishInitActivatePop : Opcode; def FinishInitGlobal : Opcode; def GetPtrDerivedPop : Opcode { let Args = [ArgUint32, ArgBool, ArgTypePtr]; } @@ -460,16 +457,24 @@ def SetThisField : AccessOpcode; // [Value] -> [] def InitThisField : AccessOpcode; +def InitThisFieldActivate : AccessOpcode; // [Value] -> [] def InitThisBitField : Opcode { let Types = [AluTypeClass]; let Args = [ArgRecordField, ArgUint32]; let HasGroup = 1; } +def InitThisBitFieldActivate : Opcode { + let Types = [AluTypeClass]; + let Args = [ArgRecordField, ArgUint32]; + let HasGroup = 1; +} // [Pointer, Value] -> [] def InitField : AccessOpcode; +def InitFieldActivate : AccessOpcode; // [Pointer, Value] -> [] def InitBitField : BitFieldOpcode; +def InitBitFieldActivate : BitFieldOpcode; //===----------------------------------------------------------------------===// // Pointer access @@ -495,15 +500,16 @@ class StoreBitFieldOpcode : Opcode { let HasGroup = 1; } -// [Pointer, Value] -> [Pointer] def Store : StoreOpcode {} -// [Pointer, Value] -> [] def StorePop : StoreOpcode {} - -// [Pointer, Value] -> [Pointer] +def StoreActivatePop : StoreOpcode {} +def StoreActivate : StoreOpcode {} def StoreBitField : StoreBitFieldOpcode {} -// [Pointer, Value] -> [] def StoreBitFieldPop : StoreBitFieldOpcode {} +def StoreBitFieldActivate : StoreBitFieldOpcode {} +def StoreBitFieldActivatePop : StoreBitFieldOpcode {} + +def Activate : Opcode {} // [Pointer, Value] -> [] def Init : StoreOpcode {} diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp index 159b4238a6a04..2f9ecf98e558e 100644 --- a/clang/lib/AST/ByteCode/Pointer.cpp +++ b/clang/lib/AST/ByteCode/Pointer.cpp @@ -502,8 +502,17 @@ void Pointer::activate() const { if (!getInlineDesc()->InUnion) return; - auto activate = [](Pointer &P) -> void { + std::function<void(Pointer &)> activate; + activate = [&activate](Pointer &P) -> void { P.getInlineDesc()->IsActive = true; + if (const Record *R = P.getRecord(); R && !R->isUnion()) { + for (const Record::Field &F : R->fields()) { + Pointer FieldPtr = P.atField(F.Offset); + if (!FieldPtr.getInlineDesc()->IsActive) + activate(FieldPtr); + } + // FIXME: Bases? + } }; std::function<void(Pointer &)> deactivate; diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp index 2856b872d44ab..45dd4f528aefb 100644 --- a/clang/test/AST/ByteCode/cxx23.cpp +++ b/clang/test/AST/ByteCode/cxx23.cpp @@ -369,7 +369,26 @@ namespace NestedUnions { return true; } static_assert(test_nested()); +} + +namespace UnionMemberCallDiags { + struct A { int n; }; + struct B { A a; }; + constexpr A a = (A() = B().a); + union C { + int n; + A a; + }; + constexpr bool g() { + C c = {.n = 1}; + c.a.operator=(B{2}.a); // all-note {{member call on member 'a' of union with active member 'n' is not allowed in a constant expression}} + return c.a.n == 2; + } + static_assert(g()); // all-error {{not an integral constant expression}} \ + // all-note {{in call to}} } + + #endif diff --git a/clang/test/AST/ByteCode/unions.cpp b/clang/test/AST/ByteCode/unions.cpp index f97990c1ff849..0fa44a259a4ff 100644 --- a/clang/test/AST/ByteCode/unions.cpp +++ b/clang/test/AST/ByteCode/unions.cpp @@ -3,6 +3,9 @@ // RUN: %clang_cc1 -verify=ref,both %s // RUN: %clang_cc1 -std=c++20 -verify=ref,both %s +#define assert_active(F) if (!__builtin_is_within_lifetime(&F)) (1/0); +#define assert_inactive(F) if ( __builtin_is_within_lifetime(&F)) (1/0); + union U { int a; int b; @@ -229,9 +232,24 @@ namespace Nested { } static_assert(foo2() == 10); - constexpr int foo3() { // both-error {{constexpr function never produces a constant expression}} + consteval int foo3() { // both-error {{function never produces a constant expression}} U2 u; + /// No active field. + assert_active(u); + assert_inactive(u.u); + assert_inactive(u.u2); + assert_inactive(u.x); + assert_inactive(u.y); + u.u.a = 10; + assert_active(u); + assert_active(u.u); + assert_active(u.u.a); + assert_inactive(u.u.b); + assert_inactive(u.u2); + assert_inactive(u.x); + assert_inactive(u.y); + int a = u.u.b; // both-note 2{{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} return 1; @@ -343,6 +361,21 @@ namespace IndirectField { static_assert(s2.f == 7, ""); } +namespace CtorActivatesFields { + struct TailClobberer { + constexpr TailClobberer() { b = false; } + bool b; + }; + + class expected { + union __union_t { + constexpr __union_t() : __unex_() {} + TailClobberer __unex_; + } __union_; + }; + constexpr expected y; +} + namespace CopyCtor { union U { int a; @@ -579,6 +612,7 @@ namespace MoveOrAssignOp { }; class F { + public: struct __long { min_pointer __data_; }; @@ -599,6 +633,11 @@ namespace MoveOrAssignOp { return true; } static_assert(foo()); + + constexpr F f2{}; + static_assert(__builtin_is_within_lifetime(&f2.__rep_)); + static_assert(__builtin_is_within_lifetime(&f2.__rep_.__l)); + static_assert(__builtin_is_within_lifetime(&f2.__rep_.__l.__data_)); } namespace CopyEmptyUnion { @@ -683,8 +722,6 @@ namespace AnonymousUnion { Long L; }; -#define assert_active(F) if (!__builtin_is_within_lifetime(&F)) (1/0); -#define assert_inactive(F) if ( __builtin_is_within_lifetime(&F)) (1/0); consteval int test() { union UU { struct { @@ -712,6 +749,104 @@ namespace AnonymousUnion { } static_assert(test() == 1); } + +namespace AccessViaPointer { + struct A { + int x; + int y; + int arr[3]; + union { int p, q; }; + }; + union B { + A a; + int b; + }; + + constexpr int write_wrong_member_indirect() { // both-error {{never produces a constant}} + B b = {.b = 1}; + int *p = &b.a.y; + + *p = 12; // both-note 2{{assignment to member 'a' of union with active member 'b'}} + + return *p; + } + static_assert(write_wrong_member_indirect() == 1); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} +} + +namespace Activation { + union U { + int a; + int b; + }; + + struct S { int& b; }; + + constexpr int foo() { // both-error {{never produces a constant expression}} + U u; + u.a = 10; + S s{u.b}; + + // LHS is a MemberExpr, but not of a union type. shouldn't activate u.b. + s.b = 12; // both-note 2{{assignment to member 'b' of union with active member 'a'}} + + return u.b; + + } + static_assert(foo() == 12); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} + + struct SS { + int a; + consteval SS() { + a = 10; + } + }; + + /// Activating the struct should also activate all the struct members. + consteval int structInUnion() { + union { + SS s; + int b; + } u{}; + + // assert_active(u.s); + // assert_active(u.s.a); + //assert_inactive(u.b); + + return u.s.a; + } + static_assert(structInUnion() == 10); + +} + +namespace Activation2 { + struct Base { + int y; + }; + struct A : Base { + int x; + int arr[3]; + union { int p, q; }; + }; + union B { + A a; + int b; + }; + + constexpr int change_member_indirectly() { + B b = {.b = 1}; + b.a.arr[1] = 1; + int &r = b.a.y; + r = 123; + + b.b = 2; + b.a.y = 3; + b.a.arr[2] = 4; + return b.a.arr[2]; + } + static_assert(change_member_indirectly() == 4); +} #endif namespace AddressComparison { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits