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 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 18:32:04 -05:00
parent 66c607414b
commit cd3f015732
5 changed files with 19 additions and 8 deletions

View File

@@ -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) {

View File

@@ -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

View File

@@ -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.

View File

@@ -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);

View File

@@ -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)'))