Skip to content

Commit 358f4e2

Browse files
committed
cross-verification stopped the withdrawal, so mcpp names the target itself
The first shape of this change simply withdrew `__CYGWIN__`. It was green here and green in four of five ecosystem repositories built against this branch; openkal-llvm-runtime failed, libunwind's `static_assert` reporting that `Registers_x86_64` does not fit `unw_context_t`. TWO INSTALLED HEADERS IN THIS ECOSYSTEM READ THAT MACRO ON PURPOSE, each saying so in its own source: `openkal-musl`'s `bits/setjmp.h` sizes `jmp_buf` by it, and `openkal-llvm-runtime`'s `__libunwind_config.h` sizes `unw_context_t`. Both are INSTALLED --- an application's own compile reads them --- so neither can use the package-private define its sibling `.S` files use, and `__CYGWIN__` was the only name mcpp kept defined target-wide. The 30-member measurement that justified withdrawal had counted THIRD-PARTY readers of the macro and not ours. Ours are load-bearing, and wrong is silent where it matters most: `setjmp.h`'s own comment says "a mismatch nothing reports until the record overruns". libunwind having a `static_assert` is what made this loud, not anything the measurement did. So mcpp states the fact itself. `-D__mcpp_target_windows__=1` answers the question those headers ask --- is this target Windows, whatever C environment is presented above it --- and being mcpp's own name, its meaning is not decided by anyone else's history. It is emitted only under this substitution; an ordinary Windows build still has `_WIN64`. `expectDefined` carries it, so a `-D` that failed to take effect is a verification failure rather than a silent one. The name differs from the design's proposed `__mcpp_format_pe__` because the two consumers do not want the object format: they size Win64 register save areas, which is the calling convention. The two co-vary on this target, and naming it for the question actually asked is the honest form. `__CYGWIN__` REMAINS DEFINED, as step one of three: this release adds the new name, those packages move onto it while still accepting the old one, and only then does a release stop defining the borrowed one. Step three taken first would leave every published copy of those headers falling to its `#else` --- the wrong record size, reported by nothing. Four loudly failing third-party members is the better state to hold for one release.
1 parent e6a3a0a commit 358f4e2

7 files changed

Lines changed: 238 additions & 170 deletions

File tree

.agents/docs/2026-09-21-openkal-ecosystem-completion-and-acceptance.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ status: active
6161

6262
#### E1 — P3:撤掉 `__CYGWIN__` 借用
6363

64-
**状态****已实现**(mcpp 2026.9.21.1)。下文保留论证;落地形态见本节末。
64+
**状态****分三步,本版落地第一步**(mcpp 2026.9.21.1)。下文保留论证;落地形态与
65+
被交叉验证挡下的那一版见本节末。
6566

6667
保留 `__CYGWIN__` 的本意是给「PE 格式 + POSIX C 环境」一个名字。实证否定了这个用法:
6768

