summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2026-01-12 23:35:52 +1100
committer Vas Crabb <vas@vastheman.com>2026-01-12 23:35:52 +1100
commitb8987c7129139317df4e262b846e57e7909083dd (patch)
tree48e3839f4599a64444d628ff8241ca4e1b919d98
parentee5ef66159633ba3feaf4f69cd4a3a2f3954cf48 (diff)
Miscellaneous recompiler improvements:
* cpu/drccache.cpp: Honour requested alignment for allocations. * cpu/drcbeut.cpp: Allow back-end to specify preferred alignment for hash table blocks. * cpu/drcbearm64.cpp: Shuffled generated code for HASHJMP a bit to be less sensitive to load latency.
-rw-r--r--src/devices/cpu/drcbearm64.cpp82
-rw-r--r--src/devices/cpu/drcbeut.cpp120
-rw-r--r--src/devices/cpu/drcbeut.h55
-rw-r--r--src/devices/cpu/drcbex64.cpp2
-rw-r--r--src/devices/cpu/drccache.cpp184
-rw-r--r--src/devices/cpu/drccache.h27
6 files changed, 303 insertions, 167 deletions
diff --git a/src/devices/cpu/drcbearm64.cpp b/src/devices/cpu/drcbearm64.cpp
index 0958614f19a..126d24af432 100644
--- a/src/devices/cpu/drcbearm64.cpp
+++ b/src/devices/cpu/drcbearm64.cpp
@@ -1540,7 +1540,7 @@ inline void drcbe_arm64::calculate_carry_shift_right_imm(a64::Assembler &a, cons
drcbe_arm64::drcbe_arm64(drcuml_state &drcuml, device_t &device, drc_cache &cache, uint32_t flags, int modes, int addrbits, int ignorebits)
: drcbe_interface(drcuml, cache, device)
- , m_hash(cache, modes, addrbits, ignorebits)
+ , m_hash(cache, modes, addrbits, ignorebits, std::align_val_t(1 << 12), std::align_val_t(1 << 12))
, m_map(cache, 0xaaaaaaaa5555)
, m_log_asmjit(nullptr)
, m_carry_state(carry_state::POISON)
@@ -1965,9 +1965,15 @@ void drcbe_arm64::op_hashjmp(a64::Assembler &a, const uml::instruction &inst)
const parameter &exp = inst.param(2);
assert(exp.is_code_handle());
- a.mov(a64::sp, a64::x29);
+ // load non-immediate mode and PC as early as possible
+ const a64::Gp mode = modep.select_register(TEMP_REG1, 8);
+ const a64::Gp pc = pcp.select_register(TEMP_REG2, 8);
+ if (!modep.is_immediate())
+ mov_reg_param(a, 4, mode, modep);
+ if (!pcp.is_immediate())
+ mov_reg_param(a, 4, pc, pcp);
- if (modep.is_immediate() && m_hash.is_mode_populated(modep.immediate()))
+ if (modep.is_immediate() && m_hash.populate_mode(modep.immediate()))
{
if (pcp.is_immediate())
{
@@ -1977,71 +1983,51 @@ void drcbe_arm64::op_hashjmp(a64::Assembler &a, const uml::instruction &inst)
}
else
{
- mov_reg_param(a, 4, TEMP_REG2, pcp);
-
- get_imm_relative(a, TEMP_REG1, (uintptr_t)&m_hash.base()[modep.immediate()][0]); // TEMP_REG1 = m_base[mode]
-
- a.ubfx(TEMP_REG3, TEMP_REG2, m_hash.l1shift(), m_hash.l1bits());
- a.ldr(TEMP_REG3, a64::Mem(TEMP_REG1, TEMP_REG3, a64::lsl(3))); // TEMP_REG3 = m_base[mode][(pc >> m_l1shift) & m_l1mask]
-
- a.ubfx(TEMP_REG2, TEMP_REG2, m_hash.l2shift(), m_hash.l2bits());
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG3, TEMP_REG2, a64::lsl(3))); // TEMP_REG1 = m_base[mode][(pc >> m_l1shift) & m_l1mask][(pc >> m_l2shift) & m_l2mask]
+ get_imm_relative(a, TEMP_REG1, uintptr_t(m_hash.base()[modep.immediate()])); // TEMP_REG1 = m_base[mode]
}
}
else
{
- get_imm_relative(a, TEMP_REG2, (uintptr_t)m_hash.base());
+ get_imm_relative(a, TEMP_REG3, uintptr_t(m_hash.base()));
if (modep.is_immediate())
- {
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG2, modep.immediate() * 8)); // TEMP_REG1 = m_base[modep]
- }
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG3, modep.immediate() << 3)); // TEMP_REG1 = m_base[modep]
else
- {
- const a64::Gp mode = modep.select_register(TEMP_REG1, 8);
- mov_reg_param(a, 4, mode, modep);
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG2, mode, a64::lsl(3))); // TEMP_REG1 = m_base[modep]
- }
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG3, mode, a64::lsl(3))); // TEMP_REG1 = m_base[modep]
if (pcp.is_immediate())
{
- const uint32_t l1val = ((pcp.immediate() >> m_hash.l1shift()) & m_hash.l1mask()) * 8;
- const uint32_t l2val = ((pcp.immediate() >> m_hash.l2shift()) & m_hash.l2mask()) * 8;
+ const uint32_t l1val = ((pcp.immediate() >> m_hash.l1shift()) & m_hash.l1mask());
+ const uint32_t l2val = ((pcp.immediate() >> m_hash.l2shift()) & m_hash.l2mask());
- if (is_valid_immediate(l1val, 15))
- {
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, l1val));
- }
+ if (!is_valid_immediate(l1val, 12))
+ a.mov(TEMP_REG2, l1val);
+ if (!is_valid_immediate(l2val, 12))
+ a.mov(TEMP_REG3, l2val);
+
+ if (is_valid_immediate(l1val, 12))
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, l1val << 3));
else
- {
- a.mov(SCRATCH_REG1, l1val >> 3);
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, SCRATCH_REG1, a64::lsl(3)));
- }
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, TEMP_REG2, a64::lsl(3)));
- if (is_valid_immediate(l2val, 15))
- {
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, l2val));
- }
+ if (is_valid_immediate(l2val, 12))
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, l2val << 3));
else
- {
- a.mov(SCRATCH_REG1, l2val >> 3);
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, SCRATCH_REG1, a64::lsl(3)));
- }
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG1, TEMP_REG3, a64::lsl(3)));
}
- else
- {
- const a64::Gp pc = pcp.select_register(TEMP_REG2, 8);
- mov_reg_param(a, 4, pc, pcp);
+ }
- a.ubfx(TEMP_REG3, pc, m_hash.l1shift(), m_hash.l1bits()); // (pc >> m_l1shift) & m_l1mask
- a.ldr(TEMP_REG3, a64::Mem(TEMP_REG1, TEMP_REG3, a64::lsl(3))); // TEMP_REG3 = m_base[mode][(pc >> m_l1shift) & m_l1mask]
+ if (!pcp.is_immediate())
+ {
+ a.ubfx(TEMP_REG3, pc, m_hash.l1shift(), m_hash.l1bits()); // (pc >> m_l1shift) & m_l1mask
+ a.ubfx(TEMP_REG2, pc, m_hash.l2shift(), m_hash.l2bits()); // (pc >> m_l2shift) & m_l2mask
- a.ubfx(TEMP_REG2, pc, m_hash.l2shift(), m_hash.l2bits()); // (pc >> m_l2shift) & m_l2mask
- a.ldr(TEMP_REG1, a64::Mem(TEMP_REG3, TEMP_REG2, a64::lsl(3))); // x25 = m_base[mode][(pc >> m_l1shift) & m_l1mask][(pc >> m_l2shift) & m_l2mask]
- }
+ a.ldr(TEMP_REG3, a64::Mem(TEMP_REG1, TEMP_REG3, a64::lsl(3))); // TEMP_REG3 = m_base[mode][(pc >> m_l1shift) & m_l1mask]
+ a.ldr(TEMP_REG1, a64::Mem(TEMP_REG3, TEMP_REG2, a64::lsl(3))); // TEMP_REG1 = m_base[mode][(pc >> m_l1shift) & m_l1mask][(pc >> m_l2shift) & m_l2mask]
}
Label lab = a.new_label();
+ a.mov(a64::sp, a64::x29);
a.adr(REG_PARAM1, lab);
a.br(TEMP_REG1);
diff --git a/src/devices/cpu/drcbeut.cpp b/src/devices/cpu/drcbeut.cpp
index 2b3b8917f6c..cbe3bab6aeb 100644
--- a/src/devices/cpu/drcbeut.cpp
+++ b/src/devices/cpu/drcbeut.cpp
@@ -10,10 +10,22 @@
#include "drcbeut.h"
#include <algorithm>
+#include <numeric>
namespace drc {
+namespace {
+
+template <typename T>
+std::size_t block_bytes(unsigned count, std::align_val_t align)
+{
+ std::size_t const blocksize = count * sizeof(T);
+ return ((blocksize + (std::size_t(align) - 1)) / std::size_t(align)) * std::size_t(align);
+}
+
+} // anonymous namespace
+
using namespace uml;
@@ -33,23 +45,47 @@ using namespace uml;
// drc_hash_table - constructor
//-------------------------------------------------
-drc_hash_table::drc_hash_table(drc_cache &cache, uint32_t modes, uint8_t addrbits, uint8_t ignorebits) :
+drc_hash_table::drc_hash_table(
+ drc_cache &cache,
+ uint32_t modes,
+ uint8_t addrbits,
+ uint8_t ignorebits,
+ std::align_val_t modealign,
+ std::align_val_t l1align,
+ std::align_val_t l2align) :
m_next_l1_block(0),
m_next_l2_block(0),
m_cache(cache),
- m_modes(modes),
+ m_modealign(std::align_val_t(std::lcm(std::size_t(modealign), alignof(drccodeptr **)))),
+ m_l1align(std::align_val_t(std::lcm(std::size_t(l1align), alignof(drccodeptr *)))),
+ m_l2align(std::align_val_t(std::lcm(std::size_t(l2align), alignof(drccodeptr)))),
m_nocodeptr(nullptr),
+ m_modes(modes),
m_l1bits((addrbits - ignorebits) / 2),
m_l2bits((addrbits - ignorebits) - m_l1bits),
m_l1shift(ignorebits + m_l2bits),
m_l2shift(ignorebits),
m_l1mask((1 << m_l1bits) - 1),
m_l2mask((1 << m_l2bits) - 1),
- m_base(reinterpret_cast<drccodeptr ***>(cache.alloc(modes * sizeof(**m_base), std::align_val_t(alignof(drccodeptr *))))),
- m_emptyl1(reinterpret_cast<drccodeptr **>(m_cache.alloc(sizeof(drccodeptr *) << m_l1bits, std::align_val_t(alignof(drccodeptr *))))),
- m_emptyl2(reinterpret_cast<drccodeptr *>(m_cache.alloc(sizeof(drccodeptr) << m_l2bits, std::align_val_t(alignof(drccodeptr)))))
+ m_base(reinterpret_cast<drccodeptr ***>(cache.alloc(modes * sizeof(drccodeptr **), m_modealign))),
+ m_emptyl1(reinterpret_cast<drccodeptr **>(m_cache.alloc(sizeof(drccodeptr *) << m_l1bits, m_l1align))),
+ m_emptyl2(reinterpret_cast<drccodeptr *>(m_cache.alloc(sizeof(drccodeptr) << m_l2bits, m_l2align))),
+ m_l1alloc(std::min<std::size_t>(L1_ALLOCATION, m_modes) * block_bytes<drccodeptr *>(1 << m_l1bits, m_l1align)),
+ m_l2alloc(L2_ALLOCATION * block_bytes<drccodeptr>(1 << m_l2bits, m_l2align))
{
- m_l1blocks.reserve(L1_ALLOCATION);
+#if defined(MAME_DEBUG)
+ osd_printf_verbose(
+ "drc_hash_table: parameters:\n"
+ "%u modes, %u address bits, %u ignored, %u L1 bits ((pc >> %u) & 0x%x), %u L2 bits ((pc >> %u) >> 0x%x)\n"
+ "L1 allocation %u (%u * %u), L2 allocation %u (%u * %u)\n",
+ m_modes, addrbits, ignorebits,
+ m_l1bits, m_l1shift, m_l1mask,
+ m_l2bits, m_l2shift, m_l2mask,
+ m_l1alloc, block_bytes<drccodeptr *>(1 << m_l1bits, m_l1align), std::min<std::size_t>(L1_ALLOCATION, m_modes),
+ m_l2alloc, block_bytes<drccodeptr>(1 << m_l2bits, m_l2align), L2_ALLOCATION);
+#endif
+
+ m_l1blocks.reserve(std::min<std::size_t>(L1_ALLOCATION, m_modes));
m_l2blocks.reserve(L2_ALLOCATION);
if (m_emptyl1)
@@ -67,7 +103,7 @@ drc_hash_table::drc_hash_table(drc_cache &cache, uint32_t modes, uint8_t addrbit
// new ones
//-------------------------------------------------
-bool drc_hash_table::reset()
+bool drc_hash_table::reset() noexcept
{
// start from the beginning of the allocated blocks
m_next_l1_block = 0;
@@ -84,6 +120,41 @@ bool drc_hash_table::reset()
//-------------------------------------------------
+// populate_mode - try to populate mode table
+// entry
+//-------------------------------------------------
+
+bool drc_hash_table::populate_mode(uint32_t mode) noexcept
+{
+ assert(mode < m_modes);
+ if (m_base[mode] != m_emptyl1)
+ return true;
+
+ // copy-on-write for the L1 hash table
+ if (m_l1blocks.size() == m_next_l1_block)
+ {
+ try { m_l1blocks.reserve(m_l1blocks.size() + std::min<std::size_t>(L1_ALLOCATION, m_modes)); }
+ catch (std::bad_alloc const &) { return false; }
+
+ drccodeptr **const l1block = reinterpret_cast<drccodeptr **>(m_cache.alloc(m_l1alloc, m_l1align));
+ if (!l1block)
+ return false;
+
+ for (unsigned i = 0; std::min<std::size_t>(L1_ALLOCATION, m_modes) > i; ++i)
+ {
+ void *const table = reinterpret_cast<uint8_t *>(l1block) + (block_bytes<drccodeptr *>(1 << m_l1bits, m_l1align) * i);
+ m_l1blocks.emplace_back(reinterpret_cast<drccodeptr **>(table));
+ }
+ }
+
+ m_base[mode] = m_l1blocks[m_next_l1_block++];
+ std::fill_n(m_base[mode], 1 << m_l1bits, m_emptyl2);
+
+ return true;
+}
+
+
+//-------------------------------------------------
// block_begin - note the beginning of a block
//-------------------------------------------------
@@ -131,7 +202,7 @@ void drc_hash_table::block_end(drcuml_block &block)
// codeptr
//-------------------------------------------------
-void drc_hash_table::set_default_codeptr(drccodeptr nocodeptr)
+void drc_hash_table::set_default_codeptr(drccodeptr nocodeptr) noexcept
{
// nothing to do if the same
drccodeptr const old = m_nocodeptr;
@@ -143,7 +214,7 @@ void drc_hash_table::set_default_codeptr(drccodeptr nocodeptr)
std::fill_n(m_emptyl2, 1 << m_l2bits, nocodeptr);
// now scan all existing L2 tables for entries
- for (size_t i = 0; m_next_l2_block > i; ++i)
+ for (std::size_t i = 0; m_next_l2_block > i; ++i)
{
for (uint32_t l2entry = 0; (1 << m_l2bits > l2entry); ++l2entry)
{
@@ -159,25 +230,10 @@ void drc_hash_table::set_default_codeptr(drccodeptr nocodeptr)
// mode/pc
//-------------------------------------------------
-bool drc_hash_table::set_codeptr(uint32_t mode, uint32_t pc, drccodeptr code)
+bool drc_hash_table::set_codeptr(uint32_t mode, uint32_t pc, drccodeptr code) noexcept
{
- // copy-on-write for the L1 hash table
- assert(mode < m_modes);
- if (m_base[mode] == m_emptyl1)
- {
- if (m_l1blocks.size() == m_next_l1_block)
- {
- drccodeptr **const l1block = reinterpret_cast<drccodeptr **>(m_cache.alloc((L1_ALLOCATION * sizeof(drccodeptr *)) << m_l1bits, std::align_val_t(alignof(drccodeptr *))));
- if (!l1block)
- return false;
-
- for (unsigned i = 0; L1_ALLOCATION > i; ++i)
- m_l1blocks.emplace_back(l1block + (i << m_l1bits));
- }
-
- m_base[mode] = m_l1blocks[m_next_l1_block++];
- std::fill_n(m_base[mode], 1 << m_l1bits, m_emptyl2);
- }
+ if (!populate_mode(mode))
+ return false;
// copy-on-write for the L2 hash table
uint32_t const l1 = (pc >> m_l1shift) & m_l1mask;
@@ -185,12 +241,18 @@ bool drc_hash_table::set_codeptr(uint32_t mode, uint32_t pc, drccodeptr code)
{
if (m_l2blocks.size() == m_next_l2_block)
{
- drccodeptr *const l2block = reinterpret_cast<drccodeptr *>(m_cache.alloc((L2_ALLOCATION * sizeof(drccodeptr)) << m_l2bits, std::align_val_t(alignof(drccodeptr))));
+ try { m_l2blocks.reserve(m_l2blocks.size() + L2_ALLOCATION); }
+ catch (std::bad_alloc const &) { return false; }
+
+ drccodeptr *const l2block = reinterpret_cast<drccodeptr *>(m_cache.alloc(m_l2alloc, m_l2align));
if (!l2block)
return false;
for (unsigned i = 0; L2_ALLOCATION > i; ++i)
- m_l2blocks.emplace_back(l2block + (i << m_l2bits));
+ {
+ void *const table = reinterpret_cast<uint8_t *>(l2block) + (block_bytes<drccodeptr>(1 << m_l2bits, m_l2align) * i);
+ m_l2blocks.emplace_back(reinterpret_cast<drccodeptr *>(table));
+ }
}
m_base[mode][l1] = m_l2blocks[m_next_l2_block++];
diff --git a/src/devices/cpu/drcbeut.h b/src/devices/cpu/drcbeut.h
index 5e2786ddaec..82639695cb3 100644
--- a/src/devices/cpu/drcbeut.h
+++ b/src/devices/cpu/drcbeut.h
@@ -34,7 +34,14 @@ class drc_hash_table
{
public:
// construction/destruction
- drc_hash_table(drc_cache &cache, uint32_t modes, uint8_t addrbits, uint8_t ignorebits);
+ drc_hash_table(
+ drc_cache &cache,
+ uint32_t modes,
+ uint8_t addrbits,
+ uint8_t ignorebits,
+ std::align_val_t modealign = std::align_val_t(alignof(drccodeptr **)),
+ std::align_val_t l1align = std::align_val_t(alignof(drccodeptr **)),
+ std::align_val_t l2align = std::align_val_t(alignof(drccodeptr *)));
// getters
drccodeptr ***base() const noexcept { return m_base; }
@@ -45,17 +52,19 @@ public:
offs_t l1mask() const noexcept { return m_l1mask; }
offs_t l2mask() const noexcept { return m_l2mask; }
bool is_mode_populated(uint32_t mode) const noexcept { return m_base[mode] != m_emptyl1; }
+ bool is_l1_populated(uint32_t mode, uint32_t l1) const noexcept { return m_base[mode][l1] != m_emptyl2; }
// set up and configuration
- bool reset();
- void set_default_codeptr(drccodeptr code);
+ bool reset() noexcept;
+ void set_default_codeptr(drccodeptr code) noexcept;
+ bool populate_mode(uint32_t mode) noexcept;
// block begin/end
void block_begin(drcuml_block &block, const uml::instruction *instlist, uint32_t numinst);
void block_end(drcuml_block &block);
// code pointer access
- bool set_codeptr(uint32_t mode, uint32_t pc, drccodeptr code);
+ bool set_codeptr(uint32_t mode, uint32_t pc, drccodeptr code) noexcept;
drccodeptr get_codeptr(uint32_t mode, uint32_t pc) const noexcept { assert(mode < m_modes); return m_base[mode][(pc >> m_l1shift) & m_l1mask][(pc >> m_l2shift) & m_l2mask]; }
bool code_exists(uint32_t mode, uint32_t pc) const noexcept { return get_codeptr(mode, pc) != m_nocodeptr; }
@@ -65,25 +74,29 @@ private:
std::vector<drccodeptr **> m_l1blocks;
std::vector<drccodeptr *> m_l2blocks;
- size_t m_next_l1_block;
- size_t m_next_l2_block;
+ std::size_t m_next_l1_block;
+ std::size_t m_next_l2_block;
// internal state
- drc_cache & m_cache; // cache where allocations come from
- uint32_t const m_modes; // number of modes supported
-
- drccodeptr m_nocodeptr; // pointer to code which will handle missing entries
-
- uint8_t const m_l1bits; // bits worth of entries in L1 hash tables
- uint8_t const m_l2bits; // bits worth of entries in L2 hash tables
- uint8_t const m_l1shift; // shift to apply to the PC to get the L1 hash entry
- uint8_t const m_l2shift; // shift to apply to the PC to get the L2 hash entry
- offs_t const m_l1mask; // mask to apply after shifting
- offs_t const m_l2mask; // mask to apply after shifting
-
- drccodeptr *** m_base; // pointer to the L1 table for each mode
- drccodeptr ** m_emptyl1; // pointer to empty L1 hash table
- drccodeptr * m_emptyl2; // pointer to empty L2 hash table
+ drc_cache & m_cache; // cache where allocations come from
+ std::align_val_t const m_modealign; // desired alignment for mode table
+ std::align_val_t const m_l1align; // desired alignment for L1 tables
+ std::align_val_t const m_l2align; // desired alignment for L2 tables
+ drccodeptr m_nocodeptr; // pointer to code which will handle missing entries
+ uint32_t const m_modes; // number of modes supported
+
+ uint8_t const m_l1bits; // bits worth of entries in L1 hash tables
+ uint8_t const m_l2bits; // bits worth of entries in L2 hash tables
+ uint8_t const m_l1shift; // shift to apply to the PC to get the L1 hash entry
+ uint8_t const m_l2shift; // shift to apply to the PC to get the L2 hash entry
+ offs_t const m_l1mask; // mask to apply after shifting
+ offs_t const m_l2mask; // mask to apply after shifting
+
+ drccodeptr *** const m_base; // pointer to the L1 table for each mode
+ drccodeptr ** const m_emptyl1; // pointer to empty L1 hash table
+ drccodeptr * const m_emptyl2; // pointer to empty L2 hash table
+ std::size_t const m_l1alloc; // bytes to allocate for L1 tables
+ std::size_t const m_l2alloc; // bytes to allocate for L2 tables
};
diff --git a/src/devices/cpu/drcbex64.cpp b/src/devices/cpu/drcbex64.cpp
index 953da55b247..852e88e74e2 100644
--- a/src/devices/cpu/drcbex64.cpp
+++ b/src/devices/cpu/drcbex64.cpp
@@ -2044,7 +2044,7 @@ void drcbe_x64::op_hashjmp(Assembler &a, const instruction &inst)
a.mov(rsp, MABS(&m_near.stacksave)); // mov rsp,[stacksave]
// fixed mode cases
- if (modep.is_immediate() && m_hash.is_mode_populated(modep.immediate()))
+ if (modep.is_immediate() && m_hash.populate_mode(modep.immediate()))
{
if (pcp.is_immediate())
{
diff --git a/src/devices/cpu/drccache.cpp b/src/devices/cpu/drccache.cpp
index b4f53bcd60f..f09a451c59a 100644
--- a/src/devices/cpu/drccache.cpp
+++ b/src/devices/cpu/drccache.cpp
@@ -18,15 +18,27 @@
namespace {
template <typename T, typename U>
-constexpr T *ALIGN_PTR_UP(T *p, U align)
+constexpr T *align_ptr_up(T *p, U align)
{
- return reinterpret_cast<T *>((uintptr_t(p) + (align - 1)) & ~uintptr_t(align - 1));
+ return !(align & (align - 1))
+ ? reinterpret_cast<T *>((uintptr_t(p) + (align - 1)) & ~uintptr_t(align - 1))
+ : reinterpret_cast<T *>(((uintptr_t(p) + (align - 1)) / align) * align);
}
template <typename T, typename U>
-constexpr T *ALIGN_PTR_DOWN(T *p, U align)
+constexpr T *align_ptr_down(T *p, U align)
{
- return reinterpret_cast<T *>(uintptr_t(p) & ~uintptr_t(align - 1));
+ return !(align & (align - 1))
+ ? reinterpret_cast<T *>(uintptr_t(p) & ~uintptr_t(align - 1))
+ : reinterpret_cast<T *>((uintptr_t(p) / align) * align);
+}
+
+template <typename T, typename U>
+constexpr bool is_ptr_aligned(T *p, U align)
+{
+ return !(align & (align - 1))
+ ? !(uintptr_t(p) & (uintptr_t(align) - 1))
+ : !(uintptr_t(p) % uintptr_t(p));
}
} // anonymous namespace
@@ -38,6 +50,37 @@ constexpr T *ALIGN_PTR_DOWN(T *p, U align)
//**************************************************************************
//-------------------------------------------------
+// helpers
+//-------------------------------------------------
+
+inline std::size_t drc_cache::free_link_bucket(std::size_t bytes) noexcept
+{
+ return ((bytes + (CACHE_ALIGNMENT - 1)) / CACHE_ALIGNMENT) - 1;
+}
+
+
+inline void drc_cache::ensure_writable(drccodeptr ptr) noexcept
+{
+ if (!m_rwx && (ptr < m_rwbase))
+ {
+ drccodeptr const top = align_ptr_down(ptr, m_cache->page_size());
+ assert(top >= m_base);
+ m_cache->set_access(top - m_near, m_rwbase - top, osd::virtual_memory_allocation::READ_WRITE);
+ m_rwbase = top;
+ }
+}
+
+
+void drc_cache::make_executable() noexcept
+{
+ drccodeptr const top = align_ptr_up(m_top, m_cache->page_size());
+ assert(top <= m_limit);
+ m_cache->set_access(m_rwbase - m_near, top - m_rwbase, osd::virtual_memory_allocation::READ_EXECUTE);
+ m_rwbase = top;
+}
+
+
+//-------------------------------------------------
// construction/destruction
//-------------------------------------------------
@@ -56,10 +99,12 @@ drc_cache::drc_cache(std::size_t bytes) noexcept
, m_flush_count(0)
#if defined(MAME_DEBUG)
, m_near_allocated(0)
+ , m_near_padding(0)
, m_near_oversize(0)
, m_near_freed(0)
, m_near_reused(0)
, m_cache_allocated(0)
+ , m_cache_padding(0)
, m_cache_oversize(0)
, m_cache_freed(0)
, m_cache_reused(0)
@@ -79,20 +124,25 @@ drc_cache::~drc_cache()
try
{
m_max_temporary = std::max<std::size_t>(m_max_temporary, m_top - m_base);
- osd_printf_verbose("drc_cache: Statistics:\nFlush count %u, near cache use %u, permanent cache use %u/%u, maximum temporary cache use %u\n",
+ osd_printf_verbose(
+ "drc_cache: Statistics:\nFlush count %u, near cache use %u, permanent cache use %u/%u, maximum temporary cache use %u\n",
m_flush_count,
m_neartop - m_near,
m_size - (m_end - m_near),
m_size - (m_limit - m_near),
m_max_temporary);
#if defined(MAME_DEBUG)
- osd_printf_verbose("Near cache allocated %u (%u oversize), freed %u reused %u\nPermanent cache allocated %u (%u oversize), freed %u reused %u\n",
+ osd_printf_verbose(
+ "Near cache allocated %u (%u oversize, %u alignment padding), freed %u, reused %u\n"
+ "Permanent cache allocated %u (%u oversize, %u alignment padding), freed %u, reused %u\n",
m_near_allocated,
m_near_oversize,
+ m_near_padding,
m_near_freed,
m_near_reused,
m_cache_allocated,
m_cache_oversize,
+ m_cache_padding,
m_cache_freed,
m_cache_reused);
#endif
@@ -128,7 +178,7 @@ void drc_cache::allocate_cache(bool rwx)
m_cache.emplace({ NEAR_CACHE_SIZE, m_size - NEAR_CACHE_SIZE }, osd::virtual_memory_allocation::READ_WRITE_EXECUTE);
m_near = reinterpret_cast<drccodeptr>(m_cache->get());
m_neartop = m_near;
- m_base = ALIGN_PTR_UP(m_near + NEAR_CACHE_SIZE, m_cache->page_size());
+ m_base = align_ptr_up(m_near + NEAR_CACHE_SIZE, m_cache->page_size());
m_top = m_base;
m_limit = m_near + m_cache->size();
m_end = m_limit;
@@ -188,35 +238,52 @@ void *drc_cache::alloc(std::size_t bytes, std::align_val_t align) noexcept
assert(bytes);
assert(std::size_t(align));
- if (UNEXPECTED((std::size_t(align) > CACHE_ALIGNMENT) || (CACHE_ALIGNMENT % std::size_t(align))))
- osd_printf_error("drc_cache: Requested alignment %u not satisfied by cache alignment %u\n", std::size_t(align), CACHE_ALIGNMENT);
-
// pick first from the free list
- if (bytes < MAX_PERMANENT_ALLOC)
+ std::size_t const bucket = free_link_bucket(bytes);
+ if (bucket < m_free.size())
{
- free_link **const linkptr = &m_free[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
- free_link *const link = *linkptr;
- if (link)
+ free_link **link;
+ for (link = &m_free[bucket]; *link; link = &(*link)->m_next)
+ {
+ if (is_ptr_aligned(*link, std::size_t(align)))
+ break;
+ }
+
+ if (*link)
{
#if defined(MAME_DEBUG)
++m_cache_allocated;
++m_cache_reused;
#endif
- *linkptr = link->m_next;
- return link;
+ return std::exchange(*link, (*link)->m_next);
}
}
// if no space, we just fail
- drccodeptr const ptr = ALIGN_PTR_DOWN(m_end - bytes, CACHE_ALIGNMENT);
- drccodeptr const limit = ALIGN_PTR_DOWN(ptr, m_cache->page_size());
+ drccodeptr const ptr = align_ptr_down(m_end - bytes, std::lcm(std::size_t(align), CACHE_ALIGNMENT));
+ drccodeptr const limit = align_ptr_down(ptr, m_cache->page_size());
if (m_top > limit)
return nullptr;
+ drccodeptr const end = align_ptr_up(ptr + bytes, CACHE_ALIGNMENT);
+ if (end < m_end)
+ {
+#if defined(MAME_DEBUG)
+ ++m_cache_padding;
+#endif
+ std::size_t const padbucket = free_link_bucket(m_end - end);
+ if (padbucket < m_free.size())
+ {
+ free_link *const link = reinterpret_cast<free_link *>(end);
+ link->m_next = m_free[bucket];
+ m_free[bucket] = link;
+ }
+ }
+
// otherwise update the end of the cache
#if defined(MAME_DEBUG)
++m_cache_allocated;
- if (bytes >= MAX_PERMANENT_ALLOC)
+ if (bucket >= m_free.size())
++m_cache_oversize;
#endif
m_limit = limit;
@@ -235,34 +302,53 @@ void *drc_cache::alloc_near(std::size_t bytes, std::align_val_t align) noexcept
assert(bytes);
assert(std::size_t(align));
- if (UNEXPECTED(((std::size_t(align) > CACHE_ALIGNMENT) || (CACHE_ALIGNMENT % std::size_t(align)))))
- osd_printf_error("drc_cache: Requested alignment %u not satisfied by cache alignment %u\n", std::size_t(align), CACHE_ALIGNMENT);
-
// pick first from the free list
- if (bytes < MAX_PERMANENT_ALLOC)
+ std::size_t const bucket = free_link_bucket(bytes);
+ if (bucket < m_nearfree.size())
{
- free_link **const linkptr = &m_nearfree[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
- free_link *const link = *linkptr;
- if (link)
+ free_link **link;
+ for (link = &m_nearfree[bucket]; *link; link = &(*link)->m_next)
+ {
+ if (is_ptr_aligned(*link, std::size_t(align)))
+ break;
+ }
+
+ if (*link)
{
#if defined(MAME_DEBUG)
++m_near_allocated;
++m_near_reused;
#endif
- *linkptr = link->m_next;
- return link;
+ return std::exchange(*link, (*link)->m_next);
}
}
// if no space, we just fail
- drccodeptr const ptr = ALIGN_PTR_UP(m_neartop, CACHE_ALIGNMENT);
+ drccodeptr const top = align_ptr_up(m_neartop, CACHE_ALIGNMENT);
+ drccodeptr const ptr = align_ptr_up(m_neartop, std::lcm(std::size_t(align), CACHE_ALIGNMENT));
+ assert(ptr >= top);
if ((ptr + bytes) > m_base)
return nullptr;
- // otherwise update the top of the near part of the cache
+ // add alignment padding to the free list
+ if (ptr > top)
+ {
+#if defined(MAME_DEBUG)
+ ++m_near_padding;
+#endif
+ std::size_t const padbucket = free_link_bucket(ptr - top);
+ if (padbucket < m_nearfree.size())
+ {
+ free_link *const link = reinterpret_cast<free_link *>(top);
+ link->m_next = m_nearfree[bucket];
+ m_nearfree[bucket] = link;
+ }
+ }
+
+ // update the top of the near part of the cache
#if defined(MAME_DEBUG)
++m_near_allocated;
- if (bytes >= MAX_PERMANENT_ALLOC)
+ if (bucket >= m_nearfree.size())
++m_near_oversize;
#endif
m_neartop = ptr + bytes;
@@ -289,7 +375,7 @@ void *drc_cache::alloc_temporary(std::size_t bytes, std::align_val_t align) noex
return nullptr;
// otherwise, update the cache top
- m_top = ALIGN_PTR_UP(ptr + bytes, std::lcm(std::size_t(align), CACHE_ALIGNMENT));
+ m_top = align_ptr_up(ptr + bytes, std::lcm(std::size_t(align), CACHE_ALIGNMENT));
ensure_writable(ptr);
return ptr;
}
@@ -303,19 +389,22 @@ void *drc_cache::alloc_temporary(std::size_t bytes, std::align_val_t align) noex
void drc_cache::dealloc(void *memory, std::size_t bytes) noexcept
{
drccodeptr const mem = reinterpret_cast<drccodeptr>(memory);
- assert(bytes < MAX_PERMANENT_ALLOC);
assert(((mem >= m_near) && (mem < m_base)) || ((mem >= m_end) && (mem < (m_near + m_size))));
// determine which free list to add to
- free_link **const linkptr = &((mem < m_base) ? m_nearfree : m_free)[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
+ auto &freelist = (mem < m_base) ? m_nearfree : m_free;
+ std::size_t const bucket = free_link_bucket(bytes);
+ if (bucket >= freelist.size())
+ return; // oversize allocations just leak
+
#if defined(MAME_DEBUG)
++((mem < m_base) ? m_near_freed : m_cache_freed);
#endif
// link is into the free list for our size
free_link *const link = reinterpret_cast<free_link *>(memory);
- link->m_next = *linkptr;
- *linkptr = link;
+ link->m_next = freelist[bucket];
+ freelist[bucket] = link;
}
@@ -361,7 +450,7 @@ drccodeptr drc_cache::end_codegen()
// update the cache top
osd::invalidate_instruction_cache(m_codegen, m_top - m_codegen);
- m_top = ALIGN_PTR_UP(m_top, CACHE_ALIGNMENT);
+ m_top = align_ptr_up(m_top, CACHE_ALIGNMENT);
m_codegen = nullptr;
return result;
@@ -394,24 +483,3 @@ void drc_cache::request_oob_codegen(drc_oob_delegate &&callback, void *param1, v
oob->m_param1 = param1;
oob->m_param2 = param2;
}
-
-
-inline void drc_cache::ensure_writable(drccodeptr ptr) noexcept
-{
- if (!m_rwx && (ptr < m_rwbase))
- {
- drccodeptr const top = ALIGN_PTR_DOWN(ptr, m_cache->page_size());
- assert(top >= m_base);
- m_cache->set_access(top - m_near, m_rwbase - top, osd::virtual_memory_allocation::READ_WRITE);
- m_rwbase = top;
- }
-}
-
-
-void drc_cache::make_executable() noexcept
-{
- drccodeptr const top = ALIGN_PTR_UP(m_top, m_cache->page_size());
- assert(top <= m_limit);
- m_cache->set_access(m_rwbase - m_near, top - m_rwbase, osd::virtual_memory_allocation::READ_EXECUTE);
- m_rwbase = top;
-}
diff --git a/src/devices/cpu/drccache.h b/src/devices/cpu/drccache.h
index 3cf44f5d600..6c6a8776a9f 100644
--- a/src/devices/cpu/drccache.h
+++ b/src/devices/cpu/drccache.h
@@ -15,6 +15,7 @@
#include "modules/lib/osdlib.h"
#include <algorithm>
+#include <array>
#include <cstdlib>
#include <list>
#include <optional>
@@ -139,16 +140,20 @@ private:
void * m_param2; // 2nd pointer parameter
};
- void ensure_writable(drccodeptr ptr) noexcept;
- void make_executable() noexcept;
-
- std::optional<osd::virtual_memory_allocation> m_cache;
-
struct free_link
{
free_link * m_next; // pointer to the next guy
};
+ using free_link_array = std::array<free_link *, MAX_PERMANENT_ALLOC / CACHE_ALIGNMENT>;
+
+ static std::size_t free_link_bucket(std::size_t bytes) noexcept;
+
+ void ensure_writable(drccodeptr ptr) noexcept;
+ void make_executable() noexcept;
+
+ std::optional<osd::virtual_memory_allocation> m_cache;
+
// core parameters
drccodeptr m_near; // pointer to the near part of the cache
drccodeptr m_neartop; // unallocated area of near cache
@@ -162,22 +167,24 @@ private:
bool m_rwx; // whether pages can be simultaneously writable and executable
// oob management
- std::list<oob_handler> m_oob_list; // list of active oob handlers
- std::list<oob_handler> m_oob_free; // list of recyclable oob handlers
+ std::list<oob_handler> m_oob_list; // list of active oob handlers
+ std::list<oob_handler> m_oob_free; // list of recyclable oob handlers
// free lists
- free_link * m_free[MAX_PERMANENT_ALLOC / CACHE_ALIGNMENT];
- free_link * m_nearfree[MAX_PERMANENT_ALLOC / CACHE_ALIGNMENT];
+ free_link_array m_free;
+ free_link_array m_nearfree;
// stats
std::size_t m_max_temporary;
uint32_t m_flush_count;
#if defined(MAME_DEBUG)
uint32_t m_near_allocated;
+ uint32_t m_near_padding;
uint32_t m_near_oversize;
uint32_t m_near_freed;
- uint32_t m_near_reused;
+ uint32_t m_near_reused;
uint32_t m_cache_allocated;
+ uint32_t m_cache_padding;
uint32_t m_cache_oversize;
uint32_t m_cache_freed;
uint32_t m_cache_reused;