Skip to content

Commit 1bd80e8

Browse files
committed
the macros this engine defines are a module, so the rule cannot drift
`src/toolchain/predefines.cppm` is the specification and the implementation of one thing. The contract is data in that module (`kContract`), the emission is a function beside it (`define_tokens`), and `tests/unit/test_predefines.cpp` asserts the two agree in BOTH directions: a macro emitted and unlisted is a promise nobody can rely on, a row listing a macro nothing emits is one a reader waits for forever. This wave already paid once for a rule kept in a document while the code moved --- the reason-token table was "the four it was missing" twice over. GENERALISED PAST WINDOWS. `__mcpp_target_<os>__` is spelt from the triple's own `os` field, so the ENGINE LEARNS NO OPERATING-SYSTEM NAME and a target added to the triple parser gets its macro with no change here --- the same discipline `[kernel-abi]` interface names follow. Measured: `x86_64-linux-gnu` gives `__mcpp_target_linux__`, `x86_64-windows-gnu` gives `__mcpp_target_windows__`, `riscv64-none-elf` gives `__mcpp_target_none__`. DEFINED ALWAYS, not only where a realisation suppressed something. Conditional emission would make its absence ambiguous: "not Windows" and "Windows, but nothing hid its macros" would read the same, which is the shape of every defect where a "no" and a "never asked" share a reading. Lowercase, `__mcpp_`-prefixed. Two conventions exist --- vendor and product names upper (`__APPLE__`, `_WIN32`), kind-of-system names lower (`__linux__`, `__unix__`) --- and these name kinds of target, sitting beside the second family in real guards. The prefix is load-bearing: a name mcpp owns means what mcpp says it means, which is exactly what `__CYGWIN__` could not offer. `__openkal__` joins the same contract; `__unix__` is listed as SUPPLIED rather than owned, so it keeps the standard spelling and mcpp may not redefine it. THE SEPARATION IS ITSELF A TESTED PROPERTY. The macro was first realised in `mcpp.toolchain.cenv`, which made it derived from a declaration --- yet whether a target is Windows does not depend on any `[c-abi]` block existing. Moving it out turned the cenv test red, correctly; that test now asserts the realisation does NOT carry it, because a token with two owners is a token that will disagree with itself. docs/21 renders the contract in both languages, and docs/24's note that the `__CYGWIN__` trade-off "may flip" is updated: it has, and the replacement has a name.
1 parent 358f4e2 commit 1bd80e8

11 files changed

Lines changed: 501 additions & 55 deletions

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@
77

88
## [2026.9.21.1] - 2026-09-21
99

10+
### 引擎定义的宏成为一份规范,而规范就是那个模块
11+
12+
`src/toolchain/predefines.cppm` 同时是契约与实现:契约是模块里的数据(`kContract`),
13+
发出是它旁边的函数(`define_tokens`),`tests/unit/test_predefines.cpp` **双向**断言两者
14+
一致——发出去却不在表里、或在表里却没人发,都让构建变红。规范与实现分处两地就会漂移,
15+
这一点本轮已经在理由令牌表上付过一次学费。
16+
17+
**`__mcpp_target_<os>__`,覆盖所有平台而不只是 Windows。** 拼法取自三元组自己的 `os`
18+
字段,**引擎不认识任何操作系统名**——三元组解析器新增一个目标,它的宏随之存在。实测:
19+
`x86_64-linux-gnu` → `__mcpp_target_linux__`;`x86_64-windows-gnu` →
20+
`__mcpp_target_windows__`;`riscv64-none-elf` → `__mcpp_target_none__`。
21+
22+
**总是定义,不只在有东西被压掉时。** 条件式发出会让缺席含义不唯一:「不是 Windows」与
23+
「是 Windows 但没有东西压掉它的宏」会读成同一件事。
24+
25+
**命名:小写,`__mcpp_` 前缀。** 业界并存两套约定——厂商与产品名大写(`__APPLE__`、
26+
`_WIN32`),系统种类名小写(`__linux__`、`__unix__`)——这些命名的是目标种类,在真实守卫里
27+
与第二族并排。`__mcpp_` 前缀承重:**一个 mcpp 拥有的名字,语义由 mcpp 自己定。**
28+
`__openkal__` 收进同一份契约;`__unix__` 列在表里但标注「供给而非拥有」,保持标准拼法。
29+
1030
### mcpp 为「这个目标是 Windows」给出自己的名字
1131

