Skip to content
Open
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
1 change: 0 additions & 1 deletion clang/lib/AST/ByteCode/Descriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 26 additions & 3 deletions clang/lib/AST/ByteCode/InitMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
}
32 changes: 24 additions & 8 deletions clang/lib/AST/ByteCode/InitMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,44 @@ 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);

/// 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;
}
Expand Down Expand Up @@ -94,7 +111,6 @@ struct InitMapPtr final {
};
};
static_assert(sizeof(InitMapPtr) == sizeof(void *));

} // namespace interp
} // namespace clang

Expand Down
36 changes: 28 additions & 8 deletions clang/lib/AST/ByteCode/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 1 addition & 32 deletions clang/lib/AST/ByteCode/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -2436,38 +2437,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;
Expand Down
31 changes: 30 additions & 1 deletion clang/lib/AST/ByteCode/Pointer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,19 @@ 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::initialize() const {
if (!isBlockPointer())
return;
Expand Down Expand Up @@ -524,7 +537,6 @@ void Pointer::initializeElement(unsigned Index) const {
assert(Index < getFieldDesc()->getNumElems());

InitMapPtr &IM = getInitMap();

if (IM.allInitialized())
return;

Expand Down Expand Up @@ -562,6 +574,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");
Expand Down
44 changes: 43 additions & 1 deletion clang/lib/AST/ByteCode/Pointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -731,6 +735,20 @@ class Pointer {
return Lifetime::Started;
if (BS.Base < sizeof(InlineDescriptor))
return Lifetime::Started;

if (inArray() && !isArrayRoot()) {
InitMapPtr &IM = getInitMap();

if (!IM.hasInitMap()) {
if (IM.allInitialized())
return Lifetime::Started;
return getArray().getLifetime();
}

return IM->isElementAlive(getIndex()) ? Lifetime::Started
: Lifetime::Ended;
}

return getInlineDesc()->LifeState;
}

Expand All @@ -739,6 +757,17 @@ class Pointer {
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;
}

Expand All @@ -747,6 +776,18 @@ class Pointer {
return;
if (BS.Base < sizeof(InlineDescriptor))
return;

if (inArray()) {
InitMapPtr &IM = getInitMap();
if (!IM.hasInitMap()) {
const Descriptor *Desc = getFieldDesc();
IM.setInitMap(new InitMap(Desc->getNumElems(), IM.allInitialized()));
}

IM->startElementLifetime(getIndex());
return;
}

getInlineDesc()->LifeState = Lifetime::Started;
}

Expand Down Expand Up @@ -791,7 +832,6 @@ class Pointer {
friend class DeadBlock;
friend class MemberPointer;
friend class InterpState;
friend struct InitMap;
friend class DynamicAllocator;
friend class Program;

Expand Down Expand Up @@ -838,6 +878,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;
}

Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 4 additions & 6 deletions clang/test/AST/ByteCode/builtin-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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__
Expand Down
Loading