@@ -99,23 +100,38 @@ status: active
99100
`#if defined(_WIN32) && !defined(__CYGWIN__)`,撤销会**翻转**它的分支。基线已有,重测
100101
即可读出净值。
101102
102-
**落地形态(2026.9.21.1)**:`cenv.cppm` 的 Windows+Posix 分支加两个 `-U` token,
103-
`expectUndefined` 加两项——**后者才是承重的**:探针会把实现出的配置的预定义与这两张表
104-
比对,一个没生效的 `-U` 是一次校验失败,不是一次沉默。
103+
**先做的那一版被跨仓库交叉验证挡下了,这条记录比结论更值钱。**
105104
106-
直接量过一次,用钉住的 clang、按 mcpp 实际发出的 token 顺序:
105+
第一次实现就是直接撤:加 `-U__CYGWIN__ -U__CYGWIN32__`,`expectUndefined` 加两项。本机
106+
全绿,五个生态仓库里四个也绿——**openkal-llvm-runtime 红了**,libunwind 的
107+
`static_assert` 失败:`Registers_x86_64` 装不进 `unw_context_t`。
107108
108-
```
109-
echo | clang -dM -E -x c - -U__CYGWIN__ -U__CYGWIN32__ \
110-
--target=x86_64-pc-cygwin -U__CYGWIN__ -U__CYGWIN32__
111-
-> __unix__ 定义;__CYGWIN__ 消失;_WIN32 仍不存在
112-
```
109+
根因是生态里有**两个已安装的公开头**有意读 `__CYGWIN__`,理由写在各自源码里:
110+
111+
| 包 | 头文件 | 它定尺寸的记录 |
112+
| --- | --- | --- |
113+
| openkal-musl | `port/include/bits/setjmp.h` | `jmp_buf` |
114+
| openkal-llvm-runtime | `__libunwind_config.h` | `unw_context_t`、`unw_cursor_t` |
115+
116+
两者都写明:这是**已安装**的头,会被**应用程序自己的编译**读到,所以用不了包私有的
117+
`OKM_TARGET_WINDOWS` / `OPENKAL_TARGET_WINDOWS`;而 `__CYGWIN__` 是 mcpp **target-wide**
118+
保持定义的唯一名字。
119+
120+
**那个「四比零」数的是第三方读者,没数我们自己的。** 而自己这两个是承重的,且错了是
121+
**静默**的——`setjmp.h` 自己的注释写着「a mismatch nothing reports until the record
122+
overruns」。libunwind 那处有 `static_assert` 才响,属于运气。
123+
124+
**落地形态(第一步)**:`cenv.cppm` 的 Windows+Posix 分支加 `-D__mcpp_target_windows__=1`,
125+
`expectDefined` 加该项与 `__CYGWIN__`——探针核对,一个没生效的 `-D` 是校验失败不是沉默。
126+
实测:三个通道(`.c`/`.cpp`/`.S`)全部到达;预定义为 `__unix__` + `__CYGWIN__` +
127+
`__mcpp_target_windows__`,`_WIN32` 仍不存在。
128+
129+
**为什么名字是 `__mcpp_target_windows__` 而不是设计里写的 `__mcpp_format_pe__`**:两个
130+
消费者要的不是「目标文件格式」,是**调用约定**(Win64 的寄存器保存区大小)。在这个目标上
131+
两者同变,但按消费者实际问的那个问题命名更诚实。
113132
114-
**`.S` 行上这对 token 出现两次**,`.c`/`.cpp` 各一次。`cEnvTokens` 会被加进包的
115-
asmflags,而形如 `-D`/`-U`/`-I` 的 token 同时经由「把 define 带进汇编」的通道到达;
116-
`--target=` 与 `-fno-short-wchar` 不是那个形状,所以只出现一次。`-U X` 两次等于一次,
117-
且真正的判据是探针,所以 e2e 不对次数作断言——那会把 flag 管线的实现细节钉死,而不是
118-
钉住被测性质。
133+
**第二、三步**:两个包改读新名字并保留 `|| defined(__CYGWIN__)`(在两种引擎上都能构建);
134+
之后的版本再停止定义借来的那个。**先做第三步会让已发布的那两个头静默落进 `#else`。**
119135
120136
#### E2 — P7-L3:链接期集合差
121137

CHANGELOG.md

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,30 @@
77

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

