Lab 01 — A GPU HAL with dlopen Backends (C)

Phase: 09 — Hardware Abstraction (Keystone) | Difficulty: ⭐⭐⭐⭐⭐ | Time: 6–10 hours Language: C11 + POSIX dlopen | Hardware: none

Concept primer: ../WARMUP.md Ch. 2–7.

Run

make            # builds the HAL core, 3 backend .so files, and the demo
./hal_demo

0. The mission

Build the literal architecture of "decouple software from hardware" (WARMUP Ch. 1): a stable API, a versioned backend ABI, and three dlopen-loaded vendor backends. The core links none of the backends — it loads them at runtime, queries their capabilities, and routes work to a capable one.

FileRole
hal.hthe contract: stable API + the hal_backend ABI (vtable + caps + version)
hal.cthe loader (dlopen/dlsym), version validation, capability router
backend_cpu_ref.creference backend: FP32 only
backend_fast.c"fast" backend: FP32 + FP16, higher perf hint
backend_limited.c"limited" backend: FP32 only, lower perf
hal_demo.cloads all, runs conformance, routes a FP16 request, checks ABI version

1. What the output shows

loaded backend: cpu_ref   (abi v2, dtypes=F32, perf=1)
loaded backend: fast      (abi v2, dtypes=F32|F16, perf=10)
loaded backend: limited   (abi v2, dtypes=F32, perf=2)
rejected backend: stale   (abi v1 != core v2) — ABI version guard works

== conformance: matmul FP32 vs reference, all backends ==
  cpu_ref : PASS
  fast    : PASS
  limited : PASS

== capability routing ==
  FP32 matmul -> fast      (highest perf among F32-capable)
  FP16 matmul -> fast      (only F16-capable backend)
  FP8  matmul -> (none)    cleanly refused: no loaded backend supports FP8
  • Three backends loaded from .so files the core never linked against — that's the decoupling, mechanically (WARMUP Ch. 3).
  • ABI version guard rejects a backend built against an incompatible ABI (WARMUP Ch. 2, 6) — the discipline that lets a year-old vendor binary still load (or be cleanly refused).
  • Conformance: one test, all backends pass (WARMUP Ch. 7).
  • Capability routing: FP16 work goes only to the FP16-capable backend; FP8 is refused cleanly instead of silently downgraded (WARMUP Ch. 4 — beating the lowest common denominator).

2. Reading order

  1. hal.hthe ABI is the contract. Study hal_backend (version first, reserved fields last) and hal_caps against WARMUP Ch. 2.
  2. hal.chal_load_backend (dlopen → dlsym → version check) and hal_select (capability routing).
  3. The three backends — note they share no source with the core, only hal.h's ABI.
  4. hal_demo.c — trace one matmul from API call → router → backend vtable.

3. Extensions (the heart of the keystone lab)

  1. Add a 4th backend with NO core recompile (backend_exp.c): build just the new .so, drop it in the load list, run. It loads and passes conformance without rebuilding hal.c/hal_demothis is what "decoupled from hardware" means. Prove it: make backend_exp.so && ./hal_demo after adding only the path.
  2. ABI evolution (WARMUP Ch. 6): add a new optional op at the end of the vtable behind a capability flag; show an old backend (without it) still loads and runs, while a new backend exposes the new op. Demonstrate append-only + reserved-field discipline.
  3. Native escape hatch (WARMUP Ch. 5): add an optional native_op pointer a backend may provide for a fast path; the router uses it when present, falls back otherwise.
  4. Performance conformance (WARMUP Ch. 7): time each backend's matmul; gate "recommended" status on a perf threshold.

4. Common pitfalls

  1. Inserting a struct field in the middle — breaks every already-built backend's field offsets (WARMUP Ch. 2). Only append, behind a version guard. Try it: reorder hal_caps fields, rebuild only the core, and watch a not-rebuilt backend misbehave.
  2. Forgetting the version check — a stale backend's misaligned struct reads as garbage; check abi_version first, before any other field.
  3. dlsym returning NULL — the backend didn't export hal_backend_register (symbol visibility); handle it, don't crash.
  4. Comparing float results with == across backends — different operation order/rounding (Phase 03's FMA caveat applies cross-vendor); conformance uses a tolerance.

5. What this lab proves about you

You've built the JD's central artifact with your own hands: a versioned, dlopen-based, capability-routing hardware-abstraction layer — the mechanical meaning of "a platform that operates independently of the underlying hardware." You can now design this for real (CUDA/ROCm/Level Zero backends), version it so vendors trust it, and explain why it's the platform's moat.