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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 商业级可用:mcpp × xlings 的裸机与嵌入式总体方案

2026-09-04 · 多仓库总体方案 · **v4:P0 引擎切片已实施**(PR #550);其余批次待实施
2026-09-04 · 多仓库总体方案 · **v5:P0 引擎切片 + B/C/D/E 四轴已实施**(PR #550、#551)

前置讨论:
[`2026-08-21-baremetal-ecosystem-assessment.md`](2026-08-21-baremetal-ecosystem-assessment.md)(七角度评估) ·
Expand Down Expand Up @@ -471,3 +471,42 @@ clang 对一次 float 乘法仍发出 `vmul.f32` —— 在没有 FPU 的 Cortex
⚠️ **软浮点行在没有 builtins 时链接不了浮点代码**(实测:`undefined symbol:
__aeabi_fmul`),这正是 §3.1.1 把 `compiler-rt-builtins` 与 C 库并列为 P0 的理由。
整数程序不受影响 —— e2e 332 的四行启动用例即为整数程序。

---

## 11. 第二轮实施(2026-09-04,PR #551)

### 11.1 六轴读数的变化

| 轴 | v4 | 现在 |
|---|---|---|
| **A 覆盖** | 🟡 引擎能编七行 | 🟡 不变(C 库源码包与板级包仍未做) |
| **B 可信** | ❌ 零真机 | 🟢 **模拟器与真机成为同一个包的两个 feature**;真机路径可声明、可解析、判据齐备,尚无实机运行记录 |
| **C 闭环** | ❌ 三个槽都没有 | ✅ `flash`/`monitor`/`debug` + `runner-exclusive`,四槽一读点 |
| **D 可复现** | ❌ `--locked` 不存在 | ✅ `--locked`/`--frozen` 断言并点名漂移;关掉快路径以免空转 |
| **E 可交付** | ❌ 全空白 | 🟢 `mcpp sbom`(CycloneDX 1.5)+ `docs/19` 支持窗口;许可闭包门与离线快照仍未做 |
| **F 可扩展** | ✅ | ✅ 未受损:新板 = 新包,引擎 diff 为零 |

### 11.2 ⭐⭐ 方案 §2.2 的判断被实施证实,§2.3 的被加强

* **两值语义是对的。** `debug` 起服务端、客户端归 IDE 这条边界成立,`debug` 与
`monitor` 在实现里逐字段同形,没有出现方案担心的「会话协议」。
* **`runner-exclusive` 比方案写的更必要。** 方案说它是「第一块真板挖出的一列」;
实施时发现它还必须**只紧不松** —— 图里任何一个包知道设备是互斥的,它就是互斥
的,后来的包保持沉默不得放松它。

### 11.3 ⚠️ 实施挖出的、方案没有的两条

1. **一条规则的第二份拷贝。** 依赖提供的 RunGlobal 条目抵达根工程走的是与
`apply()` **不同**的路径(`prepare.cppm` 的 BFS 之后)。只接了前者时,
`mcpp flash` 报「没有配置」而 `mcpp run` 找得到同一个构建程序发出的 runner。
两处现在都遍历槽表。
2. **快路径会让新槽与 `--locked` 双双空转。** `try_fast_run` 直接 exec 缓存产物,
于是 `mcpp flash` 打印 `Running target/…/bin/p`;`try_fast_build` 跳过解析,
于是被改坏的锁通过了 `--locked`。两处都按**性质**设闸(槽是不是 run、是不是
要求断言),不是按旗标。

### 11.4 仍未做

`mcpplibs/picolibc` + `compiler-rt-builtins` 源码包 · 三个板级包 · `xim:probe-rs` ·
真机 CI · 许可闭包门(`--deny-license`)· 离线整仓快照 · openarch 第四后端(P3)。

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ jobs:
tests/e2e/131_freestanding_bsp_supplies_everything.sh \
tests/e2e/132_freestanding_test_and_artifacts.sh \
tests/e2e/133_freestanding_std_subset.sh \
tests/e2e/332_cortex_m_builds_and_boots.sh; do
tests/e2e/332_cortex_m_builds_and_boots.sh \
tests/e2e/336_armv7a_builds_and_boots.sh; do
echo "=== $t ==="
bash "$t" 2>&1 | tee "$(basename "$t").log"
rc=${PIPESTATUS[0]}
Expand Down Expand Up @@ -221,6 +222,15 @@ jobs:
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; }
# ⚠️ 336 IS HERE FOR THE SAME REASON, AND ADDING IT TO THE LOOP ABOVE
# WITHOUT ADDING IT HERE WOULD HAVE BEEN THE SAME DEFECT: it declares
# `# requires: qemu-arm`, so on a shard it exits 0 having run nothing.
grep -q 'PASS: armv7-a rows build, boot' \
336_armv7a_builds_and_boots.sh.log || {
echo "336 (armv7-a) skipped on the runner that must run it"; exit 1; }
a32=$(grep -c 'booted on virt' 336_armv7a_builds_and_boots.sh.log || true)
[ "$a32" = "2" ] || {
echo "336 booted $a32 rows, expected 2"; exit 1; }

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

## [2026.9.4.2] — 2026-09-04

runner 有了名字,工具有了档位,`--locked` 成为断言,`mcpp emit sbom`。

### ⭐⭐ 一条命令,加具名的例外

`mcpp run` 覆盖常见情形的**全部,真实硬件也一样**。在设备上「运行一个程序」意味着
写进去、复位、接上输出、读回退出状态 —— 这是**一条**命令(`probe-rs run`、
`qemu-system-* -kernel`),不是几条。板级包把它作为**默认** runner,于是开发者从
模拟器换到真板时,**敲的命令不变**。

```bash
mcpp run # 默认;模拟器与真板同一条
mcpp run --runner flash # 具名的例外:只写不跑、看串口、起调试服务端、擦片
mcpp run --list-runners # 这个工程提供了哪些
```

⚠️ **引擎不认识任何 runner 名字。** `flash`、`serve`、`deploy`、`submit`、
`logcat` 对它一样陌生。**引擎里若有一份固定的名字表,就等于由引擎决定哪些领域可被
表达** —— 一个 web 包将无法自己加 `serve`。

⭐ **写程序名,不要写路径。** mcpp 先找本包 `[xlings] deps` 声明的载荷 `bin/`,
再找 `PATH`。用 `xpkg_dir` 拼绝对路径是多余的,而且引入了一个失败模式:声明不是
安装,查询返回空则没有配置任何 runner 而无话可说。

⚠️ 是否终止由 `mcpp::runner_longlived(name)` **声明**:
`openocd -c "program … exit"` 会终止而 `openocd -c "init"` 不会,拼写到最后一个
参数为止都一样,没有任何 argv 能表达这个区别。

`mcpp::run_exclusive()` 陈述「这个目标的运行不能重叠」—— 对一块板、一张 GPU、
一个串口、一个单席位 license 同样成立,`mcpp test` 据此串行化。

### `--locked`

锁一直是解析之后写、从不读回。现在它是断言:发生的解析必须等于记录的解析,不等则
**点名移动了的包与两个版本**。

⚠️⚠️ **它绝不能遇上快路径。** 实测:加闸之前,一份被故意改坏的锁通过了
`mcpp build --locked` 并打印 `Finished` —— 旗标被接受、构建正确、**断言从未跑到**。

### `mcpp emit sbom`

CycloneDX 1.5,覆盖**已记录**的解析。⚠️ 读锁而不是重新解析 —— 一份描述了与所构建
者不同的图的文档比没有更糟。归在 `emit` 之下而不是新开一级命令:`emit` 已经是
「生成描述本工程的文档」。

### ⭐⭐ 工具有了档位,而且依赖声明的工具现在真的会被装

包依赖从一开始就有 `[dependencies]` / `[build-dependencies]` / `[dev-dependencies]`
这条轴,工具只有一张表。一个同时点名模拟器与调试探针的板级包,会把两个都装给每一位
消费者,包括只想把库编出来的那一位。

```toml
[xlings.workspace]
"xim:qemu-arm" = "9.2.4-1" # 不写就是从前的行为
"xim:probe-rs" = { version = "0.24.0", when = "run" }

[feature-xlings.hardware]
"xim:probe-rs" = "0.24.0" # 不要这个 feature 就永不下载
```

| `when` | 由谁安装 | 传播到消费者 |
|---|---|---|
| *(不写)* | 每个构建命令 | 是 |
| `build` | 每个构建命令 | 是 |
| `run` | `mcpp run`、`mcpp test` | 是 |
| `dev` | 只有声明它的那个包作为根时 | **否** |

⭐ **不写 `when` 保持今天的行为,所以没有迁移。**

⚠️⚠️ **同时:`[xlings.workspace]` 的供给扩到全图。** 在此之前只有根工程的声明会被
安装,而查找(runner 按裸名找程序)已经跨全图 —— **在没有任何东西安装过的目录里
查找,是只可能失败的查找**。两者现在由同一个表达式定义。

⚠️ 档位带来的一个危险已被堵上:`mcpp build` 装得比 `mcpp run` 需要的少,而 run 的
快路径正是为跳过那一步存在的。构建缓存记下「这次构建留下了未安装的 run 档工具」,
`mcpp run` 的快路径据此拒绝该条目 —— 与它拒绝声明了 runner 的条目同理。

判据不测「装成了没有」,而测**mcpp 要装什么**:`MCPP_NO_AUTO_INSTALL=1` 下拒绝供给
并**点名它本来要装的集合**,于是 `build` 与 `run` 两条命令的差集就是被测的性质,
一次下载都不需要(`tests/e2e/335`)。

### ⭐ ARMv7-A:第一个带内存管理单元的 32 位目标行

`armv7a-none-eabi` 与 `armv7a-none-eabihf` 两行,`verified`。表里其余每个 32 位行都是
M-profile:MPU 按基址与上限描述区域,没有页表项。A-profile 有真正的 MMU,于是它是第一个
能被问「**32 位**机器的页表项长什么样」的目标 —— 这正是 openarch 的地址空间抽象从未被
一台 32 位机器问过的问题。

实测 2026-09-04(`xim:qemu-arm@9.2.4-1`):两行都在 `-M virt -cpu cortex-a15` 上启动、
经半主机打印并报回退出状态。

⚠️ **软浮点行同样需要 `-mfpu=none`,而这是在这个架构上重新实测的**,不是从 M-profile
推过来的:`armv7a-none-eabi` 在软浮点 ABI 下对一次 float 乘法仍发出 VFP 指令。

⚠️ **半主机的退出调用与 M-profile 拼法不同。** AArch32 的 `SYS_EXIT`(`0x18`)把原因码
**直接**放在 `r1`;Cortex-M 传的 `{reason, code}` 块是 `SYS_EXIT_EXTENDED`(`0x20`)。
实测:把块传给 `0x18` 打印正确而**退出状态是错的**,只看输出的测试看不出来。

⭐ 那条量化 `-mfpu=none` 的单元测试,谓词曾是 `starts_with("thumb")` —— 一个**拼法**
而不是它要陈述的性质。新行加进来时规则适用而测试**静默跳过**了它们,每条断言依然通过。
谓词已改为「32 位 ARM」。

### 发现性

`mcpp why runners` 列出本工程提供的 runner,与其余解析结果并列;
`mcpp run --list-runners` 是同一份读取,单独报告。

新增 `docs/18-devices.md`、`docs/19-supported-versions.md`(中英双份)。
指令协议版本 6。

## [2026.9.4.1] — 2026-09-04

Cortex-M 落地为七个目标行,freestanding 链接开启死代码段消除。
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ the right toolchain payload is resolved and installed automatically.
| `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 ² | ✅ |
| `armv7a-none-eabi` · `armv7a-none-eabihf` | llvm 22 — Cortex-A 32-bit, bare metal ² | ✅ |
| `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` | — | 🔄 |
Expand Down
59 changes: 59 additions & 0 deletions docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -1920,6 +1920,65 @@ triples say one, descriptors and xlings' project file say the other) and both
are accepted wherever a platform is named. A table with no key for this host
and no `default` declares nothing here.

#### `when` — which verbs need this tool (mcpp 2026.9.4.2+)

```toml
[xlings.workspace]
"xim:qemu-arm" = "9.2.4-1" # every build, as before
"xim:codegen" = { version = "1.0", when = "build" }
"xim:probe-rs" = { version = "0.24.0", when = "run" }
"xim:clang-tidy"= { version = "20", when = "dev" }
```

Package dependencies have had this axis since the beginning —
`[dependencies]`, `[build-dependencies]`, `[dev-dependencies]`. Tools had one
list, so a board-support package that named both an emulator and a debug probe
installed both for every consumer, including one that only wanted the library
to compile.

| `when` | Installed by | Reaches a consumer |
|---|---|---|
| *(omitted)* | every verb that builds | yes |
| `build` | every verb that builds | yes |
| `run` | `mcpp run`, `mcpp test` | yes |
| `dev` | only the package that declared it, as the root | **no** |

**Omitting `when` is the pre-2026.9.4.2 behaviour exactly**, so no manifest has
to change. Narrowing is optional; it is not a question an author has to answer.

`dev` is the only tier that does not propagate. It means *"while the package
that declared this is itself being developed"*, so a dependency's `dev` entry is
never installed for a consumer. Every other tier does reach one, which is the
point of a board package knowing its own machine: it declares the emulator once
and every consumer gets it.

The tier is written on the entry rather than as a second table, on the same
reasoning that makes `[dependencies]` accept both `dep = "1.0"` and
`dep = { version = "1.0", features = [...] }`. A scoped entry must name
`version` even to leave it empty (`version = ""` means *present, any version*),
because `{ when = "run" }` and a misspelt `version` key would otherwise be
indistinguishable.

#### `[feature-xlings.<feature>]` — a tool a feature needs

```toml
[features]
default = ["emulator"]
emulator = {}
hardware = {}

[feature-xlings.hardware]
"xim:probe-rs" = "0.24.0"
```

The same table, gated on a feature, spelled the way `[feature-deps.<feature>]`
is. A consumer who never asks for `hardware` never downloads a probe driver.
Entries here accept `when` exactly as the unconditional ones do.

A feature name no `[features]` table declares is reported as a schema warning:
it activates for nobody and installs nothing, and a tool whose absence is only
visible as *"the device is never reachable"* is the hardest kind to diagnose.

#### Which version a tool the project did not name resolves to

| The project declares | The version comes from |
Expand Down
22 changes: 21 additions & 1 deletion docs/13-baremetal.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ 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 eleven of them:
at `modules/toolchain-model/src/triple.cppm` carries thirteen of them:

| Triple | Tier | C library |
|---|---|---|
Expand All @@ -28,10 +28,30 @@ at `modules/toolchain-model/src/triple.cppm` carries eleven of them:
| `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 |
| `armv7a-none-eabi` | verified | none by default — Cortex-A 32-bit, soft float |
| `armv7a-none-eabihf` | verified | none by default — Cortex-A 32-bit, hard float |

`verified` means an image has been built **and run** for the row. `preview`
means it builds and links, and no emulator run has been recorded.

### ARMv7-A is the first 32-bit row with a memory management unit

Every other 32-bit row above is M-profile: an MPU that describes regions by base
and limit, and no page-table entry at all. A-profile has a real MMU with a
walker, so it is the first target on which an address-space abstraction can be
asked what a *32-bit* machine's entry looks like — short descriptors are 32 bits
wide, long (LPAE) ones 64. That question cannot be put to a machine with no
entries, which is why `openarch`'s Cortex-M backend declines the capability.

**The semihosting exit call is not spelled the way M-profile spells it.**
`SYS_EXIT` (`0x18`) on AArch32 takes the reason code in `r1` *directly*; the
`{reason, code}` block a Cortex-M board passes is `SYS_EXIT_EXTENDED` (`0x20`),
which exists because a 32-bit `r1` cannot carry both. Measured: passing the
block to `0x18` prints correctly and then reports the wrong exit status, so a
board that only checks its output cannot see the difference. This is a *board*
fact rather than a target fact; it is recorded here because it is where the next
person writing such a board will look.

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

Every other bare-metal family above is one row per architecture. Cortex-M is
Expand Down
10 changes: 8 additions & 2 deletions docs/17-the-project-environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ exists for one host platform only is therefore declared for that platform
on Linux and nothing elsewhere. The keys and the resolution rule are in
chapter 5, §2.13.

**The runner.** A program under `[xlings] deps` is also where
**Which verbs install it.** An entry may name a tier —
`{ version = "0.24.0", when = "run" }` — and a `[feature-xlings.<feature>]`
table gates one on a feature. A tool the project will not use is then not
downloaded: chapter 5, §2.13. Omitting the tier is the historical behaviour.

**The runner.** A program under `[xlings.workspace]` is also where
`[target.<triple>].runner` looks first for its first element, before `PATH`
(chapter 5, §2.7.3). The two keys together provision a user-mode emulator on a
CI host and execute a cross-built artifact through it, without the manifest
Expand All @@ -164,7 +169,8 @@ naming the payload's path.
| a library the program links | `[dependencies]` |
| the compiler | `[toolchain]`, chapter 3 |
| a host tool a dependency produces | `tools = [...]`, chapter 7 |
| a tool present in the environment | `[xlings] deps` |
| a tool present in the environment | `[xlings.workspace]` |
| a tool only one verb or one feature needs | `when = "run"`, `[feature-xlings.<f>]` |
| which environment | `[xlings] subos` |

## 7. Related chapters
Expand Down
Loading
Loading