Skip to content

2026.9.21.2: the macros mcpp owns are spelt in upper case, and the borrowed name is withdrawn - #681

Merged
Sunrisepeak merged 11 commits into
mainfrom
the-macros-mcpp-owns-are-spelt-in-upper-case
Sep 20, 2026
Merged

Sunrisepeak merged 11 commits into
mainfrom
the-macros-mcpp-owns-are-spelt-in-upper-case

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

__mcpp_target_<os>__ becomes __MCPP_TARGET_<OS>__ and __openkal__
becomes __OPENKAL__. The spelling is still the triple's own os field, now
upper-cased; the engine still learns no operating-system name.

THE CONVENTION SPLITS BY WHAT A NAME IS, NOT BY WHO WRITES IT. A vendor or
product name is upper --- __APPLE__, _WIN32, __MINGW32__, __GNUC__. A
kind-of-system name is lower --- __linux__, __unix__, __gnu_linux__.
Every row this engine OWNS is of the first kind: it names mcpp, or it names
openkal. The kind-of-system question is answered by __linux__ and its
family, which mcpp SUPPLIES rather than owns and which therefore keep their
lower-case spelling, for that exact reason.

The lower-case spelling shipped in 2026.9.21.1 on the reverse reading: that
these names sit beside __linux__ in real guards, so matching it was
consistency. THAT CONFUSES ADJACENCY WITH KIND. __APPLE__ sits in those
same guards and is upper, because it belongs to somebody.

__MCPP_ is a prefix, not the whole rule: __OPENKAL__ is owned and names
openkal rather than mcpp. The assertion in test_predefines.cpp therefore
checks the SPELLING CONVENTION --- upper case, __-wrapped --- instead of
the prefix, which would have judged __OPENKAL__ a violation when it is not.

WITHDRAWING A MACRO IS DECIDED BY A COUNT, NOT BY READING

An entry in this contract is a published interface, and withdrawing one is
SILENT: a #if selects the other branch and compiles. No mechanism available
to a build tool makes that loud. So a withdrawal is decided by a measurement
that ENUMERATES READERS, and what it finds sets how many steps the withdrawal
takes. The two this project has performed came out differently:

withdrawn                              readers found            steps
__CYGWIN__                             4 third-party members,   3
                                       + 6 sites in the two
                                       headers we INSTALL
__mcpp_target_<os>__, __openkal__      none                     1

The second row's denominator is every repository of this ecosystem, swept by
file type: no source file and no manifest reads either name, and the only
occurrences are this engine's emitter, its tests, and prose. Both names were
invented here, so no upstream code can hold one; exposure was three days for
__openkal__ and a single release for the target macro. The rule did not
change between the two rows. The count did.

A new assertion takes its denominator from the target registry rather than
from a list written beside the test: every canonical triple is parsed and its
emitted macro checked to be a valid identifier. The spelling comes from the
os field, so an os carrying a dot or a version suffix would produce a
macro no compiler accepts, and the failure would land in a user's build.

CYGWIN IS WITHDRAWN --- THE LAST STEP OF A THREE-REPOSITORY SEQUENCE

The Windows presents = "posix" realisation now adds -U__CYGWIN__ and
-U__CYGWIN32__ to the compile line. --target=x86_64-pc-cygwin stays: it
is what supplies __unix__ and suppresses _WIN32, which is what presenting
POSIX means. Only the borrowed NAME was unwanted.

  1. 2026.9.21.1 defined mcpp's own name beside the borrowed one; this
    release re-spells it, while it still has no consumer. Purely additive.
  2. openkal-musl@0.19.0 and openkal-llvm-runtime@0.14.0 read the new
    name and keep || defined(__CYGWIN__), so they build on an engine from
    either side of this change. PUBLISHED BEFORE THIS RELEASE.
  3. this release stops defining the borrowed name.

THE ORDERING IS MEASURED, NOT ASSERTED. It holds between repositories, so no
test in this one can check it. Building an openkal program for
x86_64-windows-gnu against the PUBLISHED openkal-llvm-runtime@0.13.0 on
this engine fails on libunwind's two static_asserts --- x86_64 registers do not fit into unw_context_t, UnwindCursor<> does not fit in unw_cursor_t. That is step three taken first, and it is why step two ships
first. setjmp.h is the silent half: its own comment says a mismatch is
reported by nothing until the record overruns.

