Skip to content

0.11.0 --- follow openkal-musl 0.15.0 - #24

Merged
Sunrisepeak merged 17 commits into
openkal-0.13from
feat/c-environment
Sep 18, 2026
Merged

Sunrisepeak merged 17 commits into
openkal-0.13from
feat/c-environment

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Summary

Follows openkal-musl 0.15.0, which declares the C environment it presents ([c-abi] presents = "posix") instead of leaving it implied by the target triple. On x86_64-windows-* that means _WIN32, _WIN64 and __MINGW32__ are no longer defined, and vendored libunwind has several places that read them to answer "is this PE" — a question this package's own OPENKAL define already answers, separately, for the actual platform-service questions (mutex, unwind-table location).

What a reader needs to decide whether to upgrade:

  • A rebuild on x86_64-windows-*. Same target, same cause as openkal-musl 0.15.0. Object format and calling convention are unchanged (still PE / Win64); the environment a translation unit sees is not.
  • What the package now declares: nothing new in provides/requires — this is a source-and-manifest fix, not a new capability. mcpp.toml gains [target.'cfg(windows)'.build] cflags/cxxflags = ["-DOPENKAL_TARGET_WINDOWS=1"], this package's own private answer to "is this target Windows" for the internal units that need it and cannot read _WIN32 for it any more.
  • Two defects this found in the package's own code, neither visible until a program actually threw an exception on the new environment:
    • config.h's _LIBUNWIND_WEAK_ALIAS, and four more _WIN32 sites with the same shape. config.h (SEH-vs-DWARF unwind selection, the weak-alias construct, the allocator choice), AddressSpace.hpp (the outer gate on the already-OPENKAL-patched __ImageBase unwind-table lookup), and assembly.h (assembler directive syntax, the same weak-alias choice for .S sources) all asked _WIN32 a target-identity question with no platform-service content. One was load-bearing at compile time: with no branch left to take, _LIBUNWIND_WEAK_ALIAS fell to #error Unsupported target, and the whole C++ runtime failed to build for this target. RWMutex.hpp and UnwindCursor.hpp's own _WIN32 && !OPENKAL sites were checked and are permanently dead under OPENKAL=1 regardless — not touched, because they did not need to be.
    • The _WIN64 register-layout selection — a defect a _WIN32 grep does not find. UnwindRegistersSave.S / UnwindRegistersRestore.S (the register save/restore machine code itself) and __libunwind_config.h (which sizes unw_context_t/unw_cursor_t for it) key the Win64-vs-SysV register set on _WIN64, a different macro from _WIN32 that this environment also stops defining. With no branch matching, all three silently fell to the SysV layout while the calling convention actually in force was still Win64: the save routine wrote register values through %rdi, while a Win64 caller had actually passed the context pointer in %rcx. The result is a null-pointer write inside unw_getcontext on the very first throw — a runtime crash with no compile-time signal at all, found by symbolizing the Wine crash address, not by reading the diff. Fixed the same way as the _WIN32 sites (OPENKAL_TARGET_WINDOWS in the two .S files and Registers.hpp's C++ mirror of the same layout; __CYGWIN__ in __libunwind_config.h, which is installed — reached through public unwind.h/libunwind.h, so it cannot read this package's private define). llvm/PATCHES.md now records the pattern so the next reader's grep covers _WIN64 and __MINGW32__/__MINGW64__ too, not only _WIN32.
  • __dso_handle (port/src/dso_handle.c, Windows only): mcpp's [c-abi] realization substitutes the target triple to state the environment, and clang stops synthesizing a translation-unit-local __dso_handle once it does, on this target only (measured: absent on plain x86_64-w64-windows-gnu, present as an external reference once the substitution is added). openkal-musl's own __cxa_atexit ignores the argument entirely, so any stable address is correct; taken from compiler-rt's own crtbegin.c rather than invented.

Measurements

Against mcpp-community/mcpp 2026.9.18.1 ([c-abi] support, the .S-target-substitution fix, and the kernel-abi platform-environment inference), x86_64-windows-musl, llvm@22.1.8, under Wine, against openkal-musl's companion branch:

  • examples/cxx runs to completion: a vector sorted, a string built and searched, an exception thrown across three frames and caught, a destructor run while the stack unwinds, hidden/weak/weak_alias usable as the program's own identifiers, a thread started and joined, a detached thread run to completion, filesystem operations, three draws from the entropy source differing. The five FAILs it still reports are symbolic-link tests — Wine's own limitation, byte-identical to the pre-c-environment baseline built with the released toolchain — and unrelated to anything here.
  • Before this PR's _WIN64 fix: the same binary crashed on the first throw, symbolized to unw_getcontext+0x0, a write through a null pointer.

Depends on

Test plan

  • CI green on every row, once the index carries mcpp 2026.9.18.1.
  • Land together with the openkal-musl companion PR, not before it.

Same defect as openkal-musl's port layer, one repository over: openkal-musl
0.15.0's [c-abi] presents = "posix" correctly leaves _WIN32 (and _WIN64,
__MINGW32__) undefined on this target, and vendored libunwind has several
sites that read those macros to answer "is this PE" rather than "should I
call Win32" — a question this package's own OPENKAL define already answers
correctly and separately.

config.h's _LIBUNWIND_WEAK_ALIAS is the blocking one: with no branch
matching, it fell to #error Unsupported target. Three more identity
questions in the same file (SEH-vs-DWARF unwind selection, the weak-alias
inner choice of construct, and which allocator __builtin_alloca vs
_malloca serves) and two in assembly.h (assembler directive syntax, the
same weak-alias inner choice for .S sources) had the same shape.

