Added stack allocator snapshots.
This commit is contained in:
parent
1cbd435fbf
commit
86f3790ce1
@ -80,6 +80,56 @@ public:
|
|||||||
friend class StlStackAllocator;
|
friend class StlStackAllocator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace impl
|
||||||
|
{
|
||||||
|
struct StackAllocatorSnapshotData
|
||||||
|
{
|
||||||
|
struct ChunkSnapshot
|
||||||
|
{
|
||||||
|
std::size_t allocated;
|
||||||
|
};
|
||||||
|
std::size_t numChunks;
|
||||||
|
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||||
|
std::size_t numAllocations_ = 0;
|
||||||
|
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||||
|
// just for debugging, so we don't care what memory this uses...
|
||||||
|
std::unordered_map<void*, Result<Stacktrace>> activeAllocations_;
|
||||||
|
#endif
|
||||||
|
ChunkSnapshot chunks[1]; // may be bigger than that
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class StackAllocatorSnapshot
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
impl::StackAllocatorSnapshotData* data = nullptr;
|
||||||
|
|
||||||
|
impl::StackAllocatorSnapshotData* operator->() const MIJIN_NOEXCEPT { return data; }
|
||||||
|
|
||||||
|
|
||||||
|
template<std::size_t chunkSize, template<typename> typename TBacking> requires (allocator_tmpl<TBacking>)
|
||||||
|
friend class StackAllocator;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename TStackAllocator>
|
||||||
|
class StackAllocatorScope
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
TStackAllocator* allocator_;
|
||||||
|
StackAllocatorSnapshot snapshot_;
|
||||||
|
public:
|
||||||
|
explicit StackAllocatorScope(TStackAllocator& allocator) : allocator_(&allocator), snapshot_(allocator_->createSnapshot()) {}
|
||||||
|
StackAllocatorScope(const StackAllocatorScope&) = delete;
|
||||||
|
StackAllocatorScope(StackAllocatorScope&&) = delete;
|
||||||
|
~StackAllocatorScope() MIJIN_NOEXCEPT
|
||||||
|
{
|
||||||
|
allocator_->restoreSnapshot(snapshot_);
|
||||||
|
}
|
||||||
|
|
||||||
|
StackAllocatorScope& operator=(const StackAllocatorScope&) = delete;
|
||||||
|
StackAllocatorScope& operator=(StackAllocatorScope&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
template<std::size_t chunkSize = 4096, template<typename> typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl<TBacking>)
|
template<std::size_t chunkSize = 4096, template<typename> typename TBacking = MIJIN_DEFAULT_ALLOCATOR> requires (allocator_tmpl<TBacking>)
|
||||||
class StackAllocator
|
class StackAllocator
|
||||||
{
|
{
|
||||||
@ -242,6 +292,154 @@ public:
|
|||||||
{
|
{
|
||||||
return stl_allocator_t<T>(*this);
|
return stl_allocator_t<T>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
std::size_t getNumChunks() const MIJIN_NOEXCEPT
|
||||||
|
{
|
||||||
|
std::size_t num = 0;
|
||||||
|
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||||
|
{
|
||||||
|
++num;
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
StackAllocatorSnapshot createSnapshot()
|
||||||
|
{
|
||||||
|
if (firstChunk_ == nullptr)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
using impl::StackAllocatorSnapshotData;
|
||||||
|
|
||||||
|
std::size_t numChunks = getNumChunks();
|
||||||
|
std::size_t snapshotSize = calcSnapshotSize(numChunks);
|
||||||
|
Chunk* prevFirst = firstChunk_;
|
||||||
|
StackAllocatorSnapshotData* snapshotData = static_cast<StackAllocatorSnapshotData*>(allocate(alignof(StackAllocatorSnapshotData), snapshotSize));
|
||||||
|
if (snapshotData == nullptr)
|
||||||
|
{
|
||||||
|
// couldn't allocate the snapshot
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
StackAllocatorSnapshot snapshot;
|
||||||
|
snapshot.data = snapshotData;
|
||||||
|
if (firstChunk_ != prevFirst)
|
||||||
|
{
|
||||||
|
// new chunk has been added, adjust the snapshot
|
||||||
|
// the snapshot must be inside the new chunk (but not necessarily at the very beginning, due to alignment)
|
||||||
|
MIJIN_ASSERT(static_cast<const void*>(snapshot.data) == alignUp(firstChunk_->data.data(), alignof(StackAllocatorSnapshot)), "Snapshot not where it was expected.");
|
||||||
|
|
||||||
|
// a chunk might be too small for the snapshot if we grow it (unlikely, but not impossible)
|
||||||
|
if (ACTUAL_CHUNK_SIZE - firstChunk_->allocated < MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot)) [[unlikely]]
|
||||||
|
{
|
||||||
|
firstChunk_->allocated = 0;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// looking good, adjust the numbers
|
||||||
|
++numChunks;
|
||||||
|
snapshotSize += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
|
||||||
|
firstChunk_->allocated += MIJIN_STRIDEOF(StackAllocatorSnapshotData::ChunkSnapshot);
|
||||||
|
}
|
||||||
|
// now fill out the struct
|
||||||
|
snapshot->numChunks = numChunks;
|
||||||
|
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||||
|
snapshot->numAllocations_ = numAllocations_;
|
||||||
|
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||||
|
// just for debugging, so we don't care what memory this uses...
|
||||||
|
snapshot->activeAllocations_ = activeAllocations_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::size_t pos = 0;
|
||||||
|
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next, ++pos)
|
||||||
|
{
|
||||||
|
snapshot->chunks[pos].allocated = chunk->allocated;
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreSnapshot(StackAllocatorSnapshot snapshot) MIJIN_NOEXCEPT
|
||||||
|
{
|
||||||
|
if (snapshot.data == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t numChunks = getNumChunks();
|
||||||
|
MIJIN_ASSERT_FATAL(snapshot->numChunks <= numChunks, "Snapshot contains more chunks than the allocator!");
|
||||||
|
|
||||||
|
#if MIJIN_STACK_ALLOCATOR_DEBUG == 1
|
||||||
|
MIJIN_ASSERT(snapshot->numAllocations_ >= numAllocations_, "Missing deallocation in StackAllocator!");
|
||||||
|
#elif MIJIN_STACK_ALLOCATOR_DEBUG > 1
|
||||||
|
// TODO: compare and print changes
|
||||||
|
unsigned numMismatches = 0;
|
||||||
|
for (const auto& [ptr, stack] : activeAllocations_)
|
||||||
|
{
|
||||||
|
if (snapshot->activeAllocations_.contains(ptr))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++numMismatches;
|
||||||
|
|
||||||
|
if (stack.isError())
|
||||||
|
{
|
||||||
|
std::println(stderr, "Missing deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::println(stderr, "Missing deallocation at 0x{}:\n{}", ptr, stack.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if 0 // deallocating more than expected shouldn't be a problem
|
||||||
|
for (const auto& [ptr, stack] : snapshot->activeAllocations_)
|
||||||
|
{
|
||||||
|
if (activeAllocations_.contains(ptr))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++numMismatches;
|
||||||
|
|
||||||
|
if (stack.isError())
|
||||||
|
{
|
||||||
|
std::println(stderr, "Unexpected deallocation at {}, no stacktrace ({})", ptr, stack.getError().message);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::println(stderr, "Unexpected deallocation at 0x{}:\n{}", ptr, stack.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (numMismatches > 0)
|
||||||
|
{
|
||||||
|
std::println(stderr, "{} mismatched deallocations when restoring stack allocator snapshot.", numMismatches);
|
||||||
|
MIJIN_TRAP();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// if we allocated new chunks since the snapshot, these are completely empty now
|
||||||
|
const std::size_t emptyChunks = numChunks - snapshot->numChunks;
|
||||||
|
|
||||||
|
Chunk* chunk = firstChunk_;
|
||||||
|
for (std::size_t idx = 0; idx < emptyChunks; ++idx)
|
||||||
|
{
|
||||||
|
chunk->allocated = 0;
|
||||||
|
chunk = chunk->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the other values are in the snapshot
|
||||||
|
for (std::size_t idx = 0; idx < snapshot->numChunks; ++idx)
|
||||||
|
{
|
||||||
|
chunk->allocated = snapshot->chunks[idx].allocated;
|
||||||
|
chunk = chunk->next;
|
||||||
|
}
|
||||||
|
MIJIN_ASSERT(chunk == nullptr, "Something didn't add up.");
|
||||||
|
|
||||||
|
// finally free the space for the snapshot itself
|
||||||
|
Chunk* snapshotChunk = findChunk(snapshot.data);
|
||||||
|
MIJIN_ASSERT_FATAL(snapshotChunk != nullptr, "Snapshot not in chunks?");
|
||||||
|
snapshotChunk->allocated -= calcSnapshotSize(snapshot->numChunks); // note: this might miss the alignment bytes of the snapshot, but that should be fine
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
void initAndAddChunk(Chunk* newChunk) noexcept
|
void initAndAddChunk(Chunk* newChunk) noexcept
|
||||||
{
|
{
|
||||||
@ -252,6 +450,29 @@ private:
|
|||||||
firstChunk_ = newChunk;
|
firstChunk_ = newChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isInChunk(const void* address, const Chunk& chunk) const MIJIN_NOEXCEPT
|
||||||
|
{
|
||||||
|
const std::byte* asByte = static_cast<const std::byte*>(address);
|
||||||
|
return asByte >= chunk.data.data() && asByte < chunk.data.data() + ACTUAL_CHUNK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk* findChunk(const void* address) const MIJIN_NOEXCEPT
|
||||||
|
{
|
||||||
|
for (Chunk* chunk = firstChunk_; chunk != nullptr; chunk = chunk->next)
|
||||||
|
{
|
||||||
|
if (isInChunk(address, *chunk))
|
||||||
|
{
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::size_t calcSnapshotSize(std::size_t numChunks) MIJIN_NOEXCEPT
|
||||||
|
{
|
||||||
|
return sizeof(impl::StackAllocatorSnapshotData) + ((numChunks - 1) * MIJIN_STRIDEOF(impl::StackAllocatorSnapshotData::ChunkSnapshot));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename TValue, typename TStackAllocator>
|
template<typename TValue, typename TStackAllocator>
|
||||||
friend class StlStackAllocator;
|
friend class StlStackAllocator;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user