THE RESIDUAL WINDOW IS NAMED RATHER THAN CLAIMED AWAY. A project pinning
openkal-musl at 0.18.0 or earlier EXACTLY, and upgrading past this release,
gets that silent #else. Moving the index's latest onto 0.19.0 first keeps
the window to exact pins; nothing available here closes it, because the
engine cannot know which macros a package's installed headers read.

Bootstrap pin advances from 2026.9.20.1 to 2026.9.21.1.

…rrowed name is withdrawn

`__mcpp_target_<os>__` becomes `__MCPP_TARGET_<OS>__` and `__openkal__`
becomes `__OPENKAL__`. The spelling is still the triple's own `os` field, now
upper-cased; the engine still learns no operating-system name.

THE CONVENTION SPLITS BY WHAT A NAME IS, NOT BY WHO WRITES IT. A vendor or
product name is upper --- `__APPLE__`, `_WIN32`, `__MINGW32__`, `__GNUC__`. A
kind-of-system name is lower --- `__linux__`, `__unix__`, `__gnu_linux__`.
Every row this engine OWNS is of the first kind: it names mcpp, or it names
openkal. The kind-of-system question is answered by `__linux__` and its
family, which mcpp SUPPLIES rather than owns and which therefore keep their
lower-case spelling, for that exact reason.

The lower-case spelling shipped in 2026.9.21.1 on the reverse reading: that
these names sit beside `__linux__` in real guards, so matching it was
consistency. THAT CONFUSES ADJACENCY WITH KIND. `__APPLE__` sits in those
same guards and is upper, because it belongs to somebody.

`__MCPP_` is a prefix, not the whole rule: `__OPENKAL__` is owned and names
*openkal* rather than mcpp. The assertion in `test_predefines.cpp` therefore
checks the SPELLING CONVENTION --- upper case, `__`-wrapped --- instead of
the prefix, which would have judged `__OPENKAL__` a violation when it is not.

WITHDRAWING A MACRO IS DECIDED BY A COUNT, NOT BY READING

An entry in this contract is a published interface, and withdrawing one is
SILENT: a `#if` selects the other branch and compiles. No mechanism available
to a build tool makes that loud. So a withdrawal is decided by a measurement
that ENUMERATES READERS, and what it finds sets how many steps the withdrawal
takes. The two this project has performed came out differently:

    withdrawn                              readers found            steps
    __CYGWIN__                             4 third-party members,   3
                                           + 6 sites in the two
                                           headers we INSTALL
    __mcpp_target_<os>__, __openkal__      none                     1

The second row's denominator is every repository of this ecosystem, swept by
file type: no source file and no manifest reads either name, and the only
occurrences are this engine's emitter, its tests, and prose. Both names were
invented here, so no upstream code can hold one; exposure was three days for
`__openkal__` and a single release for the target macro. The rule did not
change between the two rows. The count did.

A new assertion takes its denominator from the target registry rather than
from a list written beside the test: every canonical triple is parsed and its
emitted macro checked to be a valid identifier. The spelling comes from the
`os` field, so an `os` carrying a dot or a version suffix would produce a
macro no compiler accepts, and the failure would land in a user's build.

__CYGWIN__ IS WITHDRAWN --- THE LAST STEP OF A THREE-REPOSITORY SEQUENCE

The Windows `presents = "posix"` realisation now adds `-U__CYGWIN__` and
`-U__CYGWIN32__` to the compile line. `--target=x86_64-pc-cygwin` stays: it
is what supplies `__unix__` and suppresses `_WIN32`, which is what presenting
POSIX means. Only the borrowed NAME was unwanted.

  1. 2026.9.21.1 defined mcpp's own name beside the borrowed one; this
     release re-spells it, while it still has no consumer. Purely additive.
  2. `openkal-musl@0.19.0` and `openkal-llvm-runtime@0.14.0` read the new
     name and keep `|| defined(__CYGWIN__)`, so they build on an engine from
     either side of this change. PUBLISHED BEFORE THIS RELEASE.
  3. this release stops defining the borrowed name.

THE ORDERING IS MEASURED, NOT ASSERTED. It holds between repositories, so no
test in this one can check it. Building an openkal program for
`x86_64-windows-gnu` against the PUBLISHED `openkal-llvm-runtime@0.13.0` on
this engine fails on libunwind's two `static_assert`s --- `x86_64 registers
do not fit into unw_context_t`, `UnwindCursor<> does not fit in
unw_cursor_t`. That is step three taken first, and it is why step two ships
first. `setjmp.h` is the silent half: its own comment says a mismatch is
reported by nothing until the record overruns.

THE RESIDUAL WINDOW IS NAMED RATHER THAN CLAIMED AWAY. A project pinning
`openkal-musl` at 0.18.0 or earlier EXACTLY, and upgrading past this release,
gets that silent `#else`. Moving the index's `latest` onto 0.19.0 first keeps
the window to exact pins; nothing available here closes it, because the
engine cannot know which macros a package's installed headers read.

