Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
383 changes: 383 additions & 0 deletions .agents/docs/2026-09-07-heterogeneous-cross-platform-ecosystem.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .github/tools/build_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ SKIP=(
"examples/09-heterogeneous/hip/app|same, for the HIP payloads"
"examples/09-heterogeneous/sycl/app|needs the dpcpp payload (over a gigabyte) and a device its runtime accepts"
"examples/09-heterogeneous/vulkan/app|built AND RUN by the next step of this job, on the lavapipe payload, which needs no GPU"
"examples/10-graphics/offscreen|built AND RUN by its own step of this job, on the same lavapipe payload. It renders a real graphics pipeline offscreen and asserts the pixels, which is why it runs there rather than here"
"examples/09-heterogeneous/cann/app|its device leg needs the Ascend DRIVER, which a runner does not have: the kernel compiles and the object links, and then `libascend_hal.so` is missing -- and only that one, since 2026.9.6.6 models DT_RPATH inheritance. Correct on a machine with no NPU. Its CPU leg does build, and is not built here only because it would make this job resolve a fifth rule package for one example. Covered by the measurements in its README"
)

Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,65 @@ jobs:
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
bash .github/tools/build_examples.sh

- name: "Graphics example: render offscreen on lavapipe and assert the pixels"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cd examples/10-graphics/offscreen
"$MCPP" build
# THE SHADERS ARE THE FIRST ASSERTION, and both of them: a rule that
# compiled only the first source would leave the second header absent
# and everything after this would still pass.
for f in triangle_vert triangle_frag; do
d="target/.build-mcpp/out/spirv"
test -f "$d/$f.h" || { echo "missing $d/$f.h"; exit 1; }
# Either file: which of the two carries the words is a property of
# the shader compiler the rule chose, not of the shader. See the
# cross-platform jobs, where that choice differs.
# ONE FILE AT A TIME, because `grep -qs a b` exits 2 when `b`
# does not exist -- even on a match in `a`, and even with `-s`,
# which suppresses the message and not the status. Written as one
# grep over both names, this criterion fails whenever the route
# that produces only a header is taken, which is a failure about
# the criterion and not about the shader.
found=""
for g in "$d/$f.h" "$d/$f.inc"; do
[ -f "$g" ] && grep -q '0x07230203' "$g" && found=1
done
[ -n "$found" ] \
|| { echo "$f carries no SPIR-V magic in either $f.h or $f.inc"; exit 1; }
done
icd=$(find "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-mesa-lavapipe" \
"$HOME/.xlings/data/xpkgs/xim-x-mesa-lavapipe" \
-name 'lvp_icd.x86_64.json' -print -quit 2>/dev/null || true)
[ -n "$icd" ] || { echo "no lavapipe ICD in either store"; exit 1; }
out=$(VK_DRIVER_FILES="$icd" "$MCPP" run 2>&1) || { echo "$out"; exit 1; }
echo "$out"
# The program asserts the corners and the centre itself and exits
# non-zero on either. What CI adds is that the run reached the DEVICE:
# the software rasteriser produces the same pixels by construction, so
# the image cannot distinguish them and the device name is what does.
echo "$out" | grep -q 'llvmpipe' \
|| { echo "the run did not reach the lavapipe device"; exit 1; }
echo "$out" | grep -qE 'centre pixel: \([0-9]+, [0-9]+, [0-9]+, 255\)' \
|| { echo "no centre pixel was reported"; exit 1; }
# THE REVERSE LEG, AND IT COMPARES THE PIXELS RATHER THAN JUST
# RUNNING. The claim this example makes is that the image is a
# contract two independent rasterisers satisfy, not a property of one
# device -- so the criterion is that the two legs report the SAME
# centre pixel while reporting different devices. Measured: both give
# (124, 70, 62, 255), byte for byte.
gpu_px=$(echo "$out" | grep -m1 '^centre pixel:')
"$MCPP" build --no-accel
"$MCPP" run --no-accel | tee cpu.log
grep -q 'cpu rasteriser' cpu.log || { echo "the CPU leg did not run"; exit 1; }
cpu_px=$(grep -m1 '^centre pixel:' cpu.log)
[ -n "$gpu_px" ] && [ "$gpu_px" = "$cpu_px" ] || {
echo "the two legs disagree about the image:"
echo " device: $gpu_px"
echo " cpu: $cpu_px"
exit 1; }
echo "ok: both legs agree on $cpu_px"

