Skip to content

Commit 968377c

Browse files
committed
docs: a bare-metal chapter, and the diagnostic promise it found broken
Bare-metal support landed across #455-#459 but had no user-facing page. What existed was docs/05 §2.7.2 — the manifest reference for `[target.*]` — and an outlook section in docs/08 written before the work. Adds docs/13 (en + zh): the two commands that produce a booting image, what a freestanding target changes, the engine/target/board layering, worked examples (ISA width, the freestanding std subset, `mcpp test` on the target, the flashing artifact set, runner override), the two diagnostics, and how to write a board support package. ## The measurements are re-taken, not copied Every transcript was measured on 2026-08-20 with mcpp 2026.8.20.1 built from this tree, on x86_64-linux-gnu. That mattered: the recorded scenario notes say `text 8844` and this build measures `text 8572`, so a copied number would have been wrong on arrival. The chapter says so, and gives its component versions. Two claims that could have been repeated on trust were checked instead: * 103 of 110 headers — counted in both trees (`103` distinct `.inc` in the package, `110` `std/*.inc` in the llvm 22.1.8 payload); * five host targets for `xim:qemu-riscv` — read out of the descriptor (linux x64/arm64, darwin x64/arm64, win32 x64), which also carries the comment explaining why win32-arm64 is absent. The one claim not verified here is labelled as such: the 7 omitted headers are reported by the package to fail on a hosted x86_64 too. ## The defect writing it exposed The freestanding `import std` diagnostic ends in a copy-pasteable dependency line, and prepare.cppm carries a comment saying that line is a PROMISE which has to resolve today — because it once named a package that did not exist. It was broken again, in a second form. The line said `"0.1.0"` after `0.2.0` superseded it in the index, and 0.1.0 is not published: E_NOT_FOUND: package 'compat.std-freestanding@0.1.0' not found in the synced index So the version is part of the promise, not decoration. Fixed to `"0.2.0"` and verified end to end: trigger the diagnostic, paste its line, `mcpp run` prints `value 42`. The comment now records this recurrence, since the first note was not enough to prevent it. ## Adjacent documents * README target table gained `riscv64-none-elf` / `riscv32-none-elf` — both tier `verified` in triple.cppm, both executed under qemu by the `baremetal` CI job, and neither was listed. * docs/08 §7.3 stops being an outlook. Three of its predictions held; one was wrong in the way that matters — the C library is NOT inside the toolchain payload, it is a separate payload named by the target's own row, and that is what keeps a bare-metal package from having to name a libc. * docs/05 §2.7.2 stays as the manifest reference and now points at docs/13. `bash .github/tools/check_docs_style.sh` passes; the bilingual heading structure is identical by construction. `mcpp build` succeeds with the prepare.cppm change.
1 parent d98e972 commit 968377c

10 files changed

Lines changed: 1041 additions & 40 deletions

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ the right toolchain payload is resolved and installed automatically.
380380
| `x86_64-windows-gnu` | gcc 16 MinGW-w64 — native on Windows, cross from Linux (wine-verified) *(Windows default without Visual Studio)* ||
381381
| `x86_64-windows-msvc` | `msvc@system` (detected VS/BuildTools) or llvm ¹ *(Windows default with Visual Studio)* ||
382382
| `aarch64-macos` | llvm *(macOS default)* ||
383+
| `riscv64-none-elf` | llvm 22 — bare metal, no OS; needs no per-host cross payload ² ||
384+
| `riscv32-none-elf` | llvm 22 — bare metal, no OS; needs no per-host cross payload ² ||
383385
| `riscv64-linux-musl` || 🔄 |
384386
| `aarch64-linux-gnu` || 🔄 |
385387
| `x86_64-macos` || 🔄 |
@@ -400,6 +402,12 @@ the right toolchain payload is resolved and installed automatically.
400402
> or configure; `mcpp new && mcpp build` just works on a stock Windows box.
401403
> An explicit `[toolchain]` in `mcpp.toml` is always respected as written —
402404
> mcpp revises its own default, never yours.
405+
>
406+
> ² The bare-metal rows carry no operating system: clang and lld are
407+
> cross-compilers by construction, so any host that can install the LLVM
408+
> payload produces these targets. The C library, startup code, memory layout
409+
> and emulator travel with a board-support package rather than with mcpp — see
410+
> [docs/13 — Bare-Metal and Freestanding Targets](docs/13-baremetal.md).
403411
404412
## Documentation
405413

