Skip to content

Commit df1c1f4

Browse files
committed
revert(freestanding): drop dead-section elimination from this change
⚠️ MEASURED TO BREAK A JOB THAT IS GREEN ON main, BY A MECHANISM THAT CONTRADICTS THE CODE, WHICH IS REASON ENOUGH NOT TO SHIP IT. `--gc-sections` was correct on this machine and in the bare-metal CI job. It broke the openkal cross-build on a WINDOWS host: lld: error: unable to find library -lntdll (and four more) Three-way control, not reasoning: main, via workflow_dispatch build 3 targets on windows ✅ this PR, with gc-sections (three runs) ❌ bisect/no-gc-sections, that change alone reverted ✅ And the result contradicts the model. Both call sites sit inside `isFreestandingTarget`, which `x86_64-windows-gnu` does not satisfy; the same cross-build succeeds on a Linux host; and the `-Wl,--gc-sections` visible in the failing link line was traced to openkal-windows' own manifest rather than to the engine. The actual symptom is a MISSING `-L` for the directory holding the import libraries that package generates. So the mechanism is not understood, and an unexplained change does not belong in a release. The flags are removed here and the finding is recorded in `.agents/docs/2026-09-04-…-plan.md` §10.1 with the control that establishes it, because dead-section elimination remains a prerequisite for a C library supplied from the dependency graph. The Cortex-M rows do not depend on it: they build, link and boot without it, and the e2e still asserts the vector table is present and the float ABI is honoured on both sides. Only the two dead-code assertions are withdrawn.
1 parent b1b2e1b commit df1c1f4

8 files changed

Lines changed: 42 additions & 243 deletions

File tree

.agents/docs/2026-09-04-commercial-grade-baremetal-embedded-plan.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,10 +436,39 @@ v1 按工程依赖把 E 整个排到 P2。自审:**其中一半没有工程依
436436
| 条目 | 状态 |
437437
|---|---|
438438
| Cortex-M 目标行 |**七行**,`kKnownTargets``freestanding::kTable` 各七行 |
439-
| D11 死代码段消除 | `-ffunction-sections -fdata-sections` + `--gc-sections`(驱动路径与直接 lld 路径) |
439+
| D11 死代码段消除 | **撤出本 PR** —— 实测打断 Windows 宿主上的 openkal 交叉构建,见 §10.1 |
440440
| `docs/13` 回填两条失效限制 | ✅ 中英双份 |
441441
| 判据 | ✅ e2e 332(四行在 QEMU 中启动)+ 三条单测;CI `bare-metal e2e` 作业已绿 |
442442

443+
### ⚠️⚠️ 10.1 D11 被实测挡住,而挡住它的机制尚未查清
444+
445+
`--gc-sections` 在本机(Linux 宿主)与 CI 的裸机作业上都正确;它打断的是
446+
**openkal 在 Windows 宿主上的交叉构建**:
447+
448+
```
449+
lld: error: unable to find library -lntdll
450+
lld: error: unable to find library -lkernel32 (等五条)
451+
```
452+
453+
判据是三次对照,不是推理:
454+
455+
| 分支 | `build 3 targets on windows` |
456+
|---|---|
457+
| `main`(workflow_dispatch 对照) ||
458+
| 本 PR(含 gc-sections,三次) ||
459+
| `bisect/no-gc-sections`(只去掉 gc-sections) ||
460+
461+
⚠️ **而这条与我的机制模型矛盾**:两处 `gc-sections` 都在 `isFreestandingTarget`
462+
之内,`x86_64-windows-gnu` 不该进那个分支;本机 Linux→Windows 交叉**能成功**,且
463+
失败的链接行里的 `-Wl,--gc-sections` 经核对来自 **openkal-windows 自己的清单**
464+
而不是引擎。失败的真正现象是链接行**缺了 `-L<openkal-windows 的 out 目录>`** ——
465+
那个目录里正是它生成的 `libntdll.a` 等导入库。
466+
467+
**测量胜过模型**:一个我解释不了的改动不该合入。D11 退回待办,并带上这条判据 ——
468+
它同时是「图来源 C 库」那条路线必须先解决的前置。⭐ 附带发现:openkal 的包**已经
469+
在自己的清单里用 `-ffunction-sections``-Wl,--gc-sections`**,所以引擎侧是否
470+
需要再发一份,本身也要重新论证。
471+
443472
### ⚠️ 实施推翻的两处方案原文
444473

445474
**`extra` 列不承载浮点 ABI —— triple 已经承载了它。**
@@ -464,6 +493,8 @@ clang 对一次 float 乘法仍发出 `vmul.f32` —— 在没有 FPU 的 Cortex
464493

465494
### 未实施(按方案原顺序)
466495

496+
**D11 死代码段消除**(见 §10.1,已从本 PR 撤出,带判据)·
497+
467498
`P0``mcpplibs/picolibc` + `compiler-rt-builtins` 源码包(§3.1.1)· 板级包三个 ·
468499
`P0'``xim:probe-rs` 与 E 轴零工程项 · `P1`/`P1'` 真机 · `P1'` 槽表 ·
469500
`P2` lock 成钉与 SBOM · `P3` openarch 第四后端 · `P3'` 嵌入式 Linux。

CHANGELOG.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
## [2026.9.4.1] — 2026-09-04
77

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

1010
裸机目标表从四行增至十一行。M-profile 是七行而不是一行:为 `thumbv7em` 构建的
1111
目标文件使用 Cortex-M0 没有的指令,两种拼写产出互不兼容的目标文件,而表存在的
@@ -23,13 +23,6 @@ target = "thumbv7em-none-eabihf"
2323
因此携带 `-mfpu=none`,包括架构本来就没有 FPU 的那几行 —— 一行陈述它保证的性质,
2424
而不是从一个可以改变的默认值继承它。
2525

26-
freestanding 编译加 `-ffunction-sections -fdata-sections`、链接加 `--gc-sections`
27-
依赖的目标文件无条件进入链接(不像归档成员那样按未定义符号拉取),当 C 库改由
28-
依赖图提供时,没有死代码段消除的镜像会装进整份 C 库,而 Cortex-M 器件只有几十 KB。
29-
30-
⚠️ **链接脚本因此以新的方式承重**:中断向量表不被任何东西引用,`--gc-sections`
31-
会回收它,板级脚本必须写 `KEEP(*(.vectors))`
32-
3326
同时回填了 `docs/13` 中两条已被 2026.8.28.2 推翻的限制:当图中有包提供
3427
`hosted-standard-library` 时,裸机目标上的异常、RTTI 与 `import std` 均可用。
3528

docs/13-baremetal.md

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -59,95 +59,6 @@ Cortex-M needs no `lldEmulation` column entry: clang has a *BareMetal* toolchain
5959
for arm, so these triples reach `ld.lld` through the driver as the RISC-V and
6060
aarch64 rows do. 32-bit ARM has no `-mcmodel` axis, so that column is empty too.
6161

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.
81-
82-
⚠️ The last two rows default to no C library, and that is a statement rather
83-
than an omission: the first consumer of both rows — the `openarch` layer of
84-
machine mechanism — references no C library symbol, and if no row defaulted to
85-
this tier there would be nothing demonstrating the tier works.
86-
**A build for those rows is declarable, not absent** (mcpp 2026.8.21.3+).
87-
`xim:picolibc-aarch64` and `xim:picolibc-x86` are in the index; a project that
88-
wants one names it the same way it would choose a different one:
89-
90-
```toml
91-
[target.aarch64-none-elf]
92-
sysroot = "xim:picolibc-aarch64@1.8.12"
93-
``` An empty column means exactly what
94-
`[target.<triple>].sysroot = ""` means in a manifest, so a project targeting one
95-
begins on the zero-libc tier without asking. A project that wants a C library on
96-
those targets declares one, which is also how it would choose a different one.
97-
98-
Such a target needs no per-host cross toolchain. clang and lld are
99-
cross-compilers by construction — one binary emits every target it was built
100-
with — so the target table pins `llvm@22.1.8` on every host, and any machine
101-
that can install the LLVM payload can produce an image for any of the four.
102-
103-
### The x86_64 row is not four strings
104-
105-
⚠️ **A target row is normally an entry in two tables and nothing else. This one
106-
needed engine code, and the reason is a property of clang rather than of the
107-
instruction set.**
108-
109-
clang selects a toolchain from the triple. It has a *BareMetal* toolchain for
110-
arm, aarch64 and riscv, which links with `ld.lld` directly; it has none for
111-
x86_64, so every spelling of a bare x86_64 triple falls through to the generic
112-
GCC toolchain — whose linker is the **host's `g++`**:
113-
114-
```
115-
g++: error: unrecognized command-line option '-fuse-ld=/…/llvm/22.1.8/bin/ld.lld'
116-
```
117-
118-
Measured for `x86_64-none-elf`, `x86_64-unknown-none-elf`, `x86_64-unknown-none`,
119-
`x86_64-elf`, `x86_64-none-none` and `x86_64-unknown-unknown`, and unchanged by
120-
`-fuse-ld=lld`, `--ld-path=`, `--gcc-toolchain=` or `-B`. The one thing that
121-
does change it is putting `linux` in the OS position, which makes clang link
122-
directly and adds eight host `-L` paths to a bare-metal link.
123-
124-
Neither outcome is acceptable: routing through a host `g++` makes the row work
125-
on a Linux host and nowhere else, and host search paths on a freestanding link
126-
are the hermeticity this engine exists to keep. So the row carries a fifth
127-
column, `lldEmulation`, and when it is set the engine drives the link with
128-
`ld.lld` itself. The flag vocabulary changes with the tool — `-Map=` rather than
129-
`-Wl,-Map=`, `-m elf_x86_64` rather than `--target=` — and the driver-only flags
130-
(`-nostdlib++`, the loader tag) are dropped rather than translated.
131-
132-
The column is empty for the riscv and aarch64 rows. Their driver already reaches
133-
lld, and changing a working link to make three rows look alike is how a
134-
regression is introduced.
135-
136-
### `-mno-red-zone` is part of the target, not a preference
137-
138-
The System V x86-64 ABI reserves 128 bytes below `rsp` that a leaf function may
139-
use without adjusting the stack pointer, because on a hosted system nothing else
140-
writes there. On bare metal the processor pushes an interrupt frame at `rsp` —
141-
into the red zone — and the interrupted leaf resumes to find its locals
142-
overwritten. There is no fault and no diagnostic, and it happens only when an
143-
interrupt arrives inside a leaf.
144-
145-
There is no bare-metal x86_64 program for which the red zone is safe, so the
146-
flag is a property of the row rather than something a project remembers. It
147-
reaches the command line through a new `extra` column in the ISA-profile table,
148-
which exists because `-march`/`-mabi`/`-mcmodel` could not express it. RISC-V
149-
and aarch64 have no equivalent, which is why the column did not exist before.
150-
15162
Three things a bare-metal build requires are not properties of the ISA, and
15263
mcpp does not attempt to derive them: which startup object and libraries to
15364
select, which linker script describes the machine's memory, and how to execute

docs/zh/13-baremetal.md

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -52,82 +52,6 @@ Cortex-M 不需要 `lldEmulation` 列:clang 有面向 arm 的 *BareMetal* 工具
5252
triple 与 RISC-V、aarch64 各行一样经驱动到达 `ld.lld`。32 位 ARM 没有 `-mcmodel`
5353
这个轴,所以该列同样为空。
5454

55-
### 死代码段消除
56-
57-
freestanding 构建以 `-ffunction-sections -fdata-sections` 编译,以 `--gc-sections`
58-
链接。两半都属于引擎而不属于工程,因为依赖的翻译单元也必须带上它们,而工程够不到
59-
那些单元。
60-
61-
这两个标志从「划算」变成「必需」,发生在 C 库开始由依赖图提供的时候。依赖的目标
62-
文件是**无条件**进入链接的,不像归档成员那样只在符号仍未定义时才被拉入。当 C 库是
63-
预编译归档、目标又有若干兆字节时,这不花什么代价;而 Cortex-M 器件只有几十 KB,
64-
没有死代码段消除,每个镜像都会装进整份 C 库。
65-
66-
⚠️ **链接脚本因此以一种新的方式承重。** 中断向量表不被任何东西引用 —— 硬件按地址
67-
读取它 —— 所以 `--gc-sections` 会把它回收。板级脚本必须写 `KEEP(*(.vectors))`
68-
实测:有这条 `KEEP` 时,无人调用的函数被丢弃、向量表被保留、镜像能够启动。
69-
70-
⚠️ 后两行**默认**没有 C 库,这是声明而非遗漏:这两行的第一个消费者 —— 机器机制层
71-
`openarch` —— 一个 C 库符号都不引用,而**如果四行里没有一行默认在这一层,就没有
72-
任何东西在证明这一层可用**。空列在这里的含义与清单里
73-
`[target.<triple>].sysroot = ""` 完全一致。
74-
75-
**这两行的 C 库是可声明的,不是不存在的**(mcpp 2026.8.21.3+)。
76-
`xim:picolibc-aarch64``xim:picolibc-x86` 已在索引里;想要它的工程自行声明,
77-
而那与它换用另一份 C 库是同一个动作:
78-
79-
```toml
80-
[target.aarch64-none-elf]
81-
sysroot = "xim:picolibc-aarch64@1.8.12"
82-
```
83-
84-
这类目标不需要逐宿主的交叉工具链。clang 与 lld 在构造上就是交叉编译器 ——
85-
一个二进制发射它构建时支持的全部目标 —— 因此目标表在每个宿主上都钉
86-
`llvm@22.1.8`,任何能安装 LLVM 载荷的机器都能为这四个中的任何一个产出镜像。
87-
88-
### x86_64 这一行不止是四个字符串
89-
90-
⚠️ **一个目标行通常就是两张表里的两条记录,不含引擎代码。这一行需要引擎代码,
91-
而原因是 clang 的属性而非指令集的属性。**
92-
93-
clang 由 triple 选择工具链。它为 arm、aarch64、riscv 备有 *BareMetal* 工具链,
94-
直接以 `ld.lld` 链接;它没有 x86_64 的,于是裸 x86_64 triple 的每一种写法都落到
95-
通用 GCC 工具链上 —— 而后者的链接器是**宿主的 `g++`**:
96-
97-
```
98-
g++: error: unrecognized command-line option '-fuse-ld=/…/llvm/22.1.8/bin/ld.lld'
99-
```
100-
101-
`x86_64-none-elf``x86_64-unknown-none-elf``x86_64-unknown-none`
102-
`x86_64-elf``x86_64-none-none``x86_64-unknown-unknown` 逐一实测,结果一致;
103-
`-fuse-ld=lld``--ld-path=``--gcc-toolchain=``-B` 逐一实测,均不改变结果。
104-
唯一能改变它的是把 `linux` 放进 OS 位,而那会给一次裸机链接带来八条宿主 `-L`
105-
106-
两种结果都不可接受:经宿主 `g++` 会让这一行只在 Linux 宿主上成立;宿主搜索路径
107-
出现在 freestanding 链接上,正是本引擎存在的意义所要守住的封闭性。因此该行带了
108-
第五列 `lldEmulation`,置位时引擎直接用 `ld.lld` 驱动链接。标志的词汇随工具一同
109-
改变 —— `-Map=` 而非 `-Wl,-Map=`,`-m elf_x86_64` 而非 `--target=` —— 而仅属于
110-
驱动的标志(`-nostdlib++`、加载器标签)是被丢弃而不是被翻译。
111-
112-
riscv 与 aarch64 两行的该列为空。它们的驱动本就到得了 lld,为了让三行看起来一致
113-
而改动一条可用的链接,正是引入回归的方式。
114-
115-
### `-mno-red-zone` 是目标的属性,不是偏好
116-
117-
System V x86-64 ABI 在 `rsp` 之下保留 128 字节,叶函数可以不调整栈指针直接使用,
118-
因为在有操作系统的机器上没有别的东西会写那里。裸机上不存在做这件事的一方:处理器
119-
`rsp` 处压入中断帧 —— 正压进红区 —— 被中断的叶函数恢复后发现自己的局部变量已被
120-
覆盖。既不触发异常也没有诊断,而且只在中断恰好落在叶函数内部时发生。
121-
122-
不存在红区安全的裸机 x86_64 程序,所以这个标志是行的属性,而不是工程需要记住的
123-
事情。它经 ISA 档表新增的 `extra` 列到达命令行 —— 该列之所以存在,是因为
124-
`-march`/`-mabi`/`-mcmodel` 表达不了它。RISC-V 与 aarch64 没有对应物,这也正是
125-
此前不需要这一列的原因。
126-
127-
裸机构建需要的三样东西并不是 ISA 的属性,mcpp 也不试图推导它们:选哪个启动对象
128-
与哪些库、哪份链接脚本描述这台机器的内存、以及如何执行产出的镜像。这三样随
129-
**板级支持包**一起提供,而板级支持包是一个普通依赖。由此得到的结果是:换板子是
130-
一次依赖变更,而不是一次构建系统变更。
13155