- name: "Vulkan example: build the device half and run it on lavapipe"
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,57 @@ jobs:
"$MCPP" build
"$MCPP" --version


# GRAPHICS ON THIS HOST, BUILD ONLY, AND THAT IS THE WHOLE CLAIM.
#
# This runner has no Vulkan device, so what is asserted is what this
# platform decides: that the shader compiler THIS platform uses -- the
# rule declares `xim:shaderc` here and `xim:glslang` on Linux -- produces
# both SPIR-V headers, and that the Vulkan half compiles and links
# against the loader package. Running it is the Linux job's criterion,
# where a software device (`xim:mesa-lavapipe`) is published and the two
# legs' pixels are compared.
#
# The example is otherwise built only on Linux (`build_examples.sh` runs
# there), which is exactly the shape this change exists to remove: the
# half of a lane written for a host is the half that host never
# exercises.
- name: "Graphics: the offscreen example builds on this host"
shell: bash
run: |
set -e
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cd "$GITHUB_WORKSPACE/examples/10-graphics/offscreen"
# The toolchain is NAMED rather than inherited: leaving it to whatever
# a neighbouring step happened to select makes this step's subject
# depend on step order, which is not a property anybody reads.
"/tmp/mcpp-fresh" build --toolchain "llvm@${MCPP_LLVM_VER}"
for f in triangle_vert triangle_frag; do
d="target/.build-mcpp/out/spirv"
test -f "$d/$f.h" || { echo "missing $d/$f.h"; exit 1; }
# THE MAGIC IS NOT ALWAYS IN THE HEADER, AND THAT IS THE POINT OF
# THIS JOB. The rule chooses the shader compiler this platform
# publishes -- glslang on Linux, glslc here -- and the two split
# the declaration differently: glslang writes a complete `const
# uint32_t ...[] = {...}`, glslc an initialiser list the rule
# declares around, so the words land in `<stem>.inc`. An assertion
# naming only the header is an assertion about ONE compiler, which
# is exactly the shape this step exists to catch.
# ONE FILE AT A TIME, because `grep -qs a b` exits 2 when `b`
# does not exist -- even on a match in `a`, and even with `-s`,
# which suppresses the message and not the status. Written as one
# grep over both names, this criterion fails whenever the route
# that produces only a header is taken, which is a failure about
# the criterion and not about the shader.
found=""
for g in "$d/$f.h" "$d/$f.inc"; do
[ -f "$g" ] && grep -q '0x07230203' "$g" && found=1
done
[ -n "$found" ] \
|| { echo "$f carries no SPIR-V magic in either $f.h or $f.inc"; exit 1; }
done
echo "ok: both shader stages compiled and the Vulkan half linked"

# Integration: the mcpp built from THIS PR's source (the self-host binary,
# $MCPP = /tmp/mcpp-fresh) builds & runs a real external C++ project —
# xlings (openxlings/xlings ships its own mcpp.toml).
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,57 @@ jobs:
# restore the LLVM default for the remaining steps
"$MCPP_SELF" toolchain default llvm@20.1.7


