Skip to content

2026.9.21.1: the borrowed __CYGWIN__ is withdrawn, by measurement - #680

Merged
Sunrisepeak merged 5 commits into
mainfrom
a-borrowed-name-means-what-the-lender-made-it-mean
Sep 20, 2026
Merged

Sunrisepeak merged 5 commits into
mainfrom
a-borrowed-name-means-what-the-lender-made-it-mean

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

The change

presents = "posix" on Windows realises as a Cygwin-flavoured target. The previous release left __CYGWIN__/__CYGWIN32__ defined, so that portable third-party code needing to know the object format would keep a name for "PE format with a POSIX-presenting C environment" — and it wrote its own condition for reversal:

a trade-off for the 30-member measurement to settle — if defining it produces more new failures than it fixes, the answer flips

It produced four and fixed none

Across 60 member-target combinations, archive, sqlite3, mimalloc and c-ares each stopped at #include <windows.h>, reached through a guard of the shape #if defined(_WIN32) || defined(__CYGWIN__). Nothing in the same run failed for want of the macro.

Upstream states what it means by the name:

/* mimalloc/atomic.h */
#if defined(_WIN32) || defined(__CYGWIN__)  // we use windows locks on cygwin, but otherwise treat it at unix
/* sqlite3.c:15456 — the SQLITE_OS_WIN detection set, then #include "windows.h" */
#  if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \

A borrowed name means what the lender history made it mean, not what the borrower intended. The object-format question therefore keeps no macro at all — a package asks cfg(os = "windows"), which needs none.

What carries the change is not the two -U tokens

It is the two entries added to expectUndefined. cenv_probe::verify compares the realised configuration predefines against those lists and refuses on a mismatch, so a -U that failed to take effect is a verification failure rather than a silent one.

Measured directly with the pinned clang, in the order mcpp emits them:

echo | clang -dM -E -x c - -U__CYGWIN__ -U__CYGWIN32__ \
         --target=x86_64-pc-cygwin -U__CYGWIN__ -U__CYGWIN32__
  -> __unix__ defined, __CYGWIN__ absent, _WIN32 absent

The unit test and e2e 741 now assert the opposite side, and both record that this module has held both answers and what flipped it.

The pair appears twice on a .S line and once on .c/.cpp: cEnvTokens reaches asmflags directly, and -D/-U/-I-shaped tokens also arrive through the channel carrying defines into assembly. -U X twice is -U X, and the probe is the judge, so no count is asserted.

Verification

unit suite 123 passed, 0 failed
e2e 740, 741, 742, 743, 744 pass
e2e 01 (version cross-check) pass
predefines, pinned clang __CYGWIN__ absent, __unix__ present, _WIN32 absent

Also in this release (documents, not engine changes)

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

  • the nine items this wave did not implement, each with its criterion, owner and blocker
  • why lsp-mcpp-private is the acceptance vehicle: its platform surface is six constants, none a POSIX facility, and it makes no direct POSIX call — so any seventh platform branch is a named openkal gap
  • a cross-repository verification protocol: an engine PR is built by the ecosystem through MCPP_SOURCE_REF, and both sides must be green before it merges

That protocol exists because this very change needed a second release, a day after the first — a reading the thirty member graphs could have produced before the first one merged. mcpp-index#452 closes the mechanism gap that prevented it.

`presents = "posix"` on Windows realises as a Cygwin-flavoured target. The
previous release LEFT `__CYGWIN__`/`__CYGWIN32__` defined, so that portable
third-party code needing to know the OBJECT FORMAT would keep a name for "PE
format with a POSIX-presenting C environment", and it wrote its own condition
for reversal: a trade-off for the 30-member measurement to settle, flipping if
defining them produced more failures than it fixed.

IT PRODUCED FOUR AND FIXED NONE. Across 60 member-target combinations,
archive, sqlite3, mimalloc and c-ares each stopped at `#include <windows.h>`,
reached through a guard of the shape `#if defined(_WIN32) || defined(__CYGWIN__)`.
Nothing in the same run failed for want of the macro.

