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 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-11 17:09:43 -05:00
parent b5c9a9b38e
commit d02220f578
3 changed files with 18 additions and 5 deletions

View File

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

View File

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

View File

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