From 518185b808dce7b86d5e4cc8c56c73fa1a8e2137 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Fri, 13 Nov 2026 19:12:51 -0500 Subject: [PATCH] Set vk_sub_in_frame under the submission lock from the place the submitting thread noted, so a thread about to submit no longer changes it under another thread's replay. vk_sub_in_frame says which of its frame's submissions the one being recorded is; vk_memory.c sub_place files the mirror pre-snapshot's statistics by it during the replay, on the replaying thread and its workers. vk_be_submit wrote it on every submitting thread before submit_sync, outside sub_lock, so with the replay on the submitting threads another thread entering a submit changed it under a replay in progress, and the replay filed its work at the other submission's place. Each thread now notes the place in a thread-local (vk_be_submit, and replay_main for the asynchronous replay), and vk_sync.c submit publishes it right after taking the lock for a submission with commands (vk_sub_in_frame_publish, a weak no-op without the back end). Submissions without commands of their own (held work, the present thread's blit) leave it as it was, as before. Performance heuristics only; no timing change. Not reproduced before the change. Checks that passed: pass_cc_check on every runtime file (a header changed), no new -Wall -Wextra warnings in vk_backend.c and vk_sync.c. Co-Authored-By: Claude Opus 5.5 --- runtime/vk.h | 3 +++ runtime/vk_backend.c | 11 +++++++++-- runtime/vk_sync.c | 5 ++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/runtime/vk.h b/runtime/vk.h index 9da201b9..e24b06de 100644 --- a/runtime/vk.h +++ b/runtime/vk.h @@ -963,6 +963,9 @@ void vk_gpu_parts_log(uint64_t frame, uint64_t t0, uint64_t t1, double fence_ms) void vk_gtl_note(int kind, uint64_t sub_ns, uint64_t start_tick, uint64_t end_tick); void vk_gtl_log(uint64_t frame, int frames); extern int vk_sub_in_frame; // replay: the submission being recorded is the frame's n-th (0 the first; vk_backend.c) +// vk_sync.c submit, sub_lock held, for a submission with commands: vk_sub_in_frame takes the place in its frame the +// submitting thread noted for it (vk_backend.c vk_be_submit, replay_main). +void vk_sub_in_frame_publish(void); // Runs fn(arg) on the completion thread once fence is done on the GPU (event writes, counter reports, sync state). // Callbacks run in the order they were added, after those of earlier values; a value already done runs at once there. void vk_completion_add(uint64_t fence, void (*fn)(void *), void *arg); diff --git a/runtime/vk_backend.c b/runtime/vk_backend.c index 007d49ce..94ad5cf8 100644 --- a/runtime/vk_backend.c +++ b/runtime/vk_backend.c @@ -634,7 +634,14 @@ static uint64_t submit_sync(const uint64_t *handles, int n) { return f; } +// vk_sub_in_frame is the replay's: vk_sync.c submit sets it under the submission lock from the place the submitting +// thread noted for its submission (vk_sub_in_frame_publish), so another thread about to submit cannot change it under +// a replay in progress (vk_memory.c sub_place reads it there and on the replay workers). Submissions without commands +// of their own (held work, the present thread's blit) leave it as it was. int vk_sub_in_frame; +static _Thread_local int sub_place_next; // this thread's next submission's place in its frame + +void vk_sub_in_frame_publish(void) { vk_sub_in_frame = sub_place_next; } static void replay_main(void *arg) { (void)arg; @@ -650,7 +657,7 @@ static void replay_main(void *arg) { plat_cond_broadcast(&rcv); plat_mutex_unlock(&rl); if (it.kind == RI_SUBMIT) { - vk_sub_in_frame = sub_idx++; + sub_place_next = sub_idx++; uint64_t f = submit_sync(it.h, it.n); if (f != it.value) rt_fatal("vk: replayed submission signalled %llu, queued as %llu", (unsigned long long)f, @@ -933,7 +940,7 @@ static void vk_be_program_release(const prog_t *p) { replay_drain(DR_PROG_RELEAS static uint64_t vk_be_submit(const uint64_t *handles, int n) { vk_lowlat_submit_start(); // a frame's first submission: its simulation is over (nothing while low latency is off) if (!gfx_replay_async()) { - vk_sub_in_frame = __atomic_load_n(&nvn_submits_in_frame, __ATOMIC_RELAXED); // the front end counts it up after the submit returns + sub_place_next = __atomic_load_n(&nvn_submits_in_frame, __ATOMIC_RELAXED); // counted up after the submit returns return submit_sync(handles, n); } vk_sync_start(); diff --git a/runtime/vk_sync.c b/runtime/vk_sync.c index 10f45b25..3d057c10 100644 --- a/runtime/vk_sync.c +++ b/runtime/vk_sync.c @@ -1483,6 +1483,7 @@ static uint64_t submit(const uint64_t *handles, int n, const OwnCommands *own) { VkCtx *c = vk_ctx(); plat_mutex_lock(&sub_lock); vk_present_frames_owed(); // frames presented while another thread replayed close before this replay begins + if (n > 0 && !own) vk_sub_in_frame_publish(); // this submission's place in its frame, for the replay vk_mem_dirty_submit(); // NX2NAVI_TRACE=perf: what a mirror would upload before this submit uint64_t fence = __atomic_load_n(&issued, __ATOMIC_ACQUIRE) + 1; cur_fence = fence; @@ -1581,8 +1582,10 @@ int vk_sync_try_locked(void (*fn)(void)) { return 1; } -// vk_backend.c's, which runs the frames' bookkeeping (vk_frame_owed.h); builds without the back end have none. +// vk_backend.c's, which run the frames' bookkeeping (vk_frame_owed.h) and set the place in the frame of the submission +// being recorded; builds without the back end have neither. WEAK void vk_present_frames_owed(void) {} +WEAK void vk_sub_in_frame_publish(void) {} // st_lock held: p at the end of the queue *q. static void pending_push(Pending **q, int *head, int *n, int *cap, Pending p) {