feat(freestanding): exceptions off graph-wide, and the std subset becomes usable - #459
Open
Sunrisepeak wants to merge 2 commits into
Open
feat(freestanding): exceptions off graph-wide, and the std subset becomes usable#459Sunrisepeak wants to merge 2 commits into
Sunrisepeak wants to merge 2 commits into
Conversation
…omes usable
The freestanding std subset was described in the phase-3 wrap-up as "a separate
piece of work". That was wrong, and the research it contradicted had already
measured it. Reproduced here on the shipped payloads: with a synthesised
`__config_site`, 103 of libc++'s 110 headers compile for riscv64-none-elf, and
all 7 that fail also fail on an x86_64 host with full libc++ and glibc — they
are headers libc++ has not implemented. The freestanding compile-time loss is
zero. `-nostdinc++` is the MECHANISM of that route, not an obstacle: it keeps
libc++'s headers private to the subset package, which exports a module — the
same shape the board package already uses for the target's C library.
What actually blocked it was exceptions, and that is a whole-graph property:
* `std::optional::value()` alone pulls in `__cxa_throw`, two vtables and
`std::bad_optional_access::~bad_optional_access`, none of which can exist
without an unwinder or libc++abi.
* A project's own `[build] cxxflags` cannot fix it: they do not reach a
dependency's module compile, so the BMI and the importer disagree and clang
reports a `.pcm` "configuration mismatch" — naming a module file rather
than the setting that split the graph.
* `is_dialect_flag` deliberately does NOT propagate `-fno-exceptions`, on the
grounds that "dependencies may assume exceptions are available". Right for
a hosted target, exactly backwards here.
So `-fno-exceptions -fno-rtti` join `-ffreestanding` and `-nostdinc++` on the
target spec, which is the one place that reaches every unit in the graph.
⚠️ That change alone would have broken every existing bare-metal user on
upgrade. The dependency cache key carries the target TRIPLE but not the flags
the triple implies — and which flags those are is mcpp's decision, so it moves
between versions while the triple string does not. A project that had built
once got its old BMI back and a hard failure naming a .pcm. Fixed by adding a
`targetImpliedFlags` axis, empty for hosted targets so no existing key moves.
e2e/133 pins the whole chain in the emulator, from both sides: the subset
builds, runs `ranges::sort` with a projection plus optional/atomic/span/
string_view on the target, and `std::mutex` fails to COMPILE — capabilities
turned off in `__config_site` vanish rather than leaving a run-time stub.
The `import std` diagnostic names `std-freestanding` again, now that the
package is published.
…e one
Both bare-metal packages carried `[xlings] deps = ["xim:picolibc-riscv@1.8.12"]`
and the standard-library subset also carried `xim:llvm`. That pinned a board
package and an implementation-neutral library alike to one libc, one ISA, one
toolchain and one version of each — none of which is a property of either.
The cause was a gap in the target model rather than sloppy packaging. A hosted
target has always had its C library resolved for it: musl rides inside its gcc
payload and glibc arrives through PayloadPaths, which is why nobody writes
`xim:glibc` in a manifest. A freestanding target pins a generic clang, which
carries no target libc, and there was no axis for one — so the requirement
leaked outward into every package.
kKnownTargets now names the target's C library beside its compiler pin. It is
installed through the same channel `[xlings] deps` already use, its headers go
on every compile line and its directory on the link search path. Location is a
target fact; selection stays a board fact — a board still chooses
`-lcrt0-semihost` over a UART crt0, and still names its linker script.
Two interfaces let a package ask instead of declare: `mcpp::toolchain_dir()`
for headers that ship with the toolchain (libc++'s, for the freestanding
subset) and `mcpp::sysroot_dir()` for a file inside the target's C library (a
linker script). Both follow whatever `[toolchain]` and `--target` resolved.
Three things this turned up:
* `-L` appended to the ordinary ldflags is discarded — a freestanding link
line is replaced wholesale, so it has to go through LinkInputs. Measured:
the flag was built and then simply was not there.
* `link-script` resolves a bare name against the PACKAGE root, so a board
cannot rely on the linker's search path for it; it asks for the sysroot.
* e2e/131's privacy assertion was testing the wrong thing. It proved a
dependency's `include-dir` stays private by showing `#include <stdio.h>`
fails — but with the libc owned by the target that now correctly SUCCEEDS,
exactly as on a hosted build. Split into two: the target's C headers must
reach the consumer, and a header the BOARD ships must not.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The freestanding std subset was described in the phase-3 wrap-up as "a separate
piece of work". That was wrong, and the research it contradicted had already
measured it. Reproduced here on the shipped payloads: with a synthesised
__config_site, 103 of libc++'s 110 headers compile for riscv64-none-elf, andall 7 that fail also fail on an x86_64 host with full libc++ and glibc — they
are headers libc++ has not implemented. The freestanding compile-time loss is
zero.
-nostdinc++is the MECHANISM of that route, not an obstacle: it keepslibc++'s headers private to the subset package, which exports a module — the
same shape the board package already uses for the target's C library.
What actually blocked it was exceptions, and that is a whole-graph property:
std::optional::value()alone pulls in__cxa_throw, two vtables andstd::bad_optional_access::~bad_optional_access, none of which can existwithout an unwinder or libc++abi.
[build] cxxflagscannot fix it: they do not reach adependency's module compile, so the BMI and the importer disagree and clang
reports a
.pcm"configuration mismatch" — naming a module file ratherthan the setting that split the graph.
is_dialect_flagdeliberately does NOT propagate-fno-exceptions, on thegrounds that "dependencies may assume exceptions are available". Right for
a hosted target, exactly backwards here.
So
-fno-exceptions -fno-rttijoin-ffreestandingand-nostdinc++on thetarget spec, which is the one place that reaches every unit in the graph.
upgrade. The dependency cache key carries the target TRIPLE but not the flags
the triple implies — and which flags those are is mcpp's decision, so it moves
between versions while the triple string does not. A project that had built
once got its old BMI back and a hard failure naming a .pcm. Fixed by adding a
targetImpliedFlagsaxis, empty for hosted targets so no existing key moves.e2e/133 pins the whole chain in the emulator, from both sides: the subset
builds, runs
ranges::sortwith a projection plus optional/atomic/span/string_view on the target, and
std::mutexfails to COMPILE — capabilitiesturned off in
__config_sitevanish rather than leaving a run-time stub.The
import stddiagnostic namesstd-freestandingagain, now that thepackage is published.
Ships as 2026.8.19.4, alongside
mcpplibs/std-freestanding0.1.0.