10-
### 撤掉借来的 `__CYGWIN__`,由一次测量裁决
11-
12-
`presents = "posix"` 在 Windows 上实现成 Cygwin 形状的目标。上一版**保留**
13-
`__CYGWIN__`/`__CYGWIN32__` 定义,理由是:需要知道**目标文件格式**的第三方可移植代码,
14-
没有别的名字能指代「PE 格式 + 呈现 POSIX 的 C 环境」。那一版同时写下了自己的翻转条件
15-
——**留给 30 成员测量裁决;若定义它带来的失败比修好的多,结论就翻过来。**
16-
17-
测量在 2026-09-20 给出读数,60 个「成员 × 目标」组合:**保留它代价是四个成员**——
18-
`archive`、`sqlite3`、`mimalloc`、`c-ares`,各自停在 `#include <windows.h>`,都经由形如
19-
`#if defined(_WIN32) || defined(__CYGWIN__)` 的守卫到达。**同一轮里没有任何一项因为缺少
20-
这个宏而失败。** 四比零。
21-
22-
上游自己说明了这个名字的含义。mimalloc 把话写在守卫的注释里:
23-
`we use windows locks on cygwin, but otherwise treat it at unix`;sqlite3 把它列进
24-
`SQLITE_OS_WIN` 的检测集合,随后 `#include "windows.h"`。**它们用这个名字表达的是
25-
「Win32 可用」,不是「目标文件格式是 PE」。一个借来的名字,语义由借出方的历史决定,
26-
不由借用方的意图决定。**
27-
28-
于是「目标文件格式」这一维**不保留任何宏**:包问 `cfg(os = "windows")`,那不需要宏。
29-
若某天确实发现只能在预处理期问这件事的第三方代码,答案是 mcpp 定义一个自己的名字。
30-
31-
承重的不是那两个 `-U` token,是 `expectUndefined` 里新增的两项:探针把实现出的配置的
32-
预定义与它比对,**一个没生效的 `-U` 是一次校验失败,不是一次沉默**。
33-
34-
实测(钉住的 clang,按 mcpp 实际发出的 token 顺序):
35-
36-
```
37-
echo | clang -dM -E -x c - -U__CYGWIN__ -U__CYGWIN32__ \
38-
--target=x86_64-pc-cygwin -U__CYGWIN__ -U__CYGWIN32__
39-
-> __unix__ 定义;__CYGWIN__ 消失;_WIN32 仍不存在
40-
```
41-
42-
单测与 e2e 741 都改成断言相反的一侧,注释里记着这个模块两种答案都持有过、以及是什么
43-
把它翻过来的。
10+
### mcpp 为「这个目标是 Windows」给出自己的名字
11+
12+
`presents = "posix"` 在 Windows 上实现成 Cygwin 形状的目标,有意压掉 `_WIN32`——那正是
13+
「呈现 POSIX」的含义。但 **ABI 并没有跟着环境一起变**:调用约定仍是 Win64,寄存器保存区
14+
仍按它的大小。生态里有两个**已安装的**头按这个事实定尺寸——openkal-musl 的
15+
`bits/setjmp.h` 定 `jmp_buf`,openkal-llvm-runtime 的 `__libunwind_config.h` 定
16+
`unw_context_t`。已安装的头会被**应用程序自己的编译**读到,所以两者都用不了包私有的 define。
17+
18+
本版发 `-D__mcpp_target_windows__`。它是 mcpp 自己的名字,语义由 mcpp 自己定;只在这次
19+
替换下发出,普通 Windows 构建仍有 `_WIN64`。
20+
21+
**`__CYGWIN__` 仍然定义着,这是次序不是结论。** 30 成员测量已判定这个借来的名字代价是
22+
四个成员(`archive`、`sqlite3`、`mimalloc`、`c-ares` 各自停在 `#include <windows.h>`,
23+
经由 `#if defined(_WIN32) || defined(__CYGWIN__)`);上游用它表达「Win32 可用」,
24+
mimalloc 把这句话写在守卫自己的注释里。**一个借来的名字,语义由借出方的历史决定。**
25+
26+
**撤掉它试过了,被跨仓库交叉验证挡下。** 上面那两个头正是因为没有别的 target-wide 名字
27+
才读它;撤掉后 libunwind 的 `static_assert` 响亮地红了,而 `setjmp.h` 那一处**不会**响——
28+
它自己的注释写着「a mismatch nothing reports until the record overruns」。那次测量数的是
29+
**第三方**读者,没数我们自己的。
30+
31+
于是撤销分三步,每个中间状态都能构建:本版**增加**新名字;两个包改读新名字并保留
32+
`|| defined(__CYGWIN__)`;之后的版本再停止定义借来的那个。先做第三步会让**已发布**的那
33+
两个头全部落进 `#else`——错误的记录尺寸,没有任何东西报告。
4434

4535
## [2026.9.20.1] - 2026-09-20
4636

