Skip to content

clang 22: full BMI from --precompile poisons importers; -c <reduced>.pcm silently emits an object with no definitions #424

Description

@Sunrisepeak

两个互相独立的 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

⚠️ 已经排除的最小化尝试(别再走一遍)

两个都编译通过,即不构成复现:

  1. module a 用 GMF #include <format>,下游 import a + 窄 std::format —— 通过
  2. module aimport 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:43if has_two_phases and add_reduced_flag死代码
    —— 第 40 行已保证两者不可能同真,那条 reduced.<name>.pcm 路径永远走不到。
  • support.lua:375has_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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    upstream-bug根因在上游(编译器/构建工具/依赖),mcpp 只能绕过或声明为缺口

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions