Repository navigation
clang 22: full BMI from --precompile poisons importers; -c <reduced>.pcm silently emits an object with no definitions #424
Description
两个互相独立的 clang 缺陷,都是在跑 bench/ 标准集(xmake + payload clang)时撞到的。
mcpp 自己两条都不受影响,但都是绕过去的,不是修好的,所以记在这里。
编译器:clang version 22.1.8 (llvm-project ca7933e47d3a3451d81e72ac174dcb5aa28b59d1),
payload xim-x-llvm/22.1.8,Linux x86_64。
先把语义摆清楚:BMI 有两种角色
clang 里同一个 .pcm 可能承担两件事,这决定了它能不能瘦:
| 产生方式 | 这个 BMI 的用途 | 22.1.8 实测体积 |
|---|---|---|
--precompile |
给下游 import,并且是 codegen 的输入(clang -c x.pcm) |
full,20844 B |
-fmodule-output= |
只给下游 import,object 由源码那一次编译产出 |
reduced(已是默认),19276 B |
--precompile -fmodules-reduced-bmi |
— | 20844 B —— 这个 flag 是空操作 |
--precompile -Xclang -emit-reduced-module-interface |
强行让 --precompile 发 reduced |
19276 B |
所以 --precompile 发 full 是设计使然:那份 BMI 还要拿去生成目标文件。
缺陷 1 — full BMI 毒化下游,报错点落在 libc++ 里
把 --precompile 产出的 full BMI 发布给 importer 之后,一个用窄格式串的下游
TU 编不过:
.../include/c++/v1/__format/format_functions.h:99:30: error:
call to implicitly-deleted default constructor of
'formatter<basic_string<char>, wchar_t>'
99 | formatter<_Tp, _CharT> __f;
.../src/cli/cmd_xpkg.cppm:140:17: note: in instantiation of template class
'std::__1::basic_format_string<wchar_t, std::__1::basic_string<char>...>'
140 | "{{\"namespace\":\"{}\",\"name\":\"{}\",\"error\":\"{}\"}}",
.../__format/formatter_string.h:139:70: note: default constructor of
'formatter<basic_string<char>, wchar_t>' is implicitly deleted
那一行是纯窄字符的格式串,却被实例化成了 basic_format_string<wchar_t, ...>。
报错点在 std 头文件里,离真因很远。
换成 reduced BMI,同一份源码、同一个编译器,编译通过。
合法的 BMI 不应该改变 importer 里的重载决议 —— 这条算 clang 的缺陷。
复现
git submodule update --init bench/projects/mcpp/mcpp-2026.8.11.3
cd bench/projects/mcpp/mcpp-2026.8.11.3
# 用 xmake 的默认形状(two_phases=true ⇒ --precompile ⇒ full BMI)
xmake f -p linux -m release --toolchain=llvm --sdk=<llvm-payload> --runtimes=c++_static
xmake build⚠️ 已经排除的最小化尝试(别再走一遍)
两个都编译通过,即不构成复现:
module a用 GMF#include <format>,下游import a+ 窄std::format—— 通过module a用import std;导出一个返回std::string的函数,下游import std; import a;
加窄std::format—— full / reduced 两种 BMI 都通过
所以触发条件比「full BMI + std::format」更窄,大概率还需要导入图里存在某个
wchar_t / std::wstring 的形态(mcpp 树里有 Windows 相关代码)。最小复现尚未做出,
目前只有上面那份真实工程的复现。
缺陷 2 — -c <reduced>.pcm 不诊断,静默产出没有定义的目标文件
这条更危险,而且有 8 行的最小复现。
// rb.cppm
export module rb;
export int busy(int n) {
int acc = 0;
for (int i = 0; i < n; ++i) acc += (i * 3) % 7;
return acc;
}
export template <class T> T twice(T v) { return v + v; }
export struct Big { int a[64]; int sum() const { int s=0; for (int x : a) s+=x; return s; } };clang++ -std=c++23 --precompile -x c++-module rb.cppm -o full.pcm
clang++ -std=c++23 --precompile -Xclang -emit-reduced-module-interface \
-x c++-module rb.cppm -o red.pcm
clang++ -std=c++23 -c full.pcm -o from-full.o # 退出 0
clang++ -std=c++23 -c red.pcm -o from-red.o # 退出 0 ← 问题在这| 来源 | .o 大小 |
定义的符号数 | 定义了 busy() ? |
|---|---|---|---|
| full BMI | 1664 B | 3 | 是 |
| reduced BMI | 1288 B | 1 | 否 |
clang -c <reduced>.pcm 退出码 0,只是把函数定义丢了 —— 失败推迟到链接期
(undefined symbol),或者在有别处提供同名符号时根本不失败。
reduced BMI 按定义不含函数体,所以它不能做 codegen 输入是对的;
但这应当被诊断,而不是静默产出一个残缺的 object。
mcpp 为什么不受影响(以及这不是「修好了」)
- 缺陷 1:
src/toolchain/model.cppm:277发布给 importer 的一律是 reduced BMI。.bmiOnlyFlags = " --precompile -Xclang -emit-reduced-module-interface", - 缺陷 2:
src/build/schedule/policy.cppm里明确写了 —— 两阶段的 object 边重编源码,
绝不-c x.pcm。前端多做一遍,实测在每个并发档位仍然净赚
(-j456.3s→37.6s,-j834.0s→25.6s,-j3232.0s→18.0s)。
两条都是规避。上游修好之前,任何走 --precompile → -c x.pcm 形状的构建工具
在 clang 22 上都会踩缺陷 1。
对生态的影响:这不是 xmake 的 bug
xmake 3.1.0 的 build.c++.modules.two_phases 默认 true
(core/project/policy.lua:85),该形状是 --precompile → .pcm → -c .pcm,
必须要 full BMI。所以 rules/c++/modules/clang/builder.lua:40
-- disabled with two phases currently, LLVM currently have a bug which prevent to emit reduced bmi when using two
local add_reduced_flag = not has_two_phases and has_reduced_bmi拒绝把 reduced 和 two_phases 组合起来是正确的 —— 注释里那句 "LLVM bug" 指的正是
上面的缺陷 2。结论是:xmake 在 clang 22 上必然发 full BMI,因而必然触发缺陷 1,
这是上游语义逼出来的,不是它写错了。
顺带两处不影响正确性的小问题,值得给 xmake 提:
builder.lua:43的if has_two_phases and add_reduced_flag是死代码
—— 第 40 行已保证两者不可能同真,那条reduced.<name>.pcm路径永远走不到。support.lua:375用has_flags("-fmodules-reduced-bmi")探测是假阳性:
clang 22.1.8 接受这个 flag 但不做任何事(见上面第一张表第三行)。
待办
- 把缺陷 2 提到 llvm-project(有最小复现,可以直接开)
- 缺陷 1 先做出最小复现再提;在那之前 bench 里这一格按已声明的缺口记录
- 验证
set_policy("build.c++.modules.two_phases", false)能否让 xmake 走
-fmodule-output形状从而绕开缺陷 1 —— 若可以,bench 侧就有一个受支持的配置
而不是 hack(代价是失去 xmake 的两阶段) - 上游修好后回头检查
bmiOnlyFlags与「object 边重编源码」还有没有必要
Activity
docs(bench): publish the Linux standard set — 696 samples, gaps marke…
feat(bench): record WHICH BUILD of mcpp a report measured (task #16)
feat(bench): cross-platform benchmark suite, and the defects it expos…