docs/22-target-side.md

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ that names no C library:
377377
| Linux | `posix` / `arch-default` | the default triple already satisfies it |
378378
| macOS | `posix` / `arch-default` | one token, `-D__unix__` — Apple's clang predefines `__APPLE__`/`__MACH__` on its default triple, never `__unix__` |
379379
| freestanding | `posix` / `arch-default` | the same one token, `-D__unix__`, for the same reason: nothing here defines it either |
380-
| Windows | `posix` / `arch-default` | Cygwin-flavoured: `--target=x86_64-pc-cygwin` on the compile line only; `__CYGWIN__`/`__CYGWIN32__` are undefined on top of it (see the note below); `data-model` becomes LP64 as a consequence of the triple, not a separate flag |
380+
| Windows | `posix` / `arch-default` | Cygwin-flavoured: `--target=x86_64-pc-cygwin` on the compile line only; plus `-D__mcpp_target_windows__` (see the note below); `data-model` becomes LP64 as a consequence of the triple, not a separate flag |
381381
| 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 |
382382
| anything else | | refused, naming the target, the request and what is missing — never a silent downgrade |
383383

@@ -418,31 +418,48 @@ preprocessor sees and how wide `long` is. Realisation therefore touches only
418418
the **compile** line; the **link** line keeps the triple the graph resolved,
419419
because nothing about the object format changed.
420420

421-
**`__CYGWIN__`/`__CYGWIN32__` are undefined, and a measurement decided it
422-
(2026.9.21.1).** This page has carried both answers. A middle revision left
423-
them defined, so that portable third-party code needing to know the **object
424-
format** — not the C environment, not the platform API — would keep a name
425-
for "PE format with a POSIX-presenting C environment". That revision wrote
426-
its own condition for reversal: a trade-off for the 30-member measurement to
427-
settle, flipping if defining them produced more failures than it fixed.
428-
429-
It produced four and fixed none. Across 60 member-target combinations,
430-
`archive`, `sqlite3`, `mimalloc` and `c-ares` each stopped at
431-
`#include <windows.h>`, reached through a guard of the shape
432-
`#if defined(_WIN32) || defined(__CYGWIN__)`. Nothing in the same run failed
433-
for want of the macro.
434-
435-
**A borrowed name means what the lender's history made it mean.** Upstream
436-
uses this one to say *Win32 is available*, not *the object format is PE*
437-
mimalloc states it in the guard itself (`we use windows locks on cygwin, but
438-
otherwise treat it at unix`), and sqlite3 lists it in the `SQLITE_OS_WIN`
439-
detection set before including `windows.h`. The intent behind borrowing it
440-
does not travel with it.
441-
442-
The object-format question therefore keeps **no macro at all**. A package
443-
asks `cfg(os = "windows")`, which needs none. Should a third party ever be
444-
found that can only ask in the preprocessor, the answer is a name mcpp
445-
defines itself, not one it borrows.
421+
**`__mcpp_target_windows__` is defined here, and `__CYGWIN__` still is too
422+
(2026.9.21.1).**
423+
424+
The substitution suppresses `_WIN32` on purpose — that is what presenting
425+
POSIX means. But the **ABI did not change with the environment**: the calling
426+
convention is still Win64 and the register save areas are still its. Two
427+
INSTALLED headers in this ecosystem size records by that fact:
428+
429+
| package | header | sizes |
430+
|---|---|---|
431+
| openkal-musl | `port/include/bits/setjmp.h` | `jmp_buf` |
432+
| openkal-llvm-runtime | `__libunwind_config.h` | `unw_context_t`, `unw_cursor_t` |
433+
434+
An installed header is read by an **application's own compile**, so neither
435+
can use a package-private define. `__mcpp_target_windows__` is mcpp's own
436+
name for the question they ask — is this target Windows, whatever C
437+
environment is presented above it — and being mcpp's own, its meaning is not
438+
decided by anyone else's history. It is emitted only under this substitution;
439+
an ordinary Windows build still has `_WIN64`.
440+
441+
**`__CYGWIN__` is still defined, and that is a sequence rather than a
442+
decision to keep it.** The 30-member measurement settled that the borrowed
443+
name costs four members: `archive`, `sqlite3`, `mimalloc` and `c-ares` each
444+
stop at `#include <windows.h>`, reached through
445+
`#if defined(_WIN32) || defined(__CYGWIN__)`. Upstream means *Win32 is
446+
available* by it — mimalloc states so in the guard itself, sqlite3 lists it
447+
under `SQLITE_OS_WIN`. **A borrowed name means what the lender's history made
448+
it mean**, not what the borrower intended.
449+
450+
Withdrawing it was tried and broke the two headers above, which read it for
451+
want of any other target-wide name. libunwind's `static_assert` failed
452+
loudly; `setjmp.h`'s equivalent would not have — its own comment says *a
453+
mismatch nothing reports until the record overruns*. So the withdrawal is
454+
three steps, and each intermediate state builds:
455+
456+
1. this release defines `__mcpp_target_windows__` as well — purely additive;
457+
2. those packages read the new name, keeping `|| defined(__CYGWIN__)` so they
458+
build on both engines;
459+
3. a later release stops defining the borrowed one.
460+
461+
Taking step three first would leave every **published** copy of those headers
462+
falling to its `#else`: the wrong record size, reported by nothing.
446463

