Skip to content

Commit 0ad5b52

Browse files
feat(freestanding): exceptions off graph-wide, and the std subset becomes usable (#459)
* feat(freestanding): exceptions off graph-wide, and the std subset becomes usable The freestanding std subset was described in the phase-3 wrap-up as "a separate piece of work". That was wrong, and the research it contradicted had already measured it. Reproduced here on the shipped payloads: with a synthesised `__config_site`, 103 of libc++'s 110 headers compile for riscv64-none-elf, and all 7 that fail also fail on an x86_64 host with full libc++ and glibc — they are headers libc++ has not implemented. The freestanding compile-time loss is zero. `-nostdinc++` is the MECHANISM of that route, not an obstacle: it keeps libc++'s headers private to the subset package, which exports a module — the same shape the board package already uses for the target's C library. What actually blocked it was exceptions, and that is a whole-graph property: * `std::optional::value()` alone pulls in `__cxa_throw`, two vtables and `std::bad_optional_access::~bad_optional_access`, none of which can exist without an unwinder or libc++abi. * A project's own `[build] cxxflags` cannot fix it: they do not reach a dependency's module compile, so the BMI and the importer disagree and clang reports a `.pcm` "configuration mismatch" — naming a module file rather than the setting that split the graph. * `is_dialect_flag` deliberately does NOT propagate `-fno-exceptions`, on the grounds that "dependencies may assume exceptions are available". Right for a hosted target, exactly backwards here. So `-fno-exceptions -fno-rtti` join `-ffreestanding` and `-nostdinc++` on the target spec, which is the one place that reaches every unit in the graph. ⚠️ That change alone would have broken every existing bare-metal user on upgrade. The dependency cache key carries the target TRIPLE but not the flags the triple implies — and which flags those are is mcpp's decision, so it moves between versions while the triple string does not. A project that had built once got its old BMI back and a hard failure naming a .pcm. Fixed by adding a `targetImpliedFlags` axis, empty for hosted targets so no existing key moves. e2e/133 pins the whole chain in the emulator, from both sides: the subset builds, runs `ranges::sort` with a projection plus optional/atomic/span/ string_view on the target, and `std::mutex` fails to COMPILE — capabilities turned off in `__config_site` vanish rather than leaving a run-time stub. The `import std` diagnostic names `std-freestanding` again, now that the package is published. * feat(target): the target owns its C library, so no package has to name one Both bare-metal packages carried `[xlings] deps = ["xim:picolibc-riscv@1.8.12"]` and the standard-library subset also carried `xim:llvm`. That pinned a board package and an implementation-neutral library alike to one libc, one ISA, one toolchain and one version of each — none of which is a property of either. The cause was a gap in the target model rather than sloppy packaging. A hosted target has always had its C library resolved for it: musl rides inside its gcc payload and glibc arrives through PayloadPaths, which is why nobody writes `xim:glibc` in a manifest. A freestanding target pins a generic clang, which carries no target libc, and there was no axis for one — so the requirement leaked outward into every package. kKnownTargets now names the target's C library beside its compiler pin. It is installed through the same channel `[xlings] deps` already use, its headers go on every compile line and its directory on the link search path. Location is a target fact; selection stays a board fact — a board still chooses `-lcrt0-semihost` over a UART crt0, and still names its linker script. Two interfaces let a package ask instead of declare: `mcpp::toolchain_dir()` for headers that ship with the toolchain (libc++'s, for the freestanding subset) and `mcpp::sysroot_dir()` for a file inside the target's C library (a linker script). Both follow whatever `[toolchain]` and `--target` resolved. Three things this turned up: * `-L` appended to the ordinary ldflags is discarded — a freestanding link line is replaced wholesale, so it has to go through LinkInputs. Measured: the flag was built and then simply was not there. * `link-script` resolves a bare name against the PACKAGE root, so a board cannot rely on the linker's search path for it; it asks for the sysroot. * e2e/131's privacy assertion was testing the wrong thing. It proved a dependency's `include-dir` stays private by showing `#include <stdio.h>` fails — but with the libc owned by the target that now correctly SUCCEEDS, exactly as on a hosted build. Split into two: the target's C headers must reach the consumer, and a header the BOARD ships must not. --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 0428f9e commit 0ad5b52

