Skip to content

Add debug-only stomp allocator mode to the D3D12 backend - #314

Draft
Jasper-Bekkers wants to merge 1 commit into
mainfrom
d3d12-stomp-allocator
Draft

Jasper-Bekkers wants to merge 1 commit into
mainfrom
d3d12-stomp-allocator

Conversation

@Jasper-Bekkers

Copy link
Copy Markdown
Member

What

Opt-in guard-page ("stomp") detection for D3D12 resources created through Allocator::create_resource(). Off by default, zero cost when AllocatorCreateDesc::stomp is None.

A stomp resource is a reserved (tiled) resource. Payload tiles map into the normal sub-allocator blocks, so placement, leak reports and the visualizer keep working. Guard tiles map to one 64KB heap per memory type created with D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT and never made resident (Evict is only a hint, per Jesse Natalie). A GPU access to a guard tile page-faults and removes the device with DXGI_ERROR_DEVICE_HUNG. With quarantine on, freed resources have every tile remapped to that heap and stay alive until the allocator drops, so use-after-free faults too.

All stomp code lives in src/d3d12/stomp.rs; mod.rs only has the hook points.

API

  • AllocatorCreateDesc::stomp: Option<StompSettings>
  • StompSettings::new(queue) with mode (All, Random { probability }, OptIn, Filter(fn)), seed, tail_guard (default on), head_guard (default off), payload_alignment: Option<u64> (byte-precise tail guard, caller adds Resource::offset()), quarantine (default on)
  • ResourceCreateDesc::stomp: Option<bool> per-resource override
  • Resource::offset(), is_stomp_guarded(), stomp_layout()
  • Allocator::stomp_statistics(), committed_statistics()
  • AllocationError::InvalidStompSettings

Breaking: both descs gained a field.

What it catches

Accesses the hardware does not bounds-check: root descriptor SRV/UAV/CBV, acceleration structure builds and scratch, DXR shader tables, ExecuteIndirect arguments. Descriptor-table views are clamped by hardware. CopyBufferRegion overruns are rejected by the runtime at Close(). Textures get no guard tile (no unbounded write path exists), only use-after-free detection. CpuToGpu / GpuToCpu, MSAA and 3D below tiled tier 3 fall through to the normal path with a warning (reserved resources cannot be Map()ed).

Why reserved resources and not tight placed resource alignment

The first design placed a tight-aligned resource at the end of its own heap and created a never-resident heap right after it, verified by probing GPU VAs. Measured on an RTX 5070 Ti it guards only 55 to 105 of 500 buffers under churn (freed VA gets recycled), and the driver's VA allocator is process-wide so other threads break adjacency. Reserved resources give adjacency by construction; the spec forbids tight alignment on them. Details and numbers in docs/d3d12-stomp-allocator.md, section 8.

Tests

  • 8 unit tests (geometry, rng, decision matrix, validation)
  • 26 device tests in tests/d3d12_stomp.rs, run on WARP in CI, with an ID3D12InfoQueue sweep for debug-layer errors in every test
  • 9 hardware fault tests in tests/d3d12_stomp_fault.rs, #[ignore], one child process per test:
    GPU_ALLOCATOR_STOMP_FAULT_TESTS=1 cargo test --features d3d12 --test d3d12_stomp_fault -- --ignored --test-threads=1
  • examples/d3d12-stomp.rs removes the device by writing at the tail guard

Verified on NVIDIA RTX 5070 Ti: tail overrun, head underrun, byte-precise boundary, in-bounds and slack writes clean, buffer and texture use-after-free, cross-queue use after COPY-queue mapping, 500/500 guarded under churn, 4 threads all guarded, no debug-layer messages.

Open points

  • DRED reports fault VA 0 and no allocation names on this NVIDIA driver; breadcrumbs work. Naming is best effort.
  • Not yet run on AMD or Intel.
  • Default keeps offset() == 0 with 64KB slack; byte precision needs payload_alignment and the offset contract. Open to flipping the default.
  • windows 0.53 not built locally (dev-deps pin 0.58); relies on the minimal-versions CI job.

馃 Generated with Claude Code

Opt-in guard-page detection for resources created through
`Allocator::create_resource()`. A stomp resource is a reserved (tiled)
resource: payload tiles map into the normal sub-allocator blocks, guard
tiles map to a 64KB heap created with `CREATE_NOT_RESIDENT` that is never
made resident. A GPU access past the end (or, optionally, before the
start) page-faults and removes the device with `DXGI_ERROR_DEVICE_HUNG`.
Freed resources can be quarantined so that use-after-free faults too.

Public API: `AllocatorCreateDesc::stomp: Option<StompSettings>`,
`StompSettings::new(queue)` with `mode` (`All`, `Random`, `OptIn`,
`Filter`), `tail_guard`, `head_guard`, `payload_alignment` for byte-precise
tail guards via `Resource::offset()`, and `quarantine`;
`ResourceCreateDesc::stomp: Option<bool>` per-resource override;
`Resource::is_stomp_guarded()` / `stomp_layout()`;
`Allocator::stomp_statistics()`.

The first design tried adjacent heaps with tight placed resource
alignment and no offset contract; measured on NVIDIA it guards only
55 to 105 of 500 buffers under churn and breaks across threads, see
docs/d3d12-stomp-allocator.md section 8.

Tests: 8 unit, 26 device tests (run on WARP in CI), 9 hardware fault
tests behind `GPU_ALLOCATOR_STOMP_FAULT_TESTS=1`, and an example.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant