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