Skip to content

Commit 2927019

Browse files
committed
[clang][Interp] Implement dynamic memory allocation handling
1 parent 6e547ce commit 2927019

15 files changed

+834
-5
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
@@ -1620,6 +1620,82 @@ bool ByteCodeExprGen<Emitter>::VisitSizeOfPackExpr(const SizeOfPackExpr *E) {
16201620
return this->emitConst(E->getPackLength(), E);
16211621
}
16221622

1623+
template <class Emitter>
1624+
bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
1625+
assert(classifyPrim(E->getType()) == PT_Ptr);
1626+
const Expr *Init = E->getInitializer();
1627+
QualType ElementType = E->getAllocatedType();
1628+
std::optional<PrimType> ElemT = classify(ElementType);
1629+
1630+
const Descriptor *Desc;
1631+
if (ElemT) {
1632+
if (E->isArray())
1633+
Desc = nullptr; // We're not going to use it in this case.
1634+
else
1635+
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
1636+
/*IsConst=*/false, /*IsTemporary=*/false,
1637+
/*IsMutable=*/false);
1638+
} else {
1639+
Desc = P.createDescriptor(
1640+
E, ElementType.getTypePtr(),
1641+
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
1642+
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
1643+
}
1644+
1645+
if (E->isArray()) {
1646+
assert(E->getArraySize());
1647+
PrimType SizeT = classifyPrim((*E->getArraySize())->getType());
1648+
1649+
if (!this->visit(*E->getArraySize()))
1650+
return false;
1651+
1652+
if (ElemT) {
1653+
// N primitive elements.
1654+
if (!this->emitAllocN(SizeT, *ElemT, E, E))
1655+
return false;
1656+
} else {
1657+
// N Composite elements.
1658+
if (!this->emitAllocCN(SizeT, Desc, E))
1659+
return false;
1660+
}
1661+
1662+
} else {
1663+
// Allocate just one element.
1664+
if (!this->emitAlloc(Desc, E))
1665+
return false;
1666+
1667+
if (Init) {
1668+
if (ElemT) {
1669+
if (!this->visit(Init))
1670+
return false;
1671+
1672+
if (!this->emitInit(*ElemT, E))
1673+
return false;
1674+
} else {
1675+
// Composite.
1676+
if (!this->visitInitializer(Init))
1677+
return false;
1678+
}
1679+
}
1680+
}
1681+
1682+
if (DiscardResult)
1683+
return this->emitPopPtr(E);
1684+
1685+
return true;
1686+
}
1687+
1688+
template <class Emitter>
1689+
bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
1690+
const Expr *Arg = E->getArgument();
1691+
1692+
// Arg must be an lvalue.
1693+
if (!this->visit(Arg))
1694+
return false;
1695+
1696+
return this->emitFree(E->isArrayForm(), E);
1697+
}
1698+
16231699
template <class Emitter> bool ByteCodeExprGen<Emitter>::discard(const Expr *E) {
16241700
if (E->containsErrors())
16251701
return false;

clang/lib/AST/Interp/ByteCodeExprGen.h

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

111113
protected:
112114
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
@@ -164,7 +164,7 @@ bool Context::Run(State &Parent, const Function *Func, APValue &Result) {
164164
State.Current = new InterpFrame(State, Func, /*Caller=*/nullptr, {});
165165
if (Interpret(State, Result)) {
166166
assert(Stk.empty());
167-
return true;
167+
return !State.maybeDiagnoseDanglingAllocations();
168168
}
169169

170170
// State gets destroyed here, so the Stk.clear() below doesn't accidentally
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
@@ -575,6 +575,55 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
575575
return true;
576576
}
577577

578+
bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
579+
if (S.getLangOpts().CPlusPlus20)
580+
return true;
581+
582+
const SourceInfo &E = S.Current->getSource(OpPC);
583+
S.FFDiag(E, diag::note_constexpr_new);
584+
return false;
585+
}
586+
587+
bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
588+
bool DeleteIsArray, const Descriptor *D,
589+
const Expr *NewExpr) {
590+
if (NewWasArray == DeleteIsArray)
591+
return true;
592+
593+
QualType TypeToDiagnose;
594+
// We need to shuffle things around a bit here to get a better diagnostic,
595+
// because the expression we allocated the block for was of type int*,
596+
// but we want to get the array size right.
597+
if (D->isArray()) {
598+
QualType ElemQT = D->getType()->getPointeeType();
599+
TypeToDiagnose = S.getCtx().getConstantArrayType(
600+
ElemQT, APInt(64, D->getNumElems(), false), nullptr,
601+
ArraySizeModifier::Normal, 0);
602+
} else
603+
TypeToDiagnose = D->getType()->getPointeeType();
604+
605+
const SourceInfo &E = S.Current->getSource(OpPC);
606+
S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
607+
<< DeleteIsArray << 0 << TypeToDiagnose;
608+
S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
609+
<< NewExpr->getSourceRange();
610+
return false;
611+
}
612+
613+
bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
614+
const Pointer &Ptr) {
615+
if (Source && isa<CXXNewExpr>(Source))
616+
return true;
617+
618+
// Whatever this is, we didn't heap allocate it.
619+
const SourceInfo &Loc = S.Current->getSource(OpPC);
620+
// FIXME: There is a problem with pretty-printing the APValue we create
621+
// for pointers (&local_primitive in this case).
622+
S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc) << "";
623+
S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
624+
return false;
625+
}
626+
578627
/// We aleady know the given DeclRefExpr is invalid for some reason,
579628
/// now figure out why and print appropriate diagnostics.
580629
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {

0 commit comments

Comments
 (0)