From cd3f015732b30370a9da8e6266576451708ac41a Mon Sep 17 00:00:00 2001 From: chrg6 Date: Fri, 13 Nov 2026 18:32:04 -0500 Subject: [PATCH] Log the guest's ExitProcess line after the exit guard, and bring the comments on gfx_window_closed's return up to date. The review of the exit sequence found that svc.c case 0x07 logged "guest ExitProcess at ...; exiting" before rt_exit_begin. When a close had already begun, the guest thread that the guard then parks still claimed to exit, and past the log cap the line fell outside the end's reserve, which rt_exit_begin opens. It also found that host.h and gfx_wsys.h still said gfx_window_closed does not return, though it returns for its own thread coming back, and that rt_exit_begin's comment spoke of the window thread handling a message inside the sequence, which since the close runs on a thread of its own happens only in the fallback. svc.c logs the line after rt_exit_begin returns 1. The two header comments say gfx_window_closed returns only for its own thread coming back (gfx_present.c rt_exit_begin), and rt_exit_begin's comment says the window thread runs the sequence itself only when gfx_window_win.c exit_start could make no thread. tests/exit_order_check.py checks statically that 0x07's rt_log comes after rt_exit_begin; it fails on the code before this change and passes after it. exit_order_check and the compile check of every runtime file (a header changed) pass, with no new -Wall warnings in svc.c and gfx_present.c. Co-Authored-By: Claude Opus 5.5 --- runtime/gfx_present.c | 5 +++-- runtime/gfx_wsys.h | 4 +++- runtime/host.h | 4 +++- runtime/svc.c | 9 +++++---- tests/exit_order_check.py | 5 +++++ 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/runtime/gfx_present.c b/runtime/gfx_present.c index cd38f6dc..138c3f6f 100644 --- a/runtime/gfx_present.c +++ b/runtime/gfx_present.c @@ -115,8 +115,9 @@ void gfx_window_focus_changed(int in) { // Once per process, whichever comes first. The window's close and the console's (main.c console_closed) can come // together, or the guest's ExitProcess beside either: each ran its own sequence, so two pipeline saves and two // frame-time summaries were written while both raced. 1 for the first caller; a caller on another thread waits here -// for good while the first ends the process; 0 for the first caller's own thread coming back (a message the window -// thread handles inside the sequence), which goes on with the sequence it is in. +// for good while the first ends the process; 0 for the first caller's own thread coming back, which goes on with the +// sequence it is in. The window's close runs on a thread of its own (gfx_window_win.c exit_start); the window thread +// runs the sequence itself, and may handle a message inside it, only when that thread could not be made. static volatile uint32_t exit_tid; // the thread that runs the orderly end, 0 before one started int rt_exit_begin(void) { diff --git a/runtime/gfx_wsys.h b/runtime/gfx_wsys.h index 7608ae06..6a1e62f9 100644 --- a/runtime/gfx_wsys.h +++ b/runtime/gfx_wsys.h @@ -19,7 +19,9 @@ int wsys_probe_click(int where); // ---- what gfx_present.c provides the back end void gfx_window_mark_dirty(void); // the window changed size: the next present rebuilds the swapchain void gfx_window_focus_changed(int in); // the window gained (1) or lost (0) the focus -void gfx_window_closed(void); // the player closed the window: saves, then ends the process +// the player closed the window: saves, then ends the process. It does not return, but for its own thread coming back +// (gfx_present.c rt_exit_begin) +void gfx_window_closed(void); void gfx_window_set_refresh(double hz); // the refresh rate of the window's monitor, 0 unknown int gfx_cfg_fullscreen(void); // NX2NAVI_FULLSCREEN void gfx_cfg_window_size(int *w, int *h); // NX2NAVI_WINDOW_SIZE, the default when unparsable or out of range diff --git a/runtime/host.h b/runtime/host.h index ee8200be..703442d9 100644 --- a/runtime/host.h +++ b/runtime/host.h @@ -116,7 +116,9 @@ void perf_summary_flush(void); // prints the frame-time summary void perf_summary_flush_try(void); // the same at a fatal error, the stutter map only if its lock is free void perf_focus_lost(void); // the window lost the focus: the session's reports at the next present, in play void gfx_exit_log(void); // back end's lines for the exit summary (weak no-op in prof_win.c) -void gfx_window_closed(void); // gfx_present.c (gfx_wsys.h): the end of a closed window; does not return +// gfx_present.c (gfx_wsys.h): the end of a closed window. It does not return, but for its own thread coming back +// (gfx_present.c rt_exit_begin) +void gfx_window_closed(void); // gfx_present.c: the orderly end every exit the game asks for shares (the window's close, svc.c ExitProcess, boot.c // --seconds). rt_exit_begin: 1 for the first caller; another thread waits there for good; 0 for that caller's own // thread again. rt_exit_steps: the saves and the put-backs, the slow pipeline save late and the final save flush last. diff --git a/runtime/svc.c b/runtime/svc.c index 61356f29..67a753dc 100644 --- a/runtime/svc.c +++ b/runtime/svc.c @@ -240,11 +240,12 @@ static void svc_body(Cpu *c, uint32_t imm, uint64_t pc) { } case 0x29: get_info(c, pc); return; case 0x07: { // ExitProcess, as closing the window: the orderly end (gfx_present.c rt_exit_steps), exit code 0 - char at[160]; - rt_log("guest ExitProcess at %s; exiting", ldr_describe(pc, at, sizeof at)); - // once, shared with the window's close: a close already ending the process keeps this thread there; this - // thread coming back would resume guest code after its ExitProcess, so it ends the process instead + // once, shared with the window's close: a close already ending the process keeps this thread there, and the + // exit line is that close's alone; this thread coming back would resume guest code after its ExitProcess, so it + // ends the process instead if (!rt_exit_begin()) plat_exit(0); + char at[160]; // after the guard, which also lets the line reach the log file past its cap (main.c log_sink) + rt_log("guest ExitProcess at %s; exiting", ldr_describe(pc, at, sizeof at)); rt_exit_steps(RT_EXIT_SETTINGS); fflush(NULL); if (svc_test_exit_hook) return svc_test_exit_hook(0); diff --git a/tests/exit_order_check.py b/tests/exit_order_check.py index 23506614..ce1d2d14 100644 --- a/tests/exit_order_check.py +++ b/tests/exit_order_check.py @@ -151,6 +151,11 @@ def static_checks(): print(f'{name}: {"rt_exit_begin, then " + steps + ", then plat_exit" if ok else "FAIL"}' + (f'; calls exit steps of its own: {", ".join(own)}' if own else ''), flush=True) bad += not ok + # the guest's exit line comes after the guard: a thread the guard parks (a close already ending) logs no exit + code = strip_comments(exit07) + ok = 0 <= code.find('rt_exit_begin()') < code.find('rt_log(') + print(f'svc.c ExitProcess (0x07): its exit line after rt_exit_begin: {"ok" if ok else "FAIL"}', flush=True) + bad += not ok # the cheats rebuild's end, before the guest runs: the guard, RTSS, the boot's marker and journal, the summary cheats = (RT / 'cheats.c').read_text(encoding='utf-8') body = strip_comments(function(cheats, 'static void rebuild_if_needed(const char *dir)'))