Skip to content

Commit fde9ee1

Browse files
authored
[clang][bytecode] Don't deallocate dynamic blocks with pointers (#152672)
This fixes the edge case we had with variables pointing to dynamic blocks, which forced us to convert basically *all* dynamic blocks to DeadBlock when deallocating them. We now don't run dynamic blocks through InterpState::deallocate() but instead add them to a DeadAllocations list when they are deallocated but still have pointers. As a consequence, not all blocks with Block::IsDead = true are DeadBlocks.
1 parent 82d633e commit fde9ee1

File tree

4 files changed

+37
-26
lines changed

4 files changed

+37
-26
lines changed

clang/lib/AST/ByteCode/DynamicAllocator.cpp

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,6 @@
1313
using namespace clang;
1414
using namespace clang::interp;
1515

16-
// FIXME: There is a peculiar problem with the way we track pointers
17-
// to blocks and the way we allocate dynamic memory.
18-
//
19-
// When we have code like this:
20-
// while (true) {
21-
// char *buffer = new char[1024];
22-
// delete[] buffer;
23-
// }
24-
//
25-
// We have a local variable 'buffer' pointing to the heap allocated memory.
26-
// When deallocating the memory via delete[], that local variable still
27-
// points to the memory, which means we will create a DeadBlock for it and move
28-
// it over to that block, essentially duplicating the allocation. Moving
29-
// the data is also slow.
30-
//
31-
// However, when we actually try to access the allocation after it has been
32-
// freed, we need the block to still exist (alive or dead) so we can tell
33-
// that it's a dynamic allocation.
34-
3516
DynamicAllocator::~DynamicAllocator() { cleanup(); }
3617

3718
void DynamicAllocator::cleanup() {
@@ -42,8 +23,11 @@ void DynamicAllocator::cleanup() {
4223
for (auto &Iter : AllocationSites) {
4324
auto &AllocSite = Iter.second;
4425
for (auto &Alloc : AllocSite.Allocations) {
45-
Block *B = reinterpret_cast<Block *>(Alloc.Memory.get());
26+
Block *B = Alloc.block();
27+
assert(!B->IsDead);
28+
assert(B->isInitialized());
4629
B->invokeDtor();
30+
4731
if (B->hasPointers()) {
4832
while (B->Pointers) {
4933
Pointer *Next = B->Pointers->asBlockPointer().Next;
@@ -89,6 +73,12 @@ Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID,
8973
assert(D);
9074
assert(D->asExpr());
9175

76+
// Garbage collection. Remove all dead allocations that don't have pointers to
77+
// them anymore.
78+
llvm::erase_if(DeadAllocations, [](Allocation &Alloc) -> bool {
79+
return !Alloc.block()->hasPointers();
80+
});
81+
9282
auto Memory =
9383
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
9484
auto *B = new (Memory.get()) Block(EvalID, D, /*isStatic=*/false);
@@ -132,18 +122,34 @@ bool DynamicAllocator::deallocate(const Expr *Source,
132122

133123
// Find the Block to delete.
134124
auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
135-
const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
136-
return BlockToDelete == B;
125+
return BlockToDelete == A.block();
137126
});
138127

139128
assert(AllocIt != Site.Allocations.end());
140129

141-
Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
130+
Block *B = AllocIt->block();
131+
assert(B->isInitialized());
132+
assert(!B->IsDead);
142133
B->invokeDtor();
143134

144-
S.deallocate(B);
145-
Site.Allocations.erase(AllocIt);
135+
// Almost all our dynamic allocations have a pointer pointing to them
136+
// when we deallocate them, since otherwise we can't call delete() at all.
137+
// This means that we would usually need to create DeadBlocks for all of them.
138+
// To work around that, we instead mark them as dead without moving the data
139+
// over to a DeadBlock and simply keep the block in a separate DeadAllocations
140+
// list.
141+
if (B->hasPointers()) {
142+
B->IsDead = true;
143+
DeadAllocations.push_back(std::move(*AllocIt));
144+
Site.Allocations.erase(AllocIt);
145+
146+
if (Site.size() == 0)
147+
AllocationSites.erase(It);
148+
return true;
149+
}
146150

151+
// Get rid of the allocation altogether.
152+
Site.Allocations.erase(AllocIt);
147153
if (Site.empty())
148154
AllocationSites.erase(It);
149155

clang/lib/AST/ByteCode/DynamicAllocator.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class DynamicAllocator final {
4343
std::unique_ptr<std::byte[]> Memory;
4444
Allocation(std::unique_ptr<std::byte[]> Memory)
4545
: Memory(std::move(Memory)) {}
46+
Block *block() const { return reinterpret_cast<Block *>(Memory.get()); }
4647
};
4748

4849
struct AllocationSite {
@@ -99,6 +100,9 @@ class DynamicAllocator final {
99100

100101
private:
101102
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
103+
// Allocations that have already been deallocated but had pointers
104+
// to them.
105+
llvm::SmallVector<Allocation> DeadAllocations;
102106

103107
using PoolAllocTy = llvm::BumpPtrAllocator;
104108
PoolAllocTy DescAllocator;

clang/lib/AST/ByteCode/InterpBlock.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void Block::removePointer(Pointer *P) {
6464
}
6565

6666
void Block::cleanup() {
67-
if (Pointers == nullptr && IsDead)
67+
if (Pointers == nullptr && !IsDynamic && IsDead)
6868
(reinterpret_cast<DeadBlock *>(this + 1) - 1)->free();
6969
}
7070

clang/lib/AST/ByteCode/InterpState.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ bool InterpState::reportOverflow(const Expr *E, const llvm::APSInt &Value) {
7676

7777
void InterpState::deallocate(Block *B) {
7878
assert(B);
79+
assert(!B->isDynamic());
7980
// The block might have a pointer saved in a field in its data
8081
// that points to the block itself. We call the dtor first,
8182
// which will destroy all the data but leave InlineDescriptors

0 commit comments

Comments
 (0)