Commit Graph
32237 Commits
Author SHA1 Message Date
Douglas Whittingham c4d1fcd2ec recomp: resolve uncovered PCs against the module map in the coverage report
The report printed raw guest addresses. Those only mean something next to the
boot log from the same run, because the load bases move - and a report kept
from one run and read against another silently resolves to the wrong offset,
which is a confident wrong answer rather than a missing one. It cost a whole
trace here: the top entry was attributed to the wrong function, and the
disassembly around it supported a plausible and entirely incorrect conclusion.

The runtime already builds the module map to find each image's base; this keeps
a copy and prints it, then annotates every uncovered PC with module+offset.
2026-09-11 09:56:50 -10:00
dougchansan 6ca0aa2cab recompiler: keep chaining to unconditional branches only
Chaining conditional branches as well measured 2.00 ms/frame against 1.80 and
spread the distribution from 1.70 to 2.54 across six reps while dynarmic held
at 2.905 in the same runs.
2026-09-11 01:26:17 -10:00
dougchansan 966a5782b3 recompiler: call the target of a direct branch instead of returning to the dispatcher
Chains are bounded by a budget the dispatcher refills, so interrupts and SVCs
still get a look in, and the spent budget gives it the executed-block count.
Bounded low because these are not tail calls at -O1 and guest threads run on
512 KB fibers.
2026-09-11 00:41:59 -10:00
dougchansan 1cbf903237 frontend: compact, per-thread module dispatch
The owner hint was one global int shared by every CPU thread, so cores in
different modules overwrote each other's and the fast path missed. The scan also
walked the records themselves, ~56 bytes each with a std::string at the front.
Now a per-thread hint over a sorted array of 24-byte entries that exits early.
2026-09-10 23:46:04 -10:00
dougchansan cda579711f recompiler: movi/mvni; shl implemented but off
movi is a win. shl frees the hot block that also contains movi - transitions
drop 47% - and costs 2.13 ms/frame against 1.95, because the freed block is
SIMD-heavy and the JIT compiles it better than the emitted C.
2026-09-10 23:25:50 -10:00
dougchansan eb3158991b recompiler: stop translating fixed-point conversions
Measured: 25.0s with them, 20.7s without, on the reference replay. Translating an
instruction moves its whole block out of the JIT, and these sit in blocks the JIT
compiles better than the emitted C does.
2026-09-10 21:06:04 -10:00
dougchansan 4d68a569cd recompiler: fold the fixed-point scale into a literal 2026-09-10 19:07:25 -10:00
dougchansan 4fa6dd2123 recompiler: fixed-point conversions, ld1r, ushl/sshl; mcp: report vps 2026-09-10 18:02:19 -10:00
Douglas Whittingham 79d982f798 docs: finish neutralising the comments, and repair two broken by the pass
A blind substitution left "the target title 8 / Deluxe's" in
program_metadata.cpp and "two the target title updates" in the README, and it
left the version numbers and NAND directory indices that made the anonymising
pointless.

Comment-only. The rationale in each is worth keeping - it is why the code
looks the way it does - but none of it needed a version, an index or a
measured share to make its point.

Leaves src/frontend_common/firmware_manager.h alone: that title ID is
upstream's, not ours.
2026-09-10 17:25:48 -10:00
dougchansan 2e86c7c468 recompiler: abs/neg, fmla/fmls by element, fccmp 2026-09-10 17:03:25 -10:00
dougchansan 24925bdf8f mcp: report tas playback progress 2026-09-10 16:40:19 -10:00
dougchansan f8a4bff5ad trim comment 2026-09-10 16:37:11 -10:00
dougchansan c17af5c9ef recompiler: handle all fp-to-int rounding modes 2026-09-10 16:20:04 -10:00
dougchansan 87b3f56bae frontend: zero-initialize the debugger pane pointers
microProfileDialog is only assigned inside #if MICROPROFILE_ENABLED, which
is off on Linux, and the member had no in-class initializer. ApplyAppMode's
null guard therefore tested uninitialized heap and the setVisible call
through the garbage pointer segfaulted, so suyu -hacker exited 139 before
the MCP listener started. Plain suyu survived because it never reaches that
tail.

