Repository navigation
GPU compilation cache incorrectly handles tombstoned entries #9564
Description
Description
Summary
GPUCompilationCache::release_context_already_locked() in src/runtime/gpu_context_common.h selects entries with kernel_id > kInvalidId. Deleted (tombstoned) entries have kernel_id == kDeletedId == 1, which also satisfies that test. Their module_state was already set to nullptr, so the cache calls the free function with nullptr and decrements count a second time for the same slot. For WebGPU, this crashes Dawn.
Environment: Halide 22.0.0.dev444 (pip wheel), WebGPU backend via Dawn 74c5edcc, macOS 15 arm64. The bug is in shared GPUCompilationCache code; whether it crashes depends on the backend's free function.
Root cause
static constexpr uintptr_t kInvalidId{0};
static constexpr uintptr_t kDeletedId{1};
...
if (compilations[i].kernel_id > kInvalidId && // true for kDeletedId too
(all || compilations[i].context == context) &&
compilations[i].use_count == 0) {
f(compilations[i].module_state); // f(nullptr) for tombstones
compilations[i].module_state = nullptr;
compilations[i].kernel_id = kDeletedId;
count--; // count corrupted
}A tombstone is created by an earlier release (e.g. halide_webgpu_device_release -> delete_context). If any live entry keeps count > 0, the table is not freed, and the next release_context/release_all (for WebGPU, the halide_webgpu_cleanup destructor calling shader_cache.release_all(nullptr, wgpuShaderModuleRelease)) revisits the tombstone.
Impact
- WebGPU:
wgpuShaderModuleRelease(nullptr)dereferences null inside Dawn, so the process segfaults at exit after the work has completed successfully (test harnesses report a crash). - All backends:
countunderflows/drifts, sorelease_allmay skipfree(compilations)or later free logic misbehaves. Backends whose free function tolerates null (e.g.cuModuleUnloadreturns an error) silently hide it.
Reproducing case
## Reproducer
Run two or more distinct WebGPU kernels in one process, release the device (`halide_device_release` / `halide_webgpu_device_release`) while another kernel's compilation is cached under a different context or still has `use_count > 0`, then exit. Observed reliably with a test binary that runs many channel-conversion kernels in sequence: all checks pass, then SIGSEGV in `wgpuShaderModuleRelease` from `halide_webgpu_cleanup`.
## Expected vs actual
- Expected: each cached module is released exactly once; tombstones are skipped.
- Actual: tombstones are "released" again with `nullptr`; Dawn crashes; `count` is wrong.How did you get Halide?
pip (halide)
Halide version
22.0.0.dev444
Halide commit (if known)
No response
Target
arm-64-macos-arm_dot_prod-arm_fp16-webgpu
Operating system
MacOS Tahoe 26.6.2
Additional context
Suggested fix
Skip tombstones explicitly:
if (compilations[i].kernel_id > kDeletedId && ...)(and/or guard if (compilations[i].module_state != nullptr) before calling f). A null check in the WebGPU free path would also be prudent.
Activity