|
| 1 | +# 09 — A CUDA kernel behind a seam module |
| 2 | + |
| 3 | +What this example demonstrates, and what it does not. |
| 4 | + |
| 5 | +## The shape |
| 6 | + |
| 7 | +``` |
| 8 | +app/ |
| 9 | + src/kernels/saxpy.cu the island: compiled by nvcc, never scanned, no BMI |
| 10 | + include/saxpy/saxpy.h the island's interface: extern "C", no std types |
| 11 | + src/app.cppm the seam: a module that turns the C interface back |
| 12 | + into a C++ one |
| 13 | + src/main.cpp an ordinary consumer, which imports the seam and |
| 14 | + never sees the header |
| 15 | + build.mcpp names the sources and the architectures |
| 16 | +rules-cuda/ a build-rule package that knows how to run nvcc |
| 17 | +``` |
| 18 | + |
| 19 | +Three properties are load-bearing. |
| 20 | + |
| 21 | +**The island is not in the module graph.** No device compiler accepts C++20 |
| 22 | +modules, so `.cu` is classified as a device translation unit: never scanned for |
| 23 | +imports, never producing a BMI. Its header is classified as a header, so |
| 24 | +editing one still invalidates the fast path. |
| 25 | + |
| 26 | +**The island's interface is `extern "C"` and free of standard-library types.** |
| 27 | +nvcc drives a host compiler that mcpp did not choose, so the two sides do not |
| 28 | +share a C++ ABI and must not exchange anything that depends on one. The island |
| 29 | +also uses no standard library itself, which keeps it from linking a second copy |
| 30 | +of the C++ runtime into a program whose own copy came from mcpp's toolchain. |
| 31 | + |
| 32 | +**The seam exists for backend substitution, not for the module boundary.** It |
| 33 | +is the one place where the island underneath could become HIP or a CPU |
| 34 | +fallback without any consumer of `app.saxpy` changing, and the one place a |
| 35 | +`cfg(accelerator = ...)` section has to apply. Remove it and every importer |
| 36 | +becomes backend-specific. |
| 37 | + |
| 38 | +## The rule package, and why nvcc's host compiler is its problem |
| 39 | + |
| 40 | +nvcc refuses host compilers newer than a bound it states in its own |
| 41 | +`crt/host_config.h`, and mcpp's toolchain payload is routinely newer than that |
| 42 | +bound. The rule reads the bound, selects a host compiler that satisfies it, and |
| 43 | +says which one it chose: |
| 44 | + |
| 45 | +``` |
| 46 | +example.rules.cuda: nvcc /usr/bin/nvcc with -ccbin /usr/bin/clang++-14 |
| 47 | +``` |
| 48 | + |
| 49 | +On the machine this example was verified on, the toolkit is CUDA 12.0 |
| 50 | +(`__GNUC__ > 12` is refused, clang must be below 15) and mcpp's payload is gcc |
| 51 | +16.1.0, so passing mcpp's own compiler through would fail. `mcpp self doctor` |
| 52 | +reports the same pairing independently. |
| 53 | + |
| 54 | +Everything about nvcc's spelling lives in the rule package. The engine owns the |
| 55 | +graph, the artifact's identity and the architecture set; it does not own |
| 56 | +`-gencode`. |
| 57 | + |
| 58 | +## Verified |
| 59 | + |
| 60 | +On an NVIDIA RTX 4080 (compute capability 8.9) with CUDA 12.0 and driver |
| 61 | +550.144.03: |
| 62 | + |
| 63 | +``` |
| 64 | +$ mcpp run |
| 65 | + Running `target/.../bin/cuda-saxpy` |
| 66 | +12 24 36 48 |
| 67 | +``` |
| 68 | + |
| 69 | +which is `2.0 * [1,2,3,4] + [10,20,30,40]` computed on the device. |
| 70 | + |
| 71 | +## The one thing that is not closed yet |
| 72 | + |
| 73 | +The CUDA runtime is linked statically, so the artifact carries every |
| 74 | +redistributable component. That leaves exactly one host dependency, |
| 75 | +`libcuda.so.1` — the driver's userspace library, which NVIDIA's licence forbids |
| 76 | +redistributing and which is in ABI lockstep with the kernel module. |
| 77 | + |
| 78 | +xim already has the right shape for this: `libcuda-host-link` is a sentinel |
| 79 | +package that installs a symlink to whatever the host has, so every GPU consumer |
| 80 | +reads one path instead of reimplementing an `ldconfig` probe. This example |
| 81 | +declares it under `[xlings] deps`. |
| 82 | + |
| 83 | +What is missing is the last hop. `[xlings] deps` provisions the payload and |
| 84 | +puts its `bin/` on `PATH`, which is what a payload providing a *program* needs. |
| 85 | +A payload providing a *library* also needs its `lib/` on the artifact's runtime |
| 86 | +search path, and mcpp's private loader does not consult `/usr/lib`, so without |
| 87 | +that hop the statically linked CUDA runtime cannot `dlopen` the driver and |
| 88 | +reports it as "driver version is insufficient". Until an mcpp-index package |
| 89 | +declares that directory, the run needs it supplied: |
| 90 | + |
| 91 | +``` |
| 92 | +$ LD_LIBRARY_PATH=$(xlings pkginfo libcuda-host-link)/lib mcpp run |
| 93 | +``` |
| 94 | + |
| 95 | +This is an ecosystem gap, not a defect in the design: the durable fix is a |
| 96 | +`compat.cuda-driver` package in mcpp-index that wraps the sentinel and declares |
| 97 | +its library directory, which is the same shape every other runtime provider in |
| 98 | +that index already has. |
0 commit comments