diff options
| author | 2026-01-12 23:35:52 +1100 | |
|---|---|---|
| committer | 2026-01-12 23:35:52 +1100 | |
| commit | b8987c7129139317df4e262b846e57e7909083dd (patch) | |
| tree | 48e3839f4599a64444d628ff8241ca4e1b919d98 | |
| parent | ee5ef66159633ba3feaf4f69cd4a3a2f3954cf48 (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.cpp | 82 | ||||
| -rw-r--r-- | src/devices/cpu/drcbeut.cpp | 120 | ||||
| -rw-r--r-- | src/devices/cpu/drcbeut.h | 55 | ||||
| -rw-r--r-- | src/devices/cpu/drcbex64.cpp | 2 | ||||
| -rw-r--r-- | src/devices/cpu/drccache.cpp | 184 | ||||
| -rw-r--r-- | src/devices/cpu/drccache.h | 27 |
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; |
