Skip to content

Commit 8a3a18d

Browse files
committed
refactor(doctor): the device toolkit report belongs to the package that owns the tool
`mcpp self doctor` grew a CUDA section: it located a toolkit payload, read NVIDIA's `crt/host_config.h` for the host-compiler bound, and parsed `nvcc --dryrun` to name a back-end stage the tool could not reach. Every one of those readings was correct, and none of them belonged to the engine. The repository already states the rule as an invariant. `test_runtime_contract` refuses a vendor name beside a probe launch in `src/`, and the reason is that an engine which learns to run one vendor's tool learns to run four: the second backend arrives as a second section, the third as a third, and the engine acquires a table of tools it must keep current with releases it does not control. The same answers are now produced where the tool is known — the rule package the project imports — and reach mcpp as declarations through the build program's channel, which the engine compares without knowing what any of the names mean. Nothing is lost: the CUDA example reports the driver relation, the host-compiler bound and the unreachable stage before its first compile, and the version floor refuses a build the machine cannot run. `test_core_vendor_probes` states the property the removal establishes, over comment-stripped sources so that recording a vendor's name in a comment stays possible. It carries its own denominator: an enumeration that found fewer than a hundred files is a broken scan rather than a clean result. e2e 602 is removed with the section it tested. Its subject was the doctor report, not a build, and the same preference — payload before host — is what the example's rule package now exercises end to end.
1 parent 89c986d commit 8a3a18d

7 files changed

Lines changed: 184 additions & 903 deletions

File tree

docs/20-accelerators.md

Lines changed: 47 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -67,73 +67,47 @@ The division is deliberate. mcpp owns the graph, the artifact's identity and
6767
the set of architectures; a vendor's flag spelling, its architecture syntax and
6868
its host-compiler requirements belong to the rule.
6969

