You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(freestanding): Cortex-M as seven target rows, and dead-section elimination (2026.9.4.1)
The bare-metal target table grows from four rows to eleven. M-profile is seven
rows rather than one: an object built for `thumbv7em` uses instructions a
Cortex-M0 does not have, so the two spellings produce incompatible objects
rather than expressing a preference. The table exists so that `--target
<triple>` alone suffices to produce a correct object file, and a single
`arm-none-eabi` row plus an `-mcpu` each project remembered would move a
correctness decision out of the table and into every manifest.
⚠️ The float ABI does not settle whether the FPU is used. clang derives
`-mfloat-abi` from the `eabi`/`eabihf` suffix without help, but that governs how
floating-point values cross a function boundary, not what the compiler may emit
inside one — and `thumbv7em` implies FPv4-SP. Measured on llvm 22.1.8: under the
soft-float ABI clang still emits `vmul.f32` for a float multiply, which faults
at run time on a Cortex-M4 with no FPU after a clean compile and a clean link.
Every soft-float row therefore carries `-mfpu=none`, including the rows whose
architecture has no FPU at all — a row states the property it guarantees rather
than inheriting it from a default that is free to change. The uniform form was
not the first draft: the flag was initially applied only where a non-zero
instruction count had been observed, which made the table record a measurement
rather than a guarantee, and the unit test quantified over the rows caught it.
Freestanding builds now compile with `-ffunction-sections -fdata-sections` and
link with `--gc-sections`, on both the driver path and the direct `ld.lld` path.
Both halves belong to the engine because a dependency's translation units must
carry them and a project cannot reach those. The flags became necessary rather
than economical when a C library began arriving from the dependency graph: a
dependency's object files enter a link unconditionally, unlike an archive member
pulled only while its symbol is undefined, and a Cortex-M part has kilobytes
where the existing bare-metal rows had megabytes.
⚠️ A linker script becomes load-bearing in a new way. An interrupt vector table
is referenced by nothing — the hardware reads it by address — so `--gc-sections`
collects it, and a board's script must say `KEEP(*(.vectors))`.
The tier column records what was run. Four rows booted under
`xim:qemu-arm@9.2.4-1` and printed over semihosting: thumbv6m on `microbit`,
thumbv7m on `mps2-an385`, thumbv7em-eabihf on `mps2-an386`, thumbv8m.main-eabi
on `mps2-an505`. The remaining three build and link and are marked `preview`.
`lldEmulation` is empty on every M-profile row: clang has a BareMetal toolchain
for arm, so these triples reach `ld.lld` through the driver as the RISC-V and
aarch64 rows do, and the x86_64 row's workaround does not recur. `mcmodel` is
empty because 32-bit ARM has no such axis.
Coverage:
* `tests/e2e/332_cortex_m_builds_and_boots.sh` boots the four verified rows,
asserts a function nothing calls is absent while the vector table survives,
and measures the float ABI from both sides — the hard row's FPU instruction
count and the soft row's link failure naming `__aeabi_fmul`, which states
that the multiply did not become an FPU instruction. Both guards were
confirmed to fail independently against a reverted engine.
* Three unit tests state the rules the e2e cannot: every soft row disables the
FPU and no hard row does (with both halves counted, so neither is vacuous),
M-profile rows need no code model and no direct lld driving, and every
freestanding row compiles with per-function sections.
* The script declares `# requires: qemu-arm`, which no sharded runner has, so
it is invoked directly from the `baremetal` job where its PASS line and a
count of four booted rows can both be demanded.
Also backfills two claims in docs/13 that 2026.8.28.2 had already overturned:
exceptions, RTTI and `import std` are available on a freestanding target when a
package provides `hosted-standard-library`. The document had continued to state
them as unconditional limitations, which would lead a reader to abandon a
capability that already works.
|`thumbv8m.main-none-eabihf`| preview | none by default — Cortex-M33F/M55F, hard float |
24
31
25
32
`verified` means an image has been built **and run** for the row. `preview`
26
-
means it builds and has been observed to run, but is not yet covered by the
27
-
engine's own emulator jobs.
33
+
means it builds and links, and no emulator run has been recorded.
34
+
35
+
### M-profile is seven rows rather than one
36
+
37
+
Every other bare-metal family above is one row per architecture. Cortex-M is
38
+
not. An object built for `thumbv7em` uses instructions a Cortex-M0 does not
39
+
have, and the two spellings produce incompatible objects rather than expressing
40
+
a preference. The table exists so that `--target <triple>` alone suffices to
41
+
produce a correct object file; a single `arm-none-eabi` row plus an `-mcpu` that
42
+
each project remembered would move a correctness decision out of the table and
43
+
into every manifest.
44
+
45
+
The `eabi`/`eabihf` suffix is the float ABI, and clang derives it from the
46
+
triple without help: measured on llvm 22.1.8, `thumbv7em-none-eabi` yields
47
+
`-mfloat-abi soft` and `thumbv7em-none-eabihf` yields `hard`.
48
+
49
+
⚠️ **The float ABI does not settle whether the FPU is used.** It governs how
50
+
floating-point values cross a function boundary, not what the compiler may emit
51
+
inside one, and the `thumbv7em` architecture implies FPv4-SP. Measured: under
52
+
the soft-float ABI clang still emits `vmul.f32` for a float multiply. On a
53
+
Cortex-M4 without an FPU that instruction faults at run time, after a clean
54
+
compile and a clean link. Every soft-float row therefore carries `-mfpu=none`,
55
+
including the rows describing architectures that have no FPU at all — a row
56
+
states the property it guarantees rather than inheriting it from a default.
57
+
58
+
Cortex-M needs no `lldEmulation` column entry: clang has a *BareMetal* toolchain
59
+
for arm, so these triples reach `ld.lld` through the driver as the RISC-V and
60
+
aarch64 rows do. 32-bit ARM has no `-mcmodel` axis, so that column is empty too.
61
+
62
+
### Dead-section elimination
63
+
64
+
Freestanding builds compile with `-ffunction-sections -fdata-sections` and link
65
+
with `--gc-sections`. Both halves belong to the engine rather than to a project
66
+
because a dependency's translation units must carry them, and a project cannot
67
+
reach those.
68
+
69
+
The flags became necessary rather than merely economical when a C library began
70
+
arriving from the dependency graph. A dependency's object files enter the link
71
+
unconditionally, unlike an archive member, which is pulled only while its symbol
72
+
is undefined. That costs nothing when the C library is a prebuilt archive and
73
+
the target has megabytes; a Cortex-M part has kilobytes, and without dead-section
74
+
elimination every image would carry the whole of the C library.
75
+
76
+
⚠️ **A linker script becomes load-bearing in a new way.** An interrupt vector
77
+
table is referenced by nothing — the hardware reads it by address — so
78
+
`--gc-sections` collects it. A board's script must say `KEEP(*(.vectors))`.
79
+
Measured: with the `KEEP` present, a function nothing calls is dropped, the
80
+
table survives, and the image boots.
28
81
29
82
⚠️ The last two rows default to no C library, and that is a statement rather
30
83
than an omission: the first consumer of both rows — the `openarch` layer of
@@ -190,8 +243,8 @@ by pointing `main` at the source file that carries `_start`.
190
243
| Linker selection | `ld.lld` is addressed by **absolute path**, derived from the driver's own directory. `-fuse-ld=lld` resolves by name and finds GNU ld on any machine with binutils earlier on `PATH`, which then fails with `unrecognised emulation mode: elf64lriscv`. |
191
244
| ISA flags | `-march`, `-mabi` and `-mcmodel` come from one row per target in `src/freestanding/target.cppm`, so `--target <triple>` alone is sufficient to produce a correct object file. |
192
245
| C library | The **target's**, resolved by mcpp from the target's own table row exactly as the compiler is. A bare-metal project declares no libc, just as a hosted project declares no glibc. The engine places the sysroot's library directory on the link search path, so a board-support package selects out of it by bare name (`-lc`, `-lcrt0-semihost`). |
193
-
| Exceptions and RTTI | Off on every translation unit in the graph, including a dependency's. There is no unwinder and no `libc++abi`, so nothing can throw; `std::optional::value()` alone would otherwise reference `__cxa_throw` and three further undefined symbols. The setting belongs to the target rather than to a project's `cxxflags` because a BMI records it, and a dependency compiled with exceptions cannot be imported by a unit without them. |
194
-
| `import std` | Unavailable, and rejected at configure time with a diagnostic rather than at link time. |
246
+
| Exceptions and RTTI | Off on every translation unit in the graph, including a dependency's, **unless a package supplies a C++ runtime built for this target**. There is otherwise no unwinder and no `libc++abi`, so nothing can throw; `std::optional::value()` alone would reference `__cxa_throw` and three further undefined symbols. The setting belongs to the target rather than to a project's `cxxflags` because a BMI records it, and a dependency compiled with exceptions cannot be imported by a unit without them. A package declaring `provides = ["hosted-standard-library"]` reverses the default: exceptions and RTTI are enabled, `-ffreestanding` is dropped, and `-fasynchronous-unwind-tables` is added. |
247
+
| `import std` | Available when a package in the graph provides `hosted-standard-library` and names its own `std` module source; otherwise rejected at configure time with a diagnostic rather than at link time. |
195
248
| Entry point | `int main()` is available whenever something supplies a `crt0`. A board-support package normally does. |
196
249
| Default linkage | Static, and not as a preference: there is no loader, so there is no other option. |
197
250
@@ -716,7 +769,7 @@ targets, but that expectation is **not** covered by a test.
716
769
| Limitation | Observed behaviour |
717
770
|---|---|
718
771
|`std::format`, `std::sort` over builtin scalar types, and a complete `std::string`| Fail at **link** time naming the undefined symbol. libc++ places these entities in the compiled library — the scalar `__sort` instantiations are `extern template`, with no macro that disables them — so a target-built `libc++.a` is required. No such payload is published. |
719
-
| Exceptions and RTTI | Disabled across the whole graph. `try`/`catch` is unavailable at compile time. A board shipping a target-built`libc++abi` and unwinder has a genuine case for re-enabling them; that is the point at which this becomes a manifest key. |
772
+
| Exceptions and RTTI | Disabled across the whole graph**unless a package provides `hosted-standard-library`**, which `mcpplibs/openkal-llvm-runtime` does by carrying `libc++`,`libc++abi` and `libunwind` configured for the target. Without such a package `try`/`catch` remains unavailable at compile time. |
720
773
| Board coverage | One board family. `riscv32-none-elf` demonstrates that the ISA table is data, not that a second machine has been ported. ARM Cortex-M has not been attempted. |
721
774
| C library substitution | Expressible since 2026.8.20.2 through `[target.<triple>].sysroot`, and **verified only for the empty value** (the zero-libc tier). Pointing it at a different C library is accepted and installed through the same channel, but no second bare-metal C library is published, so that path is untested. |
722
775
|`qemu-riscv` on `win32-arm64`| The upstream package publishes no asset for that host, so installation fails on it. The failure is correct rather than silent, but the host cannot run a bare-metal image. |
0 commit comments