Skip to content

Commit c36e40f

Browse files
committed
[clang][Interp] Implement dynamic memory allocation handling
1 parent e0b66a4 commit c36e40f

20 files changed

+1198
-13
lines changed

clang/lib/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ add_clang_library(clangAST
7676
Interp/InterpBuiltin.cpp
7777
Interp/Floating.cpp
7878
Interp/EvaluationResult.cpp
79+
Interp/DynamicAllocator.cpp
7980
Interp/Interp.cpp
8081
Interp/InterpBlock.cpp
8182
Interp/InterpFrame.cpp

clang/lib/AST/Interp/ByteCodeExprGen.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,6 +2656,109 @@ bool ByteCodeExprGen<Emitter>::VisitCXXInheritedCtorInitExpr(
26562656
return this->emitCall(F, 0, E);
26572657
}
26582658

2659+
template <class Emitter>
2660+
bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
2661+
assert(classifyPrim(E->getType()) == PT_Ptr);
2662+
const Expr *Init = E->getInitializer();
2663+
QualType ElementType = E->getAllocatedType();
2664+
std::optional<PrimType> ElemT = classify(ElementType);
2665+
unsigned PlacementArgs = E->getNumPlacementArgs();
2666+
bool IsNoThrow = false;
2667+
2668+
// FIXME: Better diagnostic. diag::note_constexpr_new_placement
2669+
if (PlacementArgs != 0) {
2670+
// The only new-placement list we support is of the form (std::nothrow).
2671+
//
2672+
// FIXME: There is no restriction on this, but it's not clear that any
2673+
// other form makes any sense. We get here for cases such as:
2674+
//
2675+
// new (std::align_val_t{N}) X(int)
2676+
//
2677+
// (which should presumably be valid only if N is a multiple of
2678+
// alignof(int), and in any case can't be deallocated unless N is
2679+
// alignof(X) and X has new-extended alignment).
2680+
if (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT())
2681+
return this->emitInvalid(E);
2682+
2683+
if (!this->discard(E->getPlacementArg(0)))
2684+
return false;
2685+
IsNoThrow = true;
2686+
}
2687+
2688+
const Descriptor *Desc;
2689+
if (ElemT) {
2690+
if (E->isArray())
2691+
Desc = nullptr; // We're not going to use it in this case.
2692+
else
2693+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
2694+
/*IsConst=*/false, /*IsTemporary=*/false,
2695+
/*IsMutable=*/false);
2696+
} else {
2697+
Desc = P.createDescriptor(
2698+
E, ElementType.getTypePtr(),
2699+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
2700+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
2701+
}
2702+
2703+
if (E->isArray()) {
2704+
std::optional<const Expr *> ArraySizeExpr = E->getArraySize();
2705+
if (!ArraySizeExpr)
2706+
return false;
2707+
PrimType SizeT = classifyPrim((*ArraySizeExpr)->getType());
2708+
2709+
if (!this->visit(*ArraySizeExpr))
2710+
return false;
2711+
2712+
if (ElemT) {
2713+
// N primitive elements.
2714+
if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
2715+
return false;
2716+
} else {
2717+
// N Composite elements.
2718+
if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
2719+
return false;
2720+
}
2721+
2722+
if (Init && !this->visitInitializer(Init))
2723+
return false;
2724+
2725+
} else {
2726+
// Allocate just one element.
2727+
if (!this->emitAlloc(Desc, E))
2728+
return false;
2729+
2730+
if (Init) {
2731+
if (ElemT) {
2732+
if (!this->visit(Init))
2733+
return false;
2734+
2735+
if (!this->emitInit(*ElemT, E))
2736+
return false;
2737+
} else {
2738+
// Composite.
2739+
if (!this->visitInitializer(Init))
2740+
return false;
2741+
}
2742+
}
2743+
}
2744+
2745+
if (DiscardResult)
2746+
return this->emitPopPtr(E);
2747+
2748+
return true;
2749+
}
2750+
2751+
template <class Emitter>
2752+
bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
2753+
const Expr *Arg = E->getArgument();
2754+
2755+
// Arg must be an lvalue.
2756+
if (!this->visit(Arg))
2757+
return false;
2758+
2759+
return this->emitFree(E->isArrayForm(), E);
2760+
}
2761+
26592762
template <class Emitter>
26602763
bool ByteCodeExprGen<Emitter>::VisitExpressionTraitExpr(
26612764
const ExpressionTraitExpr *E) {

clang/lib/AST/Interp/ByteCodeExprGen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
130130
bool VisitShuffleVectorExpr(const ShuffleVectorExpr *E);
131131
bool VisitExtVectorElementExpr(const ExtVectorElementExpr *E);
132132
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
133+
bool VisitCXXNewExpr(const CXXNewExpr *E);
134+
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
133135

134136
protected:
135137
bool visitExpr(const Expr *E) override;

clang/lib/AST/Interp/Context.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ bool Context::evaluate(State &Parent, const Expr *E, APValue &Result) {
9191

9292
bool Context::evaluateAsInitializer(State &Parent, const VarDecl *VD,
9393
APValue &Result) {
94+
llvm::errs() << __PRETTY_FUNCTION__ << "\n";
95+
VD->dump();
9496
bool Recursing = !Stk.empty();
9597
ByteCodeExprGen<EvalEmitter> C(*this, *P, Parent, Stk);
9698

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//==-------- DynamicAllocator.cpp - Dynamic allocations ----------*- C++ -*-==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "DynamicAllocator.h"
10+
#include "InterpBlock.h"
11+
#include "InterpState.h"
12+
13+
using namespace clang;
14+
using namespace clang::interp;
15+
16+
DynamicAllocator::~DynamicAllocator() { cleanup(); }
17+
18+
void DynamicAllocator::cleanup() {
19+
// Invoke destructors of all the blocks and as a last restort,
20+
// reset all the pointers pointing to them to null pointees.
21+
// This should never show up in diagnostics, but it's necessary
22+
// for us to not cause use-after-free problems.
23+
for (auto &Iter : AllocationSites) {
24+
auto &AllocSite = Iter.second;
25+
for (auto &Alloc : AllocSite.Allocations) {
26+
Block *B = reinterpret_cast<Block *>(Alloc.Memory.get());
27+
B->invokeDtor();
28+
if (B->hasPointers()) {
29+
while (B->Pointers) {
30+
Pointer *Next = B->Pointers->Next;
31+
B->Pointers->PointeeStorage.BS.Pointee = nullptr;
32+
B->Pointers = Next;
33+
}
34+
B->Pointers = nullptr;
35+
}
36+
}
37+
}
38+
39+
AllocationSites.clear();
40+
}
41+
42+
Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
43+
size_t NumElements) {
44+
// Create a new descriptor for an array of the specified size and
45+
// element type.
46+
const Descriptor *D = allocateDescriptor(
47+
Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
48+
/*IsTemporary=*/false, /*IsMutable=*/false);
49+
50+
return allocate(D);
51+
}
52+
53+
Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
54+
size_t NumElements) {
55+
// Create a new descriptor for an array of the specified size and
56+
// element type.
57+
const Descriptor *D = allocateDescriptor(
58+
ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
59+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
60+
return allocate(D);
61+
}
62+
63+
Block *DynamicAllocator::allocate(const Descriptor *D) {
64+
assert(D->asExpr());
65+
66+
auto Memory =
67+
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
68+
auto *B = new (Memory.get()) Block(D, /*isStatic=*/false);
69+
B->invokeCtor();
70+
71+
InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
72+
ID->Desc = D;
73+
ID->IsActive = true;
74+
ID->Offset = sizeof(InlineDescriptor);
75+
ID->IsBase = false;
76+
ID->IsFieldMutable = false;
77+
ID->IsConst = false;
78+
ID->IsInitialized = false;
79+
assert(ID->Desc);
80+
81+
B->IsDynamic = true;
82+
83+
if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())
84+
It->second.Allocations.emplace_back(std::move(Memory));
85+
else
86+
AllocationSites.insert(
87+
{D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
88+
return B;
89+
}
90+
91+
bool DynamicAllocator::deallocate(const Expr *Source,
92+
const Block *BlockToDelete, InterpState &S) {
93+
auto It = AllocationSites.find(Source);
94+
if (It == AllocationSites.end())
95+
return false;
96+
97+
auto &Site = It->second;
98+
assert(Site.size() > 0);
99+
100+
// Find the Block to delete.
101+
auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
102+
const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
103+
return BlockToDelete == B;
104+
});
105+
106+
assert(AllocIt != Site.Allocations.end());
107+
108+
Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
109+
B->invokeDtor();
110+
111+
S.deallocate(B);
112+
Site.Allocations.erase(AllocIt);
113+
114+
if (Site.size() == 0)
115+
AllocationSites.erase(It);
116+
117+
return true;
118+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//==--------- DynamicAllocator.h - Dynamic allocations ------------*- C++ -*-=//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
10+
#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
11+
12+
#include "Descriptor.h"
13+
#include "InterpBlock.h"
14+
#include "llvm/ADT/SmallVector.h"
15+
#include "llvm/ADT/iterator_range.h"
16+
#include "llvm/Support/Allocator.h"
17+
18+
namespace clang {
19+
class Expr;
20+
namespace interp {
21+
class Block;
22+
class InterpState;
23+
24+
/// Manages dynamic memory allocations done during bytecode interpretation.
25+
///
26+
/// We manage allocations as a map from their new-expression to a list
27+
/// of allocations. This is called an AllocationSite. For each site, we
28+
/// record whether it was allocated using new or new[], the
29+
/// IsArrayAllocation flag.
30+
///
31+
/// For all array allocations, we need to allocat new Descriptor instances,
32+
/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program.
33+
class DynamicAllocator final {
34+
struct Allocation {
35+
std::unique_ptr<std::byte[]> Memory;
36+
Allocation(std::unique_ptr<std::byte[]> Memory)
37+
: Memory(std::move(Memory)) {}
38+
};
39+
40+
struct AllocationSite {
41+
llvm::SmallVector<Allocation> Allocations;
42+
bool IsArrayAllocation = false;
43+
44+
AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array)
45+
: IsArrayAllocation(Array) {
46+
Allocations.push_back({std::move(Memory)});
47+
}
48+
49+
size_t size() const { return Allocations.size(); }
50+
};
51+
52+
public:
53+
DynamicAllocator() = default;
54+
~DynamicAllocator();
55+
56+
void cleanup();
57+
58+
unsigned getNumAllocations() const { return AllocationSites.size(); }
59+
60+
/// Allocate ONE element of the given descriptor.
61+
Block *allocate(const Descriptor *D);
62+
/// Allocate \p NumElements primitive elements of the given type.
63+
Block *allocate(const Expr *Source, PrimType T, size_t NumElements);
64+
/// Allocate \p NumElements elements of the given descriptor.
65+
Block *allocate(const Descriptor *D, size_t NumElements);
66+
67+
/// Deallocate the given source+block combination.
68+
/// Returns \c true if anything has been deallocatd, \c false otherwise.
69+
bool deallocate(const Expr *Source, const Block *BlockToDelete,
70+
InterpState &S);
71+
72+
/// Checks whether the allocation done at the given source is an array
73+
/// allocation.
74+
bool isArrayAllocation(const Expr *Source) const {
75+
if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
76+
return It->second.IsArrayAllocation;
77+
return false;
78+
}
79+
80+
// FIXME: Public because I'm not sure how to expose an iterator to it.
81+
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
82+
83+
private:
84+
using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
85+
PoolAllocTy DescAllocator;
86+
87+
/// Allocates a new descriptor.
88+
template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
89+
return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
90+
}
91+
};
92+
93+
} // namespace interp
94+
} // namespace clang
95+
#endif

0 commit comments

Comments
 (0)