21 files changed

Lines changed: 753 additions & 45 deletions

.agents/docs/2026-08-19-baremetal-phase3-usable-plan.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,72 @@ manifest」的目录里,manifest 永远是最新的那个文件,快路径**永
470470

471471
**教训**:`mcpp run` 单独跑是对的,`mcpp build && mcpp run` 才错 —— **顺序本身就是被测
472472
对象**。而 130/131/132 三个测试都恰好先 `run`,所以谁也看不见。
473+
474+
### 11.5 ⚠️ 我说 E-STD「要从头实现、不该在收尾时半做」—— 这条是错的,而且调研文档早就写着
475+
476+
用户指出后回查 `2026-08-18-freestanding-baremetal-analysis.md`,X1–X10 把这条路测到了底:
477+
478+
| 我说的 | 文档实测(本轮已用**已发布载荷**复现) |
479+
|---|---|
480+
| `-nostdinc++` 之下 libc++ 的头一个都用不了 | 合成 freestanding `__config_site`**103 / 110 可编**;失败的 7 个在 x86_64 宿主上**同样失败** ⇒ 裸机这层**编译期损失为 0** |
481+
| 子集要从头实现 | libc++ 自己带 110 个 `std/<header>.inc`(每个就是该头的 `export namespace std {...}`)⇒ **子集是机械挑选**,生成物 217 行全是 `#include` |
482+
| 是独立规模的工作 | T1+T2 当时就跑通了,只有 **T3**(`std::format`/标量 `sort`/完整 `string`)需要为目标编 `libc++.a` |
483+
484+
⚠️ **`-nostdinc++` 恰恰是这条路的机制而不是障碍**:它让 libc++ 的头**私有**给子集包,
485+
包再 export 一个模块 —— 和 BSP 私有 include 目标 libc 头是同一个形状。我把它读反了。
486+
487+
⭐ 复现时只差**一个符号**:`std::__libcpp_verbose_abort`。⚠️ 而且它必须**通过 libc++
488+
自己的头**声明 —— 真符号在 ABI 内联命名空间 `std::__1::` 里,手写 `namespace std {...}`
489+
**编得过、链不上、报错一字不变**
490+
491+
#### ⚠️ 真正的阻塞点是异常,而且它是「整图属性」
492+
493+
`optional::value()` 一个就拉进 `__cxa_throw` + `vtable for std::exception` 等 4 个符号。
494+
`-fno-exceptions`:
495+
496+
- 写在工程 `[build] cxxflags`**没用** —— 到不了依赖的模块编译,于是 BMI 与导入者不一致,
497+
clang 报的是 **`.pcm` configuration mismatch**,点名一个模块文件而不是那个 flag。
498+
- `is_dialect_flag`(整图传播的那张表)**刻意排除了它**,理由写着「依赖可能假设异常可用」——
499+
**这条在 hosted 成立,在裸机上正好反过来**:没有 unwinder,谁都用不了。
500+
501+
⇒ 放进**引擎的 freestanding target flags**(和 `-ffreestanding`/`-nostdinc++` 同处),
502+
那是唯一能保证整图一致的地方。
503+
504+
#### ⚠️ 它顺带暴露了一个「升级即坏」的缓存缺陷
505+
506+
依赖缓存键有 triple,**没有 triple 隐含的那组 flag**。而**哪些 flag 由 triple 隐含是 mcpp 的决定**,
507+
会随版本变、triple 字符串不变 ⇒ 升级后复用了升级前的 BMI,硬失败,错误只点名一个 `.pcm`
508+
已加 `targetImpliedFlags` 轴(hosted 为空,不动任何现有键)。
509+
510+
### 11.6 ⭐ 用户指出的架构错误:目标的 libc 不该由包声明
511+
512+
我把 `xim:picolibc-riscv@1.8.12` 写进了 BSP 和 std 子集两个包的 `[xlings] deps`
513+
用户指出三条,**三条都对**:不该绑 libc、不该绑 riscv、不该绑编译器;而且 freestanding
514+
在普通宿主上一样能用(实测:`-ffreestanding` + 宿主 libc,同一份代码编得过)。
515+
516+
⚠️ **真因是引擎的一个结构性缺口**,不是包写得随意:
517+
518+
| | 编译器 | 目标 libc |
519+
|---|---|---|
520+
| hosted(`x86_64-linux-musl`) | 目标表 `pin` **自动** | musl 在 gcc 载荷里 / glibc 走 `PayloadPaths` —— **自动**,没人写过 `xim:glibc` |
521+
| freestanding | 目标表 `pin = llvm@22.1.8` **自动** | **没有任何一条轴** ⇒ 外溢到每个包 |
522+
523+
**`TargetInfo``sysroot`**(和 `pin` 并列),复用现有 `[xlings] deps` 物化通道
524+
安装,引擎把 `-isystem <sysroot>/include/<档位>``-L <sysroot>/lib/<档位>` 放上去。
525+
526+
**位置是目标的事实,选择是板级的事实**:引擎给位置,BSP 用**裸名**`-lcrt0-semihost`
527+
`-lc` `-lsemihost` 并指定链接脚本。
528+
529+
同时补两个「问引擎」的接口:`mcpp::toolchain_dir()``mcpp::sysroot_dir()`
530+
**两个包的 `[xlings] deps` 里 libc 与编译器全部消失**,std 子集变成与架构/libc/实现无关。
531+
532+
#### 实施中撞到的三条
533+
534+
1. ⚠️ **`-L` 加到 `f.ld` 上会被丢掉** —— freestanding **整条替换**链接线(否则载荷 cfg
535+
注入宿主 dynamic linker),所以必须放进 `LinkInputs`。实测:flag 拼出来了,然后不在。
536+
2. ⚠️ **`link-script` 的裸名会被按包根绝对化**`picolibcpp.ld` 指到包目录里。板级包要
537+
`sysroot_dir()` 自己拼 —— 它知道**要哪个脚本**,不知道**在哪**
538+
3. ⚠️ **e2e/131 的私有性断言判据失效了**:它用 `#include <stdio.h>` 编不过来证明依赖的
539+
`include-dir` 不到达消费者。libc 归目标之后 `<stdio.h>` **本来就该编得过**(和宿主一样)。
540+
⇒ 拆成两条:目标 C 头**必须**到达(正面断言),板级包**自己的****必须不**到达。
541+
**原来那条测的其实是「libc 从哪来」,不是「作用域对不对」。**

