blob: 8f5b74df62497521d68263115dce0d9325739ee8 [file]
//===-------------------- FrameAllocator.h ----------------------*- 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_FRAME_ALLOCATOR_H
#define LLVM_CLANG_AST_INTERP_FRAME_ALLOCATOR_H
#include "llvm/Support/Compiler.h"
#ifndef NDEBUG
#include "llvm/ADT/SmallVector.h"
#endif
#include <algorithm>
#include <cassert>
#include <new>
namespace clang {
namespace interp {
// Set this to 1 to collect some light statistics.
// Print via printStats().
#define FRAME_ALLOCATOR_COLLECT_STATS 0
/// Allocator for function frames.
///
/// The function frame size includes the size reserved for local variables.
/// Function frames are allocated strictly in a LIFO manner, i.e. the last
/// created frame is the first frame that is destroyed.
///
/// Since the address a function frame is allocated in needs to stay stable
/// during the lifetime of the frame, we allocate them here in chunks.
///
/// A chunk is of (at least) MinChunkSize size and only gets deallocated once it
/// is empty AND the previous chunk is also empty.
///
class FrameAllocator final {
private:
struct Chunk {
Chunk *Prev = nullptr;
const unsigned Size;
unsigned Used = 0;
alignas(sizeof(void *)) char Memory[1];
Chunk(unsigned Size) : Size(Size) {}
unsigned bytesUnused() const { return Size - Used; }
};
static constexpr unsigned MinChunkSize = (4u * 1024u) - sizeof(Chunk);
Chunk *Tail = nullptr;
#if FRAME_ALLOCATOR_COLLECT_STATS
size_t MaxSize = 0;
unsigned LargestFrame = 0;
unsigned NumFrames = 0;
unsigned NumAllocs = 0;
#endif
#ifndef NDEBUG
llvm::SmallVector<unsigned> FrameSizes;
#endif
public:
FrameAllocator() = default;
FrameAllocator(FrameAllocator &) = delete;
FrameAllocator(FrameAllocator &&) = delete;
~FrameAllocator() {
while (Tail)
deallocTail();
}
char *reserve(unsigned Size) {
if (LLVM_UNLIKELY(!Tail))
allocateNewChunk(std::max(Size, MinChunkSize));
assert(Tail);
#ifndef NDEBUG
FrameSizes.push_back(Size);
#endif
char *Mem;
if (Chunk *C = getChunkToUse(Size); C->bytesUnused() >= Size) {
Mem = &C->Memory[C->Used];
C->Used += Size;
} else {
// We need to allocate a new chunk. If the requested size is larger than
// the minimum, use that.
allocateNewChunk(std::max(Size, MinChunkSize));
Tail->Used += Size;
Mem = Tail->Memory;
}
#if FRAME_ALLOCATOR_COLLECT_STATS
LargestFrame = std::max(Size, LargestFrame);
MaxSize = std::max(MaxSize, countAllBytes());
++NumFrames;
#endif
return Mem;
}
/// Pop the memory of the last function frame that was added.
/// The passed \c FrameSize needs to match the latest size passed to
/// reserve(). If it doesn't, bad things will happen.
void pop(unsigned FrameSize) {
#ifndef NDEBUG
assert(FrameSize == FrameSizes.back());
#endif
// Frame destructor must've already been called.
assert(Tail);
Chunk *C = Tail->Used == 0 ? Tail->Prev : Tail;
assert(C);
assert(FrameSize <= C->Used);
C->Used -= FrameSize;
// Deallocate the tail chunk *if* it is empty _and_ the previous chunk is
// also empty.
// Since we create chunks specicially for large frames, we need to loop
// here.
while (Tail->Used == 0 && Tail->Prev && Tail->Prev->Used == 0)
deallocTail();
#ifndef NDEBUG
FrameSizes.pop_back();
#endif
}
private:
/// Return the chunk to use to allocate a new frame into.
/// This is not always this->Tail, since Tail might be empty AND have a
/// previous chunk. In that case, we use the previous chunk, if it does have
/// \p Size bytes left.
Chunk *getChunkToUse(unsigned Size) {
assert(Tail);
if (Tail->Used == 0 && Tail->Prev && Tail->Prev->bytesUnused() >= Size)
return Tail->Prev;
return Tail;
}
void allocateNewChunk(unsigned Size) {
char *Mem = new char[sizeof(Chunk) + Size];
auto *C = new (Mem) Chunk(Size);
C->Prev = Tail;
Tail = C;
assert(Tail);
#if FRAME_ALLOCATOR_COLLECT_STATS
++NumAllocs;
#endif
}
void deallocTail() {
assert(Tail);
Chunk *C = Tail;
Tail = Tail->Prev;
delete[] reinterpret_cast<char *>(C);
}
#if FRAME_ALLOCATOR_COLLECT_STATS
size_t countAllBytes() const {
size_t Result = 0;
Chunk *C = Tail;
while (C) {
Result += C->Size + sizeof(Chunk);
C = C->Prev;
}
return Result;
}
void printStats() const {
llvm::errs() << "*** FrameAllocator stats ***\n";
if (!Tail) {
llvm::errs() << "empty\n";
return;
}
Chunk *C = Tail;
unsigned N = 0;
while (C) {
llvm::errs() << "Chunk " << N << ": " << C->Used << " / " << C->Size
<< " (";
double Percentage =
(static_cast<double>(C->Used) / static_cast<double>(C->Size)) * 100;
llvm::errs() << llvm::formatv("{0:2}", Percentage) << "%)\n";
++N;
C = C->Prev;
}
llvm::errs() << "Max allocated bytes: " << MaxSize << '\n';
llvm::errs() << "Largest frame: " << LargestFrame << '\n';
llvm::errs() << "Frames created: " << NumFrames << '\n';
llvm::errs() << "Allocations: " << NumAllocs << '\n';
llvm::errs() << "Occupancy: ";
size_t AllUsed = 0;
size_t AllSize = 0;
for (Chunk *C = Tail; C; C = C->Prev) {
AllUsed += C->Used;
AllSize += C->Size;
}
double Occupancy =
(static_cast<double>(AllUsed) / static_cast<double>(AllSize)) * 100;
llvm::errs() << llvm::formatv("{0:2}", Occupancy) << "%\n";
}
#endif
};
} // namespace interp
} // namespace clang
#endif