# GRAPHICS ON THIS HOST, BUILD ONLY, AND THAT IS THE WHOLE CLAIM.
#
# This runner has no Vulkan device, so what is asserted is what this
# platform decides: that the shader compiler THIS platform uses -- the
# rule declares `xim:shaderc` here and `xim:glslang` on Linux -- produces
# both SPIR-V headers, and that the Vulkan half compiles and links
# against the loader package. Running it is the Linux job's criterion,
# where a software device (`xim:mesa-lavapipe`) is published and the two
# legs' pixels are compared.
#
# The example is otherwise built only on Linux (`build_examples.sh` runs
# there), which is exactly the shape this change exists to remove: the
# half of a lane written for a host is the half that host never
# exercises.
- name: "Graphics: the offscreen example builds on this host"
shell: bash
run: |
set -e
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cd "$GITHUB_WORKSPACE/examples/10-graphics/offscreen"
# The toolchain is NAMED rather than inherited: leaving it to whatever
# a neighbouring step happened to select makes this step's subject
# depend on step order, which is not a property anybody reads.
"$MCPP_SELF" build --toolchain "llvm@20.1.7"
for f in triangle_vert triangle_frag; do
d="target/.build-mcpp/out/spirv"
test -f "$d/$f.h" || { echo "missing $d/$f.h"; exit 1; }
# THE MAGIC IS NOT ALWAYS IN THE HEADER, AND THAT IS THE POINT OF
# THIS JOB. The rule chooses the shader compiler this platform
# publishes -- glslang on Linux, glslc here -- and the two split
# the declaration differently: glslang writes a complete `const
# uint32_t ...[] = {...}`, glslc an initialiser list the rule
# declares around, so the words land in `<stem>.inc`. An assertion
# naming only the header is an assertion about ONE compiler, which
# is exactly the shape this step exists to catch.
# ONE FILE AT A TIME, because `grep -qs a b` exits 2 when `b`
# does not exist -- even on a match in `a`, and even with `-s`,
# which suppresses the message and not the status. Written as one
# grep over both names, this criterion fails whenever the route
# that produces only a header is taken, which is a failure about
# the criterion and not about the shader.
found=""
for g in "$d/$f.h" "$d/$f.inc"; do
[ -f "$g" ] && grep -q '0x07230203' "$g" && found=1
done
[ -n "$found" ] \
|| { echo "$f carries no SPIR-V magic in either $f.h or $f.inc"; exit 1; }
done
echo "ok: both shader stages compiled and the Vulkan half linked"

- name: "Toolchain: LLVM — build mcpp (self-host)"
shell: bash
run: |
Expand Down
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,65 @@

## [Unreleased]

## [2026.9.7.1] - 2026-09-07

### 两处只在 Linux 之外成立的缺陷,以及第一条图形管线

**引擎与规则层早就与平台无关,而生态只在 Linux 上完整。** 把规则包在另外两个平台上
真跑一遍,暴露了两处引擎缺陷 —— 两处的共同形状是:一段代码的正确性依赖于宿主,而 CI
只在其中一个宿主上执行它。

**macOS 14 上构建程序用不了 `std::println`,而显而易见的修法更糟(已记录,未修)。**
`host_link_tokens` 的「信任 cfg」出口不给 `Toolchain::linkRuntimeDirs` 发 `-L`,于是
macOS 上 `-lc++` 经 SDK 解析到**系统**那份,而头文件来自载荷。macos-14 上编译一个只有
`import std` 的构建程序:

ld64.lld: error: undefined symbol: std::__1::__is_posix_terminal(__sFILE*)

`std::print` 不是 header-only 的,它的两个重载都要到 libc++ **dylib** 里取支持符号,
而那两个符号是在 macOS 14 不带的那一版里加进去的。macOS 15 有,所以这个项目用到的每
一台 macOS runner 都是绿的。