The three sibling panes get the same treatment: their guards should mean
what they say regardless of which widgets a given platform compiles in.
2026-09-10 14:57:09 -10:00
dougchansan 5f48ddebf3 docs: Linux builds and runs; record what it needs that Windows does not
The README said only the Windows build was exercised. Both now build and run,
and the five Linux-specific requirements are worth stating rather than
rediscovering: CMake 3.31, bundled fmt, Qt6 Charts, system Boost, and skipping
the ownfoil submodule.
2026-09-10 14:22:49 -10:00
dougchansan b0ea70a3a0 build: bump dynarmic for the include_next boost shim 2026-09-10 14:19:34 -10:00
dougchansan 9c0aad4374 build: bump dynarmic for the portable boost/variant shim
The relative-path fix only worked where CPM had fetched boost. On Linux the
system package is used instead and there is no CPM boost, so the path pointed at
nothing.
2026-09-10 14:16:25 -10:00
dougchansan 3c7a146c6e fix: stray carriage returns inside string literals break every non-MSVC build
Three log format strings in arm_recomp.cpp contained a literal CR byte before
the \n:

    o += "  --- SVCs by call count ---\r\n";   (an actual 0x0D, not an escape)

MSVC accepts that. GCC treats the CR as end of line and reports "missing
terminating \" character" followed by a cascade of stray-backslash errors, so
the first Linux build of this fork failed 242 files in.

They came from repairing heredoc-mangled literals during earlier work and went
unnoticed because the file is otherwise LF-terminated and \r\n in a log looks
perfectly normal on Windows.

Found by building on Linux for the first time, which is the point of doing so.
2026-09-10 14:09:20 -10:00
dougchansan 23d7674606 feat: expose TAS playback and recording over MCP
trigger_ui_action gains tas_start_stop, tas_record and tas_reset.

Comparing two CPU backends needs the same workload twice, and this emulator has
no savestates - CreateSaveState and LoadSaveState are stubs - so replaying a
recorded run is the only way to reach the same point in a real race repeatedly.
The TAS engine already does record and playback; it just had no way in other
than a keyboard shortcut.
2026-09-10 12:46:40 -10:00
dougchansan 6137bb75dd recomp: index blocks directly instead of binary searching for them
recomp_lookup ran two binary searches on every block boundary - one over
segments, one over that segment's entries - which for the main image is about
twenty probes across 600,000 entries, tens of millions of times a second. It was
the largest remaining cost in the recompiled path.

Guest instructions are 4-byte aligned and a module's blocks span one contiguous
range, so (pc - lo) >> 2 indexes a flat array directly: one bounds check, one
load. Built once from the sorted tables in recomp_image_set_base, which the
loader calls before any guest thread exists, and read-only afterwards - so no
locking and no per-thread state. Costs one pointer per instruction word, about
24 MB for the main image, and falls back to the binary search if the allocation
fails.

Measured on the target title, frame limiter and vsync off:

  phase   before   after    vs dynarmic
  race     152      189     0.77x -> 0.95x
  title    430      795     0.27x -> 0.43x

The race phase is now within 5% of the JIT.

A thread-local cache in front of the old lookup was tried first and rejected: it
gained 40% on the title screen but lost 34% in the race, because 16 KB per thread
stops being free once the title actually uses its threads. Indexing the module
once, shared and read-only, has the same effect with none of that.

recomp_runtime.h now includes <stdlib.h>. The generated module source pulls in
only that header and <stdint.h>, so calloc was an implicit declaration returning
int, the pointer was truncated to 32 bits, and the first lookup dereferenced
garbage - a crash 20 seconds into boot with nothing in the log to explain it.
2026-09-10 12:27:40 -10:00
dougchansan 6c71eb03bf recomp: resolve guest addresses through the page table
Every guest load and store in generated code was an indirect call into the
emulator, which then walked its own page table. The recompiled build topped out
around 415 FPS on the target title's title screen no matter how little work
there was, against dynarmic's 1891 - the signature of a fixed per-access cost
rather than anything workload-dependent.

Memory::GetPageTableView hands the recompiler what it needs to resolve an
address itself, and the generated accessors now mirror GetPointerImpl's fast
path exactly: mask the address, bounds check, read one page table entry, extract
the backing pointer. A null pointer means unmapped, debug, or GPU-tracked
memory, all of which still take the callback so the rasterizer is told about the
access - nothing suyu would have done is bypassed.

