diff --git a/runtime/nvn_api_core.c b/runtime/nvn_api_core.c index ac4a43b9..79553e29 100644 --- a/runtime/nvn_api_core.c +++ b/runtime/nvn_api_core.c @@ -747,7 +747,7 @@ NVN_API(nvnQueueSubmitCommands) { NVN_BLOCKING(); nvn_call_note(0, (uintptr_t)q, 1); uint64_t no = __atomic_add_fetch(&lod_entered, 1, __ATOMIC_SEQ_CST); - q->submits++; + __atomic_add_fetch(&q->submits, 1, __ATOMIC_RELAXED); // two of the game's threads may submit on one queue at once uint64_t f; if (gfx_replay_async()) { // Prepare and queue under one lock: submit numbers follow queue order (gfx_replayed_submits). diff --git a/runtime/vk_backend.c b/runtime/vk_backend.c index 11df1faf..07af851c 100644 --- a/runtime/vk_backend.c +++ b/runtime/vk_backend.c @@ -949,20 +949,20 @@ static void vk_be_guest_read(uint64_t addr, uint64_t size) { // The count of such waits that came back while event-ordered work was still held (vk_exec.c): a submission stopped at an // event wait returns its value with its remainder held for a later replay, so the game may take a wait for that value // as the whole submission done and reuse what the held part still reads. How often that happens decides whether the -// value should be signalled only after the remainder (audit finding F-2-05). The count is unlocked: a diagnostic. +// value should be signalled only after the remainder (audit finding F-2-05). The counts are atomic adds without a lock: +// the game's threads wait at the same time, and each line is logged by the thread whose add reached it. // A wait for value 0 is left out: the window acquire's sync stands for no submission (nvn_api_obj.c // nvnWindowAcquireTexture gives it fence 0), and such waits were half of the lines in two sessions of 2026-11-13. static uint64_t synced_n, synced_held; static void synced_held_count(uint64_t fence) { if (!fence) return; - synced_n++; + uint64_t n = __atomic_add_fetch(&synced_n, 1, __ATOMIC_RELAXED); int held = vk_exec_held(); if (!held) return; - synced_held++; - if (synced_held <= 10 || synced_held % 600 == 0) + uint64_t k = __atomic_add_fetch(&synced_held, 1, __ATOMIC_RELAXED); + if (k <= 10 || k % 600 == 0) rt_log("vk: a game's wait for %llu returned with %d event-ordered submission(s) still held (%llu of %llu such " - "waits so far)", (unsigned long long)fence, held, (unsigned long long)synced_held, - (unsigned long long)synced_n); + "waits so far)", (unsigned long long)fence, held, (unsigned long long)k, (unsigned long long)n); } static void vk_be_cpu_synced(uint64_t fence) { diff --git a/runtime/vk_exec.c b/runtime/vk_exec.c index 9b135838..fcdeb647 100644 --- a/runtime/vk_exec.c +++ b/runtime/vk_exec.c @@ -1473,7 +1473,9 @@ static int cont_ready(const Cont *c) { // taken after the game has finished writing, and the frame is replayed in the call the game's fence stands for. // Only state may precede the wait in the command buffer, and the event must be one the CPU has signalled before; // the wait ends after NX2NAVI_GATE_WAIT_MS at the latest, and the submission is then held as before. -static struct { uint64_t n, ns, late; } GW; // since the last perf line: waits, their time, waits that ran out +// Since the last perf line: waits, their time, waits that ran out. vk_exec_gate_wait runs on the submitting thread +// before the submission lock, so several threads add at once: atomics, and the thread whose add ends the window logs it. +static struct { uint64_t n, ns, late; } GW; static const void *gate_first_wait(const op_hdr *h) { for (int k = 0; k < 256; k++) { @@ -1508,19 +1510,21 @@ void vk_exec_gate_wait(const uint64_t *handles, int n) { uint64_t t0 = kern_now_ns(), now = t0; for (unsigned i = 0; !vk_exec_event_holds_now(w); i++) { if ((i & 15) == 15 && (now = kern_now_ns()) - t0 >= (uint64_t)cap_ns) { - GW.late++; + __atomic_add_fetch(&GW.late, 1, __ATOMIC_RELAXED); break; } if (i < 20000) plat_pause(); else plat_yield(); } - GW.n++, GW.ns += kern_now_ns() - t0; - if (GW.n >= 600) { + __atomic_add_fetch(&GW.ns, kern_now_ns() - t0, __ATOMIC_RELAXED); + if (__atomic_add_fetch(&GW.n, 1, __ATOMIC_RELAXED) == 600) { // one thread takes the window; adds after it count in + uint64_t wn = __atomic_exchange_n(&GW.n, 0, __ATOMIC_RELAXED); + uint64_t wns = __atomic_exchange_n(&GW.ns, 0, __ATOMIC_RELAXED); + uint64_t late = __atomic_exchange_n(&GW.late, 0, __ATOMIC_RELAXED); if (rt_trace & RT_TRACE_PERF) rt_log("perf: gate: %llu submissions waited for the CPU's signal before their copies, %.2f ms on average; " - "%llu waited %lld ms without it and were held", (unsigned long long)GW.n, (double)GW.ns / (double)GW.n / 1e6, - (unsigned long long)GW.late, (long long)(cap_ns / 1000000)); - GW.n = GW.ns = GW.late = 0; + "%llu waited %lld ms without it and were held", (unsigned long long)wn, (double)wns / (double)wn / 1e6, + (unsigned long long)late, (long long)(cap_ns / 1000000)); } }