Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
473 changes: 473 additions & 0 deletions .agents/docs/2026-09-04-commercial-grade-baremetal-embedded-plan.md

Large diffs are not rendered by default.

29 changes: 27 additions & 2 deletions .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:
# test, and assert its PASS line appeared. A skip fails this job.
# ──────────────────────────────────────────────────────────────────
baremetal:
name: bare-metal e2e (riscv64-none-elf, qemu)
name: bare-metal e2e (riscv64-none-elf + cortex-m, qemu)
runs-on: ubuntu-24.04
timeout-minutes: 40
env:
Expand Down Expand Up @@ -153,6 +153,18 @@ jobs:
XLINGS_HOME="${MCPP_HOME:-$HOME/.mcpp}/registry" \
"$XLINGS_BIN" install xim:picolibc-riscv -y
test -d "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs/xim-x-picolibc-riscv"
# ⚠️ The M-profile emulator, in BOTH homes for the reason above.
# `xim:qemu-arm` carries `qemu-system-arm` as well as
# `qemu-system-aarch64`; test 332 addresses it by absolute path out of
# the payload, so what matters is that the payload EXISTS in the home
# mcpp uses rather than that a shim resolves.
"$XLINGS_BIN" install xim:qemu-arm -y
XLINGS_HOME="${MCPP_HOME:-$HOME/.mcpp}/registry" \
"$XLINGS_BIN" install xim:qemu-arm -y
# Reachable AND runnable before the tests, so that a missing emulator
# fails this step rather than silently skipping test 332.
ls "${MCPP_HOME:-$HOME/.mcpp}"/registry/data/xpkgs/xim-x-qemu-arm/*/bin/qemu-system-arm \
| sort -V | tail -1 | xargs -I{} {} --version | head -1

- name: Bare-metal e2e
timeout-minutes: 25
Expand All @@ -175,7 +187,8 @@ jobs:
for t in tests/e2e/130_freestanding_riscv_build_and_run.sh \
tests/e2e/131_freestanding_bsp_supplies_everything.sh \
tests/e2e/132_freestanding_test_and_artifacts.sh \
tests/e2e/133_freestanding_std_subset.sh; do
tests/e2e/133_freestanding_std_subset.sh \
tests/e2e/332_cortex_m_builds_and_boots.sh; do
echo "=== $t ==="
bash "$t" 2>&1 | tee "$(basename "$t").log"
rc=${PIPESTATUS[0]}
Expand All @@ -196,6 +209,18 @@ jobs:
grep -q 'PASS: the freestanding std subset' \
133_freestanding_std_subset.sh.log || {
echo "133 (std subset) skipped on the runner that must run it"; exit 1; }
# ⚠️ 332 declares `# requires: qemu-arm`, which no sharded runner has
# — so on the shards it exits 0 without running. This job is the only
# place its PASS line can be demanded.
grep -q 'PASS: cortex-m rows build, boot' \
332_cortex_m_builds_and_boots.sh.log || {
echo "332 (cortex-m) skipped on the runner that must run it"; exit 1; }
# ⭐ And a count, because four `grep -q` calls that each matched say
# nothing about how many rows the script actually booted: a fixture
# that stopped iterating would still print its PASS line.
booted=$(grep -c 'booted on ' 332_cortex_m_builds_and_boots.sh.log || true)
[ "$booted" = "4" ] || {
echo "332 booted $booted rows, expected 4"; exit 1; }

# ──────────────────────────────────────────────────────────────────
# Hermetic (no host toolchain): the ONLY environment class that
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [2026.9.4.1] — 2026-09-04

Cortex-M 落地为七个目标行,freestanding 链接开启死代码段消除。

裸机目标表从四行增至十一行。M-profile 是七行而不是一行:为 `thumbv7em` 构建的
目标文件使用 Cortex-M0 没有的指令,两种拼写产出互不兼容的目标文件,而表存在的
理由正是让 `--target <triple>` 单独足以产出正确的目标文件。

```toml
[build]
target = "thumbv7em-none-eabihf"
```

⚠️ **浮点 ABI 不决定 FPU 是否被使用。** `eabi`/`eabihf` 由 clang 从 triple 读出,
它约束浮点值如何跨越函数边界,不约束函数内部发什么指令 —— 而 `thumbv7em` 架构
蕴含 FPv4-SP。实测:软浮点 ABI 下 clang 对一次 float 乘法仍发出 `vmul.f32`,在
没有 FPU 的 Cortex-M4 上于运行期触发异常,而编译与链接都是干净的。每个软浮点行
因此携带 `-mfpu=none`,包括架构本来就没有 FPU 的那几行 —— 一行陈述它保证的性质,
而不是从一个可以改变的默认值继承它。

freestanding 编译加 `-ffunction-sections -fdata-sections`、链接加 `--gc-sections`。
依赖的目标文件无条件进入链接(不像归档成员那样按未定义符号拉取),当 C 库改由
依赖图提供时,没有死代码段消除的镜像会装进整份 C 库,而 Cortex-M 器件只有几十 KB。

⚠️ **链接脚本因此以新的方式承重**:中断向量表不被任何东西引用,`--gc-sections`
会回收它,板级脚本必须写 `KEEP(*(.vectors))`。

同时回填了 `docs/13` 中两条已被 2026.8.28.2 推翻的限制:当图中有包提供
`hosted-standard-library` 时,裸机目标上的异常、RTTI 与 `import std` 均可用。

## [2026.9.3.2] — 2026-09-03

`[xlings.workspace]` 的**推荐书写形态**定为命名空间在键上,官方包全部使用它;
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,11 @@ the right toolchain payload is resolved and installed automatically.
| `aarch64-macos` | llvm *(macOS default)* | ✅ |
| `riscv64-none-elf` | llvm 22 — bare metal, no OS; needs no per-host cross payload ² | ✅ |
| `riscv32-none-elf` | llvm 22 — bare metal, no OS; needs no per-host cross payload ² | ✅ |
| `thumbv6m-none-eabi` | llvm 22 — Cortex-M0/M0+/M1, bare metal ² | ✅ |
| `thumbv7m-none-eabi` | llvm 22 — Cortex-M3, bare metal ² | ✅ |
| `thumbv7em-none-eabihf` | llvm 22 — Cortex-M4F/M7F, hard float ² | ✅ |
| `thumbv8m.main-none-eabi` | llvm 22 — Cortex-M33/M55, soft float ² | ✅ |
| `thumbv7em-none-eabi` · `thumbv8m.base-none-eabi` · `thumbv8m.main-none-eabihf` | llvm 22 — builds and links; no emulator run recorded | 🔄 |
| `riscv64-linux-musl` | — | 🔄 |
| `aarch64-linux-gnu` | — | 🔄 |
| `x86_64-macos` | — | 🔄 |
Expand Down
65 changes: 59 additions & 6 deletions docs/13-baremetal.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,71 @@ covers the hosted link model this chapter departs from.
## Overview

A freestanding target is a target whose `os` field is `none`. The target table
at `modules/toolchain-model/src/triple.cppm` carries four of them:
at `modules/toolchain-model/src/triple.cppm` carries eleven of them:

| Triple | Tier | C library |
|---|---|---|
| `riscv64-none-elf` | verified | `xim:picolibc-riscv` |
| `riscv32-none-elf` | verified | `xim:picolibc-riscv` |
| `aarch64-none-elf` | preview | none by default — the zero-libc tier; `xim:picolibc-aarch64` is declarable |
| `x86_64-none-elf` | preview | none by default — the zero-libc tier; `xim:picolibc-x86` is declarable |
| `thumbv6m-none-eabi` | verified | none by default — Cortex-M0/M0+/M1 |
| `thumbv7m-none-eabi` | verified | none by default — Cortex-M3 |
| `thumbv7em-none-eabi` | preview | none by default — Cortex-M4/M7, soft float |
| `thumbv7em-none-eabihf` | verified | none by default — Cortex-M4F/M7F, hard float |
| `thumbv8m.base-none-eabi` | preview | none by default — Cortex-M23 |
| `thumbv8m.main-none-eabi` | verified | none by default — Cortex-M33/M55, soft float |
| `thumbv8m.main-none-eabihf` | preview | none by default — Cortex-M33F/M55F, hard float |

`verified` means an image has been built **and run** for the row. `preview`
means it builds and has been observed to run, but is not yet covered by the
engine's own emulator jobs.
means it builds and links, and no emulator run has been recorded.

### M-profile is seven rows rather than one

Every other bare-metal family above is one row per architecture. Cortex-M is
not. An object built for `thumbv7em` uses instructions a Cortex-M0 does not
have, and 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; a single `arm-none-eabi` row plus an `-mcpu` that
each project remembered would move a correctness decision out of the table and
into every manifest.

The `eabi`/`eabihf` suffix is the float ABI, and clang derives it from the
triple without help: measured on llvm 22.1.8, `thumbv7em-none-eabi` yields
`-mfloat-abi soft` and `thumbv7em-none-eabihf` yields `hard`.

⚠️ **The float ABI does not settle whether the FPU is used.** It governs how
floating-point values cross a function boundary, not what the compiler may emit
inside one, and the `thumbv7em` architecture implies FPv4-SP. Measured: under
the soft-float ABI clang still emits `vmul.f32` for a float multiply. On a
Cortex-M4 without an FPU that instruction faults at run time, after a clean
compile and a clean link. Every soft-float row therefore carries `-mfpu=none`,
including the rows describing architectures that have no FPU at all — a row
states the property it guarantees rather than inheriting it from a default.

Cortex-M needs no `lldEmulation` column entry: clang has a *BareMetal* toolchain
for arm, so these triples reach `ld.lld` through the driver as the RISC-V and
aarch64 rows do. 32-bit ARM has no `-mcmodel` axis, so that column is empty too.

### Dead-section elimination

Freestanding builds compile with `-ffunction-sections -fdata-sections` and link
with `--gc-sections`. Both halves belong to the engine rather than to a project
because a dependency's translation units must carry them, and a project cannot
reach those.

The flags became necessary rather than merely economical when a C library began
arriving from the dependency graph. A dependency's object files enter the link
unconditionally, unlike an archive member, which is pulled only while its symbol
is undefined. That costs nothing when the C library is a prebuilt archive and
the target has megabytes; a Cortex-M part has kilobytes, and without dead-section
elimination every image would carry the whole of the C library.

⚠️ **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. A board's script must say `KEEP(*(.vectors))`.
Measured: with the `KEEP` present, a function nothing calls is dropped, the
table survives, and the image boots.

⚠️ The last two rows default to no C library, and that is a statement rather
than an omission: the first consumer of both rows — the `openarch` layer of
Expand Down Expand Up @@ -190,8 +243,8 @@ by pointing `main` at the source file that carries `_start`.
| 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`. |
| 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. |
| 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`). |
| 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. |
| `import std` | Unavailable, and rejected at configure time with a diagnostic rather than at link time. |
| 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. |
| `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. |
| Entry point | `int main()` is available whenever something supplies a `crt0`. A board-support package normally does. |
| Default linkage | Static, and not as a preference: there is no loader, so there is no other option. |

Expand Down Expand Up @@ -716,7 +769,7 @@ targets, but that expectation is **not** covered by a test.
| Limitation | Observed behaviour |
|---|---|
| `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. |
| 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. |
| 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. |
| 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. |
| 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. |
| `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. |
Expand Down
7 changes: 7 additions & 0 deletions docs/16-the-target-triple.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,13 @@ other's rows.
| `riscv32-none-elf` | verified | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `aarch64-none-elf` | preview | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `x86_64-none-elf` | preview | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv6m-none-eabi` | verified | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv7m-none-eabi` | verified | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv7em-none-eabi` | preview | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv7em-none-eabihf` | verified | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv8m.base-none-eabi` | preview | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv8m.main-none-eabi` | verified | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |
| `thumbv8m.main-none-eabihf` | preview | `llvm@22.1.8` | ✅ payload | ✅ payload | ✅ payload | ✅ payload |

`✅ payload` a toolchain payload here produces it · `⚙ graph` no payload, but a
dependency can supply the system · `✅ system` located on the machine, not
Expand Down
Loading
Loading