447464
**A `kernel-abi` provider's own units are INFERRED onto the platform boundary
448465
— it never has to say so (mcpp 2026.9.18+, a mid-PR revision from the

docs/zh/22-target-side.md

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ libunwind 的 `assembly.h`,正是按这个宏来选寄存器保存集的)。mcpp
309309
| Linux | `posix` / `arch-default` | 默认三元组已经满足 |
310310
| macOS | `posix` / `arch-default` | 一个令牌,`-D__unix__`——Apple 的 clang 默认三元组预定义的是 `__APPLE__`/`__MACH__`,从来不是 `__unix__` |
311311
| 裸机(freestanding) | `posix` / `arch-default` | 同样一个令牌,`-D__unix__`,原因相同:这里同样没有任何东西定义它 |
312-
| Windows | `posix` / `arch-default` | 采用 Cygwin 式语义:仅在编译行加 `--target=x86_64-pc-cygwin`;在其之上再加 `-U__CYGWIN__ -U__CYGWIN32__`(见下方说明);`data-model` 变为 LP64 是三元组切换的结果,不是另一个开关 |
312+
| Windows | `posix` / `arch-default` | 采用 Cygwin 式语义:仅在编译行加 `--target=x86_64-pc-cygwin`;并加 `-D__mcpp_target_windows__`(见下方说明);`data-model` 变为 LP64 是三元组切换的结果,不是另一个开关 |
313313
| 任意目标 | `builtins = "iso"` | 关闭代码生成阶段假定平台 C 库在场的惯用法识别——本轮实测到的唯一一例是 Apple 目标上的 `-fno-builtin-memset_pattern16`;`src/toolchain/cenv.cppm` 记录了还核实过哪些、结论是不适用 |
314314
| 其余情况 | | 明确拒绝,点名目标、请求与缺什么——不静默降级 |
315315

@@ -337,25 +337,37 @@ Windows 一行是旗舰情形:`x86_64-w64-windows-gnu` 与 `x86_64-pc-cygwin`
337337
同样的 PE 格式、同样的 Win64 调用约定、同样的 SEH——差别只在预处理器看到什么、`long` 有多宽。
338338
因此实现只触及**编译**行;**链接**行保持图解析出的三元组,因为目标文件格式没有变化。
339339

