Skip to content

0.15.0 --- the C environment is declared, not implied by the target - #37

Merged
Sunrisepeak merged 16 commits into
mainfrom
feat/c-environment
Sep 18, 2026
Merged

Sunrisepeak merged 16 commits into
mainfrom
feat/c-environment

Conversation

@Sunrisepeak

@Sunrisepeak Sunrisepeak commented Sep 17, 2026

Copy link
Copy Markdown
Member

Summary

Declares the C environment this package presents ([c-abi] in mcpp.toml, per the design in mcpplibs/openkal .agents/docs/2026-09-18-openkal-c-environment-and-personalities-design.md §3.2/§4), rather than leaving it implicit in the mcpp target triple, and fixes the five places in this package's own sources that had been reading the compiler's _WIN32 instead of stating what they needed.

What a reader needs to decide whether to upgrade:

  • A rebuild on x86_64-windows-*. The environment a translation unit sees on that target changes: _WIN32/_WIN64/__MINGW32__ are no longer defined, the data model is LP64 (was LLP64), and wchar_t is 32 bits (was 16). Object format and calling convention are unchanged — still PE / Win64. A program built against this package for that target needs a Clang-based toolchain (mcpp's [c-abi] realization is Clang-specific: a --target= substitution plus -f[no-]short-wchar) and rebuilds against the new declaration; it does not need any source change of its own unless it read _WIN32 itself.
  • What the package now declares: [c-abi]presents = "posix" (defines __unix__, not _WIN32/__MINGW32__), data-model = "arch-default" (musl's own LP64 on this architecture), wchar = 32 (musl's own wchar_t, unconditionally), builtins = "iso". The same environment on every target, Windows included. Verified by mcpp's own probe translation unit, not merely asserted.
  • Two defects this found in the package's own code, neither visible until a program actually ran on the new environment:
    • The jmp_buf layout mismatch. port/include/bits/setjmp.h decided the Win64-vs-SysV register layout by reading _WIN32. Once the environment stops defining it, the header silently falls back to the SysV layout (measured: sizeof(jmp_buf) 164 bytes) while port/src/okm_setjmp.S — once it can even be assembled — still writes the Win64 layout (256 bytes of register content) into it: a ~92-byte overrun on the very first setjmp, reported by nothing. Worse, okm_setjmp.S itself failed to assemble at all under the new environment (expected absolute expression) before this PR, because it read the same macro. Fixed: the header reads __CYGWIN__ (mcpp keeps it defined target-wide for this environment, reaching an application's own compile, which the header's readers need); the assembly file reads a new private OKM_TARGET_WINDOWS (this package's own manifest define, since the file is never compiled by anything but this package's own build). Confirmed both views agree (392 bytes, _Static_assert-checked on both sides with the actual compile flags each gets) and that a longjmp back into a caller frame — five live registers, a 256-byte local buffer, distinguishable locals — returns everything intact, under Wine.
    • Four more _WIN32 sites with the same shape, all internal (OKM_MUSL_INTERNAL): features.h's weak_alias mechanism (this one was load-bearing — without it, stat/fstat and 289 other musl aliases stop being created on this object format at all), okm_start.c's argv/init-array handling, okm_format.c's init/fini-array shims, and okm_spawn.c's .exe retry on a posix_spawn that names a program without a suffix. All four now read OKM_TARGET_WINDOWS.
  • Four old patches removed (musl/PATCHES.md records each and why removing it is correct): the pthread_t/_Addr declaration in include/alltypes.h.in and the five generated alltypes.h headers revert to upstream musl's unsigned long; the three L"..." rewrites in src/stdio/vfwscanf.c, src/stdlib/wcstol.c, src/stdlib/wcstod.c revert to plain wide literals. Both existed only to survive Windows's old LLP64/16-bit-wchar_t presentation.
  • musl-generated/x86_64-windows/bits/alltypes.h regenerated for LP64: byte-identical to musl-generated/x86_64/bits/alltypes.h.
  • README: new "The C environment this package presents" section, the version-compatibility table updated (openkal-llvm-runtime = "0.11.0").
  • examples/c-abi: probe asserting sizeof(long) == 8, sizeof(wchar_t) == 4, a wide literal carries a code point above U+FFFF and round-trips, _WIN32 is not defined, __unix__ is defined. Runs on every CI row.

Measurements

All of the below are against mcpp-community/mcpp 2026.9.18.1, which is what carries [c-abi] support, the assembler-target-substitution fix, and the kernel-abi platform-environment inference this package's own dependency graph needs (openkal-windows, which correctly keeps calling real Win32 APIs).

x86_64-windows-musl, llvm@22.1.8, under Wine, against released openkal-windows 0.8.0 unmodified:

Regression Before this PR After
argv truncated argv[0] cut short, "arg with space" cut to "arg with spa" full and exact
a Windows-style absolute path stats ENOENT succeeds
posix_spawn retries .exe EINVAL succeeds, child runs

setjmp/longjmp: see the jmp_buf defect above — confirmed and fixed, with sizes.

Native Linux, gcc and llvm: every existing probe unaffected (examples/posix, examples/threads-cxx, examples/identifiers, examples/net, examples/subprocess, the new examples/c-abi).

Depends on

  • mcpp-community/mcpp 2026.9.18.1 (releasing) for [c-abi] realization, the .S-file target substitution, and the kernel-abi inference.
  • openkal-llvm-runtime 0.11.0 (companion PR) for the same class of fix in vendored libunwind, including a second, _WIN64-keyed defect (register-save/restore layout) found only by actually running an exception through it.

Test plan

  • CI green on every row (the "windows, gcc" row is removed as of the update below), once the index carries mcpp 2026.9.18.1.
  • Do not merge or release until CI is green and the companion openkal-llvm-runtime PR is ready to land alongside it.

Update: two more type-identity defects found and fixed, and the CI matrix changed

  • kal_u64 vs uint64_t: okm_syscall.c passed uint64_t* where kal_fs_seek reads kal_u64* — same width under LP64, different type (kal_u64 is __UINT64_TYPE__, authoritative; uint64_t is this package's own generated type). Fixed at all 5 call sites.
  • A general check for this whole class of defect: port/src/okm_type_identity.c, compiled for every target by the existing package-wide glob, asserts every typedef the generated headers commit to (wchar_t, wint_t, size_t, ptrdiff_t, intptr_t, uintptr_t, int32_t/uint32_t, int64_t/uint64_t, intmax_t/uintmax_t) against the compiler's own builtin macro for the same type, via _Generic — a type-identity check, not a width check, so it catches exactly this class. It found two more, both on aarch64-macos (wint_t, intmax_t/uintmax_t) and the same three plus int64_t/uint64_t on x86_64-apple-macos — a target nothing in this repository builds or tests, previously falling silently into the generic x86_64 (Linux) answer. musl-generated/x86_64-macos/ is new; all fixed and documented in musl-generated/README.md.
  • The "windows, gcc" CI row is removed, not fixed: measured, x86_64-w64-mingw32-gcc reports __SIZEOF_LONG__ == 4 unconditionally (the Windows ABI itself, no flag changes it), so no GCC version can realize this package's LP64 declaration on that target. README gains a paragraph stating this and the toolchain requirement (Clang, via mcpp's --target= substitution).

`_WIN32` used to answer three questions at once on this port's Windows
target: Win32 is callable, the C runtime is Windows CRT, the machine code
is PE/Win64. Only the third was ever true here, and the other two were
worked around rather than corrected --- four patches in musl/PATCHES.md
existed only to make Windows survive an LLP64 data model and a 16-bit
wchar_t that this port's own C library never has anywhere else.

This declares the environment instead of leaving it implicit in the target
triple. mcpp.toml gains a `[c-abi]` block: `presents = "posix"` (no
`_WIN32`, `__unix__` defined, everywhere including Windows), `data-model =
"arch-default"` (musl's own LP64 on every 64-bit target, Windows included),
`wchar = 32` (musl's own wchar_t, unconditionally), `builtins = "iso"` (the
compiler must not assume a platform C library beneath this one). See
mcpplibs/openkal .agents/docs/2026-09-18-openkal-c-environment-and-
personalities-design.md, sections 3.2 and 4.

The four patches this replaces are gone: `include/alltypes.h.in` and the
five generated `alltypes.h` headers declare `pthread_t` as upstream musl
does again, and `src/stdio/vfwscanf.c`, `src/stdlib/wcstol.c` and
`src/stdlib/wcstod.c` write `L"..."` as musl always has --- both patches
existed only to survive a data model and a wchar_t width this package no
longer states for Windows. `musl-generated/x86_64-windows/bits/alltypes.h`
is regenerated for LP64 rather than hand-edited: it is now byte-identical
to `musl-generated/x86_64/bits/alltypes.h`, which is what stating the same
data model on the same architecture produces. musl/PATCHES.md and
musl-generated/README.md record what was removed and why removing it is
correct rather than merely convenient.

README gains a section stating the environment table per target and points
at the two limits that are not about it and remain: name resolution on
Windows, and the compiler-rt builtins a C program needs when it names this
package directly on x86_64-windows-gnu.

examples/c-abi checks the declaration rather than assuming it:
sizeof(long) == 8, sizeof(wchar_t) == 4, a wide literal carries a code
point above U+FFFF, `_WIN32` is not defined, `__unix__` is defined. It runs
on every CI row, not only Windows, because the claim is that the
environment is now the same one everywhere.

Measured against released mcpp 2026.9.17.3 (which does not parse [c-abi]
and silently ignores the block): every existing probe and the new one pass
unchanged on native Linux, gcc and llvm. The Windows row is expected red
until mcpp understands [c-abi] --- the regenerated header now states LP64
for a target whose compiler (plain MinGW) cannot produce an 8-byte `long`,
so the library itself fails to compile there (`kal_u64` pointer-width
mismatches in okm_syscall.c) rather than merely failing the new probe.

Keeps openkal 0.13.0, openkal-linux 0.13.0, openkal-macos 0.10.0 and
openkal-windows 0.8.0.
[c-abi] presents = "posix" (0.15.0) correctly leaves _WIN32 undefined on
this target, and five sites still asked it what target they were on rather
than what C environment they were compiling for: features.h's weak_alias
mechanism, okm_start.c's argv/init-array handling, okm_format.c's
init/fini-array shims, okm_spawn.c's `.exe` retry, and okm_setjmp.S's
choice between the SysV and Win64 register-save layouts. Each is
OKM_MUSL_INTERNAL (or, for the assembly file, compiled only by this
package's own build) and now selects on OKM_TARGET_WINDOWS instead, a
private define this package's own manifest supplies under
[target.'cfg(windows)'.build] and does not carry to a consumer.

bits/setjmp.h cannot use that private define: it is installed, and an
application's compile does not see it. It reads __CYGWIN__ instead, which
mcpp now keeps defined target-wide for this environment for exactly this
purpose (PE object format, POSIX C environment) — a macro that holds
equally for the header's installed readers and is the one construct this
package did not have to invent. okm_setjmp.S, which writes the record that
header sizes, is not installed and reads OKM_TARGET_WINDOWS instead; the
two files answer the same question about the same target without reading
the same macro, and the header records why that has to be true rather than
merely happening to be.

Measured under mcpp's [c-abi] realization for x86_64-windows-musl, via
Wine: argv arrives untruncated, an absolute Windows-style path stats
successfully, posix_spawn retries with .exe and the child runs, and a
longjmp back into a caller frame returns with its locals and its return
value intact. sizeof(jmp_buf) claim: with the old _WIN32-only guard,
port/include/bits/setjmp.h and musl's own internal setjmp.c disagreed
about the Win64-vs-SysV record layout the moment an application was built
without _WIN32 defined and this package's own build still had it (or vice
versa) — the two are no longer coupled to the same macro's definedness at
all, which is the defect this fixes rather than one this measures a size
for.

Two things measured but not fixed here, because they are not sites this
package owns:

- llvm/libunwind (openkal-llvm-runtime) has the same class of defect,
  including one _WIN64-keyed pair (register save/restore layout and context
  record size) that a plain grep for _WIN32 does not find; fixed on that
  package's own branch.
- openkal-windows 0.8.0 is not yet exempted from the POSIX presentation
  (design doc §3.4, c-environment = "platform"), so its own Win32 API
  calls — CommandLineToArgvW, CreateFileW, CreateProcessW and their
  relatives — are compiled against this package's 32-bit wchar_t against
  data the OS always hands back as 16-bit UTF-16. That is the actual cause
  of the argv truncation, the path ENOENT and the spawn EINVAL as measured
  end-to-end; confirmed by adding the exemption to a local checkout and
  re-measuring, and out of scope for this package to fix.
Was "not yet carried by a released openkal-llvm-runtime" since 0.15.0
declared the environment. It is now: 0.11.0 pins this version and carries
the port-layer fix (OKM_TARGET_WINDOWS in place of _WIN32) that makes the
declaration something a program can actually build against on Windows.
Sunrisepeak and others added 12 commits September 18, 2026 10:19
okm_syscall.c passed uint64_t* where kal_fs_seek reads a kal_u64* --- the
same width under the LP64 this package now declares, and a different type:
kal_u64 is __UINT64_TYPE__ (the compiler's own statement of this target's
64-bit type, authoritative) and uint64_t is this package's own generated
`unsigned _Int64`, and nothing requires the two spellings to agree beyond
the coincidence that they used to, under the LLP64 this package used to
declare for Windows. Fixed: the five call sites (SYS_copy_file_range,
SYS_lseek) now hold kal_u64 locals, including the two `&(kal_u64){0}`
compound literals. Checked every other kal_u64*/kal_u32*-taking interface
this port calls (kal_fs_capacity, kal_task_wait, kal_task_wake,
kal_process_stop_requested): kal_fs_capacity's two call sites already used
kal_u64 correctly, and nothing else in this port passes a mismatched
pointer to any of them.

This is the third instance of the same shape found in this wave ---
jmp_buf's layout, this, and wchar_t's signedness on aarch64-macos --- so
this adds the check that finds the class rather than the next member of it
individually: port/src/okm_type_identity.c, an ordinary translation unit
under port/src (compiled for every target, unconditionally, by the
existing package-wide glob), asserting every typedef this port's generated
headers commit to against the compiler's own builtin macro for the same
type on the actual target being built --- _Generic, so a same-width
different-type mismatch fails to compile rather than passing a weaker
sizeof(T) == sizeof(builtin) check the way every defect above would have.

It found two more, both on macOS, neither caught by any existing probe
because nothing in this port passes a wint_t, intmax_t or uintmax_t across
a boundary strict enough to refuse an incompatible type outright:

- wint_t is `unsigned` in both generated macOS headers; Apple's own
  __WINT_TYPE__ is signed `int` on both architectures. Fixed.
- intmax_t/uintmax_t were derived from the same _Int64 override that
  correctly fixes int64_t/uint64_t, which is wrong for macOS specifically:
  Apple's own intmax_t is `long` where its int64_t is `long long` --- two
  different 64-bit types on the same target. Fixed by stating
  intmax_t/uintmax_t directly instead of deriving them.

And a third: `x86_64-apple-macos` --- which nothing in this repository
builds or runs for, but which the generic `not(windows)` row silently
claimed to answer for --- disagrees with Linux's x86_64 answer the same way
aarch64-macos already known about does (int64_t/uint64_t, wint_t,
intmax_t/uintmax_t all differ). musl-generated/x86_64-macos/ is new,
mcpp.toml gains the third row for this architecture (mutually exclusive
with the other two, the same shape aarch64 already has), and
musl-generated/README.md records the table and the measurements.

Measured for every target this package builds for (x86_64-linux,
x86_64-windows via mcpp's [c-abi] realization, x86_64-apple-macos,
aarch64-linux, aarch64-apple-macos, riscv64-linux): the check compiles
clean on all six now. Before the three fixes above, three of the six
failed it.
Measured rather than assumed: x86_64-w64-mingw32-gcc reports
__SIZEOF_LONG__ 4 unconditionally, for every version, with no flag that
changes it, because that is the Windows ABI itself and not a default GCC
chooses per target. This package has declared LP64 for x86_64-windows-*
since 0.15.0, realized only through a Clang-specific mechanism (mcpp's
--target= substitution) that GCC has no equivalent of. A build that hands
GCC this declaration does not get a weaker version of it; it gets a
uint64_t that is actually 32 bits wide, silently, everywhere this port or
a program above it assumes otherwise.

Removed the "windows, gcc" CI row rather than working around it: it was
testing a toolchain configuration this package no longer supports, not a
degraded form of the one it does. Not replaced with a Clang-based Windows
row here, because this package's own examples that name no C++ runtime
still meet the separate, already-documented compiler-rt-builtins limit for
a C program naming this package directly on this target, regardless of
which compiler realizes [c-abi] --- a gap this repository's CI leaves open
rather than papering over with a different toolchain and the same missing
dependency. openkal-llvm-runtime's own CI exercises this target end to end,
on a real Windows machine, with this package in its graph.

README: new paragraph in "The C environment this package presents" stating
the toolchain requirement and the measurement, so a reader who builds this
package with GCC on Windows finds the sentence that explains why, rather
than discovering it from a CI row that is no longer there.
…robe

This script's include list is a hand-maintained second copy of mcpp.toml's,
documented above as a thing that falls behind the first --- and it had:
`-Imusl-generated/"$arch"` named the generic architecture directory, not
`"$arch"-macos`, so a probe that claims to cross-compile for the other
system was actually compiling against Linux's answer for every typedef
that directory overrides.

Found by port/src/okm_type_identity.c, added in the previous commit and
picked up here by this script's own `port/src/*.c` glob: the static
assertions for wchar_t, wint_t, int64_t/uint64_t and intmax_t/uintmax_t
failed under this script specifically, on both architectures, for exactly
the divergences that commit's musl-generated fixes address. Fixed the one
line; both architectures build clean now (1365 objects each, 0 indirect
symbols, the same two names as before).
…ling

Same bug as the previous commit's probe-cross-macos.sh, in the sibling
script that shares its hand-maintained include list: named
`musl-generated/$arch`, not `$arch-macos`. This one is what CI's
cross-link job builds `examples/cross-hello` with, and it is what turned
that job red after the previous commit's fix already made
probe-cross-macos.sh pass --- the same drift, in the second of two copies,
found one script later.

Measured locally: `cross-build-macos.sh aarch64 examples/cross-hello/src/main.c`
now produces a Mach-O arm64 executable as before.
mcpp-community/mcpp#668 is released and the index has it: [c-abi]
realization, the assembler-target-substitution fix, and the kernel-abi
platform-environment inference this package's own graph (openkal-windows)
needs. Every measurement in the last several commits on this branch was
taken against a locally built binary of this exact version; this makes CI
build against the same one rather than the pin that predates all of it.
…d accepts GCC where the realisation is empty
musl's package-level [c-abi] block states a hosted environment, and the
engine probes every target that resolves c-abi against it. A
freestanding target has no C library to present an environment for,
and probing it against musl's hosted declaration finds a mismatch
the declaration did not mean to make: clang's freestanding
preprocessor reports __SIZEOF_WCHAR_T__ as 16, not 32; a Windows host
cross to a freestanding target leaks _WIN32 from the host preprocessor;
both are real, and neither is wrong about the target it is reporting.

The override says, for os = "none" only, "this target has no C
environment, do not probe one here." Hosted targets keep the
package-level declaration unchanged; freestanding targets skip the
probe and the structural mismatch does not arise. The freestanding
build of this package still produces the headers libc++'s
<__mbstate_t.h> reaches into (musl remains the dep, the headers are
needed), it just does not impose the hosted [c-abi] declaration on a
target it does not cover.
The first override tried presents = "none", which the engine parsed but
ignored: the c-abi layer still resolved to musl and the probe still ran
against the package-level declaration (wchar=32), so the
__SIZEOF_WCHAR_T__ mismatch on freestanding kept failing.

This override keeps "posix" (the engine still sets the source-level
macros it would set for a POSIX target) but matches wchar to what
clang's freestanding preprocessor actually reports (16 bits, not 32,
because musl's hosted answer is musl's, not the toolchain's). data-model
stays "lp64" because that's what riscv64-none-elf is regardless of
whether an operating system is present.

Linux and macOS hosts cross-compiling to riscv64-none-elf pass with
this declaration. Windows host still fails: clang on Windows leaks
_WIN32 into the probe even with --target=riscv64-none-elf, and that
needs an mcpp-side fix to strip host macros. Not in this package.
host contamination on Windows and forces wchar = 32 on freestanding

Closes §F: the Windows-host × riscv64-none-elf c-abi probe mismatch
that 2026.9.18.2 could not detect through the package layer alone.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This package declares `[c-abi]` (presents, data-model, wchar,
builtins) and the engine's `cenv_probe::verify` reads the
predefined macros that back it. The probe needs the host-macro
strip on Windows hosts (`-U_WIN32` / `-U_WIN64` / `-U__MINGW32__` /
`-U__MINGW64__`) and the freestanding `-fno-short-wchar`
discipline — both shipped in `mcpp 2026.9.18.3`. Older engines
silently misbuild this package: the probe sees host contamination,
the package declares a clean environment, the build succeeds
against the wrong one. State the floor here so a reader landing on
the README from a search knows the upgrade is required and not a
suggestion.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@Sunrisepeak
Sunrisepeak merged commit 770d422 into main Sep 18, 2026
5 checks passed
@Sunrisepeak
Sunrisepeak deleted the feat/c-environment branch September 18, 2026 09:18
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.

1 participant