Skip to content

A null memory pool or buffer gets 0 or NULL from the NVN getters instead of a host crash (Ocarina of Time, Hyrule Field) - #1446

Closed
lunks wants to merge 1 commit into
seedds:mainfrom
lunks:pr/nvn-null-pool
Closed

lunks wants to merge 1 commit into
seedds:mainfrom
lunks:pr/nvn-null-pool

Conversation

@lunks

@lunks lunks commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

What and why

Ocarina of Time (Switch 2, 0400486024DEC000) can pass a NULL memory pool to nvnBufferInitialize on entering Hyrule Field, about 1.5 minutes after a save load (#1445):

[RED] BufferInitialize: pool has no storage (pool 0x0 flags 0 offset 0 size 0x3d0)
CRASH sig=11 addr=0x4 pc=h_nvnMemoryPoolGetFlags+0

The host then crashes reading the pool in the next getter. The eight getters (nvnMemoryPoolGetFlags, nvnMemoryPoolMap, nvnMemoryPoolGetSize, nvnBufferGetAddress, nvnBufferMap, nvnBufferGetSize, nvnBufferGetMemoryPool, nvnBufferGetMemoryOffset) now answer 0 or NULL for a null pool or buffer.

This only moves the failure. What made the game's allocator return no pool is not known. The host-side log shows no refused allocation or pool failure before it; the numbers at the crash (from #1445's log): heap 6.20 GB, alias region 16 GB with 5.25 GB mapped, 5,061 memory pools, 11.3 GB GPU allocated. The existing reference entry for the same two log lines (Model heaps sized by the host's command record sizes) is another candidate, and it now notes this case.

The change is the hle_nvn.c part of #1444 (ksolenk38ze), taken alone so it can land without the rest; the commit keeps their authorship.

Checked

Docs

  • The finding is in its game note or its docs/reference/<subsystem>.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • No game files, keys or data extracted from a game in the change or the attachments

…ead of a host crash

nvnMemoryPoolGetFlags, nvnMemoryPoolMap, nvnMemoryPoolGetSize, nvnBufferGetAddress, nvnBufferMap, nvnBufferGetSize, nvnBufferGetMemoryPool and nvnBufferGetMemoryOffset read through their argument without a check. When the game's allocator gives nvnBufferInitialize no pool (Ocarina of Time on entering Hyrule Field: `BufferInitialize: pool has no storage (pool 0x0 ...)`), the next getter call on the null pool crashed the host (SIGSEGV at 0x4 in h_nvnMemoryPoolGetFlags). They now answer 0 or NULL.

This only moves the failure: what made the game's allocator return no pool is not known. From seedds#1444 (hle_nvn.c); the reference entry notes the case.

Checked: make -C dm/host GAME=040074A01BF12000 builds; test_docs.py and docs_symptoms.py --check pass. Not played.
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation labels Nov 6, 2026
gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
… NULL from the NVN getters instead of a host crash (Ocarina of Time, Hyrule Field)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
gnosticgeek pushed a commit to gnosticgeek/nxforge that referenced this pull request Nov 6, 2026
) replaces the pull request heads