340-
**`__CYGWIN__`/`__CYGWIN32__` 取消定义,而这是一次实测裁决的(2026.9.21.1)。** 本页
341-
两种答案都写过。中间一版保持它们定义,为的是让需要知道**目标文件格式**——不是 C 环境,
342-
也不是平台 API——的第三方可移植代码,留住「PE 格式加呈现 POSIX 的 C 环境」这个组合的名字。
343-
那一版自己写下了翻转条件:留给 30 个成员那轮实测判定的权衡,若定义它带来的失败比修好的多,
344-
结论就翻过来。
345-
346-
它带来四个,修好零个。60 个「成员 × 目标」组合里,`archive``sqlite3``mimalloc`
347-
`c-ares` 各自停在 `#include <windows.h>`,经由形如
348-
`#if defined(_WIN32) || defined(__CYGWIN__)` 的守卫到达。同一轮里没有任何一项因为**缺少**
349-
这个宏而失败。
350-
351-
**一个借来的名字,它的语义由借出方的历史决定。** 上游用它表达的是「**Win32 可用**」,
352-
不是「目标文件格式是 PE」——mimalloc 把这句话写在守卫自己的注释里(`we use windows locks
353-
on cygwin, but otherwise treat it at unix`),sqlite3 把它列进 `SQLITE_OS_WIN` 的检测集合
354-
随后 `#include "windows.h"`。借用时的意图不会跟着名字一起走。
355-
356-
于是「目标文件格式」这个问题**不保留任何宏**。包问 `cfg(os = "windows")`,那不需要宏。
357-
若某天确实发现只能在预处理期问这件事的第三方代码,答案是 mcpp 定义一个**自己的**名字,
358-
而不是继续借别人的。
340+
**这里定义 `__mcpp_target_windows__`,而 `__CYGWIN__` 仍然定义着(2026.9.21.1)。**
341+
342+
这次替换有意压掉 `_WIN32`——那正是「呈现 POSIX」的含义。但**ABI 并没有跟着环境一起变**:
343+
调用约定仍是 Win64,寄存器保存区仍按它的大小。生态里有两个**已安装的**头按这个事实定尺寸:
344+
345+
|| 头文件 | 它定尺寸的记录 |
346+
|---|---|---|
347+
| openkal-musl | `port/include/bits/setjmp.h` | `jmp_buf` |
348+
| openkal-llvm-runtime | `__libunwind_config.h` | `unw_context_t``unw_cursor_t` |
349+
350+
已安装的头会被**应用程序自己的编译**读到,所以两者都用不了包私有的 define。
351+
`__mcpp_target_windows__` 是 mcpp 为它们所问的那个问题给出的**自己的**名字——这个目标是不是
352+
Windows,无论上面呈现的是什么 C 环境——既然是自己的名字,它的语义就不由别人的历史决定。
353+
它只在这次替换下发出;普通的 Windows 构建仍然有 `_WIN64`
354+
355+
**`__CYGWIN__` 仍然定义着,这是一个次序问题,不是决定留下它。** 30 成员测量已经判定这个
356+
借来的名字代价是四个成员:`archive``sqlite3``mimalloc``c-ares` 各自停在
357+
`#include <windows.h>`,经由 `#if defined(_WIN32) || defined(__CYGWIN__)` 到达。上游用它
358+
表达的是「**Win32 可用**」——mimalloc 把话写在守卫自己的注释里,sqlite3 把它列进
359+
`SQLITE_OS_WIN`**一个借来的名字,语义由借出方的历史决定**,不由借用方的意图决定。
360+
361+
撤掉它试过了,结果打断了上面那两个头——它们读它,正是因为没有别的 target-wide 名字可用。
362+
libunwind 的 `static_assert` 响亮地红了;`setjmp.h` 那一处不会——它自己的注释写着
363+
「a mismatch nothing reports until the record overruns」。所以撤销分三步,每个中间状态都能构建:
364+
365+
1. 本版**增加** `__mcpp_target_windows__`——纯增量;
366+
2. 那两个包改读新名字,保留 `|| defined(__CYGWIN__)` 以便在两种引擎上都能构建;
367+
3. 之后的版本再停止定义借来的那个。
368+
369+
先做第三步,会让**已发布**的那两个头全部落进各自的 `#else`:错误的记录尺寸,而没有任何
370+
东西报告。
359371

360372
**`kernel-abi` 提供者的自身单元被推导落到平台边界上——它不必自己说出来**
361373
(mcpp 2026.9.18+,PR 进行中根据 openkal-musl 尖峰实验做的修订)。提供

0 commit comments

Comments
 (0)