13256
## 从零到一个可运行的镜像
13357

src/freestanding/linkline.cppm

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ inline std::string link_flags(const Spec& s, const LinkInputs& in,
8989
// suggesting it does.
9090
for (auto const& f : compile_flags(s)) { out += ' '; out += f; }
9191
out += " -nostdlib -nostartfiles -static";
92-
// The link half of the pair set in mcpp.freestanding.target's
93-
// `compile_flags`. Routed through the driver, hence `-Wl,`.
94-
out += " -Wl,--gc-sections";
9592
if (!in.lld.empty())
9693
out += " -fuse-ld=" + esc(in.lld);
9794
// ⚠️ BEFORE the libraries the board selects, and it has to be on THIS line
@@ -132,11 +129,6 @@ inline std::string link_flags_direct(const Spec& s, const LinkInputs& in,
132129
// No loader exists, so an image must not name one. The driver line reaches
133130
// this through `-static`; here it is said to the linker.
134131
out += " --no-dynamic-linker";
135-
// Said to the linker directly here, as `-Wl,--gc-sections` is said through
136-
// the driver on the line above. The two paths must agree: a target whose
137-
// link is driven by `ld.lld` itself would otherwise keep every section its
138-
// dependencies emit, and the difference would show only as size.
139-
out += " --gc-sections";
140132
if (!in.sysrootLib.empty())
141133
out += " -L" + esc(in.sysrootLib);
142134
if (!in.linkerScript.empty())

src/freestanding/target.cppm

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -291,30 +291,6 @@ inline std::vector<std::string> compile_flags(const Spec& s,
291291
// `-ffreestanding` so the ordering of this function's output stays a
292292
// function of the table rather than of the row.
293293
for (auto flag : s.extra) out.emplace_back(flag);
294-
// ⭐⭐ ONE SECTION PER FUNCTION, SO THE LINKER CAN DROP WHAT NOTHING CALLS.
295-
//
296-
// These two flags do nothing on their own; they are the half of
297-
// `--gc-sections` that has to happen at compile time, and the link half is
298-
// in mcpp.freestanding.linkline. Both halves are here rather than left to a
299-
// project because a dependency's translation units must carry them too, and
300-
// a project cannot reach those.
301-
//
302-
// ⚠️ WHY THIS BECAME NECESSARY RATHER THAN MERELY NICE. A dependency's
303-
// object files enter the link unconditionally (docs/13), unlike an archive
304-
// member, which is pulled only while its symbol is undefined. That costs
305-
// nothing when the C library is a prebuilt archive and the target has
306-
// megabytes — the arrangement every bare-metal row had until now. A C
307-
// library that arrives from the dependency graph is object files, and a
308-
// Cortex-M part has kilobytes: without this, every image carries the whole
309-
// of the C library whether or not it calls into it.
310-
//
311-
// ⚠️ AND IT MAKES A LINKER SCRIPT LOAD-BEARING IN A NEW WAY: an interrupt
312-
// vector table is referenced by nothing — the hardware reads it by address
313-
// — so `--gc-sections` collects it. A board's script must say
314-
// `KEEP(*(.vectors))`. Measured: with the KEEP present, a dead function is
315-
// dropped and the table survives; the image boots.
316-
out.emplace_back("-ffunction-sections");
317-
out.emplace_back("-fdata-sections");
318294
// ⭐ AND `-ffreestanding` ITSELF IS ONE OF THE THINGS THE GRAPH DECIDES.
319295
//
320296
// The paragraph above this function names what the flag changes: "no `main`

0 commit comments

Comments
 (0)