.github/workflows/ci-linux-e2e.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ jobs:
174174
# PASS line or it does not.
175175
for t in tests/e2e/130_freestanding_riscv_build_and_run.sh \
176176
tests/e2e/131_freestanding_bsp_supplies_everything.sh \
177-
tests/e2e/132_freestanding_test_and_artifacts.sh; do
177+
tests/e2e/132_freestanding_test_and_artifacts.sh \
178+
tests/e2e/133_freestanding_std_subset.sh; do
178179
echo "=== $t ==="
179180
bash "$t" 2>&1 | tee "$(basename "$t").log"
180181
rc=${PIPESTATUS[0]}
@@ -192,6 +193,9 @@ jobs:
192193
grep -q 'PASS: bare-metal mcpp test names its failure' \
193194
132_freestanding_test_and_artifacts.sh.log || {
194195
echo "132 (test + artifacts) skipped on the runner that must run it"; exit 1; }
196+
grep -q 'PASS: the freestanding std subset' \
197+
133_freestanding_std_subset.sh.log || {
198+
echo "133 (std subset) skipped on the runner that must run it"; exit 1; }
195199
196200
# ──────────────────────────────────────────────────────────────────
197201
# Hermetic (no host toolchain): the ONLY environment class that

docs/05-mcpp-toml.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,9 @@ that has none.
931931
|---|---|
932932
| Link line | `-nostdlib -nostartfiles -static`, and nothing hosted — no crt files, no dynamic linker, no C++ runtime. The linker is addressed by **absolute path** (`-fuse-ld=<payload>/bin/ld.lld`), because `-fuse-ld=lld` resolves through `PATH` and finds GNU ld on any machine with binutils earlier on it. |
933933
| ISA flags | `-march` / `-mabi` / `-mcmodel` come from the target table, so `--target <triple>` alone is enough to produce a correct object file. |
934-
| `import std` | **Unavailable.** `std` is one module over the entire library — threads, filesystem and iostreams included — so there is no subset of it to build without an OS. What a firmware imports instead is the module its **board package** exports, which is where the target's C library is already wrapped. |
934+
| C library | **The target's**, resolved by mcpp from the target's own row exactly as the compiler is — a bare-metal project declares no libc, just as a hosted one declares no glibc. Its headers reach every translation unit and its directory is on the link search path, so a board package selects out of it by bare name (`-lc`, `-lcrt0-semihost`). *Which* objects and *which* linker script remain board decisions. |
935+
| Exceptions and RTTI | **Off**, on every translation unit including a dependency's. There is no unwinder and no `libc++abi`, so nothing can throw; `std::optional::value()` alone would otherwise pull in `__cxa_throw` and three more undefined symbols. It belongs to the target rather than to a project's `cxxflags` because a BMI records it — a dependency compiled with exceptions cannot be imported by a unit without them. |
936+
| `import std` | **Unavailable.** `std` is one module over the entire library — threads, filesystem and iostreams included — so there is no subset of it to build without an OS. Two ordinary dependencies replace it: the **board package** wraps the target's C library, and **`std-freestanding`** carries the parts of the standard library that need no OS (103 of libc++'s 110 headers, measured). |
935937
| Entry point | `int main()` works **as long as something supplies a `crt0`** — a board package normally does, and then a firmware's entry point is an ordinary `main` whose return value reaches the host through semihosting. Only a zero-libc board needs an explicit target whose `main` points at the file carrying `_start`. |
936938