70-
## The host compiler a device compiler will accept
71-
72-
nvcc refuses host compilers newer than a bound it states in its own
73-
`crt/host_config.h`, and mcpp's toolchain payload is frequently newer than that
74-
bound. Because mcpp supplies the host compiler, it can report the pairing
75-
before anything is compiled:
76-
77-
```
78-
$ mcpp self doctor
79-
Checking device toolkit
80-
warning: cuda will refuse this host compiler: gcc 13 exceeds the bound of 12
81-
stated in /usr/include/crt/host_config.h.
82-
```
83-
84-
The bound is read from the toolkit rather than tabulated in mcpp, so a toolkit
85-
mcpp has never seen still answers, and a header mcpp cannot parse yields no
86-
bound and therefore no claim.
87-
88-
**A payload is read before the host.** A toolkit installed through xlings is the
89-
one a build will use, and it is usually the newer one: a 12.9 payload states
90-
`gcc <= 14` and a 13.3 payload `gcc <= 15`, where a distribution's CUDA 12.0
91-
states `gcc <= 12`. Both package stores are searched — mcpp's own and the one
92-
`xlings install` writes to — and the host's locations remain, last, because a
93-
machine with a distribution toolkit and no payload is a real configuration.
94-
95-
**What is not checked here, and why.** A device runtime must not be newer than
96-
the driver it runs against; when it is, the build compiles and links cleanly and
97-
fails at the first allocation with *"CUDA driver version is insufficient for
98-
CUDA runtime version"*. mcpp knows the relation — `driver_accepts_toolkit`
99-
states when one version may meet another, including that minor-version
100-
compatibility means a 12.9 runtime is fine on a driver serving 12.4 — but it
101-
does not ask the machine which driver it has, because asking means running a
102-
vendor's tool and the engine owns no vendor probes. Those numbers reach the
103-
report as declarations instead: a toolkit payload states the driver it needs,
104-
and the package that owns the host driver states what the host has.
105-
106-
This is reported rather than enforced: a project that compiles no device code
107-
is unaffected by an incompatible pair.
108-
109-
## Whether the device compiler can reach its own back-end
110-
111-
A toolkit can be installed, complete and on `PATH` and still fail at its first
112-
stage. nvcc runs `cicc`, `cudafe++`, `ptxas` and `fatbinary` as bare names, on
113-
a `PATH` it prepends itself from an `nvcc.profile` beside its own binary. On
114-
Debian-family packaging that profile is a symlink into `/etc`, so a container
115-
or sandbox that replaces `/etc` removes it. nvcc then keeps the ambient `PATH`
116-
and reports:
117-
118-
```
119-
sh: 1: cicc: not found
120-
```
121-
122-
The message names neither nvcc nor the profile, and nothing about the toolkit
123-
is missing, so the obvious checks all pass. `mcpp self doctor` asks nvcc for
124-
its plan instead of assuming one:
125-
126-
```
127-
$ mcpp self doctor
128-
Checking device toolkit
129-
warning: nvcc cannot reach its own back-end: it invokes 'cicc' by name, and
130-
that name does not resolve on the search path it states.
131-
```
132-
133-
The plan comes from `nvcc --dryrun`, which prints the stages and the `PATH`
134-
nvcc will use without compiling anything. A dryrun that produces no plan --
135-
there is no nvcc, or the output is not one -- yields no finding, because a
136-
probe that reaches no answer must not invent one.
70+
## What the rule package reports before the first compile
71+
72+
Three things go wrong late with a device toolkit, and none of them is a fact
73+
about the build graph. They are read and reported by the **rule package** that
74+
drives the tools -- `examples/09-cuda-kernel/rules-cuda` shows each one -- and
75+
the engine owns none of them (`tests/unit/test_core_vendor_probes.cpp` holds
76+
that line, so a second backend never grows a second copy inside mcpp).
77+
78+
**The host compiler a device compiler will accept.** nvcc refuses host
79+
compilers newer than a bound it states in its own `crt/host_config.h`, and
80+
mcpp's toolchain payload is frequently newer than that bound. On the nvcc
81+
route the rule reads the bound from the toolkit it resolved -- a payload
82+
before the host, because a toolkit installed through xlings is the one the
83+
build uses and is usually the newer one (a 12.9 payload states `gcc <= 14`
84+
where a distribution's CUDA 12.0 states `gcc <= 12`) -- and says which
85+
compiler it chose and why, through `mcpp::warning`. The primary route has no
86+
such bound: `clang -x cuda` is its own host compiler.
87+
88+
**Whether the device compiler can reach its own back-end.** A toolkit can be
89+
installed, complete and on `PATH` and still fail at its first stage: nvcc
90+
runs `cicc`, `cudafe++`, `ptxas` and `fatbinary` as bare names on a `PATH` it
91+
prepends from an `nvcc.profile` beside its own binary, and a sandbox that
92+
replaces `/etc` removes a Debian-packaged profile. The rule asks nvcc for its
93+
plan (`nvcc --dryrun`) rather than assuming one, resolves each stage, and
94+
names the first one that does not resolve together with the payload that
95+
provides it. A dryrun that produces no plan yields no finding.
96+
97+
**Whether the driver is new enough for the runtime.** A device runtime must
98+
not be newer than the driver it runs against; when it is, the build compiles
99+
and links cleanly and fails at the first allocation with *"CUDA driver
100+
version is insufficient for CUDA runtime version"*. The rule reads the
101+
driver's version through the driver's own library (reached through the
102+
sentinel package, never through `/usr/lib`) and states it as a fact; it states
103+
the floor its runtime needs; and the engine compares the two before anything
104+
is compiled -- see the probe channel in [07 — build.mcpp](07-build-mcpp.md).
105+
The engine reads a name, a relation and a version; `cuda.driver` is data
106+
flowing through.
107+
108+
Reported rather than enforced where a wrong answer would cost more than none:
109+
a machine with no rule package in its project has nothing vendor-specific to
110+
say and says nothing, and a probe that reaches no answer invents none.
137111

138112
## Declaring what a build targets
139113

@@ -257,10 +231,12 @@ combinators.
257231

258232
## Two boundaries worth stating
259233

260-
**`--accel` is a `build` option**, alongside `--static` and `--toolchain`, and
261-
is not repeated on `run`, `test` or `pack`. Those read `[build] accel` from the
262-
manifest like every other build input; the flag exists for overriding one
263-
build, which is the case `build` covers.
234+
**`--accel` and `--no-accel` are `build`, `run` and `test` options** (run and
235+
test from 2026.9.6), as `--target` and `--profile` are. `pack` reads
236+
`[build] accel` from the manifest like every other build input. The flag was a
237+
`build`-only option at first, and the measured consequence was a project whose
238+
CPU-only variant could be built and not run: `mcpp build --no-accel` produced
239+
it, and `mcpp run` handed back the device build.
264240

265241
**`mcpp pack` does not emit the `accel` field.** It could write whatever the
266242
manifest declared, and that is exactly why it does not: the field states what an

docs/zh/20-accelerators.md

Lines changed: 32 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -51,64 +51,35 @@ tarball 已经发出去了。设备源文件必须被显式点名。
5151
这个划分是刻意的。mcpp 拥有构建图、产物身份与架构集合;厂商的 flag 拼法、
5252
架构语法与宿主编译器要求属于规则包。
5353

54-
## 设备编译器接受哪些宿主编译器
55-
56-
nvcc 拒绝比它在自己的 `crt/host_config.h` 中声明的上界更新的宿主编译器,
57-
而 mcpp 的工具链载荷常常比那个上界更新。由于宿主编译器由 mcpp 提供,
58-
它可以在任何编译发生之前报告这个配对:
59-
60-
```
61-
$ mcpp self doctor
62-
Checking device toolkit
63-
warning: cuda will refuse this host compiler: gcc 13 exceeds the bound of 12
64-
stated in /usr/include/crt/host_config.h.
65-
```
66-
67-
上界是从工具包读出的,不是抄在 mcpp 里的表,因此一个 mcpp 从未见过的工具包同样能作答;
68-
而一个 mcpp 无法解析的头文件产生不出上界,也就不产生任何断言。
69-
70-
**载荷先于 host 被读取。** 经 xlings 装的工具包才是构建会用的那个,而且通常更新:
71-
12.9 载荷声明 `gcc <= 14`、13.3 载荷声明 `gcc <= 15`,而发行版的 CUDA 12.0 声明
72-
`gcc <= 12`。两个包 store 都会搜 —— mcpp 自己的,以及 `xlings install` 写入的那个;
73-
host 的位置保留在最后,因为「有发行版工具包、没有载荷」是一种真实配置。
74-
75-
**这里不检查什么,以及为什么。** 设备运行时不得比它将运行于其上的驱动更新;
76-
更新时构建**干净地编译并链接**,却在第一次分配时失败,报
77-
*"CUDA driver version is insufficient for CUDA runtime version"*
78-
mcpp 知道这个关系 —— `driver_accepts_toolkit` 陈述一个版本何时可以遇上另一个,
79-
包括「小版本兼容」意味着 12.9 的运行时在只服务到 12.4 的驱动上没问题 ——
80-
但它**不去问机器装的是哪个驱动**,因为问就意味着运行一个厂商的工具,而引擎不持有
81-
任何厂商探针。那两个数字改以**声明**的形式抵达:工具包载荷声明它需要的驱动,
82-
持有宿主驱动的那个包声明宿主有什么。
83-
84-
这是报告而非强制:一个不编译任何设备代码的工程,不受不兼容配对的影响。
85-
86-
## 设备编译器能否够到自己的后端
87-
88-
一个工具包可以安装完整、就在 `PATH` 上,却仍然在第一个阶段失败。
89-
nvcc 以裸名调用 `cicc``cudafe++``ptxas``fatbinary`,依赖的是它自己
90-
从紧邻其二进制的 `nvcc.profile` 前置进来的一条 `PATH`。在 Debian 系的打包里,
91-
那个 profile 是指向 `/etc` 的符号链接,于是任何替换了 `/etc` 的容器或沙箱都会移除它。
92-
nvcc 随即沿用环境里原有的 `PATH`,并报出:
93-
94-
```
95-
sh: 1: cicc: not found
96-
```
97-
98-
这条消息既没有提到 nvcc,也没有提到 profile,而工具包本身一样不缺,
99-
于是所有显而易见的检查都会通过。`mcpp self doctor` 因此去问 nvcc 要它的计划,
100-
而不是假设一份:
101-
102-
```
103-
$ mcpp self doctor
104-
Checking device toolkit
105-
warning: nvcc cannot reach its own back-end: it invokes 'cicc' by name, and
106-
that name does not resolve on the search path it states.
107-
```
108-
109-
计划来自 `nvcc --dryrun` —— 它打印各个阶段与 nvcc 将要使用的 `PATH`,
110-
而不编译任何东西。一次没有产生计划的 dryrun(没有 nvcc,或输出不是一份计划)
111-
不产生任何结论:一个够不到答案的探测不应当发明一个。
54+
## 规则包在第一次编译之前报告的事
55+
56+
设备工具包有三件事出错得很晚,而没有一件是关于构建图的事实。它们由驱动这些工具的
57+
**规则包**读取并报告 —— `examples/09-cuda-kernel/rules-cuda` 逐一演示 —— 引擎一件都
58+
不拥有(`tests/unit/test_core_vendor_probes.cpp` 守住这条线,于是第二个后端不会在
59+
mcpp 里长出第二份拷贝)。
60+
61+
**设备编译器接受哪些宿主编译器。** nvcc 拒绝比它在自己的 `crt/host_config.h` 中声明
62+
的上界更新的宿主编译器,而 mcpp 的工具链载荷往往比那个上界更新。在 nvcc 路线上,规则
63+
从它解析到的工具包读出上界 —— 载荷先于宿主,因为经 xlings 安装的工具包才是构建会用
64+
的那个,通常也是更新的那个(12.9 载荷写着 `gcc <= 14`,发行版的 CUDA 12.0 写着
65+
`gcc <= 12`)—— 并通过 `mcpp::warning` 说出它选了哪个编译器、为什么。主路线没有这条
66+
上界:`clang -x cuda` 自己就是宿主编译器。
67+
68+
**设备编译器能否够到自己的后端。** 工具包可以装好、完整、在 `PATH` 上,却在第一阶段
69+
就失败:nvcc 以裸名调用 `cicc``cudafe++``ptxas``fatbinary`,靠的是它从自己二进制
70+
旁边的 `nvcc.profile` 前置进来的一条 `PATH`,而替换了 `/etc` 的沙箱会拿走 Debian 打包的
71+
那个 profile。规则向 nvcc 要它的计划(`nvcc --dryrun`)而不是假设一个,逐个解析各阶段,
72+
点名第一个解析不到的以及提供它的载荷。产不出计划的 dryrun 不产生任何结论。
73+
74+
**驱动是否新到足以承载运行时。** 设备运行时不能比它将遇到的驱动更新;更新时,构建编译
75+
干净、链接干净,到第一次分配才以 *"CUDA driver version is insufficient for CUDA runtime
76+
version"* 失败。规则经驱动自己的库(经由 sentinel 包够到,绝不经 `/usr/lib`)读出驱动
77+
版本并陈述为事实;陈述它的运行时需要的下界;引擎在编译任何东西之前比较两者 —— 见
78+
[07 — build.mcpp](07-build-mcpp.md) 的探针通道。引擎读到的是一个名字、一个关系、一个
79+
版本;`cuda.driver` 是流过引擎的数据。
80+
81+
凡是错答比不答更贵的地方都只报告不强制:工程里没有规则包的机器没有任何厂商相关的话
82+
要说,于是什么都不说;够不到答案的探针不发明答案。
11283

11384
## 声明一次构建的目标
11485

@@ -218,7 +189,9 @@ cxxflags = ["-DMYAPP_ROCM"]
218189

219190
## 两条值得写明的边界
220191

221-
**`--accel``build` 的选项**,与 `--static``--toolchain` 同级,
192+
**`--accel``--no-accel``build``run``test` 三者的选项**(run 与 test 自 2026.9.6 起),与 `--target``--profile` 同级;`pack` 与其它构建输入一样从 manifest 读 `[build] accel`。它起初只挂在 `build` 上,实测的后果是一个工程的 CPU-only 变体能构建却不能运行:`mcpp build --no-accel` 产出了它,而 `mcpp run` 交回的是设备构建。
193+
<!-- 下面一段保留原有说明 -->
194+
**历史:`--accel` 曾只是 `build` 的选项**,与 `--static``--toolchain` 同级,
222195
不在 `run``test``pack` 上重复。那些命令与读取任何其它构建输入一样,
223196
从 manifest 读 `[build] accel`;这个 flag 的用途是覆盖单次构建,而那正是 `build` 覆盖的场景。
224197

0 commit comments

Comments
 (0)