| //===-------------------- 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 |