937939
**A minimal firmware**

docs/07-build-mcpp.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,28 @@ both claiming to know how to run the artifact is a configuration error, and
133133
mcpp reports it naming both rather than merging them into an argv that is
134134
neither one's.
135135
136+
### Asking instead of declaring: `toolchain_dir` / `sysroot_dir` (2026.8.19.4+)
137+
138+
```cpp
139+
const char* tc = mcpp::toolchain_dir(); // the resolved toolchain's payload root
140+
const char* sr = mcpp::sysroot_dir(); // the TARGET's C library root, or ""
141+
```
142+
143+
A package that needs headers shipped by the toolchain — libc++'s, for a
144+
freestanding standard-library subset — or a file inside the target's C library
145+
— a linker script, for a board-support package — asks for the directory rather
146+
than declaring a dependency on the thing that provides it.
147+
148+
⚠️ The difference is not cosmetic. Declaring `xim:llvm` pins a package to one
149+
standard-library implementation; declaring `xim:picolibc-riscv@1.8.12` pins it
150+
to one C library, one architecture and one version. Neither is a property of a
151+
package whose content is implementation-neutral. Asking follows whatever
152+
`[toolchain]` and `--target` actually resolved.
153+
154+
`sysroot_dir()` is empty on a hosted target: there the C library arrives with
155+
the compiler payload or through the runtime binding, and nothing has to look
156+
for it.
157+
136158
### Finding an `[xlings] deps` payload: `xpkg_dir` (2026.8.19+)
137159

138160
`dep_dir` answers for **mcpp** dependencies. An xlings package is a different

