Skip to content

Commit 2118bfe

Browse files
committed
The C library layer declares the C environment it presents, the engine realises and checks it
Until now the compiler payload's target triple implied the environment a C program compiles against. The moment a package supplies the C library instead (mcpp:c-abi=<impl>), that stops being true: openkal-musl on x86_64-windows-gnu generates PE/Win64 code while presenting a POSIX environment, and every #ifdef _WIN32 above it is asking the wrong layer. - [c-abi] manifest block: presents (posix/windows/none), data-model (arch-default/lp64/llp64/ilp32), wchar (16/32), builtins (iso/platform). Only a package that also provides mcpp:c-abi=<impl> may declare it. presents/data-model/wchar carry no default; an unknown key or value is always a parse error naming the key. Absent block changes nothing. (modules/manifest/src/{targetside_model,toml,types}.cppm) - Realisation (src/toolchain/cenv.cppm): a pure, table-driven mapping from request to compiler tokens with no package names. The flagship case -- presents=posix on Windows/x86_64 -- realises as a Cygwin-flavoured compile-only identity switch (--target=x86_64-pc-cygwin, -U__CYGWIN__ -U__CYGWIN32__), leaving the link line on the graph's resolved triple, because the two triples measure identical machine code. Reaches C, C++, the dependency scan and the std module precompile. A request this engine cannot realise is refused naming the target, request and what is missing. [package] c-environment = "platform" opts a package out of the realisation entirely. - Verification, not trust (src/toolchain/cenv_probe.cppm): one -E -dM predefined-macro dump, no codegen and no execution, checked against the declaration and cached per configuration. Caught a real mapping bug during development (data-model = "llp64" alongside presents = "posix" on Windows silently read as already-satisfied); the mapping now refuses that combination directly instead of relying on the probe to catch it late. - __openkal__ defined for every target-side unit when the resolved kernel-abi layer's interface is openkal, read from the layer's value. - Closure visibility: provides = ["platform-sdk"] is a package's own statement; the Target report gains a platform-deps line, and [build] platform-dependencies = "refuse" fails the build when one is present. (src/build/prepare.cppm) - The realised environment and __openkal__ fold into the build fingerprint, so LP64 and LLP64 builds of one source/manifest never share a directory. The equivalent store-key gap for install-hook artifacts is designed but not built here -- documented in docs/22 as a known gap. Two pre-existing defects found while verifying this are filed separately, not fixed here: #666 (a Clang-built mcpp binary SIGSEGVs in its own ELF runtime inspector; GCC-built does not) and #667 (a GCC self-host ICE importing mcpp.targetside from a new consumer under a parallel build; -j1 avoids it). Docs: docs/22 ([c-abi], verification, fingerprint), docs/21 (a declared environment moves the compiled triple, not the linked one), docs/24 (the three macro families, the __openkal__ rule, platform units), docs/06 (platform-sdk), and their zh mirrors. Tests: test_manifest.cpp, test_cenv.cpp (14 cases covering the whole mapping table), e2e 741. Design: mcpplibs/openkal .agents/docs/2026-09-18-openkal-c-environment-and-personalities-design.md
1 parent 2b42f74 commit 2118bfe

21 files changed

Lines changed: 2061 additions & 7 deletions

CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,44 @@
55

66
## [Unreleased]
77