Upstream says what it means by the name. mimalloc puts it in the guard's own
comment --- `we use windows locks on cygwin, but otherwise treat it at unix`
--- and sqlite3 lists it in the `SQLITE_OS_WIN` detection set before including
`windows.h`. A BORROWED NAME MEANS WHAT THE LENDER'S HISTORY MADE IT MEAN, not
what the borrower intended by it.

The object-format question keeps no macro at all: a package asks
`cfg(os = "windows")`, which needs none. If a third party is ever found that
can only ask in the preprocessor, mcpp defines a name of its own.

WHAT CARRIES THE CHANGE IS NOT THE TWO `-U` TOKENS. It is the two entries added
to `expectUndefined`: `cenv_probe::verify` compares the realised
configuration's predefines against those lists and refuses on a mismatch, so a
`-U` that failed to take effect is a verification failure rather than a silent
one. Measured directly with the pinned clang, in the order mcpp emits them:

    echo | clang -dM -E -x c - -U__CYGWIN__ -U__CYGWIN32__ \
             --target=x86_64-pc-cygwin -U__CYGWIN__ -U__CYGWIN32__
      -> __unix__ defined, __CYGWIN__ absent, _WIN32 absent

The unit test and e2e 741 now assert the opposite side, and both record that
this module has held both answers and what flipped it. The pair appears twice
on a `.S` command line and once on `.c`/`.cpp`, because `cEnvTokens` reaches
asmflags directly and `-D`/`-U`/`-I`-shaped tokens also arrive through the
channel that carries defines into assembly; `-U X` twice is `-U X`, and the
probe is the judge, so no count is asserted.

Also in this release, as documents rather than engine changes:

  * `.agents/docs/2026-09-21-openkal-ecosystem-completion-and-acceptance.md`
    --- the nine items this wave did not implement, each with its criterion,
    its owner and what blocks it; why `lsp-mcpp-private` is the acceptance
    vehicle (its platform surface is six constants, none of them a POSIX
    facility, and it makes no direct POSIX call); and a cross-repository
    verification protocol: an engine PR is built by the ecosystem through
    `MCPP_SOURCE_REF` and both sides must be green BEFORE it merges. That
    protocol exists because this very change needed a second release --- a
    reading the thirty member graphs could have produced before the first one.
…or it

The plan listed `memset_pattern16` as an openkal-musl gap and the hard blocker
for macOS acceptance, on the strength of a report that the link still fails on
the new stack. The engine already does the thing that should prevent it:
`cenv.cppm` emits `-fno-builtin-memset_pattern16` for `builtins = "iso"` on
macOS and iOS, and openkal-musl declares exactly that.

AND THE FLAG REACHES THE PACKAGES NAMED. Measured by emitting a build database
for `aarch64-macos` with 2026.9.21.1: all 29 of zstd's translation units carry
it, including the one on the chain the report named.