docs/zh/05-mcpp-toml.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,9 @@ cd blinky && mcpp run
827827
|---|---|
828828
| 链接线 | `-nostdlib -nostartfiles -static`,且不带任何 hosted 的东西 —— 没有 crt 文件、没有动态链接器、没有 C++ 运行时。链接器用**绝对路径**寻址(`-fuse-ld=<载荷>/bin/ld.lld`),因为 `-fuse-ld=lld``PATH` 解析,在任何 binutils 排前面的机器上都会找到 GNU ld。 |
829829
| ISA flag | `-march` / `-mabi` / `-mcmodel` 来自 target 表,所以只写 `--target <triple>` 就足以产出正确的目标文件。 |
830-
| `import std` | **不可用。** `std` 是覆盖整个库的一个模块 —— 线程、文件系统、iostreams 全在内 —— 没有 OS 就没有它的子集可编。固件真正 import 的是**板级包导出的模块**,目标的 C 库已经在那里包好了。 |
830+
| C 库 | **属于 target**,由 mcpp 从目标自己那一行解析,和解析编译器同理 —— 裸机工程不声明 libc,正如宿主工程不声明 glibc。它的头进入每一个翻译单元,它的目录进入链接搜索路径,所以板级包用**裸名**选库(`-lc``-lcrt0-semihost`)。**选哪个**启动对象、**用哪份**链接脚本仍然是板级决定。 |
831+
| 异常与 RTTI | **关闭**,作用于每一个翻译单元,依赖的也不例外。没有 unwinder、没有 `libc++abi`,谁都抛不了;否则光是 `std::optional::value()` 就会拉进 `__cxa_throw` 等四个未定义符号。它属于 **target** 而不是工程的 `cxxflags`,因为 **BMI 会记录这个配置** —— 带异常编出来的依赖,不带异常的单元 import 不进来。 |
832+
| `import std` | **不可用。** `std` 是覆盖整个库的一个模块 —— 线程、文件系统、iostreams 全在内 —— 没有 OS 就没有它的子集可编。取代它的是两个普通依赖:**板级包**包住目标的 C 库,**`std-freestanding`** 提供标准库里不需要 OS 的那部分(实测 libc++ 110 个头里的 103 个)。 |
831833
| 入口点 | **只要有人提供 `crt0`,`int main()` 就能用** —— 板级支持包通常就提供它,于是固件的入口就是普通的 `main`,它的返回值经 semihosting 传回宿主。**只有零 libc 的板子**才需要显式声明 target 并把 `main` 指向携带 `_start` 的那个文件。 |
832834

833835
**一个最小固件**

docs/zh/07-build-mcpp.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ for (auto a : {"-machine","virt","-nographic","-no-reboot","-kernel"})
121121
⚠️ **只能有一个依赖提供 runner。** 两个板级支持包都声称知道怎么跑这个产物是配置
122122
错误;mcpp 会**同时点名两个**并报错,而不是把它们并成一个谁也不是的 argv。
123123
124+
### 问,而不是声明:`toolchain_dir` / `sysroot_dir`(2026.8.19.4+)
125+
126+
```cpp
127+
const char* tc = mcpp::toolchain_dir(); // 已解析工具链的载荷根目录
128+
const char* sr = mcpp::sysroot_dir(); // 目标的 C 库根目录,没有则为 ""
129+
```
130+
131+
一个包需要工具链自带的头(比如 freestanding 标准库子集要的 libc++ 头),或者需要
132+
目标 C 库里的某个**文件**(比如板级支持包要的链接脚本)时,应当**问这个目录在哪**,
133+
而不是去声明一个依赖来把它拽进来。
134+
135+
⚠️ 这不是写法差异。声明 `xim:llvm` 会把包**钉死在一个标准库实现**上;声明
136+
`xim:picolibc-riscv@1.8.12` 会把它钉死在**一个 C 库、一种架构、一个版本**上。而这些
137+
都不是一个内容全是标准规定的名字的包的属性。****则会跟随 `[toolchain]`
138+
`--target` 真正解析到的结果。
139+
140+
宿主目标上 `sysroot_dir()` 为空:那里 C 库随编译器载荷或运行时绑定而来,没人需要找它。
141+
124142
### 找到 `[xlings] deps` 的载荷:`xpkg_dir`(2026.8.19+)
125143