Measured on the attract sequence, frame limiter and vsync off:

  phase   before   after
  race     133      152   (+14%)
  title    362      430   (+19%)

Against dynarmic the race-phase ratio goes 0.62x -> 0.77x.

Forcing the accessors inline at every access site was tried first and was worse
than doing nothing: main.dll went from 100 MB to 222 MB and the race phase lost
14% (133 -> 115). Whatever the call costs, the instruction cache costs more, so
they are defined once per module instead. The two changes had been made
together; separating them is what showed the page table lookup was the win and
the inlining was the loss.

RecompHostMem gains static_asserts on every field offset. This struct is
duplicated by hand between the emitter and the bridge because the generated
project shares no headers with the emulator, and inserting the new fields in the
middle of one while appending them to the other made recompiled code dereference
a function pointer as a page table. suyu died during boot with no diagnostic.

RuntimeC is now assembled from two literals: it passed MSVC's 16380-byte limit
for a single string literal (C2026).
2026-09-10 10:19:39 -10:00
dougchansan 04debd04ac feat: report FPS through the MCP state tool
get_emulator_state has always advertised an "fps" field in its schema and never
filled it in, so callers got null and had to read the status bar off a
screenshot.

Now reports fps, frame_ms, emulation_speed and shaders_building. Served from a
cache updated on the status bar's own tick rather than by calling
GetAndResetPerfStats in the handler: that call clears the counters, so a poller
and the status bar cannot both use it without stealing each other's numbers.

shaders_building matters as much as the fps value for anything automated - a
sample taken while shaders compile measures the shader compiler, not the
emulator, and those samples have to be discarded rather than averaged in.
2026-09-10 09:28:10 -10:00
dougchansan bbe9850653 build: point the dynarmic submodule at our fork
externals/mcl/include/boost/variant.hpp shipped with an absolute path into a
developer's home directory, so the build failed anywhere else. mk8-recomp had
been carrying that as the last hand-maintained patch file, applied by its
bootstrap script.

Forked to dougchansan/dynarmic, branch mk8-recomp, with the path made relative;
this repository now points there. suyu-emu/dynarmic is not archived, so unlike
the fixes in this repository that one could reasonably go upstream as a PR.
2026-09-10 08:54:31 -10:00
dougchansan 5760549db8 docs: README reflects the continuation and what changed in 0.0.5
The archive banner said this was the final release and that no further
development was planned, which stopped being true when the fork picked it up.
Replaced with a continuation notice: where the base came from, why the name and
version line are kept, and what the (mk8-recomp) suffix in BUILD_FULLNAME is for.

Added a Changes in v0.0.5 section. Five of the entries are defects in suyu
itself rather than recompiler work and matter to anyone running the emulator -
in particular that no installed update or DLC was ever indexed in NAND, for any
title, which also explains why the installed-title listing always reported zero.

Also noted that only the Windows build is exercised here. Linux and Android are
inherited from upstream and have not been built since the fork; claiming
otherwise in a status section would be a guess.
2026-09-10 08:34:26 -10:00
dougchansan 05c443c0bb build: 0.0.4 -> 0.0.5, and record where this came from
suyu was archived upstream; this continues it rather than forking under a new
name, so the numbering continues too: 0.0.4 -> 0.0.5.

Upstream is inconsistent about its own version - the repository is named
suyu-v0.0.4, the tag reads v0.04-latest, and BUILD_FULLNAME was hardcoded to
v0.04. Normalised to the three-part form here. Read literally, v0.04 means 0.4,
which was evidently not the intent.

BUILD_FULLNAME carries (mk8-recomp) so a built binary says which 0.0.5 it is.
The archived repository could in principle be picked up by someone else, and the
suffix removes that ambiguity without putting anything odd in the version number
itself.

PROVENANCE.md records the base commit, the licensing, and the significant
changes GPL-3 5(a) asks modified versions to state - including the five defects
fixed in suyu itself, which are not recompiler-specific and affect ordinary
emulation.
2026-09-10 08:26:18 -10:00