Bootstrap pin advances from 2026.9.20.1 to 2026.9.21.1.
… is verified against is overridable

E3: A REQUIREMENT NOBODY ANSWERED READ EXACTLY LIKE A CONFIRMED ONE.

Three situations exist and two of them build: the resolved implementation
states a `provides-interfaces` list containing the requirement (build), states
a list without it (refused), or states nothing at all (build). The third is
deliberate --- the key postdates the implementations, and a graph that has not
adopted it must keep building --- but until now it produced output identical
to the first, so a consumer inspecting a green build could not tell "checked
and agreed" from "never asked".

    note kernel-abi interfaces: fakekernel@0.1.0 states none, 2 requirements unchecked

The note names the implementation taken from the RESOLVED layer: a reader
told only that something went unchecked cannot act on it.

`tests/e2e/743` gains two legs rather than one. Leg C asserts the note is
present, leg D that it is absent when the provider does state its list.
WITHOUT LEG D, LEG C PASSES AGAINST AN ENGINE THAT PRINTS THE LINE
UNCONDITIONALLY, which measures nothing. Leg C also asserts the COUNT, the
one part of the message the fixture determines: a note reporting "1" or "0"
would satisfy every assertion that matches only an identifier.

THE OPENKAL BRANCH IS OVERRIDABLE FOR ONE RUN

This repository already has one half of a cross-verification protocol: the
ecosystem repositories build against an mcpp PR branch through
`MCPP_SOURCE_REF`, so an engine change is measured against them before it
merges. The reverse was hard-coded to `main`, which makes a change that
REQUIRES a coordinated ecosystem commit unverifiable until after that commit
lands --- and unmergeable until then, since `openkal-cross` is the job that
fails.

`__CYGWIN__`'s withdrawal is the case that showed it. `openkal-cross` builds
`openkal-llvm-runtime@main`, whose installed header read only the borrowed
name, so this engine's own CI reproduced the ordering constraint as a red
cell. The cell is CORRECT --- the packages must publish first --- but
verifying the engine before that publish needs the input. Left empty, nothing
changes.

E2 (the link-time set difference) is recorded in the plan as withdrawn from
this release, with its blocker corrected: it is not the workload the plan
claimed but a design decision. `SURFACE.txt` is not installed by any package,
and docs/22 states that the engine knows no member of either set --- so where
an interface-to-symbol map lives, and whether the engine can read one without
learning this ecosystem's vocabulary, has to be answered before the feature
has a shape.
Reading the measured diagnostics and the recipes apart:

  curl        lib/setopt.c:31: 'linux/tcp.h' file not found
  cmp-module  asio/detail/config.hpp:899: 'linux/version.h' file not found

CURL IS A RECIPE DEFECT, the same shape as C3/expat. `compat.curl.lua`
generates `#define HAVE_LINUX_TCP_H 1` inside `#if defined(__linux__)`.
openkal runs on the Linux kernel, so `__linux__` is CORRECT; what is wrong is
the recipe reading it as "glibc's whole Linux userspace is installed". The
same block asserts `HAVE_GLIBC_STRERROR_R` --- openkal-musl is musl, so that
one is actively false --- along with `HAVE_SYS_EVENTFD_H`, `HAVE_FSETXATTR`
and a hard-coded host path in `CURL_CA_BUNDLE`. The honest test is
`__has_include(<linux/tcp.h>)`: standard C, and it asks the question being
asked rather than inferring which headers exist from which kernel.

CMP-MODULE IS THE REAL C2. asio's `#include <linux/version.h>` sits outside
every `ASIO_DISABLE_*` guard, so no configuration macro prevents it, and no
manifest key reaches inside a third-party header --- which is justification
one in `predefines.cppm`.

`members.toml` already has an `[excluded]` table whose stated semantics are
exactly "cannot be built in any openkal graph". What is missing is a second
reason category, not a second table.

E2's blocker is corrected in the same pass: not the workload the plan claimed
but a design decision, since SURFACE.txt is installed by nothing and docs/22
states the engine knows no member of either set.
…ile using it