126144
`dep_dir` 回答的是 **mcpp** 依赖。xlings 包是另一个命名空间、另一套 store 布局,

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.8.19.3"
3+
version = "2026.8.19.4"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/build_program.cppm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export namespace mcpp::build {
3838
// target/profile/feature change re-runs the program.
3939
struct BuildProgramEnv {
4040
std::string targetTriple; // resolved canonical triple; "" = host
41+
// The resolved toolchain's payload root and the target's own C library
42+
// root. Both exist so a package can ASK instead of DECLARE — see
43+
// hostprogram::toolchain_dir / sysroot_dir for why declaring was wrong.
44+
std::string toolchainDir;
45+
std::string targetSysroot;
4146
std::string profile; // effective profile name (dev/release/…)
4247
std::vector<std::string> features; // active feature closure of the package
4348
// Artifact home (bin/cache/out). Empty → <root>/target/.build-mcpp (the
@@ -295,6 +300,12 @@ contract_env(const fs::path& root, const fs::path& outDir, const BuildProgramEnv
295300
e.emplace_back("MCPP_TARGET_ENV", t.env);
296301
}
297302
e.emplace_back("MCPP_HOST", hostT);
303+
// Always emitted, empty when they do not apply: a build program reads
304+
// these through `env_or`, which cannot tell "absent" from "empty", and an
305+
// absent variable would make the answer depend on whatever the parent
306+
// process happened to export.
307+
e.emplace_back("MCPP_TOOLCHAIN_DIR", env.toolchainDir);
308+
e.emplace_back("MCPP_TARGET_SYSROOT", env.targetSysroot);
298309
e.emplace_back("MCPP_PROFILE", env.profile);
299310
e.emplace_back("MCPP_OUT_DIR", outDir.string());
300311
e.emplace_back("MCPP_MANIFEST_DIR", root.string());

src/build/cache_key.cppm

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ import mcpp.manifest;
5656
import mcpp.modgraph.scanner;
5757
import mcpp.toolchain.detect;
5858
import mcpp.toolchain.fingerprint;
59+
import mcpp.toolchain.triple;
60+
import mcpp.freestanding.target; // the flags a bare-metal triple implies
5961

6062
export namespace mcpp::build::cache_key {
6163

@@ -79,6 +81,22 @@ struct BuildAxes {
7981
std::string compilerVersion;
8082
std::string driverIdentity;
8183
std::string targetTriple;
84+
// ⚠️ The flags the TRIPLE implies, not the ones anyone wrote down.
85+
//
86+
// A freestanding triple silently carries `-march`/`-mabi`/`-mcmodel`/
87+
// `-ffreestanding`/`-nostdinc++`/`-fno-exceptions`/`-fno-rtti`, and which
88+
// ones is a decision MCPP MAKES — so it changes between mcpp versions
89+
// while the triple string does not. Without this axis the key cannot tell
90+
// the two apart, and an upgrade that adds a flag serves BMIs built before
91+
// it. That is not a stale-cache annoyance; it is a hard failure whose
92+
// message names a .pcm file:
93+
//
94+
// error: exception handling was enabled in precompiled file
95+
// 'mcpplibs.riscv_virt_rt.pcm' but is currently disabled
96+
//
97+
// Measured on exactly that upgrade. Empty for hosted targets, so nothing
98+
// else's key moves.
99+
std::vector<std::string> targetImpliedFlags;
82100
std::string stdlibId;
83101
std::string stdlibVersion;
84102
// B
@@ -198,6 +216,7 @@ nlohmann::json to_json(const BuildAxes& b, const PackageAxes& p) {
198216
{"compiler_version", b.compilerVersion},
199217
{"driver_identity", b.driverIdentity},
200218
{"target_triple", b.targetTriple},
219+
{"target_implied_flags", b.targetImpliedFlags},
201220
{"stdlib", b.stdlibId},
202221
{"stdlib_version", b.stdlibVersion},
203222
};
@@ -244,6 +263,7 @@ std::string key_hex(const BuildAxes& b, const PackageAxes& p) {
244263
put(s, "ccver", b.compilerVersion);
245264
put(s, "driver", b.driverIdentity);
246265
put(s, "triple", b.targetTriple);
266+
put_list(s, "targetflags", b.targetImpliedFlags);
247267
put(s, "stdlib", b.stdlibId);
248268
put(s, "stdlibv", b.stdlibVersion);
249269
// B
@@ -295,6 +315,9 @@ BuildAxes build_axes(const mcpp::toolchain::Toolchain& tc,
295315
: (tc.binaryPath.empty() ? std::string{}
296316
: mcpp::toolchain::hash_file(tc.binaryPath));
297317
b.targetTriple = tc.targetTriple;
318+
if (auto ft = mcpp::toolchain::triple::parse(tc.targetTriple))
319+
if (auto spec = mcpp::freestanding::resolve(*ft))
320+
b.targetImpliedFlags = mcpp::freestanding::compile_flags(*spec);
298321
b.stdlibId = tc.stdlibId;
299322
b.stdlibVersion = tc.stdlibVersion;
300323

0 commit comments

Comments
 (0)