Skip to content

Commit bf3dd8a

Browse files
committed
[clang][Interp] Implement dynamic memory allocation handling
1 parent 826fe84 commit bf3dd8a

16 files changed

+844
-6
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/Function.cpp
7676
Interp/InterpBuiltin.cpp
7777
Interp/Floating.cpp
78+
Interp/DynamicAllocator.cpp
7879
Interp/Interp.cpp
7980
Interp/InterpBlock.cpp
8081
Interp/InterpFrame.cpp

clang/lib/AST/Interp/ByteCodeExprGen.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,82 @@ bool ByteCodeExprGen<Emitter>::VisitSizeOfPackExpr(const SizeOfPackExpr *E) {
18141814
return this->emitConst(E->getPackLength(), E);
18151815
}
18161816

1817+
template <class Emitter>
1818+
bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
1819+
assert(classifyPrim(E->getType()) == PT_Ptr);
1820+
const Expr *Init = E->getInitializer();
1821+
QualType ElementType = E->getAllocatedType();
1822+
std::optional<PrimType> ElemT = classify(ElementType);
1823+
1824+
const Descriptor *Desc;
1825+
if (ElemT) {
1826+
if (E->isArray())
1827+
Desc = nullptr; // We're not going to use it in this case.
1828+
else
1829+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
1830+
/*IsConst=*/false, /*IsTemporary=*/false,
1831+
/*IsMutable=*/false);
1832+
} else {
1833+
Desc = P.createDescriptor(
1834+
E, ElementType.getTypePtr(),
1835+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
1836+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
1837+
}
1838+
1839+
if (E->isArray()) {
1840+
assert(E->getArraySize());
1841+
PrimType SizeT = classifyPrim((*E->getArraySize())->getType());
1842+
1843+
if (!this->visit(*E->getArraySize()))
1844+
return false;
1845+
1846+
if (ElemT) {
1847+
// N primitive elements.
1848+
if (!this->emitAllocN(SizeT, *ElemT, E, E))
1849+
return false;
1850+
} else {
1851+
// N Composite elements.
1852+
if (!this->emitAllocCN(SizeT, Desc, E))
1853+
return false;
1854+
}
1855+
1856+
} else {
1857+
// Allocate just one element.
1858+
if (!this->emitAlloc(Desc, E))
1859+
return false;
1860+
1861+
if (Init) {
1862+
if (ElemT) {
1863+
if (!this->visit(Init))
1864+
return false;
1865+
1866+
if (!this->emitInit(*ElemT, E))
1867+
return false;
1868+
} else {
1869+
// Composite.
1870+
if (!this->visitInitializer(Init))
1871+
return false;
1872+
}
1873+
}
1874+
}
1875+
1876+
if (DiscardResult)
1877+
return this->emitPopPtr(E);
1878+
1879+
return true;
1880+
}
1881+
1882+
template <class Emitter>
1883+
bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
1884+
const Expr *Arg = E->getArgument();
1885+
1886+
// Arg must be an lvalue.
1887+
if (!this->visit(Arg))
1888+
return false;
1889+
1890+
return this->emitFree(E->isArrayForm(), E);
1891+
}
1892+
18171893
template <class Emitter> bool ByteCodeExprGen<Emitter>::discard(const Expr *E) {
18181894
if (E->containsErrors())
18191895
return false;

clang/lib/AST/Interp/ByteCodeExprGen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
108108
bool VisitOffsetOfExpr(const OffsetOfExpr *E);
109109
bool VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *E);
110110
bool VisitSizeOfPackExpr(const SizeOfPackExpr *E);
111+
bool VisitCXXNewExpr(const CXXNewExpr *E);
112+
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
111113

112114
protected:
113115
bool visitExpr(const Expr *E) override;