1232
`presents = "posix"` 在 Windows 上实现成 Cygwin 形状的目标,有意压掉 `_WIN32`——那正是

docs/21-the-target-triple.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,81 @@ produced rows* and *every row the expected table names was reached*, rather than
611611
a total: a cell that vanishes because a payload was not restored is otherwise
612612
indistinguishable from a cell that passed.
613613

614+
## The macros mcpp defines
615+
616+
`src/toolchain/predefines.cppm` is the specification and the implementation of
617+
this table at once: the contract is data in that module, the emission is a
618+
function beside it, and `tests/unit/test_predefines.cpp` asserts the two agree
619+
in both directions. A macro emitted and unlisted, or listed and never emitted,
620+
fails a build rather than drifting into a release.
621+
622+
| macro | when | owned |
623+
|---|---|---|
624+
| `__mcpp_target_<os>__` | one per build, spelt from the triple's `os` field | yes |
625+
| `__openkal__` | the resolved `kernel-abi` layer's interface name is `openkal` | yes |
626+
| `__unix__` | the `[c-abi]` realisation supplies it where the toolchain would not | no |
627+
628+
**An engine should not define macros, and each row has to justify itself.** A
629+
package states what it needs in its manifest and the engine answers by
630+
RESOLUTION — `cfg(os = "windows")`, `cfg(c-abi = "musl")`, a capability, a
631+
feature. That path is testable, reportable, and visible to a reader of the
632+
manifest; a macro is none of those. Two justifications have survived:
633+
634+
1. **The source is not ours to edit** and asks in the preprocessor. Upstream C
635+
selects platform behaviour with `#if`, and no manifest key reaches inside a
636+
third-party `.c`.
637+
2. **The reader is an installed header.** A package's own build defines can be
638+
spelled in its manifest, but a header it INSTALLS is read by an
639+
application's own compile, which those defines never reach.
640+
641+
**`__mcpp_target_<os>__` — the rule.** Defined for every target-side
642+
translation unit, always, one per build. The spelling is the triple's own `os`
643+
field, so `x86_64-linux-gnu` gives `__mcpp_target_linux__`, `x86_64-windows-gnu`
644+
gives `__mcpp_target_windows__`, and `riscv64-none-elf` gives
645+
`__mcpp_target_none__`. The engine learns no operating-system name: a target
646+
added to the triple parser gets its macro with no engine change.
647+
648+
*Allowed:* learning the target's operating system where the C environment
649+
presented above it has suppressed the platform's own macros, and sizing a
650+
record by the target's ABI. A Windows target presenting POSIX has no `_WIN32`
651+
on purpose — while the calling convention is still Win64, which is what
652+
`openkal-musl`'s `bits/setjmp.h` and `openkal-llvm-runtime`'s
653+
`__libunwind_config.h` each size a record by.
654+
655+
*Forbidden:* selecting a header the manifest could select, or standing in for
656+
`cfg(os = …)` in a package this project controls.
657+
658+
**It is defined always, not only where something is suppressed.** Conditional
659+
emission would make its absence ambiguous: "not Windows" and "Windows, but
660+
nothing hid its macros" would read the same. A macro whose absence means one
661+
thing is worth one `-D`.
662+
663+
**Naming.** `__mcpp_`-prefixed, `__`-suffixed, lowercase, words separated by
664+
`_`. Two conventions exist in the wild — vendor and product names are upper
665+
(`__APPLE__`, `_WIN32`, `__MINGW32__`), kind-of-system names are lower
666+
(`__linux__`, `__unix__`) — and these name kinds of target, sitting beside
667+
that second family in real guards: `#if defined(__linux__) ||
668+
defined(__mcpp_target_windows__)`.
669+
670+
The `__mcpp_` prefix is load-bearing. A name mcpp owns means what mcpp says it
671+
means. The alternative was tried: `__CYGWIN__` was left defined so that code
672+
needing "PE object format, POSIX C environment" would have a name, and a
673+
30-member measurement found four members reading it as *Win32 is available*
674+
and reaching `#include <windows.h>` — which is what upstream means by it. **A
675+
borrowed name means what the lender's history made it mean**, not what the
676+
borrower intended.
677+
678+
`__unix__` is the exception that proves the rule: mcpp SUPPLIES it rather than
679+
owning it, so it keeps the standard spelling and mcpp may not change its
680+
meaning. Read it exactly as on any other POSIX system.
681+
682+
**Stability.** An entry here is a published interface. Removing one, or
683+
narrowing when it is defined, is a breaking change for source this project
684+
does not control, and the failure is usually SILENT — a `#if` selects the
685+
other branch and compiles. Withdrawal is therefore a sequence, never an edit:
686+
add the replacement, let consumers move onto it while still accepting the old
687+
name, and only then stop defining it.
688+
614689
## Custom Targets
615690