Upstream landed Ocarina of Time in main (chain adec7d81d970: "holds 60 fps in
Kokiri Forest, 4 GB less") and closed the pull requests it folded in, with
reworded and partly different code. Mixing a pull request head with its landed
version hunk by hunk silently broke things (two GlobalToStorage tables, a
duplicate AF2ip, two mtk_pso definitions), so the rule here is one version per
feature:

- Pull requests upstream closed (seedds#1299, seedds#1300, seedds#1312, seedds#1343, seedds#1345, seedds#1353,
  seedds#1354, seedds#1359, seedds#1360, seedds#1373, seedds#1428, seedds#1430, seedds#1434): their hunks are taken out
  of our side before the three-way merge, so upstream's landed version stands
  alone. Gone with them: bindless pool table patching (reg_bump), the ghost
  table's per-submission hold, the growing raw pool table and its write-log
  sizing, seedds#1428's pool_buf spares (upstream's pool_table_take is the same
  idea), the ixc-based indirect mesh draw (upstream uses mdx), and their tests
  (t_pool_table, t_mesh_indirect, t_gmem_raw, finalized_bindless_pool,
  test_bnsh_nvn2, test_ndve).
- Open pull requests that upstream's landing supersedes: seedds#1394 (upstream's
  mtl_pool_addressed) and seedds#1393 pool_skip_retired (upstream's budgeted pool
  walk; the profile does not use it) are dropped with their tests and docs.
  seedds#1368's mesh pipeline prewarm is dropped too: upstream kept the background
  build in nvn_geometry.inc, and the prewarm's mtk_build / mtk_claim /
  g_warm_mps went with it.
- Kept, as upstream does not have them: NXForge's own features (GPU watchdog,
  present filters and anti-aliasing, aniso floor, quick settings, pipeline
  archive compaction, upload breakdown, memory report lines) and the still
  open pull requests seedds#1304, seedds#1305, seedds#1308, seedds#1320, seedds#1331, seedds#1332, seedds#1355, seedds#1356,
  seedds#1369, seedds#1376, seedds#1377, seedds#1379, seedds#1382, seedds#1390, seedds#1391, seedds#1392, seedds#1414, seedds#1420,
  seedds#1436, seedds#1439, seedds#1446, seedds#1453.
- Combined where both sides changed the same lines: GW_N 1 << 16 with seedds#1320's
  wide-write intervals (GW_PAGES = SBW_WPAGES, 128 slots), the CPU upload log
  with upstream's linear upload route, arch_open_end before the compaction
  limit, cbn_prog in geo_gs_one (seedds#1382).
- Translator: upstream's sources and binaries, plus seedds#1414's
  InstEmitAmpereMem diff and the LDSM SubgroupBarrier in
  InstEmitAmpereTensor.cs. The binaries and dmsh_chain.lock are upstream's
  until the Mac rebuilds them (dotnet publish, tool_lock --update-lock,
  dmsh_chain --header); dmsh_chain.h is regenerated for now.
- RED_ONCE keys: upstream took 153 and 154, so the present anti-aliasing lines
  move to 156 and 157 and seedds#1420's packaged-entry line to 158 (array 159).
- Host: hle_net.c and t_net_eventfd.c are upstream's (its eventfd poll waits
  out the whole timeout, which our 5d7920c fixed another way).
- Kept ours: dm/tools/romfs_image.py and its test, so the 26 GB RomFS image is
  not packed again.

dm/tools tests on Linux: same failures as on 875449e (Linux-only quirks),
plus test_nlg4 and test_dmsh_pool_arrays, which fail on upstream main too.
Not yet built on the Mac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
@seedds

seedds commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Thanks for splitting this out of #1444. I'm closing it without landing, because it hides the crash rather than fixing it.

  • NVN doesn't check either. The driver's pool and buffer getters don't null-check their argument, and the host currently matches that. In v131072, main+0x8e5130 creates a buffer from the game's own allocation record (pool at record +0x10, size from src+0x120). It doesn't check nvnBufferInitialize's result and calls nvnMemoryPoolGetFlags on the same pool right after. A null pool there would crash on console too, so the game never gets there with one on console. The null pool is the divergence to find.
  • It moves the failure into game code. nvnBufferInitialize returns before writing the buffer, so with the guards the game carries on with a buffer that was never set up. nvnBufferMap answers NULL and the next write goes through NULL in game code. That crash would tell us less than the current one, which names the null pool.
  • No measured benefit. The crash wasn't reproduced and no run shows the game going on afterwards.
  • The docs line. It adds an unconfirmed case to the "Model heaps sized by the host's command record sizes" entry. An open crash with an unknown cause belongs in the game's note, marked open.

The cause is tracked in #1445: why that record has no pool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants