两个互相独立的 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
.bmiOnlyFlags = " --precompile -Xclang -emit-reduced-module-interface",
发布给 importer 的一律是 reduced BMI。
- 缺陷 2:
src/build/schedule/policy.cppm 里明确写了 —— 两阶段的 object 边重编源码,
绝不 -c x.pcm。前端多做一遍,实测在每个并发档位仍然净赚
(-j4 56.3s→37.6s,-j8 34.0s→25.6s,-j32 32.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 但不做任何事(见上面第一张表第三行)。
待办
两个互相独立的 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可能承担两件事,这决定了它能不能瘦:--precompileimport,并且是 codegen 的输入(clang -c x.pcm)-fmodule-output=import,object 由源码那一次编译产出--precompile -fmodules-reduced-bmi--precompile -Xclang -emit-reduced-module-interface--precompile发 reduced所以
--precompile发 full 是设计使然:那份 BMI 还要拿去生成目标文件。缺陷 1 — full BMI 毒化下游,报错点落在 libc++ 里
把
--precompile产出的 full BMI 发布给 importer 之后,一个用窄格式串的下游TU 编不过:
那一行是纯窄字符的格式串,却被实例化成了
basic_format_string<wchar_t, ...>。报错点在 std 头文件里,离真因很远。
换成 reduced BMI,同一份源码、同一个编译器,编译通过。
合法的 BMI 不应该改变 importer 里的重载决议 —— 这条算 clang 的缺陷。
复现
两个都编译通过,即不构成复现:
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 行的最小复现。
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()?clang -c <reduced>.pcm退出码 0,只是把函数定义丢了 —— 失败推迟到链接期(undefined symbol),或者在有别处提供同名符号时根本不失败。
reduced BMI 按定义不含函数体,所以它不能做 codegen 输入是对的;
但这应当被诊断,而不是静默产出一个残缺的 object。
mcpp 为什么不受影响(以及这不是「修好了」)
src/toolchain/model.cppm:277.bmiOnlyFlags = " --precompile -Xclang -emit-reduced-module-interface",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拒绝把 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 但不做任何事(见上面第一张表第三行)。
待办
set_policy("build.c++.modules.two_phases", false)能否让 xmake 走-fmodule-output形状从而绕开缺陷 1 —— 若可以,bench 侧就有一个受支持的配置而不是 hack(代价是失去 xmake 的两阶段)
bmiOnlyFlags与「object 边重编源码」还有没有必要