feat: 图形栈闭合与分发档位 —— 标签、一条没人依赖的边、一个被钉住的 pin (#405, #407) (2026.8.10.2) - #408
Merged
Merged
Conversation
图形栈不可用不是一个 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 都先证伪过:撤掉对应修改必须变红,并已实测。
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
图形栈不可用不是一个 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在 xlings2026.8.9.2起解析正常(四段版本 semver 重写)。红的原因是 mcpp-index CI 钉在 mcpp2026.8.8.2,它内带 xlings2026.8.8.1—— 正好落在修复之前。xim-pkgindex一个字都不用改。3. 图形拿不到 GPU 的直接原因在 mcpp 自己的链接命令行里
全仓
--disable-new-dtags零处;唯一相关的一处显式写了--enable-new-dtags。变更
_mcpp_staged_cache—— 那个聚合本来就是为「stage 边丢掉编译边携带的次序」建的。不动 cache key / entry schema,现有缓存全部有效build.ninja自己声明形态(# mcpp:graph=),快路径校验它即将重放的那张图。读取侧不变式,同时删掉 #387 的写入侧修补 —— 写入侧要求每个未来的图重写者都记得调用,这正是mcpp test那半边一直坏着的原因resolution.json的loader_tags,warn-first$ORIGIN;不碰动态加载器self-contained/static遇 run 期能力需求 plan 期硬拒,并指出改用vendoredidentity: ok/mismatch/missing/unverified,跟随符号链接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),所以这是一分为二,不是一起翻。本地验证
test_loader_contract)。2026.8.8.2逐条复现,同一个环境根因:本机共享 gcc 载荷的specs被历史安装污染 ——--dynamic-linker指向已被改名走的glibc/2.44,rpath 里还有约 40 条来自已删除沙箱的/tmp/tmp.*条目。这台机器上跑不了任何build.mcpp与共享库用例,CI 是这部分的真判据。30_pack_modes)是真回归,已定位并修复:不得 patchelf 动态加载器 —— 它不是被搜索的库,它是执行搜索的程序,改它会让 self-contained 档在main之前段错误。每条新 e2e 都先证伪过(实测,非推理)
212std.gcm: No such file or directory/Bad import dependency213# mcpp:graph=test+Finished dev in 0.00s214215<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。守卫是对的,我是错的。