Skip to content

Commit 422144b

Browse files
committed
fix(examples): 09 names the cuRAND payload the clang route needs
clang's CUDA wrapper includes curand_mtgp32_kernel.h unconditionally; the example built on a developer machine only because the host's /usr/include supplied it. mcpp.rules.cuda 0.1.0 refuses the clang route without xim:libcurand and names the entry; the example now declares it.
1 parent 03b5074 commit 422144b

3 files changed

Lines changed: 184 additions & 6 deletions

File tree

.agents/docs/2026-09-05-heterogeneous-build-ecosystem-design-v2.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,8 @@ Status is one of `done`, `open`, `deferred (reason)`.
312312
| M2 | Module family `mcpp.rules.*` / `mcpp.tools.*`; specification I8 and section 7 corrected; docs 05 and 07 (both languages); examples 09 and 10 consume `mcpp:plugins` from the index | `grep -r 'mcpp\.build\.\(cuda\|spirv\)'` over docs, examples and tests is empty; the reserved-prefix warning still fires for `mcpplibs` and not for `mcpp` (e2e 309) | M1 | done |
313313
| M3 | Chapter 20 renamed to `20-heterogeneous-builds.md` (both languages); every reference updated | `check_docs_style.sh` passes; no reference to `20-accelerators.md` remains || done |
314314
| M4 | No emoji in docs, README, CHANGELOG, code comments, or the two design documents | a grep over the emoji ranges returns nothing outside program output strings || done |
315-
| M5 | Version 2026.9.5.3 in `mcpp.toml` and `modules/versioning`; CHANGELOG; unit and e2e suites; PR; CI green; self-review | e2e summary has no failure other than 168 (pre-existing on `origin/main`); all CI jobs green on the PR head | M1–M4 | open |
316-
| M6 | Release, mirror, index bump, bootstrap pin | both mirrors return 200 and identical bytes for every asset; `origin/main:pkgs/m/mcpp.lua` in xim-pkgindex names 2026.9.5.3 as `latest`; `.xlings.json` pin bumped | M5 | open |
315+
| M5 | Version 2026.9.5.3 in `mcpp.toml` and `modules/versioning`; CHANGELOG; unit and e2e suites; PR; CI green; self-review | e2e summary has no failure other than 168 (pre-existing on `origin/main`); all CI jobs green on the PR head | M1–M4 | done: unit 102/102, e2e 304 passed with 168 as on main, PR #566 37/37 green, merged as 03b5074 |
316+
| M6 | Release, mirror, index bump, bootstrap pin | both mirrors return 200 and identical bytes for every asset; `origin/main:pkgs/m/mcpp.lua` in xim-pkgindex names 2026.9.5.3 as `latest`; `.xlings.json` pin bumped | M5 | release run 33964745434 green on 03b5074 (six jobs); four assets on `xlings-res/mcpp` at GitHub and GitCode each return 200 with the upstream sha256; bump PR openxlings/xim-pkgindex#763 opened by publish-ecosystem, awaiting CI and merge; bootstrap pin after it |
317317

318318
#### mcpp-plugins (new repository, single PR, version 0.1.0)
319319

