Skip to content
Merged
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
132 changes: 132 additions & 0 deletions .agents/docs/2026-08-21-baremetal-optimization-plan.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# 裸机方向的优化方案

⭐ **全部实施完毕(2026-08-21)。** 每一节末尾新增「已实施」小节,记录实测结果
与本文写错的地方。⚠️ 其中 §4 的判据 1 **被机制本身否掉**,而否掉它的正是尝试
实现它的过程。

2026-08-21。本文为 `2026-08-21-baremetal-ecosystem-assessment.md` 列出的四项优先事项给出
可执行方案。每项包含:成因(读码得出)、设计、实施步骤、判据、依赖与刻意不做的事。

Expand Down Expand Up @@ -143,6 +147,36 @@ motivating case(openarch 模板)不构成问题,它本来就 pin 了近期版本
* **不做严重级别分层(note/warn)。** 一个级别不够用之前不加第二个。
* **不做「警告即失败」开关。** 那是消费者的策略,不是引擎的。

### 1.9 已实施(mcpp 2026.8.21.2)

三个设计判断都成立,而**兼容性一节写得比实际更悲观**。

⭐ **§1.3 说用了 `mcpp::warning` 的包在老引擎上「失败形态是 clang 的
『no member named』而不是 mcpp 的诊断」。错了 —— 引擎早有那个机制。**
`mentions_missing_mcpp_api` 按**命名空间 `'mcpp'`** 匹配,所以新 API 自动被它
覆盖。实测(旧引擎 2026.8.20.3):

```
error: 'warning' is not a member of 'mcpp'
The `mcpp` build module this engine bundles does not have that name.
Either the package was written for a newer mcpp (try `mcpp self update`;
this is mcpp 2026.8.20.3), or the name is misspelled …
```

**§0 的教训在本节内部又应验一次:凭读码不足以下结论,要去跑。**

⚠️ **§1.5 预判的「唯一真实实现风险」是对的,而测试写法上有一个未预料到的坑:**
不修改任何东西的第二次构建走**全工程 fast path**(`Finished dev in 0.00s`),
根本到不了 build.mcpp 阶段 —— 断言它等于测了 fast path。回归测试必须先 `touch`
一个源文件。由此定下一条边界并写进 docs/07:**全工程 no-op 构建什么都不打印,
包括这一条。**

⭐ **判据 2 的「先看到它失败」真做了**:临时移除缓存命中处的发射,第二条断言变
红而第一条仍绿。

采用侧:`openarch` 0.6.0 与 `riscv-virt-rt` 0.5.2。干净机器实测,而
`riscv-virt-rt` 那条**归属到依赖而不是根** —— 包名由引擎传入,不由构建程序自己拼。

---

## 2. 【高】补 aarch64 / x86_64 的目标 C 库
Expand Down Expand Up @@ -226,6 +260,40 @@ aarch64 有上游端口;x86_64 的端口存在但受关注远少于 arm/riscv。
* **不追 newlib / musl 的裸机变体。** 一个实现先跑通,第二个由需求驱动。
* **不为 arm32(`arm-none-eabi`)开新行。** 那是新目标而非本项范围。

### 2.8 已实施 —— ⭐ 覆盖两个目标而不是一个,且三条缺陷全是第二个族才暴露的

§2.3 要求先测量。测量结果推翻了「可能只有 aarch64 成立」的预期:**两个都成立**
(`libc.a` 7,070,160 / 6,458,288)。

构建器泛化为**一个脚本三个族**(而不是加第二份拷贝 —— 那正是 §4/R2 要防的)。
加两个族只暴露了三条 riscv 两年没暴露的缺陷:

| 缺陷 | 症状 | riscv 为什么没暴露 |
|---|---|---|
| 稀疏检出少 `third-party` | `'siphash/SipHash.h' file not found` | `emupac.cpp` 是 aarch64 专属 |
| 没设 `CMAKE_CXX_COMPILER_TARGET` | `unknown register name 'x30' in asm` | riscv 的 builtins 全是 C 与汇编,**没有 C++ 源** |
| 链接脚本默认加载地址 | **链接成功,一跑就挂** | 0x80000000 是 riscv `virt` 的 RAM 基址 |

⭐ 第三条最值得记:**它链接成功**,只表现为机器不动。给 qemu 4GB 内存就跑起来
—— 假设由此被证实。**一个只在配置慷慨的机器上才工作的加载地址,是最坏的一种错:
它通过。**

⚠️⚠️ **x86_64 的链接问题上有一次假绿差点定案。** `-fuse-ld=lld` 看起来解决了;
把 PATH 收到只剩 coreutils 后**裸名与 `-B<llvm>/bin` 两种都倒在**
`collect2 ... [cannot find ld]` —— 之前的通过是**环境里恰好有 `lld`**。真解与
mcpp 引擎对这个 triple 的做法逐字相同:把链接从驱动手里拿走。

**判据**:aarch64 的 `printf` 镜像在 qemu `virt` **默认 128MB 下链接并运行**;
x86_64 在 `0x100000` 链接成功(⚠️ 它没有 semihosting —— 不给控制台时报
`undefined symbol: stdout` 是**正确答案**)。

