feat(ci): drive the Level Zero backend against a stub loader (#382)
feat(ci): drive the Level Zero backend against a stub loader The loader check from #365 stops at `dlopen` plus symbol resolution, and deliberately so: a runner with no Intel GPU has no driver for the loader to return, so `zeInit` failing there is correct rather than a defect. The consequence is that everything past `try_load_library` is unexercised by any runner this project has. Device enumeration, the count-then-buffer idiom and its clamp, the BDF mapping, the delta arithmetic, and the metric refresh all ship on the strength of review alone. The struct layouts are the sharpest edge. The coverage that exists compares our `#[repr(C)]` struct against our own transcription of the spec, which catches a mis-sized field and did once: `zes_pci_properties_t` declared three `ze_bool_t` fields as `u32`, inflating it from 56 to 64 bytes. It cannot catch two same-typed fields in the wrong order, a wrong offset that preserves the total, or a spec value transcribed wrong in both the struct and its assertion. ## The stub `tests/fixtures/level_zero/stub_ze_loader.c` plays the driver's part, exporting all 23 symbols the backend resolves: the four mandatory ones, plus `zesInit` so initialisation takes the modern Sysman route, plus the engine, power, temperature, memory, frequency, and fan families. It is compiled against the **vendor headers**, not against our transcription. That is the whole point: the vendor's own struct is filled on the C side and read back through our type on the Rust side, so a value arriving intact is evidence the two layouts agree field by field. A hand-written stub using our definitions would only prove our definitions agree with themselves. Values are chosen to be individually identifiable. `zes_freq_state_t` carries five `f64` fields; the stub gives each a different number, so `actual` read one field early reports 1200, 2200, or 2300 rather than a plausible 2100. Counters advance by a fixed step per call, no clock reads and no randomness, so the second refresh is exactly 25.00% compute, 10.00% render, and 45.00 W, asserted as constants rather than ranges. ## How it binds, and why nothing shipped can reach it `LIBZE_PATHS[0]` is the bare SONAME `libze_loader.so.1`, and `dlopen` on a bare SONAME searches `LD_LIBRARY_PATH` before the default paths. A stub built under that name in a directory placed first therefore wins over the real `libze1` the same job installs, with no new branch in the loader. The rejected alternative was an `ALL_SMI_LEVEL_ZERO_LIBRARY` path override, which would have put a "load this arbitrary shared object" switch into every released binary in exchange for nothing this route does not provide. So: `LIBZE_PATHS` is unchanged, no environment override exists in production code, and the stub is neither compiled into the `all-smi` binary nor into any release artifact. `LD_LIBRARY_PATH` is set on one workflow step, so every other step still sees the real loader. ## The tests `tests/level_zero_stub.rs` is its own integration binary, therefore its own process, because `LZ_RUNTIME` is a process-wide `OnceCell` and the library's unit tests already latch it. Whoever ran first would otherwise decide which loader the entire binary sees. Five tests, driving only the public API: sorted BDF enumeration end to end; the exact delta-derived percentages and watts; the point-in-time families landing in a real `GpuInfo`; a device whose enumerator reports 4096 handles and then fills one, exercising both the `MAX_L0_HANDLES` clamp and the post-fill truncate; and an unknown BDF binding to nothing. They fail loudly rather than skipping, in the same three-part shape as #365: an env key arms the assertions, a marker prints only after they pass, and the CI step greps for it. ## Verification - The C stub cannot be compiled from this macOS host: it needs the vendor headers, which have no macOS package. CI is the compile check, and the build step also asserts with `nm` that all 23 symbols are exported. - Rust side verified locally: the integration binary compiles both with the backend on (through the scratch probe) and off (macOS, where the file compiles away), and runs green unarmed. - `cargo fmt --check` and `cargo clippy --all-targets -- -D warnings` clean. Reachability proof and the full CI evidence follow in a PR comment once the first run reports. Closes #379
J
Jeongkyu Shin committed
89cd907bbb5ee2be0426c3a2f733283a6a1760af
Parent: ba82d92
Committed by GitHub <noreply@github.com>
on 8/23/2026, 5:11:05 PM