Both ecosystem packages went green against the mcpp PR branch, AND NEITHER
GREEN TOUCHED `__MCPP_TARGET_WINDOWS__`:

  openkal-musl          linux/gcc, linux/llvm, macos/llvm; the cross-link row
                        is Linux<->macOS
  openkal-llvm-runtime  x86_64-linux-gnu and riscv64-none-elf

No package's CI in this ecosystem builds `x86_64-windows-gnu`, and the whole
subject of this change is a macro that exists only on Windows targets. The
greens are real and they measure something else --- a criterion that ran,
passed, and whose object was not present.

The only job that builds that target is mcpp's own `openkal-cross`, and it
had the ecosystem branch hard-coded to `main`, so an engine change requiring
a coordinated ecosystem commit was unverifiable until after that commit
landed. `openkal_ref` closes it; empty means today's behaviour.

Consequence for ordering: the example in that job reaches the runtime through
`path = "../.."` and the runtime's manifest pins `openkal-musl = "0.19.0"`
from the INDEX, so musl 0.19.0 has to be registered before the input can be
used.
…that leg

Read job by job instead of from the first job of a run:

  openkal-llvm-runtime  `host-dimension` has a Windows-host row that builds
                        every target, including x86_64-windows-gnu, and it
                        resolved openkal-musl@0.19.0 on the PR engine. The
                        Windows leg of E1 WAS verified.
  openkal-musl          linux/gcc, linux/llvm, macos/llvm, a Linux<->macOS
                        cross-link, and a start-on-the-other-system job. No
                        Windows cell at all.

I concluded from the first job alone that no package in the ecosystem builds
Windows-over-openkal. That was wrong and the conclusion it cast doubt on
stands.

The real gap is smaller and still a gap: the package whose change this round
is `bits/setjmp.h` --- an installed header whose only branch is about
Windows --- has no Windows cell of its own. It is verified today only
transitively, because the runtime's CI pulls musl in as a path dependency and
compiles it along the way.
…othesis was right

Following the criterion the finding itself wrote down --- instrument
`run_dtors` --- with `&dtors` printed at both sites:

    registered dtor, dtors=0x7ffffe994680, &dtors=0x7ffffe9946a8
    run_dtors called, dtors=0,             &dtors=0x7ffffe9946c8

`run_dtors` IS called, which retires one branch. `&dtors` DIFFERS three times
in one thread, which gives the other: `__thread` is emutls here, emutls keeps
its per-thread blocks behind a pthread key of its own, that key's destructor
had already released this thread's block, and every read afterwards allocates
a fresh zeroed one.

HYPOTHESIS ONE IN SECTION 4 WAS CORRECT AND WAS RECORDED AS REFUTED. What was
wrong was the probe: musl runs key destructors in creation order, and that
probe created its own key BEFORE first touching a thread-local, so emutls
outlived it. The real ordering is the reverse. A probe cannot report an
ordering it was constructed to avoid --- the predicate was right and the
object's construction excluded the condition under test.

Fixed in openkal-llvm-runtime 0.15.0 by keeping the list in the TLS key's own
value, which no other key's teardown can reach. Two conditions asserted, in
examples/cxx, on both targets: the thread_local is constructed, and its
destructor runs.
Both layers located and fixed, shipping in openkal-llvm-runtime 0.15.0. The
second layer: `__thread` is emutls under `-femulated-tls`, emutls keeps its
per-thread blocks behind a pthread key of its own, and that key's destructor
releases this thread's block before libc++abi's runs. Criterion: `&dtors`
differs three times in one thread.

It was closed by the criterion the finding document itself wrote down. And
the hypothesis that document recorded as refuted was correct --- the probe
created its own key before first touching a thread-local, and musl runs key
destructors in creation order, so emutls outlived it. The predicate was
right; the object's construction excluded the condition under test.
`status: resolved` is not in the vocabulary the structure check accepts
(active | landed | superseded | abandoned), and the agents index had not been
regenerated. Both caught by `check_docs_structure.sh`, which is the check
that exists so a front-matter word nobody reads does not drift.
Measured rather than assumed. A minimal criterion --- a setjmp/longjmp
program with `_Static_assert(sizeof(jmp_buf) >= 32 * sizeof(unsigned long
long))`, declaring `openkal-musl` alone, built for x86_64-windows-gnu:

  compiles   which is the layer that matters, since a short jmp_buf is
             silent and a compile-time assertion is the only thing that
             makes it loud
  does not link   cpow.o and others leave references unresolved; openkal-musl
             alone is not a complete link, because the openkal implementation
             and compiler-rt are assembled by openkal-llvm-runtime