616691

docs/24-openkal-cross.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,15 @@ combination, "PE format with a POSIX-presenting C environment," is
5757
`__CYGWIN__`/`__CYGWIN32__`, which the Cygwin-flavoured realisation
5858
therefore leaves DEFINED rather than folding into the three rows above —
5959
see [22's own note](22-target-side.md#the-c-environment-a-c-abi-package-presents-mcpp-2026918)
60-
for the full trade-off (it is not a settled fact, and may flip). Reading it
61-
as a fourth C-environment macro, rather than as what it actually is — an
62-
object-format fact `__CYGWIN__` happens to be the only name for — is
63-
exactly the shape of confusion this whole section exists to head off.
60+
for the full trade-off. Since 2026.9.21.1 `__CYGWIN__` is no longer the only
61+
name for that fact: mcpp defines `__mcpp_target_<os>__` for every target
62+
(`docs/21`, "The macros mcpp defines"), so source needing to know the target
63+
under a presented environment has a name mcpp owns. `__CYGWIN__` remains
64+
defined while the ecosystem's installed headers move onto the new name, and
65+
is then withdrawn. Reading either as a fourth C-environment macro, rather
66+
than as what they are — a fact about the TARGET, not about the environment
67+
presented above it — is exactly the shape of confusion this whole section
68+
exists to head off.
6469

6570
**`__openkal__` — the rule.** The engine defines it, for every target-side
6671
unit, whenever the resolved `kernel-abi` layer's interface name is

docs/zh/21-the-target-triple.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,62 @@ x86_64 宿主行得通的路线,而平台模拟器会直接拒绝异构 guest
545545
行都被跑到**,而不是一个总数:一格因为载荷没被恢复而消失,与一格通过了,在退出码
546546
上没有区别。
547547

548+
## mcpp 定义的宏
549+
550+
`src/toolchain/predefines.cppm` 同时是这张表的规范与实现:契约是那个模块里的数据,
551+
发出是它旁边的函数,而 `tests/unit/test_predefines.cpp` 双向断言两者一致。发出去却不在
552+
表里、或在表里却没人发,都会让构建变红,而不是漂进一次发布。
553+
554+
|| 何时定义 | 是否 mcpp 拥有 |
555+
|---|---|---|
556+
| `__mcpp_target_<os>__` | 每次构建一个,拼法取自三元组的 `os` 字段 ||
557+
| `__openkal__` | 解析出的 `kernel-abi` 层接口名是 `openkal` ||
558+
| `__unix__` | `[c-abi]` 实现供给它,在工具链不会定义它的地方 ||
559+
560+
**引擎原则上不该定义任何宏,每一行都要为自己辩护。** 包在清单里陈述需要什么,引擎以
561+
**解析**回答——`cfg(os = "windows")``cfg(c-abi = "musl")`、能力、feature。那条路可测、
562+
可报告、读清单就能看见;宏三样都不是。只有两条理由通过了:
563+
564+
1. **源码不归我们改**,而它在预处理期发问。上游 C 用 `#if` 选平台行为,任何清单键都伸不进
565+
第三方的 `.c` 里。
566+
2. **读者是已安装的头。** 包自己的构建 define 可以写在清单里,但它**安装出去**的头会被
567+
**应用程序自己的编译**读到,那些 define 永远到不了那里。
568+
569+
**`__mcpp_target_<os>__` 的规则。** 为每一个目标侧翻译单元定义,总是定义,每次构建一个。
570+
拼法取自三元组自己的 `os` 字段:`x86_64-linux-gnu` 给出 `__mcpp_target_linux__`,
571+
`x86_64-windows-gnu` 给出 `__mcpp_target_windows__`,`riscv64-none-elf` 给出
572+
`__mcpp_target_none__`。引擎不认识任何操作系统名:三元组解析器新增一个目标,它的宏随之
573+
存在,引擎无需改动。
574+
575+
*允许:* 当上面呈现的 C 环境压掉了平台自己的宏时,用它得知目标的操作系统;以及按目标的
576+
ABI 给记录定尺寸。呈现 POSIX 的 Windows 目标**有意**没有 `_WIN32`,而调用约定仍是 Win64
577+
——那正是 `openkal-musl``bits/setjmp.h``openkal-llvm-runtime`
578+
`__libunwind_config.h` 各自据以定尺寸的事实。
579+
580+
*禁止:* 选一个清单本可以选的头;或在本项目控制得了的包里用它代替 `cfg(os = …)`
581+
582+
**它总是被定义,不只在有东西被压掉时。** 条件式发出会让它的**缺席**含义不唯一:
583+
「不是 Windows」与「是 Windows,但没有东西压掉它的宏」会读成同一件事。一个缺席只有一种
584+
含义的宏,值一个 `-D`
585+
586+
**命名。** `__mcpp_` 前缀、`__` 后缀、小写、单词用 `_` 分隔。业界并存两套约定——厂商与
587+
产品名用大写(`__APPLE__``_WIN32``__MINGW32__`),系统种类名用小写(`__linux__`
588+
`__unix__`)——而这些命名的是目标的种类,在真实守卫里与第二族并排出现:
589+
`#if defined(__linux__) || defined(__mcpp_target_windows__)`
590+
591+
`__mcpp_` 这个前缀是承重的。**一个 mcpp 拥有的名字,语义由 mcpp 自己定。** 另一条路试过:
592+
`__CYGWIN__` 曾被保留定义,为的是让需要「PE 目标文件格式 + POSIX C 环境」的代码有个名字,
593+
而 30 成员测量发现四个成员把它读成「**Win32 可用**」并走向 `#include <windows.h>`——那正是
594+
上游用它表达的意思。**一个借来的名字,语义由借出方的历史决定**,不由借用方的意图决定。
595+
596+
`__unix__` 是印证这条规则的例外:mcpp **供给**它而不拥有它,所以它保持标准拼法,mcpp 也
597+
不得改变它的含义。读它就按在任何别的 POSIX 系统上那样读。
598+
599+
**稳定性。** 这张表里的一条是**已发布的接口**。删掉一条、或收窄它被定义的条件,对本项目
600+
控制不了的源码就是破坏性变更,而失败通常是**静默**的——一个 `#if` 选了另一条分支,照常
601+
编译。所以撤销永远是一个**序列**,不是一次编辑:先加替代品,让消费者迁移过去并仍接受旧
602+
名字,之后才停止定义它。
603+
548604
## 自定义目标
549605

550606

docs/zh/24-openkal-cross.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@ C++ 运行时 —— 成为一组由依赖图解析、并由当前运行的编
4545
代码里,给这个特定组合——「PE 格式 + 呈现 POSIX 的 C 环境」——起名字的唯一办法,是
4646
`__CYGWIN__`/`__CYGWIN32__`,所以 Cygwin 式的实现让它们保持**定义**,而不是把它折进上面
4747
三行里的任何一行——完整的权衡说明见
48-
[22 自己的说明](22-target-side.md#c-abi-包陈述它呈现的-c-环境mcpp-2026918)(不是已经定论
49-
的事实,结论可能翻过来)。把它当成第四个 C 环境宏来读,而不是它实际是的样子——一个
50-
`__CYGWIN__` 恰好是唯一名字的目标文件格式事实——正是这一整节想要提前避免的那种混淆。
48+
[22 自己的说明](22-target-side.md#c-abi-包陈述它呈现的-c-环境mcpp-2026918)。自
49+
2026.9.21.1 起,`__CYGWIN__` 不再是那个事实的唯一名字:mcpp 为每个目标定义
50+
`__mcpp_target_<os>__`(见 `docs/21`「mcpp 定义的宏」),于是需要在被呈现的环境之下得知
51+
目标的源码,有了一个 mcpp 自己拥有的名字。`__CYGWIN__` 在生态的已安装头迁移到新名字期间
52+
仍然定义着,之后才撤销。把两者中的任何一个当成第四个 C 环境宏来读,而不是它们实际是的
53+
样子——一个关于**目标**的事实,而不是关于其上呈现的环境——正是这一整节想要提前避免的
54+
那种混淆。
5155

5256
**`__openkal__` 的规则。** 只要解析出的 `kernel-abi` 层的接口名是 `openkal`,引擎就为目标侧的
5357
每一个编译单元定义它——取自层的取值,绝不取自包名,因此第二个 `mcpp:kernel-abi=openkal`

src/build/prepare.cppm

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import mcpp.toolchain.clang;
3535
import mcpp.toolchain.hostflags; // the compile-token producer the package std module reuses
3636
import mcpp.toolchain.cenv; // [c-abi] declaration → compiler configuration (design 2026-09-18)
3737
import mcpp.toolchain.cenv_probe; // [c-abi] declaration is checked, not trusted (design §3.2)
38+
import mcpp.toolchain.predefines; // the macros this engine defines: contract and emission in one module
3839
import mcpp.toolchain.cppfly;
3940
import mcpp.toolchain.detect;
4041
import mcpp.toolchain.dialect;
@@ -11146,14 +11147,36 @@ prepare_build(bool print_fingerprint,
1114611147
// `-fno-builtin-memset_pattern16`) and none is rejected — so nothing
1114711148
// here is filtered a second time; if a future token IS GAS-hostile,
1114811149
// `cenv::realise` is where to split it, not this broadcast.
11150+
// THE MACROS THIS ENGINE DEFINES --- the contract, the rules for
11151+
// reading them and the reason each one exists rather than a manifest
11152+
// key, are `mcpp.toolchain.predefines`. That module is the
11153+
// specification and the implementation of the same thing, so this
11154+
// site decides only WHERE the tokens go, never WHICH they are.
11155+
if (tc) {
11156+
std::string targetOs;
11157+
if (auto ttOs = mcpp::toolchain::triple::parse(tc->targetTriple))
11158+
targetOs = ttOs->os;
11159+
const auto engineDefines =
11160+
mcpp::toolchain::predefines::define_tokens(
11161+
targetOs, tc->kernelAbiIsOpenkal);
11162+
// Into `cflags`/`cxxflags` only: these are all `-D`, and the
11163+
// channel that builds an assembly unit's flags keeps the -D/-U/-I
11164+
// words of those two (`compile_commands::unit_asm_flags`), so a
11165+
// second copy here would put each one on a `.S` line twice.
11166+
for (auto& p : packages) {
11167+
appendUniqueFlags(p.privateBuild.cflags, engineDefines);
11168+
appendUniqueFlags(p.privateBuild.cxxflags, engineDefines);
11169+
}
11170+
}
11171+
1114911172
if (tc && (tc->kernelAbiIsOpenkal || !tc->cEnvTokens.empty()
1115011173
|| !tc->cEnvBuiltinsTokens.empty())) {
11151-
static const std::vector<std::string> kOpenkalDefine = {"-D__openkal__"};
1115211174
for (auto& p : packages) {
11153-
if (tc->kernelAbiIsOpenkal) {
11154-
appendUniqueFlags(p.privateBuild.cflags, kOpenkalDefine);
11155-
appendUniqueFlags(p.privateBuild.cxxflags, kOpenkalDefine);
11156-
}
11175+
// `__openkal__` is emitted above, with the rest of the
11176+
// engine's own defines; it is NOT subject to the
11177+
// `c-environment = "platform"` exception below, because that
11178+
// exception is about which C environment a package's headers
11179+
// see, not about whether its code may call `kal_*`.
1115711180
if (p.manifest.cEnvironment == "platform") continue;
1115811181
appendUniqueFlags(p.privateBuild.cflags, tc->cEnvTokens);
1115911182
appendUniqueFlags(p.privateBuild.cxxflags, tc->cEnvTokens);

0 commit comments

Comments
 (0)