docs/05-mcpp-toml.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,11 @@ underneath. They need no per-host cross toolchain: clang and lld are
904904
cross-compilers by construction, so any host that can install the llvm payload
905905
can produce them.
906906

907+
This section is the manifest reference. The worked examples — scaffolding,
908+
running, testing on the target, the freestanding standard-library subset and
909+
writing a board-support package — are in
910+
[13 — Bare-Metal and Freestanding Targets](13-baremetal.md).
911+
907912
```bash
908913
mcpp build --target riscv64-none-elf
909914
mcpp run --target riscv64-none-elf # via [target.<triple>].runner

docs/08-toolchain-internals.md

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -528,27 +528,38 @@ The machinery is already arch-parameterized; the work is data:
528528
3. nothing else: `-B`/`-L`/loader emission, the fixup pipeline, and the
529529
hermetic check are all name-agnostic.
530530

531-
### 7.3 Embedded / bare-metal toolchains (outlook)
532-
533-
The model extends naturally to `arm-none-eabi`-class toolchains because the
534-
hard parts of the hosted world *disappear* rather than multiply:
535-
536-
- **No dynamic linker**: `loader` stays empty — already legal everywhere
537-
(renderers omit `--dynamic-linker`; the pack/deploy story is flashing, not
538-
ELF interp).
539-
- **No glibc payload**: newlib/picolibc live inside the toolchain's own
540-
sysroot ⇒ `CLibMode::Sysroot`, the exact mode self-contained musl uses
541-
today. `is_musl_target`-style self-containment detection generalizes to a
542-
capability flag ("ships own C library").
543-
- **Fixup kind = none or gcc-like** depending on how the payload is built
544-
(a cross gcc payload still wants PT_INTERP/RUNPATH alignment for the
545-
*host-run* compiler binaries — that part is identical to today's gcc kind;
546-
the *target* side needs nothing).
547-
- **Hermetic check** generalizes: assert crt0/semihosting stubs resolve
548-
inside the toolchain payload instead of Scrt1.o/loader.
549-
- What genuinely needs new design: per-target `[target.'cfg(...)']` specs
550-
for MCU flags (`-mcpu`, `--specs=nosys.specs`), linker-script handling,
551-
and a run/flash story — build-graph concerns above this document's layer.
531+
### 7.3 Embedded and bare-metal toolchains
532+
533+
`riscv64-none-elf` and `riscv32-none-elf` are implemented, and the user-facing
534+
account is [13 — Bare-Metal and Freestanding Targets](13-baremetal.md). This
535+
section records how the resulting shape relates to the hosted model above.
536+
537+
Three of this section's earlier predictions held:
538+
539+
- **No dynamic linker.** `loader` stays empty, which every renderer already
540+
permitted; the deployment story is flashing rather than ELF interp.
541+
- **The target side needs no fixup.** Host-run compiler binaries still want
542+
PT_INTERP/RUNPATH alignment, identical to today's gcc kind.
543+
- **MCU flags, linker-script handling and a run story genuinely needed new
544+
design.** All three landed, and above this document's layer as predicted:
545+
ISA flags come from a one-row-per-target table in
546+
`src/freestanding/target.cppm`, the linker script arrives through the
547+
`link-script` build directive, and execution through `runner`.
548+
549+
One prediction was wrong, and the correction is the load-bearing part of the
550+
design. The C library does **not** live inside the toolchain payload, so this
551+
is not `CLibMode::Sysroot` with a different sysroot. picolibc is a separate
552+
payload named by the target's own table row
553+
(`sysroot = xim:picolibc-riscv@1.8.12` in `src/toolchain/triple.cppm`), on the
554+
same footing as that row's compiler `pin`. Resolving it from the target rather
555+
than from the toolchain is what keeps a bare-metal *package* from having to
556+
name a libc, exactly as a hosted package never names glibc.
557+
558+
The freestanding link line is also **replaced** rather than extended — see
559+
`src/freestanding/linkline.cppm` — because every hosted link flag is wrong
560+
there rather than merely unnecessary. Anything appended to the ordinary ldflags
561+
earlier in the pipeline is discarded, which is why the target sysroot's `-L`
562+
is emitted on that line and not with the generic flags.
552563

553564
### 7.4 Non-ELF platforms
554565

0 commit comments

Comments
 (0)