clang/lib/AST/Interp/Context.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ bool Context::Run(State &Parent, const Function *Func, APValue &Result) {
167167
State.Current = new InterpFrame(State, Func, /*Caller=*/nullptr, {});
168168
if (Interpret(State, Result)) {
169169
assert(Stk.empty());
170-
return true;
170+
return !State.maybeDiagnoseDanglingAllocations();
171171
}
172172

173173
// State gets destroyed here, so the Stk.clear() below doesn't accidentally

clang/lib/AST/Interp/Context.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ class Context final {
6969

7070
/// Classifies an expression.
7171
std::optional<PrimType> classify(QualType T) const;
72+
std::optional<PrimType> classify(const Expr *E) const {
73+
if (E->isGLValue()) {
74+
if (E->getType()->isFunctionType())
75+
return PT_FnPtr;
76+
return PT_Ptr;
77+
}
78+
79+
return classify(E->getType());
80+
}
7281

7382
const CXXMethodDecl *
7483
getOverridingFunction(const CXXRecordDecl *DynamicDecl,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//==---- DynamicAllocator.cpp - Types for the constexpr VM -------*- 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+
Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
17+
size_t NumElements) {
18+
assert(NumElements > 0);
19+
// Create a new descriptor for an array of the specified size and
20+
// element type.
21+
const Descriptor *D = allocateDescriptor(
22+
Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
23+
/*IsTemporary=*/false, /*IsMutable=*/false);
24+
return allocate(D);
25+
}
26+
27+
Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
28+
size_t NumElements) {
29+
assert(NumElements > 0);
30+
// Create a new descriptor for an array of the specified size and
31+
// element type.
32+
const Descriptor *D = allocateDescriptor(
33+
ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
34+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
35+
return allocate(D);
36+
}
37+
38+
Block *DynamicAllocator::allocate(const Descriptor *D) {
39+
assert(D->asExpr());
40+
41+
auto Memory =
42+
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
43+
auto *B = new (Memory.get()) Block(D, /*isStatic=*/false);
44+
B->invokeCtor();
45+
46+
InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
47+
ID->Desc = const_cast<Descriptor *>(D);
48+
ID->IsActive = true;
49+
ID->Offset = sizeof(InlineDescriptor);
50+
ID->IsBase = false;
51+
ID->IsFieldMutable = false;
52+
ID->IsConst = false;
53+
ID->IsInitialized = false;
54+
assert(ID->Desc);
55+
56+
if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())
57+
It->second.Allocations.emplace_back(std::move(Memory));
58+
else
59+
AllocationSites.insert(
60+
{D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
61+
return B;
62+
}
63+
64+
bool DynamicAllocator::deallocate(const Expr *Source,
65+
const Block *BlockToDelete, InterpState &S) {
66+
auto It = AllocationSites.find(Source);
67+
if (It == AllocationSites.end())
68+
return false;
69+
70+
auto &Site = It->second;
71+
assert(Site.size() > 0);
72+
73+
// Find the Block to delete.
74+
auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
75+
const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
76+
return BlockToDelete == B;
77+
});
78+
79+
assert(AllocIt != Site.Allocations.end());
80+
81+
Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
82+
B->invokeDtor();
83+
S.deallocate(B);
84+
Site.Allocations.erase(AllocIt);
85+
86+
if (Site.size() == 0)
87+
AllocationSites.erase(It);
88+
89+
return true;
90+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//==- DynamicAllocator.h - Bytecode allocator for the constexpr VM -*- C++
2+
//-*-=//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
11+
#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
12+
13+
#include "Descriptor.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+
55+
unsigned getNumAllocations() const { return AllocationSites.size(); }
56+
57+
/// Allocate ONE element of the given descriptor.
58+
Block *allocate(const Descriptor *D);
59+
/// Allocate \p NumElements primitive elements of the given type.
60+
Block *allocate(const Expr *Source, PrimType T, size_t NumElements);
61+
/// Allocate \p NumElements elements of the given descriptor.
62+
Block *allocate(const Descriptor *D, size_t NumElements);
63+
64+
/// Deallocate the given source+block combination.
65+
/// Returns \c true if anything has been deallocatd, \c false otherwise.
66+
bool deallocate(const Expr *Source, const Block *BlockToDelete,
67+
InterpState &S);
68+
69+
/// Checks whether the allocation done at the given source is an array
70+
/// allocation.
71+
bool isArrayAllocation(const Expr *Source) const {
72+
if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
73+
return It->second.IsArrayAllocation;
74+
return false;
75+
}
76+
77+
// FIXME: Public because I'm not sure how to expose an iterator to it.
78+
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;
79+
80+
private:
81+
using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
82+
PoolAllocTy DescAllocator;
83+
84+
/// Allocates a new descriptor.
85+
template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
86+
return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
87+
}
88+
};
89+
90+
} // namespace interp
91+
} // namespace clang
92+
#endif

clang/lib/AST/Interp/EvalEmitter.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ EvalEmitter::~EvalEmitter() {
3535

3636
llvm::Expected<bool> EvalEmitter::interpretExpr(const Expr *E) {
3737
if (this->visitExpr(E))
38-
return true;
38+
return S.maybeDiagnoseDanglingAllocations();
3939
if (BailLocation)
4040
return llvm::make_error<ByteCodeGenError>(*BailLocation);
4141
return false;
4242
}
4343

4444
llvm::Expected<bool> EvalEmitter::interpretDecl(const VarDecl *VD) {
4545
if (this->visitDecl(VD))
46-
return true;
46+
return S.maybeDiagnoseDanglingAllocations();
4747
if (BailLocation)
4848
return llvm::make_error<ByteCodeGenError>(*BailLocation);
4949
return false;

clang/lib/AST/Interp/Interp.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,55 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
624624
return true;
625625
}
626626

627+
bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
628+
if (S.getLangOpts().CPlusPlus20)
629+
return true;
630+
631+
const SourceInfo &E = S.Current->getSource(OpPC);
632+
S.FFDiag(E, diag::note_constexpr_new);
633+
return false;
634+
}
635+
636+
bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
637+
bool DeleteIsArray, const Descriptor *D,
638+
const Expr *NewExpr) {
639+
if (NewWasArray == DeleteIsArray)
640+
return true;
641+
642+
QualType TypeToDiagnose;
643+
// We need to shuffle things around a bit here to get a better diagnostic,
644+
// because the expression we allocated the block for was of type int*,
645+
// but we want to get the array size right.
646+
if (D->isArray()) {
647+
QualType ElemQT = D->getType()->getPointeeType();
648+
TypeToDiagnose = S.getCtx().getConstantArrayType(
649+
ElemQT, APInt(64, D->getNumElems(), false), nullptr,
650+
ArraySizeModifier::Normal, 0);
651+
} else
652+
TypeToDiagnose = D->getType()->getPointeeType();
653+
654+
const SourceInfo &E = S.Current->getSource(OpPC);
655+
S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
656+
<< DeleteIsArray << 0 << TypeToDiagnose;
657+
S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
658+
<< NewExpr->getSourceRange();
659+
return false;
660+
}
661+
662+
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
663+
const Pointer &Ptr) {
664+
if (Source && isa<CXXNewExpr>(Source))
665+
return true;
666+
667+
// Whatever this is, we didn't heap allocate it.
668+
const SourceInfo &Loc = S.Current->getSource(OpPC);
669+
// FIXME: There is a problem with pretty-printing the APValue we create
670+
// for pointers (&local_primitive in this case).
671+
S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc) << "";
672+
S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
673+
return false;
674+
}
675+
627676
/// We aleady know the given DeclRefExpr is invalid for some reason,
628677
/// now figure out why and print appropriate diagnostics.
629678
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {

0 commit comments

Comments
 (0)