Three explanations survive, none of them decidable here --- a Linux host
cannot compile for macOS, there being no SDK: the report predates the flag;
the synthesised idiom is `memset_pattern4`/`8`, for which the engine emits no
flag (clang's `-fno-builtin-` family is one flag per idiom) though the report
names `16`; or the flag does not reach LLVM's loop-idiom pass.

So the next step is a reading, not an implementation. `lsp-mcpp-private`'s
`aarch64-macos --profile release` leg answers it directly, and each surviving
explanation has its own follow-up. Adding the symbol to openkal-musl now would
be repairing a gap that may not exist.
…tself

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.
`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.
@Sunrisepeak

Copy link
Copy Markdown
Member Author

生态级 review(协议 §6.4)

合入门槛的第四项。五个问题逐条回答,每条都给判据而不是判断。

1. 这次改动改变了哪些包的命令行?

每一个目标侧包,多一个 -D__mcpp_target_<os>__=1。实测(emit build-database):

x86_64-linux-gnu    -D__mcpp_target_linux__=1
x86_64-windows-gnu  -D__mcpp_target_windows__=1
riscv64-none-elf    -D__mcpp_target_none__=1

这不构成额外的重建代价,而我一开始算错了。 量到旧版与本版指纹目录不同(495e0380cf20eb01)时,我差点把它归给这个宏。查下来:指纹的第 8 项就是 mcpp 版本fingerprint.cppm),cache_key.cppm 自己的注释也写着「the mcpp version is already part of this key, so every release invalidates it anyway」。每次发布本就使缓存失效,这个宏不增加任何代价。

2. 有没有包的清单因此需要改?

没有。改动是纯增量,不动任何清单键。

两个包应当迁移到新名字(openkal-muslbits/setjmp.hopenkal-llvm-runtime__libunwind_config.h),那是撤销序列的第二步,不是本版的前提。它们今天读 __CYGWIN__,而本版仍定义它。

3. 索引 min_mcpp 要不要动?

不要。没有描述符语法变化。231 个描述符在新旧两版引擎下都解析通过。

4. 有没有消费者被钉在旧版本上而看不到这次改动?

有,且按构造如此——精确钉是常态。但新宏只对选择去读它的包有意义,旧钉的包行为逐字节不变。

5. 生态 CI 的绿,是替换过工作树的绿吗?

是的,五个 openkal 仓库都用 MCPP_SOURCE_REF 从本分支现场构建引擎,并各自断言 PATH 上的 mcpp 就是它构建的那一个(日志里的 under review: 行)。

所以它们回答的是**「引擎与生态的代码合不合得上」,不回答「发布物到不到得了」**。后者由两件事回答,都不在本 PR 的门槛里而在发布之后:mcpp 自己的 openkal-cross(clone 默认分支、按索引解析,不做工作树替换),与沙箱里对已发布物的验证。

本 PR 自身的风险点

唯一的新行为是「总是多发一个 -D」。 它落在 cflags/cxxflags,不落 asmflags——因为构建汇编单元 flags 的通道已经保留这两者的 -D/-U/-I 词,再加一次会让每个 .S 行上出现两份。

被交叉验证挡下的那一版记录在案。 先做的实现是直接撤 __CYGWIN__;本机全绿、五个生态仓库四个绿,openkal-llvm-runtime 红——libunwind 的 static_assertRegisters_x86_64 装不进 unw_context_t。根因是那两个已安装的公开头有意读这个宏,而我的「四比零」只数了第三方读者。若无本协议,这会在发布后才被发现,且 setjmp.h 那一侧是静默的。

…rong

`aarch64-macos --profile release` over the openkal stack links, and the
artefact references no `memset_pattern` symbol at all. Measured here, on this
Linux host, with mcpp 2026.9.21.1 and llvm@22.1.8 through openkal-macos
0.12.0 / openkal-musl 0.18.0 / openkal-llvm-runtime 0.13.0:

    zstd 1.5.7   compiles, links; Mach-O 64-bit arm64, NOUNDEFS; 0 refs
    xz 5.8.3     compiles, links

Both are on the chain the report named. The engine has emitted
`-fno-builtin-memset_pattern16` for `builtins = "iso"` on macOS since before
this branch, and that report was taken on an engine that did not. Nothing is
implemented for C4, and nothing should be: adding the symbol to openkal-musl
would have repaired a gap that does not exist.

THE BELIEF THAT KEPT THIS UNMEASURED IS THE MORE USEFUL FINDING. Both this
plan and the previous wave's self-review stated that macOS facts need a macOS
runner, because a probe targeting `aarch64-macos` failed here. That probe
declared no openkal dependency, so it took the PLATFORM path, which does need
an Apple SDK. The openkal path does not --- universal cross-building is the
premise of the whole system, and a Linux host reaches `arm64-apple-macos14.0`
through it with every layer resolved from the graph.

The cost of that belief compounded: it filed C4 as unmeasurable, and it filed
openkal-macos's `provides-interfaces` as needing a runner when 0.12.0 derived
it mechanically. Both corrected, with the rule beside them --- a claim that
some platform cannot be measured locally has to be tested with a probe that
goes THROUGH the stack under test, not one that bypasses it.
@Sunrisepeak
Sunrisepeak merged commit 24711c7 into main Sep 20, 2026
40 of 42 checks passed
@Sunrisepeak
Sunrisepeak deleted the a-borrowed-name-means-what-the-lender-made-it-mean branch September 20, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants