Skip to content

Commit

Permalink
[clang][Interp] Implement dynamic memory allocation handling
Browse files Browse the repository at this point in the history
  • Loading branch information
tbaederr committed Nov 23, 2023
1 parent 6e547ce commit 2927019
Show file tree
Hide file tree
Showing 15 changed files with 834 additions and 5 deletions.
1 change: 1 addition & 0 deletions clang/lib/AST/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ add_clang_library(clangAST
Interp/Function.cpp
Interp/InterpBuiltin.cpp
Interp/Floating.cpp
Interp/DynamicAllocator.cpp
Interp/Interp.cpp
Interp/InterpBlock.cpp
Interp/InterpFrame.cpp
Expand Down
76 changes: 76 additions & 0 deletions clang/lib/AST/Interp/ByteCodeExprGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,82 @@ bool ByteCodeExprGen<Emitter>::VisitSizeOfPackExpr(const SizeOfPackExpr *E) {
return this->emitConst(E->getPackLength(), E);
}

template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) {
assert(classifyPrim(E->getType()) == PT_Ptr);
const Expr *Init = E->getInitializer();
QualType ElementType = E->getAllocatedType();
std::optional<PrimType> ElemT = classify(ElementType);

const Descriptor *Desc;
if (ElemT) {
if (E->isArray())
Desc = nullptr; // We're not going to use it in this case.
else
Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD,
/*IsConst=*/false, /*IsTemporary=*/false,
/*IsMutable=*/false);
} else {
Desc = P.createDescriptor(
E, ElementType.getTypePtr(),
E->isArray() ? std::nullopt : Descriptor::InlineDescMD,
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init);
}

if (E->isArray()) {
assert(E->getArraySize());
PrimType SizeT = classifyPrim((*E->getArraySize())->getType());

if (!this->visit(*E->getArraySize()))
return false;

if (ElemT) {
// N primitive elements.
if (!this->emitAllocN(SizeT, *ElemT, E, E))
return false;
} else {
// N Composite elements.
if (!this->emitAllocCN(SizeT, Desc, E))
return false;
}

} else {
// Allocate just one element.
if (!this->emitAlloc(Desc, E))
return false;

if (Init) {
if (ElemT) {
if (!this->visit(Init))
return false;

if (!this->emitInit(*ElemT, E))
return false;
} else {
// Composite.
if (!this->visitInitializer(Init))
return false;
}
}
}

if (DiscardResult)
return this->emitPopPtr(E);

return true;
}

template <class Emitter>
bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
const Expr *Arg = E->getArgument();

// Arg must be an lvalue.
if (!this->visit(Arg))
return false;

return this->emitFree(E->isArrayForm(), E);
}

template <class Emitter> bool ByteCodeExprGen<Emitter>::discard(const Expr *E) {
if (E->containsErrors())
return false;
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/Interp/ByteCodeExprGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>,
bool VisitOffsetOfExpr(const OffsetOfExpr *E);
bool VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *E);
bool VisitSizeOfPackExpr(const SizeOfPackExpr *E);
bool VisitCXXNewExpr(const CXXNewExpr *E);
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);

protected:
bool visitExpr(const Expr *E) override;
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ bool Context::Run(State &Parent, const Function *Func, APValue &Result) {
State.Current = new InterpFrame(State, Func, /*Caller=*/nullptr, {});
if (Interpret(State, Result)) {
assert(Stk.empty());
return true;
return !State.maybeDiagnoseDanglingAllocations();
}

// State gets destroyed here, so the Stk.clear() below doesn't accidentally
Expand Down
90 changes: 90 additions & 0 deletions clang/lib/AST/Interp/DynamicAllocator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//==---- DynamicAllocator.cpp - Types for the constexpr VM -------*- C++ -*-==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "DynamicAllocator.h"
#include "InterpBlock.h"
#include "InterpState.h"

using namespace clang;
using namespace clang::interp;

Block *DynamicAllocator::allocate(const Expr *Source, PrimType T,
size_t NumElements) {
assert(NumElements > 0);
// Create a new descriptor for an array of the specified size and
// element type.
const Descriptor *D = allocateDescriptor(
Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false,
/*IsTemporary=*/false, /*IsMutable=*/false);
return allocate(D);
}

Block *DynamicAllocator::allocate(const Descriptor *ElementDesc,
size_t NumElements) {
assert(NumElements > 0);
// Create a new descriptor for an array of the specified size and
// element type.
const Descriptor *D = allocateDescriptor(
ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements,
/*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false);
return allocate(D);
}

Block *DynamicAllocator::allocate(const Descriptor *D) {
assert(D->asExpr());

auto Memory =
std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize());
auto *B = new (Memory.get()) Block(D, /*isStatic=*/false);
B->invokeCtor();

InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData());
ID->Desc = const_cast<Descriptor *>(D);
ID->IsActive = true;
ID->Offset = sizeof(InlineDescriptor);
ID->IsBase = false;
ID->IsFieldMutable = false;
ID->IsConst = false;
ID->IsInitialized = false;
assert(ID->Desc);

if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end())
It->second.Allocations.emplace_back(std::move(Memory));
else
AllocationSites.insert(
{D->asExpr(), AllocationSite(std::move(Memory), D->isArray())});
return B;
}

bool DynamicAllocator::deallocate(const Expr *Source,
const Block *BlockToDelete, InterpState &S) {
auto It = AllocationSites.find(Source);
if (It == AllocationSites.end())
return false;

auto &Site = It->second;
assert(Site.size() > 0);

// Find the Block to delete.
auto AllocIt = llvm::find_if(Site.Allocations, [&](const Allocation &A) {
const Block *B = reinterpret_cast<const Block *>(A.Memory.get());
return BlockToDelete == B;
});

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

Block *B = reinterpret_cast<Block *>(AllocIt->Memory.get());
B->invokeDtor();
S.deallocate(B);
Site.Allocations.erase(AllocIt);

if (Site.size() == 0)
AllocationSites.erase(It);

return true;
}
92 changes: 92 additions & 0 deletions clang/lib/AST/Interp/DynamicAllocator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//==- DynamicAllocator.h - Bytecode allocator for the constexpr VM -*- C++
//-*-=//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H
#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H

#include "Descriptor.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/Allocator.h"

namespace clang {
class Expr;
namespace interp {
class Block;
class InterpState;

/// Manages dynamic memory allocations done during bytecode interpretation.
///
/// We manage allocations as a map from their new-expression to a list
/// of allocations. This is called an AllocationSite. For each site, we
/// record whether it was allocated using new or new[], the
/// IsArrayAllocation flag.
///
/// For all array allocations, we need to allocat new Descriptor instances,
/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program.
class DynamicAllocator final {
struct Allocation {
std::unique_ptr<std::byte[]> Memory;
Allocation(std::unique_ptr<std::byte[]> Memory)
: Memory(std::move(Memory)) {}
};

struct AllocationSite {
llvm::SmallVector<Allocation> Allocations;
bool IsArrayAllocation = false;

AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array)
: IsArrayAllocation(Array) {
Allocations.push_back({std::move(Memory)});
}

size_t size() const { return Allocations.size(); }
};

public:
DynamicAllocator() = default;

unsigned getNumAllocations() const { return AllocationSites.size(); }

/// Allocate ONE element of the given descriptor.
Block *allocate(const Descriptor *D);
/// Allocate \p NumElements primitive elements of the given type.
Block *allocate(const Expr *Source, PrimType T, size_t NumElements);
/// Allocate \p NumElements elements of the given descriptor.
Block *allocate(const Descriptor *D, size_t NumElements);

/// Deallocate the given source+block combination.
/// Returns \c true if anything has been deallocatd, \c false otherwise.
bool deallocate(const Expr *Source, const Block *BlockToDelete,
InterpState &S);

/// Checks whether the allocation done at the given source is an array
/// allocation.
bool isArrayAllocation(const Expr *Source) const {
if (auto It = AllocationSites.find(Source); It != AllocationSites.end())
return It->second.IsArrayAllocation;
return false;
}

// FIXME: Public because I'm not sure how to expose an iterator to it.
llvm::DenseMap<const Expr *, AllocationSite> AllocationSites;

private:
using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>;
PoolAllocTy DescAllocator;

/// Allocates a new descriptor.
template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) {
return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...);
}
};

} // namespace interp
} // namespace clang
#endif
4 changes: 2 additions & 2 deletions clang/lib/AST/Interp/EvalEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ EvalEmitter::~EvalEmitter() {

llvm::Expected<bool> EvalEmitter::interpretExpr(const Expr *E) {
if (this->visitExpr(E))
return true;
return S.maybeDiagnoseDanglingAllocations();
if (BailLocation)
return llvm::make_error<ByteCodeGenError>(*BailLocation);
return false;
}

llvm::Expected<bool> EvalEmitter::interpretDecl(const VarDecl *VD) {
if (this->visitDecl(VD))
return true;
return S.maybeDiagnoseDanglingAllocations();
if (BailLocation)
return llvm::make_error<ByteCodeGenError>(*BailLocation);
return false;
Expand Down
49 changes: 49 additions & 0 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,55 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
return true;
}

bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) {
if (S.getLangOpts().CPlusPlus20)
return true;

const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_new);
return false;
}

bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray,
bool DeleteIsArray, const Descriptor *D,
const Expr *NewExpr) {
if (NewWasArray == DeleteIsArray)
return true;

QualType TypeToDiagnose;
// We need to shuffle things around a bit here to get a better diagnostic,
// because the expression we allocated the block for was of type int*,
// but we want to get the array size right.
if (D->isArray()) {
QualType ElemQT = D->getType()->getPointeeType();
TypeToDiagnose = S.getCtx().getConstantArrayType(
ElemQT, APInt(64, D->getNumElems(), false), nullptr,
ArraySizeModifier::Normal, 0);
} else
TypeToDiagnose = D->getType()->getPointeeType();

const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_new_delete_mismatch)
<< DeleteIsArray << 0 << TypeToDiagnose;
S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here)
<< NewExpr->getSourceRange();
return false;
}

bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source,
const Pointer &Ptr) {
if (Source && isa<CXXNewExpr>(Source))
return true;

// Whatever this is, we didn't heap allocate it.
const SourceInfo &Loc = S.Current->getSource(OpPC);
// FIXME: There is a problem with pretty-printing the APValue we create
// for pointers (&local_primitive in this case).
S.FFDiag(Loc, diag::note_constexpr_delete_not_heap_alloc) << "";
S.Note(Ptr.getDeclLoc(), diag::note_declared_at);
return false;
}

/// We aleady know the given DeclRefExpr is invalid for some reason,
/// now figure out why and print appropriate diagnostics.
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {
Expand Down
Loading

0 comments on commit 2927019

Please sign in to comment.