https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/173333
>From 575e860c925036bd9d5686bfb18ef85baa174c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]> Date: Sat, 13 Dec 2025 19:22:50 +0100 Subject: [PATCH] asdf --- clang/lib/AST/ByteCode/Descriptor.h | 1 - clang/lib/AST/ByteCode/InitMap.cpp | 29 ++++++- clang/lib/AST/ByteCode/InitMap.h | 32 +++++-- clang/lib/AST/ByteCode/Interp.cpp | 36 ++++++-- clang/lib/AST/ByteCode/Interp.h | 33 +------ clang/lib/AST/ByteCode/Pointer.cpp | 69 ++++++++++++++- clang/lib/AST/ByteCode/Pointer.h | 45 ++++++---- clang/lib/AST/CMakeLists.txt | 1 + clang/test/AST/ByteCode/builtin-functions.cpp | 10 +-- clang/test/AST/ByteCode/cxx23.cpp | 33 +++++++ clang/test/AST/ByteCode/lifetimes26.cpp | 87 ++++++++++++++++--- 11 files changed, 289 insertions(+), 87 deletions(-) diff --git a/clang/lib/AST/ByteCode/Descriptor.h b/clang/lib/AST/ByteCode/Descriptor.h index 057f40f8f1f69..5e4c2a8d94774 100644 --- a/clang/lib/AST/ByteCode/Descriptor.h +++ b/clang/lib/AST/ByteCode/Descriptor.h @@ -275,7 +275,6 @@ struct Descriptor final { void dump(llvm::raw_ostream &OS) const; void dumpFull(unsigned Offset = 0, unsigned Indent = 0) const; }; - } // namespace interp } // namespace clang diff --git a/clang/lib/AST/ByteCode/InitMap.cpp b/clang/lib/AST/ByteCode/InitMap.cpp index ae841dd6df189..b62e30a46535f 100644 --- a/clang/lib/AST/ByteCode/InitMap.cpp +++ b/clang/lib/AST/ByteCode/InitMap.cpp @@ -10,9 +10,6 @@ using namespace clang; using namespace clang::interp; -InitMap::InitMap(unsigned N) - : UninitFields(N), Data(std::make_unique<T[]>(numFields(N))) {} - bool InitMap::initializeElement(unsigned I) { unsigned Bucket = I / PER_FIELD; T Mask = T(1) << (I % PER_FIELD); @@ -29,3 +26,29 @@ bool InitMap::isElementInitialized(unsigned I) const { unsigned Bucket = I / PER_FIELD; return data()[Bucket] & (T(1) << (I % PER_FIELD)); } + +// Values in the second half of data() are inverted, +// i.e. 0 means "lifetime started". +void InitMap::startElementLifetime(unsigned I) { + unsigned LifetimeIndex = NumElems + I; + + unsigned Bucket = LifetimeIndex / PER_FIELD; + T Mask = T(1) << (LifetimeIndex % PER_FIELD); + if ((data()[Bucket] & Mask)) { + data()[Bucket] &= ~Mask; + --DeadFields; + } +} + +// Values in the second half of data() are inverted, +// i.e. 0 means "lifetime started". +void InitMap::endElementLifetime(unsigned I) { + unsigned LifetimeIndex = NumElems + I; + + unsigned Bucket = LifetimeIndex / PER_FIELD; + T Mask = T(1) << (LifetimeIndex % PER_FIELD); + if (!(data()[Bucket] & Mask)) { + data()[Bucket] |= Mask; + ++DeadFields; + } +} diff --git a/clang/lib/AST/ByteCode/InitMap.h b/clang/lib/AST/ByteCode/InitMap.h index 44184f4ba18a0..ade6600a54e59 100644 --- a/clang/lib/AST/ByteCode/InitMap.h +++ b/clang/lib/AST/ByteCode/InitMap.h @@ -24,20 +24,32 @@ struct InitMap final { using T = uint64_t; /// Bits stored in a single field. static constexpr uint64_t PER_FIELD = sizeof(T) * CHAR_BIT; + /// Number of fields in the init map. + unsigned NumElems; /// Number of fields not initialized. unsigned UninitFields; + unsigned DeadFields = 0; std::unique_ptr<T[]> Data; public: /// Initializes the map with no fields set. - explicit InitMap(unsigned N); - -private: - friend class Pointer; + explicit InitMap(unsigned N) + : NumElems(N), UninitFields(N), + Data(std::make_unique<T[]>(numFields(N))) {} + explicit InitMap(unsigned N, bool AllInitialized) + : NumElems(N), UninitFields(AllInitialized ? 0 : N), + Data(std::make_unique<T[]>(numFields(N))) {} + + void startElementLifetime(unsigned I); + void endElementLifetime(unsigned I); + + bool isElementAlive(unsigned I) const { + unsigned LifetimeIndex = NumElems + I; + unsigned Bucket = LifetimeIndex / PER_FIELD; + return !(data()[Bucket] & (T(1) << (LifetimeIndex % PER_FIELD))); + } - /// Returns a pointer to storage. - T *data() { return Data.get(); } - const T *data() const { return Data.get(); } + bool allElementsAlive() const { return DeadFields == 0; } /// Initializes an element. Returns true when object if fully initialized. bool initializeElement(unsigned I); @@ -45,6 +57,11 @@ struct InitMap final { /// Checks if an element was initialized. bool isElementInitialized(unsigned I) const; +private: + /// Returns a pointer to storage. + T *data() { return Data.get(); } + const T *data() const { return Data.get(); } + static constexpr size_t numFields(unsigned N) { return ((N + PER_FIELD - 1) / PER_FIELD) * 2; } @@ -94,7 +111,6 @@ struct InitMapPtr final { }; }; static_assert(sizeof(InitMapPtr) == sizeof(void *)); - } // namespace interp } // namespace clang diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 889ac1e1a9a7e..69423a13cb090 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1907,10 +1907,6 @@ bool EndLifetime(InterpState &S, CodePtr OpPC) { if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy)) return false; - // FIXME: We need per-element lifetime information for primitive arrays. - if (Ptr.isArrayElement()) - return true; - endLifetimeRecurse(Ptr.narrow()); return true; } @@ -1921,10 +1917,6 @@ bool EndLifetimePop(InterpState &S, CodePtr OpPC) { if (Ptr.isBlockPointer() && !CheckDummy(S, OpPC, Ptr.block(), AK_Destroy)) return false; - // FIXME: We need per-element lifetime information for primitive arrays. - if (Ptr.isArrayElement()) - return true; - endLifetimeRecurse(Ptr.narrow()); return true; } @@ -2324,6 +2316,34 @@ bool FinishInitGlobal(InterpState &S, CodePtr OpPC) { return true; } +bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) { + assert(S.Current->getFunction()); + // FIXME: We iterate the scope once here and then again in the destroy() call + // below. + for (auto &Local : S.Current->getFunction()->getScope(I).locals_reverse()) { + if (!S.Current->getLocalBlock(Local.Offset)->isInitialized()) + continue; + const Pointer &Ptr = S.Current->getLocalPointer(Local.Offset); + if (Ptr.getLifetime() == Lifetime::Ended) { + // Try to use the declaration for better diagnostics + if (const Decl *D = Ptr.getDeclDesc()->asDecl()) { + auto *ND = cast<NamedDecl>(D); + S.FFDiag(ND->getLocation(), + diag::note_constexpr_destroy_out_of_lifetime) + << ND->getNameAsString(); + } else { + S.FFDiag(Ptr.getDeclDesc()->getLocation(), + diag::note_constexpr_destroy_out_of_lifetime) + << Ptr.toDiagnosticString(S.getASTContext()); + } + return false; + } + } + + S.Current->destroy(I); + return true; +} + // https://github.com/llvm/llvm-project/issues/102513 #if defined(_MSC_VER) && !defined(__clang__) && !defined(NDEBUG) #pragma optimize("", off) diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 9accbbc1605a9..24007fd5b2d31 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -122,6 +122,7 @@ bool CheckFunctionDecl(InterpState &S, CodePtr OpPC, const FunctionDecl *FD); bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC, const FixedPoint &FP); +bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I); bool isConstexprUnknown(const Pointer &P); enum class ShiftDir { Left, Right }; @@ -2437,38 +2438,6 @@ inline bool SubPtr(InterpState &S, CodePtr OpPC, bool ElemSizeIsZero) { return true; } -//===----------------------------------------------------------------------===// -// Destroy -//===----------------------------------------------------------------------===// - -inline bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I) { - assert(S.Current->getFunction()); - - // FIXME: We iterate the scope once here and then again in the destroy() call - // below. - for (auto &Local : S.Current->getFunction()->getScope(I).locals_reverse()) { - const Pointer &Ptr = S.Current->getLocalPointer(Local.Offset); - - if (Ptr.getLifetime() == Lifetime::Ended) { - // Try to use the declaration for better diagnostics - if (const Decl *D = Ptr.getDeclDesc()->asDecl()) { - auto *ND = cast<NamedDecl>(D); - S.FFDiag(ND->getLocation(), - diag::note_constexpr_destroy_out_of_lifetime) - << ND->getNameAsString(); - } else { - S.FFDiag(Ptr.getDeclDesc()->getLocation(), - diag::note_constexpr_destroy_out_of_lifetime) - << Ptr.toDiagnosticString(S.getASTContext()); - } - return false; - } - } - - S.Current->destroy(I); - return true; -} - inline bool InitScope(InterpState &S, CodePtr OpPC, uint32_t I) { S.Current->initScope(I); return true; diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp index 2e38c1bb65d36..1221e70ae0aea 100644 --- a/clang/lib/AST/ByteCode/Pointer.cpp +++ b/clang/lib/AST/ByteCode/Pointer.cpp @@ -496,6 +496,57 @@ bool Pointer::isElementInitialized(unsigned Index) const { return isInitialized(); } +bool Pointer::isElementAlive(unsigned Index) const { + assert(getFieldDesc()->isPrimitiveArray()); + + InitMapPtr &IM = getInitMap(); + if (!IM.hasInitMap()) + return true; + + if (IM.allInitialized()) + return true; + + return IM->isElementAlive(Index); +} + +void Pointer::startLifetime() const { + if (!isBlockPointer()) + return; + if (BS.Base < sizeof(InlineDescriptor)) + return; + + if (inArray()) { + const Descriptor *Desc = getFieldDesc(); + InitMapPtr &IM = getInitMap(); + if (!IM.hasInitMap()) + IM.setInitMap(new InitMap(Desc->getNumElems(), IM.allInitialized())); + + IM->startElementLifetime(getIndex()); + return; + } + + getInlineDesc()->LifeState = Lifetime::Started; +} + +void Pointer::endLifetime() const { + if (!isBlockPointer()) + return; + if (BS.Base < sizeof(InlineDescriptor)) + return; + + if (inArray()) { + const Descriptor *Desc = getFieldDesc(); + InitMapPtr &IM = getInitMap(); + if (!IM.hasInitMap()) + IM.setInitMap(new InitMap(Desc->getNumElems(), IM.allInitialized())); + + IM->endElementLifetime(getIndex()); + return; + } + + getInlineDesc()->LifeState = Lifetime::Ended; +} + void Pointer::initialize() const { if (!isBlockPointer()) return; @@ -530,7 +581,6 @@ void Pointer::initializeElement(unsigned Index) const { assert(Index < getFieldDesc()->getNumElems()); InitMapPtr &IM = getInitMap(); - if (IM.allInitialized()) return; @@ -568,6 +618,23 @@ bool Pointer::allElementsInitialized() const { return IM.allInitialized(); } +bool Pointer::allElementsAlive() const { + assert(getFieldDesc()->isPrimitiveArray()); + assert(isArrayRoot()); + + if (isStatic() && BS.Base == 0) + return true; + + if (isRoot() && BS.Base == sizeof(GlobalInlineDescriptor) && + Offset == BS.Base) { + const auto &GD = block()->getBlockDesc<GlobalInlineDescriptor>(); + return GD.InitState == GlobalInitState::Initialized; + } + + InitMapPtr &IM = getInitMap(); + return IM.allInitialized() || (IM.hasInitMap() && IM->allElementsAlive()); +} + void Pointer::activate() const { // Field has its bit in an inline descriptor. assert(BS.Base != 0 && "Only composite fields can be activated"); diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h index 9032bdade850f..98709592916d0 100644 --- a/clang/lib/AST/ByteCode/Pointer.h +++ b/clang/lib/AST/ByteCode/Pointer.h @@ -15,6 +15,7 @@ #include "Descriptor.h" #include "FunctionPointer.h" +#include "InitMap.h" #include "InterpBlock.h" #include "clang/AST/ComparisonCategories.h" #include "clang/AST/Decl.h" @@ -721,6 +722,9 @@ class Pointer { /// Like isInitialized(), but for primitive arrays. bool isElementInitialized(unsigned Index) const; bool allElementsInitialized() const; + bool allElementsAlive() const; + bool isElementAlive(unsigned Index) const; + /// Activats a field. void activate() const; /// Deactivates an entire strurcutre. @@ -731,25 +735,33 @@ class Pointer { return Lifetime::Started; if (BS.Base < sizeof(InlineDescriptor)) return Lifetime::Started; - return getInlineDesc()->LifeState; - } - void endLifetime() const { - if (!isBlockPointer()) - return; - if (BS.Base < sizeof(InlineDescriptor)) - return; - getInlineDesc()->LifeState = Lifetime::Ended; - } + if (inArray() && !isArrayRoot()) { + InitMapPtr &IM = getInitMap(); - void startLifetime() const { - if (!isBlockPointer()) - return; - if (BS.Base < sizeof(InlineDescriptor)) - return; - getInlineDesc()->LifeState = Lifetime::Started; + if (!IM.hasInitMap()) { + if (IM.allInitialized()) + return Lifetime::Started; + return getArray().getLifetime(); + } + + return IM->isElementAlive(getIndex()) ? Lifetime::Started + : Lifetime::Ended; + } + + return getInlineDesc()->LifeState; } + /// Start the lifetime of this pointer. This works for pointer with an + /// InlineDescriptor as well as primitive array elements. Pointers are usually + /// alive by default, unless the underlying object has been allocated with + /// std::allocator. This function is used by std::construct_at. + void startLifetime() const; + /// Ends the lifetime of the pointer. This works for pointer with an + /// InlineDescriptor as well as primitive array elements. This function is + /// used by std::destroy_at. + void endLifetime() const; + /// Compare two pointers. ComparisonCategoryResult compare(const Pointer &Other) const { if (!hasSameBase(*this, Other)) @@ -791,7 +803,6 @@ class Pointer { friend class DeadBlock; friend class MemberPointer; friend class InterpState; - friend struct InitMap; friend class DynamicAllocator; friend class Program; @@ -838,6 +849,8 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) { OS << ' '; if (const Descriptor *D = P.getFieldDesc()) D->dump(OS); + if (P.isArrayElement()) + OS << " index " << P.getIndex(); return OS; } diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index f9a5f4f0e7ecd..3534996af12dc 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -73,6 +73,7 @@ add_clang_library(clangAST ByteCode/Compiler.cpp ByteCode/Context.cpp ByteCode/Descriptor.cpp + ByteCode/InitMap.cpp ByteCode/Disasm.cpp ByteCode/EvalEmitter.cpp ByteCode/Function.cpp diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp index 3076b5239ebbe..bdb66aeaad539 100644 --- a/clang/test/AST/ByteCode/builtin-functions.cpp +++ b/clang/test/AST/ByteCode/builtin-functions.cpp @@ -1848,11 +1848,9 @@ namespace WithinLifetime { } xstd; // both-error {{is not a constant expression}} \ // both-note {{in call to}} - /// FIXME: We do not have per-element lifetime information for primitive arrays. - /// See https://github.com/llvm/llvm-project/issues/147528 consteval bool test_dynamic(bool read_after_deallocate) { std::allocator<int> a; - int* p = a.allocate(1); // expected-note 2{{allocation performed here was not deallocated}} + int* p = a.allocate(1); // a.allocate starts the lifetime of an array, // the complete object of *p has started its lifetime if (__builtin_is_within_lifetime(p)) @@ -1865,12 +1863,12 @@ namespace WithinLifetime { return false; a.deallocate(p, 1); if (read_after_deallocate) - __builtin_is_within_lifetime(p); // ref-note {{read of heap allocated object that has been deleted}} + __builtin_is_within_lifetime(p); // both-note {{read of heap allocated object that has been deleted}} return true; } - static_assert(test_dynamic(false)); // expected-error {{not an integral constant expression}} + static_assert(test_dynamic(false)); static_assert(test_dynamic(true)); // both-error {{not an integral constant expression}} \ - // ref-note {{in call to}} + // both-note {{in call to}} } #ifdef __SIZEOF_INT128__ diff --git a/clang/test/AST/ByteCode/cxx23.cpp b/clang/test/AST/ByteCode/cxx23.cpp index 819460628c1b7..427bdfdb6b3c5 100644 --- a/clang/test/AST/ByteCode/cxx23.cpp +++ b/clang/test/AST/ByteCode/cxx23.cpp @@ -12,6 +12,8 @@ inline constexpr void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; namespace std { template<typename T, typename... Args> constexpr T* construct_at(T* p, Args&&... args) { return ::new((void*)p) T(static_cast<Args&&>(args)...); } +template<typename T> +constexpr void destroy_at(T *p) { p->~T(); } // #destroy } constexpr int f(int n) { // all20-error {{constexpr function never produces a constant expression}} @@ -495,4 +497,35 @@ namespace InactiveLocalsInConditionalOp { static_assert(test2(true)); } + +namespace PrimitiveArrayLifetimes { + consteval int test1() { + int a[3]; + assert_active(a[0]); + assert_active(a[1]); + assert_active(a[2]); + std::destroy_at(&a[0]); + assert_inactive(a[0]); + + std::construct_at(&a[0]); + assert_active(a[0]); + return a[0]; + } + static_assert(test1() == 0); + + consteval int test2() { + int a[3] = {1,2,3}; + assert_active(a[0]); + assert_active(a[1]); + assert_active(a[2]); + std::destroy_at(&a[0]); + assert_inactive(a[0]); + + std::construct_at(&a[0]); + assert_active(a[0]); + return a[0]; + } + static_assert(test2() == 0); +} + #endif diff --git a/clang/test/AST/ByteCode/lifetimes26.cpp b/clang/test/AST/ByteCode/lifetimes26.cpp index c3163f8a562bf..02559e4c24029 100644 --- a/clang/test/AST/ByteCode/lifetimes26.cpp +++ b/clang/test/AST/ByteCode/lifetimes26.cpp @@ -1,8 +1,6 @@ // RUN: %clang_cc1 -verify=expected,both -std=c++26 %s -fexperimental-new-constant-interpreter // RUN: %clang_cc1 -verify=ref,both -std=c++26 %s -// both-no-diagnostics - namespace std { struct type_info; struct destroying_delete_t { @@ -13,12 +11,26 @@ namespace std { } inline constexpr nothrow{}; using size_t = decltype(sizeof(0)); enum class align_val_t : size_t {}; + + using size_t = decltype(sizeof(0)); + template<typename T> struct allocator { + constexpr T *allocate(size_t N) { + return (T*)__builtin_operator_new(sizeof(T) * N); + } + constexpr void deallocate(void *p) { + __builtin_operator_delete(p); + } + }; + template<typename T, typename ...Args> + constexpr void construct_at(void *p, Args &&...args) { + new (p) T((Args&&)args...); + } }; constexpr void *operator new(std::size_t, void *p) { return p; } namespace std { template<typename T> constexpr T *construct_at(T *p) { return new (p) T; } - template<typename T> constexpr void destroy_at(T *p) { p->~T(); } + template<typename T> constexpr void destroy_at(T *p) { p->~T(); } // #destroy } constexpr bool foo() { @@ -49,18 +61,69 @@ static_assert((destroy_pointer(), true)); namespace DestroyArrayElem { - /// This is proof that std::destroy_at'ing an array element - /// ends the lifetime of the entire array. - /// See https://github.com/llvm/llvm-project/issues/147528 - /// Using destroy_at on array elements is currently a no-op due to this. - constexpr int test() { + + constexpr int test1() { + int a[4] = {}; + std::destroy_at(&a); // both-error@#destroy {{object expression of non-scalar type 'int[4]' cannot be used in a pseudo-destructor expression}} \ + // both-note {{in instantiation of function template specialization 'std::destroy_at<int[4]>' requested here}} \ + // both-note {{subexpression not valid}} + return 0; + } + static_assert(test1() == 0); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} + + constexpr int test2() { + int a[4] = {}; + std::destroy_at(&a[1]); + int r = a[1]; // both-note {{read of object outside its lifetime}} + std::construct_at(&a[1]); + return 0; + } + static_assert(test2() == 0); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} - std::destroy_at(&a[3]); - int r = a[1]; + constexpr int test3() { + int a[4]; /// Array with no init map. std::construct_at(&a[3]); + return a[3]; // both-note {{read of uninitialized object}} + } + static_assert(test3() == 0); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} + + constexpr int test4(bool b) { + int a[4]; + a[0] = 12; + std::construct_at(&a[3]); + return b ? a[0] : a[3]; // both-note {{read of uninitialized object}} + } + static_assert(test4(true) == 12); + static_assert(test4(false) == 0); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} + + + constexpr int test5() { + int *a = std::allocator<int>{}.allocate(3); + a[0] = 1; // both-note {{assignment to object outside its lifetime}} + int b = a[1]; + std::allocator<int>{}.deallocate(a); + return b; + } + static_assert(test5() == 1); // both-error {{not an integral constant expression}} \ + // both-note {{in call to}} + + constexpr int test6() { + int *a = std::allocator<int>{}.allocate(3); + std::construct_at<int>(&a[0]); + a[0] = 1; + std::construct_at<int>(&a[1]); + a[1] = 2; + std::construct_at<int>(&a[2]); + a[2] = 3; - return r; + int b = a[1]; + std::allocator<int>{}.deallocate(a); + return b; } - static_assert(test() == 0); + static_assert(test6() == 2); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