8+
### C 库层可以声明它呈现的 C 环境,引擎实现并校验:设计 2026-09-18(2026.9.18.1)
9+
10+
三元组的 OS 段过去总是同时回答两件事:目标机器长什么样,以及源码面对的 C 环境是什么。
11+
一旦某个包接管了 C 库(`mcpp:c-abi=<impl>`),这两个问题就可能有不同答案——openkal-musl
12+
在 `x86_64-windows-gnu` 上生成 PE/Win64 代码,却是一个呈现 POSIX 环境的 musl 移植版,而
13+
`_WIN32` 在这种图里同时冒充了「机器是 Windows」与「C 运行时是 Windows CRT」两件事,后者
14+
是假的。这一版本让 C 库层把它实际呈现的环境说出来,由引擎实现并校验,不再由三元组或某个
15+
库自己猜。
16+
17+
- **`[c-abi]` 块**:只有提供 `mcpp:c-abi=<impl>` 层的包可以声明,其余情况在清单解析阶段
18+
即被拒绝。`presents`(`posix`/`windows`/`none`)、`data-model`
19+
(`arch-default`/`lp64`/`llp64`/`ilp32`)、`wchar`(`16`/`32`)三个键没有默认值,
20+
`builtins`(`iso`/`platform`)默认 `platform`;拼错的键或取值都是解析错误而不是静默
21+
忽略。不声明该块的包,产出的命令行与这项能力之前逐字节相同。
22+
(`modules/manifest/src/{targetside_model,toml,types}.cppm`,单测 `test_manifest.cpp`)
23+
- **实现(realisation)**:新模块 `mcpp.toolchain.cenv` 保存「请求 → 三元组与开关」的映射
24+
——通用知识,不含包名。Windows 上 `presents = "posix", data-model = "arch-default"`
25+
采用 Cygwin 式语义,仅在编译行把 `--target=` 换成 `x86_64-pc-cygwin` 并去掉
26+
`__CYGWIN__`/`__CYGWIN32__`;链接行保持图解析出的三元组不变,因为两个三元组生成的机器码
27+
实测完全一致(PE、Win64 调用约定、SEH)。无法满足的请求明确拒绝,点名目标、请求与缺什么。
28+
`[package] c-environment = "platform"` 让一个包(如 openkal-windows)的自身单元退出这项
29+
实现,继续按三元组自身的默认环境编译。(`src/toolchain/cenv.cppm`,单测 `test_cenv.cpp`)
30+
- **声明被校验,不被信任**:新模块 `mcpp.toolchain.cenv_probe` 用最终参数编译一次纯预处理
31+
探针(`-E -dM`,不执行、不需要目标可在本机运行),核对 `__SIZEOF_LONG__`、
32+
`__SIZEOF_WCHAR_T__` 与环境身份宏是否与声明相符,不符即失败并同时打印声明值与实测值;
33+
结果按配置缓存。(`src/toolchain/cenv_probe.cppm`)
34+
- **`__openkal__`**:`kernel-abi` 解析为 `openkal` 时,引擎为目标侧全部单元定义它——取自层
35+
的取值,不取自包名。只能用于决定是否调用 `kal_*`,不得用于选择头文件或推断平台
36+
(`docs/24`)。
37+
- **闭包可见性**:`provides = ["platform-sdk"]` 是包对自己的陈述;构建报告新增一行列出
38+
图中所有这样的包(没有则为空),`[build] platform-dependencies = "refuse"`
39+
让它们的出现直接失败构建。(`src/build/prepare.cppm`,`docs/06`)
40+
- **指纹**:解析出的环境与 `__openkal__` 参与构建指纹,LP64 与 LLP64 两次构建绝不共享输出
41+
目录。安装钩子的存储键尚未补上同一个缺口,已在 `docs/22` 记录为已知差距。
42+
- 文档:`docs/22`(`[c-abi]`、校验、指纹)、`docs/21`(声明的环境如何移动编译三元组而不
43+
移动链接三元组)、`docs/24`(三组宏、`__openkal__` 的规则、平台单元)、`docs/06`
44+
(`platform-sdk` 标记)及对应 zh 镜像。
45+
846
### 目标侧由依赖图供给时,编译侧关掉对应的隐式搜索:#662(2026.9.17.3)
947

1048
链接侧早在 #511 就已经按 `plan.targetSide.cAbi.prebuilt()` 撤掉 `-nostdlib`,编译侧一直

docs/06-features-and-capabilities.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,32 @@ exists to name — it works for the declaring package and then hands the SDK's
523523
headers, unasked, to a consumer that may be built for a target where that SDK
524524
does not belong at all.
525525

