ADR-1296: GPU init failure paths are tested device-free, by compiling the backend TU against runtime stubs¶
- Status: Accepted
- Date: 2026-09-22
- Deciders: Lusoris
- Tags: testing, hip, cuda, sycl, gpu, code-quality, agents
Context¶
Every GPU backend extractor ends init with a ladder of failure branches that release the stream, the events, the loaded modules and every device or pinned allocation claimed so far. The ladder is the only thing standing between a failed init and a use-after-free, because vmaf_feature_extractor_context_close rejects an uninitialised context, so close never runs after a failed init and nothing downstream reclaims a tier the ladder skips.
Until now nothing tested those branches. The GPU tests in core/test/ are parity gates: they need a real device, run in the gpu suite, and skip on a host without one. A failure branch cannot be reached from a parity gate at all — the allocations it unwinds are exactly the ones that succeeded. T-HIP-INIT-UNWIND-REPORTS-SUCCESS-2026-09-22 is what that gap costs: three branches across two HIP extractors released every resource and then returned 0, because the unwind ladder's terminal maps hipSuccess to 0 and all three were handed hipSuccess. The framework set is_initialized and the next submit ran on freed device memory. The defect survived a HISS-01 refactor, an ADR-0759 change to the same path, and a written AGENTS.md note describing it as intended behaviour.
Decision¶
We will test GPU init failure paths by compiling the backend translation unit directly into a test target that defines every symbol the TU references — the runtime entry points, the embedded kernel blobs and the handful of libvmaf helpers — and driving the failure from one of those stubs. The target links no GPU runtime and needs no device, so it runs in the fast suite and never skips. core/test/test_hip_adm_init_unwind.c and core/test/test_hip_ssimulacra2_init_unwind.c are the reference implementations.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Stub-interposed TU compiled into the test target (chosen) | Runs on any host in the fast suite; the stubs tally every allocation, so a skipped unwind tier fails as a leak, not just as a wrong return code; the stub surface is small and nm -u enumerates it exactly | A second definition of the extractor symbol, so the target must not link libvmaf; the stub list drifts if the TU gains a runtime call | Only option that actually fails when the bug is present, on a host with no AMD GPU |
A gpu-suite test that forces a real allocation failure on the device | Exercises the genuine runtime | Needs hardware in CI; an OOM large enough to fail hipMalloc is not reliably reproducible; cannot reach the dictionary branch at all, which is a host allocation | Unreachable for the actual defect, and skips everywhere CI runs |
LD_PRELOAD interposition of the HIP runtime | No source-level stubs | Linux-only, so the gate would skip on macOS and Windows (HISS-21); still needs the real ROCm runtime installed | Loses the platform neutrality the stub target gets for free |
Command-line -D renames of the injected entry point | The pattern test_framesync_init_failure started from | A -D is in force before the headers are read, so it rewrites the declaration too — the failure mode that forced test_framesync_interpose.h | Defining the symbol in the test TU and linking no libvmaf removes the precondition instead of steering around it |
| Leave the paths untested | No new targets | Three live use-after-frees shipped undetected | Rejected on the evidence |
Consequences¶
- Positive: a GPU
initfailure branch is now reachable from a test that runs everywhere. The ledger form of the assertion ("every pointer handed out came back") catches a skipped unwind tier, which a return-code assertion alone does not — the second defect in T-HIP-INIT-UNWIND-REPORTS-SUCCESS-2026-09-22 was exactly that, and the gate reproduces it independently. - Negative: the test target compiles the extractor a second time and redefines its registration symbol, so it must never link
libvmaf. The stub list has to track the TU's undefined symbols; when the TU gains a runtime call the target fails to link, which is a loud failure but still a maintenance tax.nm -u <the TU's object>regenerates the list. - Neutral / follow-ups: the two HIP targets are gated on
enable_hipcc == true, matching theHAVE_HIPCCguard the failure paths live behind, so the hip tidy lane (configured withenable_hipcc=false, seeT-TIDY-RATCHET-GPU-LANES-UNREPRODUCIBLE-2026-09-22) does not measure them. The CUDA and SYCL ADM twins were audited for the same defect during T-HIP-INIT-UNWIND-REPORTS-SUCCESS-2026-09-22 and are clean, so no equivalent target was added for them; one should be if their unwind shape ever changes.
References¶
docs/state.mdrowT-HIP-INIT-UNWIND-REPORTS-SUCCESS-2026-09-22; local ledgerBUG-092.- ADR-0759 — added
buf_devto the ADM unwind ladder. - ADR-1289 — the SSIMULACRA2 host-helper split that introduced
ss2h_load_modules. - ADR-1142 — whole-tree standards; the new targets are lint-clean under the hip lane's clang-tidy profile.
core/test/test_framesync_interpose.h— prior interposition target, and the-D-rename failure this ADR's approach avoids.core/src/feature/hip/AGENTS.md— the unwind-ladder invariants this gate protects.