From d02220f5789f66e869bf01fb2d1a30e8163ea303 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Wed, 11 Nov 2026 17:09:43 -0500 Subject: [PATCH] Give NVN2 compute programs the local memory their control section asks for, and put spills after it. An NVN2 compute program's control section gives its local memory in bytes a thread at +0x68, beside the shared memory size at +0x64 that the job already carried. Without it the translator declared its default of 0x1000 bytes: one of the game's compute programs needs 4,752 bytes a thread, and its translation declared a 1,024-word local array that it indexed up to word 1,187, outside the array. The job now carries "local_mem" from +0x68, and the lowering starts a compute program's spill area after its own local memory (at least 0x1000 bytes) instead of always at 0x1000, where a larger program's own memory would have overlapped it. Checked: that program's translation now declares 1,188 words (indices up to 1,187) and passes spirv-val; two programs of 3,456 bytes a thread translate as before. All 266 compute programs whose stored input id starts with 0 in an installed shader cache translate and pass spirv-val (vulkan1.3), and tools/xlate_smoke.py passes. Co-Authored-By: Claude Opus 5.5 --- runtime/gfx_shader.c | 5 +++++ tools/xlate/AmpereLowering.cs | 11 +++++++---- tools/xlate/Program.cs | 7 ++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/runtime/gfx_shader.c b/runtime/gfx_shader.c index 1653b3b7..77be1d9f 100644 --- a/runtime/gfx_shader.c +++ b/runtime/gfx_shader.c @@ -1527,6 +1527,11 @@ static size_t build_request(char *o0, const char *hex, const char *stage, const memcpy(&magic0, control, 4); if (!shared && magic0 == 0x98761234u && !strcmp(stage, "cs")) memcpy(&shared, control + 0x64, 4); o += sprintf(o, "},\"shared\":%u", shared); + // Their local memory, bytes a thread, follows at +0x68. Without it the translator declares its 0x1000-byte + // default: one compute program of the game needs 4,752 bytes a thread and indexed past the end of its array. + uint32_t local = 0; + if (magic0 == 0x98761234u && !strcmp(stage, "cs")) memcpy(&local, control + 0x68, 4); + if (local && local <= (1u << 20)) o += sprintf(o, ",\"local_mem\":%u", local); } if (!strcmp(stage, "cs")) { uint32_t l[3] = {1, 1, 1}; diff --git a/tools/xlate/AmpereLowering.cs b/tools/xlate/AmpereLowering.cs index 9d0bc383..864843d2 100644 --- a/tools/xlate/AmpereLowering.cs +++ b/tools/xlate/AmpereLowering.cs @@ -2536,7 +2536,10 @@ static class AmpereLowering // localBytes: the local memory size the lowered program needs when that changed (spill mode), else 0. Graphics // programs carry it in their header already; compute and task/mesh programs need it from the translator's host // (job "local_mem"). - public static byte[] Lower(byte[] code, int size, bool graphics, out int localBytes, int[] table = null, MeshInfo mesh = null) + // computeLocal: a compute program's own local memory in bytes, where its spill area starts (0: the translator's + // compute default of 0x1000). + public static byte[] Lower(byte[] code, int size, bool graphics, out int localBytes, int[] table = null, MeshInfo mesh = null, + int computeLocal = 0) { localBytes = 0; int header = graphics || mesh != null ? 0x80 : 0; @@ -2555,9 +2558,9 @@ static class AmpereLowering try { lowered = new Lowerer(insns, sph, !graphics, table, mesh) { C1 = C1Bytes }.Run(out branches, out first); } catch (RegisterPressureException) { - // Local memory after the program's own: the header's sizes (translator: low + high + CRS / 32), or the - // translator's compute default (0x1000) for compute programs. - spillAt = 0x1000; + // Local memory after the program's own: the header's sizes (translator: low + high + CRS / 32), or for a + // compute program its own size (computeLocal), the translator's compute default (0x1000) when not given. + spillAt = computeLocal > 0 ? computeLocal : 0x1000; if (header != 0) spillAt = ((int)(BitConverter.ToUInt32(code, 4) & 0xffffff) + (int)(BitConverter.ToUInt32(code, 8) & 0xffffff) + (int)(BitConverter.ToUInt32(code, 12) & 0xffffff) / 32 + 15) & ~15; diff --git a/tools/xlate/Program.cs b/tools/xlate/Program.cs index 840078e6..8360dc9e 100644 --- a/tools/xlate/Program.cs +++ b/tools/xlate/Program.cs @@ -199,8 +199,13 @@ static class SwshXlate } else { - code = AmpereLowering.Lower(code, size, (string)job["stage"] != "cs", out int local, table); + // A compute program's own local memory: "local_mem" from its control section, at least the + // translator's default of 0x1000 bytes; the lowering's spill area goes after it. + bool cs = (string)job["stage"] == "cs"; + int own = cs ? (Math.Max(job["local_mem"] is JsonNode lm ? (int)lm : 0, 0x1000) + 15) & ~15 : 0; + code = AmpereLowering.Lower(code, size, !cs, out int local, table, null, own); if (local != 0) job["local_mem"] = local; + else if (cs) job["local_mem"] = own; } if (job["lowered_out"] is JsonNode lo) File.WriteAllBytes((string)lo, code); }