@@ -329,17 +329,17 @@ Status is one of `done`, `open`, `deferred (reason)`.
329329
|---|---|---|---|---|
330330
| I1 | `pkgs/m/mcpp.plugins.lua` (GLOBAL and CN URLs, floor 2026.9.5.3); `mcpplibs:rules-cuda` kept, marked superseded | `mcpp add mcpp:plugins` resolves in a sandbox | P3, M6 | open |
331331
| I2 | `compat.vulkan-runtime` 2026.09.05: the pattern list is reduced to proprietary vendor userspace; an ICD's needs are computed by closing over the manifests' libraries; a farmed soname an installed payload also provides is re-pointed at the payload when the payload's versioned symbol set covers the host copy's (the `GLIBCXX`/`CXXABI` nodes of libstdc++ included); `HOST-SURFACE.txt` states the class of every entry | measured on this machine: 37 vendor entries, 20 payload substitutions, 8 host sonames no installed payload provides (`libbsd`, `libedit`, `libicudata`, `libicuuc`, `liblzma`, `libmd`, `libzstd`, `libtinfo`), 8 host Mesa ICDs, 3 host copies newer than the payload (`libdrm_amdgpu`, `libLLVM.so.20.1`, `libxml2`); example 10 still answers `12 24 36 48` || done |
332-
| I3 | `compat.opencl-headers`, `compat.opencl` verified with a probe (`tests/examples/opencl`, a workspace member); `compat.opencl-runtime` 2026.09.05 farms the libraries the host manifests name, their closure and the vendor family, prefers payloads, records the surface; payload entries of `OCL_ICD_FILENAMES` are left to the payload | the probe enumerates the NVIDIA platform on this machine and zero platforms on a runner; the pocl platform once X2 is installed | X2 for the pocl half | open |
332+
| I3 | `compat.opencl-headers`, `compat.opencl` verified with a probe (`tests/examples/opencl`, a workspace member); `compat.opencl-runtime` 2026.09.05 farms the libraries the host manifests name, their closure and the vendor family, prefers payloads, records the surface; payload entries of `OCL_ICD_FILENAMES` are left to the payload | the probe enumerates the NVIDIA platform on this machine and zero platforms on a runner; the pocl platform once X2 is installed | X2 for the pocl half | done for the host half: the probe lists `NVIDIA CUDA / NVIDIA GeForce RTX 4080` here and zero platforms on the Linux runner; pocl half open |
333333
| I4 | CI green; merge; index artifact published | `Publish Index Artifact` green on the merge commit | I1–I3 | open |
334334

335335
#### xim-pkgindex (PR #762)
336336

