mirror of
https://github.com/blackbearreloaded/ProsperoEden.git
synced 2026-10-19 05:09:50 -05:00
The decoder tables are built from one parse of each instruction pattern
This commit is contained in:
1 parent
66fe402d5b
commit
2baaf548ae
3 files changed
+66
No files matched your search
@@ -291,6 +291,34 @@ list(FILTER jit_sources EXCLUDE REGEX "(^|/)a64_translate\\.cpp$")
|
||||
set_property(TARGET dynarmic PROPERTY SOURCES "${jit_sources}")
|
||||
target_sources(dynarmic PRIVATE "${PORT_BUILD_DIR}/a64_translate.cpp")
|
||||
|
||||
# Decoder tables: upstream fills 4096 lists and, for each list, parses the bit string of every
|
||||
# instruction pattern again (~640 for A64, ~260 for A32): about half a second in the first block
|
||||
# a game compiles, on the guest core that asked for it. Parse every pattern once, then fill the
|
||||
# lists from that, in the same order (tools/check-decoder-startup.py).
|
||||
foreach(decoder IN ITEMS "A64/decoder/a64" "A32/decoder/arm")
|
||||
set(decoder_source "${PROJECT_SOURCE_DIR}/src/dynarmic/src/dynarmic/frontend/${decoder}")
|
||||
file(READ "${decoder_source}.h" decoder_header)
|
||||
get_filename_component(decoder_name "${decoder}" NAME)
|
||||
set(decoder_loop " for (size_t i = 0; i < t.size(); ++i) {\n#define INST")
|
||||
set(decoder_match " if ((i & make_fast_index(mask)) == make_fast_index(expect)) { \\\n t[i].emplace_back(")
|
||||
set(decoder_end "#include \"./${decoder_name}.inc\"\n#undef INST\n }\n return t;")
|
||||
foreach(anchor IN ITEMS decoder_loop decoder_match decoder_end)
|
||||
string(FIND "${decoder_header}" "${${anchor}}" offset)
|
||||
if(offset LESS 0)
|
||||
message(FATAL_ERROR "Pinned ${decoder_name} decoder table changed (${anchor})")
|
||||
endif()
|
||||
endforeach()
|
||||
string(REPLACE "${decoder_loop}" " std::vector<Handler> all;\n {\n#define INST" decoder_header "${decoder_header}")
|
||||
string(REPLACE "${decoder_match}" " { \\\n all.emplace_back(" decoder_header "${decoder_header}")
|
||||
string(REPLACE "${decoder_end}" "#include \"${decoder_source}.inc\"\n#undef INST\n }\n for (size_t i = 0; i < t.size(); ++i)\n for (auto const& e : all)\n if ((i & make_fast_index(e.mask)) == make_fast_index(e.expect))\n t[i].push_back(e);\n return t;" decoder_header "${decoder_header}")
|
||||
# The other use of the pattern list in the header (the instruction's name).
|
||||
string(REPLACE "#include \"./${decoder_name}.inc\"" "#include \"${decoder_source}.inc\"" decoder_header "${decoder_header}")
|
||||
write_derived("${PORT_BUILD_DIR}/include/dynarmic/frontend/${decoder}.h" "${decoder_header}")
|
||||
endforeach()
|
||||
# The override headers shadow upstream's only by include order: a definition on the target
|
||||
# recompiles every dynarmic object that saw the upstream ones.
|
||||
target_compile_definitions(dynarmic PRIVATE EDEN_DECODER_ONE_PARSE=1)
|
||||
|
||||
# Cancel low-lane extraction of a scalar that was just zero-extended to Q.
|
||||
# Preserve other lanes, arbitrary vectors and all floating-point operations.
|
||||
file(READ "${PROJECT_SOURCE_DIR}/src/dynarmic/src/dynarmic/ir/opt_passes.cpp" optimizer)
|
||||
|
||||
@@ -66,6 +66,7 @@ python3 -B "$root/tools/check-heap-growth.py"
|
||||
python3 -B "$root/tools/check-crash-report.py"
|
||||
"$scratch/build/bin/eden-scalar-check"
|
||||
"$scratch/build/bin/eden-memory-check"
|
||||
python3 -B "$root/tools/check-decoder-startup.py"
|
||||
python3 -B "$root/headless/check_audio_shutdown.py" "$scratch/build/headless/core.cpp" "$scratch/source/src/core/core.cpp"
|
||||
"$scratch/build/bin/eden-romfs-check"
|
||||
"$scratch/build/bin/eden-ryujinx-check"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""The first block a game compiles must not pay for the instruction decoder's table.
|
||||
|
||||
Dynarmic builds a table of 4096 lists from its ~640 A64 (and ~260 A32) instruction patterns the
|
||||
first time a block is translated. Upstream parses every pattern's bit string again for each of
|
||||
the 4096 lists: about half a second on the first compilation, on the guest core that asked for
|
||||
it. The port parses each pattern once (headless/CMakeLists.txt, decoder tables).
|
||||
|
||||
This runs the host build's cold-compilation benchmark (eden-memory-check --cold-compile): three
|
||||
times the same 8192 blocks, each in a new JIT. Only the first builds the table, so it may not
|
||||
take much longer than the others. It also checks that both derived decoders have the one-parse
|
||||
form and list the handlers in upstream's order.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
cache = Path((root / '.local/headless-cache').read_text().strip())
|
||||
for name in ('A64/decoder/a64.h', 'A32/decoder/arm.h'):
|
||||
derived = (cache / 'build/headless/include/dynarmic/frontend' / name).read_text()
|
||||
assert 'for (size_t i = 0; i < t.size(); ++i) {\n#define INST' not in derived, name
|
||||
assert 'all.emplace_back(' in derived and 't[i].push_back(e);' in derived, name
|
||||
|
||||
def trials():
|
||||
output = subprocess.run([str(cache / 'build/bin/eden-memory-check'), '--cold-compile'],
|
||||
capture_output=True, text=True, check=True, timeout=120).stdout
|
||||
seconds = [float(s) for s in re.findall(r'COLD_COMPILE trial=\d blocks=8192 chain=1 seconds=([0-9.]+)', output)]
|
||||
assert len(seconds) == 3, output
|
||||
return seconds
|
||||
|
||||
# The best of three runs: another program's burst must not fail the build.
|
||||
ratio, first, later = min((s[0] / min(s[1:]), s[0], min(s[1:])) for s in (trials() for _ in range(3)))
|
||||
print(f'Decoder table: first compilation {first * 1000:.0f} ms, later {later * 1000:.0f} ms, ratio {ratio:.1f}')
|
||||
assert ratio < 3.0, 'the first compilation still pays for the decoder table'
|
||||
print('Decoder startup: the table is built from one parse of each pattern PASS')
|
||||
Reference in new issue
Block a user