526+
#### Making it visible to the closure, not only private (mcpp 2026.9.18+)
527+
528+
`visibility = "private"` answers "does this dependency leak to a consumer's
529+
`-I` list" and says nothing about "is this dependency in the graph at all" —
530+
a question [22 — The Target Side](22-target-side.md#closure-visibility)
531+
answers for the WHOLE build. The SDK package itself states the fact a report
532+
or a refusal needs:
533+
534+
```toml
535+
[package]
536+
name = "some.windows-headers"
537+
version = "1.0.0"
538+
provides = ["platform-sdk"]
539+
```
540+
541+
`platform-sdk` is an ordinary, UNPREFIXED capability — like `blas` above, not
542+
like `mcpp:c-abi=<impl>` — because it names no layer this engine resolves,
543+
only a fact a package states about itself. A build's `Target` report lists
544+
every package in the graph that declares it (empty when none), and `[build]
545+
platform-dependencies = "refuse"` fails the build outright when one is
546+
present — the machine-checkable form of "this build is a closure entirely on
547+
its kernel-abi implementation and nothing else." Declaring `provides =
548+
["platform-sdk"]` and `visibility = "private"` together is the complete
549+
statement: private keeps the headers off a consumer's search path, and
550+
`platform-sdk` keeps the fact off nobody's report.
551+
526552
## Current limitations
527553

528554
**A default feature is turned off in the manifest, not on the command line.**

docs/21-the-target-triple.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,21 @@ is in use. A payload for it does not exist on any host; its system can only come
468468
from a dependency graph, which is what `toolchain list` reports as
469469
`via dependency graph`.
470470

471+
### A declared environment can move the compiled triple, never the linked one (mcpp 2026.9.18+)
472+
473+
A `c-abi` package's `[c-abi]` block ([22 — The C Environment](22-target-side.md#the-c-environment-a-c-abi-package-presents-mcpp-2026918))
474+
can change what `--target=` a COMPILE receives — `x86_64-windows-gnu` compiles
475+
as `x86_64-pc-cygwin` when the graph's C library declares `presents =
476+
"posix"` — without changing the triple this chapter is otherwise about. The
477+
resolved triple (`mcpp toolchain list`'s spelling, the output directory name,
478+
the `Target` report's head line, the LINK line) stays exactly what the graph
479+
and the toolchain resolved; only the compiler's own `--target=` token, on
480+
compile commands, is substituted, because that is the one place the
481+
declaration's environment-identity macros and data model come from. A reader
482+
who greps a `compile_commands.json` for `--target=` and finds a spelling this
483+
chapter never lists is looking at exactly this — see docs/22 for what
484+
triggered it and why.
485+
471486
## The Build Host Is A Third Axis
472487

473488
The two axes above — which compiler, and where the C library comes from — are

docs/22-target-side.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,49 @@ Interface and implementation are separate columns. `openkal` is an interface
188188
and `openkal-windows` an implementation of it; collapsing the two would conceal
189189
why one source reaches several machines.
190190

191+
## Closure Visibility
192+
193+
The five-layer report above answers "where does each layer come from"; it
194+
says nothing about the OTHER packages in the graph — a dependency bound to
195+
one platform's SDK is exactly as invisible to it as an ordinary one. Two
196+
mechanisms close that gap (design 2026-09-18 §6).
197+
198+
**What counts as a platform dependency, precisely.** A package brings one if
199+
and only if it says so — `provides = ["platform-sdk"]`, an ordinary,
200+
unprefixed capability. Nothing here is inferred from header paths, link
201+
flags or a dependency's `visibility`: inference would have exactly the
202+
silent-typo failure mode the reserved `mcpp:` prefix exists to avoid for the
203+
five layers, applied to a fact this engine cannot otherwise observe. [06 —
204+
A platform SDK dependency stays private](06-features-and-capabilities.md#a-platform-sdk-dependency-stays-private)
205+
is the pattern such a package's own manifest follows.
206+
207+
**The report.** A build's `Target` report gains a line naming every package
208+
in the graph that declares `platform-sdk`, empty when none:
209+
210+
```
211+
Target platform-deps —
212+
```
213+
214+
```
215+
Target platform-deps some.windows-headers@1.0.0
216+
```
217+
218+
Printed under the same visibility rule as the five layers: only when there
219+
is something to report, or always under `MCPP_VERBOSE`.
220+
221+
**The refusal.** `[build] platform-dependencies = "refuse"` fails the build
222+
outright when the graph contains one — the machine-checkable form of "this
223+
build is a closure entirely on its kernel-abi implementation and nothing
224+
else":
225+
226+
```toml
227+
[build]
228+
platform-dependencies = "refuse"
229+
```
230+
231+
The only accepted value is `"refuse"`; absent (the default) allows platform
232+
dependencies, today's behaviour.
233+
191234
## What A Package Declares
192235

193236
### provides
@@ -245,6 +288,124 @@ toolchain values ([32 — Authoring a Payload](32-authoring-a-payload.md)). It
245288
must not build a different variant into the same store directory, because the
246289
first consumer would then decide the variant for every later one.
247290

291+
### The C Environment A `c-abi` Package Presents (mcpp 2026.9.18+)
292+
293+
A traditional stack never has to say this: the compiler payload's target
294+
triple already implies the environment a C program compiles against. The
295+
moment a package supplies the C library instead, that stops being true —
296+
openkal-musl on `x86_64-windows-gnu` generates PE/Win64 code while
297+
presenting a POSIX environment to source, because it is a musl port and
298+
every `#ifdef _WIN32` in the libraries above it is asking the wrong layer.
299+
The `[c-abi]` block is how the C library states, once, what it actually
300+
presents — and only the package that supplies the layer may state it.
301+
302+
```toml
303+
# openkal-musl's manifest
304+
[package]
305+
provides = ["mcpp:c-abi=musl"]
306+
307+
[c-abi]
308+
presents = "posix" # posix | windows | none
309+
data-model = "arch-default" # arch-default | lp64 | llp64 | ilp32
310+
wchar = 32 # 16 | 32
311+
builtins = "iso" # iso | platform (default platform)
312+
```
313+
314+
**Who may declare it.** A package that writes `[c-abi]` without also listing
315+
`mcpp:c-abi=<impl>` in `provides` is stating a fact about a layer it does not
316+
supply, and that is always wrong rather than merely unusual — it is refused
317+
at manifest parse time, naming the missing `provides` entry.
318+
319+
**The four keys, and their closed value sets.**
320+
321+
| Key | Values | Answers |
322+
|---|---|---|
323+
| `presents` | `posix` / `windows` / `none` | which environment-identity macros source sees (`__unix__` vs `_WIN32` vs neither) |
324+
| `data-model` | `arch-default` / `lp64` / `llp64` / `ilp32` | how wide `long` is |
325+
| `wchar` | `16` / `32` | how wide `wchar_t` is |
326+
| `builtins` | `iso` / `platform` (default) | whether the compiler may assume the platform C library's own extensions |
327+
328+
`presents`, `data-model` and `wchar` carry no default: a block that omits one
329+
of them is refused naming the missing key, because "absent" is not the same
330+
statement as any of the three closed values could make. `builtins` alone
331+
defaults to `platform`, today's behaviour. An unrecognised key or an
332+
unrecognised value is always a parse error naming the key — never silently
333+
ignored. **A package that declares no `[c-abi]` block changes nothing**: the
334+
resolved target side, every compile command and every cache key are
335+
byte-identical to a build before this feature existed.
336+
337+
Three facts, kept separate, because none of them implies another: `presents`
338+
picks the source branch, `data-model`/`wchar` pick the ABI. POSIX does not
339+
imply LP64 (it is ILP32 on a 32-bit architecture), and LP64 does not imply
340+
POSIX.
341+
342+
**Realisation.** Once the `c-abi` layer resolves to a package that declares
343+
this block, mcpp turns the request into compiler configuration for every
344+
target-side unit — the C library itself, the C++ runtime, the compiler
345+
runtime's builtins, and every ordinary package in the graph — covering C,
346+
C++ and assembly compiles, the dependency scan, and the `std` module
347+
precompile alike. mcpp holds one mapping table from request to triple and
348+
flags, generic knowledge that names no C library:
349+
350+
| Target | Request | Realisation |
351+
|---|---|---|
352+
| Linux | `posix` / `arch-default` | the default triple already satisfies it |
353+
| macOS | `posix` / `arch-default` | the default triple already satisfies it |
354+
| Windows | `posix` / `arch-default` | Cygwin-flavoured: `--target=x86_64-pc-cygwin` on the compile line only, `-U__CYGWIN__ -U__CYGWIN32__` (those interfaces are not in this graph); `data-model` becomes LP64 as a consequence of the triple, not a separate flag |
355+
| any | `builtins = "iso"` | turns off code-generation idioms that assume a platform C library — `-fno-builtin-memset_pattern16` on Apple targets is the one this survey measured; see `src/toolchain/cenv.cppm` for what else was checked and found not to apply |
356+
| anything else | | refused, naming the target, the request and what is missing — never a silent downgrade |
357+
358+
The Windows row is the flagship case: `x86_64-w64-windows-gnu` and
359+
`x86_64-pc-cygwin` produce IDENTICAL machine code — same PE format, same
360+
Win64 calling convention, same SEH — and differ only in what the
361+
preprocessor sees and how wide `long` is. Realisation therefore touches only
362+
the **compile** line; the **link** line keeps the triple the graph resolved,
363+
because nothing about the object format changed.
364+
365+
**A package's own units can opt out.** A package that provides
366+
`mcpp:kernel-abi=openkal` (openkal-windows, say) has to see the platform's
367+
own environment — it includes platform declarations and `_WIN32` must be
368+
true for it. Such a package, or a platform shim, states:
369+
370+
```toml
371+
[package]
372+
provides = ["mcpp:kernel-abi=openkal"]
373+
c-environment = "platform" # this package's own units compile in the
374+
# triple's own default environment, whatever
375+
# the graph's c-abi declares
376+
```
377+
378+
This is a boundary rule, documented rather than enforced by the engine
379+
beyond the flag itself: the interface such a package exposes to the rest of
380+
the graph must still cross in fixed-width types only (SPEC §5.4).
381+
382+
**Verification, not trust.** A declaration is checked, never trusted — the
383+
same rule openkal applies to its own conformance claims. Once the tokens
384+
above are known, mcpp compiles one syntax-only probe (`-E -dM`, a predefined-
385+
macro dump — cheap, and it needs no execution, which matters because the
386+
realised environment is routinely a cross target) with them and reads back
387+
`__SIZEOF_LONG__`, `__SIZEOF_WCHAR_T__` and which environment-identity
388+
macros are defined, comparing them against the declaration. A mismatch fails
389+
the build and prints both the declared and the measured values. The result
390+
is cached per configuration (compiler binary identity + exact flags), so a
391+
build that resolves the same configuration twice pays for the probe once.
392+
393+
**Fingerprint.** The realised environment participates in the build's
394+
fingerprint (`compileFlags`, §92's field 7): two builds whose C library
395+
declares `lp64` and `llp64` compile the same source into objects whose
396+
`long` disagrees in width, so they never share an output directory, and
397+
neither can reuse a cached object the other produced.
398+
399+
**Store key — not yet closed.** A package whose *install hook* compiles a
400+
static library from source into the shared store is keyed by package and
401+
version, not by which environment it was built against — the same gap
402+
[requires](#requires) already documents for a C++ runtime selection. Closing
403+
it the same way (a `requires`-shaped statement of the environment, checked
404+
at resolution and refused on mismatch) is designed but not yet implemented;
405+
until it is, such a package's install hook must not build more than one
406+
environment's variant into one store directory, exactly as the C++-runtime
407+
case already requires.
408+
248409
### Standard Library Module Sources
249410

250411
A package that is a standard library states where its `std` module source is

docs/24-openkal-cross.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,45 @@ reaches every architecture the compiler supports.
3434
The claim is verified by a matrix of three hosts and three targets, each cell
3535
building one source and running the result.
3636

37+
## Three Layers, Three Macro Families (mcpp 2026.9.18+)
38+
39+
A build over openkal answers three different questions, and until this
40+
release one macro (`_WIN32`) answered two of them at once — the root cause
41+
of every openkal-Windows failure whose diagnosis named a missing platform
42+
header: the code was asking "is this openkal" through a macro that actually
43+
meant "is the Windows CRT present."
44+
45+
| Family | States | Defined by | Example |
46+
|---|---|---|---|
47+
| kernel ABI | `kal_*` is callable, and behaves the same on every platform | the layer providing `mcpp:kernel-abi=openkal` | `__openkal__` |
48+
| C environment | the shape of the C environment source sees | the layer providing `mcpp:c-abi=<impl>`, via [`[c-abi]`](22-target-side.md#the-c-environment-a-c-abi-package-presents-mcpp-2026918) | `__unix__`, `_WIN32`, `__MINGW32__` |
49+
| system & architecture | the underlying OS and processor | the target triple | `__linux__`, `__APPLE__`, `__x86_64__` |
50+
51+
**`__openkal__` — the rule.** The engine defines it, for every target-side
52+
unit, whenever the resolved `kernel-abi` layer's interface name is
53+
`openkal` — read from the LAYER's value, never from a package name, so a
54+
second implementation (`openkal-macos`, `openkal-opensbi`, …) needs no
55+
engine change.
56+
57+
*Allowed:* gating whether a call site invokes `kal_*` at all. Its meaning is
58+
identical on every target, so using it this way never smuggles platform
59+
information into source that is supposed to be implementation-agnostic.
60+
61+
*Forbidden:* selecting a header, inferring whether `_WIN32` is real,
62+
working around a missing SDK, or telling `linux`/`windows`/`macos` apart.
63+
Those are the C-environment layer's or the platform layer's questions —
64+
write `cfg(c-abi = "…")` or `cfg(kernel-abi = "…")` in the manifest instead
65+
(and see [22 — Adaptation To The Resolved Target Side](22-target-side.md#adaptation-to-the-resolved-target-side)
66+
for the predicate grammar).
67+
68+
**Platform units.** A package that itself needs the platform's own
69+
environment — openkal-windows, or a platform shim under [06's private
70+
dependency pattern](06-features-and-capabilities.md#a-platform-sdk-dependency-stays-private)
71+
— states `[package] c-environment = "platform"` (docs/22) rather than
72+
reading `__openkal__` or any other macro to work it out: the boundary is
73+
declared, not inferred, and everything crossing it is still fixed-width
74+
(SPEC §5.4).
75+
3776
## What A Project Writes
3877

3978
```toml

docs/zh/06-features-and-capabilities.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,28 @@ some.windows-headers = { version = "1.0", visibility = "private" }
447447
可以工作,却会把 SDK 的头文件不由分说地交给消费方,而消费方构建的目标上很可能
448448
根本不该出现这个 SDK。
449449

450+
#### 让闭包也能看见它,而不只是私有(mcpp 2026.9.18+)
451+
452+
`visibility = "private"` 回答的是「这个依赖会不会泄漏到消费方的 `-I` 列表」,不回答
453+
「这个依赖到底在不在图里」——后者是[22 —— 目标侧](22-target-side.md#closure-visibility)
454+
要为**整个构建**回答的问题。由 SDK 包自己陈述报告或拒绝开关需要的事实:
455+
456+
```toml
457+
[package]
458+
name = "some.windows-headers"
459+
version = "1.0.0"
460+
provides = ["platform-sdk"]
461+
```
462+
463+
`platform-sdk` 是一个普通的、不带命名空间前缀的能力——像上面的 `blas`,不像
464+
`mcpp:c-abi=<impl>`——因为它不指代引擎解析的任何一层,只是包对自己陈述的一个事实。
465+
构建的 `Target` 报告会列出图中每一个声明了它的包(没有则显示为空);
466+
`[build] platform-dependencies = "refuse"` 则在它出现时直接让构建失败——这是
467+
「本次构建完全是基于其 kernel-abi 实现的闭包,不多不少」这句话的机器可核验形式。
468+
同时声明 `provides = ["platform-sdk"]``visibility = "private"` 才是完整的陈述:
469+
private 让头文件不出现在消费方的搜索路径上,`platform-sdk` 让这个事实不从任何人的
470+
报告里消失。
471+
450472
## 当前边界
451473

452474
**默认 feature 在 manifest 里关掉,不在命令行上关掉。** 没有 `--no-default-features`

0 commit comments

Comments
 (0)