Skip to content

feat: 图形栈闭合与分发档位 —— 标签、一条没人依赖的边、一个被钉住的 pin (#405, #407) (2026.8.10.2) - #408

Merged
Sunrisepeak merged 1 commit into
mainfrom
feat/graphics-closure-and-distribution-tiers
Aug 10, 2026
Merged

Sunrisepeak merged 1 commit into
mainfrom
feat/graphics-closure-and-distribution-tiers

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

设计:.agents/docs/2026-08-10-graphics-closure-and-distribution-tiers-design.md
实施计划:.agents/docs/2026-08-10-graphics-closure-implementation-plan.md
Closes #405, closes #407.

图形栈不可用不是一个 bug,是三层各自独立的故障。追到代码之后,其中两层的既有结论是错的 —— 按它们去修,修完还是坏的。

三条被测量推翻的结论

1. #405 的 issue 根因是错的(而且是这个 issue 上第二次)

issue 说「谓词漏判 ⇒ 不发 std 的 stage 边」。生成物否掉了它:

65: build gcm.cache/std.gcm : stage_file <cache>/std/…/std.gcm     ← 边就在这
83: build _mcpp_staged_cache : phony <3 个 dep .o> <3 个 dep .gcm>  ← 不含它
90: build obj/main.o : cxx_object … || _mcpp_staged_cache           ← 零引用 ⇒ 永不执行

scan_packages 的 packages 本来就含依赖包根(prepare.cppm:3784/:4183),谓词恒为真。真因是没有任何边依赖那条边。按 issue 里的修法(entry.json 记 imports_std)作用在一个已经为真的谓词上 —— 改完仍然坏。

2. index 上 8 个图形成员全红不是数据缺陷

xim:libglvnd@>=1.7.0.1 在 xlings 2026.8.9.2 起解析正常(四段版本 semver 重写)。红的原因是 mcpp-index CI 钉在 mcpp 2026.8.8.2,它内带 xlings 2026.8.8.1 —— 正好落在修复之前。xim-pkgindex 一个字都不用改。

3. 图形拿不到 GPU 的直接原因在 mcpp 自己的链接命令行里

全仓 --disable-new-dtags 零处;唯一相关的一处显式写了 --enable-new-dtags。

变更

内容
#405 std BMI 进 _mcpp_staged_cache —— 那个聚合本来就是为「stage 边丢掉编译边携带的次序」建的。不动 cache key / entry schema,现有缓存全部有效
#407 build.ninja 自己声明形态(# mcpp:graph=),快路径校验它即将重放的那张图。读取侧不变式,同时删掉 #387 的写入侧修补 —— 写入侧要求每个未来的图重写者都记得调用,这正是 mcpp test 那半边一直坏着的原因
加载器标签契约 可执行 DT_RPATH、库 DT_RUNPATH。这是 xlings 图形栈设计里 E2(构建侧)的 mcpp 那一半
rule E 标签校验落在产物上,写进 resolution.json 的 loader_tags,warn-first
pack bundle 里每个 ELF 都重写为 $ORIGIN;不碰动态加载器
HOST-REQUIREMENTS 驱动只能来自目标机器,所以诚实产出是 bundle 加一份声明
自带 libc 的档 self-contained / static 遇 run 期能力需求 plan 期硬拒,并指出改用 vendored
artifact 身份判决 identity: ok/mismatch/missing/unverified,跟随符号链接
pin 内带 xlings → 2026.8.10.4(16 个 pin 由 check_version_pins.sh 机器校验)

为什么标签是决定性的

DT_RUNPATH 只对携带它的对象自己发起的 dlopen 生效。图形程序到驱动要经三到四层 dlopen,而这些 dlopen 都不是它发起的 —— 是 libGLX.so.0 / libEGL.so.1 代发的。所以应用二进制自身的 dlopen 引用数是 0,「需不需要传递标签」在二进制上看不出来。同一路径只翻标签,egl/gles2/egl-surfaceless 从 llvmpipe 变 NVIDIA。

反过来在库上强制 RPATH 有害(传递性打断 eglInitialize,xlings#593),所以这是一分为二,不是一起翻。

本地验证

  • unit 77/77 通过(含新增 test_loader_contract)。
  • e2e 183 通过 / 25 失败 / 8 跳过。25 条里 24 条用已发布的 2026.8.8.2 逐条复现,同一个环境根因:本机共享 gcc 载荷的 specs 被历史安装污染 —— --dynamic-linker 指向已被改名走的 glibc/2.44,rpath 里还有约 40 条来自已删除沙箱的 /tmp/tmp.* 条目。这台机器上跑不了任何 build.mcpp 与共享库用例,CI 是这部分的真判据。
  • 第 25 条(30_pack_modes)是真回归,已定位并修复:不得 patchelf 动态加载器 —— 它不是被搜索的库,它是执行搜索的程序,改它会让 self-contained 档在 main 之前段错误。

每条新 e2e 都先证伪过(实测,非推理)

测试 撤掉什么 观察到的红
212 std BMI 不进聚合 std.gcm: No such file or directory / Bad import dependency
213 快路径不校验 header # mcpp:graph=test + Finished dev in 0.00s
214 契约 flag 链接边上没有标签 flag
215 不重写 bundle 内的 .so 产物 RUNPATH 里出现 <store>/xim-x-glibc/2.39/lib:<store>/xim-x-gcc/…

两个测试陷阱(都踩过,已写进注释)

  • 212/213 全程不删产物:删了会让 ninja 以「图过期」的样子失败,快路径回退到完整 prepare,未修的二进制也会绿。213 的第一版还 touch src/main.cpp,那会按 mtime 让快路径失效 —— 同样是自我遮蔽,已删掉。
  • 214 用共享库当对照组:它不带 flag,所以它的标签就是链接器默认值。默认值哪天变了,是库那条断言先红并说明原因,而不是可执行那条静默地为了别的理由继续通过。

一处架构边界(被既有守卫抓到,很有价值)

第一版的 discovery 是 mcpp 从能力名推断的。test_runtime_contract 的
SourceOwnsNoProviderSpecificSelectionOrProbeBranch 立刻抓住 —— 那是把 provider 专属知识写进 mcpp。改成声明式([[runtime.requirements]] discovery = "..."),mcpp 只承载不推断,未声明报 unknown。守卫是对的,我是错的。

图形栈不可用不是一个 bug,是三层各自独立的故障。追到代码之后,其中两层的既有结论
是错的 —— 按它们去修,修完还是坏的。

## 三条被测量推翻的结论

1. #405 的 issue 根因是错的(我自己写的那条,而且是这个 issue 上第二次)。
   它说「谓词漏判 ⇒ 不发 std 的 stage 边」。生成物否掉了它:边就在 build.ninja 第 65 行。
   `scan_packages` 的 packages 本来就含依赖包根,谓词恒为真。真因是**没有任何边依赖
   那条边**,ninja 于是从不执行它。按 issue 里的修法(entry.json 记 imports_std)
   作用在一个已经为真的谓词上 —— 改完仍然坏。

2. mcpp-index 上 8 个图形成员全红不是数据缺陷。`xim:libglvnd@>=1.7.0.1` 在
   xlings 2026.8.9.2 起解析正常(四段版本 semver 重写),红的原因是 index CI 钉在
   mcpp 2026.8.8.2,它内带 xlings 2026.8.8.1 —— 正好落在修复之前。
   xim-pkgindex 一个字都不用改。

3. 图形拿不到 GPU 的直接原因在 mcpp 自己的链接命令行里:全仓没有一处
   `--disable-new-dtags`,唯一相关的一处显式写了 `--enable-new-dtags`。

## 修复

- **#405** 缓存命中时,被恢复的包传递依赖的 std BMI 没有消费者。修法把它放进
  `_mcpp_staged_cache` —— 那个聚合本来就是为「stage 边丢掉编译边携带的次序」建的,
  std BMI 是同一缺陷早一条边。不动 cache key / entry schema,现有缓存全部有效。
  缓存 miss 时依赖在本地编译、把 std 边带进图,所以第一个构建它的人永远是好的、
  之后每个人都坏 —— 这就是它伪装成升级回归的方式。

- **#407** 三种模式写同一个 build.ninja,快路径只比源码 mtime。改成让图自己声明形态
  (`# mcpp:graph=normal|test`),快路径校验它即将重放的那张图。**读取侧不变式**,
  并同时删掉 #387 留下的写入侧修补 —— 写入侧要求每个未来的图重写者都记得调用,
  这正是 `mcpp test` 那半边在 `--configure-only` 修好之后仍然坏着的原因。

## 新增

- **加载器标签契约**(`mcpp.build.loader_contract`):可执行 DT_RPATH、库 DT_RUNPATH。
  DT_RUNPATH 只对携带它的对象**自己发起**的 dlopen 生效,而图形程序到驱动的三到四层
  dlopen 都不是它发起的 —— 是 libGLX.so.0 代发的。所以决定能否上 GPU 的是标签不是路径。
  反过来在库上强制 RPATH 有害(打断 eglInitialize),所以一分为二。链接期与 pack 的
  patchelf 期读同一条契约。这是 xlings 图形栈设计里 E2(构建侧)的 mcpp 那一半。
- **rule E**:标签校验落在产物上,写进 resolution.json 的 `loader_tags`,warn-first。
  记录而不只是告警 —— 只在沉默中通过的检查,和根本没跑的检查,输出完全相同。
- **pack 不再残留构建机路径**:此前只重写主二进制,bundle 进来的每个 .so 都保留着
  指向构建机 xlings store 的绝对 RUNPATH。「依赖 xlings 生态」是设计选择,
  「依赖这一台机器的这一份 store」是缺陷,而且在构建它的机器上跑得好好的。
  **不碰动态加载器** —— 它不是被搜索的库,它是执行搜索的程序;patchelf 改它会让
  self-contained 档在 main 之前段错误(30_pack_modes 抓到的)。
- **HOST-REQUIREMENTS**:自包含有底。驱动只能来自目标机器(与内核模块锁步 + 禁止
  再分发),所以诚实产出是 bundle 加一份声明。带 discovery 列,因为几种发现机制
  互不通用。
- **自带 libc 的档硬拒宿主能力**:self-contained 与 static 在 plan 期失败并指出改用
  vendored。两者坏在同一件事上(#392/#401 的两个方向),此前都链得过去然后运行时崩。
- **`[[runtime.requirements]] discovery`**:声明式,mcpp 绝不推断 —— 从能力名推断
  就是把 provider 专属知识写进 mcpp,`test_runtime_contract` 正是为此设的门。
- **artifact 身份判决**:resolution.json 每个 artifact 带 `identity`
  (ok/mismatch/missing/unverified),跟随符号链接。这是 mcpp 已经对私有 libc 执行的
  规则的推广,纯路径事实。`why runtime` 的 `(none declared)` 改为
  `(not declared by the environment — nothing to verify)`:有名无物是未验证,不是通过。

## 变更

- 内带 xlings 升到 2026.8.10.4(16 个 pin 由 check_version_pins.sh 机器校验)。

## 本地验证

- unit 77/77 通过(含新增 test_loader_contract:契约、图形态、宿主要求、身份判决)。
- e2e 183 通过 / 25 失败 / 8 跳过。**25 条里 24 条用已发布的 2026.8.8.2 逐条复现**,
  同一个环境根因:本机共享 gcc 载荷的 specs 被历史安装污染 —— `--dynamic-linker`
  指向已被改名的 glibc/2.44,rpath 里还有约 40 条来自已删除沙箱的 /tmp/tmp.* 条目。
  第 25 条(30_pack_modes)是真回归,已定位并修复(不得 patchelf 动态加载器)。
- 每条新 e2e 都先证伪过:撤掉对应修改必须变红,并已实测。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mcpp build 会重放 mcpp test 留下的构建图:源码没动,普通构建却编测试文件并失败 Bug: 依赖 BMI 缓存命中时,被恢复的包 BMI 的传递 import std 没有进入消费方的构建图

2 participants