Commit Graph
31 Commits
Author SHA1 Message Date
Valiantsin DzerakhandClaude Opus 5.5 7310481885 Save XMM registers with SSE moves in kern_ap_stub on builds without AVX
The asynchronous preemption stub saved and restored YMM0-15 with vmovdqa and ran vzeroupper in inline asm, which
-march=x86-64-v2 does not reach. The v2 runtime library therefore carried VEX code, and a CPU without AVX stopped
with an illegal instruction the first time a core owner was interrupted, a few seconds into boot (issue #454,
Pentium Gold G6400, totk+0xe7a2b50 = kern_ap_stub+0x20). Without __AVX__ the stub now saves XMM0-15 with movdqa into
the same slots; v2 game code keeps nothing in the upper YMM halves. The v3 stub is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:05:35 +03:00
Valiantsin DzerakhandClaude Opus 5.5 0ab16c21b5 SignalToAddress modify by waiting count subtracts 2 when it wakes all waiters
SignalAndModifyByWaitingCountIfEqual with count <= 0 and at least one waiter stored value - 1; Horizon stores
value - 2 (mesosphere KAddressArbiter::SignalAndModifyByWaitingCountIfEqual; yuzu, LAS Recomp and nxnative agree).
The word was already updated with compare-and-swap, as LAS Recomp 1144aea made theirs; the count > 0 values were
right.

--probe-kernel arbiter part expected the old value: it now wants 4 for value 6 with one waiter and count -1, and
4 for two waiters and count 0. Before: 5 for both. After: ok.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:16:06 +03:00
Valiantsin DzerakhandClaude Opus 5.5 1f824a6581 WaitSynchronization leaves a pending cancel to the next wait that would block
Ours took a pending CancelSynchronization first, before the signalled objects and before the timeout-0 return, so a
poll or a wait on a signalled object used it up and the blocking wait it was meant for ran on. Horizon checks a
signalled object, then timeout 0, then the cancel (mesosphere KSynchronizationObject::Wait; LAS Recomp 1144aea moved
the poll the same way): both keep it pending. A cancel that wakes a blocked wait still ends it cancelled:
CancelSynchronization now wakes it with R_CANCELLED, which the loop tells apart from a signal's -1.

--probe-kernel events part: a cancel sent before a poll and before a wait on a signalled event; each is followed by a
blocking wait with a 500 ms timeout. Before: the poll and the signalled wait returned 0xec01 (cancelled) and the
blocking waits timed out (0xea01). After: timeout, success, then cancelled both times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:15:54 +03:00
Valiantsin DzerakhandClaude Opus 5.5 7c1cd42aa5 Bind a system event again after the guest destroyed it
The game destroys the nfp activate and deactivate events and attaches the same SystemEventType again before each
detection. kern_bind_system_event kept the first writable handle per guest address and returned early, so the second
Attach left the event destroyed and the game's next TryWaitSystemEvent aborted (issue #398, equipping the Amiibo
power). A kept handle now counts only while the SDK's state byte of the event is nonzero; otherwise the event is
created again and its entry takes the new handle, and a signal to a destroyed event is dropped. probe-services
destroys and attaches both nfp events again and checks that they work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:20:36 +03:00
Valiantsin DzerakhandClaude Opus 5.5 659a2fa75f Let totk.ini set free_cores again and say loudly that the game crashes with it
Reverts dafc35f: free_cores = 1 in totk.ini lifts the virtual core gate again, as in 0.2.8. The mode stays as an
experiment, and every place a player meets it says the game crashes: totk.ini.example, a WARNING line in the start log,
and a line under every crash report and FATAL while the gate is off (kern_free_cores_note), so a log from #212 names
the cause at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 10:10:09 +03:00
Valiantsin DzerakhandClaude Opus 5.5 cacbdf2549 Leave a guest thread by our own register jump instead of the C runtime's longjmp
ExitThread went back to the thread start with longjmp on a buffer from a non-unwinding setjmp, whose frame field is 0.
When Control Flow Guard is on for the process, ucrtbase's longjmp checks the buffer first and ends the process with
fast fail 13 (0xc0000409) on a frame of 0: offset 0xb373c of ucrtbase 10.0.26100.9711, the offset in the event logs of
#222, #176 and #75. Exploit protection can force CFG on any program, and the first guest thread to exit, right after the
game's first six-axis read, killed the process with no line in the log; the abort and invalid-parameter handlers never
run for a fast fail.

