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

feat: 图形栈闭合与分发档位 —— 标签、一条没人依赖的边、一个被钉住的 pin (#405, #407) (2026.8.10.2)#408
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_packagespackages 本来就含依赖包根(prepare.cppm:3784/:4183),谓词恒为真。真因是没有任何边依赖那条边。按 issue 里的修法(entry.jsonimports_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.jsonloader_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

2 participants