Skip to content

Commit e94e72a

Browse files
committed
Reapply "[clang][Interp] Implement dynamic memory allocation handling (#70306)"
This reverts commit 48d703e.
1 parent e9b2a25 commit e94e72a

19 files changed

+1213
-8
lines changed

clang/lib/AST/CMakeLists.txt

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

clang/lib/AST/Interp/Compiler.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2771,6 +2771,117 @@ bool Compiler<Emitter>::VisitCXXInheritedCtorInitExpr(
27712771
return this->emitCall(F, 0, E);
27722772
}
27732773

2774+
template <class Emitter>
2775+
bool Compiler<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
2776+
assert(classifyPrim(E->getType()) == PT_Ptr);
2777+
const Expr *Init = E->getInitializer();
2778+
QualType ElementType = E->getAllocatedType();
2779+
std::optional<PrimType> ElemT = classify(ElementType);
2780+
unsigned PlacementArgs = E->getNumPlacementArgs();
2781+
bool IsNoThrow = false;
2782+
2783+
// FIXME: Better diagnostic. diag::note_constexpr_new_placement
2784+
if (PlacementArgs != 0) {
2785+
// The only new-placement list we support is of the form (std::nothrow).
2786+
//
2787+
// FIXME: There is no restriction on this, but it's not clear that any
2788+
// other form makes any sense. We get here for cases such as:
2789+
//
2790+
// new (std::align_val_t{N}) X(int)
2791+
//
2792+
// (which should presumably be valid only if N is a multiple of
2793+
// alignof(int), and in any case can't be deallocated unless N is
2794+
// alignof(X) and X has new-extended alignment).
2795+
if (PlacementArgs != 1 || !E->getPlacementArg(0)->getType()->isNothrowT())
2796+
return this->emitInvalid(E);
2797+
2798+
if (!this->discard(E->getPlacementArg(0)))
2799+
return false;
2800+
IsNoThrow = true;
2801+
}
2802+
2803+
const Descriptor *Desc;
2804+
if (ElemT) {
2805+
if (E->isArray())
2806+
Desc = nullptr; // We're not going to use it in this case.
2807+
else
2808+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
2809+
/*IsConst=*/false, /*IsTemporary=*/false,
2810+
/*IsMutable=*/false);
2811+
} else {
2812+
Desc = P.createDescriptor(
2813+
E, ElementType.getTypePtr(),
2814+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
2815+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
2816+
}
2817+
2818+
if (E->isArray()) {
2819+
std::optional<const Expr *> ArraySizeExpr = E->getArraySize();
2820+
if (!ArraySizeExpr)
2821+
return false;
2822+
2823+
const Expr *Stripped = *ArraySizeExpr;
2824+
for (; auto *ICE = dyn_cast<ImplicitCastExpr>(Stripped);
2825+
Stripped = ICE->getSubExpr())
2826+
if (ICE->getCastKind() != CK_NoOp &&
2827+
ICE->getCastKind() != CK_IntegralCast)
2828+
break;
2829+
2830+
PrimType SizeT = classifyPrim(Stripped->getType());
2831+
2832+
if (!this->visit(Stripped))
2833+
return false;
2834+
2835+
if (ElemT) {
2836+
// N primitive elements.
2837+
if (!this->emitAllocN(SizeT, *ElemT, E, IsNoThrow, E))
2838+
return false;
2839+
} else {
2840+
// N Composite elements.
2841+
if (!this->emitAllocCN(SizeT, Desc, IsNoThrow, E))
2842+
return false;
2843+
}
2844+
2845+
if (Init && !this->visitInitializer(Init))
2846+
return false;
2847+
2848+
} else {
2849+
// Allocate just one element.
2850+
if (!this->emitAlloc(Desc, E))
2851+
return false;
2852+
2853+
if (Init) {
2854+
if (ElemT) {
2855+
if (!this->visit(Init))
2856+
return false;
2857+
2858+
if (!this->emitInit(*ElemT, E))
2859+
return false;
2860+
} else {
2861+
// Composite.
2862+
if (!this->visitInitializer(Init))
2863+
return false;
2864+
}
2865+
}
2866+
}
2867+
2868+
if (DiscardResult)
2869+
return this->emitPopPtr(E);
2870+
2871+
return true;
2872+
}
2873+
2874+
template <class Emitter>
2875+
bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
2876+
const Expr *Arg = E->getArgument();
2877+
2878+
// Arg must be an lvalue.
2879+
if (!this->visit(Arg))
2880+
return false;
2881+
2882+
return this->emitFree(E->isArrayForm(), E);
2883+
}
2884+
27742885
template <class Emitter>
27752886
bool Compiler<Emitter>::VisitExpressionTraitExpr(const ExpressionTraitExpr *E) {
27762887
assert(Ctx.getLangOpts().CPlusPlus);

clang/lib/AST/Interp/Compiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
190190
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
191191
bool VisitCXXStdInitializerListExpr(const CXXStdInitializerListExpr *E);
192192
bool VisitStmtExpr(const StmtExpr *E);
193+
bool VisitCXXNewExpr(const CXXNewExpr *E);
194+
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
193195

194196
// Statements.
195197
bool visitCompoundStmt(const CompoundStmt *S);
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, unsigned EvalID) {
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, EvalID);
51+
}
52+
53+
Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
54+
size_t NumElements, unsigned EvalID) {
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, EvalID);
61+
}
62+
63+
Block *DynamicAllocator::allocate(const Descriptor *D, unsigned EvalID) {
64+
assert(D);
65+
assert(D->asExpr());
66+
67+
auto Memory =
68+
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
69+
auto *B = new (Memory.get()) Block(EvalID, D, /*isStatic=*/false);
70+
B->invokeCtor();
71+
72+
InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
73+
ID->Desc = D;
74+
ID->IsActive = true;
75+
ID->Offset = sizeof(InlineDescriptor);
76+
ID->IsBase = false;
77+
ID->IsFieldMutable = false;
78+
ID->IsConst = false;
79+
ID->IsInitialized = false;
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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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 allocate 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, unsigned EvalID);
62+
/// Allocate \p NumElements primitive elements of the given type.
63+
Block *allocate(const Expr *Source, PrimType T, size_t NumElements,
64+
unsigned EvalID);
65+
/// Allocate \p NumElements elements of the given descriptor.
66+
Block *allocate(const Descriptor *D, size_t NumElements, unsigned EvalID);
67+
68+
/// Deallocate the given source+block combination.
69+
/// Returns \c true if anything has been deallocatd, \c false otherwise.
70+
bool deallocate(const Expr *Source, const Block *BlockToDelete,
71+
InterpState &S);
72+
73+
/// Checks whether the allocation done at the given source is an array
74+
/// allocation.
75+
bool isArrayAllocation(const Expr *Source) const {
76+
if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
77+
return It->second.IsArrayAllocation;
78+
return false;
79+
}
80+
81+
/// Allocation site iterator.
82+
using const_virtual_iter =
83+
llvm::DenseMap<const Expr *, AllocationSite>::const_iterator;
84+
llvm::iterator_range<const_virtual_iter> allocation_sites() const {
85+
return llvm::make_range(AllocationSites.begin(), AllocationSites.end());
86+
}
87+
88+
private:
89+
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
90+
91+
using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
92+
PoolAllocTy DescAllocator;
93+
94+
/// Allocates a new descriptor.
95+
template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
96+
return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
97+
}
98+
};
99+
100+
} // namespace interp
101+
} // namespace clang
102+
#endif

0 commit comments

Comments
 (0)