337337
| # | task | criterion | depends on | status |
338338
|---|---|---|---|---|
339-
| X1 | `zstd`, `xz`, `icu`, `libedit`, `libmd`, `xcb-util`, `xcb-util-image`, `xcb-util-keysyms`, `xcb-util-renderutil`, `xcb-util-wm`, `xcb-util-cursor`: conda-forge repacks for x86_64 and aarch64, published to `xlings-res/<name>` on both mirrors, recipes with `deps`, `exports`, `declare_libs`, headers and `.pc` | each installs; every `DT_NEEDED` of each payload library resolves inside payloads; CI green || open |
339+
| X1 | `zstd`, `xz`, `icu`, `libedit`, `libmd`, `xcb-util`, `xcb-util-image`, `xcb-util-keysyms`, `xcb-util-renderutil`, `xcb-util-wm`, `xcb-util-cursor`: conda-forge repacks for x86_64 and aarch64, published to `xlings-res/<name>` on both mirrors, recipes with `deps`, `exports`, `declare_libs`, headers and `.pc` | each installs; every `DT_NEEDED` of each payload library resolves inside payloads; CI green || done, pending CI: all eleven published on both mirrors (44/44 GET checks by the agent, two re-verified by sha256 here), static and isolation checks green; finding: an aarch64 install would fail today because `xim:glibc`, `xim:gcc-runtime`, `xim:ncurses` and `xim:libxcb` publish no aarch64 asset and `deps` are per OS, not per architecture -- recorded as the base-package half of A3 |
340340
| X1b | `libbsd` built in the subos harness (not on conda-forge), x86_64 | same | X1 (`libmd`) | open |
341-
| X2 | `pocl` 7.1 (CPU OpenCL) repacked for both architectures; its ICD reached through `OCL_ICD_FILENAMES` in the subos environment | `clinfo`-equivalent probe reports the pocl platform with no GPU || open |
342-
| X3 | `mesa-lavapipe` aarch64 payload | `archs` lists both; the aarch64 tarball is on both mirrors || open |
341+
| X2 | `pocl` 7.1 (CPU OpenCL) repacked for both architectures; its ICD reached through `OCL_ICD_FILENAMES` in the subos environment | `clinfo`-equivalent probe reports the pocl platform with no GPU || done, pending CI: pocl 7.1 repacked for both architectures (x86_64 179 MB, aarch64 222 MB; conda placeholders rewritten in place), assets on both mirrors verified by GET and byte comparison; the Khronos loader with `OCL_ICD_FILENAMES=<payload>/lib/libpocl.so` enumerates `Portable Computing Language` and `NVIDIA CUDA` in one process; the recipe declares that variable and never `OCL_ICD_VENDORS` (the agent had wired the latter, which replaces the host scan; corrected). Finding: xlings 2026.9.3.2 records but does not persist a NEW package's `subos.env` declaration (existing entries such as `mesa@25.0.7.2` are applied), so the automatic subos wiring is blocked on an xlings fix; the verification script sets the variable explicitly |
342+
| X3 | `mesa-lavapipe` aarch64 payload | `archs` lists both; the aarch64 tarball is on both mirrors || done, pending CI: `mesa-lavapipe-26.2.1-linux-aarch64.tar.gz` (99,534,504 bytes) on both mirrors, `archs` lists both; the two closures differ by four files none of which is `DT_NEEDED` by `libvulkan_lvp.so` |
343343
| X4 | `xim:mesa` gains the Intel Vulkan driver | on an Intel machine `HOST-SURFACE.txt` has no driver entry | libclc and SPIRV-LLVM-Translator payloads | deferred: anv requires `intel_clc`, which needs a libclc and clang chain this index does not publish yet; the chain is a separate packaging round and is recorded here rather than approximated |
344344
| X5 | CI green; merge; index artifact published | `Publish Index Artifact` green on the merge commit | X1–X3 | open |
345345

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#!/usr/bin/env bash
2+
# Ecosystem verification for mcpp 2026.9.5.3 and the heterogeneous-build round
3+
# (design: 2026-09-05-heterogeneous-build-ecosystem-design-v2.md, section 6.3,
4+
# rows V1 and V2). Run inside a fresh xlings subos:
5+
#
6+
# xlings subos verify-953 --sandbox --cmd "MCPP_VERIFY_VERSION=2026.9.5.3 bash <this file>"
7+
#
8+
# and once more on the host with a GPU (MCPP_VERIFY_HOST=1), where the host
9+
# ICD path and the CUDA lane are exercised as well.
10+
#
11+
# Every assertion carries its own `|| fail`; the closing line is a count.
12+
set -uo pipefail
13+
14+
VER="${MCPP_VERIFY_VERSION:?set MCPP_VERIFY_VERSION}"
15+
STORE="${MCPP_VERIFY_BIN:-$HOME/.xlings/data/xpkgs/xim-x-mcpp/$VER/bin/mcpp}"
16+
SRC="${MCPP_VERIFY_SRC:-}" # a checkout of mcpp at v$VER (examples)
17+
PLUGINS="${MCPP_VERIFY_PLUGINS:-}" # a checkout of mcpp-plugins at v0.1.0 (fixtures)
18+
XL="${XLINGS_BIN:-$(command -v xlings)}"
19+
20+
fails=0
21+
fail() { printf 'ASSERT-FAIL: %s\n' "$1"; fails=$((fails + 1)); }
22+
ok() { printf 'ok: %s\n' "$1"; }
23+
section() { printf '\n== %s ==\n' "$1"; }
24+
25+
# -- A. identity and mirrors -------------------------------------------------
26+
section "A. identity"
27+
[ -x "$STORE" ] || fail "no released binary at $STORE"
28+
got=$("$STORE" --version 2>&1 | head -1)
29+
[ "$got" = "mcpp $VER" ] && ok "$got from the store path" || fail "version is '$got'"
30+
"$STORE" self config --mirror "${MCPP_VERIFY_MIRROR:-CN}" >/dev/null 2>&1 || fail "mcpp self config --mirror"
31+
"$XL" config --mirror "${MCPP_VERIFY_MIRROR:-CN}" >/dev/null 2>&1 || fail "xlings config --mirror"
32+
ok "mirror ${MCPP_VERIFY_MIRROR:-CN} configured for mcpp and xlings"
33+
34+
# -- B. the plugin collection resolves from the index -------------------------
35+
#
36+
# A consumer that names mcpp:plugins with one feature and imports the member
37+
# under its declared name. The SPIR-V header's first word is the proof that the
38+
# rule ran through the engine path (constrained glob, device source list, a
39+
# `role = "source"` action ordered before compilation).
40+
section "B. mcpp:plugins, feature rules-spirv"
41+
work=$(mktemp -d)
42+
mkdir -p "$work/spv/shaders" "$work/spv/src"
43+
cat > "$work/spv/mcpp.toml" <<'EOT'
44+
[package]
45+
name = "spv-probe"
46+
version = "0.1.0"
47+
[language]
48+
standard = "c++23"
49+
modules = true
50+
import_std = true
51+
[dependencies.mcpp]
52+
plugins = { version = "0.1.0", features = ["rules-spirv"], host-module = true }
53+
[xlings.workspace]
54+
"xim:glslang" = "15.1.0"
55+
[build]
56+
accel = "vulkan1.2"
57+
sources = ["src/*.cpp", { glob = "shaders/*.comp", accel = "vulkan1.2" }]
58+
[targets.spv-probe]
59+
kind = "bin"
60+
main = "src/main.cpp"
61+
EOT
62+
cat > "$work/spv/shaders/scale.comp" <<'EOT'
63+
#version 450
64+
layout(local_size_x = 64) in;
65+
layout(std430, binding = 0) buffer Data { float v[]; };
66+
void main() { v[gl_GlobalInvocationID.x] *= 2.0; }
67+
EOT
68+
cat > "$work/spv/build.mcpp" <<'EOT'
69+
import std;
70+
import mcpp;
71+
import mcpp.rules.spirv;
72+
int main() {
73+
mcpp::rerun_if_changed_glob("shaders/**/*.comp");
74+
mcpp::rules::spirv::options opt;
75+
opt.includes = { "shaders" };
76+
return mcpp::rules::spirv::compile(opt) ? 0 : 1;
77+
}
78+
EOT
79+
cat > "$work/spv/src/main.cpp" <<'EOT'
80+
#include <cstdint>
81+
#include <cstdio>
82+
#include "scale_comp.h"
83+
int main() { std::printf("magic=%08x\n", scale_comp_spv[0]); return scale_comp_spv[0] == 0x07230203u ? 0 : 1; }
84+
EOT
85+
out=$(cd "$work/spv" && "$STORE" build 2>&1 && "$STORE" run 2>&1)
86+
printf '%s\n' "$out" | grep -q '^magic=07230203' && ok "mcpp.rules.spirv from mcpp:plugins produced a SPIR-V header" \
87+
|| fail "spirv consumer: $(printf '%s' "$out" | tail -4 | tr '\n' ' ')"
88+
89+
# -- C. example 10 on the CPU driver payload ------------------------------------
90+
section "C. examples/10-vulkan-compute on xim:mesa-lavapipe"
91+
if [ -n "$SRC" ] && [ -d "$SRC/examples/10-vulkan-compute/app" ]; then
92+
ex10="$work/ex10"; cp -r "$SRC/examples/10-vulkan-compute/app" "$ex10"
93+
out=$(cd "$ex10" && "$STORE" build 2>&1); rc=$?
94+
[ $rc -eq 0 ] && ok "example 10 built" || fail "example 10 build: $(printf '%s' "$out" | tail -4 | tr '\n' ' ')"
95+
icd=$(ls "$ex10"/.mcpp/.xlings/data/xpkgs/xim-x-mesa-lavapipe/*/share/vulkan/icd.d/*.json 2>/dev/null | head -1)
96+
[ -n "$icd" ] || icd=$(ls "$HOME"/.xlings/data/xpkgs/xim-x-mesa-lavapipe/*/share/vulkan/icd.d/*.json 2>/dev/null | head -1)
97+
[ -n "$icd" ] && ok "lavapipe manifest at $icd" || fail "no lavapipe ICD manifest in any store"
98+
out=$(cd "$ex10" && VK_DRIVER_FILES="$icd" "$STORE" run 2>&1)
99+
printf '%s\n' "$out" | grep -q '12 24 36 48' && ok "example 10 answered 12 24 36 48 on the payload driver" \
100+
|| fail "example 10 run on lavapipe: $(printf '%s' "$out" | tail -4 | tr '\n' ' ')"
101+
printf '%s\n' "$out" | grep -qi 'llvmpipe' && ok "the device was llvmpipe" || printf 'note: device name not printed (%s)\n' "$(printf '%s' "$out" | head -2 | tr '\n' ' ')"
102+
out=$(cd "$ex10" && "$STORE" build --no-accel 2>&1 && "$STORE" run --no-accel 2>&1)
103+
printf '%s\n' "$out" | grep -q '12 24 36 48' && ok "example 10 --no-accel answered 12 24 36 48" \
104+
|| fail "example 10 --no-accel: $(printf '%s' "$out" | tail -3 | tr '\n' ' ')"
105+
hs=$(ls "$ex10"/.mcpp/.xlings/data/xpkgs/compat-x-vulkan-runtime/*/mcpp_generated/vulkan_runtime/HOST-SURFACE.txt 2>/dev/null | head -1)
106+
if [ -n "$hs" ]; then
107+
printf -- '--- %s\n' "$hs"; sed -n '/^## farmed/,$p' "$hs" | head -60
108+
hostlines=$(grep -c -- '-- host;' "$hs" || true)
109+
if [ -n "${MCPP_VERIFY_HOST:-}" ]; then ok "host surface recorded ($hostlines host entries, see above)"; else
110+
[ "$hostlines" -eq 0 ] && ok "no host library is on the runtime path in the sandbox" || fail "$hostlines host entries in a sandbox"; fi
111+
else fail "no HOST-SURFACE.txt written by compat.vulkan-runtime"; fi
112+
else
113+
printf 'skip: MCPP_VERIFY_SRC not set or has no examples/10-vulkan-compute\n'
114+
fi
115+
116+
# -- D. example 09, the CPU variant everywhere and the device on a host with one --
117+
section "D. examples/09-cuda-kernel"
118+
if [ -n "$SRC" ] && [ -d "$SRC/examples/09-cuda-kernel/app" ] && [ -n "${MCPP_VERIFY_CUDA:-}" ]; then
119+
ex09="$work/ex09"; cp -r "$SRC/examples/09-cuda-kernel/app" "$ex09"
120+
out=$(cd "$ex09" && "$STORE" build --no-accel 2>&1 && "$STORE" run --no-accel 2>&1)
121+
printf '%s\n' "$out" | grep -q '12 24 36 48' && ok "example 09 --no-accel answered 12 24 36 48" \
122+
|| fail "example 09 --no-accel: $(printf '%s' "$out" | tail -3 | tr '\n' ' ')"
123+
out=$(cd "$ex09" && "$STORE" build 2>&1); rc=$?
124+
[ $rc -eq 0 ] && ok "example 09 device variant compiled through mcpp.rules.cuda" || fail "example 09 build: $(printf '%s' "$out" | tail -4 | tr '\n' ' ')"
125+
if [ -n "${MCPP_VERIFY_HOST:-}" ]; then
126+
out=$(cd "$ex09" && "$STORE" run 2>&1)
127+
printf '%s\n' "$out" | grep -q '12 24 36 48' && ok "example 09 answered 12 24 36 48 on the device" || fail "example 09 device run: $(printf '%s' "$out" | tail -3 | tr '\n' ' ')"
128+
fi
129+
else
130+
printf 'skip: set MCPP_VERIFY_CUDA=1 (and MCPP_VERIFY_SRC) to exercise the CUDA lane\n'
131+
fi
132+
133+
# -- E. OpenCL: the loader, the adapter, and the CPU implementation ---------------
134+
section "E. OpenCL"
135+
mkdir -p "$work/ocl/tests"
136+
cat > "$work/ocl/mcpp.toml" <<'EOT'
137+
[package]
138+
name = "ocl-probe"
139+
version = "0.1.0"
140+
[dependencies.compat]
141+
opencl = "2026.05.29"
142+
EOT
143+
cat > "$work/ocl/tests/platforms.cpp" <<'EOT'
144+
#define CL_TARGET_OPENCL_VERSION 300
145+
#include <CL/cl.h>
146+
#include <CL/cl_ext.h>
147+
#include <cstdio>
148+
int main() {
149+
cl_uint n = 0; const cl_int rc = clGetPlatformIDs(0, nullptr, &n);
150+
if (rc == CL_PLATFORM_NOT_FOUND_KHR || (rc == CL_SUCCESS && n == 0)) { std::printf("platforms=0\n"); return 0; }
151+
if (rc != CL_SUCCESS) { std::printf("clGetPlatformIDs=%d\n", (int)rc); return 1; }
152+
cl_platform_id ids[8]; clGetPlatformIDs(n < 8 ? n : 8, ids, nullptr);
153+
std::printf("platforms=%u\n", n);
154+
for (cl_uint i = 0; i < n && i < 8; ++i) { char name[128] = {}; clGetPlatformInfo(ids[i], CL_PLATFORM_NAME, sizeof name, name, nullptr); std::printf("platform: %s\n", name); }
155+
return 0;
156+
}
157+
EOT
158+
out=$(cd "$work/ocl" && "$STORE" test 2>&1)
159+
printf '%s\n' "$out" | grep -q '^platforms=' && ok "the loader linked and enumerated: $(printf '%s' "$out" | grep '^platform' | tr '\n' ' ')" \
160+
|| fail "opencl probe: $(printf '%s' "$out" | tail -4 | tr '\n' ' ')"
161+
if "$XL" install pocl -y >/dev/null 2>&1; then
162+
ok "xim:pocl installed"
163+
out=$(cd "$work/ocl" && "$XL" subos current --cmd "cd '$work/ocl' && '$STORE' test" 2>&1 || true)
164+
lib=$(ls "$HOME"/.xlings/data/xpkgs/xim-x-pocl/*/lib/libpocl.so 2>/dev/null | head -1)
165+
out2=$(cd "$work/ocl" && OCL_ICD_FILENAMES="$lib" "$STORE" test 2>&1)
166+
printf '%s\n' "$out2" | grep -q 'Portable Computing Language' && ok "the pocl platform is enumerated through OCL_ICD_FILENAMES" \
167+
|| fail "pocl not enumerated: $(printf '%s' "$out2" | grep '^platform' | tr '\n' ' ')"
168+
else
169+
printf 'note: xim:pocl not installable here (not published yet, or this arch)\n'
170+
fi
171+
172+
printf '\n== result: %d assertion(s) failed ==\n' "$fails"
173+
[ "$fails" -eq 0 ]

examples/09-cuda-kernel/app/mcpp.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ cuda-runtime = "2026.09.05"
4040
[xlings.workspace]
4141
"xim:cuda-nvcc" = "12.9.86"
4242
"xim:cuda-cudart" = "12.9.79"
43+
# cuRAND's headers. clang's CUDA wrapper includes curand_mtgp32_kernel.h for
44+
# every device unit, so the clang route needs them even though this kernel
45+
# never calls cuRAND; on a developer machine the host's /usr/include used to
46+
# supply the header silently. The 10.3.x line pairs with CUDA 12.x.
47+
"xim:libcurand" = "10.3.10.19"
4348
"xim:libcuda-host-link" = { linux = "0.0.1" }
4449

4550
[build]

0 commit comments

Comments
 (0)