plat_jmp_save and plat_jmp_go keep and restore the Win64 callee-saved registers, MXCSR and the x87 control word
themselves and validate nothing; Linux keeps setjmp and longjmp. Started with CFG forced on, the build before this
change ends with 0xc0000409 at the same log line and in --probe-kernel; this one reaches gameplay and passes the probe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 00:53:17 +03:00
Valiantsin DzerakhandClaude Opus 5.5 dafc35fb35 Keep the virtual core gate on when totk.ini sets free_cores
Earlier settings menus wrote free_cores = 1 under the label "Virtual core gate", which promised the gate. Without it a
thread that holds no virtual core reports its ideal core to GetCurrentProcessorNumber, so the game's job workers all
report core 0 and run its per-core code at once. On 1.2.1 two of them add the same per-core NVN control memory, which
retires the command handle the other just recorded, and its CallCommands fails at start (#212); on 1.4.3 the game
corrupts a heap in main+0x82e00 or hangs at the title menu. A player with such a totk.ini crashed at every start and
the menu no longer shows the row.

free_cores is no longer a setting: only TOTK_FREE_CORES in the environment lifts the gate, and a free_cores value in
totk.ini is logged as ignored. The kernel probe asks the kernel whether the gate is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 23:19:34 +03:00
Valiantsin DzerakhandClaude Opus 5.5 a554a7c13c Take the virtual core gate out of the settings menu
The row was labelled "Virtual core gate" with the text "Lets only one thread run per virtual core, as on the
console", but switching it on wrote free_cores = 1, which lifts the gate. Without the gate guest threads run in
parallel on one virtual core, and TotK keeps its per-core data without locks: an Arc B580 player who turned it on
crashed at every start with "CallCommands: command handle ... is not a live recording" (#212). free_cores stays an
ini key for experiments; the log now says when it is set, and totk.ini.example warns that the game can crash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:56:48 +03:00
Wellington Dias 1d51343ac1 Build and run the runtime natively on Linux.
Shared runtime code now goes through runtime/platform/platform.h, implemented by platform/win32.c and a new
platform/linux.c: futex locks, a userfaultfd and PAGEMAP_SCAN write watch, signal-based async preemption, our own
fiber switch, and a SIGSEGV handler for the thaw and crash hooks. The window, input, menu and software keyboard get
SDL3 back ends beside the Win32 ones, with the swapchain (gfx_present.c) and input merging (input.c) shared. SHA-256
replaces BCrypt, ELF builds get their own override sections, and the build tools gain Linux branches.

On Linux the selftest and the gfx-abi, astc, nvn and window probes pass. probe-vkmem still fails: imported guest
memory cannot be write-watched on Linux, which the shadow buffers in vk_memory.c will address.
2026-10-29 16:56:56 -03:00
Valiantsin DzerakhandClaude Opus 5.5 ce150130df Make code comments say what they mean instead of pointing at things a reader cannot follow
Comments cited codes from local notes (port-lessons P1, backlog), task labels (T6c1-T6c3, M4), bare upstream commit
hashes and issue numbers, a file that no longer exists (nvn_api.c, now nvn_api_*.c), a generated file (main_0103.c)
and line numbers into upstream files that refs/ has moved past, so they pointed at unrelated code. Those now name the
file, function or project and say what the cited code does. Attribution of copied code keeps its pinned commits.

Corrected facts: TOTK_REPLAY_ASYNC defaults to 0, and the shader cache, pipeline cache and pipeline keys live next to
totk.exe, not in build/v121.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:58:01 +03:00
Valiantsin DzerakhandClaude Opus 5.5 4e764cd39a Take world-clock windows in TOTK_PROFILE_FRAMES and TOTK_CRIT: w<lo>-<hi> is seconds of game time since the save loaded, so a profile or critical-path session covers the same play at any frame rate instead of a present range that lands elsewhere on a slow machine
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:56:50 +03:00
Valiantsin DzerakhandClaude Opus 5.5 c2943970cc Merge the spin wait gates: they also end the fight hangs
The generator emits a guest poll such as `ldr w9, [x19]; cmp w9, #1; b.eq` as a plain C load, and clang hoists it out
of the loop: the lock wait in f_7001fa0e2c compiled to `cmp; jne; jmp .`, so a thread that found the lock held spun
forever after its holder released it. A fight run hung this way in 3 of 13 launches on an RX 9070, with the UBO
snapshot on and off; gdb showed the thread on that `jmp` in every sample. The gate is an opaque call on every
back-edge of the 868 loops tools/spin_waits.py lists, so each pass reloads memory: the exe goes from 182 self-jumps to
148, and 144 of those are the game's own `b .` instructions. 8 of 8 fight runs then passed; tests/diff/func.py with
--patches passed 5470 cases on 878 functions, and the four probes passed on 1.4.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:05:44 +03:00
Valiantsin DzerakhandClaude Opus 5.5 5da6c5bb89 Merge the frame critical path recorder: TOTK_CRIT=<first>-<last frame> with TOTK_TRACE=sched writes the guest threads' state changes and wakes, read by tools/crit_path.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:13:40 +03:00
Valiantsin DzerakhandClaude Opus 5.5 2fe6a6d4c1 Keep MXCSR in line with FPCR where FPCR changes instead of checking it in every FP op
The FP helpers compared c->fpcr with a cached copy before every operation to resync MXCSR lazily. MXCSR now
follows FPCR.FZ at once: MSR FPCR calls rt_fpcr_sync, and so do rt_call (thread, fiber and host entry into guest
code), fiber resume (FIBER_FLAG_FLOAT_SWITCH fibers keep their own MXCSR) and the differential test harness per
case. op and vop only test FPCR.RMode for the slow path, and their redo check is one ordered-not-equal compare of
|r| with the smallest normal, the same NaN-or-smallest-normal set as before.

Hot guest code shrinks by about a quarter (132 functions holding half the guest cycles, 1.4.3); fight frame time
moves 1-3%, inside the run-to-run spread. lift, simd, func, atomic and flags_check pass on 1.4.3; simd.py fails
without the harness sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:25:25 +03:00
Valiantsin DzerakhandClaude Opus 5.5 ba52fe6e89 Record the critical path of the frame with TOTK_CRIT
With TOTK_TRACE=sched, TOTK_CRIT=<lo>-<hi> records every state change and wake of the guest threads and every
present over those frames (runtime/kernel.c crit_add from acct() and wake(), crit_frame at the present) and writes
them to TOTK_CRIT_OUT at the last frame. tools/crit_path.py walks back from each present through the waits to the
thread whose signal ended them and splits the frame interval by thread and state; the sums equal the interval.

A 1.4.3 fight: with the replay on the submitting thread the path is 7.3 ms of replay on the render thread and 4.3 ms
of guest code; with TOTK_REPLAY_ASYNC=1 it is 10.1 ms of guest code through the fork-join of the main thread and five
workers, at the same frame interval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:26:07 +03:00
Valiantsin DzerakhandClaude Opus 5.5 101f2c8b5e Give the virtual core up in nn::fs host functions and in NVN memory pool setup, as the Switch blocks those threads in IPC: the game's loader threads held a core through CloseFile, CreateCacheStorage and nvnMemoryPoolInitialize (up to 20 ms) while job workers waited. Core diagnostics now also report waits on an owner in a host call, with its module, offset, latest SVC and guest lr, and the AllocateMemoryPages and AllocateAddressRegion lines need TOTK_TRACE=host
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:42:14 +03:00
Valiantsin DzerakhandClaude Opus 5.5 3984344635 Spin wait gates: tools/spin_waits.py (the nxnative search) lists the loops that only another thread can end, the patch tables carry them as spin lines (857 in 1.4.3, 864 in 1.2.1), the generator sends their back-edges through rt_spin_gate, and the gate pauses the core once per pass and hands the virtual core to a waiter after 50 us of polling (TOTK_SPIN, TOTK_SPIN_HANDOFF_US)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:51:47 +03:00
Valiantsin DzerakhandClaude Opus 5.5 97d3762ba1 Strip milestone and task labels from code comments and drop the dead gfx_stubs.c
Comments named the agent task (T1-T7, M2-M5, Task 2.x) that once owned each file or block, and pointed at local
notes the repository does not carry (backlog items, milestone plans, research notes). They now say what the code
does. gfx_stubs.c held weak stand-ins for functions that every build has defined for a long time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:12:50 +03:00
Valiantsin DzerakhandClaude Opus 5.5 42b73c4098 Note what each 2026-10 frame time fix needs on a Linux host: the SleepThread spin is for the Windows timer only, the file I/O moved off the game and render threads is portable with fsync in place of FlushFileBuffers, the slow-present profiler is Windows-only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:01:22 +03:00
Valiantsin DzerakhandClaude Opus 5.5 95ae91687c Spin short SleepThread calls on the performance counter: the high-resolution waitable timer ended the SDK's 20 us polling sleeps after 460-515 us on the main thread and the job workers; the sched trace now reports wake-to-run and core-grant-to-run latency and asked versus taken sleep time per thread
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:17:13 +03:00
Valiantsin DzerakhandClaude Opus 5.5 bd9851f4f2 Trace the guest main thread's waits by object and by the thread whose signal ended them (TOTK_TRACE=sched), and sample the main thread alone with call chains (TOTK_PROFILE_MAIN=1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:23:35 +03:00
Valiantsin DzerakhandClaude Opus 5.5 b0805112ed Merge main into task/menu: the docs and comment cleanup, with the Dear ImGui set-up of menu.cpp kept in the LAS Recomp notices row
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:16:23 +03:00
Valiantsin DzerakhandClaude Opus 5.5 8b514f6ead Keep every default in the settings table: cfg_value, cfg_int, cfg_double and cfg_bool read it, and the consumers of table keys drop their own defaults
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:32:30 +03:00
Valiantsin DzerakhandClaude Opus 5.5 7b85a2681a Keep free_cores=0 as gate on, refuse to rewrite totk.ini after a failed read, drop empty-valued lines on Reset, and add plat_file_set_readonly and plat_file_size
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:23:55 +03:00
Valiantsin DzerakhandClaude Opus 5.5 111f748c51 Remove citations of local build/ notes from code comments and correct the TOTK_REPLAY_ASYNC default in vk_backend.c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:23:21 +03:00
Valiantsin DzerakhandClaude Opus 5.5 8553b00c9b Drop the bisect modes 3 to 5 of TOTK_ASYNC_PREEMPT and log interrupts after the owner is resumed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 07:44:47 +03:00
Valiantsin DzerakhandClaude Opus 5.5 8f9716527a Keep the interrupted code's flags in kern_ap_stub, take the whole integer context, log the first interrupts and add test modes 2 to 5 of TOTK_ASYNC_PREEMPT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:03:10 +03:00
Valiantsin DzerakhandClaude Opus 5.5 9fea5cc37b Credit MacNativeGames for the virtual-core preemption in THIRD_PARTY_NOTICES.md and the kernel.c header
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:49:11 +03:00
Valiantsin DzerakhandClaude Opus 5.5 c66ac84925 Preempt a virtual core owner in top-level game code from the waiting thread, with priority inheritance and the SDK's preemption disable, behind TOTK_ASYNC_PREEMPT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:37:11 +03:00
Valiantsin DzerakhandClaude Opus 5.5 d52cdfc6ee Add TOTK_CORE_DIAG: classify every virtual core wait by what the owner was doing, and answer SVC 0x36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 23:23:48 +03:00
Valiantsin DzerakhandClaude Opus 5.5 19c9b648d1 Add the recompiler, Windows runtime, tools and docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 22:27:54 +03:00