在这一条上加 `-L<载荷>/lib` 试过了,**它买来一个更糟的问题**:那会让 `-lc++` 解析到
工具链自己的 dylib,也就是 `dist::mechanism_for` 在 Mach-O 上明确拒绝的
ToolchainCoupled —— LLVM 的 macOS libc++abi 与 libunwind dylib **向上链接**
`/usr/lib/libc++`,于是系统 libc++ 与工具链的那份同时载入,跨两份释放的对象在
libmalloc 里 abort(#202)。CI 报的正是这条路的第一步:链接停在 `__cxa_end_catch`
与其余那些系统 libc++ 会再导出、而载荷那份不会的 ABI 符号上。

所以这个宿主上的 C++ 运行时就是系统那一份,而下限由分发契约选的**静态 libc++** 变成
真的 —— 那是产物的机制,不属于一个 mcpp 编译、就地跑一次、然后丢掉的辅助程序。
**限制照实写下来:macOS 14 上构建程序不能用 `std::print` / `std::println`。**
`std::format` 是 header-only 的,没有这个问题;本轮 mcpp:plugins 的六个模块因此全部
改用它。判据 `HostFlags.OnlyTheSpelledOutExitNamesTheToolchainRuntimeDirs` 把这条
不对称写成了一条会跑的断言。

**版本约束里的 `>` 被 cmd.exe 读成重定向(Windows)。** mcpp 把供给请求作为 JSON 参数
放在 shell 命令行上;`shell::quote` 回答的是子进程的 argv 解析(`\"`),而 cmd 不认这
个转义,于是走到 `>` 时引号数是偶数,`>` 成了重定向:

Provisioning [xlings.workspace] entries declared by dependencies (xim:shaderc@>=2026.3)
The filename, directory name, or volume label syntax is incorrect.

`>=` 正是每个规则包声明下界用的形态,而在此之前没有任何一条能在 Windows 上生效的声明
带过 `>`。修法是标准的双重转义,判据是两个解析器的模拟器加一条反向腿。

### `examples/10-graphics/offscreen`:第一条图形管线,判据是像素

此前所有异构示例都是计算。这一个是图形:顶点与片段两个着色器阶段、一条 render pass、
`vkCmdDraw` 一个三角形、`vkCmdCopyImageToBuffer` 取回像素。**离屏而不是开窗**,因为那
是可断言的形态。同一道接缝背后是一个自己写的软件光栅器,而两条腿的中心像素**逐字节
相同** —— 图像是契约,设备名是唯一区分它们的东西。

这个示例挖出一条使用者会撞上的规则:**依赖不能被 layer 条件化**。
`cfg(accelerator = ...)` 下的 `[build]` 源生效而依赖被忽略,于是包被丢掉、包含它的源
被留下。

### 文档

`docs/20` 新增「每条 lane 到得了哪些平台」:三件事同时为真才叫一条 lane 在某个平台上
成立,而第三件(规则自己那段按宿主分岔的代码编译得过)是最容易被默认成立的那一件。

## [2026.9.6.6] - 2026-09-07

### 一个包一个版本:规则自带环境,工程只写例外
Expand Down
2 changes: 2 additions & 0 deletions docs/01-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ examples.
| 09d | [`…/hip`](../examples/09-heterogeneous/hip/) | The same computation in HIP, reaching an NVIDIA device | `mcpp.rules.hip`, HIP as a header layer over the CUDA runtime, a two-chunk `accel` |
| 09e | [`…/multi-backend`](../examples/09-heterogeneous/multi-backend/) | Several backends in ONE artifact, chosen at run time — the library shape, not the program shape | `accel` as a set, `cfg(accelerator = "none")` and its negation, a dispatch chain, a module seam over a C island boundary |
| 09f | [`…/cann`](../examples/09-heterogeneous/cann/) | An Ascend C kernel behind the same seam. **Does not build yet** — its README names the two missing pieces | the `.asc` device extension, `op_kernel`/`op_host` as an island CANN already has, `accelerator = "none"` for the fallback |
| 10 | [`examples/10-graphics`](../examples/10-graphics/) | Graphics rather than compute: a rendering pipeline whose result is pixels | `mcpp.rules.spirv` for the vertex and fragment stages, offscreen rendering as the assertable form |
| 10a | [`…/offscreen`](../examples/10-graphics/offscreen/) | A triangle rasterised by Vulkan into a buffer, and the same triangle by a software rasteriser behind the same seam | two shader stages from one glob, a render pass with no window or swapchain, a pixel as the criterion |

## Suggested Reading Order

Expand Down
Loading
Loading