Files
verilator/src/V3PoolAllocator.h
T
Geza Lore 7dcd4e0b65 Improve MTask coarsening in multi-threaded scheduling (#8120)
This is a large refactor of the MTask graph and coarsening algorithm, in
prep for fixing the bug described in #7913, it can also improve the
resulting multi-threaded schedule.

Two major changes:

OrderMTaskGraph now maintains the critical paths of the MTasks through
mutation. There are 2 ways to mutate the graph, which are done via
methods on the graph itself: adding an edge (used during construction,
and will be used later during fixing data hazards), or merging an MTask
into another (used during contraction). All critical path measures are
automatically updated and propagated on any mutation, so no external
algorithm needs to maintain them explicitly.

The merge candidate scoreboard used during contraction is simplified to
remove deferral of updated scores. This simplifies the code and results
in a greedily more optimal schedule. (The previous tranched rescore was
an optimization to work around the previous std::set based scoreboard,
however since the algorithm now uses an efficient PairingHeap,
verilation time is not impacted by the more accurate scoring, while
yielding better results).

Combining these two into a single patch as the code is highly
interdependent and any one change without the other would be just a
noisy transit point with unclear performance implications. Together it
should be a clear improvement.

Also added a stronger validation step run with '--debug-partition',
which checks all invariants throughout the algorithms.
2026-08-17 23:14:52 +02:00

86 lines
3.0 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Chunked pool allocator
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#ifndef VERILATOR_V3POOLALLOCATOR_H_
#define VERILATOR_V3POOLALLOCATOR_H_
#include "config_build.h"
#include "verilatedos.h"
#include <cstddef>
#include <memory>
#include <new>
#include <utility>
#include <vector>
// Hands out elements of a single type, allocated 'N_ChunkSize' at a time for
// efficiency. Released elements are recycled via a free list stored in the
// same storage. All memory is released when the pool is destroyed, so the
// elements it handed out must not be used beyond its lifetime, and must all
// have been released by then.
template <typename T_Elem, size_t N_ChunkSize = 128>
class PoolAllocator final {
static_assert(N_ChunkSize > 0, "Chunk size must be non-zero");
// A slot of storage, holding either a live element, or a link in the free list.
union Slot final {
T_Elem m_elem; // Storage for the allocated element
Slot* m_nextFreep; // Link to the next free slot
Slot() {}
~Slot() {}
};
// MEMBERS
Slot* m_freep = nullptr; // Head of the free list
std::vector<std::unique_ptr<Slot[]>> m_allocated; // The allocated chunks
public:
// CONSTRUCTORS
PoolAllocator() = default;
VL_UNCOPYABLE(PoolAllocator);
VL_UNMOVABLE(PoolAllocator);
// METHODS
// Allocate an element, constructed with the given arguments
template <typename... Args>
T_Elem* alloc(Args&&... args) {
// If no free slots available, then make some
if (!m_freep) {
// Allocate in chunks for efficiency
m_allocated.emplace_back(new Slot[N_ChunkSize]);
// Chain the new slots into the free list
Slot* const chunkp = m_allocated.back().get();
for (size_t i = 1; i < N_ChunkSize; ++i) chunkp[i - 1].m_nextFreep = &chunkp[i];
chunkp[N_ChunkSize - 1].m_nextFreep = nullptr;
m_freep = chunkp;
}
// Free slots are available, pick up the first one
Slot* const slotp = m_freep;
m_freep = slotp->m_nextFreep;
return new (&slotp->m_elem) T_Elem{std::forward<Args>(args)...};
}
// Destroy an element, and return its slot for future allocation
void free(T_Elem* elemp) {
elemp->~T_Elem();
Slot* const slotp = reinterpret_cast<Slot*>(elemp);
slotp->m_nextFreep = m_freep;
m_freep = slotp;
}
};
#endif // Guard