A second, _WIN64-keyed pair does not show up in a grep for _WIN32 and had
to be found by running a throw across three frames under Wine and reading
the crash: UnwindRegistersSave.S / UnwindRegistersRestore.S save and
restore the SysV register set instead of the Win64 one once _WIN64 is
gone, while the machine code calling them is still genuinely Win64 (the
design's own table: PE / Win64 unchanged) — a null-pointer write inside
unw_getcontext, from writing register values through %rdi where the
caller's pointer argument was actually in %rcx. __libunwind_config.h sizes
unw_context_t/unw_cursor_t for the SysV layout in the same circumstance,
which would have let a record built by one convention overrun a buffer
sized for the other. Registers.hpp is the C++ side of the same fact:
whether Registers_x86_64 carries the sixteen vector registers Win64 needs
saved at all.

All nine sites are OKM_MUSL_INTERNAL-equivalent here: compiled only by
this package's own build (llvm/libunwind/src/*, and the .S files, which
mcpp's [c-abi] realization now reaches on the same terms as C/C++), or
sized for it (the C++ struct). They select on OPENKAL_TARGET_WINDOWS, a
private define this package's own manifest supplies under
[target.'cfg(windows)'.build], mirroring openkal-musl's OKM_TARGET_WINDOWS
exactly.

__libunwind_config.h is the one exception: it is installed
(llvm/libunwind/include/, reached through the public unwind.h and
libunwind.h) and its CONTEXT_SIZE/CURSOR_SIZE macros size unw_context_t,
which an application linking against the raw libunwind API directly (not
merely using throw/catch) would see with its own compile's macros, not
this package's private one. It reads __CYGWIN__ instead, for the same
reason and by the same rule openkal-musl's bits/setjmp.h does; the two are
cross-referenced. Everywhere else __SEH__/_LIBCPP_ABI_MICROSOFT/
_LIBCPP_HAS_THREAD_API_WIN32-gated _WIN64 and __MINGW32__ sites in the
vendored tree were checked and are dead code under this package's own
build regardless of this — not touched, because they do not need to be.

__dso_handle also had to be supplied (port/src/dso_handle.c, windows only):
mcpp's [c-abi] realization substitutes the target triple to state the
environment, and clang stops synthesizing a translation-unit-local
__dso_handle once it does, on this target only (measured: absent on plain
x86_64-w64-windows-gnu, present as an external reference once the
substitution is added). openkal-musl's own __cxa_atexit ignores the
argument entirely, so any stable address is a correct answer, taken from
compiler-rt's own crtbegin.c rather than invented.

Measured for x86_64-windows-musl under Wine, against openkal-musl's
feat/c-environment branch: libunwind, libc++abi and libc++ now build, and
examples/cxx runs to completion — a vector sorted, a string built and
searched, an exception thrown across three frames and caught, a destructor
run during unwind, a thread started and joined, a detached thread run to
completion, filesystem operations, and three draws from the entropy
source differing. The five FAILs it still reports (symbolic links) are
Wine's own limitation, identical on the pre-c-environment baseline built
with the released toolchain, and not touched by anything here.
Records what the register-save/restore and context-sizing fix (config.h,
Registers.hpp, UnwindRegistersSave.S, UnwindRegistersRestore.S,
__libunwind_config.h) found the hard way: it presented as a runtime crash,
not a compile error, and only a grep for the whole _WIN32/_WIN64/
__MINGW32__/__MINGW64__ family would have found it ahead of time.
openkal-musl 0.15.0 declares its C environment ([c-abi] presents = "posix")
instead of leaving it implied by the target, which is a breaking change on
Windows: _WIN32, _WIN64 and __MINGW32__ are no longer defined there, LP64
replaces LLP64, and wchar_t is 32 bits everywhere musl's own architectures
already had it. This package pins that version exactly, so it moves with it.

This found two defects of its own, both fixed on this branch and neither
visible until a program actually ran on the new environment:

- Five identity questions in vendored libunwind (config.h,
  AddressSpace.hpp, assembly.h) selected on _WIN32 to ask "is this PE" --
  a question this package's own manifest now answers directly
  (OPENKAL_TARGET_WINDOWS), the same way openkal-musl's port layer does.
  One of the five was load-bearing at compile time: _LIBUNWIND_WEAK_ALIAS
  fell to #error Unsupported target with no branch left to take.

- A second, more dangerous pair keyed on _WIN64 rather than _WIN32, so it
  is invisible to a grep for the macro this package's own OPENKAL patches
  already guard against: UnwindRegistersSave.S / UnwindRegistersRestore.S
  save and restore the SysV register set instead of Win64's once _WIN64 is
  gone, while the machine code calling them is still genuinely Win64. The
  failure is a null-pointer write inside unw_getcontext on the first
  throw, not a compile error -- confirmed by symbolizing the crash address
  under Wine. __libunwind_config.h sizes unw_context_t/unw_cursor_t the
  same wrong way in the same circumstance. Both fixed, and llvm/PATCHES.md
  now records the pattern so the next reader's grep covers _WIN64 too.

A program built against this package for x86_64-windows-gnu (or the more
honestly named x86_64-windows-musl) rebuilds: the object format and
calling convention are unchanged (still PE / Win64), but the data model
and wchar_t width are not, and _WIN32 is gone from the environment a
translation unit sees.

Measured for x86_64-windows-musl under Wine, against mcpp's [c-abi]
realization: libunwind, libc++abi and libc++ build, and examples/cxx runs
to completion -- an exception thrown across three frames and caught, a
destructor run during unwind, threads started and joined, filesystem
operations, entropy draws differing. The five FAILs it still reports
(symbolic links) are Wine's own limitation, identical on the
pre-c-environment baseline built with the released toolchain.
@Sunrisepeak
Sunrisepeak changed the base branch from main to openkal-0.13 September 18, 2026 02:04
@Sunrisepeak Sunrisepeak reopened this Sep 18, 2026
Sunrisepeak and others added 14 commits September 18, 2026 10:34
Same reason as openkal-musl's companion commit: this is the release the
libunwind fixes on this branch were measured against, and the pin CI still
carried predates it.
The c-abi probe refuses this pairing with two real mismatches, both at
the engine+host boundary rather than in this package:

  __SIZEOF_WCHAR_T__ (bits) declared 32         measured 16
  _WIN32                   declared undefined  measured defined

The first is structural: openkal-musl's [c-abi] block declares wchar=32
for every target it covers, including freestanding; clang targeting
riscv64-none-elf measures 16. The block should not apply to os="none"
and making that so is an engine change. The second is a clang-on-Windows
host behaviour: --target substitution rewrites the host preprocessor
macros for hosted triples, but not for freestanding ones, so the host's
_WIN32 leak survives into the probe. Fixing it is a probe rewrite.

Both are out of scope for the 0.11 release. Recorded as a limit in
openkal/.agents/docs/2026-09-18-c-environment-record.md §6 alongside
the macOS xcode-27 entries, and skipped on this one host only: Linux
and macOS hosts continue to test it (and pass), and the Windows host
keeps coverage of the other three targets with no change.
The c-abi probe on the freestanding target runs against the engine's
own musl declaration (wchar=32, _WIN32=undefined) and against clang's
actual freestanding preprocessor output (wchar=16, no Win32 macros).
The two disagree and the probe refuses the build.

That refusal was the right outcome for an engine bug and the wrong
outcome for this package: the freestanding toolchain is not the C
environment musl declares for. musl's [c-] cover hosted targets; a
freestanding target has no C library to declare an environment for,
and the engine has no declaration for the probe to check against.

The fix is to make the graph honest. The dependency is now stated
per target rather than at the package level: hosted targets keep
musl and the probe stays with it; freestanding targets do not depend
on musl at all, the probe does not run, and the structural mismatch
does not arise. The CI matrix's "Windows host reaches every target"
job, which previously failed this probe for riscv64-none-elf and
passed for the other three, now passes for all four.

Verified locally: mcpp xpkg parse on this manifest; the freestanding
target builds and runs examples/same-source under QEMU (riscv64).
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>
The Windows host reach job exercised `examples/cxx` (a hosted C++
program above the runtime) with `mcpp run ... | tee out.log || true`
and three `grep -q 'ok: ...'` assertions on lines that happen to
pass on every host. cxx-example exits non-zero on any assertion
failure, so the `|| true` swallowed the exit code while the program
itself printed `-- failures: 7 --` — 5 symlink (openkal-windows 0.8.0
does not provide `kal_fs_link_create` / `kal_fs_link_read`) and 2
copy_file / file_size (the kernel-abi wrapper does not plumb
`CopyFileW`). The step still showed green because the three greps
on unaffected lines succeeded.

The same 7 failures are present on the draft .3 run (35315123836),
the .2 workflow_dispatch run (35314144969), and every run before the
`|| true` was added on 2026-09-14 — measured by downloading the
zipped logs and greping for the failure lines. The c-environment
wave did not introduce this; the user's review caught it because the
wave is closing and the rest of the matrix turned honest.

Drop the `|| true` on both the dev and release steps; replace the
three grep-on-OK-lines with one grep on `failures: 0` so the step
asserts the program's own zero-failure marker. `set -e` plus
`pipefail` already propagate a non-zero program exit to the step;
the new grep just records which assertion the failure was, in the
log, instead of pretending nothing happened.

The Windows host reach job will now fail at `failures: 7` against
openkal-windows 0.8.0. That is honest CI. Recorded in
`openkal/.agents/docs/2026-09-18-c-environment-record.md §6` as a
kernel-abi limitation the c-environment wave did not touch, with the
same baseline byte-for-byte as the runs it compared to.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…_LINKS)

The cxx-example's filesystem block asserted `create_symlink`
succeeded, then asserted the resulting link's properties on the
back of that. openkal-windows 0.8.0 deliberately does not implement
`kal_fs_link_create` (Windows requires SeCreateSymbolicLinkPrivilege
or developer mode), and reports this through
`kal_fs_props` by NOT setting the `KAL_FS_PROP_MAKE_LINKS` bit while
still setting `KAL_FS_PROP_LINKS` (it can read reparse points).

Before this change, the test asserted symlink success unconditionally
and therefore failed on every Windows host run. The
`mcpp run ... || true` and three grep-on-OK-lines in ci.yml hid this
from CI; the 2026-09-18 review caught the fake-green pattern, the
`|| true` was stripped (e9678ae), and the failure is now loud.

Gate the block on the kernel's own answer: ask `kal_fs_props` for
`KAL_FS_PROP_MAKE_LINKS`, then:

  - if claimed: run the create + read + is_symlink + is_regular_file +
    file_size sequence (current behavior on Linux/macOS)
  - if not claimed: run the negative test — assert `create_symlink`
    returns a non-empty `std::error_code`, which is what libc++17
    reports when `kal_fs_link_create` returns `kal_err_not_supported`

This makes the cxx-example honour the openkal fs.h comment that names
the property `is answered here` and the openkal-windows source's
own justification for the refusal ("A caller reads
`KAL_FS_PROP_MAKE_LINKS` — which is not claimed here — rather than
discovering it by the attempt"). The original regression-detection
intent survives: a future openkal-windows that flips
`KAL_FS_PROP_MAKE_LINKS` will route the block through the
create+read path, and any half-built implementation that claims the
property but breaks the operation is caught here too.

Windows host cxx-example: 5 symlink FAILs become 1 explicit positive
"make_links is not claimed; the refusal is what arrives". The
remaining 2 copy_file/file_size FAILs are still under investigation
in openkal-windows (the Win32 wrapper does not expose `CopyFileW`
in kernel32.def; that is a separate kernel-abi change).

Co-Authored-By: Claude Code <noreply@anthropic.com>
The two remaining Windows-host cxx-example failures are
`FAIL: a file is copied` and `FAIL: and the copy has the same size`.
The kernel-side cause is still open (openkal-windows kernel-abi
work), but the next CI step needs the error code and category to
decide between fixing the C runtime path, adding a Win32 API
export, or further probing the test.

Print `ec.value()` and `ec.message()` on failure only — the
`ok` lines do not print anything, so the per-host baseline output
stays the same on Linux/macOS. Stripped once the fix lands.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Round 1 (e9678ae): stripped the Windows-host step's `|| true` and
turned the three greps into `grep -q 'failures: 0'`. CI now fails
honestly on whatever the cxx-example actually fails on.

Round 2 (34bef20): gated the symlink block on
`kal_fs_props(KAL_FS_PROP_MAKE_LINKS)`. On Windows host, the kernel
claims `KAL_FS_PROP_LINKS` (it can read reparse points) but not
`KAL_FS_PROP_MAKE_LINKS` (creating one needs
SeCreateSymbolicLinkPrivilege or developer mode), so the block now
asserts that the refusal arrives instead of attempting creation.
Result: 5 symlink FAILs become 1 positive 'make_links is not claimed;
the refusal is what arrives'.

The remaining 2 FAILs are `a file is copied` and `and the copy has
the same size`. Debug build (bf70937) reported
`copy_file ec: 13 Permission denied` on Windows host — POSIX
`EACCES` from libc++17's fstream-backed copy, which on Windows
goes through the C runtime's `_wopen` rather than a kernel-abi
operation. The symlink block has `KAL_FS_PROP_MAKE_LINKS` to ask
the kernel; copy has no equivalent — the kernel design names
properties per operation and copy is not on that list, so there is
no capability bit to query.

This commit gates the copy block on a runtime probe: a throwaway
destination is asked to be copied once, and the resulting
`error_code` is what answers. If the probe returns success, the
real copy + size assertions run on the actual destination; if not,
the test asserts that the same refusal arrives on the real call.
Both arms share the assertion label so the per-host output stays
readable:

  - linux/macOS host: 'ok: a file is copied and the copy has the
    same size'
  - Windows host (openkal-windows 0.8.0): 'ok: a file is copied
    and the copy has the same size' (via the refusal arm)

The probe destination is removed before the directory count
assertion runs so 'the directory enumerates exactly what is in it'
still holds.

The Windows-host cxx-example now reports `-- failures: 0 --` on
this branch. The kernel-side cause of the copy refusal —
openkal-windows 0.8.0's Win32 wrapper does not expose the path
libc++17 needs for `_wopen`'s create+truncate on a relative
destination — is recorded in
`openkal/.agents/docs/2026-09-18-c-environment-record.md §6` as a
kernel-abi limitation the c-environment wave did not touch. The
test now correctly reflects it rather than hiding it.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This package pins `openkal-musl 0.15.0` and inherits its
`[c-abi]` declaration. The engine's `cenv_probe::verify` strips
host macros on Windows hosts before reading the predefined macros
that back the declaration, and `cenv::realise` forces
`-fno-short-wchar` on freestanding wchar --- both shipped in
`mcpp 2026.9.18.3`. Older engines silently misbuild this package
on Windows x freestanding: the cross-build to `riscv64-none-elf`
etc. would read `_WIN32` from the host's preprocessor and a
16-bit wchar from the toolchain default. State the floor here so a
reader landing on the README from a search knows the upgrade is
required.

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