⚠️ **发布时又踩了一次「照抄上一版实测数字」**:描述符里的 sha256 是加载地址修复
**之前**那一版的。发布出去的产物是对的,写错的是描述符。修完做了**四方核验**:
描述符 = 本地归档 = GLOBAL = CN。另一条:CN 端漏传 `.sha256` 侧文件,表现为
`HTTP 404`。

**§2.4 的决策按建议执行:目标表的行没有填。** 这两个包是**可声明**的,不是默认的。

---

## 3. 【中】第二块板
Expand Down Expand Up @@ -278,6 +346,34 @@ aarch64 有上游端口;x86_64 的端口存在但受关注远少于 arm/riscv。
* **不上真实硬件。** CI 里没有硬件,而一个只能在作者机器上验证的板级包,其判据无法被
他人复核。真实硬件是第三块板的事。

### 3.6 已实施(`mcpplibs/aarch64-virt-rt` 0.1.0)

⭐ **判据 2 达成,而且比预测的更强。** §3.4 预期「两块板的指令集合相同」,并预留
了它可能失败。由两个构建程序自己的 `build.mcpp.cache` 的 `d <tag>` 行实测 ——
那是引擎记下的「发出过什么」,不是读源码:

```
riscv-virt-rt ldflag runner
aarch64-virt-rt ldflag runner
```

**同样两条。** C 库体现为 `ldflag` 的**值**不同:

```
riscv-virt-rt -lcrt0-semihost -lc -lsemihost
-lclang_rt.builtins-riscv64 -T …/picolibcpp.ld
aarch64-virt-rt -T …/virt.ld
```

⇒ **「板级包」与「C 库提供者」可分离,板级包接口完整。** CI 把它写成硬断言对着
字面量比,所以任何一侧变了都必须来说明。

**§3.3 预判「零 libc 的板需要自带最小 crt0」是对的**,而它的性质比预判更准确:
那是**内容**的差别,不是**接口**的差别 —— 包多了一个 `boot.S`,指令一条没多。

⚠️ **`.bss` 不被任何别的东西清零**(`-kernel` 只加载 PT_LOAD 段),而那种失败
**数据相关、不会在写代码的机器上复现** ⇒ 例子断言 `bss zeroed` 而不是假设它。

---

## 4. 【中】把「判据放在能观察的环境里」变成可检查的
Expand Down Expand Up @@ -338,6 +434,42 @@ n=$(LD_TRACE_LOADED_OBJECTS=1 "$loader" "$bin" | wc -l)
⚠️ 判据 1 是这项唯一有意义的判据。一个对已知的三次事故都报不出来的 linter,没有理由
相信它能报出第四次。

### 4.5 已实施 —— ⚠️ 判据 1 不可达,而这是本文最重要的一处更正

`tools/lint-ci-assertions.sh` 已落地并接进 mcpp 的 CI(打印,不失败)。

⚠️⚠️ **判据 1「把 0.4.0、0.5.0 两份历史工作流喂给它,两份都必须被标出」在这个
机制下不可达,而发现这一点花了一次实现。**

0.5.0 的缺陷是可 lint 的:`if: matrix.arch == 'riscv64'` 把一个步骤门到矩阵的
一行上,一行文本就说明白了。0.4.0 的缺陷**不是**:那一步只断言了 `mcpp build`,
也只声称了这么多 —— 文件里没有任何东西是错的,它只是比仓库在别处做出的主张更
弱。**⭐ linter 检查写下来的东西,不检查没写的东西。**

新增 R4 抓的是同一个**形状**的未来版本(把执行断言放在跑不了东西的作业里),
这是这个机制能给的全部。该限制写在工具头部第一段,以免下一个读者去找一条从不
运行的路径。

**两条规则在跑过之后被收窄,两次都是误报率决定的:**

* **R3** 原本对任何 `2>/dev/null | grep` 报警。而 `cmd 2>/dev/null | grep -q .`
在机器看来一模一样,做的却是**相反**的事 —— 它断言输出非空,正是本规则要求的。
收窄到只对 `-v` / `-c` / 取反管道(按缺席判定)报警。
* **R4** 原本对任何 `mcpp run` 报警,在**六个完全正确的作业**上误报:宿主目标的
`mcpp run` 不需要模拟器。收窄到只对 freestanding 目标。

⚠️ **一条误报率那么高的规则不会被读**,而这条工具的全部价值就是被读到。

**对 mcpp 自身跑,剩一条真的**:`nm "$BIN" 2>/dev/null | grep -c` 在 `nm` 失败
时打印「0 (good)」。已修成可区分两种情况。

⭐ **R2 已在 openarch 上落地并被反向验证过**:`.ci-identical-files` 声明「示例与
模板的构建程序自 `// The emulator.` 起逐字节相同」,故意弄坏一侧,它报了。

⚠️ **写那条检查时,它先在自己身上犯了它要防的错。** 第一版用 sed 地址,而标记
是一行 C++、含 `/` —— sed 把它当模式结尾,报 `unknown command: '/'`,**两边都空,
diff 通过**。改用 `grep -F` + `tail`,并断言两侧非空。

---

## 5. 【低】单 ISA 的第二个后端(riscv S 态)
Expand Down
Loading