So a Windows cell there is either compile-only (which does catch this defect,
and is cheap) or pulls the runtime in as a path dependency --- the mirror of
what the runtime's own CI already does. Either is new work.
@Sunrisepeak

Copy link
Copy Markdown
Member Author

Ecosystem review before merge

Per repository, what this release does to something already published.

Published packages

package effect measured
openkal-musl ≤ 0.18.0 breaks on a Windows target presenting POSIX: bits/setjmp.h reads only __CYGWIN__, falls to #else, short jmp_buf, reported by nothing yes — the sibling defect in libunwind fires two static_asserts on the same build
openkal-musl 0.19.0 unaffected: reads __MCPP_TARGET_WINDOWS__ with || defined(__CYGWIN__) beside it yes, cross-verified against this branch
openkal-llvm-runtime ≤ 0.13.0 breaks, loudly: x86_64 registers do not fit into unw_context_t yes, built on this engine against the published 0.13.0
openkal-llvm-runtime 0.14.0 unaffected yes — five jobs green against this branch, including the Windows-host row of host-dimension
every other package in the index untouched: no manifest key changed, no descriptor grammar changed check_version_pins, and 231 descriptors already parse

The ordering is the mitigation and it is not optional. 0.19.0 and 0.14.0 are
published and registered; the index's latest for both has moved. This release
goes out after that, not before.

What is not mitigated, and is named rather than claimed away

A project pinning openkal-musl at 0.18.0 or earlier exactly, and upgrading
the engine past this release, gets the silent #else. No mechanism available
here closes it: the engine cannot know which macros a package's installed
headers read. Recorded in docs/22 and in cenv.cppm at the emission site.

The renamed macros

__mcpp_target_<os>__ and __openkal__ stop being defined. Enumerated across
every repository of this ecosystem, by file type: no source file and no
manifest reads either name
; the only occurrences were this engine's emitter,
its tests, and prose. Both names were invented here, so no upstream code can
hold one, and exposure was three days for one and a single release for the
other. A withdrawal argued from anything other than that count is the argument
this project already got wrong once, in the first shape of this same branch.

Index

No new manifest key, so min_mcpp does not move and no client loses the index.
latest_mcpp moves with the release as usual.

Build cache

The macro rename changes field 7 of the fingerprint. It costs nothing: field 8
is the mcpp version, so every release already invalidates every entry.

What this release is expected to FIX

Four members of the compatibility measurement — archive, sqlite3,
mimalloc, c-ares — stop at #include <windows.h> reached through
#if defined(_WIN32) || defined(__CYGWIN__). The measurement that says so is
the one that moves pins.toml onto this release, and it is the criterion this
change is accountable to. It has not run yet; the figure will be reported
there, not here.

Six sections. B and C are CHANGE and must fail on the previous release; E and
F are GUARD and must pass on both. Measured, on the host, against the genuine
published archive of 2026.9.21.1:

    2026.9.21.1   fails=2   B: no upper-case target macro
                            C: an unanswered requirement produced no note
    2026.9.21.2   fails=0

C'\''s second leg passes on both by design: it asserts the note is ABSENT when
the provider does state its list. Without it, the first leg would pass against
an engine that printed the line unconditionally --- a negative control is not
a hole in a CHANGE section.

B carries both directions in ONE translation unit, because either alone passes
for the wrong reason: an engine defining NEITHER spelling satisfies "the
lower-case one is gone", and one defining BOTH satisfies "the upper-case one
is here". Its second leg asks a freestanding target for its own macro, which
is what says the spelling is DERIVED from the triple rather than enumerated.

D and F need openkal-llvm-runtime 0.14.0 from the index and reported NOT RUN
rather than passing when it was not yet registered.
E1b and E1c did not exist when the table was written; C2 turned out to be two
items with two different owners; E2 left this round with its blocker
corrected. Each row now says what state it is in rather than only what would
count as passing, because a table of criteria with no readings is a list of
intentions.

Recorded readings: the verification script's control run (2026.9.21.1
fails=2, 2026.9.21.2 fails=0), the nine green cells of openkal-cross, and the
two directions the A3 check was measured in.
@Sunrisepeak
Sunrisepeak merged commit e9230df into main Sep 20, 2026
40 of 42 checks passed
@Sunrisepeak
Sunrisepeak deleted the the-macros-mcpp-owns-are-spelt-in-upper-case branch September 20, 2026 21:28
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