# Wangshu llmdoc 文档地图

> 项目状态:**P1(crescent 解释器)+ P2(bridge 分层桥,PB0-PB7 + 后续优化轮 #1-#4)+ P3(gibbous/wasm 编译层,PW0-PW10)全卷已收口;P4(method JIT)多 PJ 已完成(PJ0-PJ11 + PJ10 native emit,2026-07-01),P5 仍为规划**——P1 全卷(M0-M14 + 收尾轮 + 测试加固轮 + 完整性补全轮 + 长稳承诺轮 + 外部审查修复轮 + 官方测试套与性能轮)与 P2 全卷(2026-06-13 单会话 PB0-PB7 冲刺 + 同会话 P2 后续优化轮 #1-#4:bridge 包骨架 + 回边/入口采样 + IC 反馈聚合 + F1-F7 可编译性检查 + TierState 状态机 + Logger + mock P3 + e2e 验收 + stdlib 白名单 + 阈值校准占位 + sync.Pool (C) 双表混合 + megamorphic 主动识别)交付。验收:simple 9.0x / arith 7.0x / loop 2.45x over gopher-lua;realworld 五项中四项反超(fib 1.31x / binary-trees 1.09x / spectral-norm 1.43x / nbody 1.08x;fannkuch 0.82x 为剩余短板);70 种子 + 随机脚本差分测试官方 5.1.5 逐字节一致;P2 通过四档验收(byte-equal、profile 累积、升层日志、多 State `-race`)。**P3 PW0-PW10 全卷已收口**(PW0 spike 检查过线 wazero call boundary 36.7ns<150ns → PW1 包骨架 + arena 收养 wazero memory → PW2 直线 opcode + crescent↔gibbous trampoline 端到端 + VS0 值栈 arena 迁移 → PW3 算术/比较 opcode(f64 快路径 + slow-path 助手)→ PW4 控制流 + relooper 结构化生成(循环/分支/嵌套逐字节一致)+ FORPREP/FORLOOP → PW4b TFORLOOP 泛型 for(经 h_tforloop 调迭代器 + base 刷新)→ PW5 表 IC opcode(GETGLOBAL/SETGLOBAL/GETTABLE/SETTABLE/SELF inline IC 快照固化跳哈希 + NEWTABLE/SETLIST 走助手,失效逐字节降级、零 deopt)→ PW6 CALL/TAILCALL 跨层三向互调(base 刷新解 growStack 段重定位 UAF + 错误 status 链穿越冒泡)→ PW7 CLOSURE/CLOSE(VARARG 白名单拒)→ PW8 线程级 tier 规则(协程不升层,onMain bool 升层门禁)→ PW9 端到端总验收(正确性轴 V1-V13 三方逐字节 + V17 四 build + V18 -race + 性能轴 V14 loop 核 2.58x≥2x 全过;V15 geomean 0.79x 未过,跨层调用税拆 PW10);全 38 opcode 除 VARARG(F1 排除)外全部可翻译。**PW10 消除 gibbous→gibbous 跨层调用税:R1/R2/R3/R3.5 + 零跨界 ①/基建-a/基建-b/③a/③b/④-i 骨架/顶层升层 callOnStack 全过线**(R1 memadapter env holder 导出共享 funcref 表 TableSlots=8192,各 gibbous module active element 段自注册 `run` / R2 完整 CallInfo→linear-memory 迁移 = 长期延后的 VS0-e,4 word/帧,R2a→R2b-1~4 收口/只写影子/翻转/退役 / R3 `call_indirect` 直接分派消 `code.Run` 重入,3 路 i64 sentinel + `ciTransferRef` 中转字 + `h_callerr` 弹孤立帧 / R3c-fix 出错点就地标注使错误逐字节等纯解释器 / R3.5 host helper `WithFunc`→`WithGoFunction` 消反射装箱 / 零跨界 ① top mirror 字 + 基建-a closure slot 缓存 + ③a savedTop + ③b emitReturn 守卫快路径 / 基建-b proto cache 段 + ④-i emitCall 守卫骨架 + fastCallHits mirror 字 / callOnStack 顶层升层 cl 直接走 enterGibbous + TopLevelUplift 探针);**本机 Xeon 6982P 2s×3 count 实测基线(2026-06-16)**:**loop 2.95x(+10% over R3.5 2.67x,③ RETURN 拆帧真实收益)/ table 0.88x / call 0.52x / mixed 0.99x**;**call 0.52x 是 bench kernel 结构性架构边界**——profile `/tmp/call.prof` 实证四 kernel body 含 ReasonUnknownCall(F2-b 静态分析不能确定被调函数不 yield)→ body 不可升 → 顶层升层 + ④ emitCall fast body 均对 bench kernel 无显著效果;**④-ii fast body 未交付**(预估上限 0.57x 仍 <1x,实现复杂度 ~200 行 wasm 字节级 codegen,UAF 高,ROI/UAF 不利留 followup,emit 原语 i64.add/i64.or 已保留供未来 ④-ii);**PW10 收口为「已完成子里程碑 + 架构边界文档化」**,旧文档所述「四核全翻面」+「剩 R4/R5 待实现」失实须替换;**VS0-e 全量收口(2026-06-16)**——task #6 描述三项里两项早隐性交付(协程栈 VS0-c / CallInfo word 位打包 PW10 R2),本会话补齐 varargs 进栈下区子步 ①~④(`c22798b`/`4e50687`/`966318c`/`ed95020` + 反思 `b9a2c54`),官方 Lua 5.1 真栈布局 `[func | vararg | R(0)..]` 完成,退役 `ci.varargs` Go slice + `th.ciVarargs` 影子 + setVarargs/clearVarargs/varargsAt 三 API,统一栈模型 + 简化 GC 路径达成;**P4 设计文档集已扩展(2026-06-24)**——单文件 360 行 → 子目录 10 文件 ~8200 行(00-08 + implementation-progress,P3 同档详细深度),含 PJ0-PJ11 里程碑预设 + V1-V22 验收口径 + 34 项跨文档回填请求登记(方案 A:P4 自管投机生命周期 / P2 三态枚举不变;原 RJ-8/9/10 撤回);P5 仍是单文件架构决策深度**。`docs/design/` 共约 44 篇约 3.8 万行设计文档仍是规范源(P1 全卷 00-12 可实现深度、**P2 全卷 00-06 + implementation-progress 可实现深度(PB0-PB7 + 后续优化轮 #1-#4 全过线)**、**P3 全卷 00-08 + implementation-progress 可实现深度(子目录 10 文件 ~8800 行,含全 opcode 翻译表 + WAT 伪码 + V1-V18 验收口径;PW0-PW9 + PW4b 全过线)**、**P4 全卷 00-08 + implementation-progress 详细设计深度(子目录 10 文件 ~8200 行,2026-06-24 扩展,含 PJ0-PJ11 + V1-V22 + 34 项回填请求,方案 A:P4 自管投机生命周期/P2 状态机不变;原 RJ-8/9/10 撤回)**、P5 架构决策);P1 实现现状与 P3 迁移留口见 `docs/design/p1-interpreter/implementation-progress.md` 对账表;P2 实施现状对账表见 `docs/design/p2-bridge/implementation-progress.md`;P3 实施现状对账表见 `docs/design/p3-wasm-tier/implementation-progress.md`;**P4 实施现状(立项前)对账表见 `docs/design/p4-method-jit/implementation-progress.md`**(含 34 项 RJ 回填请求,方案 A)。本文档库是源文档之上的**知识压缩层**,记录意图与路由。
> 启动阅读顺序请看 [[startup]](本文件不重复有序启动清单)。
> **术语:`P2+` = P2 后续优化轮**(P2 主体 PB0-PB7 交付之后的持续优化项),**不是 P3 阶段**——本轮 2026-06-13 已把 P2 后续优化轮 #1-#4 全部融入 P2 主交付。见 design `p2-bridge/` 各篇与 `implementation-progress.md` §0 头注。
> **术语:`PW`(P-Wasm)= P3 实现里程碑编号**(对应 P1 的 M、P2 的 PB);PW0 = wazero call boundary spike 检查,PW1-PW9 = 翻译器到端到端验收。见 `docs/design/p3-wasm-tier/00-overview.md` §4。

## 类别用途

- **`must/`** —— 每次任务都应先读的微型启动文档。只放跨任务、稳定、几乎每次都用得上的知识。
- **`overview/`** —— 项目/大特性的身份、边界与角色。
- **`architecture/`** —— 检索地图、所有权边界、流程与不变式。
- **`guides/`** —— 一篇一个工作流。
- **`reference/`** —— 稳定查阅事实:契约、schema、约定、术语。
- **`memory/`** —— 历史过程记忆。`memory/reflections/` 归 reflector 所有;`memory/decisions/` 与 `memory/doc-gaps.md` 归 recorder 所有。

## 现有文档与路由提示

### must/
- [[design-premises]] — **最重要的 MUST**。四组不可妥协前提:① 列内核负载形状(两个校准测量:LuaJIT 仅比 luajc 快 6%;生产端到端被稀释到 ±5-7% 噪声)② Go runtime 四项税 → 边界几十~百 ns 固定成本 ③ 五条贯穿原则 ④ 第一天 NaN-boxing 值表示承诺。**判断任何提案是否合理前先读这篇。**

### overview/
- [[project-overview]] — 项目身份与边界:望舒/Lua=月亮意象、纯 Go 嵌入式 Lua VM 定位、三层目标(Lua 5.1 核心)、三项非目标(§6)、首个目标宿主(多运行时规则引擎)、当前状态。**想知道「这是什么、不做什么」看这篇。**

### architecture/
- [[evolution-roadmap]] — 分层 VM 五阶段流水线 P1→P5(人力/倍率/验收/前置 spike)、月相 tier 命名映射。**问演进路线、阶段门槛、wazero<150ns spike、crescent/gibbous/fullmoon 看这篇。** 注意流水线图倍率与正文验收门槛不在同一坐标系。
- [[value-representation]] — 值表示与内存模型:NaN-boxing vs Go tagged struct 决策、自管 arena、自写 mark-sweep GC、同一块内存使编译层成增量。**问值/内存/GC/为什么这样选看这篇。**

### reference/
- [[embedding-contract]] — 宿主嵌入契约:`Compile→Program`、`Program.Call(state, arena, args)`(收尾轮已完成,差异标注见该篇)、arena ABI(类型化扁平列 + 字符串区 + presence bitmap,零拷贝读)、per-item 简易 API 子集已完成(含 Table 读写全闭环 + ForEach + globals baseline 状态隔离 + `CallInto` 零分配边界路径 + issue #13 parity-friendly 快路径 typed-array table 族 / `GlobalsSlot` + issue #10 批量 array table 构造 `NewArrayTable`/`Preallocate`)、State 生命周期 admin API(issue #9/#11:`Options.{Initial,Max}ArenaBytes` + `ArenaCapKB`/`GCCountKB` 观测 + `Collect`/`MaybeCollectNow` GC 节奏显式驱动 + `SetHostTriggeredCollect` experimental)、分层执行运行期管理 admin API(PR #115:`SetTierEnabled` kill switch + `TierEnabled` + `TierStatsSnapshot`/`TierStats` 观测,生产灰度 P3/P4 一键退回解释器,完整部署指南 `docs/embedding-tiers.md`)、drop-in 定位。字段级 spec 在 `docs/design/p1-interpreter/11-embedding-arena-abi.md`。**问宿主怎么嵌入、API 形状、边界成本/CallInto、GC 节奏/arena 容量管理、分层执行 kill switch/观测看这篇。**
- [[glossary]] — 术语表 + prior art 借鉴点。**遇到 NaN-boxing/arena/tier/月相/deopt/列内核等术语,或问参照项目看这篇。**

### guides/
- [[multi-doc-drafting]] — 多文档并行起草工作流:回填请求节协议、单点收口、验收口径收口点指定、子代理失败恢复纪律、收尾主动盘点不确定决策、向用户提问自包含契约。**要一次起草多篇互引文档、或大型设计任务收尾时看这篇。**
- [[perf-optimization-workflow]] — 性能优化工作流:profile 先行(预判清单会被推翻)、每项独立 benchmark 验证 + 单域提交、可疑优化 benchmark 否决门 + 快 revert、归因诚实、**跨机器/跨参数基线对照**(同 commit 同硬件同参数复测,memory/reflection 写 perf 数字必标硬件/参数/日期)、池化/复用类优化配套清单。**做性能优化前看这篇。**
- [[public-api-incremental-delivery]] — 公共 API 增量交付工作流(9 条纪律):设计承诺源三角验证、GCRef 接根(指向 [[embedding-contract]] 不变式条款)、单域物理隔离手法、错误消息稳定语义、行为变更显式标注、范围扩张顺手收口、对位测试断言先 grep oracle、internal 接口签名避免反向依赖标准库、**对称面检查**(写入/读出闭环)。**接公共面 issue / 扩 ABI 表面 / drop-in 对位补面 / 给 Program 加字段时看这篇。**
- [[design-claims-vs-codebase-physics]] — 设计稿主张须对本码库 physics 重新验证(6 维判据,四空间内部 + 一时间 + 一空间外部):设计稿抽象记号只表语义不带物理不变式,热路径主张实现前逐条重核——① 边界成本预算(~143ns/跨层,`(call $x)` 能否塌成 inline)② arena 段重定位(跨层固定 token/视图会不会被 grow 搬走、谁刷新)③ 成本归类(架构成本 vs 实现浪费,援引前提判否优化前先分类)④ GC 根可达性(复用栈/共享 arena 返回值的根)**⑤ 时间维度**(设计稿/task 描述/stub sentinel 注释承诺/外部依赖现状在前序事实变更后失效,需显式审计;含 PW7 难点过期 / VS0-e 调研先于实现 / P4 doc-review 外部依赖现状 / PR #28 sentinel 注释承诺与检查翻 true 解耦 / issue #67 promotion 烤死快照 vs 运行期跨 Run 重建(身份 guard 跨 Run 落空,换 key/shape 内容 guard)五实例)**⑥ 空间维度**(同一时刻跨 OS / shell / runtime / runner 物理环境差异:bash 3.2 vs 4+ cheatsheet / actions/cache 对 symlink 的 tar 语义 / homebrew 包政策更迭;PR #28 三平台矩阵 CI 三连实例,跨过提升阈值)。横跨性能(死优化)+ 正确性(UAF + 静默 sentinel)+ 跨部署环境(静默 build break)。§4 另有 **§4.1 新对象类型的分配路径必须照抄同族分配器的每一步**(2026-07-29,#205:file-handle userdata 漏 `LinkSweep`,收集器根本看不见对象,一次 `collectgarbage` 就以 arena 索引越界 panic;`crescent/alloc.go` 里三个现成分配器都写着 `AllocX` + `LinkSweep` + `AllocCharge`,同族里出现三次的动作是契约;症状离原因很远——panic 在 GC 里而错误在分配处,所以「功能全对」与「会破坏 arena」可以同时成立)。**照抄设计稿热路径上的 `(call $x)`/固定 token/成本主张前、检查翻 true 同 commit、接延后 ≥6 个月的 task 前、给一个新对象类型加分配路径时、大文档发布前、CI 矩阵扩平台前、写 shell 脚本要跨 OS 跑前看这篇。**
- [[prove-the-path-under-test]] — 证明在测的路径(绿色 ≠ 在测你以为在测的):测试通过 / 性能数字不动 / 新机制就位**单独任何一个都不证明在测路径被执行**——反模式三档(空测 / 不公平基准 / 静默替身 / 覆盖度自欺)+ 反向侧解药四招(毒化哨兵 helper / 白盒命中计数器 / 非空载体先证路径再读数 / 错误路径用例)+ 正向侧解药三招(§4 覆盖度先 grep 既有 oracle 如 luasuite/官方 5.1 套再决定是否补语料 / **§5 fuzz 探索空间维度动了就重探**:接受面 / 硬件 / OS / 运行参数都是 fuzz 探索空间的维度,任一维动一格就等于新一轮独立探索,变化后立即跑一轮 60~120s fuzz smoke 是廉价高产的默认动作;含 P4 opSupported 扩面三 seed 抓 inline 快路径 bug + M5 Pro 抓 amd64 长期未抓到的 fatal + LEN/MOD/POW 进 opSupported 90s 抓 PerOpCode 回放乱序老 bug 三实例 / **§6 多档机制叠加崩点的 bypass 探针根因 isolate**:N 档机制叠加一样的 SIGSEGV/SIGILL 形式时写 minimal payload 直接调下一层 bypass 当前调试层,20 行代码 + 5 分钟把 N 档收敛到一档;含 CI runner 形式盲配套——多后端/多平台首次真机 execute 必配真机 runner)+ **§7 诊断侧对偶**:退化数字 ≠ 慢在你以为的路径,收到「某条路径导致 N 倍退化」类归因、动手写止血/修复计划前,先 grep build tag / 读函数头注 / 跑白盒探针证该路径在当前二进制里真的存在且可达 + **§7.1 复现侧**:「没复现」不等于「问题不存在」——收到「某机制没生效」的指控要写复现测试时,复现测试自己必须先用白盒探针(`SafepointCalls` / `PromotionCount`)证明走的就是被指控路径,走错路径的复现失败是伪证据、会把真 bug 关掉(PR #161:P3 loop fuel 重新武装 bug 的第一版 FORLOOP 复现走了解释器路径差点误判不存在,换 `test/regression/loop_stepbudget_p3_test.go` 已证明段内 back-edge 的写法才稳定复现) + **§8 度量侧**:加速数字 ≠ 机制在稳态生效,读投机/inline 快路径的收益或复查历史 perf 裁决时,度量单位必须是跨 Run 稳态的 exit-reason dispatch 计数而非单个 Run 内命中数——选错度量单位会把「从没生效的优化」读成「生效但太贵」(issue #67 amd64 NodeHit:烤进段的 TableRef 身份 guard 跨 Run 100% 落空,「命中 17 万次」是单 Run 假信号,PR #74 arm64-only 裁决被证伪;修法换 hmask 边界 + nodeRef guard 使跨 Run 重建的同 shape 表真命中) + **§9.0 选址侧(在 §9.1 之后新增,顺序上却在它之前)**:想在差分 harness 里接受 / 跳过某类平台差异时,**先判这个差异该在哪一侧消除,判在渲染侧就不要设计判据**。两类:「同一个抽象值的不同书写方式」(NaN 符号位——同一个 NaN、两种合规拼写,IEEE 754 不赋那一位数值语义)→ 在**渲染处**消除;「两侧本来就是不同的东西」(引用值地址、两侧独立选定的实现上限如 `stack overflow` / `too many syntax levels` / 200 local variables)→ 才在**比较时**归一或跳过。前者放到比较侧一定不收敛而且原因**可证**:那个符号字节一进字符串就是普通数据,`#` / `==` / `..` / 算术能把它搬到任何地方(`string.len(0/0)*100` 是 400 对 300,输出里连一个 nan 字节都不剩),没有可锚定的标记,任何下游规则都必须读一个两侧不相等的量;后者的标记是**结构性的**(地址永远带 `table: 0x` 前缀、护栏永远是固定错误消息),不会被算术冲掉。判在渲染侧还要确认那一侧**可控**——本仓最初漏掉的正是这一步:对 oracle 的心智模型是「外部真值」,在语义上对、在**物理**上错(它是仓库自己 vendor 进 `internal/oracle/_lua515/`、自己用 cgo 编译进测试二进制的目标,而且已经为确定性 stub 掉 `os.time`/`math.random`/`pairs` 顺序),模型错在物理层于是把「改 oracle 的渲染」整类方案排除在视野外**且不留痕迹**;所以动手前先查参照实现的**供给链**(源码在哪、谁编译的、已打过什么补丁、改它会不会破坏它作为基准的意义)。配套两条:「我实测过某条路不行」保护的是**那一个选项**不保护由它推出的范围(实测证明「改 wangshu 值层不可行」是对的,外推成「只能在 harness 侧容忍」是错的),写否证结论时同时写下它**没有**排除什么;**反复出现 edge case 是选址错误的信号,不是实现不完善的信号**(缺陷密度长期不降本身就是判据),这与 §9.4 是两个尺度——§9.4 管一个机制内部该不该继续修,§9.0 管这个机制装在哪一层,**作用域比判据本身更要紧** + **§9.1–§9.5 差分 harness 判据侧(前提是已按 §9.0 判定该在比较侧处理)**:harness 判据分叉的后果不是误报而是**单侧 skip**——「任一侧报 limit 即 skip」的框架下,判据只在一侧命中会把这条输入连同同一次运行里所有真实差异一起静默丢掉,连可疑的绿灯都不留。四节 —— **§9.1 判据的输入包括它读的值和它查表用的键**(端到端求值列出所有读取点;那一轮踩过 needle 用本引擎拼写 / 对称标记作用于不对称扫描对象 / provenance 按字符串值索引 / 剥符号的 key 只剥第 1 字节 / `#t` 作预扫上界五种载体,「对称的规则作用在不对称的数据上整体依然不对称」);**§9.2 判据自身消耗的资源也是它的输入,而这是三个独立的问题**——时间(一侧解释执行一侧 C:逐字节扫 80 KiB fmt,PUC 392ms OK / wangshu 1m22s 撞 budget)、分配量(PUC 的 GC 在 alloc 帽内回收临时串、wangshu 的 arena 回收不了:103 字节脚本耗尽 arena 而 PUC 706ms 跑完)、matcher 步数预算(只有 wangshu 计步,悬崖约 1 MiB,含前导量词的模式约 1024 字节就撞;**这一维成本比值测不出来**,预算触发时失败那侧反而更快,pcall 包住时还是假分歧),三者要三个测试;**§9.3「不比较那个量」往往比「把比较做对」更可行**(限制扫描规模失败五次,五次都是比较了一个两侧不相等的长度——输出长度 / 输入长度 / 分桶 / 放宽容差 / 带内容例外的输入长度;解法不是第六种比较而是取消比较,把扫描做成不需要上限的;「不可能相等」是可证的——`#` 与 `==` 能把 `..` 强制转换带来的符号字节搬进一个数字,输出里连 nan 字节都不剩;另外规模上限只能限制 harness 自己的循环次数,一旦变成「这个输入可不可比」的判据就会像 `__concat_scan_cap` 那样把超 4096 元素的表整类切掉);**§9.4 同一处代码改到第三版还在出同类缺陷 = 抽象选错,不是实现没写对**(span 吸收逻辑六版才收敛,前五版每次都「修好上一版、引入一个新的」;收敛那版的做法是**先把需要的性质列成约束**——无重试 / 无迭代 / 无 offset 运算——再找同时满足全部约束的写法,三条一起看答案几乎唯一而单看任何一条前五版里都有某版满足它;信号 = 第 N 版的改动若是「加一个上限 / 加一个特殊情况 / 换一个基准点」,它几乎肯定是第 N+1 个缺陷的来源,收敛那版通常反而更短)+ **§9.5 harness 的守护测试自己也要过 §2**(成本测试第一版断「两侧 verdict 一致」,把 bug 改回去照样通过——慢 74 倍仍在预算内;必须断成本比值,因为那才是「输入再大一点就变单侧 skip」的连续量;后来输入上限又让 12 个大变体里 9 个在扫描前短路,测试在量一段没跑起来的代码)+ **§9.6 验证一个差分 crasher 修好了:断 equal 不是断绿,并且双向验证(2026-07-26 新增)**——差分 fuzz 里 **skip 也让测试变绿**但含义相反(equal = 两侧输出逐字节相同,skip = 这条输入被丢掉了,连同它这一次运行里可能携带的真差异),把 skip 读成「修好了」等于把这条输入又藏了一次;两个方向都要走:① 修复后的 HEAD 上断它以**逐字节 equal** 通过(少这一步分不清「修好了」与「被 skip 藏起来了」)② 修复前的 base commit 上断它**确实 FAIL**(少这一步排除不掉「本来就是 nightly 的 flake」);实例 = #187-#190 四个 crasher 在 redesign 之前的 base `c982610` 上全部 FAIL、在当前 master 上全部以真正的 equal 通过,而且**一行代码没改** —— 四个都是 §9.0 那个 NaN 符号字节的表现,根因已在产生处消除,**正是因为消除在产生处四个表现才一起消失**(走下游识别那条路时四个 issue 会各自需要一条新的判据分支);推论:一批 crasher 能被同一个改动一起解决本身就是「根因修在了正确位置」的事后确认信号,分组纪律见 [[unreproducible-crasher-triage]]。聚合 PW5 inline-proof / PW6 TierStuck no-op / PW9 vararg 空测 / PW10 R3 错误路径盲区 / PW10 R1-R2 工作负载错配 / PW10 ③b 快路径命中盲区 / VS0-e 覆盖度先验证 / PR #28 darwin/arm64 bypass 探针 + CI runner 形式盲 / issue #40 arm64 归因证伪(诊断侧对偶,§7) / 三个 fuzz 探索空间维度实例(§5) / issue #67 auto-mode 测试静默退化(未强制测试悄悄滑向纯解释器,解药 `PromotionCount>0` 兜底断言) / issue #67 amd64 NodeHit 跨 Run 度量单位(§8,单 Run 命中数 vs 跨 Run 稳态 dispatch,解药 `TestPJ10_TableNodeHit_CrossRunInline` 断言每 Run dispatch < 500) / PR #161 P3 loop fuel 重新武装复现侧(§7.1,复现测试先证明走的是被指控路径) / issue #184/#185 差分 harness 判据侧(§9.1–§9.5,26 轮盲审、13 轮抓到的缺陷是修上一轮时引入的,四个不对称维度 + 九个 gate 测试) / **同一个问题的选址侧(§9.0,走到第 31 轮后整体转向:差异改在产生它的地方消除,机制整层删除约 −550 行而覆盖面反而增加。前十六个实例都是「在某处证明某件事」,这一个是「先证明这件事该在哪里做」)**十七个独立实例**(跨过 ≥3 实例阈值多次);另 issue #91 顶层 diamond 修复配白盒 `TestP3_TopLevelDiamondPromotes` 断言 `PromotionCount>0` 是 §2(b) 解药的又一应用(byte-equal 分不清真升层与静默 CFAIL 回退,这个能力级 bug 藏这么久正是缺这条正交证据)。与 [[design-claims-vs-codebase-physics]] 构成「实现前重验 / 实现后证明路径」对偶双防线,与 [[perf-optimization-workflow]] §1/§3 配 perf 数字完成三关。**写差分 / 差分测试 / 性能 / IC 快路径 / wasm 快路径 / 错误冒泡类任何对路径执行做断言的测试前、扩接受面 / 换硬件 / 改 fuzz 参数后要不要立刻跑 fuzz 时、调试机制叠加多档崩点时、收到退化归因准备写修复计划前、收到「某机制没生效」的指控要写复现测试时、写 auto/自然触发路径测试不带 force-all 时、读投机/inline 快路径收益或复查历史 perf 裁决时、**想在差分 harness 里接受 / 跳过某类平台差异时(§9.0 先判该在渲染侧还是比较侧消除)**、**写下「我实测过了这条路不行」时(§9.0 同时写下它没排除什么)**、**发现自己在为同一个机制反复补 edge case 时(§9.0 先数缺陷密度降没降)**、**给差分 harness 写跳过判据 / 加前置检查 / 加任何随输入规模增长的扫描时**、**准备给一个两侧不相等的量设计容差或分桶时**、**发现同一处代码改到第三版还在出同类缺陷时**、**要说「这个差分 crasher 已经修好了」之前(§9.6 断逐字节 equal 不是断绿 + 在修复前的 base 上双向验证)**、**修完一个 issue / crasher 报上来的那个具体输入、准备收工时(§4.1:一个 reported case 只是接受面的一个采样,要枚举所有到达同一段代码的通道并逐个验证它真的执行——#192 报 `tonumber("nan(0)")`,枚举通道才发现 base 10 整条路由错、六条分歧里五条没有 issue)**、**读到 / 写下「已知差异 / 已登记豁免 / 已接受偏离」这类注释时(§4.2:必须指出哪一行代码或哪个测试执行了它;一句无执行体的豁免声明比一个已知 bug 更糟,因为它读起来像已经处理过了)**、**回归测试期望与实现冲突、要决定改哪一边时(§4.3:先跑 oracle / 写 C 探测取事实再决定;默认怀疑实现会诱导去「修」一个本来正确的实现,而且改坏之后有一条绿色测试替它背书;**同一陷阱的另一个入口 = issue 正文陈述的参照行为也要亲自跑一遍,#199 说 `io.write` 返回文件句柄、实测 5.1 返回布尔**)**、**修完一个曾经被 skip 绕过的 bug 之后(§4.6:grep 为它加过的 skip / 豁免逐条撤或收窄,确认相应用例现在零 skip 参与比对而不是「测试绿了」,收窄剩下的那条连名字一起改;一个还活着的 skip 与一句无执行体的豁免声明读起来一样,是 §4.2 的时间对偶,#197 那条 skip 覆盖任何提到 error 第二参数的输入)**、**测格式化 / 转换 / 解析类接口时(§4.7:把「不给可选参数」当独立维度扫,参照实现的默认值差异只在省略参数时暴露——`%g` 的显式精度一直是对的,只有默认精度 C 是 6 而 Go 是最短往返)**、**修一个「顺手记下的一组」式 issue 时(§4.3:逐项**重新核对**而不是逐项直接修,集合式 issue 的错误率明显高于专门查证过的单条——#202 七项里三项实测与 lua5.1 一致、根本不成立;自己开这类 issue 要在正文写明未逐项核实)**、**一个改动功能上做对了但让既有压力测试失败、失败点在不熟悉的子系统契约上时(§4.3b:撤回并写清「做到哪、卡在哪、下一步要先搞清什么」,「卡在哪」具体到哪个约定没搞清——`io.stdout` 那次是非 finalizer 路径创建 userdata 的分配与 rooting 约定;撤回的理由不是「太贵」)**、**给一个阈值定数值、或发现两处读同一个常数时(§4.5/§4.5b:先测参照实现在上限附近的耗时;再问它防的是**正确性**还是**资源**,防的东西不同就该是两个常数——`table.insert` 移位上限的产品侧 2^27 与 harness skip 的 2^20;形状逐字对应、数值分开。量参照实现的边界时让**决定它的那个量**变化一格再测,`unpack` 的阈值随参数个数变化才排除了「硬编码 7997」)**、**把两个阈值拆开之后要写测试钉住这件事时(§4.5c:不要在测试里复制那两个数——不导出 / 跨包时复制品会与任一侧各自漂移而测试照旧绿;把两个阈值**之间**的区间做成用例,用「该被执行 / 该被拒绝」表达,并且两个方向都断——`TestInsertShiftThresholdsStayDistinct` 断「必须被执行」+「必须便宜」,只断前者时两个数合到高的那个仍然全绿)**、**给一个批量操作(拼接 / 重复 / 格式化 / 序列化)定预算、或给一个新算子加资源限制时(§4.5d,2026-08-03 新增:① **计量单位要与真实成本同量纲**——一个批量操作有好几个可读的量(元素个数 / 循环次数 / 输入长度 / 产出长度),只有一个与真实代价成正比而最容易拿到的常常不是它;挑错量的后果不是记账偏小而是**整类输入完全绕过记账**,因为那个量被别的东西界住之后预算再也拦不住成本沿另一个维度增长——`table.concat` 的遍历被表长界住,**256 个元素**按个数看微不足道、每个 2 KiB 时实际 53 秒,代价是拼出来的字节数;与 §8 是同一个错误在限流侧的形式(那条讲读性能数字选错单位、这条讲写预算选错单位);手法 = 先写下「这次调用的代价 ≈ 什么的函数」再让记账读那个量,自查用「便宜的量保持很小、真实成本的量推很大」的输入看记账涨不涨。② **同类资源只该有一个计量器**——`string.rep` / `string.format` / `table.concat` 都走 CONCAT 那个 `chargeBulkWork`(为此导出成 `ChargeBulkWork`),各自定阈值会互相漂移、哪个先触发变得难以预测,而预算本身是**可加**的量、多个阈值表达不了「几种批量操作叠加起来超了」这件 fuzzer 会构造的事;与 §4.5b 互补——那条讲两个**目的不同**的阈值不该共用一个数,这条讲两个**目的相同**的判据不该拆成两个数,合起来是「按目的分组、同组共用一个计量器」,自查 = 问「新限制与既有那个同时被一个脚本触碰时谁先触发」,答不出来就说明只该有一个)**、**被测功能依赖 stdin / tty / 信号 / 工作目录这类进程级环境时(§4.10:测试驱动没提供它,测出来的「不工作」是假的——`io.read` 的八个探针全返回 nil 是因为 `go test` 不转发 stdin,实现一处没改;探针结果全是同一个「什么都没有」的值时先怀疑输入没到)**、**给运行时加了任何写外部世界的 API 之后(§9.7:同一轮把它接进差分 harness 的捕获,并写一个「新路径 + 老路径各一段」的用例确认捕获结果里两段都在;漏接的表现最安静——两侧都不捕获、都一致、都不报错,连 skip 都不留)**、**差分比较报一致而输出是空的或是错误文本时(§9.8:确认那不是两侧同样地坏掉了,空输出与错误文本是两个红旗;看 verdict 与实际输出,不只看 diff 是否为空)**、**调紧一个 harness 的资源限制之后(§9.2 补记,2026-08-04 新增:注入一个**必须被发现**的缺陷证明检测能力还在——「减小预算不损失覆盖」不能只靠推理,它的失效方式是「输入根本没跑完」而表现是测试照旧通过;把 p4 fuzz 的 step budget 从 `1<<20` 减到 `1<<16` 时用「截断升层侧返回值」注入了一个真实 P1-vs-P4 分歧,确认新预算下仍然 FAIL、撤掉注入后通过,覆盖那一半则用白盒计数器读 `PromotionCount` 在三个预算下完全相同)**、**新增一个只被 build tag 化文件使用的标识符时(§4.10d,2026-08-04 新增:给它**同样的** tag 并按消费方的每一种组合各 vet 一次——「有没有被用到」是每种构建组合各自的事实,可见范围比消费方宽就一定有一种组合让它孤立;`fuzzbudget.Steps` 第一版没加 tag 被判 unused,因为消费方都在 `(wangshu_p3 || wangshu_p4) && wangshu_profile` 之后)**、**在仓里读到一段既有注释精确描述了手头症状时(§7.2,2026-08-04 新增:先加一行打印或计数器证明那条路径真的被执行,再开始在那里查——§7 主体讲的归因来自 issue / 外部 review / 自己的直觉,本节讲归因来自**仓库自己写下的注释**,那是最难怀疑的一种,因为它同时给了「症状描述」与「解释」,读到它搜索就停了;#229:peroptranslator 里 `lastReplayPC` 那段注释逐字写着 `NEWTABLE head + SETLIST → "SETLIST: not a table"`,探针一行没打出来、那条路径根本没走到,真因在共享调用层)**、**拿到一个「X 报错 / X 断言失败」形式的缺陷时(§7.3,2026-08-04 新增:**抬错的位置不是缺陷的位置**——报错点恰恰是唯一确定没有 bug 的地方,它正确地发现了数据不对,所以第一个问题是「X 读到的坏数据是谁写的」;两个手法 = **具体坏值往往能直接指认写入者**(`tag=65528` 就是 `value.TagNil`,而 nil 是**清理动作**的值、不是任何指令的自然产物,线索直接指向 GC 的栈根扫描)+ **栈迹里缺少某一层也是证据**(没有 JIT 帧就说明抬错的是解释器、破坏在更早的已升层调用里;与 §7 的 `NativeRunCount` 分诊实例是同一手法的反面——那里靠探针**确认**走了哪条路,这里靠栈迹**排除**了一整层))**看这篇。** **2026-08-05 新增两小节(#232/#233)**:**§9.0a 归一化只能救「被打印出来」的差异,救不了「被测量」的差异**——归一化的前提是那个值在输出里以自己的形式出现,而脚本可以不打印它而是**测量**它(`#`、`string.len`、比较、当表键、算术),测量结果是普通数字、与那个值再无字面联系。这一条给 §9.0 那张表补了一格:引用值地址一直被列在「两侧本来就是不同的东西」(归一为 `0xADDR`),那对地址的**值**是对的,但 `#tostring(t)` 量的是它的**宽度**——PUC `%p` 12 位给 21、望舒 `0x%08x` 8 位给 17,而 `NormalizeOutput` 拿到的字节流里只有 `21` 和 `17`(与 `string.len(0/0)` 4 对 3 完全一样的机制)。修法回到渲染处:oracle 的 prelude 包一层 `tostring` 把 PUC 的地址渲染成 8 位,望舒侧的宽度成为契约由 `TestAddressLengthIsComparable` 钉住。**归一化管值、渲染处管宽度,两件事都要做**;自查办法 = 为要归一的那个量写一个不打印它的脚本,写得出来就说明归一化有洞。**§9.1a 一个锚点的「反例集合」要用它自己的失败输入去检验,不能靠读它的注释**——`addrRe` 的 `\b` 有清楚的注释解释它为什么在(防止把脚本自己的 hex 归一掉),注释与意图都对,但 `\b` 的语义是 word 字符边界而 word 字符**包含数字和下划线**,意图里的「单词」与 `\b` 里的「word」差的正好是这一类,而这个差只能从行为看出来;判据 = 锚点左右各能出现的字符类列一张表逐格写用例(数字、下划线、字母、行首),并且**两侧都要有用例**(只写「必须归一」那一半时「把锚点整个删掉」也会通过)。**§9.1b 检查「代码有某个属性」的测试不能作用在文件文本上,注释会满足它**(2026-08-09,#236–#241,载体是 shell 自测脚本):`scripts/test-fetch-lua-tarball.sh` 要钉住 curl 调用带 `--connect-timeout` / `--max-time` / `--retry`(它们的缺席正是 #236–#241 的直接原因),第一版 grep 整个文件,于是把 `--max-time` **从 curl 调用里删掉之后照旧通过**——那个词在文件顶部的注释里还在(注释正好在解释「bounded: `--connect-timeout` and `--max-time`」);shell 没有 AST 可查使文本搜索成为最方便的实现,而**这类脚本的注释里必然提到那些标志**、注释与代码在文本上不可区分,注释写得越好测试越容易假绿;判据 = 检查代码属性的测试要作用在**代码本身**上(先截取那条调用再查),**一个注释就能满足的测试没有在测代码**,自查是把属性从代码里删掉、**保留一切注释**跑一次;这条给 [[2026-07-25-issue179-test-go-fuzz-retry-revive-round]] 教训 1(shell 判据必须配 positive marker)补上另一半——**positive marker 的作用域也要证明**,本轮那个用例是有 marker 的,假绿的原因是 marker 的搜索范围里存在一个非代码的来源;也是 [[unreproducible-crasher-triage]]「防住某个数值的回归测试要用变异实测确认」在**非数值属性**上的形式(那条改回一个数值、本条删掉一个标志),而变异实测是本轮唯一发现它的手段(那段 grep 逻辑读起来完全正常)。
- [[backend-capability-vs-profitability]] — 后端接受面按能力/收益分层:capability(`SupportsAllOpcodes`——能不能编,正确性)/ profitability(`WorthPromoting`——编了赚不赚,性能)/ 尺寸地板(`MinPromotableLener`——固定 Run 成本摊薄)/ 地板豁免(`FloorExempter`——地板的 per-proto 反向门,issue #67:proto 虽短于地板但热路径派发通道不付固定 Run 成本,P4 用 `ProtoSeg2SegEligible` 回答,裁决缓存 `ProfileData.floorExempt`;豁免裁决只问一次不重问,不豁免裁决在 `OnBackEdge` 预热里程碑(回边计数 ==1 / ==HotBackEdgeThreshold)重置重问一次,防冷 IC 时的临时裁决把 proto 永久钉死解释器)四类 per-backend 可选接口,bridge 侧 `considerPromotion` 依次咨询;**forceAll 只绕收益类不绕能力类**(差分测试覆盖面由能力层决定,不因收益判断缩水;地板豁免不适用于 forceAll——forceAll 本就绕地板),收益拒绝 → TierStuck 吸收(静态判断重复问不变);四实例——P4 amd64 CALL 密度门(`totalOps/callCount>=16`)+ P4 `MinPromotableLener` 尺寸地板(fixed Run ~111ns > 解释器 78ns,tiny proto 停 10)+ P3 wasm helper 密度地板(`total/helperBound>=7`,nbody 43.5→89.7ms 回归驱动,issue #39)+ P4 `FloorExempter` 地板豁免(spectral-norm 热 proto `A(i,j)` 9 op 被地板永久钉死解释器,arm64 auto 16.6ms 慢 force 3.7x,issue #67)+ **P3 wasm `WorthPromoting` back edge 维度(issue #92:直线小体无循环摊薄边界往返,`straightLineMinCodeLen=32` 拒,收益门家族第 5 实例,判据从 op 密度扩到结构维度)**;能力层放行 ≠ 承诺 auto 升的正面样本:**issue #91 relooper 放行顶层 diamond 让 fannkuch 主函数第一次能升 P3(纯能力层),升了也就解释器水平(force 6.20ms vs P1 5.91ms),auto 仍被 CALL 密度门拒——两层分开修、分开量**;工作流「新后端/新 op 接入先分类:编错 → capability / 编了净亏 → profitability / 短 proto 摊薄不回 → 尺寸地板 / 短 proto 但走段内派发不付固定 Run 成本 → 地板豁免」+「收益阈值校准 = 白盒 op 构成统计先行 + 实测迭代边界样本」+「共享分析器改动后每后端独立复测」+ **§4「互补 PR 合入顺序:收益门先合」(能力修复放行新形式 + 收益门防反噬分两 PR 并行时过滤门必须先合,防「能达但没兜底」回归窗口,实证 #91/#92 PR #100 先于 #101)**。**§6 记录该四类接口的测试覆盖现状**:全部只在 auto 模式生效、长期缺 auto 专属测试网,issue #67 auto-mode 覆盖轮(PR #75)补上 `Bridge.SetHotThresholds` testing-only 入口 + 四类新覆盖套件;**并记「升层判据每加一维,热度类 fixture 必须跟着满足全部维度」纪律**(issue #21 尺寸地板 ≥10 op + issue #92 back edge 两实例,`TestPromotionCount_P3_NoForce_HotEntry_Lifts` 被两次改造,连锁失败是门在工作的正信号非回归)。**给某后端接入新 opcode / 新形式时、某后端 auto 模式性能回归但 forceAll 正常时、共享可编译性判据改动后每后端复测出现分歧时、写「按 shape 拒收」类升层门时、能力修复与收益门分两 PR 并行定合入顺序时、改升层判据后热度类 fixture 连锁失败时、想知道 auto-only 接口测试覆盖现状时看这篇。**
- [[cross-backend-semantic-fix-sweep]] — 跨后端语义修复同步扫描:同一语义风险(NaN 别名/unordered/guard 失效)在 P3 wasm / P4 amd64 / P4 arm64 各有独立 inline 快路径,修 bug 的心理边界停在「当前后端」而真实边界是「所有绕过 host 语义的站点」——三实证升 guide(#67 arm64 改良未回移 amd64 / #103 arm64 修 unordered 未回查 amd64 / #107 P4 修 NaN 别名未查 P3 wasm,#37 注释写 "both arches" 但枚举本身漏了 P3)。纪律:修语义类 bug 同 PR 内枚举全部后端(以 bridge 注册 Compiler 为准,不凭记忆)+ grep 同名 op 全部 emit 站点 + 每受影响后端配 prove-the-path 载体 + 端口轮顺手修双向审计;附四个已实证风险家族(NaN 别名 sign-flip→result guard 解药 / unordered 比较 / 位相等≠语义相等 / 跨 Run 身份 guard)+「PUC 语义由 C 实现定义」节(与 PUC byte-equal 分歧先 grep `_lua515/` 源码;**含「改写输入再委托宿主标准库」不收敛判据**——同一 site 被打穿第 2 次就换手写 C 语义 renderer,2026-07-13 stringFnFormat `%#X` 第三次打穿实证)+「同族 harness 防护不对称」节(给一个 fuzz harness 加防护时立刻横向扫兄弟 harness,#127/#130 是 FuzzOracleDiff 有帽而三个老 harness 没帽欠下的债)+「运行时断言接口的扩面不对称」节(#155:给靠运行时类型断言满足的接口加方法只补 amd64 漏 arm64 镜像,断言静默返回 false 使 seg2seg 优化整体静默关闭——结果仍正确、命中数归零,只有白盒命中数断言 + 双架构 CI 能抓;检查项 = grep 按 build tag 分文件的全部实现 struct 同批补齐)。新增「**对齐 PUC 之前先分清那个行为是有定义的还是 UB**」节(2026-07-28,四格判据):有定义的 C 要**对齐**(`strtoul` 的无符号取反与溢出饱和——`tonumber("-7",8)` 得 2^64-7、20 个 `f` 配 base 16 饱和到 2^64-1);UB 且跨 arch 不一致的要**产品侧钉参照平台(x86-64)+ 差分侧跳过那个区间**(`%u`/`%x`/`%o` 的 `cUnsignedCast` 与 `string.char` 的 `cCharCast` 两个实证),而且**只跳 UB 区间**(`char(2^53)` 大但 int64 可表示、截断有定义,照旧比对);**C 未指定的两侧都不对齐**(实参求值顺序 gcc 在 x86-64 从右往左、arm64 从左往右,两个官方 build 报不同的参数编号,取可辩护行为 + harness 只跳歧义写法);**参照实现自相矛盾时按语言规范选**(`print` 用 `fputs` 截断内嵌 NUL 而 PUC 自己的 `io.write` 用带长度的 `fwrite` 不截断,5.1 手册明确字符串 8-bit clean,那个截断是 C 调用的产物,wangshu 选不截断并把偏离记进注释)。顺序上先判位置(见 [[prove-the-path-under-test]] §9.0)再判这四格,跳过还必须有执行体。同轮给「PUC 语义由 C 实现定义」节补了更细的一个刻度:**读到源码还不够,源码里的隐式转换也要展开**(`luaL_checkint` 是 `(int)luaL_checkinteger`,漏掉中间那步 `lua_Integer` 会得出与真值相反的结论)。该节的**第四个刻度**是 **错误消息本身也可能被包了一层**(2026-07-29,#206:`luaL_error` 把给它的文本直出,而 `luaL_checkstack`/`luaL_argerror`/`luaL_typerror` 会再套一层格式;`string.byte` 的上限公式与 `unpack` 一样是 `8000 - nargs`,但消息是 `stack overflow (string slice too long)` 而不是裸串,照抄裸串让 12 个 oracle 用例仍分歧;推广一步——修一个与已修函数「公式相同」的兄弟函数时,公式可以照抄,消息 / 默认值 / 参数校验各自独立核对)。该节的**第五个刻度**是 **校验在控制流里的位置也是要照抄的东西**(2026-08-02,#216:`string.gsub` 的 repl 类型检查被写在替换循环**里面**而 PUC 是在循环**之前**用 `luaL_argcheck(tr)` 校验的,于是第 4 个参数把循环次数压成 0 时那个非法参数从来没被看到、`gsub("", "", nil, .0)` 成功返回;两种写法**只在「参数非法 + 循环执行零次」这一种输入上分岔**,而 fuzzer 只要找到另一个参数能把次数压到 0 就能构造它;判据 = 搬校验时记下它在 C 源码里的控制流位置并保持一致,对每个懒校验问「有没有输入能让这段循环 / 分支执行零次」——`n = 0` / 空串 / 空表 / 空区间 / 提前 return 都是候选)。**执行体纪律**段另补:**豁免的执行体必须覆盖被豁免函数的全部别名与兼容名**(2026-08-02,#217/#219:`__wrapArgOrder` 包了 `math.fmod` 却没包它的 `LUA_COMPAT_MOD` 别名 `math.mod`——同一个 C 函数、那条性质逐字成立,而按名字写的判据看不见它,于是同一写法被开成两个 issue,`math.atan2` 也从来没被包过;判据 = 豁免的边界是「具备被豁免那条性质的全部入口」,先查 `LUA_COMPAT_*` 那一族,用一句可以拿去枚举的性质写清单而不是往清单里补名字;同一个豁免被 fuzzer 报了第二次而产品没缺陷时先怀疑覆盖面)。新增「**第六个刻度:错误从哪个函数抬出来也是要照抄的东西,而「懒抬」意味着每个读取点各要一次检查**」(2026-08-04,#228:`unfinished capture` 在 PUC 是从 `push_onecapture` 抬的,也就是一个捕获**真的被读出来**的时候,而 `add_value` 只有表替换 / 函数替换 / `%n` 展开三条路径走到那里——纯字符串或数字替换走 `add_s`、只展开 `%n`、从不读捕获,所以 `gsub("abc","(","r")` 正常返回 `"rarbrcr", 4` 而 `match`/`find` 抬错;望舒在 `collectCaptures`(生产侧)就抬使所有消费者都早抬。判据一 = 把一个错误从早抬改成懒抬时要在**每个** materialize 点各加一次检查,**参照实现的位置本身就是清单**(「谁调 `push_onecapture`」),第一版修法只在 `%n` 那条路径加检查而表替换会**无条件**读捕获 1;判据二 = 改动一个语义边界之后必须跑官方测试套,**fuzz seed 是单向的、官方套是双向的**——抓到那个漏洞的是 `pm.lua:193` 而不是 oracle seed)。新增「**共享层恢复动作的兄弟路径不对称:第 N+1 处漏做就是缺陷**」节(不对称家族第三种,前两种是横向的多后端 emit 站点 / 多架构断言实现,本条是**纵向**的同一条调用约定在多条返回 / 退出路径上各做一次;2026-08-04,#229:`doReturn` 的**终止分支**没把 top 恢复成 caller 的逻辑帧顶,而同一条纪律(PUC `lvm.c` `OP_RETURN` 的 `if (b) L->top = L->ci->top`)在 `doReturn` 自己的非终止分支 / gibbous `DoReturn` / `callHost` 三处早就在做,漏做使 caller 的活寄存器留在 top 之上、被 GC 的 `visitThreadValues` 当陈旧残留清成 nil,NEWTABLE 结果被清掉、SETLIST 报 `not a table`;漏做的原因是那个条件读起来像「已经没有 caller 了」,而 gibbous 的 `TailCall` / `DoCall` / `CallBaseline` / `ExecutePlainCallInlineFrame` 都会以 `entryDepth = ciDepth-1` 开一层**嵌套** `executeFrom`,「离开这一层的 entry 帧」不等于「离开最后一个 Lua 帧」;检查项 = grep 同一个恢复 / 清理动作的所有出现处,**数量不齐就是信号**;是 [[design-claims-vs-codebase-physics]] §4.1「同族分配器的每一步」的恢复侧对偶,共享「同族里重复出现的动作是契约」这条元纪律,两者症状都离原因很远)。**在任一后端修语义类 bug 时、端口轮顺手修某站点时、审计 inline 快路径 IEEE/tag 风险时、与 PUC byte-equal 分歧调查时(含判有定义 / UB / 未指定 / 参照实现自相矛盾四格、展开隐式转换、消息经哪个 `luaL_*` 抬出来、校验在控制流的哪个位置、以及**错误从哪个函数抬出来**)、发现参照实现在同类操作上自相矛盾时、**想把一个错误从早抬改成懒抬(或反过来)时**、**改动了参照实现的某个语义边界之后(除了 seed 与差分,必须跑 `test/luasuite`)**、给某个函数加豁免 / skip / 包装时(先查别名与兼容名)、给 fuzz/smoke harness 加防护时、给运行时断言接口加方法时、**给共享调用层加一条新的返回 / 退出路径时(grep 同族路径的恢复动作数一数齐不齐)**看这篇。** **2026-08-05 再扩两处(#234)**:**第七个刻度——一个分支的所有出口都要对一遍,不只对 seed 走到的那个**。前六个刻度讲读到源码那一行之后还要看什么,本条讲**横向**还差什么:定位到的那一行往往是一个分支的**入口**,而分支有多个出口,fuzz 报的是其中一个。PUC `add_s` 的 `%` 分支有三个出口加一个边界情况(`%%` / `%0`–`%9` / 其余非数字 / `i++` 后越过长度读),望舒只对了前两个——末尾的 `%` 被当成字面量(要求了 `i+1 < len(rb)`),而任何非数字被抬错,所以 `gsub("a","a","%z")` 在 lua5.1 是 `"z"`,**这一半在 base 上同样分歧、是既有缺陷**,只修被报的那一半会留一个已知的洞;判据 = 把那个分支的每个出口写成一行「这个出口在望舒是什么行为」逐行核对(是 [[prove-the-path-under-test]] §4.1 在参照实现**控制流**上的形式)。**「有定义 vs UB」表加第五格:有定义但可疑**——PUC 对末尾 `%` 做 `i++` 然后越过长度读 `news[i]`,读到 `lua_tolstring` 保证的 NUL、于是每次匹配吐一个 NUL 字节(`gsub("aaa","a","x%")` 是 `x\0x\0x\0`,hexdump 实测)。它**不是** UB(那个位置确实有 NUL,读它有定义)也**不是**参照实现自相矛盾,但显然是怪癖而非规则。判据不在「有没有定义」那一维,而在「**我们比什么**」那一维:oracle 逐字节比较,不照抄的话凡是以 `%` 结尾的替换串就永远不可比(那个差的字节还会流进长度、比较、表键);照抄时注释里要写清这是怪癖不是规则。自查办法 = 照抄之前写下「不照抄的后果是什么」,答「某类输入永远不可比」就照抄,答「某个边界值上不一样」就先按四格分类。
- [[unreproducible-crasher-triage]] — 不可复现 crasher 的处置模式:fuzz/CI 报 crasher 但落盘 input 本地重放通过时的止损流程——⓪ 静默死亡先做两步分类(2026-07-19 新增,排在复现矩阵之前:先分类退出方式——`exit status 2` 是 Go runtime 自身 fatal 的退出码、worker 死时几乎必然打印了栈迹,`signal: killed` 才是 OS SIGKILL;**这一步之前还有半步(2026-08-09,#236–#241):先定位这个退出码是「谁的」** —— 数字退出码看起来像全局命名空间而根本不是,shell 报的是最后一条命令的退出码、不是 errno,`exit code 28` 在 curl 那张表里是 `CURLE_OPERATION_TIMEDOUT`、在 errno 表里才是 `ENOSPC`,两张表恰好在这个数字上都有条目且都在讲资源类失败,所以错的解释读起来完全自然,自查是「说出 28 是 X 之前先说出 28 是**谁的** 28」;**同轮另加一条与退出码正交的免费证据:时间形式先于错误码** —— 立刻失败(<1s)排除超时/锁等待、失败前长时间沉默(分钟级)排除资源耗尽,「apt 成功之后沉默 2 分 15 秒才失败」这一条就足以否掉磁盘写满,而错误码是显式的、时间戳是隐式的所以注意力天然落在码上,自查是「说出这一步花了多久 + 这类失败应该花多久」并反向搜那一类的特征字符串;与下文「上限的余量」是同一个量的前瞻/回溯两个方向);再查子进程 stdio 接线——internal/fuzz coordinator 起 worker 时 `cmd.Stderr` 留 nil、栈迹被系统性接进 /dev/null;两步各花几分钟,解开 concat storm 家族 8 天 10 例僵局);① 第一步永远是版本核对(失败 run 的 headSha 相对当前 master 是否已含相关修复,`gh run view <id> --json headSha` + `git merge-base` 一条命令排除「撞的是已修复代码」这个最便宜的解释,不是先撒复现矩阵);② 真假 crasher 分界(承 2026-07-03 分诊纪律:deadline 字样 + 无落盘 = 工具链 flake;落盘但精确重放干净 + 几千万 execs 后无声死 = 进程级资源耗尽嫌疑);③ 七角度复现矩阵检查单(精确重放 / harness 镜像 hammer / 错误恢复路径 / 资源泄漏探针 / 定向 fuzz / GC 压力 / 版本核对);④ 判定为不可复现后的处置(corpus 入库常驻回归 + 诊断硬化三层:`GOMEMLIMIT` 软限制(已被观察到接不住 concat storm 族)→ 失败时 dump `free -m` / `vm.max_map_count` 进 artifact → PR #165(2026-07-19)worker 取证设施——机制 A 尸检(TestMain 在 fuzz-worker 模式把 fd 2 dup 到 fuzz-forensics/worker-<pid>-stderr.log + SetTraceback("all"))+ 机制 B 飞行记录仪(每次 exec 在长度/NUL 检查后定长 20KiB 覆盖写 seq/时间戳/target/输入,最大 gated 输入 16KiB 逐字节可恢复、热路径 0 alloc,恢复不进任何 corpus 的在飞输入),go-fuzz.sh 按 target 隔离目录 fuzz-forensics/<FuzzTarget>/ 防跨调用删证据、失败时 dump 指向原始文件、nightly artifact 上传;不硬编修复不无限挖根因;minimized 输入本身往往不是死因——重放干净更可能因为真实压力来自 4 parallel worker 叠加峰值或 minimization 前的变体);⑤ **concat storm 家族(#123-#167)已根因定性并修复(2026-07-20,PR #168)——真因是 CPU wall-clock 撞 Go fuzz 10 秒 per-input 看门狗,不是内存 OOM**(取证栈迹 `panic: deadlocked!` 定性,历轮 GOMEMLIMIT / arena cap 都因内存假说整体错误而无效;根因是 step budget 不计 CONCAT 字节工作量,修复见 doConcat 里 chargeBulkWork 按 len>>6 记账;只解决这一族已知形状 byte-heavy concat 循环,不代表所有不可复现 crasher 都解决,历史内存假说叙事保留但标注证伪;印证取证设施投资价值);⑥ **一批 crasher 先问会不会被同一个改动一起解决(2026-07-26 新增,排在分头查根因之前)**:版本核对管「一条 crasher 是不是撞的已修复代码」,这一条管「一批 crasher 之间是不是同一件事」——nightly 按 run 自动开 issue,同一个根因会在不同日期以不同的最小化输入、不同标题与 hash 各开一个,看起来是 N 件独立的事;手法 = 先在当前 HEAD 上把每一条重放一遍,通过的归成一组只做一次根因确认,再对剩下的逐条查,顺序反了就会把一个根因查 N 遍(#187-#190 四个 `FuzzOracleDiff` crasher 由 nightly 在四个不同日期开出,全部是 NaN 符号位渲染差异这同一个根因的表现,被 PR #191「在 oracle 的渲染处消除」一起解决,一行代码没改);**一批 crasher 能被同一个改动一起解决本身就是「根因修在了正确位置」的事后确认信号**,反之修完之后同族还在一条一条冒出来就是选址错误的信号([[prove-the-path-under-test]] §9.0);确认「修好了」走那篇 §9.6(断逐字节 equal 不是断绿 + 双向验证);**已修好的 crasher 仍然值得入库,判据是它触到的写法是否为新、与有没有改代码无关**(那四条覆盖 `%q` 作用于 NaN / 比较两个 format 的返回值 / 多个符号 flag 同时出现三个已有 seed 到不了的维度);⑦ **修完一条 fuzz 输入之后扫它所属的维度**:fuzz 给出的是一个家族的采样点,「这一条不再有差异」不等于「这个家族已经收口」,维度从 reproducer 结构里读(哪个 verb / 哪些 flag / 哪个运算符 / 值从哪来 / 文本被谁消费),#187-#190 轮扫了 236 + 140 种同族写法确认零差异;与 [[prove-the-path-under-test]] / [[cross-backend-semantic-fix-sweep]] 的关系:那两篇管「可复现问题怎么修对」,本篇管「不可复现问题怎么止损」,两篇在「验证一个 crasher 是否真的修好」这一点上接口(本篇负责分组,那篇 §9.6 负责给每一条定性)。⑧ **同一写法第 N 次(N >= 3)被 fuzzer 开成 issue 时,该动的是「被接受的区间有多宽」而不是再挪一个 seed**:挪 seed 只让这一条输入退出并行重放,不改变 fuzzer 下一次还能生成同样的输入;`table.insert` 的重移位跨度被开了三个 issue(每一条都正确且对称、只是耗数秒),答案是产品上限与 harness skip 被绑成同一个数而它们回答不同的问题——产品侧 2^27 是正确性边界(参照实现能完成的工作不该被拒),harness skip 降到 2^20 纯属资源(#203,数值该不该分开见 [[prove-the-path-under-test]] §4.5b,形状必须逐字对应见 [[2026-07-28-four-diff-divergence-issues]] 教训 7)。**这一条 2026-07-29 有了正向结算**:#208 是同族的下一个 crasher,fuzz run 跑在把 skip 降到 2^20 **之前**的 commit 上,核一次版本就结束、**一行代码没改**;「不需要新动作」本身就是那次改动改对了位置的事后确认(与 [[prove-the-path-under-test]] §9.6 推论同构)。⑨ **改完区间之后要问「有什么东西固定住这个改动吗」**(2026-07-29,#209 —— 同族第三个 issue、也是第二个第一档命中):回归 seed 表达的是「这个输入不崩」,表达不了「那个结构性决定还在」;两个阈值被合回一个之后现有 seed 全都照旧通过(合到 2^20 则产品开始拒绝 lua5.1 能完成的移位而昂贵区 seed 只是被 skip,合到 2^27 则昂贵那一段重新进并行重放而现有 seed 恰好都在跳过区),所以补了 `TestInsertShiftThresholdsStayDistinct` 断两个阈值**之间**那一段必须由产品执行、且必须便宜(写法见 [[prove-the-path-under-test]] §4.5c);同节另记**过期 issue 的分布也要看**(核完版本再问「为什么这类还在被开」,三种答案里「修法对但没被固定住」最容易漏,因为它长得完全像「修法还没进 nightly 跑的那个 commit」)与**判断 seed 是否重复要按它走到的分支、不按源码模式**(四个 `table.insert(t,4...` 里 `4294967298` 窄化成 +2、根本不走 skip,按模式合并会删掉唯一用例)。⑩ **三档是关于「要不要复现一遍」的分类,它不回答「有没有缺陷」**(2026-08-02,#212–#219):版本核对排除的是「撞的是已修复代码」这条最便宜的**解释**,分的是成本档而不是缺陷档——nightly 每晚跑的就是当时的 master,所以「全落在一个旧 commit 上」是**常态而不是线索**;#209 那一轮八个字面事实指向第一档、一行代码没改,而 #212–#219 八个 run 同样全落在 `5383aec` 上,实际重放之后**五个如实复现、三个是真产品缺陷**,所以无论落在哪一档都要在当前 HEAD 上把每一条 seed 实际跑一遍再分缺陷档(自查:说「这一批是过期的」之前先看有没有一条命令的输出支持这句话)。同轮另补两条:**先算 seed hash 去重**(排在按行为分组之前、更便宜——八个 issue 只有六个不同 hash,同一个输入在两个夜晚各被最小化一次就会开出两个 issue;hash 相同是「同一个输入」,行为分组是「不同输入同一个根因」)与**重 workload 挪进 `test/regression/` 时要连 harness 的限制一起抄**(#218 只抄了脚本、没抄 `SetStepBudget(1 << 20)`,1.8 秒变 87 秒——那 1.8 秒不是一亿次迭代的代价、是一百万步预算的代价;不抄等于把「因为太重而搬出 corpus」的问题原封不动搬进每次 `make` 都跑的常规套件,自查办法是量一次耗时与 harness 对齐)。⑪ **一个家族的第 N 次复发,先去读那个家族自己的结论,而不是从现象重新推**(2026-08-03,#222):⑤ 那一节末尾按名字列出的三个候选算子(string.rep / string.format / table.concat「尚未按工作量记账」)**本轮结算了**——实测在 1<<20 step budget 内、且**完全没有触发预算**的情况下,三者各自的紧循环分别跑 **21 秒 / 20 秒 / 53 秒**,单次 `prog.Run` 就超过 10 秒看门狗而 `FuzzAutoPromote` 每输入跑**四次** Run;修法与 CONCAT 一样按**产出字节数**走同一个 `ChargeBulkWork`(`internal/crescent/state.go` 把 `chargeBulkWork` 导出),21/20/53 秒变 46/90/70 毫秒且预算正确触发,八种普通写法(含 1 MiB 的 rep、10000 元素的 concat)与 lua5.1 逐字节一致。**头条教训是我怎么才想到去看它们的**:先怀疑「这个 seed 太重」(本地重放 0.77 秒、在自己 corpus 里只是**第三重**的,两个更重的早该先死)、再怀疑内存(单 seed 峰值 RSS 106 MB,CI 是 `GOMEMLIMIT=512MiB` 软限制;全 corpus 并行峰值 525 MB),两条都被自己的测量否掉,之后才回来读那一节——而那一节不但已经定性(CPU wall-clock 撞看门狗、不是内存),还把三个算子按名字列了出来,**答案在 issue 被开出来之前就写好了**;判据 = 处理明显属于已知家族的新 issue 时第一步 grep 那个家族的既有结论(尤其「范围」「后续候选」「已知未覆盖」这类小节),它与「第一步永远是版本核对」同一层级——都是先用最便宜的检查排除最便宜的解释,自查办法是动手做第一次测量前先说出这个家族上一次的定性结论。同节另把「minimized 输入往往不是死因」这个**现象**写成**判据**:这类死因下落盘的必然是最小化后的**轻**输入(单次 Run 远低于 10 秒,否则最小化过程自己就会被打死),所以「本地重放干净」是必然属性、不含任何缺陷信息,要改去量「harness 每输入几次 Run × 单次耗时」是否逼近看门狗,并把写法抽成**不最小化**的紧循环重量(21/20/53 秒就是这样量出来的)。计量单位与「同类资源只该有一个计量器」两条落在 [[prove-the-path-under-test]] §4.5d。**⑨ 版本核对的第四步 + 上限的余量(2026-08-04 新增,#224/#225)**:版本核对补一步——**把 seed 也拿到那个「修复之前」的 commit 上跑一遍**,因为「当前 HEAD 上通过」与「是哪个改动让它通过的」是两个问题,只答前者就得不到「还要不要继续查」;#224/#225 两个 run 都在 #222 合并之前的 commit 上、按前三步该判第一档,而它们在**那个 commit 上也已经被界住**,于是「已被上一轮修好」这个归因被证伪(判据 = 确认一个 issue 已被某改动修好时要确认修法与现象之间**真有因果**,在 base 上也不复现就说明归因错了;与 [[prove-the-path-under-test]] §9.6 双向验证同一纪律换了时点)。新增「上限的余量:界住不等于够快」一节:**一个资源上限只要界住还不够,它允许的量必须与外部看门狗差一个数量级**——`预算允许的量 × 单位量的本地耗时 × 目标机器的慢速倍率 = 投射 wall-clock`,三个因子只有第一个写在代码里;`1<<20` 允许约 64 MiB concat、本地每子测试 0.7–1.3 秒、CI 慢约 10 倍(数字在 `chargeBulkWork` 注释里),投射 **12–13 秒**对 **10 秒**看门狗,六个 seed **两个已超、四个余量 < 1.4 倍**,余量不够时输入从「无界」变成「只是太慢」而看门狗分不出这两者(症状一样是 `panic: deadlocked`,于是同一族反复被开成 issue 而每次单看都找不到产品缺陷)。**第一版的修法(按被开成 issue 的六个 seed 把预算减半到 `1<<19`)被一次独立审计推翻**:那六个 seed 确实都拿到 ≥1.8 倍余量,而**邻域仍在看门狗之上**——按四次 Run × 慢 10 倍投射,`1<<19` 下 `out=out.."x"` 循环 14 秒、`t[tostring(i)]=i` 循环 18 秒、`s:gsub("%a","x")` 循环 **41 秒**(看门狗的四倍、比被修的 seed 还糟)。于是同一节补了两条判据。**其一,上限要由「计费相同时最贵的写法」定,不能由「恰好被开成 issue 的那个写法」定**——`gsub` 每次调用按约两倍主串计费但实际工作远多于等额计费的 concat,**等额计费不等于等额 wall-clock**,所以定值前要把同一计费额度下最贵的几种写法都量一遍(沿计费口 `ChargeBulkWork` 枚举候选),终值 `1<<16` 让最坏那条降到 5.2 秒、余量 1.93 倍,是第一个满足数量级判据的值,corpus 全量重放 5.5 秒 → 0.85 秒,产品代码零改动。**其二,一个「防住某个数值」的回归测试必须用变异实测确认它真的会因为那个数值变化而变红**——同一次审计实测把预算调回 `1<<20`(正是那个回归)时那个测试照旧通过,因为上界写了 1 秒而它自己的注释里推导出的是 250 毫秒(`10 秒 / 10 倍 / 4 次 Run`),而且它拿「四次 Run 的投射」比「一次 Run 的测量」,再加上只用被开成 issue 的那两个 seed 连 `1<<16` 与 `1<<19` 都分辨不出;现在上界是推导出的 250 毫秒、用例表加进上面那三个约束预算的写法,能同时抓住 `1<<19` 与 `1<<20`。覆盖不损失这次是**实测**的:harness 自己的 seed corpus 在 `1<<20` / `1<<19` / `1<<16` 三个预算下 `PromotionCount` 完全相同(那些写法几次调用后就升层、从不接近任何一个上限),另外**注入一个真实 P1-vs-P4 分歧**确认检测能力未受影响、90 秒引导式 fuzz 干净;变窄的一处如实记下——`1<<20` 时有两个 seed 会把 arena 推到上限、`1<<16` 时没有,是收窄而不是空洞(`test/regression/issue144_regression_test.go` 直接覆盖 arena cap,且两个预算下 step budget 都先于 arena cap 触发)。⑫ **都真复现的那一档也有便宜检查:按失败形式分诊入口**(2026-08-04,#228/#229):前面各节都是「怎么便宜地判掉**不需要**复现的那一部分」——版本核对、seed hash 去重、按根因分组;一批 crasher 全部如实复现时这些都用完了,而这一档的便宜检查是**不同失败形式对应不同的第一个动作**。两行:**oracle 差分**(与 lua5.1 输出不同)→ 去 `_lua515/` 读参照实现的语义边界(接受面 / 抬错时机 / 默认值 / 上限各在哪一行、由谁抬出),走 [[cross-backend-semantic-fix-sweep]] 的「PUC 语义由 C 实现定义」六个刻度;**层间分歧**(P1 成功而 P3/P4 抬错)→ 先问「谁写坏了它读到的数据」、别从加速层的 emit 读起,在抬错点打栈迹看**缺哪一层**,走 [[prove-the-path-under-test]] §7.3。#228/#229 两个 run 同在 `efa9aa6`,而版本核对这次不重要——两个都真复现、而且**互不相关**(hash 不同、子系统不同,「会不会被同一个改动一起解决」这一格给出的是否),本篇只负责把入口分对。**fuzz/nightly 报 crasher 但本地重放不出时、一批 crasher 全部如实复现、要分头开工时(按失败形式选分诊入口:oracle 差分去读参照实现的语义边界 / 层间分歧先问谁写坏了数据)、一次拿到多个 crasher issue 想分头开工时(先算 hash 再分组)、拿到一个明显属于已知家族的新 crasher 时(先读那个家族的既有结论,别从现象重新推)、worker 静默死亡先分类退出方式与 stdio 接线时、判 worker 死亡是 input bug 还是进程级资源耗尽时、确认一条 fuzz 输入零差异之后想收工时、同一写法第三次被 fuzzer 开成 issue 时、修完一个反复出现的问题准备收工时、准备合并或删除 corpus seed 时、处理一个已经被修掉的 issue 时(核完版本仍要实际跑一遍,并且**在那个旧 commit 上也跑一遍**验证归因)、给 fuzz harness 或任何跑在 CI 上的东西定资源上限时(算余量,小于一个数量级等于没有余量;而且「可达的最坏」要按同一计费额度下最贵的写法量,不是按手里恰好有的那一条)、写完一个「防住某个数值」的测试之后(立刻把被防的值改回去跑一次,不变红就是没有防住任何东西)、调紧一个 harness 的资源限制之后(注入一个必须被发现的缺陷证明检测能力还在,再读一个白盒计数器证明覆盖没变)、把一个 seed 转成显式回归测试时、遇到本地无法复现的低频事件想投资诊断硬化时看这篇。** ⑬ **同源可能症状不同,所以分组的输入不能只是症状**(2026-08-05,#232/#233):上面各条分组手法在操作上的输入都是**症状**(nightly 给的标题、hash、最小化 seed 全都描述表现),而两条表现差得越远越不会被放到一起——「差得远」恰恰可能是同一个量的两种可见度。#232 是「原始输出不同」(`io.write(0)print(print)` 让输出成为 `0function: 0x...`,`addrRe` 的 `\b` 在数字与 `function` 之间不匹配、地址整段逃过归一化),#233 是「长度不同」(`t={0}print(#tostring(t))`,PUC 21 对望舒 17),看着一个是渲染问题一个是语义问题,实际是**同一个地址宽度差异**:前者是否分歧取决于真实地址恰好几位(所以间歇),后者稳定。判据 = 同一批里若有两条都涉及同一种**引擎相关的值**(地址、指针、时间、随机数、GC 数值),**先假设同源再去找那个共同量**;自查办法是把每条 seed 里的引擎相关量各列一行,相同就别急着分头查。与 [[prove-the-path-under-test]] §9.6 推论互补:那条讲修完之后「一批被同一改动一起解决 = 修在正确位置」,本条讲动手之前「症状不同不代表根因不同」。**追加触发场景**:拿到一批 crasher 准备分头查时先列引擎相关量。 ⑭ **新节「CI 自动化本身的失败信号」(2026-08-09,#236–#241)**:前面各节管「报上来的 crasher 怎么分诊」,这一节管**产生那些 issue 的机制**自己的缺陷,排在分诊之前——分诊的输入就是那批 issue。两条:**一个失败步骤让后续步骤 skip,会造出「报红但什么都没测」**(nightly 的 oracle 安装步骤挂了,三个差分 fuzz 步骤全 `skipped`,那一晚探索预算为零;红色的默认含义是「跑了并且发现了问题」,而依赖步骤失败的含义相反、两者在同一个红叉上,危险方向是**乐观**那一侧;判据 = 加依赖步骤时问「这一步挂了之后,后面被 skip 的步骤里有没有本来该产生结论的」,有就要在失败信息里写「未执行」而不只是「失败」;自查是把步骤分成「准备类」与「产生结论类」两栏;**本仓现状如实记:这半还没做完**,infra issue 的 body 说的是失败原因类别、不是「本轮探索预算为零」)+ **自动开 issue 的去重键必须与「一次事件」的粒度对齐**(标题里嵌 `matrix.variant` 让两次上游抖动开了六个 issue,而 infra 失败天然横跨所有 tier、divergence 失败才天然属于某个 tier,同一个模板服务了两类性质不同的事件;**多加一维只会让分组更细、永远不会报错**,所以没有任何自动信号、全部代价落在读 issue 的人身上;判据 = 先问「这类事件的天然单位是什么」再选键,对每一维问「这一维变化时是同一件事还是两件事」,答「同一件事」的不进键但要挪进正文,去重键与信息量是两件事)。**追加触发场景**:给 CI 加依赖步骤时、给自动开 issue / 告警加去重键时。

### memory/
- `memory/doc-gaps.md` — 已识别的文档缺口与待外部确认事项(随实现推进收敛;含已收口审计记录)。
- `memory/decisions/2026-06-11-design-review-decisions.md` — 设计评审轮 7 项裁决归档(验收 oracle 改官方 5.1.5、stdlib 反转对齐 gopher 提供面+三层禁用、ColInt64 超界报错、luac 同构软承诺等),每项含设计文档落点指针。**查「某决策为什么这样定、落在哪」先看这篇。**
- `memory/reflections/2026-06-11-design-doc-completion.md` — 设计文档集补齐(P1 全卷 + P2-P5)的过程反思:并行起草+单点收口模式、子代理中断恢复教训。
- `memory/reflections/2026-06-11-design-review-round.md` — 设计评审决策轮过程反思:主动盘点不确定决策的收益、裁决后即时 grep 同步、AskUserQuestion 自包含教训。
- `memory/reflections/2026-06-12-p1-implementation-sprint.md` — P1 实现冲刺(M8-M14 单会话收口)过程反思:difftest 后置的代价(5 个单测漏掉的语义 bug)、lcode.c 同构须到 helper 层、ci 指针刷新不变式、「简化实现+接口留口」模式。**做实现冲刺或 P2 接 difftest 前看这篇。**
- `memory/reflections/2026-06-12-p1-closeout-round.md` — P1 收尾轮(「已知简化」9 项全量完成)过程教训:对称机制复用通道(yield 借 error 哨兵)、IC 命中必须验同键(动态 key 指令)、Program 运行期可写字段须 State 私有化规则、生成器类型封闭纪律。**P2 实现 IC、给 Program 加字段、或写受控文法生成器前看这篇。**
- `memory/reflections/2026-06-12-test-hardening-round.md` — 测试加固轮(go-fuzz 目标/生成器两期/GC 压力/错误消息差分测试/nightly)过程教训:每类 fuzz 上线当天即抓到 5 个真实 bug——fuzz 目标空转(脚本在跑但无 `func Fuzz*`)是最危险的虚假安全感、「末位多值源 A 处理」同族 bug 三处分布证明同构逻辑须抽 helper、top 恢复纪律(`L->top = ci->top`)是 5.1 调用约定的一部分且症状离根因极远、GC 压力模式让正常模式难触发的时序 bug 必现(弱表链截断实例)。**搭新防线、写调用桥、或评估「防线是否真在防」时看这篇。**
- `memory/reflections/2026-06-12-completeness-gap-round.md` — 完整性补全轮(特性探测 corpus + 25 缺口修复)过程教训:**差分 fuzz 两轴正交模型**——随机生成器文法跟实现走,只测「已实现行为的正确性」,对「缺特性」结构性失明;特性探测 corpus 按官方手册逐节写,测「特性面完整性」;probe 上线在 570+ 随机脚本全绿下一次扫出 25 个缺口。另含:probe 先过 oracle 纪律(两例笔误)、goIfTrue/goIfFalse 恒跳丢值 bug(`nil and 2` 错产 false)、pattern 灾难性回溯有界失败裁量、「probe 转绿 → 进生成器文法」护栏闭环。**设计新防线选参照系、评估 diff-fuzz 覆盖含义、或 P2+ 接新执行层时看这篇。**
- `memory/reflections/2026-06-12-longevity-review-fix-round.md` — 长稳与审查核销轮(freelist 复用 + 12 轮外部审查 22+ 项全核销)过程教训:**三轴防线模型**(fuzz=行为自洽 / probe=特性面完整性 / review=规范同构+形式组合,前两轴全绿不构成第三轴可省略的理由)、**同构须到时序层**(constFold 须在 exp2RK 之后,操作顺序本身就是规范)、**注释承诺审计**(注释声称的防线不存在就是 bug 预告,四实例)、**内存复用类变更的「良性→致命」升级清单纪律**(复用前先列哪些潜伏 bug 会从良性变 UAF,根审计/尺寸单一源/debug 设施同批)、修复轮收尾的历史 bug 保护测试盘点。**做资源复用类变更、引入外部审查、或写 C→Go 移植代码前看这篇。**
- `memory/reflections/2026-06-12-official-suite-perf-round.md` — 官方测试套与性能轮(luasuite 移植扫出 20 项分歧全修 + profile 驱动六项优化 + IC DataOff 实测回退否决)过程教训:三轴升级为**四轴防线模型**(官方套=作者语义断言,独有负断言能力)、stopAt 棘轮机制、profile 先行(最大收益项 closeUpvals 不在预判清单)、benchmark 否决门 + 快 revert、归因诚实(9.0x 主因是 thread 复用消固定开销)、快路径家族审计(cbaae3f 反例)。**做性能优化、接官方测试套、或评估防线覆盖时看这篇。**
- `memory/reflections/2026-06-12-issue1-api-gap-round.md` — issue #1 公共面 API 缺口轮(per-item drop-in 子集 + Register/Module + 公共 HostFn + Value.kFunction + pin 表 GC 根)过程教训:**「设计承诺源回看」纪律**(issue 字面边界与 issue 描述的业务场景不自洽是常见根因,从设计承诺源反推真实最小可用集)、**宿主长持 GCRef 必须接 GC 根**(first-class function value 暴露的对偶面,pin 表/`visitExtraRefs` 接 R8 类常驻根,Release 可选但根必须有)、**单域提交的物理隔离手法**(一锅写完后按文件抽取再 git add 分次,比 git add -p 选 hunks 更可靠)、**错误消息稳定语义纪律**(不带里程碑编号/hash/时态承诺,公共面措辞与实现状态解耦);附带短评:**主库 go.mod 零外部依赖**(benchmark oracle 依赖按用途拆独立子模块,`d1ff096`)。**两条 promotion 候选(增量交付 guide 立项、GCRef 接根升 reference 不变式)已在 issue234 反思轮定下来完成。**
- `memory/reflections/2026-07-14-issue56-api-gap-round-3.md` — issue #5/#6 公共面 API 缺口轮 3(Table.ForEach + globals baseline 状态隔离)过程教训:对称面缺口延迟发现(写入/读出闭环检查)、GC 根两条通道(pin 表 visitExtraRefs vs baseline visitExtraValues)、线性扫描量级显式注明、一样的工作流三轮零阻塞验证 guide 已稳定。**promotion 已完成**:[[public-api-incremental-delivery]] 第 9 条对称面检查 + [[embedding-contract]] 不变式段补 baseline GC 根指针。
- `memory/reflections/2026-06-12-issue234-api-gap-round-2.md` — issue #2/#3/#4 公共面 API 缺口轮 2(Table / HideFileLoaders 沙箱 / Context 取消钩子)过程教训:一样的工作流第二轮验证「设计承诺源回看 / GCRef 接根 / 单域物理隔离 / 错误消息稳定」四条纪律可复用(尤其 kTable 复用 kFunction 一样的 pin 表零额外成本,验证机制级通用),**新增四条**:**行为变更显式标注**(`fromInner → fromInnerWithPin` 静默扩面被评审抓出,`bb1e9a8` godoc 补 v0.1.1→v0.1.2 行为变更段,是错误稳定语义纪律的对偶面)、**范围扩张顺手收口**(发现上轮裁口阻挡本轮 spec 时现场收掉比留 trick 跨 issue 强 100 倍,但 commit message 必须显式标注)、**对位测试断言先 grep oracle**(PUC 5.1 实际错误带变量名前缀,印象写法反而验证了实现已对齐官方)、**internal 接口签名避免反向依赖标准库**(`SetCancelHook(func() error)` 抽象签名留在门面层注入 context,保持 internal 零标准库非基础包依赖,P3+ JIT 一样的机制可共用)。**两条 promotion 已完成**:[[public-api-incremental-delivery]] guide 立项聚合 8 条纪律 + [[embedding-contract]] 升级公共 first-class GCRef-bearing value 接 GC 根为契约级不变式。**做公共 API 演进对外披露评审、规划「相对前版静默改了什么」时看这篇。**
- `memory/reflections/2026-06-13-drift-audit-and-fuzz-hardening-round.md` — 累积偏移审计 + fuzz wall-clock + 嵌入式 hardening 轮(`bb1e9a8..701b1a4`)过程教训:**① 累积偏移审计纪律**——同一驱动源接入到第 N 轮后必做横向审计(本期 6 issue 跨 3 轮),与官方套轮「快路径家族审计」([[official-suite-perf-round]] 做对了 1)同「同族审计」纪律家族(前者性能优化扫同族快路径,本条公共 API 演进扫同族累积偏移);**② 审计具体完成手法**——主导判断而非外包子代理(累积偏移是判断题不是搜集题)、维度 AB 划分(违设/别扭)、严重度三档分级(本轮收/P2+ 入档/建项目纪律)、留「没有违设、看似别扭其实是正解」节防审计偏负面;**③ 测试机制依赖底层框架的时间维度假设**——fuzz wall-clock(`-fuzztime`)与 `SetStepBudget` budget 不同步,seed 上限必须远低于 fuzztime 量级(`SetStepBudget(1<<20)` 下保守取 `1e5` 而非 `1e6`),与「指令计数不能替代 wall-clock 事件触发」同原理(量级层不可跨界假设);**④ 真 bug 由 flake 排查顺手抓出**——修 flake 的副作用(改 seed/阈值/采样率)等价于覆盖路径变化 = fuzz 新一轮探索,本期 1e9→1e5 紧边界 mutate 距离更短抓到 `string.rep("k", 1e14)` 真 OOM,机制级而非巧合;**⑤ 嵌入式 hardening 阈值类**——主动偏离对位 backend 的特殊关系(底线优先级「宿主进程不可崩 > 与对位字节一致」),不同于豁免(合法的不比对)、不同于设计差异(语义有意不同),阈值口径分配类 1 GiB / 循环类 1<<24 + 引入条件三条落 `12-testing-difftest.md §4.9`,与长稳轮「内存复用配套清单」同属「承诺优先级冲突的明确裁判机制」家族。附:`make all` 默认扩面(`44dc6f8` 三件套 → 七件套,工程纪律选择)。**触发场景**:做累积偏移审计 / 写 fuzz/budget 类测试 / 设计 hardening 阈值 / 一样的 issue 接入多轮后想暂停做横向审计时看这篇。
- `memory/reflections/2026-06-13-issue8-boundary-cost-round.md` — issue #8 边界成本轮(`CallInto` 零分配 + 四档真实世界 benchmark)过程教训:**① 实现浪费 vs 架构成本辨析**——性能 issue 第一分类问题,边界双拷贝(72 B/2 allocs,VM 栈→inner→public)是可消除的实现冗余而非 NaN-boxing/arena 架构成本,前提一能判否优化提案但不能掩护实现浪费(尤其首个目标宿主真实热路径 + 已承诺 drop-in 对标场景);**② 零拷贝复用栈返回值的双重验证**——GC 根可达性(runningThread 复位后 mainTh 仍常驻根,`SetGCStressMode` + string 返回值 500 轮无 UAF)+ 覆写契约(返回值底层复用栈、下次进 VM 前消费完,与旧 `Call` 独立拷贝可长持的根本差异,godoc ⚠️ + 串台/独立性测试都留),与长稳轮「内存复用配套清单」同家族;**③ benchmark 必须覆盖真实交互形式**——「VM 不是自 high 产品」,旧基准全不跨界致 9× 头条是嵌入者够不到的天花板、真实热路径劣化不可见,固化四档(纯VM micro / 真实负载纯VM / 边界 mini / 真实负载 embedded),边界档并列 `Call` vs `CallInto` vs gopher 让问题与修复同屏可验;**④ README 性能数字同机同日整节重测**(本轮 simple 9.0×→5.9× 机器差异,四档自洽)。**promotion 候选**:教训 1（实现浪费 vs 架构成本）+ 教训 3（benchmark 覆盖真实形式）候选进 [[perf-optimization-workflow]],首次样本暂留观察。**触发场景**:收到「某形式比对标对象慢」的性能 issue / 做返回内部缓冲区切片类零分配优化 / 给嵌入式 VM 设计或评估 benchmark / 改 README 性能数字时看这篇。
- `memory/reflections/2026-06-13-p2-doc-expansion-round.md` — P2 单文件设计稿扩展为子目录详细设计的过程教训(`p2-bridge.md` 703 行 → `p2-bridge/` 8 文件 7453 行,multi-doc-drafting 二轮验证):**① multi-doc-drafting 三协议二轮零阻塞**——并行起草 + 单点收口 + 回填请求节 + 唯一验收口径同 P1 19 篇起草轮验证有效,一样的工作流第二次跑零阻塞,验证机制级稳定;**② 子代理工具卡死的 Bash heredoc 兜底**(新教训)——子代理 Write/Edit 卡死但 Bash 可用,自救协议「`cat >> file <<'EOF' ... EOF` 续写」是救命招;主助理「停子代理 + 派新子代理续写」是合法救援动作,介于 multi-doc-drafting guide 「同篇连续失败 2-3 次主助理亲写」上限之前的中间档;**③ 拆子目录前必须做章节映射表**(新纪律)——单文件 → 子目录拆分时,带 §X 章节号的引用不能批量指向总览(章节不存在 = 断链),必须按章节映射(§3 IC→02-ic-feedback、§4 可编译性→03-compilability 等);**④ 子代理定下来 + 用户裁决**——子代理保守(YAGNI)+ 用户偏激进(预设接口零成本)是健康协作:本轮 AST 协议子代理选 ① 用户采纳;profile 归属子代理选 (B) 用户改 (B+C) 嵌套预设 (C) 接口避免事后改接口。**promotion 候选**:教训 2(Bash heredoc 兜底)+ 教训 3(章节映射表)候选进 [[multi-doc-drafting]] guide,首次样本暂留观察。**触发场景**:做大型文档起草扩展 / 子代理 Write 卡死 / 单文件 → 子目录拆分 / 多人协作下的不确定决策收口时看这篇。
- `memory/reflections/2026-06-14-p3-pw5-table-ic-round.md` — P3 PW5 表 IC opcode 翻译轮(翻译复杂度峰值,四提交 `bb3f16f..5c181b1`)过程教训:**① 设计稿热路径上的 `(call $helper ...)` WAT 伪码是记法不是承诺**——`02-translation.md` §3.4 把 `$ic_slot_load`/`$ic_key_match` 写成助手调用,但 PW0 实测一次跨层 imported 调用 ~143ns,快路径上一调「跳哈希」省的几 ns 立刻被边界成本吞光、IC inline 收益归零;正解是全 inline `i64.load`——table 活在 arena=wazero linear memory、`GCRefOf` 低 48 位即字节偏移、`SNAP_INDEX` 是编译期立即数 ⟹ 所有槽 offset 都是常量,复用 VS0「偏移现算寻址」同一条物理洞察;**② inline 覆盖按「逐字节可证性」分级而非「能不能做」**——只 inline 可证逐字节一致的形式(常量键:同表同代次 ⟹ 键匹配整段跳过,顺手解了字符串常量键值烧不进 Wasm 的难题;寄存器键 ArrayHit:`f64(key)==Index+1` 绕开 i32 截断陷阱),刻意路由到助手的脆弱情形(NodeHit/MonoMeta `__index`/带字符串常量值的 SETTABLE),no-compromise 的真实含义是对主导情形交付验收口径而非穷尽 inline;**③ 守卫多的路径用「两块结构($done/$slow)+ 恒定 br 深度」**胜过深嵌 if 的 br 深度算术(br 深度与检查数解耦,加减守卫不必重算);**④ 证明「跳过了哈希」的 inline-proof 测试需毒化/哨兵助手**——普通 e2e 无法区分 inline 快路径与助手回退(两者都正确),用「毒化助手返回 error」或「哨兵不写 `R(A)`」让两条路径在断言上可分辨,这是验证验收口径本身(路径被走到)而非只验正确性的唯一手段;**⑤ Edit old_string 吞掉相邻 func 声明**——在已有声明正前方插代码时锚点骑边界,留下无宿主孤立函数体。**promotion 候选(首次样本,暂留观察)**:教训 1(设计稿热路径 `$helper` 须按边界成本预算重判,P3 翻译全程复发,可并入 [[issue8-boundary-cost-round]]「实现浪费 vs 架构成本」对偶面)+ 教训 4(inline-proof 须用毒化/哨兵助手,P3 分级 inline 全程通用测试技术)。**触发场景**:接 P3 后续翻译里程碑(PW6/PW7)、照抄设计稿 WAT 伪码前、做「快路径 inline vs 退助手」分级裁量、写「证明某条快路径被走到」的测试、或在已有函数声明正前方用 Edit 插代码时看这篇。
- `memory/reflections/2026-06-14-p3-pw6-crosslayer-call-round.md` — P3 PW6 CALL/TAILCALL/RETURN 跨层互调轮(P3 最大里程碑,三提交 `6546e45..eaab36d`,gibbous→{crescent,gibbous,host} 三向分派)过程教训:**① 设计稿热路径里跨层传递的固定 token(此处 `$base`)实现前须拿本码库内存物理重核,否则藏 codebase 专属 UAF**——`04-trampoline.md` §2.2 把 `$base`(linear memory 字节偏移)画成入口一次性收到全程不变,但 gibbous 帧经 `h_call` 调更深 Lua 帧时嵌套 `growStack` 会把值栈段在 arena 重定位、改 `stackBaseW`,返回后陈旧 `$base` 指向已 Free 旧段 = UAF;解释器经 `slot()` 现算免疫,但 `$base` 是 wazero local 中途无法自刷新——这是 VS0 形式 Y 别名雷区的 gibbous 帧对偶;解法是 `h_call`/`h_tailcall` 返回重算的新 base(i64,负哨兵表错)而非 status,`local.tee`→负则 `return 1` 冒泡/否则 `local.set $base` 刷新(插桩测实证 helper(100) 深递归 stackCap 64→256 段确搬家);**② 慢路径复用解释器换三重免费红利**——CALL/TAILCALL 路由回 crescent `doCall`/`doTailCall`/`executeFrom`(gibbous→gibbous 也降级解释器跑,gibbous 仅优化层):(a) 逐字节一致按构造成立、(b) proper TCO 拿 O(1) 栈免费(`f(1e5,0)` 不溢出)、(c) 错误冒泡+pcall 清理透明(gibbous 帧 CallInfo 形式同 crescent,`ciTop` 回退零特判),是「解释器永不退役」作正确性基底而非仅 fallback 的兑现;**③ 一个 opcode 既当 BB 终结子(多 BB)又当单 BB 直线末指令时两条发射路径都要处理**——TAILCALL 后仅死代码 RETURN ⟹ `reachableBlocks==1` 走单 BB 直线路径 `emitOpcode`(当时无 TAILCALL 分支 → default「未实现」→ 静默回退),RETURN 早有的 `emitOpcode`+终结分派双处理本是现成模板,tail-shaped(`return f()`)/trivial-leaf 是独立 CFG 形状须专测;**④ tier 升层 e2e 深 baseline 自升进 TierStuck 吸收态**——深循环/递归 baseline(尾递归 n=1000)在 baseline 期触自然升层阈值 → `considerPromotion` 走 `NotCompilable`→`TierStuck` 永久卡死 → 后续手工 `promoteProto` 成 no-op(返 false→静默 skip),修复是 oracle 与 gibbous 各用独立 State + 深跑前先 promote(与「绿色≠在测你以为在测的」同源)。其它:HostState `Call`/`GetGlobal` 等撞既有 `State` 公共 API 改 `Do*`/`*Raw`(PW5 一样的复发模式)、CALL B=0/C=0 与 TAILCALL B=0 多值窗口需跨 opcode 维护 top 故保守回退、`h_call` 返 i64 单值兼载新 base 与错误负哨兵。**promotion 候选**:教训 1 **已是该家族第二实例**(PW5 边界成本维度 + 本轮段重定位内存物理维度,使其从首次样本升为真实模式),**建议从 memory 提升**——可立 guide「设计稿主张须对本码库 physics 重新验证」聚合 [[issue8-boundary-cost-round]]+[[p3-pw5-table-ic-round]] 教训 1+本轮;教训 4(tier 升层 e2e TierStuck 吸收态陷阱)首次样本暂留观察。**触发场景**:接 P3 后续里程碑(PW7-PW9)、照抄设计稿把热路径值画成跨层固定 token 前、决定「加速层重写 vs 路由回解释器」、接兼具 BB 终结子与单 BB 末指令双重身份的 opcode、写含深循环/递归 baseline 的 tier 升层 e2e、或给 HostState 加镜像 opcode 名方法时看这篇。
- `memory/reflections/2026-06-14-p3-pw7-pw4b-closure-tforloop-round.md` — P3 PW7(CLOSURE/CLOSE/VARARG)+ PW4b(TFORLOOP 泛型 for)翻译轮(本轮后全 38 opcode 除 VARARG 外全可翻译,两提交 `6f2fd0e`/`aec94c6`)过程教训:**① 设计稿标注的「难点」可能已被前序里程碑解掉,动手前先核实它是否仍是难点**——设计稿 §3.7 给 PW7 估 0.5-1 人月并把「开放/关闭 upvalue 在 linear memory 形式下的存储协议」标为核心难点,但该难点早被 VS0-c(值栈 arena 迁移)解掉(open upvalue 经 `owner.slot(idx)` 形式 Y 自动重定位 + gibbous 侧 upvalue 访问从 PW2 起走 helper)⟹ CLOSURE/CLOSE 仅路由 helper 复用 `makeClosure`/`closeUpvals`、无新协议,真正非平凡点反是设计稿当脚注的「跳过 CLOSURE 后随伪指令」——难点发生**时间维度漂移**(基础变更追溯性溶解后续里程碑的难点,设计稿不会自动更新该判断);**② `emitOpcode` 的 `(skip,err)` 重构让发射循环跳过 opcode 后随的伪指令数据字**——CLOSURE A Bx 后紧跟 `SubNUps[Bx]` 条 MOVE/GETUPVAL 伪指令(是数据非可执行 opcode),两条发射循环(单 BB 直线 + `emitBlockBody`)按 pc 迭代会误译成寄存器拷贝,解法 `emitOpcode` 返回 `(skip,err)` + CLOSURE 返回 `Proto.SubNUps[Bx]`(注释「symbexec 精确跳过用」天生为此设计)+ 循环 `pc += 1 + skip`,且**先做纯机械签名重构验证 PW1-PW6 零回归再加 CLOSURE 分支**(机械改 vs 新语义混一 diff 则回归不可区分),与 PW5 SETLIST C=0「下一字是数据」同物理现象的两种应对(那里回退/这里跳过);**③ TFORLOOP base 刷新是 [[design-claims-vs-codebase-physics]] §2 第 4 实例且这次在 planning 阶段就预判到**——TFORLOOP 迭代器调用走 `callLuaFromHost` 可 growStack 重定位值栈段 ⟹ 返回后 `$base` 陈旧 UAF(PW6 `h_call` 同雷区),因 guide §2 已存在、规划阶段即预判 → `h_tforloop` 返回从设计稿 3 值状态(0/1/3)改为 i64 同载刷新 base 与 3 状态(newbase≥0 续/-1 ERR/-2 退),教训成本在规划时一次付清而非再经 GC 压力失败重新发现,**是 guide 提升(PW6 轮)的正面确认而非新 promotion**;**④ TFORLOOP/CLOSURE 复用解释器跨层机制是 PW6「慢路径复用解释器」红利的延续**——TForLoop 复用 `callLuaFromHost`(被升层迭代器自动经 PW6 `doCall`→enterGibbous 再入)/Closure 复用 `makeClosure`/Close 复用 `closeUpvals`,PW6 跨层机械使新控制流 opcode 几乎零增量(复用基底会复利、每里程碑降低下一个成本)。其它:VARARG 不需发射代码(不在白名单→`SupportsAllOpcodes` 假→回退,设计稿「Wasm unreachable 防御翻译」多余因白名单已在 emit 前挡住,`TestPW7_VarargRejected` 锁死)、TFORLOOP e2e 用自定义 Lua 迭代器(crescent 无 stdlib 否则 import cycle)走相同 `R(A)(R(A+1),R(A+2))` 路径、一样的 TierStuck-on-deep-baseline 陷阱(独立 State + 深跑前先 promote)、i64Const 负哨兵用 `^uint64(0)`/`^uint64(0)-1`(`uint64(int64(-1))` 是 Go 常量溢出编译错)、两个机械 Edit 失手(heredoc EOF 痕迹 / 游离 `}`)被 build 抓到(PW5「Edit 吞声明」同族)。**promotion 候选**:教训 1(设计稿「难点」可能已被前序里程碑解掉,动手前重核)首次样本暂留观察,PW8 线程级 tier 规则难点可能也已被 `th==mainTh` 守卫部分解掉,若再现可并入 [[design-claims-vs-codebase-physics]] 作「时间维度」补充(physics 不只空间不变式,也含「前序里程碑已改变的事实」);教训 3 是 guide 正面确认非新 promotion。**触发场景**:接 P3 PW8/PW9、开始任何标注「难点」的里程碑前(先核实难点是否仍在)、给 opcode 加后随数据字的翻译、热路径值穿过可能 growStack 的助手时、写 tier 升层 e2e(深 baseline TierStuck 陷阱)时看这篇。
- `memory/reflections/2026-06-15-p3-pw9-acceptance-perf-round.md` — P3 PW9 端到端总验收轮(正确性轴交付 + 性能轴空测翻案,五提交 `bb39b06`/`e94a80e`/`f556c19`/`419e278`/`ed0375a`)过程教训:**① 空测陷阱(vacuous-benchmark):tier-vs-tier 基准不证明被测层真被走到就是 0 信息**——loop 核基准测得 gibbous≈crescent(≈1.0x),据此推出「memory-resident 流量相同、dispatch 消除不足 2x、须上 locals 缓存」并准备把性能轴当**根本限制**拆里程碑(`bb39b06` commit message 已写入),但那 `for` 写在**顶层 vararg chunk**(F1 结构性排除永不升层)⟹ 实际测的是 crescent==crescent,≈1.0x 是构造性必然非发现;修正后真实数 **2.58x**(crescent 5.61ms vs gibbous 2.17ms,V14 ≥2x 立刻达标)。差一点把**本就过线**的性能轴叙述成「memory-resident 根本限制」并立**错的后续里程碑**(locals 缓存——与真瓶颈正交)。原则:**加速路径 `≈1.0x` 是红旗不是发现**(默认「没走加速路径」),任何 tier A vs B 基准读数前必须先证 tier B 真被执行(把 kernel 包进可升层载体非顶层 chunk);**② 非空断言测试是解药,且这是 PW5/PW6 之后同家族第 3 个实例——「绿色 ≠ 在测你以为在测的」已跨过提升阈值**——修复不是「再跑一遍」而是经已证非空路径重测(kernel 包进非 vararg 内层函数反复调用使过热度)+ 白盒 `TestPW9_ForceAllPromoteReal` 经真实公共 force-all 路径断言 proto **确实到达 `TierGibbous`** + `TestPW9_ForceAllRespectsStructuralGates` 断言 vararg 即便 force-all 也不升层;三实例(PW5 inline-proof 毒化助手 / PW6 TierStuck no-op 静默 skip / PW9 vararg 不升层)共同形式是**静默替身路径**产出「看起来对」的结果把真要测的路径挤掉;**③ 编译期 F7 是「无后端注入」条件下算出的历史占位非能力陈述,检查裁决须在条件匹配处重判**——编译期 `analyzeCompilability` 用临时 throwaway Bridge 跑 F7 时 `b.p3==nil` ⟹ F7 恒触发烧成 `CompNotCompilable+ReasonBackendUnsupp`,force-all 仅绕热度则一个 proto 都升不动;`recheckCompilabilityForce` 在 forceAll 下对**真实注入的后端**重判 F7、**保留** F1-F6 结构性排除(已 AST-free 烧进 `proto.CompReasons`,`ReasonsBitmap &^ ReasonBackendUnsupp`),即「绕编译期 F7 占位、不绕真实 F1-F6 检查」;**④ 真瓶颈是跨层调用税非 memory-resident 寄存器,原计划 locals 缓存正交治错了成本**——修正后真实画面:loop 2.58x / mixed 1.60x 过线,但 table 0.68x / call 0.14x(慢 7 倍)调用密集核退化,根因 gibbous→gibbous 经 `h_call` 双跨层(Wasm→Go→Wasm,PW0 实测 ~143ns/次,30 万次小叶调用 ≈ 43ms 纯边界税),是 `must/design-premises` 前提一/前提二边界成本物理的运行期实证;geomean 一个总数会抹平双峰(赢的核/亏的核),须按 kernel 形式分桶看;**⑤ 里程碑级架构改动配 spike 检查,绝不盲目重写核心假设未验证的方案**——跨层调用税真解是 gibbous→gibbous 直接分派,被两条码库 physics 挡住(每 Proto 独立 wazero module ⟹ 跨 module 必穿 host;Lua 帧活在 Go `th.cis` ⟹ 帧设置需 host 除非 CallInfo 迁 linear memory=VS0-e),**生死未知数**是 wazero 能否往已实例化 module 追加函数(当前证据:不能);决策**不盲目开干**——用 spike 检查(对齐 PW0 precedent)先验 S-A intra-module `call_indirect` 成本(<30ns)+ S-B 增量 module 重编生命周期,绿则重写、红则退守「拒升小叶函数启发式」(预写红灯退路)。**promotion 候选**:教训 2(非空断言:绿色 ≠ 在测你以为在测的)**第 3 个独立实例已跨过提升阈值,强烈建议提升**为测试类 guide(workname:`prove-the-path-under-test`),聚合三类静默替身路径反模式 + 解药(毒化哨兵助手 / 正向 tier 断言 / 非空载体+路径断言),或并入 [[test-hardening-round]]「fuzz 目标空转」框架——它与 [[design-claims-vs-codebase-physics]] 构成「实现前重验主张 / 实现后证明在测路径」对偶双防线;教训 1(空测陷阱)随教训 2 一并进 guide(差分套 + 基准都适用「先证路径,再读数」);教训 3(条件维度缓存裁决是快照非真相)首次样本暂留观察,若再现可作 design-claims guide「条件维度」补充;教训 5(里程碑级架构改动配 spike 检查)PW0 已是 precedent、本轮第 2 次自觉援用,首次以「教训」形式记录暂留观察,PW10 spike 真执行后可升 spike-gate 范式 guide;教训 4(瓶颈实测推翻模型)暂留 memory。**触发场景**:写任何 tier-vs-tier(加速层 vs 基线)性能基准或差分套时、看到加速路径 ≈1.0x 的读数时(默认「没走加速路径」)、评估一个检查/缓存/分析结果的存储裁决是否仍有效时(尤其它在与运行期不同的条件下算出)、一个性能数与你的成本模型矛盾时(先重导模型再定修复)、为架构改动在「全面重写 vs 退守启发式」间抉择时、或开始任何里程碑规模、核心可行性假设未验证的改动时(先 spike 检查)看这篇。
- `memory/reflections/2026-06-15-p3-pw10-r1-r2-callinfo-migration-round.md` — P3 PW10 消除 gibbous→gibbous 跨层调用税轮(**PW10 在飞行中**:Phase 0 spike + R1 共享 funcref 表 + R2 CallInfo 迁入 linear memory 已交付;R3 `call_indirect` 直调收益 + R4 + R5 未做,13 提交 `457559b..9d70247`)过程教训:**① 里程碑级架构改动配 spike 检查(镜像 PW0/PW9);而一个解 architecture FORK 的第二探针,能挖出比第一个被证可行方案远更简单的设计**——真解被两条码库 physics 挡住(每 Proto 独立 wazero module 跨 module 必穿 host / Lua 帧活在 Go `th.cis`),生死未知数是 wazero 能否增量提升;建 `spike/p3indirect/`(独立 module 镜像 PW0 `spike/p3boundary`):**S-A** intra-module `call_indirect` ≈2.5ns(比 ~35ns 裸 host 跨界便宜 14x)、**S-B** rebuild-all 生命周期可行(≈1.2ms@256fn),到此 spike 已绿可承诺 **Arch-1「rebuild-all」**——但它复杂(代际实例/跨代守卫/O(N²) 重编);**关键决策是没止步于「难路可行」**,补 **S-C** 去解先前一笔带过的 FORK「各 Proto 模块能否共享同一张 imported funcref 表」⟹ S-C 通过(跨 module 经共享表 `call_indirect` ≈2.5ns 零惩罚),挖出 **Arch-2「共享表」** 戏剧性地简于 Arch-1(保持「一模块一 Proto」几乎不动、无 rebuild/生命周期/O(N²));决策报告归档 `spike/p3indirect/DECISION.md`;**spike 绿灯只意味「这条具体路可行」不意味「这是该走的路」**,若存在未审视的 FORK,动手建难路前先用一个增量探针打那条更简单的分支——与 [[perf-optimization-workflow]] §3「benchmark 否决门」同构但发生在架构选型层;**② 高风险物理数据结构迁移(VS0-e CallInfo 迁入 linear memory)的「收口→并行只写影子→翻转→退役」增量剧本,并把唯一 UAF-风险翻转单独隔离一提交**——CallInfo 从 Go `[]callInfo` 搬进每线程 arena 段(4 word/帧,触及 GC 根扫描/growStack/协程栈/~108 调用点),拆成 R2a(COLD 字段收口走 accessor,行为不变)→ R2b-1(arena 段只写影子 + `wangshu_trace` 往返自检,段从不被读故行为可证不变)→ R2b-2(`growCISeg` 动态增长盖满深度,仍只写)→ R2b-3(GC 根扫描翻转为从段 READ `cl`——**唯一最高 UAF-风险步,单独隔离一提交**,夹在两个安全提交之间使故障必然归因到这一步)→ R2b-4(退役 Go slice;`currentCI` 返回稳定 `th.cur` scratch 镜像、非当前帧经 `ciAt(depth)` 值快照、`truncateCI` 解栈、varargs 留 Go 影子 `th.ciVarargs`);**③ `th.cur` 稳定地址洞见用结构性修复消解 [[design-claims-vs-codebase-physics]] §2 反复出现的 held-pointer 重定位雷区(而非靠纪律躲)**——旧 `currentCI` 返回 `&th.cis[len-1]`(指进 Go slice,append 搬家 = §2 雷区对偶:那里 arena grow 搬走 `$base`,这里 slice append 搬走 `&th.cis[i]`);`th.cur` 是地址恒定的固定 struct 字段(永不重定位),非当前帧用 form-Y 值快照(每次从 depth 重算不缓存指针),雷区被**构造性消除**而非小心绕过——是 §2 从「翻转后回传刷新值」(PW6 `$base`/PW7 TFORLOOP 纪律档)到「稳定地址 + 值快照使其无从失效」(结构档)的升级应对;**④ `wangshu_trace` 自检双职**——`verifyCISeg` 往返读回断言段与 Go 权威一致,既是真实打包正确性安全网(push 现场立即 panic + trace-tag 贯穿整套 difftest 层间套比单元往返强),又给 lint 误判「未使用」的读函数正名(绕 unused 误报比 suppress 干净);⚠️ trace-gated 自检会在重构中被无意删掉(R2b-1/R2b-4 各踩一次),重写自检点须清单化确认接线仍在;**⑤ 空测/不公平基准陷阱第 4 次复发——这次是 apples-to-oranges 工作负载错配,非「未走加速路径」**——给 4 个 benchmark 加 gibbous 列时跳出 baseline loop「慢 20 倍」(gibbous 把 kernel 包内层函数调 50 次对比既有跑裸顶层循环一次的 bench,wrapped×50 vs bare,修复加匹配的 `_WangshuKernel` 列)、arith「慢 5 倍」(call-entry 开销主导便宜算术,隔离探针证同样算术放循环里 gibbous 快 1.88x);修正后每数自洽:gibbous 计算/循环密集赢(loop 2.45x / arith-in-loop 1.88x)、调用密集输(`h_call` 双跨层税,R3 将修)。其它:`wangshu_profile` 给 crescent 加 ~28% 故 bench-all 让 crescent/gopher 跑默认 tag、gibbous 跑 p3+profile 分开(统一 tag 扭曲基线)、机械 ~108 站点转换委托 worker 配规则但协程 resume/traceback 安全关键站点亲验、新增 `make bench-gibbous`/`make bench-all`。**promotion 候选**:**教训 5**(基准公平性:被走到的路径 + 匹配的工作负载 + 惊人数字触发隔离探针)**该家族第 4 个独立实例**(PW5 inline-proof / PW6 TierStuck no-op / PW9 vararg 空测 / 本轮工作负载错配,新形式 apples-to-oranges)**强烈强化** PW9 留下的 `prove-the-path-under-test` 候选,跨过应成 first-class guide 或并入 [[perf-optimization-workflow]] 的阈值;**教训 1**(spike 检查 + 探未审视的 architecture FORK 而非只探第一可行路)PW0/PW9 已立范式,本轮第 2 次援用 + 首次贡献「spike 绿灯≠最简路径」延伸维度,接近提升为 spike-gate/architecture-fork 工作流 guide;**教训 2**(物理数据结构迁移「收口→只写影子→翻转→退役」+ 隔离单一灾难步)**VS0-a/b/c/d/e 第 5 次应用**,建议提升为迁移类 guide;**教训 3**(`th.cur` 稳定地址)建议作 [[design-claims-vs-codebase-physics]] §2 升级应对补进 guide(纪律档/结构档并列);**教训 4**(build-tag 门控往返自检双职 + 须随重构保持接线)首次样本暂留观察,可并入教训 2 迁移 guide。**触发场景**:开始任何里程碑规模、核心可行性假设未验证的架构改动时(先 spike 检查 **且** 探更简单的未审视 architecture FORK)、做物理数据结构迁移时(套「收口→并行只写影子→翻转→退役」,把唯一 UAF/损坏-风险翻转单独隔离)、引入并行镜像结构时(配 build-tag 往返自检并确保随重构保持接线)、一个重定位/held-pointer 雷区反复出现时(找稳定地址 + 值快照的结构性修复而非再加一轮「记得刷新」纪律)、加一个 tier-vs-tier 或 A-vs-B 基准列时(验被走到的路径 + 匹配的工作负载形状,与架构矛盾的数字触发隔离探针)、在会改变基线行为的 tag 下做基准时(拆开跑别让基线背它不付的税)看这篇。
- `memory/reflections/2026-06-15-p3-pw10-zerocross-stage3-round.md` — P3 PW10 零跨界 ③ RETURN 拆帧子轮(承 R3+R3.5,**PW10 仍在飞行中**:基建-a closure slot 缓存 + ③a savedTop 基建 + ③b emitReturn 守卫快路径已交付,④ CALL 建帧快路径仍待。三提交 `8aa4c02..1bff7d2`)过程教训:**① 已识别未触发风险须配套触发用例,否则规划文件留口=纸面**——计划文件标了「caller==gibbous + ciDepth≥2 触发解嵌套陷阱」的风险列表,但**未配最小触发用例**;Stage 4 difftest 全绿+性能没动的「成功」其实只证明守卫**未被触发**(快路径恒不命中),真要测的解嵌套陷阱处于结构盲区;教训不是「忘了想该风险」而是「想到了但只留在脑里/文档里没固化成测试」,纪律:计划文件每个风险标记末尾加「触发该风险的最小测试用例 = ___」,空白立刻补别留到下轮。**② difftest 快路径命中盲区(第 2 实例,跨过提升阈值)**——V1-V13 force-all 语料对 ③b 守卫**命中形式**结构盲(A/B 实证:把 gibCI resync 删掉时 difftest 仍全绿、R3 三件套也全挂),根因 difftest 只保证「输出 byte-equal」**不保证「快路径走到」**;承 PW9 [[2026-06-15-p3-pw9-acceptance-perf-round]] 教训 2(空测/未走加速路径)+ PW10 R3 [[2026-06-15-p3-pw10-r3-call-indirect-round]] 教训 4(错误路径盲区)+ PW10 R1-R2 [[2026-06-15-p3-pw10-r1-r2-callinfo-migration-round]] 教训 5(工作负载错配)+ PW5/PW6 同家族,**`prove-the-path-under-test` 已是第 5 个独立实例,跨过 first-class guide 阈值**;解药 = 「指针探针 + 命中计数器」(本轮 `TestPW9_ZeroCross_ReturnFastHit` 经 doReturnHits 计数验「helper→f 不增 = 快路径命中」)。**③ 跨机器/跨参数 perf 基线漂移**——Stage 4 一开始误判 ③ 引起 table/mixed 回归,经同 commit 同硬件同参数 git worktree 复测(3 分钟)证差异源自硬件/bench 参数而非代码;现行基线本机 Xeon 6982P 2s×3 count:loop 2.95x / table 0.88x / call 0.52x / mixed 0.99x;**纪律**:任何 perf 数字判定回归/收益前,必须同 commit 同硬件同参数复测,绝不拿 memory/reflection/implementation-progress 里历史数字直接下结论;memory/reflection 写 perf 数字时必须标硬件/参数/日期(本轮发现 index.md / startup.md / implementation-progress.md 三处均缺标)。**触发场景**:写计划文件标风险列表时(每条配触发用例)、加 Wasm 内快路径/IC inline 类「绿色不等于走到」机制时(配命中探针)、看到 perf 数字与预期/历史差异时(先重测同 commit 别先归因代码)、跨机器 perf 复现时(标硬件/参数/日期)看这篇。
- `memory/reflections/2026-06-16-vs0e-varargs-stack-underflow-round.md` — VS0-e 收口轮(varargs 进栈下区,M14 留口完成,P3 PW10 完整收尾):四子步 4 提交 `c22798b`/`4e50687`/`966318c`/`ed95020`,把 task #6 描述三项里两项已隐性交付 + 真剩的 varargs 进栈下区做完(对齐官方 Lua 5.1 真布局 `[func | vararg | R(0)..]`)。**四教训 + promotion 决策**:① 调研先于实现——延后 ≥6 个月的 task 描述常失实,本轮发现 task #6 三项里两项已被 VS0-c + PW10 R2 隐性收掉,实施前重核把范围从「2-3 小时高 UAF」缩到「2 小时低 UAF」。与 [[2026-06-14-p3-pw7-pw4b-closure-tforloop-round]] 教训 1「难点过期」同家族第 2 个独立实例,**暂留观察**,候选 落点 [[design-claims-vs-codebase-physics]] §5「时间维度」新章;② 设计文档比 task 描述更权威——P1 `05-interpreter-loop.md §8.5` 早就明确栈下区真布局,VS0-e 是完成原设计而非新设计。首次样本暂留观察;③ ciWords 4→5 段布局扩张 = PW10 ③ 收口前曾撞过的 fork(`[[project_pw10_zerocross_milestone]] §"fork 已解"`),但 PW10 预留的 wasm 端单点 `helpers_index.go ciFrameBytes` 让本次扩张改动面只有 15 行。首次样本暂留观察;④ 既有官方 5.1 luasuite 覆盖度比手写语料强 N 倍——luasuite/closure.lua 已含 NeedsArg + vararg + 协程多值 yield/resume 最复杂组合,无需再加冗余语料。**与 `prove-the-path-under-test` 候选同家族第 7 个独立实例,新维度「覆盖度正向先验证」对偶面(以往六实例都是「绿色不等于走到」反向侧)。已 promote 为 first-class guide [[prove-the-path-under-test]],本轮 ④ 落 §4「正向侧解药」节**。**触发场景**:接延后 ≥6 个月的 task 前(先重核每一项现状,描述可能失实)、task 描述与设计文档冲突时(以设计文档 + 已完成实现为准)、需要扩段布局/word 时(grep 既有 ciFrameBytes 类常量定位单点)、加 e2e 语料前(先 grep 既有官方 5.1/PUC 测试套是否已覆盖)。
- `memory/reflections/2026-06-16-p3-pw10-architectural-ceiling-round.md` — P3 PW10「按原定计划完成 P3」诚实收口轮(R3.5 之后:基建-a closure slot / 基建-b proto cache / ③a savedTop / ③b emitReturn 守卫快路径 / ④-i emitCall 守卫骨架 + fastCallHits mirror 字 / callOnStack 顶层升层 + TopLevelUplift 探针 / emit 原语 i64.add/i64.or 保留)。**三教训**:**① profile 才是合同(不是立项数字)**——立项时的数字目标(0.X x → ≥Y x)是「方向陈述」,完成中 profile 揭示「立项假设的瓶颈 ≠ 实测主导项」时须先重评目标可达性再决定继续/止损/换路径,绝不为追原数字硬上高 UAF 实现(本轮:profile `/tmp/call.prof` 证 bench kernel body 含 UnknownCall F2-b 不可升 ⟹ call 0.52x 是结构性架构边界,④-ii fast body 即使完成也只到 0.57x 仍 <1x,实现复杂度 ~200 行 wasm 字节级 codegen,**决策止损**留 followup);**② stop hook 诚实解读**——/goal stop hook 强制不结束时若 profile 证明数字不可达,收口应是「已完成子里程碑 + 文档化不可达边界」,绝不硬上 UAF 代码追数字;**③ wasm codegen 复杂度峰值需前置编排**——④-ii fast body 需在 wasm 字节级直接组帧、绑 ciTransferRef、IC mirror 写回、错误路径展开,复杂度峰值远超 ③b 守卫快路径,**前置编排成本** vs **ROI** 须在立项时就算清,不要走到「机制 80% 已铺好但最后 20% 全是 wasm 字节级 UAF 雷区」再止损。**promotion**:教训 1「profile 才是合同」已完成 [[perf-optimization-workflow]] §7「立项数字目标 vs profile 实测瓶颈」(首次以 first-class guide 立节,作 perf 判定纪律四件套之一:profile 先行 §1 + benchmark 否决门 §3 + 跨机器基线对照 §5 + 完成中止损 §7);教训 2/3 暂留反思(首次样本)。**触发场景**:perf 里程碑完成中 profile 揭示原计划某子步在事实上不可达时、/goal stop hook 强制不结束但 profile 证明数字不可达时、wasm codegen 复杂度峰值的里程碑收口决策(止损 vs 硬上)时看这篇。
- `memory/reflections/2026-06-15-p3-pw10-r3-call-indirect-round.md` — P3 PW10 R3(`call_indirect` 直调消 `code.Run` 重入)+ R3.5(host helper 改 wazero 零分配 API 消反射装箱)轮(**承 R1-R2 的续轮**,6 提交 `6f2712e..1bf9d53`:R3a 中转字基建 → R3b-1 `h_callerr` + DoReturn 写中转字 → R3c emitCall 改 `call_indirect` → R3c-fix 出错点就地标注 → R3.5 helper 零分配)过程教训:**头条——性能 spike 必须量 `allocs/op` 不只量 `ns/op`,只量时间维度会把成本归因到错误机制、让整个里程碑追错东西**:PW10 Phase 0 的 S-A 探针量了 dispatch ns(intra-module `call_indirect` 2.5ns vs 裸 host 35ns)据此把跨层税判为「~143ns 分派延迟」,R3 全部使命就是用 `call_indirect` 替掉 `h_call` 里的 `code.Run` 重入消这个延迟——R3 做完且逐字节一致后基准**纹丝没动**(call 核仍 ~6x 慢),memprofile 立刻揭穿:调用核 **1.4M allocs/op**(解释器 2),全来自 wazero `WithFunc` 的反射装箱(每跨界把每个实参 boxing 成 `reflect.Value`),边界税主导项是**分配不是延迟**,spike 漏量了分配维度;R3.5(与 R3 正交)把 25 个 helper 从 `WithFunc`(反射)改 `WithGoFunction`(零反射 stack-based)后 allocs 对齐解释器、**四核全翻面**(loop 2.65x / table 1.26x 原 0.64x / mixed 1.14x 原 0.68x / call 0.49x 原 0.17x);判据:做 spike/micro-benchmark `-benchmem` 是默认非可选,跨 FFI/host/reflect 边界默认每调用堆分配、用 memprofile 钉死。**教训 2**:perf 里程碑「机制完成 ≠ 收益交付」——加速里程碑做完(且逐字节一致)后必须用 `-benchmem` 对目标核复测确认收益兑现,数字没动是红旗 → 回 profile 重找瓶颈(本轮真瓶颈在反射装箱、R3 没碰)。**教训 3**:出错点锚定错误行号——延迟标注(攒错误到顶层用『当前帧』补行号)+ 错误路径弹帧的潜伏失配只在「弹帧的帧 ≠ 标注的帧」时浮现(R3 逐层弹帧打破解释器「错误路径不弹帧」满足的隐含不变式),`raiseGibbous` 在失败点就地标注,净结果 gibbous→gibbous 错误**逐字节等于纯解释器、严格优于旧 PW6c baseline**(是正确性改进)。**教训 4**:全成功语料的 difftest 对错误路径**结构性失明**——V1-V13 全绿没抓到教训 3 的回归,因 `runWangshuTiered` 在出错时 `t.Fatalf`(语料全是成功用例,无一例黄金输出本身是错误),强化 `prove-the-path-under-test` 家族「错误路径也要被差分测试语料覆盖」。残留 call 核 0.49x 是真实 `h_call`+`h_return` 双跨界(R4/Option B 靶子);用户在「先修零分配 helper」与「先做 R4」间显式选前者。**promotion 候选**:教训 1(spike 量时间+分配双维度)**本轮头条、强信号**,建议新增一节进 [[perf-optimization-workflow]] 或作 §1「量对维度」补充;教训 2(机制完成≠收益交付)与教训 1 配对作「里程碑收尾纪律」、或并入 [[p3-pw9-acceptance-perf-round]] 教训 4 作收尾侧对偶;教训 4 强化 PW9/R1-R2 的 `prove-the-path-under-test` 候选(贡献「错误路径须被差分测试覆盖」新维度);教训 3 可作 [[design-claims-vs-codebase-physics]] §2「延迟消费上下文相关状态 + 中途上下文被改 = 潜伏失配」邻接补充(首次样本暂留)。**触发场景**:做性能 spike/micro-benchmark 时(`-benchmem` 默认、跨 FFI/host/reflect/wazero `WithFunc` 边界默认每调用堆分配)、spike 给出「机制 A 比 B 便宜 N 倍」延迟结论并据此立里程碑时(先问 A/B 各自 allocs/op)、perf 里程碑「机制就位+测试绿+逐字节一致」后(用 `-benchmem` 对目标核复测、数字没动回 profile)、一个机制延迟消费上下文相关状态而另一机制会在延迟期改变该上下文时(就地捕获别延迟)、看一个差分/差分测试套时(问「有没有一个用例黄金输出本身是错误消息?」——若 harness 对错误 `Fatalf`、语料全成功则错误路径是结构盲区)看这篇。
- `memory/reflections/2026-06-16-issue13-parity-friendly-fastpath-round.md` — issue #13 公共面 API 缺口轮 4(typed-array `NewFloat/Int64/Bool/StringArrayTable` 四件 + `GlobalsSlot` 预解析句柄,4 提交 `452ddb6`/`0834647`/`219475a`/`0d93486`)过程教训:**[[public-api-incremental-delivery]] 9 条纪律第 4 次应用零阻塞零新增**(一样的 guide 经 issue #1 / #2-4 / #5-6 / #13 四轮验证可复用)。**两个新维度入档**:**① 跨 consumer perf 调研的「入口纪律」**——给消费侧某 issue 起草自家侧建议前,先读 consumer 自家 llmdoc 反思/decisions/doc-gaps(本轮靠先读 pineapple `wangshu-borrow-optimization-survey.md` 避开了重提 arena 列轨 -46% 这条 pineapple 已 deferred 的方案);**② parity-friendly 作 wangshu 公共 API 设计分类轴**——跨引擎 `lua_script` 字节对等是 pineapple 一类 consumer 的硬约束,把公共 API 分成 **parity-breaking**(`arena.xs[i]` 类自家专属能力)和 **parity-friendly**(`NewXxxArrayTable` 类纯 Lua 表外观 + `GlobalsSlot` 类不可见的句柄预解析),是 [[issue8-boundary-cost-round]]「实现浪费 vs 架构成本」的**消费侧对偶面**——前者评判内部成本归类,后者评判公共面对外侵入度(是否要求 consumer 改脚本)。**promotion 候选 4 项全首次样本暂留观察**(对齐 issue234/issue56 两轮才升 guide 的节奏):入口纪律 / parity 分类轴 / ByRef + opaque handle 双件套(热循环 binding pattern)/ guide 4 次零阻塞应升「实践证据」节(本轮第二次复发,建议下次再现入档)。**触发场景**:接消费侧某 issue 想给 wangshu 加 API 时(先读 consumer 反思/decisions 避免重提其 deferred 方案)、评估 wangshu 公共 API 是否要求 consumer 改脚本(parity 轴)、实现「热循环 binding」类 API(ByRef + opaque handle pattern)时看这篇。
- `memory/reflections/2026-06-17-pineapple-bench-batch-wrapper-spike.md` — pineapple §6 spike 反预期轮(batch-wrapped item-mode + DataFrame.GetColumn 完整实施 + 三 backend × 二维 BatchWrapper 实测)过程教训:**三 backend 全部反向**(预估 -17/-34/-30%,实测 +22/+27/+30%)否决 §6 方案,**两条独立致命根因**:① wangshu p3 当前 build auto-lift 路径根本不工作(`analyze_on.go::analyzeCompilability` 用临时 Bridge 无 P3,所有 Proto 编译期被烧 ReasonBackendUnsupp,运行期 considerPromotion 直接 Stuck)→ **影响所有外部 embedding 的 p3 perf 解读,不止 pineapple,本反思直接 motivate 提 wangshu 自家 issue #18**;② boundary 已被 #112/115/116 压死的形式下,把 host loop 翻进 VM 让 wrapper 内层多 7000+ opcodes × ~80ns 净增 ~560µs,反噬 boundary 节省的 ~100µs。**触发场景**:接到"机理估算 -XX%"类复合估算的优化提案前先 prove-the-path-under-test(估算只是 spike 前 sanity check 不是方案 motivation)、依赖 wangshu p3 build 自然热度升层产出 perf 数字前用 PromotionCount() 白盒验、boundary 已压死形式下评估"翻 VM 包装"类方案前算 VM 内 wrapper 自己的 opcode 净增成本时看这篇。**关联**:教训 1 直接 motivate 提 wangshu #18 + 本反思的「reflection 教训 → actionable issue」工作流在 issue #13 / issue #18 两轮验证。
- `memory/reflections/2026-06-17-issue18-p3-autolift-fix-round.md` — issue #18 P3 自然热度升层路径修复轮(承上轮 [[2026-06-17-pineapple-bench-batch-wrapper-spike]] 教训 1)过程教训:**① "编译期保守占位 + 运行期实判清洗"pattern**——`ReasonBackendUnsupp` 字面"不支持"实际是"编译期不知道运行期注入谁"的占位,`recheckCompilabilityRuntime`(原 `recheckCompilabilityForce` rename)实现运行期清占位 + 对真实后端重判逻辑,守卫双路 `forceAll || (placeholder_bit && p3_available)`;**② "白盒接通正确性"≠"宿主形式性能加速"**——白盒 `PromotionCount > 0` 证 issue #18 修复确实有效,但 pineapple 形式实测 p3 比 p1 反而慢 20%(升层后 host→crescent→gibbous wasm 边界成本 > 解释器 4-opcode dispatch),是 [[2026-06-17-pineapple-bench-batch-wrapper-spike]] 教训 3「短工作量翻 VM 反噬」家族第 2 实例,但纪律「先把事情做对再优化」、正确性补丁和性能加速补丁分开评估;**③ "留 X PR 收口"godoc 承诺当 issue 信号对待**——`analyze_on.go:31` 注释"留 P3 PR 收口"在仓库躺了 7 天没人 promote 成 issue,直到 pineapple bench spike 反向触发,**纪律**:完成"留 X 收口"godoc 承诺时同时开 issue 锚定 + grep 关键字 `留 \S+ 收口` 进 milestone closeout checklist;**④ reflection 教训直接转 issue 的工作流二次验证**(承 issue #13 轮 + 本轮 issue #18,24h 内完成教训→issue→修复→闭环 reflection)。**触发场景**:Compile 期不知道运行期会注入哪个后端 / 后端是 plugin 形式时(教训 1 pattern)、修了一个底层机制后 bench 性能反差但白盒探针证机制工作正常时(教训 2 视角)、看到"留 X PR 收口"类 godoc 承诺时(教训 3 转 issue)、写 reflection 时主动标"具备直接转 issue 的成熟度"教训(教训 4 工作流)。
- `memory/reflections/2026-06-17-issue21-short-proto-guard-round.md` — issue #21 short proto 守卫:消除 pineapple p3 反噬 + 修正反思教训 2 估算错(承 [[2026-06-17-issue18-p3-autolift-fix-round]] 教训 2,close pineapple p3 perf 调查闭环)过程教训:**① profile 实测 ≫ 机理推算,复合估算分项任何一条偏 50% 整体结论可能反向**——v4 profile 实证 spike 教训 2「钩税 13% + wasm 反噬 6%」估反了,实际钩税可忽略 + wasm 是唯一主导项(p3 build cpu profile 中钩税路径完全不在 top 200);第三次复发(spike 估错 + issue #18 教训 2 估错 + 本轮)**可升 [[perf-optimization-workflow]] 作 first-class 纪律**;**② 升层触发路径的多源性**——`OnEnter`(host loop)≠ 唯一,`OnBackEdge`(user 脚本内循环回跳)同等重要;教训 2 估错根因就是漏算 CommonRow 形式 user 写的 `for` 循环触发 OnBackEdge 升层这条路径;**③ 守卫位置选择「counter 累积后」vs「累积前」是 profile 诊断完整性边界**——第一次实施把守卫放累积前导致 4 个 profile_test 失败(EntryCount/BackEdge 期望准确),挪到「累积后但 considerPromotion 前」既保 profile 诊断又跳过 perf-suboptimal 决策;**④ testing fixture 与生产路径守卫耦合**——新 sampling 守卫前先 grep 所有 fixture 工厂(makeProto / makeProtoWithCode / promoteProto)看依赖,两条修正路径并用:fixture padding(mock 测试)+ forceAll 显式绕过(e2e 测试)。**触发场景**:用机理估算做 perf 投资决策时(教训 1 实测验证)、评估某 proto 升层情况时(教训 2 列两条触发路径)、加 sampling 钩快路径守卫时(教训 3 守卫位置分层)、新生产路径守卫破坏 testing fixture 时(教训 4 padding/绕过双路径)。
- `memory/reflections/2026-06-29-p4-darwin-arm64-codepage-ci-接入.md` — P4 darwin/arm64 codepage 真实现 + macos-latest CI 接入轮(PR #27 本批 14 commits `278cf12..bfc0ab1`)过程教训:**① 同包 cgo+asm 物理边界**(Go 工具链禁止同包 cgo 文件 + Plan 9 asm 共存,W^X cgo 必须拆 `internal/jit/jitcgo/` 子包 forward 模式,保主库纯 Go 零 cgo)/ **② QEMU 用户模式不真支持 mprotect RWX→RX**(arm64 CI 翻 true 需 macos-latest M1 真机机,linux/arm64 仅 QEMU 不足覆盖)/ **③ 跨 arch 浮点 ULP 容差**(amd64 87-bit ext / arm64 64-bit IEEE-754 在 sin/cos 等超越函数尾位差异,e2e 断言放 ULP 容差非 byte-equal)/ **④ macos-latest perf threshold 偏苛**(GitHub macos runner 共享 CPU 抖动大,perf 类断言要么 skip 要么放宽阈值 ≥3x 余量)/ **⑤ runtime.GOOS DCE 陷阱**(`if runtime.GOOS == "darwin"` 在交叉编 GOOS=linux 时整段 DCE,W^X 平台分支须用 build-tag 文件物理隔离非运行时判断)/ **⑥ codepage-only sanity probe 隔离根因**(真 execute SIGSEGV 时先剥离 emit/relocate 跑「只 mmap+mprotect+ret」最小 probe,把根因从「全链路」收敛到「W^X 系统调用本身」)。**当前状态**:linux/arm64 检查翻 true ✅ + darwin/arm64 codepage 真实现(jitcgo 子包)+ macos-latest M1 接入 CI 安全子集(到达 CI 绿,真 execute SIGSEGV 留 followup PR Mac 物理机调试),darwin/arm64 检查暂 false。promotion 候选:教训 6「codepage-only sanity probe isolate 根因」是 [[prove-the-path-under-test]] guide 正向侧解药节第二实例(首次样本 VS0-e 之后),候选升 guide §正向侧解药节补充,**首次该样本暂留观察**;教训 1「同包 cgo+asm 物理边界」+ 教训 5「runtime.GOOS DCE 陷阱」首次样本暂留。**触发场景**:接入跨 arch CI 时(QEMU vs 真机机覆盖差异)、做 W^X / mmap-RX-RWX 类系统级 JIT 时(同包 cgo+asm 物理边界 + DCE 陷阱)、写 perf 类断言跑共享 CI runner 时(阈值余量)、e2e 真 execute SIGSEGV 时(codepage-only sanity probe 剥离 emit/relocate)看这篇。
- `memory/reflections/2026-06-24-p4-doc-review-round.md` — P4 文档审查轮(v0.2.0-rc5..f24684a,主助理派 4 子代理并行审 8143 行 / 10 文件 / 24 项问题)过程教训:**① 跨文档传递 perf 数字基线丢失 = 隐蔽坐标系错误**(loop 2.95x over P1 与 luajc 4.4x over gopher-lua 被串成 2.95x ≪ 4.4x,实际 7.2x > 4.4x;已扩散至 index.md / startup.md 影响 P4 立项动机叙事)首次样本暂留,候选并入 [[perf-optimization-workflow]] §5「跨文档传递维度」;**② multi-doc-drafting 收口表只查来源齐全性、未查目标侧一致性**(RJ-7 vs RJ-8 在 P2 04 目标处方案对立)首次样本暂留,候选入 [[multi-doc-drafting]]「目标侧一致性二次审查」新纪律;**③ 外部依赖现状(wazero / Go / 平台 API)+ 内部章节号易过期**(wazero internal/engine/compiler 已切 wazevo / wazero darwin/arm64 MAP_JIT 实现不存在 / Go 1.22 已过保 / 子目录扩展期 ~20 处章节引用错位)**已跨过 3 实例阈值**(联 [[2026-06-14-p3-pw7-pw4b-closure-tforloop-round]]「难点过期」+ [[2026-06-16-vs0e-varargs-stack-underflow-round]]「调研先于实现」),强烈建议提升,候选落 [[design-claims-vs-codebase-physics]] §5「时间维度」新章或独立 guide;**④ 工作流确认**:并行 investigator 审查 + 主助理收口写最终报告 = 大文档审查有效模式(multi-doc-drafting 三协议审查侧首次镜像零阻塞),建议作 [[multi-doc-drafting]] guide「适用面延伸到审查侧」实践证据。**触发场景**:大型文档扩展后做质量审查时(并行 investigator + 主助理收口)、大型文档发布前(对外部依赖现状 + 内部章节号做事实层 checklist)、多人 / 多子代理并行起草后做单点收口时(检查目标侧一致性 + 来源齐全性)、跨文档转述 perf 数字时(必带 over baseline)看这篇。
- `memory/reflections/2026-06-30-pr27-f3-3b-darwin-arm64-execute-roundup.md` — PR #28(承 PR #27 F3-#3b)darwin/arm64 真机 execute 闭环轮(3 commits / 4 bug 修复 / 5 小时,base `feat/p4-reworded`):本机 M1 上完成 PR #27 留下的 arm64 真机 BL→mmap 执行调试尾巴,把 `archSupportsSpec`/`archSupportsFrameInline` 五处占位翻 true + macos-latest CI 跑全套。**头条「bypass trampoline 探针 20 行 + 5 分钟把三 hypothesis 收敛到一档」已 promote 进 [[prove-the-path-under-test]] §5**(机制叠加 N 档崩点一样的 SIGSEGV/SIGILL 形式时写 minimal payload 直接调下一层 bypass 当前调试层,本会话 H1 trampoline ABI / H2 PAC / H3 entitlement 三 hypothesis 一次性收敛到 H1);**「sentinel 注释承诺与检查状态解耦」已 promote 进 [[design-claims-vs-codebase-physics]] §5「时间维度」**作家族第 4 形式(`arch_arm64.go::archSseOpForArith` stub 注释「当前检查返 false 本函数不会被调用」在检查翻 true 后变静默 bug,与 PW7 难点过期 / VS0-e 调研先于实现 / P4 doc-review 外部依赖现状过期同源)。其它实例:bug #1 trampoline_arm64.s `STP (R19,R20), 0(RSP)` 覆盖 Go arm64 auto-prologue LR slot @ [SP+0](三相等指纹 `pc=lr=x19`)/ bug #2 ciSegBase byte offset 协议 amd64 §9.20.9 commit-5l 已修过 arm64 PJ8 漏改 / bug #3 Pop 段尾缺 ret 一样的。**amd64↔arm64「同源 bug 漏改」纪律暂留观察**(同会话 2 实例同接入面,模式跨会话稳定性需后续 PJ-N / P5 后端再现验证)。**触发场景**:调试机制叠加 N 档崩点时(bypass 探针根因 isolate)、检查翻 true 同 commit(grep stub sentinel 注释承诺)、多后端镜像接入前(grep 前后端历史 fix commit 逐条对位)、看 PR comment 列 N 个 hypothesis 时(默认探针而非穷举)、跨平台 trampoline ABI 调试时(Go arm64 auto-prologue LR @ [SP+0..8)、FP 由 `STUR X29, [SP, #-8]` 写 user space 外故不占 user 区,user STP 从 [SP+8) 起)看这篇。
- `memory/reflections/2026-06-30-p4-pj10-perop-translator-round.md` — P4 PJ10 通用 per-opcode 翻译器初版交付轮(2026-06-30 单会话 21 commits `a94bcec..HEAD`,~3420 行,80 e2e subtests 全过,`make test-p4` 全绿含官方 Lua 套)过程教训:**① 正确性 floor(语义覆盖 = Go 端回放)vs 性能 ceiling(native amd64 multi-BB CFG emit + label resolver)拆分**——设计稿 §3 原画 native CFG 是「性能前提」非「语义前提」,实际完成选「Go 端回放 + 占位 mmap stub `xor eax,eax; ret` 3 字节」物理路径,把 35/38 opcode 接住(VARARG 设计永不接 + JMP / TEST / LOADBOOL C!=0 需真 CFG 留 followup),语义与 P1 解释器逐字节一致——架构图同一时间点不分层 floor/ceiling 须实现者动手前显式拆,与 [[design-claims-vs-codebase-physics]] §5「时间维度」对偶(空间维度新维度,首次样本暂留观察);**② stop-hook 升压判据「profile / scope-check 区分可达 vs 不可达」**——[[p3-pw10-architectural-ceiling-round]] PW10 是「数字不可达就止损落档」、本轮 PJ10 是「范围可达就真的写完」(两次 stop-hook 升压都被强制不结束,实测 TFORLOOP 是固定 4-op pattern + CLOSURE SubNUps + CLOSE 单点 host.Close + FORPREP/FORLOOP 数值循环 Go 端 dispatch 完全够用,0d7cbd2 + 4e0e863 两个 commit 真补齐——这是与 PW10 升压结果方向相反的对偶轮),首次明确升压方向判据,暂留观察;**③ CLOSURE SubNUps 跳 pseudo 数据字 idiom 第 2 实例复发**——P3 PW7 [[p3-pw7-pw4b-closure-tforloop-round]] 教训 2 + P4 PJ10 peroptranslator 同 idiom 跨翻译器,**已跨过 2-实例阈值**,建议在 PW7 反思教训 2 反引本反思作实证(memory 内反引不升 guide;P5 trace JIT 第 3 实例再升 guide);**④ P2 bridge F2-b 白名单决定 P4 测试可写形式**——pairs/ipairs 不在 F2-b 安全调用白名单 ⟹ `for k,v in pairs(t)` TFORLOOP 测试 proto 在 P2 检查被烧 ReasonUnknownCall 不升层 ⟹ P4 PJ10 永远不被走到,解法 hand-written iterator(`customIter` 经 F2-b 静态分析判定已知调用形式),与 [[issue18-p3-autolift-fix-round]] 教训 1 是 pattern 家族但维度不同(本条空间维度过滤器、那条时间维度 lifecycle),首次样本暂留观察。**promotion 决策**:全 4 条教训 **首次样本暂留观察**,本轮不升 guide(教训 3 CLOSURE SubNUps 跨过 2-实例阈值建议 memory 内反引到 [[p3-pw7-pw4b-closure-tforloop-round]] 教训 2 作实证,P5 trace JIT 第 3 实例再升 guide「字节码 opcode + 后随数据字 idiom 跨翻译器复用 `SubNUps[]` 跳表」)。**触发场景**:照设计稿完成任何新里程碑前(教训 1:先把设计稿主张拆「正确性 floor」+「性能 ceiling」两层,floor 优先把语义一次接住,性能件按 profile 决定何时上)、`/goal` stop-hook 强制不结束时(教训 2:先 profile / scope-check 区分「可达只是还没做」vs「真的不可达需止损」,前者真写、后者止损落档,**绝不**只把 commit message 写「留 followup」推迟到后续里程碑)、给新翻译器(P5 trace JIT / 新后端)写 opcode 发射循环时(教训 3:`Proto.SubNUps[Bx]` 是规范级跳表,CLOSURE 后随 pseudo MOVE/GETUPVAL 数据字 pc 步进经它)、给某 tier 写 e2e 用到 stdlib 函数(pairs/ipairs/string.* 等)前(教训 4:先 grep `f2bSafeFuncs`/`checkF2bSafeCall` 看是否在 P2 bridge F2-b 白名单,不在则 hand-write 等价 lua 形式)、看 PJ10 物理路径分歧(设计稿 §3 native amd64 multi-BB CFG vs 实际 Go 端回放 + 占位 stub)时(这是「正确性 floor 已交付、性能 ceiling 留 followup」的实证样本)看这篇。
- `memory/reflections/2026-06-30-pr28-f3-3c-tri-platform-matrix-ci.md` — PR #28(承 F3-#3b)F3-#3c 三平台矩阵 CI 等同轮(`efcfe5e..5afea11`,5 commits / 5 小时 / 39/39 CI checks + machine APPROVE):把 PR #28 推到「arm64 与 amd64 地位等同」CI 形式——test/fuzz-smoke/conformance/difftest × {amd64, ubuntu-24.04-arm, macos-latest} × {p1, p3, p4} 矩阵化,踩 **5 个独立 macOS 兼容性 paper cut**(brew lua@5.1 撤 / actions/cache symlink 半残 / bash 3.2 `${arr[@]}` set -u / bash 3.2 `mapfile`+`declare -A` / fuzz fail input 丢失)总周期 ~2 小时 = 决策成本 4-6 倍。**头条「bash 3.2 vs 4+ 兼容性 + actions/cache symlink + homebrew 包政策」已 promote 进 [[design-claims-vs-codebase-physics]] §6「空间维度」新章**(跨 OS / shell / runtime 物理环境差异,与 §5「时间维度」并列;含 §6.2 bash 3.2 vs 4+ cheatsheet 三行表 + §6.3 actions/cache symlink tar 语义 + §6.4 grep checklist)。**「跨平台等同决策成本预算 N 倍 paper cut」+「fuzz fail 必须 artifact upload」+「PR comment hand-off 跨平台 bug 通道」三候选首次样本暂留观察**(后续 P5 后端跨 OS / cross-arch fuzz 扩面 / 新 OS 接入再现可升 guide;fuzz artifact upload 是 [[prove-the-path-under-test]] 对偶面「证 input 在 testdata 而非 runner 销毁」,留 hook 不丢)。**触发场景**:做「跨平台 / 跨 N 维度等同」类简单决策时(预算 N 倍 paper cut)、写 bash 脚本要跨 OS 跑时(`grep -nE 'mapfile|declare -A|\$\{.*\[@\]\}'` 是提交前默认动作)、加 `actions/cache` 缓 symlink 类文件时(切「缓源目录 + restore 后重 install」)、加 fuzz CI 时(配 `if: failure() + actions/upload-artifact testdata/fuzz/`)、跨平台 bug 没本机复现路径时(PR comment 列复现路径+artifact 下载+hypothesis 三档)、GHA cross-arch CI 接入前(查 runner image 别默认走 QEMU / self-hosted,`ubuntu-24.04-arm` 已 GA 真机)看这篇。
- `memory/reflections/2026-07-02-p4-beat-p3-opset-round.md` — P4 must-beat-P3 op-set 扩面轮(2026-07-02,分支 `feat/p4-beat-p3`,30 commits `dc9baf0..79bd6dc`)过程教训:承 [[2026-07-01-p4-pj10-native-round]] 把 P4 amd64 native 相对 P3 wasm 的对比面从少数几核推到 25/27 对基准 ≥2%(多在 +10%~+85%),README perf 表整节更新。**七条教训**:**① exit-reason 协议解 mmap+morestack 物理不兼容的「第三态」**——`16bd225` 引入 mmap 段不 call Go,而是把 `(helperCode, a, b, c, pc)` 打包进 `jitCtx.exitArg0` + 状态字置 `ExitInlineHelper` + `RET`,Go 端 `nativeCode.Run` 是 dispatcher 循环解包 → 分派 host 方法 → `RefreshJitCtxAddrs`(arena 可能已 grow)→ 从 `codePage + resumeOff` 重入,把 [[2026-07-01-p4-pj10-native-round]] 教训 1 的「shim call in mmap vs saveGoG Go-side dispatch」二态划分升级为「U 形 exit-reason」第三态,opSupported 从 18 op 扩到覆盖 CALL / UNM / GETUPVAL / SETUPVAL / GETGLOBAL / SETGLOBAL(18-bit Bx 拆两 9-bit payload)/ 多值 RETURN(HelperReturn 终结不重入);首次样本暂留,P5 trace JIT / OSR exit 再撞可升 guide「受限执行环境的 exit-reason dispatch 协议」;**② invariant consumer 若跳过 per-access 复验,对 producer 纪律的要求严于解释器路径**——`cc6557e` 修的最深 bug:`insertNewKey` Brent 式重定位改 key→slot 映射但没 BumpGen,解释器 `icGetTable` per-access 复验 NodeKey 掩盖多年,P4 native GETGLOBAL NodeHit inline 快路径 + P3 wasm `emitGetGlobal` 一样的 gen-only guard 是同一颗定时炸弹,fuzz seed `4b3d10ff` 抓出;**invariant 强度不是由 producer 单方面定义,是由最严格的 consumer 定义**;**首次样本足够强**,建议作 [[design-claims-vs-codebase-physics]] §2「跨层固定 token / 视图段重定位」的**时序对偶**补充(那节管空间性 held-pointer,本条管时序性 gen-guard),或独立立项 guide「invariant contract 强度 = 最严 consumer 定义」;P3 wasm 侧确认修复覆盖 + 若 P5 出现第 3 consumer 即升 guide;**③ 验收门(entry gate)与 emit 质量对性能收益贡献相当,且方向相反容易被误读**——GETGLOBAL ungated 反回归(Transform CallInto 290us→332us,`ff6ac62` revert opSupported 接受 → `7b17b70` 带 NodeHit-IC gate + inline 快路径回来变 +11%,同一 emit 接受策略切换 25 个百分点全景)/ CALL 密度门 `totalOps/callCount>=16`(fib 类 CALL 密集 body 单次 exit round trip ~15-25 解释器 op 单纯亏)/ MinPromotableLener hook(bridge 加 per-backend hook,P4 实测 fixed Run 成本 ~111ns > 解释器 78ns 使 tiny proto 下地板停 10);**接受面 vs emit 面按 op 名合成单张 opSupported 表把两个正交轴强绑**,shape-dependent 净胜的 op 拆「emit 能力(始终有)」+「接受门(shape 判据)」;与 [[2026-07-01-p4-pj10-native-round]] 教训 3「prefer-native 拦截过宽」是同类**入口判据窄化**家族的 op 粒度版,共同基础「实测再定判据」;**④ Fuzz 是唯一能兜底 gen-only 快路径 + inline 类快路径的现实防线**,本轮三种子实证:(a) `f7f0bb1a` x86 SSE NaN 结果 `0xFFF8...` 与 NaN-box tag 空间别名 → arith inline 加 result guard 路由 `host.Arith` canonicalize / (b) `4b3d10ff` insertNewKey Brent relocate 无 gen bump(见教训 2)/ (c) `d9bce2e` LT/LE inline UCOMISD 缺 IsNumber 守卫使 reg operands boolean 时静默比较位模式;共同结构「inline 快路径省完整校验 → 常规 test 覆盖不到 → fuzz shape 混合扫出」,三种子入 corpus `testdata/fuzz/FuzzP4ForceAllPromote/`;**⑤ 共享分析器改进可能是 per-backend 回归**——`45b8b53` dataflow-track stdlib alias(`local sqrt = math.sqrt` 类;funcState.localAliasAsts 记 AST 而非 name,`AnalyzeProtoWithOuter` 加 `outerAliases` 参数,visitor.safeAliases 处理 reassignment/shadowing/scope 失效)让 nbody 过 F2-b 白名单升层,P4 效果 874ms→43.2ms(~20x),**P3 wasm 侧同 shape 反 43.5ms→89.7ms(≈2x,issue #39)**——一个 proto「能升」≠「升了赚」,共享分析器改动后每个后端独立跑一次 all-bench 复测,若 per-backend 分歧优选(a) 收窄共享判据 / (b) 加 per-backend 净胜门(`MinPromotableLener` hook 形式可扩)/ (c) 记 issue 交后续里程碑并在 memory 显式记录 per-backend 分歧数字;**⑥ 共享机器 + 无限制并发 fuzz + pgrep 轮询壳把 load 打爆的过程雷**——共享 80 用户机器上并发跑多个 `go test -fuzz`(默认 24 worker)+ `while pgrep` 轮询壳把 load 打到 11+ 被 kill;规则(已入用户 memory):fuzz `-parallel=4` / bench 串行 `-cpu=1` / 不写轮询壳 / 一次一个重活;**⑦ Bench 方法论三雷**——P3/P4 并跑互扰(须严格串行同 -cpu=1 同硬件时间不重叠)/ `-race` 二进制 profiling(racecall 顶到 top 盖真瓶颈,perf 工作用无 -race build)/ build tag → bench 名分歧(P4 tag 下叫 `GibbousJIT*` 默认 build 叫 `Gibbous*`,-bench regex 不匹配时 `go test` 静默 output `PASS`+ 0 iter 无 error message);perf 会话开始先跑「探针命令」验数字非零 + 名字匹配。**验证**:P4 amd64 vs P3 wasm 25/27 对基准 ≥2%(多在 +10%~+85%),2 对 CallOnly-auto 平手(解释器 vs 解释器噪声,README 显式记录);fuzz 120s / 150s(-parallel=4)全绿,3 新种子入 corpus;V14 luajc 档无回归;V15b heavy P4>P3 未退;README perf 表全表更新(2026-07-02 f736c92,「≥1.5x 加粗」规则一致化 79bd6dc);follow-up 已开 issue #37(arm64 exit-reason port,arm64 shim-free 设计天然适合)/ issue #39(P3 nbody 回归)/ task #57 待办。**promotion 决策**:教训 2「invariant consumer 定义强度」样本足够强建议 [[design-claims-vs-codebase-physics]] §2 时序对偶补充或独立立项,余六条首次样本或已入用户 memory(process-level 不升 guide)暂留观察。**触发场景**:mmap / 受限执行环境 codegen 想调 runtime helper 时(教训 1:U 形 exit-reason)、引入 gen-only / snapshot 类 invariant consumer 前(教训 2:producer 侧列全所有能改语义但不 bump gen 的路径 + fuzz corpus 主动覆盖 + 文档写清 invariant 强度契约)、新 opcode 接入 opSupported 前(教训 3:emit 能力 vs 接受面拆两层,shape-dependent 净胜的 op 带 shape 判据 gate)、写 inline 快路径省完整校验时(教训 4)、改共享分析器 / 通用可升层性判据时(教训 5:per-backend 全 bench 复测)、共享 / 多租机器上跑并发工具前(教训 6:强制降 -parallel/-cpu 到个位数,不写轮询壳)、perf 会话开始时(教训 7:探针命令验数字非零 + 名字匹配,profiling 前查 -race,bench-all wrapper 加输出行数 sanity)看这篇。
- `memory/reflections/2026-07-01-p4-pj10-native-round.md` — P4 PJ10 native emit 轮(2026-07-01,分支 `feat/pj10-native`,`ed2235b..0c9db3a` ~15 commits)过程教训:承 [[2026-06-30-p4-pj10-perop-translator-round]] 的 Go 端回放骨架 + `xor eax,eax; ret` 占位 stub,一次性做到位换成真原生 codegen——CFG builder + 两遍 label resolver + 35 opcode 每 arch 一份 emit(amd64 / arm64)。V15b heavy 三本 P4 native > P3 wasm 达 HeavyArith 2.3x / HeavyRecursion 2.3x / HeavyFloatloop 3.0x(10 iter × 3 samples)。**六条教训**:**① mmap 段内不能调 Go helper——morestack 走不过 unregistered code page,热路径必须 inline,冷路径退 Go 端 dispatch**(Go 1.17+ ABIInternal R14=G / `morestack` prologue 检查 SP vs g.stackguard,进入 mmap 段后 PC 不在 `_func` 表 unwinder 撞死),物理约束决定 opSupported gate 形状:18 op mmap-safe 子集 inline,其余 op saveGoG + shim;首次样本暂留观察,后续 P5 trace JIT 再撞可升 guide「mmap 段内 helper call 的可行性 gate」;**② RK 字段 9-bit 表示层交接边界口径切换,uint8 截断 K 引用低 8 位**——shape-spec 层负责 RK 拆分 native 层需自拆 ⟹ 函数签名沿用 uint8 B/C 会把 C=256 截成 0,`f778ab7` 拓宽到 int;与 [[2026-06-30-p4-pj10-perop-translator-round]] 教训 3「CLOSURE SubNUps 跨翻译器复发」同结构(同一条 bytecode 物理事实每个翻译器独立撞),两者构成「Lua 5.1 spec 物理事实 · 翻译器边界独立撞」家族第 2 实例;**③ prefer-native 拦截过宽立刻压掉 25 个已调优测试 —— 判据必须精确匹配 shape-spec 触达不到的形式**(初版 `4b5abf8` AnalyzeNative 就走 native,PJ3/PJ5/PJ7 shape-spec 测试全红;`0c9db3a` 收窄到「多 BB + ≥4 op live BB」)——与 [[2026-06-16-p3-pw10-architectural-ceiling-round]] 教训 1「profile 才是合同」是**入口侧对偶**(那管止损,本管窄化),共享判据基础「实测再决定形式」;**④ Tail-call gibbous dispatch 首版误重入 enterGibbous 双压帧**——`enterGibbous` 内含 `enterLuaFrame`,与 SetTailcall 已复用父帧组合成双压帧使 base 错位到实参数字上,解法 tail-call 帧上直接跑 `code.Run` inline;**⑤ arm64 vs amd64 分支编码差异要求 arch-aware label fixup**(amd64 rel32 / arm64 B rel26 word-scaled / B.cond rel19),`65cb85f` 加 `fixupKind` enum + `addFixupKind`——多 arch label fixup 从起点带 kind enum 别单 arch 写完再补;**⑥ FORLOOP inline 前向 rel8 手算精算**——`000b6bf` FORLOOP inline 里 jae / jmp 的 rel8 按字节精算,`movsd xmm0,[rbx+disp32]` 是 8 字节不是 9,能用符号 fixup 就用不用 rel8 手算;必须 inline 时每条指令字节数写在注释里 + 每个前向分支目标块字节数独立列。**promotion 决策**:全 6 条教训**首次样本暂留观察**;教训 2 已跨 2-实例阈值(表示层交接 bit-width 独立核对),P5 第 3 实例再升 guide;教训 3 是 [[perf-optimization-workflow]] §7 profile 才是合同的入口侧对偶,首次以 first-class 教训形式出现暂留;教训 1「mmap 段内 shim + Go morestack 不兼容」是 P4/P5 mmap-based codegen 通用洞察,若 P5 trace JIT 再撞可升 guide。**触发场景**:mmap / unregistered code page 里想调 Go helper 时(教训 1)、从上游 IR / spec 层往下游 codegen 迁,签名要吃更细的字段时(教训 2:bit-width 独立核对)、引入新 tier / 快路径 / 优化档,想改 Compile 入口时(教训 3:入口判据 = 新档独占形式集)、helper 组合(enterLuaFrame + SetTailcall + enterGibbous 类)出现帧栈 / 状态双写时(教训 4)、多 arch codegen 起点(教训 5)、短距前向分支想省 rel32 用 rel8 时(教训 6)看这篇。
- `memory/reflections/2026-07-03-issue40-arm64-stopbleed-round.md` — issue #40 arm64 P4 调试轮阶段 1 止血轮(2026-07-02~07-03,PR #47,f921626/7168648/bf65839)过程教训:issue 把 arm64 HeavyArith 慢 ~20x 归因于「PerOpCode head-op replay 跨界路径」,但该路径 build tag 排除、arm64 二进制里根本不存在——原止血计划「收紧 arm64 升层接受面」若直接实施是无效操作(接受面本来就在拒收)。**五条教训**:**① 退化归因前先证「被怪罪的路径」真的存在/被执行**(**[[prove-the-path-under-test]] 诊断侧对偶,已 promote 进该 guide §6**)——build tag 追踪 1 小时内证伪原根因假设;**② force/auto 模式不对称是免费的根因二分器**——emit/接受面机制影响两种模式,只有 force 专属机制(retry window)才致数字不对称,一次 `-benchtime=1x` 探针矩阵把嫌疑收窄到 force 专属路径;**③「停留重试」状态 × 高频触发点 = 意外的每事件全量重算**——forceAll retry window(前轮 `fd055e9`)组合每回边触发 `considerPromotion`,拒收结果不写回致窗口内每回边重跑全量后端分析(`recheckCompilabilityRuntime` 占 22.38% CPU,HeavyArith 1.5 GB/op),修法按「结果真正可能变化的粒度」(同一次 entry)去重;**④ 树遍历器的递归 guard 必须沿递归链传递,不能绑在 walker 实例上**——`walkFuncExpr` 建 sub-visitor 隔离信号时把递归展开 guard 也隔离(应拷贝继承),致递归闭包字面量无限展开触发 Go 1GB 栈上限 fatal(既有 bug,fuzz 抓出,`bf65839` 修复);**⑤ fuzz 的机器/架构多样性本身是覆盖度维度**——同一 corpus 在 Apple M5 Pro 上 9-80s 抓到 amd64 侧 120s/150s 多轮未抓到的既有 bug,新硬件先跑 fuzz smoke 是廉价高产动作。**触发场景**:接手带根因假设的性能 issue 时(教训 1)、写退化修复计划前(教训 1)、给状态机加 stay-and-retry 语义时(教训 3)、写树遍历器 sub-visitor 时(教训 4)、新硬件环境就绪时(教训 5)看这篇。
- `memory/reflections/2026-07-03-issue37-arm64-exit-reason-port-round.md` — issue #37 / #40 阶段 2:P4 native exit-reason 协议 amd64 → arm64 移植轮(2026-07-03,分支 `feat/p4-arm64-exit-reason` 8 commits,darwin/arm64 M5 Pro 单会话)过程教训:按上一轮预写在 issue #37 评论里的 7 步实施顺序执行零返工(`.llmdoc-tmp` 调查报告已丢失但 issue 评论落点清单存活——**重要移植前调查结论要写进持久渠道**,首次样本);**核心教训「移植不是复制——硬件语义差异让每个 op 都要重判」三实例**:(a) arm64 UNM 的 -NaN e2e 探针抓出 canonNaN sign-flip 变 value.Nil 位模式的静默错果,**回查发现 amd64 emitUNM 一样的既有 bug 双 arch 同修**(移植轮给旧 arch 挖 bug,与 stopbleed 教训 5「fuzz 机器多样性」邻接但机制不同:语义对照而非随机探索);(b) 旧 arm64 scaffold 的有符号整数比较条件码(GE/LT/GT/LE)在 FCMPE unordered(NaN)时错判,换 FP 安全族(MI/PL/LS/HI)后 NaN 无额外分支正确;(c) arm64 不需要 arith NaN 结果 guard(AArch64 default NaN 为正 + 传播保留输入)而 amd64 SSE 需要——同一 guard 两 arch 一必需一多余;**arch 差异也是简化机会**(FCMPE unordered ⇒ Z=0 单 B.NE 替代 amd64 jne+jp 双跳 / 槽地址活在 X3 免 SETTABLE 重算 idx);**e2e 探针载体要避开无关 op 噪声**(GETTABLE inline-hit 探针第一版被 kernel 里 GETUPVAL 的 exit-reason 往返淹掉 dispatched=1192,表改参数传入后干净——[[prove-the-path-under-test]]「探针载体设计」维度候选);过程教训:`git checkout` 整文件清调试代码误回退同文件未提交工作(~200 行重写,应精确反向编辑或先 stash);每步独立 commit + 每步 fuzz 节奏良好(stopbleed 轮用户反馈的直接应用);HelperCompareSlow 首次出现 dispatcher 向段内回传数据的双向通道形式(packed bit0 经 exitArg0 回传 + 段内 resume 块分支,供 arm64 后续 SELF/LEN/CONCAT 参考)。**触发场景**:做跨 arch 移植前(先把调查结论写 issue 评论 + 逐 op 重判语义勿机械镜像)、写快路径命中计数断言时(载体只留被测 op 可产生 dispatch)、清理临时调试代码时(勿 `git checkout` 整文件)看这篇。
- `memory/reflections/2026-07-03-issue45-issue39-round.md` — issue #45(P4 amd64 exit-reason 收尾)+ issue #39(P3 helper 密度收益门)单会话双 PR 轮(2026-07-03,PR #53 + PR #55,均 39/39 CI 全绿 + bot APPROVE rebase merge):PR #53 把 amd64 emit_ops 里最后的段内 shim 调用(EQ / LT/LE 非数字回退 / arith 慢路径 / LEN / CONCAT / SELF / TAILCALL / CLOSURE / CLOSE / TFORLOOP)全部迁 exit-reason 协议 + **删掉 `nativeCode.Run` 的 defer recover() 兜底**(教训 4「删兜底本身是主张的测试」:shim 面清零后 recover 若仍在则「主张未验证」永远悬着,删掉跑全量测试 + fuzz 保持绿即证明) + LEN/MOD/POW 进 opSupported 后 90s fuzz **立刻**抓到 master 上潜伏的 PerOpCode 回放乱序老 bug(seed `21c645c46a1268c6`,`function f()return{0}end f(f())` → P4 报 `SETLIST: not a table` 而 P1 正常;根因 PerOpCode「副作用先跑、head op 后物化」隐含「head 写与副作用读互不依赖」假设,NEWTABLE 进 head-op 集合就破了,三种形式:NEWTABLE head + SETLIST 报错 / LOADK head + SETGLOBAL 或 SETTABLE 静默存旧值;修法 `AnalyzeShape` 预计算 lastReplayPC + 任何 `pc < lastReplayPC` 的 head op **降级**为有序副作用 + `slotKindReg` 读回,第一版全拒误杀 `TestPJ10_SetList` 教训:「先扫全 hazard 面再选拒绝 vs 降级档,拒绝的成本远高于降级的实现成本」);PR #55 给 bridge 加 `PromotionGater { WorthPromoting(proto) bool }` 可选接口 + wasm Compiler 实现 op 构成密度地板 `total/helperBound>=7`(阈值靠白盒 op 构成统计先行 5 fib 漏网 → 7 全部回归内核被拒,教训 5「阈值靠白盒统计 + 实测迭代不要一次定死」),仅 auto 模式生效 forceAll 绕过保差分测试覆盖,拒绝 → TierStuck 吸收;nbody auto 89.7→43.2ms 与 P1 解释器同等、fib 24.9→10.9、binary-trees 104→38.4、spectral-norm 40→20.7 全面回收,heavy 三本 P3 赢面不变;**五教训**:教训 1「接受面/硬件/参数任一维度变化 = 新一轮独立的 fuzz 探索」**已跨提升阈值(第三实例)** → **promote 进 [[prove-the-path-under-test]] §5「fuzz 探索空间维度动了就重探」**(与 [[2026-07-02-p4-beat-p3-opset-round]] 教训 4 + [[2026-07-03-issue40-arm64-stopbleed-round]] 教训 5 三实例)/ 教训 2「后端接受面能力 vs 收益分层」**已跨提升阈值(第三实例)** → **promote 立新 guide [[backend-capability-vs-profitability]]**(CALL 密度门 + `MinPromotableLener` + `PromotionGater` 同结构 hook 家族)/ 教训 3「推迟执行模型必须审计执行顺序 hazard」首次样本暂留观察(与 [[design-claims-vs-codebase-physics]] §2 空间维度构成时间维度对偶,不合并)/ 教训 4「删兜底本身是主张的测试」首次样本暂留 / 教训 5「阈值靠白盒统计 + 实测迭代不要一次定死」首次样本暂留(已入 [[backend-capability-vs-profitability]] §4 工作流)。**触发场景**:接受面 / 硬件 / 参数变动后要不要立刻跑 fuzz smoke 时(教训 1)、某后端要拒收某类 proto 时(教训 2:先分「编错」还是「编了净亏」)、给推迟执行模型 / lazy evaluation 加新 op 到「延后」集合前(教训 3:先列 hazard 表格 + 用最小探针家族扫全实际命中面 + 拒绝 vs 降级)、某 `defer recover()` / fallback 上游触发条件被认为已消除时(教训 4:删掉跑测试 + fuzz 绿灯即主张成立)、选性能相关数值阈值时(教训 5:先跑白盒统计给分布再从分布选阈值再实测边界样本)。
- `memory/reflections/2026-07-06-issue50-emit-call-inline-round.md` — issue #50 Spike 5(PJ10 native 段到段 CALL 分派,让 call 密集内核反超 gopher-lua)amd64 收口轮(2026-07-06,分支 `feat/issue50-emit-call-inline`):做四件事消掉每次 gibbous→gibbous 调用的 exit-reason 往返——段内 GETUPVAL inline(`emitGETUPVALInline`)+ 双语义 RETURN 扩展到 MultiReturn proto(`emitReturnDualSemantics`)+ `ProtoSeg2SegEligible` 把 seg2seg 被调方接受面从 never-exits 扩到 arith/compare(deopt 守卫)+ GETUPVAL(inline)+ 嵌套 CALL + CALL 密度门对 seg2seg 合格 proto 放宽。实测 fib 10.5x / fannkuch 6.9x / binary-trees 1.35x over gopher-lua(同机 `-benchtime=2s -count=3` 中位数),amd64 全部正确性门全绿(difftest-p4 / crescent / conformance-p4 / peroptranslator / `-race` / 20k 随机差分脚本);arm64 因本地无执行环境(无 qemu-aarch64)+ 用户不建 PR 不触发 CI arm64 矩阵,推迟到 issue #61。**五教训**:教训 1「多 RETURN proto 静默破坏 seg2seg,症状是错值不是崩溃」(seg2seg 测试语料须含 multi-BB / 多 RETURN / 递归被调方,单 BB never-exits 叶子对多 RETURN 拆帧路径是结构盲区)是 [[prove-the-path-under-test]] 家族「绿色 ≠ 在测你以为在测的」又一形式,具体维度首次出现暂留观察 / 教训 2「eligibility predicate 调 analyzer、analyzer 回调 predicate = 无限递归」(拆纯 op 检查 `seg2segOpsEligible` 与合成 predicate)首次样本暂留 / 教训 3「open-upvalue owner 解析不能通用 inline——owner 在 Go 侧 `st.uvOwner` map 不在 upval 对象内,仅单线程无协程可 inline,用 `inlineUpvalSafe` 位门控」首次样本暂留,与 [[design-claims-vs-codebase-physics]] §2 邻接但轴不同不合并 / 教训 4「deopt 重跑幂等前提:被调方不写参数寄存器(`dest A >= NumParams` 门控)+ 无外部副作用」首次样本暂留,若 P4 OSR exit / P5 trace JIT 再遇同族可升 guide / 教训 5「arm64 验证是物理 + 用户决策阻塞而非工作量」已入用户 memory「真『不可达』只两类」process-level 不升 guide 作实证记录。**触发场景**:给 seg2seg / 跨段直调机制写测试语料时(教训 1:含 multi-BB / 多 RETURN / 递归被调方)、写「eligibility predicate 调 analyzer」类判据时(教训 2)、给依赖 Go 侧归属信息的操作做 inline 快路径时(教训 3:按运行时形式门控)、给「投机执行 + 失败 deopt-redo 整段」机制定接受面时(教训 4:显式含不写重放输入 + 无副作用两条幂等前提)、某平台本地无执行环境且触发其 CI 验证的前置动作被用户否决时(教训 5:物理 + 用户决策的合法推迟)看这篇。
- `memory/reflections/2026-07-07-issue67-auto-mode-coverage-round.md` — issue #67 auto-mode 覆盖轮(PR #75)过程教训:生产跑 auto promotion,但仓库每套 P3/P4 测试长期只经 `SetForceAllPromote(true)` 驱动,auto-only 决策链(阈值中途越线 / `recheckCompilabilityRuntime` 自然路径 / `PromotionGater` / 地板 + `FloorExempter`)完全无网(issue #67 正是此类 bug);未强制的测试也测不到——脚本永远够不到生产阈值(entry 200 / back edge 1000),auto 退化成纯解释器。新增 `Bridge.SetHotThresholds(entry, backEdge)`(0=保持不变,与 `SetForceAllPromote` 同纪律的 testing-only 入口,只改「何时」触发不改「决策什么」)+ 四类新覆盖(`test/difftest/auto_test.go` / `test/conformance/conformance_auto_test.go` / `FuzzAutoPromote` / nightly auto-mode leg)。**两条教训**:① 「未强制测试 ≠ auto 覆盖」——`PromotionCount>0` 兜底断言是解药,且开发中当场应验(P3 首版语料因 `WorthPromoting` 密度门拒收全部短核而挂,逼语料换长纯算术核),**已 promote 为 [[prove-the-path-under-test]] 家族第 13 个独立实例**;② 有状态差分 harness 的 run-for-run 基线——`FuzzAutoPromote` 首次 CI 跑抓到 harness bug(seed `861f54880d2009d5`,留作回归种子):脚本改写全局变量时同一 State 后续 Run 合法产生不同行为,但 harness 只跑一次基线却跑两次 auto State,把跨 run 状态漂移误判成 tier 分歧;修法基线与被测逐 run 同步对比,首次样本暂留观察。**触发场景**:给某个自然触发路径(auto 升层 / 自然热度)写测试套时(教训 1)、写「同一个有状态对象反复调用 + 独立基线对照」的差分 harness 时(教训 2)。
- `memory/reflections/2026-07-08-issue77-math-intrinsics-round.md` — issue #77 math.* intrinsic emission 轮(PR #87,分支 `feat/issue77-math-intrinsics`,双 arch + CI 全绿 + bot 两轮 APPROVE)过程教训:给 P4 native JIT 加 `math.*`(sqrt/floor/ceil/abs/max/min)intrinsic emission,CALL 站点 IC 观察到被调是已知纯数值 host closure 时段内直接发射硬件指令(amd64 SQRTSD/ROUNDSD/sign-clear/UCOMISD-select;arm64 FSQRT/FRINTM/FRINTP/FABS/FCMP+FCSEL),不再 exit-reason 往返到 Go host closure;是 issue #67 NodeHit inline 后 n-body 剩余 dispatch 几乎全是 sqrt 的直接后续。**四条教训**:**① 给热路径加新特化分支时,把新检查挂在「不适用它的输入本就会走到的既有 guard-miss 慢路径」上,换热路径零开销**——math intrinsic 被调方是 host closure,`protoID` 字段是 `hostFnID` 而非 Lua `protoID`,必然过不了普通快路径的 protoID guard、直接落到 guard-miss 慢路径,把 intrinsic 检查挂在那条慢路径上,fib 等热 Lua 调用走 seg2seg 完全不碰 intrinsic guard(零开销),fib/spectral/fannkuch 无回归实测证实;首次样本暂留观察(与 [[perf-optimization-workflow]] 的流程纪律不同层,是 codegen 结构手法),等第二个不同形状实例确认是通用手法而非本例 protoID guard 布局巧合。**② 数值 intrinsic 的 byte-equal 依据在值表示层的 NaN 规范化,不在指令语义**——`value.NumberValue` 把任何 NaN 规范成 `canonNaN`(值世界唯一 NaN),SSE/NEON 对 canonNaN 要么原样传播(结果 `< qNanBoxBase` 直接存)要么产生负 indefinite(被 result-NaN guard 抓走路由 host),承 arith inline 先例(fuzz bug `f7f0bb1a`);暂留观察但接近阈值,是 [[design-premises]] 第四组「NaN-boxing 值表示承诺」在 JIT 段内浮点快路径上的正确性对偶落点,第三个段内浮点快路径撞同一 NaN-box tag 区时升为显式条款。**③ 机制只在 proto 升 native 后生效 → 测试脚本必须真能升层**——直接 `math.sqrt(x)` 过不了 F2-b unknown-call(proto 不升 native、intrinsic 路径永远到不了),须用 `local sqrt = math.sqrt` 别名(analyzer stdlib alias 追踪认得),是 [[prove-the-path-under-test]] 「未升层测试静默测了解释器」家族又一实例——不只「auto 阈值没到」会静默退化,「调用形式过不了 F2-b 静态门」也会,两者都靠 `PromotionCount>0` 兜住(§2(b) 解药);memory 内反引 [[2026-07-07-issue67-auto-mode-coverage-round]],不新增 guide 正文小节。**④(过程)已知不该提交的 untracked 产物,注意到的当下就加 .gitignore**——`git add -A` 把 pre-push hook 产物 `.rebasechk` 误扫进提交被 bot 抓到,之前明明注意到它是残留还特意没 commit,下一次 `git add -A` 又带进去;已 `git rm` + 加 `.gitignore`(commit `0e78ba6`);首次样本暂留观察。**触发场景**:给某快路径加新特化分支时(教训 1:先问新分支的输入在既有 guard 序列里天然落到哪个 miss 分支,把新检查寄生到那个已经要走慢路径的点)、给 JIT 加产生浮点结果的快路径前(教训 2:先查值表示层怎么规范化 NaN/特殊值,正确性依据在那里不在指令语义)、给只在「proto 升 native 后」才生效的机制写测试时(教训 3:先确认脚本真能升层 `PromotionCount>0`,`math.*` 类须用 `local` 别名过 F2-b)、注意到不该提交的 untracked 产物时(教训 4:当下就加 `.gitignore`,别只在脑里记着不 add)看这篇。
- `memory/reflections/2026-07-08-issue67-amd64-nodehit-crossrun-round.md` — issue #67 amd64 NodeHit 跨 Run inline 补齐轮(PR #82,分支 `feat/issue67-amd64-nodehit`)过程教训:把 arm64 上已修正的 identity-free NodeHit inline(hmask 边界 + nodeRef guard)移植到 amd64,推翻 PR #74「amd64 NodeHit inline 实测反而 ~3% 回归、故 arm64-only」的历史结论——那 ~3% 不是 inline 的成本,而是一道烤进段里的 TableRef 身份 guard 跨 Run 100% 落空的白费开销:n-body 的 `bodies[i]` 这类局部表每个 Run 都在新 arena 偏移重建,promotion 只烤一次快照,身份 guard 跨 Run 必然不通过,段里每次都发射 guard、每次都不通过、每次都照样 exit-reason 往返到 host,inline 快路径跨 Run 从没真的生效过;之前看到的「inline 命中 17 万次」是单个 Run 内的假信号。**度量教训(头条)**:任何投机 / inline 快路径的收益,度量单位必须是**跨 Run(稳态)的 exit-reason dispatch 计数**,不是单个 Run 内的命中数——选错度量单位会把一个从没生效的优化误判成「生效但太贵」,再叠一个碰巧同向的噪声(~3%),就能推出完全错误的架构结论(arm64-only)。修正 commit 6a10721(原在 arm64):身份 guard 对正确性并不必要——一个 node 自己的 key 字段就能唯一标识这条 entry,换成两道与表地址无关的 guard(hmask 边界 `Index <= hmask` + `nodeRef != 0`),同 shape 的表跨 Run 重建也能真命中。本轮把修正后的 guard 移植到 amd64,共用 `emitTableNodeHitPrelude`;实测 amd64 n-body auto 43.5ms→7.0ms(~6.2×),跨 Run exit-reason dispatch 875k→50k,fib / binary-trees / spectral-norm / fannkuch 无回归;arm64 M5 Pro n-body auto 0.98×→5.7×。新增 `TestPJ10_TableNodeHit_CrossRunInline`(断言每 Run dispatch < 500,身份 guard 时代 ~18000),补上 arm64 那一轮缺的跨 Run prove-the-path。**头条已 promote 为 [[prove-the-path-under-test]] §8「度量单位/时间窗口」第 14 个独立实例**(度量侧新维度:单 Run 命中数 vs 跨 Run 稳态 dispatch)。**触发场景**:读一个投机 / inline 快路径的收益数字时(度量单位必须是跨 Run 稳态 dispatch,不是单 Run 命中数;看到「命中 N 次」漂亮但整体数字反而回归,默认怀疑「机制被触发过但跨 Run 稳态从没真生效」)、给 IC / inline 快路径写「把编译期身份快照烤进段」的 guard 前(问「这个身份会不会被跨 Run 重建打穿」,若被测对象每 Run 在新 arena 偏移重建就换成与地址无关的 key/shape guard)、遇到一个历史 perf 裁决(某优化「实测反而回归、故某平台 only / 故放弃」)时(先复查它当年的度量单位是不是单 Run / 单窗口,用跨 Run 稳态计数重测再决定要不要推翻)看这篇。
- `memory/reflections/2026-07-08-pr83-forprep-stackgrow-fallout-round.md` — PR #83(fixes #78/#80/#81)FORPREP + native 调用栈溢出连锁修复轮:承 issue #67 auto-mode 覆盖轮(PR #75),`FuzzAutoPromote` 上线数天内抓到两个此前无测试网的 P4 native-tier miscompile。**#78** 两 arch 共有的 FORPREP 缺 `IsNumber` 数字守卫(nil limit 的 NaN-box 使 FORLOOP 静默零迭代退出,不报 PUC 5.1 的 "'for' limit must be a number" 错;修法三槽守卫 + 新 `HelperForPrep` exit-reason 退 `host.ForPrep` 走 PUC coercion-then-error);**#80** 两个叠加 bug——(a) `nativeCode.Run` 把入口捕获的 `base`(arena 字节偏移)当固定 token 传给每次 `RefreshJitCtxAddrs`,但 host 调用 growStack 会重定位值栈段留悬垂偏移(修法从活线程状态重算 `vsBase`)/ (b) seg2seg 段内直调从不进 Go 侧 `ensureStack`,深递归溢出初始栈段写进邻居 arena 对象(修法新增 `jitCtx.valueStackEnd` + 两 arch fast-body 边界守卫);**#81** nightly workflow `eval` 剥掉 `-tags` 引号的纯 CI 配置 bug。**三条教训**:① fuzz 探索空间维度重置不止「扩接受面」触发,「新增此前从未被驱动过的执行模式(auto vs force-all)」同样触发——确认 [[prove-the-path-under-test]] §5 既有断言第四次验证,不新增分支;② **[[design-claims-vs-codebase-physics]] §2 第二独立实例**:#80(a)与 P3 PW6 的 `$base` UAF 是同一条物理事实(arena 段可被 grow 重定位)在两个独立加速层(P3 wasm trampoline / P4 native dispatcher)里各自被撞中,判据须在新子系统里重新逐条核对,不能假设「P3 已修 P4 就免疫」——**已 promote 补入该 guide §2**;③ 段内快路径必须显式复刻 host 路径隐式维护的每条不变式(host `enterLuaFrame` 隐式 `ensureStack`,seg2seg 段内直调绕过 host 通道也绕过了它维护的保障),与 [[backend-capability-vs-profitability]] 的「host 通道固定成本假设不适用段内通道」是同一二分轴的另一面,首次样本暂留观察。**触发场景**:新增/扩展任何加速层且存在「入口捕获 arena/栈偏移当固定 token」模式时(教训 2)、给绕过 host 通道的快路径设计接受面/守卫时(教训 3)、给自然触发路径新上线测试网时(教训 1,预期短期内抓到既有 bug 是正常现象)。
- `memory/reflections/2026-07-08-pr86-deep-recursion-nosplit-round.md` — PR #86(fixes #85,files #89)深递归双 bug 连锁修复轮:**#85** 深升层递归报假 "C stack overflow"——解释器 Lua→Lua 递归平坦(单 executeFrom 循环,仅受 `maxLuaCallDepth`=20000 约束),P4 路径超出 seg2seg 深度上限后每层递归都是真 Go 重入链(`Run → dispatcher → ExecutePlainCallInlineFrame → enterGibbous → Run …`),每层 `nCcalls++`(上限 200),深度 1000 合法递归被误报;修法为 `gibbousReentryCCallCap = maxCCallDepth/2` 软水位线(frame.go),四个 gibbous 派发入口(doCall 升层分支 / 两个 zero-cross 分支 / executeFrom TAILCALL 派发)超水位回落解释器平坦执行,深度约束回到 20000 与 P1 逐字节一致。**#89**(存量 bug,被 #85 修复暴露成 PR 自己 CI 秒撞的 GC "found pointer to free object"):seg2seg 段内直调每层 `sub sp` 发生在 morestack 不可触发的 NOSPLIT 窗口内且对 linker nosplit 记账不可见,Go 栈守卫只保留 `StackNosplitBase = 800` 字节,旧 `segToSegDepthCap = 128`(~4KB)在 SP 贴近守卫时穿透栈分配踩坏相邻堆对象——旧注释「~4KB comfortably within the stack」对照错了物理量(goroutine 栈大小 vs NOSPLIT 允许量);修法 cap 降 16(worst case 608B < 800B,实测边界 16 从不崩 / 64、128 必崩),提回需接 05 §3.4 设计过未接线的自管 spill 栈 → issue #89。**三条教训**:① 修复本身是 fuzz 维度重置器——一个 bug 修复扩大可达状态空间会立刻暴露下个存量 bug,同 PR fuzz 撞新 crasher 先 master 复现区分存量 vs 新引入,不反射性回滚([[prove-the-path-under-test]] §5 第五次验证 + 新触发方式);② 栈预算声明必须对照正确物理量——NOSPLIT 上下文预算是 `StackNosplitBase`(800B)不是栈大小,mmap 段内 SP 移动 linker 看不见须人工记账([[design-claims-vs-codebase-physics]] 候选新实例);③ 依赖运行时内存布局的 crash 最小化时保持 harness 形状(简化 repro 不崩、原形交错 State 崩)+ `SetGCPercent(1)` 确定性化 + 二分与解析算术交叉验证。
- `memory/reflections/2026-07-08-pr95-spill-stack-fuel-round.md` — PR #95(fixes #89)spill 栈接线 + 燃料计费轮:amd64 侧接线 05 §3.4 自管 spill 栈(trampoline 切 SP 到 64 KiB Go 堆 buffer,进段前读 codeAddr 防 FP 相对寻址踩垃圾、恢复不碰 RAX/R0),`segToSegDepthCap` 16→128;darwin/arm64 M5 Pro 真机接力四项验证全绿。**接力中 FuzzAutoPromote 12 秒撞出 #85→#89 链第三环 bug**:step budget 无异步生产者(计费只在 `st.preempt()` 解释器 call/回边点),seg2seg 段内直调全绕过,cap=128 后深度 ≤128 子树整棵段内跑(~φ^128 次调用零计费),`fib(5510)`+budget 永久挂死;修法 `JITContext.segCallFuel` 燃料计数(段内派发 dec+jz、归零回退 host 路径恢复计费点;有 budget/context 灌 4096 否则 1<<31,无预算负载零感知,消耗燃料重灌时记入 `stepUsed` 同口径计费)。**三条教训**:① 解除 workaround/限制时列出它顺带提供的全部隐式保障(cap=16 碰巧兜底了计费频率),逐条确认正式机制覆盖——「修复即维度重置」三连环([[prove-the-path-under-test]] §5);② 异步信号(preemptFlag)vs 同步计费(stepUsed)是两种模型,段内感知同步计费须用燃料/配额预支而非 flag 检查;③ 真机接力 fuzz 摇测不可省——交叉编译 + CI 矩阵全绿仍 12 秒撞出真 bug(QEMU 不真模拟 mmap 执行、CI fuzz 时长远短于本地)。
- `memory/reflections/2026-07-09-issue52-close-round.md` — issue #52 收口轮(PR #99):补齐 `opSupported` 最后四个 op(TAILCALL/TFORLOOP/CLOSURE/CLOSE,双架构 exit-reason,协议形态各镜像既有先例——HelperReturn 终止 run / HelperCompareSlow verdict 回段 / P3 伪指令 skip / 普通往返),白名单只剩 VARARG 永久门。**关键教训**:P4 验收 e2e 抓到 clean master 上就存在、P1/P3/P4 三层共享的 `doTailCall` 丢多返回值存量 bug(传父帧定长 nresults 而非 PUC 的 LUA_MULTRET,`return unpack(t)` 丢第三个值)——「给此前无 native 覆盖的 op 写验收测试 = 新一格探索维度,抓到的往往是被解释器宽容路径掩盖的存量 bug」([[prove-the-path-under-test]] §5 家族再确认);操作纪律:新路径测试失败先 oracle 定期望、再 clean master(git stash)定归属,两步做完才决定「修改动」还是「独立 commit 修存量 + conformance 锁三层」;e2e 手算期望值必先过 oracle(本轮手算 12,oracle 说 27)。另记 GHA hosted runner 池未接单的 CI 基础设施噪声识别特征(conclusion=cancelled + steps 空 + "not acquired by Runner" annotation,处置 rerun 即可)。
- `memory/reflections/2026-07-09-issue91-94-p3-audit-round.md` — P3 摸底四连修复轮(PR #100/#101):一场 P3 auto 模式生产路径 probe(挂 bridge Logger 逐 proto 看升层决策 + cpuprofile + 控制变量 kernel 扫描)一次产 4 个带实证 issue,根因是 P4 上线后 P3 auto 生产路径长期缺 probe——「没有观察就没有发现」。**#91**:relooper `computeScopes` 只处理单向 improper overlap,漏对称方向,顶层 if/else diamond 永远升不了 P3(连 force-all 也不行),`normalizeScopes` 定点迭代修复 + 白盒 `TestP3_TopLevelDiamondPromotes` 断言 `PromotionCount>0`(byte-equal 分不清升层与静默 CFAIL 回退,bug 藏这么久正因此);**#92**:`WorthPromoting` 缺 back edge 维度,直线小体升层每次付 wasm 边界往返却无循环摊薄,加 `straightLineMinCodeLen=32` 拒(仅 auto);**#93**:README P3 列分母错配(kernel×50 vs 裸顶层×1)低估 ~50 倍;**#94**:`Bridge.OnEnter` 每帧查 map,加 `OnEnterID`/`OnBackEdgeID` 走 ProtoID 索引 slice。**核心教训**:教训 1 能力修复 ≠ 收益改变(fannkuch 能编但不赚,正面确认 [[backend-capability-vs-profitability]] 分层)、教训 2 互补 PR 合入顺序(收益门先合防回归窗口)、教训 3 fixture 随升层判据多维化演进(第 2 实例连 issue #21)——**三条均 promote 进 [[backend-capability-vs-profitability]]**(§3 fannkuch 实例 + §3(e) back edge 维度 + §4 互补 PR 合入顺序 + §6 fixture 多维化),#91 白盒 `PromotionCount>0` 补进 [[prove-the-path-under-test]] §2(b);教训 4 跨机器 bench 接力用 PR 评论(第 2 实例连 issue #89,接近阈值暂留观察)、教训 5 集中 probe 产批量 issue(暂留观察)。**触发场景**:某后端 auto 收益回归 / 能力修复与收益门分 PR 并行定合入顺序 / 改升层判据后热度类 fixture 连锁失败 / 给某条长期没人看的生产路径体检时看这篇。
- `memory/reflections/2026-07-09-pr101-session-side-findings.md` — PR #101 会话侧补充(与上篇同轮互补,arm64 侧):**① issue #102 的发现路径**——PR #101 无关的 fuzz-smoke 失败经 clean master 归属确认为存量 bug(P4 段内 FORLOOP 回边不计费 step budget:#89 的 segCallFuel 只计费了 seg2seg CALL 派发,回边这半没做;#77 intrinsic 内联消掉循环体最后一次跨 Go 机会后窗口密封,`SetStepBudget` 下解释器 40ms 报错 / P4 force 9s 跑完 2.77 亿次迭代),处置 = 开 #102 + PR 评论说明 + rerun,**慢 seed 故意不入 corpus**(会让每轮 CI 决定性重撞 hang 检测器);教训:给绕过 host 的快路径接计费时须**枚举全部段内控制流形态**(CALL 派发/循环回边/TFORLOOP 继续臂),不是只修触发用例那条——#89→#102 是「修复即维度重置」链的又一环。**② 多机同分支 push 事故**(工作流失败记录):未经指令 commit+push + pre-push hook 内层 force 语义覆盖了队友刚推的 commit;纪律 = 指令到哪步停哪步、协作分支 push 前必 fetch、push 被拒绝不无脑重试先看拒因。**触发场景**:CI fuzz 撞慢 seed 要不要入 corpus / 给段内快路径接抢占计费 / 多机接力分支上要 push 时看这篇。
- `memory/reflections/2026-07-09-issue103-compare-ieee-round.md` — issue #103 修复轮(PR #104):P4 inline 比较快路径漏 IEEE 边值语义,fuzz seed `765ba4598e721c69` 撞出 tier divergence(NaN<0 被 amd64 判 true,无限递归变正常返回)。两类根因一次修:**amd64 LT/LE** UCOMISD+裸 jcc 把 unordered 解析到错误后继(四种 op/A 全反;修法 jp 预分支,镜像 arm64 的 MI/PL/LS/HI 族)+ **两 arch EQ** 位相等比较漏 canonNaN(NaN==NaN 误 true)与 ±0(-0.0==0.0 误 false)两个 IEEE 例外(修法 canonNaN/幅值检查,K 操作数门控保 `x==1` 常见形状零开销)。**三条教训**:① 双后端修某类语义 bug 时须**跨 arch 反向扫同类站点**——arm64 端口轮 2026-07-03 就修了 unordered 却没回头查 amd64,带病一周+;与 [[2026-07-08-issue67-amd64-nodehit-crossrun-round]](arm64 guard 改良未移植回 amd64)构成「双后端修复不对称」模式第 2 实例(第 3 实例 #107 后已升 [[cross-backend-semantic-fix-sweep]]);② canonNaN 规范化是双刃剑——审计不变式除了问「谁依赖它成立」还要问「**谁的正确性恰好依赖它不成立**」(位相等≠语义相等的所有站点在 canonNaN 语境下需重审);③ 一个 fuzz 失败顺手扫全 family(LT/LE 查完顺手查 EQ 多挖两个潜伏 bug,inline 快路径 bug 从不孤立)。**触发场景**:在一个后端修语义类 bug 时 / 审计 NaN-box 或规范化类不变式时 / inline 快路径出 IEEE 边值问题时看这篇。
- `memory/reflections/2026-07-10-issue102-loop-fuel-round.md` — issue #102 修复轮(PR #105):P4 native 段内循环回边不计 step budget——循环体全 inline(纯算术或 #77 math intrinsic)后段内碰不到任何 Go 侧计费点,277M 次迭代 + `SetStepBudget(1<<20)` 在 P4 force-all 上 9s 跑完 vs 解释器 40ms 报错(fuzz seed `3edb662d8f1525de`,#89 segCallFuel 的姊妹缺口)。修法:独立 `jitCtx.loopFuel` 计数器(FORLOOP condTrue 内联 dec+jnz / 负 sBx JMP 一致;arm64 ldr-sub-str+cbnz 镜像)+ `HelperLoopFuel` exit-reason + `host.LoopPreempt` 计费/重灌/检查;PerOpCode 回放路径经 `LoopFuelTick` 扣同一计数器。**三条教训**:① **计费点与检查点是两件独立的事**——第一版复用 segCallFuel,build 绿但白盒 probe 揭穿一次未触发(dispatcher 每 resume 重灌抹平段内递减),且该路径全程没有检查 stepUsed 的点,billed-but-never-checked 仍是漏洞;检查必须在 LoopPreempt 内完成([[prove-the-path-under-test]] 在设计期的应用);② review bot 抓出 **seg2seg deopt stranding**——callee 在 segCallDepth>0 耗尽燃料走 deopt 不经 LoopPreempt,计数器停 0 后段内 sub+jnz 回绕 2^32;修法按 armed && loopFuel==0 滞留特征在 RefreshJitCtxAddrs 识别并重灌不计费(deopt-redo 在 baseline 重跑由 st.preempt 计费,再计双记);回归测试载体 inner 循环体需 ≥3 op 否则编成 PJ3 spec 模板进不了 seg2seg(prove-the-path delta 断言立刻抓住);③ **fuel counter 家族三问表**——每个重灌点须回答「谁计费、谁检查、drain 归零而无法出段时怎么办」,segCallFuel(每 resume 重灌)与 loopFuel(只 LoopPreempt + armed 切换重灌)的差异正源于 drain 站点出段拓扑不同。**触发场景**:给绕过 host 的段内快路径接计费/抢占时 / 新增 fuel 类计数器时 / seg2seg deopt 路径与计数器状态交互时看这篇。
- `memory/reflections/2026-07-10-issue106-107-nightly-crashers-round.md` — issue #106/#107 修复轮(PR #108):nightly run 29053951054(跑在 PR #105 合入前)p3/p4 各撞一 crasher。**#106**(p4,3.3e23 次迭代 intrinsic 循环)重放证实已被 #105 loopFuel 修复——worktree 在旧 commit 重放 180s 挂 / 当前 master 0.12s PASS,seed 入 corpus;**#107**(p3)真 bug:wasm emitUnm 快路径裸 f64.neg 把 canonNaN sign-flip 成 0xFFF8...=value.Nil 位模式,`-(0%0)` 把 NaN 写成 Nil,升层后第 2 次 Run 起误报 arithmetic-on-nil;修法镜像 P4 #37 的 result guard(negged < qNanBoxBase 才走快路径)。**三条教训**:①「双后端修复不对称」第 3 实例(#67/#103/本轮),#37 注释写 "both arches" 但漏了 P3 wasm——已升 [[cross-backend-semantic-fix-sweep]];② canonNaN 双刃剑第二家族:位运算类(neg sign-flip / abs mask)可把 canonNaN 移进/移出 tag 空间,补 #103 识别的比较类之外,result guard 是通用解药;③ nightly 失败先查 run headSha 归属已固化五步(headSha → 包含关系 → 旧 commit 阳性重放 → 现 master 阴性重放 → seed 入 corpus),第 2 次遇到「issue 开出来之前 bug 已修好」(#97 先例)。**触发场景**:nightly crasher 处置 / 修 inline 快路径语义 bug / 审计 NaN-box 位变换时看这篇。
- `memory/reflections/2026-07-10-issue112-selftail-loop-round.md` — issue #112 修复轮(PR #113):P4 升层后 HeavyRecursion 比 P1 慢 12%(表格唯一「升层净亏」负载,arm64 -46%)。根因精确到协议:collatz 全部递归调用是 TAILCALL,而 HelperTailCall(#52)每层终止 native run——每层付一次段退出 + host.TailCall + executeFrom + Go 重入。修法(#112 方向 1):mono 自尾调用编成段内循环——PUC 帧复用语义使「自尾调用」与「用搬移后参数重进本段」位级等价,fast body = 全 64 位身份 guard(TagFunction<<48|currentClosureRef,payload-only 是 #107 别名家族)→ 参数搬移 → nil-fill(镜像 enterLuaFrame,一段代码两义务:参数不足补 nil + 上迭代局部不泄漏)→ loopFuel dec+jnz(#102 验收条款)→ jmp BB 0;guard miss 走原路。HeavyRecursion 6.48→1.85ms(0.88×→3.1× vs P1)。**三条教训**:① **closeUpvals 可跳过性靠编译期蕴含链证明**(proto 无 CLOSURE op ⟹ 本帧局部不可能被捕获 ⟹ closeUpvals 恒 no-op;绕过 host 隐式不变式时证明义务是「不变式在此形状恒空」,手段是字节码静态蕴含不是「跑了没挂」);② 前轮教训(#107 guard 位宽 / #102 回边计费)在设计期直接复用零返工——guide 的价值在跨 session,本轮是首次「设计期消费」实证;③ **性能 issue 的 diagnosis 做到「修法可按图施工」深度**是把探索成本从实现轮搬到 diagnosis 轮的杠杆(本轮 branch 到 PR ~1.5h 零方向调整)。**触发场景**:给绕过 host 通道的快路径证明「跳过某清理步骤 sound」时 / 开性能 issue 决定 diagnosis 深度时 / 段内新增回边或身份 guard 时看这篇。
- `memory/reflections/2026-07-10-tier-admin-luajit-bench-round.md` — P4/P3 分层执行生产化三件套(`SetTierEnabled` 运行期开关 + `TierStatsSnapshot` State 级观测 + `docs/embedding-tiers.md` 嵌入方部署文档)+ wangshu-vs-LuaJIT 同源对比基准四件套(engine-neutral `bench.lua` + `benchlua` 二进制 + 驱动脚本 + `.github/workflows/bench-vs-luajit.yml`),PR #115。五条教训:吸收态转移点分因计数而非事后遍历反推 / 测 Stuck 分类的载体调用次数要过完 force-all 的 64 次 warm-up retry window / kill switch 三段式路径证明(promote→off→on,`NativeRunCount` delta 断言 + 第三段 `Promoted` 不变把「重开不重编译」隐性约定钉死)/ 跨引擎 checksum 廉价双重防线(静默错果 + 引擎 DCE 掉计时体)/ 自校验脚本预期值先跑 P1 拿真值别心算(`t = f(100)` 是赋值不是累加)。
- `memory/reflections/2026-07-11-issue117-118-nan-forloop-round.md` — PJ3 字节级 FORLOOP spec 模板遇 NaN limit/init/step 死循环(#117/#118,PR #119):`for A=0,0%0 do end` 常量折叠出 NaN limit,`ucomisd idx,limit; ja` 在 unordered 上永假、arm64 `fcmpe;b.gt` 一样,单线程 run 无 safepoint、spec 模板通道无 step 计费点,mmap 段死循环。三处修:amd64 换比较序 + `jb`、arm64 `CondGT`→`CondHI`、analyzer step 门 `<=0`→`!(>0)`。**核心教训**:① unordered 分支去向必须显式论证(#103 之后第二例,同修复手法族,换操作数序把 unordered 落在触发侧);② shape gate 按「拒绝侧默认」写(`!(x>0)` 而非 `x<=0`);③ 「同语义多通道 emit」是 cross-backend-semantic-fix-sweep 的**通道内**变体(per-op FORLOOP 早修好、spec 模板漏网),同类扫描要扩到「后端 × 通道」;④ prove-the-path 再一次立功——初版载体带 body 落到没坏过的 per-op 通道,`SpecForLoopHits` delta=0 当场抓出空测。三条 promotion 均已落 [[cross-backend-semantic-fix-sweep]](枚举维度扩「后端 × 通道」+ 加 FORLOOP 通道内变体第 4 实例 + 加 unordered 修法范式条 + 新增 shape gate 拒绝侧默认小节)。**触发场景**:手写 `ucomisd`/`fcmpe` 后接条件跳转时(注释里必须显式回答 unordered 跳到哪)、写 shape/IC gate 比较条件时(用「必须满足接受条件」正向形式)、修某后端某 op 语义时(除了跨架构还要跨通道扫)、给 spec 模板通道写载体时(用 `SpecXxxHits` delta 断言精确匹配模板形状)看这篇。
- `memory/reflections/2026-07-11-issue123-unreproducible-crasher-round.md` — nightly p4 `FuzzAutoPromote` worker 无声死亡调查轮(#123,PR #124):worker 跑 35 分钟 / 5100 万 execs 后死于 `exit status 2` 无 panic 输出,落盘 corpus `326b508ea720a654` 是合法通过用例(无界非尾递归 + 每层 60 次全局写循环)。按 2026-07-03 分诊纪律(真 crasher 判据 = 落盘 input 可复现)判定不是 input 决定的 VM bug——七角度复现矩阵全干净(精确重放 ×10 / 阈值镜像 hammer ×300 / bare+pcall+coroutine 三种恢复路径 / mmap 泄漏探针 300 层升层 / 6 分钟定向 fuzz 1600 万 execs / GOGC=1 + SetGCStressMode / 失败 run headSha=d6e05bd 已含 #117/#118 修复,同代码 p1 腿 45 分钟 / 9100 万 execs 干净),特征指向 fuzz worker 进程级资源耗尽。处置:corpus 入库常驻回归 + `scripts/go-fuzz.sh` 加 `GOMEMLIMIT=6GiB` 把无声外部 kill 转成带栈的 runtime OOM fatal + worker 无声死时 dump `free -m` / `vm.max_map_count` 进 artifact,不硬编修复不无限挖根因。**核心教训**:① 无法复现 crasher 时版本核对该是第一步不是第七步(用户提示「看看 run 开始时的代码版本」,一条 `gh run view --json headSha` 就能排除「撞的是已修复代码」这个最便宜的解释);② 分诊纪律真的把资源耗尽和 VM bug 分开来了,避免了硬修不存在的 VM bug;③ 不可复现问题的正确投资方向是「让下次复发自带诊断」(把外部 kill 转成 in-process fatal + 系统状态快照 dump);④ 七角度复现矩阵是「crasher 不复现时还能穷举什么」的实用检查单。**已 promote 为 first-class guide [[unreproducible-crasher-triage]]**(不可复现 crasher 处置模式),聚合版本核对先行 + 分诊判据 + 七角度检查单 + 处置纪律(corpus 入库 + 诊断硬化)四条。**触发场景**:fuzz/nightly worker 报 crasher 但落盘 input 精确重放跑不出来时(hook:nightly worker 无声死亡的七角度排查与处置模式)。
- `memory/reflections/2026-07-11-issue125-return-freereg-round.md` — `return f() or (f())` RETURN 读错寄存器轮(#125,PR #126):nightly fuzz corpus `function sum() for A=0,0 do end end return sum() or (sum())` 在 P1 返 `0`、auto 返 `nil`,PUC luac oracle 说答案是 `nil`——表象是「P1-vs-auto 层间分歧指向升层路径」,真相是共享前端 `stmtReturn` 的 codegen 存量 bug 让两个 tier 各自读到不同的栈垃圾。根因:`stmtReturn` 单值带短路跳转链分支 `base := fs.freereg` 在 `exp2NextReg` 之前捕获,而 `exp2NextReg` 内部先 `freeExp`(freereg 回落一格)再把值物化到低一格,`RETURN A = base` 因此指向值的后一格,读到未定义数据。修法:`RETURN` 与 `freereg` 恢复改用 `single.info`(`exp2reg` 实际物化的位置),与上方无跳转链快路径写法一致;横向审计 `stmt.go` 另外四处 `base := freereg` 捕获(num-for / gen-for / vararg-return / multi-return)均在表达式求值前捕获、中间无 `freeExp`,安全。验证:corpus 入 `testdata/fuzz/FuzzAutoPromote/b03a5a1dd9e56fbf` 常驻(轻量、budget-bounded,unreproducible-crasher-triage guide corpus 位置判据首次正向消费)+ `test/difftest/corners_test.go` 加 5 个 `corner_ret_*` probes + 三 build 全绿。**核心教训**:① **层间分歧归因先问 PUC oracle**——差分 fuzz 的 P1 参照系本身可能是错的,若 oracle 与两层都不一致 bug 在共享层(前端 codegen / stdlib / VM 共享语义),不在升层路径,已 promote 进 [[unreproducible-crasher-triage]] guide「真 crasher 但失败形式是层间分歧」新节;② **前端 codegen freereg capture-use 间距审计判据**——`base := fs.freereg`(或等价水位缓存)与 use 之间夹了任何会移动 `freereg` 的调用(`freeExp` / `exp2NextReg`)就红旗,权威位置是 `e.info` 不是预捕获水位,修法用 `single.info` 消费物化后位置或后移 capture,并入 [[unreproducible-crasher-triage]] 新节末尾一并 promote;③ **`luac -l` 逐指令对照法**——面对 codegen bug 不知落哪个 pass 时,让 PUC luac 出参考字节码与 wangshu dump 逐指令 diff,操作数级 diff 直接指出发射分支(本轮 `RETURN 1 2` vs `RETURN 2 2` 一眼锁定),暂留反思;④ **unreproducible-crasher-triage corpus 位置判据首次正向消费**——轻量 budget-bounded corpus 走 `testdata/fuzz/` 判据首次真的跑通,已进 [[unreproducible-crasher-triage]] guide 位置判据段落作实证。**触发场景**:差分 fuzz 报 P1-vs-auto / P3-vs-P4 / amd64-vs-arm64 分歧、归因升层 / 后端路径之前(先问 PUC oracle 拿真值)/ 前端 codegen review 见 `base := fs.freereg` 或等价水位缓存模式(capture 与 use 之间任何 `freeExp` / `exp2NextReg` 都是红旗)/ 怀疑前端 codegen bug 但不知落哪个 pass(`luac -l` 对同源代码出参考字节码)/ fuzz corpus 入库位置抉择(轻量 + budget-bounded 走 `testdata/fuzz/`,重 workload 走 `test/regression/`)。
- `memory/reflections/2026-07-12-cgo-oracle-fuzz-round.md` — cgo-embedded PUC 5.1.5 oracle + `FuzzOracleDiff` 差分 fuzz 一轮扫出 35 处 P1 vs PUC 语义分歧全修复轮(2026-07-11~07-12,分支 `feat/cgo-oracle-fuzz`,PR #128,37 commits):新增 `internal/oracle` 包内嵌官方 Lua 5.1.5 源码(`_lua515/` 下划线目录 go tool 默认忽略 + 走上游 `etc/all.c` 惯例单编译单元 `luaall_c` + build tag `wangshu_oracle_cgo && cgo` 门控,默认 build 仍零 cgo),shim 三道防护(字节口径 allocator 上限 + `LUA_MASKCOUNT` 指令 hook 协程继承 + 输出上限);共享 Lua prelude 两端都跑作对称性核心不变式(捕获 print/io.write / 决定性 stub / 极限错误经 pcall-xpcall-resume 再抛 / 拒绝二进制 chunk / pattern 检查 / **在活 wangshu State 上枚举 globals 白名单使 stdlib 增长自动同步** / 有序 pairs/next/foreach);`FuzzOracleDiff` skip 规则只对资源上限与实现常量(语法深度 / 栈溢出 / 复杂度天花板)差异返回 LIMIT 不可比即 skip,其余场景要求结果类等价 + 类型前缀锚定的地址规范化后逐字节相等 + NaN 符号规范化。**35 处分歧修复覆盖 stdlib/词法/前端 codegen/值语义四域**:string 库数字强制走 `luaL_checklstring` / `__tostring` 原样透传 / coroutine 错误类型名 `"thread"` / 未知转义直接透传字面(5.1 而非 5.2)/ `string.upper`/`lower` 逐字节 ASCII(Go `strings.ToUpper` 会把二进制字符串非法字节替 U+FFFD 损内容)/ `string.format` 校准(拒 `%F` / `%u`/`%x`/`%o` 无符号 cast / `%c`/`%s` 走 C sprintf NUL + strlen 语义 / `scanformat` 硬约束 flags ≤ 5、width/precision ≤ 2 位 / `%s` 忽略 `'0'` / 无符号 verb 忽略 `' '`/`'+'`)/ 常量折叠拒 div/mod-by-zero 与 NaN 结果 + ±0 常量槽先到先占 / 算术 RK 物化顺序先 o2 后 o1 / **`tonumber` 重写走 C99 `strtod` 接受面**(hex float / `inf`/`nan` 词 / 溢出饱和到 ±inf / hex 整数 fallback 走 `strtoul` 的 endptr 契约保留 `'x'` 停位)/ 表构造器字段**按源码顺序**赋值(重构 `TableExpr.Items`,旧两遍 codegen 覆盖顺序反了)/ 字符串真走**共享 metatable**(`{__index=string}`,`getmetatable("")` 返回它,读写两侧都经 `metaFieldOfValue` 使包括 `__newindex` 在内的所有 metamethod 真的触发)/ `load` 只吃 reader 函数(字符串接受面是 5.2)/ `os.time` 表协议 / `unpack` 范围实参经 C `int` 收窄(NaN → int64 min → 低 32 bit 为 0)。**六条教训**:① 参照系一升级,潜伏分歧成片浮出(此前只有子进程 `fork-lua5.1` 差分 + 手写脚本;in-process byte-equal oracle 装上后同一轮 fuzz+argsweep 抛出 35 处此前不被覆盖的分歧,预算按覆盖面而非已知 bug 数估)/ ② **PUC 语义由 C 代码定义不由手册定义,直接读 `lstrlib.c`/`lobject.c`/`llex.c` 比探针快也准**(sprintf 边界经宿主 libc,strtod/strtoul endptr 契约都在源码里明写;探针试常数半小时的边界读源码五分钟落定)/ ③ State 级长持 GCRef 必须接根再一次(stringMeta 表被 State 字段持有,`TestGCStress_AllocHeavy` 立刻 UAF,加入 `visitExtraRefs`,GC stress 测试在合并前兜底)/ ④ 测试 harness 静默 skip / 静默 pipe 关闭是持续雷区(`go-fuzz.sh` 探针把编译失败静默 skip 让 oracle-smoke 长期拿绿灯不做事 + `| grep -q` 因 SIGPIPE 提早关 pipe 使循环内第二次及以后迭代的 `go test` 全挂;探针默认写法「失败即致命 + 必需目标断言 + 先落盘再 grep 不写 `| grep -q` + 循环内探针加 `</dev/null`」)/ ⑤ review 抓到的四类共性——「新写的 fast path 是不是把某个已存在的 slow path 义务落下了」(模板级 vs 源码级测试义务不同 / 语义要经统一入口而不是绕过 / 探针失败当红旗 / 规范化要有锚点)/ ⑥ **argsweep 与 fuzz 互补**(fuzz 走随机 mutation 抓值层边界,argsweep 按 stdlib 每函数枚举退化实参形式抓形式层覆盖;本轮 35 处里 fuzz 抓 20 处 + argsweep 抓 15 处)。**Promotion 决策**:教训 1 首次样本暂留观察(下一轮再上更强 oracle 再实证可开独立小 guide 或并入 [[unreproducible-crasher-triage]] 决定诊断投入邻接段);**教训 2 已落 [[cross-backend-semantic-fix-sweep]]「PUC 语义源码定义」新节**(differential fuzz vs PUC 的具体手法,与该 guide 已有的跨后端/跨通道语义扫描同域);教训 3 是 [[public-api-incremental-delivery]] guide + [[embedding-contract]] 不变式段既定条款的又一实证不升 guide(memory 内反引);教训 4 接近阈值,下一轮再实证可开独立小 guide「shell 探针默认写法 checklist」或作 [[prove-the-path-under-test]] §6/§7 的 shell 层对偶;教训 5/6 首次样本暂留观察。**触发场景**:装新 oracle / 新差分维度前(按覆盖面估预算)、与 PUC 5.1.5 byte-equal 分歧调查(先读 `_lua515/` 对应 C 实现再决定修法)、在 State 上加长持字段(同 PR 加 `visitExtraRefs` 靠 `TestGCStress_*` 兜底)、写 shell 探针 / 循环内跑 `go test`(避 `| grep -q` + 循环内加 `</dev/null` + 探针失败致命 + 必需目标断言)、review 一个新加的 fast path / inline 快路径(按被绕过的既有慢路径义务逐条对)、扫某个大接受面(stdlib / 前端 codegen)时(fuzz + argsweep 一起上)。
- `memory/reflections/2026-07-12-i123-deadline-to-package-timeout-round.md` — i123 回归测试从自建 per-run wall-clock deadline(5s→30s 演进后再被 p4/macos-latest 打穿,PR #129)转向 go test 包级 `-timeout` 兜底:测试要抓「#123 类段内无限循环永不返回」是量纲错配——任何有限秒数都跟共享 runner 的可变速度对赌,harness 自带 timeout 触发时还带全体 goroutine 栈 dump 严格更优;自建 deadline 只在超时后测试还需继续执行时才有正当理由。`test/regression/forloop_nan_limit_test.go` 的 `mustFinish` 10s 模式余量约 1000× 暂不动,作观察项。反问「若下一档 runner 再打穿这条路径能收敛吗」是打破「调常量」局部锁定的杠杆。三教训均首次样本暂留观察,教训 1(裁判机制侧「测试自身是否称职」的元问题)后续再实证可开新 guide `test-timeout-mechanism-selection` 或并入 [[prove-the-path-under-test]] 作裁判侧新节。**触发场景**:测试因 CI runner 慢触发内建 wall-clock 报「did not terminate within Ns」时(先问失败模式是「永不返回」还是「慢过阈值」)、要不要同步替换全仓一样的 `mustFinish` 类模式时(按每处余量分档)、发现自己在「调常量以修 flake」的第 N 轮时(反问下一档打穿后还剩什么路径)。
- `memory/reflections/2026-07-13-nightly-concat-oom-and-format-hash-round.md` — 2026-07-13 nightly 巡检轮(issue #127/#130 + PR #131 自我暴露的 `%#X` 分歧):#127(p3)与 #130(p4)是同根因 quadratic concat 风暴(`out=out..f(i)` 单次执行冲到 arena 2 GiB / Go 堆 13 GiB → 4 并行 worker 进程级 kill,step budget 来不及先触发),精确重放全部干净,符合 [[unreproducible-crasher-triage]] 分诊判据;修法给 FuzzCompileRun / FuzzAutoPromote / FuzzP4ForceAllPromote 三个老 harness 补 `MaxArenaBytes: 64 << 20`(FuzzOracleDiff 上周随 PR #128 一同上线时已带,同族防护不对称),差分豁免从 budget-only 扩为资源限制类 `isResourceLimitErr`(匹配 budget 或 `arena:` 内部 panic)。PR #131 自己的 30s oracle-smoke 立刻撞出 `print(string.format("%#X",0))` 分歧(Go `fmt` 打 `0X0`、C printf 省零值前缀打 `0`),是 stringFnFormat unsigned 分支第三次被打穿(前两次:`%100X` 宽度、`% 00X0` 忽略旗标);把「改写 spec 后委托 `fmt.Sprintf`」换成手写 `cUnsignedFormat` 直接实现 C99 语义(自解析 flags/width/precision + strconv digits + 精度零扩展 + '#' 前缀规则 + C 补零规则),41 用例与本机 PUC lua5.1 逐字节相等,90s oracle fuzz 冒烟(21.6 万 execs)干净,bot APPROVE。**三教训**:① **头条「改写输入再委托宿主标准库适配外部 C 语义」是不收敛补丁路径**——Go `fmt` 与 C `printf` 在 '#' 上的分歧不止一处(零值前缀省略 / `%#08X` 补零内侧 / `%#o` 强制首位 0 / `%#.0o` 零值 C 打 "0" Go 打 ""),分歧集合开放,手写 renderer 把语义收敛为封闭集合(C99 一页规则),与 [[2026-07-12-i123-deadline-to-package-timeout-round]] 「调常量修 flake 不收敛 → 换机制」同族第 2 实例,再现一次建议升 guide,暂名 `non-converging-patch-path-detection`;② **同族 harness 防护不对称**——PR #128 给 FuzzOracleDiff 加 MaxArenaBytes 帽时没横向扫三个老 harness,#127/#130 两个 nightly crasher 就是这个欠账;与 [[cross-backend-semantic-fix-sweep]] 同构但对象是测试 harness(修/防的心理边界停在「当前文件」,真实边界是「全部同类站点」),作为该 guide 的 harness 侧对偶延伸候选(首次样本暂留);③ **资源限制类差分豁免按「类」豁免而非按「具体错误」豁免**——tier 间分配点不同,同 input 可能一条腿撞 budget、另一条腿撞 arena 帽,豁免只认 budget 字符串会把资源竞速误判为 tier 分歧,首次样本暂留。**后记(同日,PR #132)**:巡检统计连续轮数时发现两轮 nightly p1 腿 conclusion=cancelled(非 failure 故无 triage issue,静默损失)——oracle-diff 步骤只传 `wangshu_oracle_cgo` tag,而 **build tag 是加法**,自动发现型 `go-fuzz.sh` 把该 tag 下可编的全部 5 个目标各按 45m 重跑,冲破 350m job timeout;修法给脚本加第三参数 `target_regex`(显式过滤零匹配 exit 1)+ nightly 与 PR 门禁 oracle 腿都传 `FuzzOracleDiff`。教训:「按 tag 选目标」的意图必须用显式目标过滤表达,tag 不会隐式缩小目标集;cancelled 与 failure 同样要点开看原因。**触发场景**:同一个「改写输入 + 委托宿主标准库」的 site 被外部 C 语义打穿第 2 次时(先问路径能否收敛,答案否就换手写实现)、给某个 fuzz harness 加防护 / 上限帽时(横向问一句「兄弟 harness 有没有同样的暴露面」)、写差分豁免规则时(资源类按「资源尽头族」认而不是按具体错误串认)、tag 门控调用「自动发现全部目标」类脚本时(tag 是加法,显式传目标过滤)、巡检看到 conclusion=cancelled 的 run 时(与 failure 同样点开查原因)。
- `memory/reflections/2026-07-14-issue133-argerror-caller-name-round.md` — 2026-07-14 定时巡检补跑轮(issue #133,PR #134):nightly 差分 fuzz 种子 `coroutine.create(coroutine.resume)` 牵出两个独立根因——① **luaL_argerror 函数名派生规则整体分歧**:PUC 5.1.5 的 `bad argument #N to 'name'` 中 name 来自【调用方的调用点】(ldebug.c `getfuncname` → `getobjname` 对 CALL/TAILCALL/TFORLOOP 的 A 操作数 symbexec),不是被调函数身份;推论:别名报别名(`local r=string.rep; r(nil)` 报 'r')、method 调用 self 不计入 #N 且减到 0 时改报 `calling 'X' on bad self`、TFORLOOP 站点报 `"(for generator)"`、纯 C-to-C 边界(pcall 直调/sort 比较器/元方法)保持 `'?'`;wangshu 之前每个 stdlib 站点硬编码被调名。修法:结构化 `NewArgError(narg, extra)` + `State.resolveArgError` 在 Lua 调用边界统一改写(doCall/doTailCall/TFORLOOP,解释器与 gibbous 对称),`callLuaFromHost` wrapper 在宿主边界冻结 `'?'`,~84 个 stdlib 站点迁移;② **coroutine.create 拒绝 C 函数**(luaB_cocreate 的 lua_iscfunction 检查)。验证:~70 条探针逐字节等 PUC、三 build 全绿、fuzz 三连冒烟干净、difftest errmsg 语料 +7 条 `argerr_*`、corpus 入库。**三教训**:① 分歧涉及「派生逻辑」而非「输出格式」时默认背后是面级子系统,先探针测绘完整行为面再一次性实现(已进 [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义」节作第二个面级实例);② 错误消息依赖抛出点拿不到的上下文时,用「结构化错误字段 + 拥有上下文的边界层统一改写 + 解析权冻结」而不是把上下文穿透传给每个抛出点(随 ① 一并入 guide);③ 跨行结构机械迁移不要用单发多行正则,先收窄到单行可判定子集(首次样本暂留)。**触发场景**:与 PUC 分歧涉及名字/计数/回退派生逻辑时、错误消息需要调用方上下文时、脚本机械迁移几十个站点时看这篇。
- `memory/reflections/2026-07-14-issue137-cmp-const-order-round.md` — 2026-07-14 定时巡检补跑轮之一(issue #137,PR #140):nightly oracle fuzz(corpus `9e5e75e5c04112a2`:`print(0*-0~=0%0,0)`)报的真实分歧,尾部字面 0 打成 "0"、PUC 打 "-0"。根因是**常量表登记顺序**:PUC `luaK_infix` 对比较运算符走 `default` 分支、**无条件在解析右子树之前** `exp2RK` 左操作数(arith 分支才有 `if (!isnumeral(v))` 延迟,因为 arith 要留给 constfolding),于是 `0*-0` 折叠出的 `-0` 先占共享零槽(±0 去重按数值相等、先到先得、保留符号),后续字面 `0` 复用它 → 打 "-0";wangshu 的 `exprCompare` 错误地对 numeral 左操作数也延迟到右子之后,让 `0%0` 的 `+0` 抢先登记。修法:`exprCompare` 无条件在解析右子树前物化左操作数,镜像 luaK_infix;swap/副作用序/非 numeral 路径不受影响。同轮附带 #135(p3 auto 不可复现资源耗尽,corpus 入库)。**两教训**:① 「先物化谁」这类求值顺序在下游有**先到先得的去重/唯一化**(常量表 ±0 保留首符号)时会改变可观察输出,不是纯内部细节——顺序无关的直觉遇到带副作用的共享结构就失效,必须逐字节对齐 PUC(本仓 neg_zero 常量去重同族第 2 实例,首次以「顺序敏感」维度出现,暂留);② 前端 codegen 顺序类分歧权威是 lcode.c 的 `luaK_infix`/`luaK_posfix` 分支结构,arith 与 comparison 的 isnumeral 非对称必须逐条照抄不能想当然统一(是 [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义」原则在前端求值顺序维度的应用,memory 内反引)。**触发场景**:改前端 codegen「两个操作数先算哪个」的顺序时(先问下游有无先到先得去重结构)、与 PUC 分歧表现为「顺序/先后」而非「值算错」时看这篇。
- `memory/reflections/2026-07-14-issue136-callvoid-misaccept-round.md` — 2026-07-14 定时巡检补跑轮之一(issue #136,PR #140):nightly P4 force-all fuzz(corpus `933229fef53684cc`)报的真实误编译。长度 6 的 proto `GETUPVAL; CALL 0 1 2; SETGLOBAL 0; LOADK 0; RETURN 0 2` 被 `analyzeCallVoidForm`(compiler.go)的长度 6 `Code[1]==CALL` 分支误接受成「0 参 1 返 getter」,该分支只校验尾部隐式 RETURN、**从不检查 CALL 与 RETURN 之间的指令**,于是 SETGLOBAL 和覆盖返回槽的 LOADK 被静默丢弃,返回了 CALL 结果(make 闭包)而非 LOADK 的 0;破坏只在 proto **第二次进入**时显现(第一次寄存器状态恰好看起来对),且返回值须是堆引用(闭包)才可见。修法:CALL 与 RETURN 之间只允许多返值 MOVE 拷贝,其余任何 op 拒绝该形态;clC<3 分支额外断言无间隙(review 加固);合法 getter/setter/带参形态仍升层且逐字节一致。**三教训**:① spec-template 形态匹配器必须逐条覆盖 proto 的每个 op——只按首尾锚点接受、不校验中间指令会静默吞掉真实语义(接受一个形态 = 承诺它每条指令都被忠实翻译,未覆盖的 op 是空头承诺),候选并入 [[backend-capability-vs-profitability]] 作「接受侧穷尽性」对偶(首次样本暂留);② 「第二次进入才错」类误编译要求语料反复调用被测 proto,单次调用是结构盲区([[prove-the-path-under-test]] 家族又一实证,p4 difftest 语料放 20 次循环);③ gate 二分是错误方向时(三个 gate 全关 bug 仍在)立刻转「精确最小复现 + 沿分派链逐分支排查」(首次样本暂留)。**触发场景**:写/改「按 proto 形状接受快路径」的 spec-template 匹配器时、给「只在第二次进入才错」类 tier 误编译写回归语料时、gate 二分定位误编译但全关仍复现时看这篇。
- `memory/reflections/2026-07-14-p3-loop-stepbudget-round.md` — 2026-07-14 巡检补跑轮第 4 个 crasher(PR #140):PR 自己的 CI p3 fuzz-smoke 撞出 `FuzzAutoPromote/bb525447c652d8d9`(`for i=0,5/0 do X=0*i end` 无限循环),master 上就存在的真实 bug。根因:**P3 wasm 升层后的循环回边不计 step budget**——回边只 inline 检 `gcPending`(纯 GC 用),`host.Safepoint` 只做 GC 不计费,step budget 无 async producer ⟹ 全 inline 的死循环在解释器立即抛 `instruction budget exceeded`、升 P3 却永挂。这是 **issue #102(P4 native loop fuel)的 P3 对偶**。修法照抄 P4 `loopFuel`:加 `loopBudget` linear-memory fuel 字,回边 inline 自减 + 归零(或 gcPending 置位)才跨层 `Safepoint` 计费/重填/超额 raise;**无 budget 时重填大额** `loopFuelUnlimited`(1<<30)使稳态循环几乎不跨层(实测 1e6 迭代 **0 次**跨层,白盒 `SafepointCalls()` 断言)。顺带认清原判「不可复现资源耗尽」的 #135(`sum(555555520)` 有限但巨大)其实是同一 bug 的温和实例,本修一并真修好。**两教训**:① 「段内无抢占点 → 计费缺口」是物理事实,P4 修过(#102)后必须横向查 P3 wasm——step budget 是解释器在回边维护、每个加速层都要各自复刻的不变量,修一个 tier 的计费缺口要同 PR 查所有 tier(候选并入 [[cross-backend-semantic-fix-sweep]] 作「抢占/计费不变量跨 tier 对称」新实例族,与语义扫描并列,首次样本暂留);② 判「不可复现」前要跑到真正暴露 bug 的 tier/模式——#135 单 Run 解释器重放干净≠不可复现,bug 在 auto/P3 才暴露(候选补入 [[unreproducible-crasher-triage]] 重放步骤,首次样本暂留)。**触发场景**:给某加速层修「段内绕过 host 的计费/抢占缺口」时(横向查所有 tier)、给加速层循环加抢占计费时(inline fuel 自减 + 归零才跨层 + 无 budget unlimited 重填保零跨层)、判差分 fuzz crasher 为不可复现前(先用报告它的 tier/模式重放)看这篇。
- `memory/reflections/2026-07-15-issue143-pj3-loopfuel-round.md` — issue #143/#144 修复轮(PR #146):P4 PJ3 spec FORLOOP 模板 loopFuel 回边计费(段内无抢占点 → step-budget 计费缺口不变式家族第 3 实例,#102 P4 native → #135 P3 wasm → #143 P4 PJ3 spec template)。修法镜像 #102 段内 resume 机制,spill xmm/d 寄存器到 jitCtx scratch 字段,amd64/arm64 对称。#144 按不可复现 crasher 处置走串行回归测试。三教训:① 计费缺口查同 tier 内独立 emit 通道([[cross-backend-semantic-fix-sweep]] 后端 × 通道维度强化);② xmm 寄存器 spill/reload 是 resume 模式的新维度;③ trampoline 协议升级为非显而易见的前置依赖。三条均首次样本暂留。**触发场景**:给某加速层的独立 emit 通道(spec template / per-op 通道)修计费缺口时(教训 1)、给寄存器驻留状态跨 host 往返时(教训 2)、给现有模板路径加新 exit-reason 协议时(教训 3)。
- `memory/reflections/2026-07-14-comment-translation-tokenizer-gate-round.md` — 全仓中文注释译英轮(PR #141,分支 `chore/translate-comments-to-english`):单会话把 311 个源文件(305 `.go` + 6 `.s`)的中文代码注释译成英文,严格只动注释、字符串字面量与代码零改动,CI 全绿 + bot APPROVE 无任何问题。**头条教训**:大规模「只改某一类 token」的机械改动,验证不能靠 `grep -P '//.*[CJK]'` 抽查(发现不了字符串字面量越界、且会把 `·`/`÷`/全角标点误判),要用 `go/scanner` 逐 token 比对工作树与 master——只允许 `COMMENT` token 变化,其余 token 流(尤其 `STRING`)必须逐字符 == master,一次性证明「只动了注释」+「字符串字面量零改动」。本轮正是靠它定位并还原了 19 个文件里 289 处被并行子代理越界翻译的 `t.Skip`/`t.Errorf`/Lua 源码 fixture 字符串字面量(difftest/oracle 逐字节比对的错误消息改了就是改行为)。是 [[prove-the-path-under-test]]「绿色 ≠ 在测你以为在测的」在**改动范围**维度的对偶(CI 全绿只证行为没坏、不证「只动了该动的」),首次样本暂留观察。另:注释中文最终判据用 UTF-8 感知 `\p{Han}` + 显式全角标点区间(`\x{3000}-\x{303f}`/`\x{ff00}-\x{ffef}`)而非 CJK 表意区 + 扫 U+FFFD;范围边界按 CLAUDE.md 字面收窄到 `.go`/`.s`(Makefile/脚本/字符串字面量默认不在内);flaky 子代理收尾靠主助理亲扫。**触发场景**:做「批量只改某一类 token」的机械改动时(动手前想好 tokenizer diff against baseline)、派并行子代理做机械翻译/改写时(预期部分空转 + 部分越界,收尾要有不依赖子代理自觉的权威闸门)、判注释是否还有中文时(UTF-8 感知 `\p{Han}` + 全角标点区间 + U+FFFD)看这篇。
- `memory/reflections/2026-07-18-issue155-158-nightly-crasher-round.md` — 2026-07-18 nightly crasher 巡检轮(#155/#156/#157/#158/#159,PR #160):两个真 bug + 三个不可复现。**#155**(P4 seg2seg 混合宽度 RETURN miscompile):seg2seg 依赖 `emitReturnDualSemantics` 段内 teardown,无法像解释器 `doReturn` 那样把不足的返回值补 nil 到 caller 期望的 C-1 个,callee 混合宽度 RETURN 时 0 值路径让 caller 读到陈旧寄存器;修法 `populateCallIC` 的 NeverExits 再加 `seg2SegRetWidthOK` 要求(`ProtoSeg2SegRetCount` 只统计 CFG 可达块内 RETURN 宽度——luac 每个显式 return 后追加的死尾 `RETURN 0 1` 会毒化直接扫 code 数组的静态分析,教训 2)。**本轮最大教训(教训 1,已 promote 进 [[cross-backend-semantic-fix-sweep]] 新节)**:给 `bridge.NativeSegAddrer` 这种靠运行时类型断言满足的接口加方法时只补 amd64 漏 arm64 镜像,断言在 arm64 上静默失败使 seg2seg 整体静默失效(结果仍正确、命中数归零),本地 amd64 全绿,只有 arm64 CI leg 的白盒命中数断言(hits=0)抓到,修复 commit 5f76472。**#158**(stdlib 真 bug):`string.format` `%X/%x/%u/%o` 对 >= 2^63 的值,PUC 走 x86-64 gcc 的 `(unsigned long long)(double)` 降级序列而 Go `uint64(int64(f))` 饱和,`stringlib.go` 加 `cUnsignedCast` 按参考平台(x86-64 gcc)行为手写模拟 + gcc -O2 探针逐点验证,arm64 FCVTZU 分岔属 C UB 区从 difftest corners 显式豁免(教训 3)。**#156/#157/#159**:不可复现 concat storm 家族,按 [[unreproducible-crasher-triage]] 处置;新信号 = `GOMEMLIMIT=512MiB`(PR #154)没接住 41.5M execs 处的静默死亡,下一层硬化方向是 harness 按 seed 记 wall-clock(教训 4,已补进该 guide 硬化层级现状)。**触发场景**:给「靠运行时类型断言满足」的接口加方法时(grep 全部按 build tag 分文件的实现 struct,双架构 CI 全绿才算完成)、写按 RETURN / 终结指令宽度做 proto 级静态分析时(先过 buildCFG + reachableBlocks 可达性)、与 PUC 分歧落在 C 实现定义/UB 区且 C 侧跨架构不一致时(按参考平台行为手写模拟 + difftest corners 显式豁免)、nightly concat storm 家族再报不可复现 crasher 时(检查当前诊断硬化层级)看这篇。
- `memory/reflections/2026-07-18-review-p3-loopfuel-rearm-round.md` — 2026-07-18 外部增量 code review 修复轮(PR #161,分支 `fix/review-p3-loopfuel-rearm`):review 报告指出两个问题,均先写定向验证确认属实、再经用户 /goal 指令修复。**问题 1(P3 loop fuel 不重新武装)**:`Safepoint` 在无预算时把 wasm 线性内存的 fuel 字填成 `loopFuelUnlimited`(1<<30),`SetStepBudget`/`SetCancelHook` 只改 Go 侧字段,「先无预算跑热 P3 循环、再开预算」的顺序下新预算要等约十亿次 back-edge(回边)后才被查询;修法(commit 2945b57)在 `enterGibbous` 镜像 P4 `RefreshJitCtxAddrs` 的 armed-state transition 处理(武装方向重置 fuel 字且不把武装前消耗计入新预算,解除方向恢复 unlimited 保快路径),回归测试四方向且 arm/cancel 两测在无修复代码上确实失败。**问题 2(GOMEMLIMIT 机制性描述错误)**:`go-fuzz.sh` 注释与 triage guide 声称 GOMEMLIMIT 会触发带栈 Go OOM fatal,核对官方文档为纯软限制、运行时任何情况下不会因它主动 fatal;错误认知从 #123 轮起传播三周、跨四个载体,两处已改写(commits bc29cd5/40979a1)。**三教训**:① 复现「机制没生效」的指控时复现测试自己也要 prove-the-path——第一版 FORLOOP 复现走了解释器路径、budget 生效差点把真 bug 判为不存在,换 `test/regression/loop_stepbudget_p3_test.go` 已证明段内 back-edge 的写法才稳定复现(**已 promote 进 [[prove-the-path-under-test]] §7.1「复现侧」**);② host-API 调用时序类 bug(arm-after-warm)用定向回归测试而非硬造 fuzz target——fuzz 探索空间是脚本内容,调用时序不在其中(首次样本暂留);③ 机制性声称写进注释/文档时须附官方文档或实验佐证,引用作推理链关键环节前花一分钟 `go doc` 核对原文——文档库自引用会让错误认知越滚越可信(首次样本暂留)。**附记(合入前第二轮增量 review,commits 961f665/000e7fb/a171457)**:第一轮修复自身有盲区——用聚合布尔 `stepBudget > 0 || ctx != nil` 检测武装转换只看得到聚合 false↔true 边沿,「ctx 已武装、随后再开 budget」两头都是 armed=true 检测失明,ctx-only 阶段的 partial drain 被计入全新预算(P3/P4 双 tier 探针证实);修法 `State.budgetGen` 代际计数,SetStepBudget/SetCancelHook 各自 bump,P3 enterGibbous 与 P4 RefreshJitCtxAddrs 比对代际、变更时无计费丢弃旧窗口(教训 D:配置变更检测用代际计数不用聚合布尔——布尔编码不了「发生过变化」);且第一轮的 cancel 回归测试是假绿(warm 与被测用了不同 Program,被测 Run 在解释器 frame-entry preempt 就返回、从没进 P3,删掉修复照样通过)——恰好违反本反思正文教训 A / guide §7.1 自己刚写的规则,重写为同一 Program + 全局 knob + mid-run cancel + SafepointCalls delta>0 + 及时性 <5s 双断言;stale billing 对 fuel 窗口相位敏感(10 个相位只 2 个触发),测试改为扫描整个窗口宽度(教训 E:相位敏感 bug 的回归测试扫全窗口不赌单点)。**附记二(合入前第三轮增量 review,commit 16b88d6)**:increment-3 的代际方案矫枉过正——`SetCancelHook` 也 bump 同一个 `budgetGen`,消费方把任何代际变化都当「预算所有权变更」无计费丢弃旧 fuel 窗口,但「budget 持续武装、只是 ctx set/replace/remove」时 drain 前后属于同一活跃预算(`SetContext` 不重置 `stepUsed`),每次 toggle 白送一个未计费 quantum 窗口,交替 SetContext/RemoveContext 可持续绕过步数配额(budget=100 十轮 ~400 back-edge 从不报错);修法 budgetGen 收窄为只由 SetStepBudget bump(与 stepUsed reset 对齐),ctx 变化只影响窗口大小、消费方从 live armed 状态推导(教训 F,教训 D 的修正:决定「丢弃 vs 结算」的检测信号粒度必须恰好是所有权本身——过窄漏检真变更、过宽把非所有权变化也当变更,同一机制连续两轮踩了两头);increment-3 的测试只测了「ctx 先武装、budget 后加」单方向,反方向没测所以矫枉过正无人发觉(教训 G:配对状态机 bug 的回归测试要把状态转换矩阵两个方向都列出来测);新增双 tier CtxToggle 回归测试(10 轮 toggle + 必须 raise + Safepoint 真穿越 + rounds<=5),对 increment-3 版本验证过失败。**触发场景**:收到「某机制没生效」的指控要写复现测试时(教训 A)、判断 bug 该配定向回归测试还是 fuzz target 时(教训 B)、在注释/文档写「某 runtime 机制会做 X」或引用既有机制性声称时(教训 C)、给 State 级开关(budget/cancel-hook/tier 开关类)加运行期切换语义时(凡「加速层把 Go 侧配置镜像成段内快照」的地方,配置变更点都必须有对应的镜像刷新)、给「多个独立配置项汇成一个观察量」的机制写变更检测时(教训 D:代际计数)、给相位敏感的触发条件写回归测试时(教训 E:扫全窗口)、给「丢弃 vs 结算」类判定设计检测信号时(教训 F:信号编码范围与所有权精确对齐,过窄过宽两头都出漏洞)、修复配对状态机 bug 补回归测试时(教训 G:状态转换矩阵双向都测)看这篇。
- `memory/reflections/2026-07-19-issue163-tostring-meta-round.md` — 2026-07-19 nightly crasher 巡检轮(#163/#162,PR #164):一个真实 stdlib 语义 bug + 一个不可复现。**#163**(非函数 `__tostring` 元字段):PUC 的 `luaL_callmeta` 把 `__tostring` 字段推栈后直接 `lua_call`,语义由调用机制决定——非可调用值报 "attempt to call a X value"、带 `__call` 的表被正常调用;wangshu 的 `valueToStringMeta` 用 `Tag(h) == TagFunction` 做类型过滤,非函数一律静默回退地址输出,verdict class 分歧;修法一行(commit dc86490):任意非 nil 都经 `ProtectedCallDirect` 调用,把决定权还给既有调用机制。**#162**:不可复现 concat storm 家族第 10 例(worker 12.4M execs 处静默死、`GOMEMLIMIT=512MiB` 在场没接住、本地重放 4.6s 干净),按 [[unreproducible-crasher-triage]] 处置不入 corpus;新观察 = minimized 输入本身往往不是死因(重放干净的更可能解释是 4 parallel worker 叠加峰值或 minimization 前的变体),per-seed wall-clock 硬化优先级上升、待用户决定是否立项。**两教训**:① PUC 元方法语义常是「把字段当可调用的东西直接调用、错误留给调用机制报」,类型过滤会同时吞掉应报错路径 + 误杀 `__call` 表路径(候选并入 [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义」节,首次样本暂留);② 写共享 string metatable 的 difftest corner 必须探完恢复(`__tostring = nil`),否则污染后续 corner 的 harness 自身 tostring(首次成文暂留)。**触发场景**:实现或审查元方法字段消费点(`__tostring`/`__index` 函数分支/`__call` 链)时、元方法相关 oracle diff fuzz 报 verdict class 分歧时、写要改共享 metatable 的 difftest corner 时、concat storm 家族再报不可复现 crasher 时看这篇。
- `memory/reflections/2026-07-19-fuzz-worker-forensics-round.md` — 2026-07-19 fuzz worker 取证设施轮(PR #165,分支 `feat/fuzz-worker-forensics`,用户主动立项:彻底解决 concat storm 家族 10 例「查不到死因」的问题)。**破局线索(头条教训)**:动手前两个几分钟量级的检查直接改变问题性质——① 家族 10 例的死亡签名全是 `exit status 2`,这是 Go runtime 自身 fatal 的退出码(OS OOM killer 的 SIGKILL 会报 `signal: killed`),worker 死时几乎必然打印了完整栈迹;② 查 internal/fuzz 源码,coordinator 起 worker 时 `cmd.Stderr` 留 nil,os/exec 把它接到 /dev/null——尸检报告每次都写了、每次都被扔掉;「查不到死因」实为「输出没接住」,此前多轮全在复现矩阵与 GOMEMLIMIT 上打转,最便宜的检查被排在了最后(**已 promote 进 [[unreproducible-crasher-triage]]「静默死亡先做两步分类」新节**,与「版本核对先行」同量级)。**交付两个机制**:机制 A 尸检(`fuzz_forensics_test.go` TestMain 检测 `-test.fuzzworker`,fd 2 dup 到 `fuzz-forensics/worker-<pid>-stderr.log` + `SetTraceback("all")`,linux 用 `Dup3`/darwin 用 `Dup2`,合成 fatal 探针验证栈迹真进日志——prove-the-path 应用于诊断设施本身)+ 机制 B 飞行记录仪(每次 fuzz 回调把 seq/时间戳/target/输入以单次 `WriteAt` 覆盖写进定长 per-PID 记录,恢复不进任何 corpus 的「在飞输入」,是它取代了此前构想的 per-seed wall-clock;初始交付为 8KiB、回调开头记录、共享目录,外部审查轮修正为 20KiB、各 target 长度/NUL 门后记录、16KiB 最大 gated 输入逐字节可恢复、热路径 0 alloc、按 target 子目录隔离——见该反思的修正附记)+ 配套(go-fuzz.sh 清理与失败 dump / nightly artifact `**/fuzz-forensics/**` / .gitignore)(**机制 A/B 均已写进 [[unreproducible-crasher-triage]] 诊断硬化第三层**)。**教训 2**:取证设施必须 best-effort(错误全吞,绝不把健康的 fuzz run 变成失败)且不能给被诊断的问题添加压力——review 抓出每 exec 分配 8KiB buffer 造成 GC churn(正在调查的恰是内存压力),改包级复用(commit `6a189eb`),首次成文暂留。**展望**:下一次复发即有完整栈迹与在飞输入;若 stderr 仍只有 header,则死因在 Go runtime 之外,同样是决定性排除信息。**触发场景**:fuzz / CI / 生产报「进程无声消失」时(先分类退出方式再查 stdio 接线,最后才撒复现矩阵)、给低频复发问题设计诊断硬化时、给 fuzz / 测试基础设施加常驻观测代码时(错误全吞 + 非目标模式零开销 + 热路径不引入分配)、concat storm 家族下次复发时(先看 artifact 里的 worker stderr 日志与飞行记录)看这篇。
- `memory/reflections/2026-07-20-concat-storm-root-cause-round.md` — 2026-07-20 concat storm 家族(#123-#167)根因定性突破轮(PR #168,修复 nightly p3 crasher #166/#167):破局全靠 PR #165 取证设施——取证上线后家族第一次复发,worker stderr 抓到完整栈迹 `panic: deadlocked!` + goroutine 卡在 `Intern → doConcat → executeLoop`,一步定性死因是 Go fuzz 的 **per-input 10 秒看门狗**(`internal/fuzz/worker.go` `time.AfterFunc(10s, panic)`)——**是 CPU wall-clock 撞看门狗,不是内存 OOM**;历轮误判为内存(加 GOMEMLIMIT / 降 arena cap 都不奏效、最小化 corpus 本地重放永远干净)全因死因根本不在内存维度。根因:`preempt()` 每指令边界只把 stepUsed 加 1,不计 CONCAT / Intern 的字节工作量,`for i=1,N do glob=cat(i) end`(cat 内 `return "<~15KB 字面量>"..i`)每迭代扣约 2 步却拷贝约 15KB,单次 Run 约 2.7s、harness 每输入 4×run≈11s > 10s 看门狗;落盘的是最小化后的轻输入故本地重放干净。修复:共享 `doConcat` 调 `chargeBulkWork(len)` 按 `len>>6`(1 步/64B)记账,三层(P1/P3/P4)单点覆盖;比率初版 `>>10` 本地过但 CI(慢约 10×)撞 13.5s,收紧 `>>6`。**七条教训**:① 取证投资兑现且兑现方式正是设计意图 / ② 静默死亡两步分类直接给答案(`exit status 2` = Go fatal 有栈迹 + 取证接住 fd 2) / ③ **指令预算必须度量工作量而非条数**(单指令能做 O(n) 工作的算子须按工作量记账,下批候选 string.rep / string.format / table.concat)/ ④ 家族级误分类可持续数周,真值来自第三方证据(取证栈迹)而非表象反推(内存假说反复「解释得通却修不好」是整体错误的信号)/ ⑤ 限流比率按最慢目标环境定 + CI 实测(本地 vs CI runner 速度差数量级,[[design-claims-vs-codebase-physics]] §6 空间维度新实例)/ ⑥ in-test wall-clock 断言合法性看它守的是任意选的秒数还是真实固定外部阈值(守 Go fuzz 硬编 10 秒看门狗合法)/ ⑦ **prove-the-path 也适用于自己写的回归测试**(被测路径与参照路径汇聚到同一段代码、返回同一可观测结果时,只断言那个结果无法区分两条路径=fake-green,须用只有目标通道会动的正交探针如 `ConcatHelperHits`;force-promote 行为按 tier 不对称——P4 促升、P3 对含 concat 的循环不促升,要用探针实测)。**promotion 决策(reflector 建议)**:教训 1/2/4 已作 concat storm 家族**结案数据点**写入 [[unreproducible-crasher-triage]]「concat storm 家族已根因定性并修复」新节;教训 3(指令预算须度量工作量)候选进 [[design-claims-vs-codebase-physics]] 或独立条目,VM 行为侧对账已入 `docs/design/p1-interpreter/implementation-progress.md`;教训 5/6 候选并入既有 guide 对应小节。**触发场景**:concat storm 家族(或同类)再报不可复现 crasher 时(先看取证栈迹 panic 首行:`panic: deadlocked!` = fuzz 10 秒看门狗、死因是 CPU 而非内存,历轮内存假说已证伪)、设计 / 审计 budget / fuel / step-budget 类限流机制时(问「有没有单步能做无界工作的操作」)、选 byte-charge / timeout 类常量时(按最慢目标环境定 + CI 实测)、给「永不返回 / 长时间运行」类测试加 wall-clock 断言时(守真实固定外部阈值合法、守任意选的秒数交给包级 timeout)看这篇。
- `memory/reflections/2026-07-22-oracle-format-nan-inf-round.md` — 2026-07-22 nightly-fuzz 巡检轮(PR #172,修 2 个 `FuzzOracleDiff` crasher #170/#171,同根因):`string.format` 对非有限浮点(NaN/Inf)在 `f/e/E/g/G` verb 下的渲染与 PUC 5.1.5 不一致(#170 `%E`→oracle `-NAN`/wangshu `NaN`;#171 `%f`→oracle `nan`/wangshu `NaN`)。根因:`stringlib.go` 浮点 verb 分支用 Go `fmt.Sprintf`(拼 `NaN`/`+Inf`/`-Inf`),而 PUC 走 C `sprintf` 由宿主 glibc 决定(小写 verb → `nan`/`inf`,大写 → `NAN`/`INF`,且 glibc 为 NaN 保留符号列导致 width−1 quirk);修法新增 `cFormatSpecialFloat` 逐字节复刻 glibc,`string.format` 共享 stdlib 单点覆盖 P1/P3/P4。**四条教训**:① PUC 语义由 C 代码 + 宿主 libc 定义,非有限值格式化尤其如此——真值最终落 libc(`sprintf`)时读 C 源码只能看到「转发」,以 oracle 实测字节为准(是 [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义」纪律的 libc 层子类实例,已落该 guide);② glibc 非有限值格式化的 width 语义有反直觉 quirk,必须建覆盖矩阵实测不能从单点外推(本轮从「小写 NaN width−1」外推「所有非有限 width−1」一步把 Inf 全改错,靠 93 组 verb × 符号 × flag × width 矩阵逼出完整真值表,首次以独立教训出现暂留观察);③ 读外部 oracle 确切输出时走真正 harness 路径别手搓简化探针(手写 `oracle.Exec` 探针因 prelude 拼接 + 空 GlobalSet 连续两次拿空/崩溃伪信号,构造 corpus 跑真 `FuzzOracleDiff -run` 一次就对,是 [[prove-the-path-under-test]] 家族在读真值侧的对偶,首次样本暂留);④ 字节级差异用机器精确数别肉眼数空格(`[...]` 包裹 + `${#inner}`,corpus 里 Go 语法字符串用 python goquote 而非 shell `printf %q`)。**触发场景**:与 PUC 5.1.5 byte-equal 的分歧涉及格式化 / 数值转换时(先 grep `_lua515/` 看转发给哪个 libc 函数,真值落 libc 就以 oracle 实测为准)、对付宿主 libc / 外部真值面猜规则时(构造覆盖矩阵别单点外推)、要读某外部 oracle 对某输入确切输出时(复用既有 harness 入口别手搓简化调用)、比对带空白的字节串差异时(机器数长度别肉眼数)看这篇。
- `memory/reflections/2026-07-23-oracle-arg-coercion-round.md` — 2026-07-23 nightly-fuzz 巡检轮(PR #176,修 2 个 `FuzzOracleDiff` crasher #174/#175,不同表象同类根因:wangshu 参数强制转换比 PUC 5.1.5 严)。#174 `string.rep("...", "0X0")` 次数参数是 Lua 十六进制整数字符串,PUC `luaL_checknumber` 强制转成 0,wangshu 报「number expected」;#175 `tonumber(0, "2")` 带 base 时 PUC `luaL_checkstring` 把 number 强制转 string,wangshu 报「string expected」。**根因**:`toNumberStr`(`internal/stdlib/stdlib.go`,被 string/table/math 各库约 28 处调用)用裸 `strconv.ParseFloat` 不认 Lua hex 整数,而仓库早有对齐 PUC `luaO_str2d` 的权威实现 `crescent.ParseLuaNumber` 却没被复用;`tonumber` base 分支硬性要求第一参数为 string。**修复**:`toNumberStr` 改走 `crescent.ParseLuaNumber`(一处对齐所有调用点)+ `tonumber(x, base)` 第一参数接受 number 强制转 string;回归 `TestStdlib_NumericArgCoercion`(期望值经 oracle 核对,如 `tonumber(255,16)==597` 反直觉)+ corpus 入 `testdata/fuzz/FuzzOracleDiff/`。**三教训**:① 一个能力已有权威实现时别在别处另写标准库简化版——两份实现宽松度迟早分叉直到 fuzz 撞出(与 [[cross-backend-semantic-fix-sweep]]「语义经统一入口」同族,已落该 guide「stdlib 跨 number/string 边界强制转换复用 VM 侧权威实现」延伸段,首次样本暂留);② 共享 helper 单点修复能一次对齐一大批调用点但要意识到影响面,改前 grep 数清调用点、改后全套件 + 定向 fuzz(首次样本暂留);③ 差分类回归测试期望值以 oracle 实测为准别凭直觉(与 [[2026-07-22-oracle-format-nan-inf-round]] 教训 3 同族第二实例,仍暂留)。**触发场景**:给某个「X → Y」语义敏感转换加实现或改行为时(先 grep 全仓尤其 crescent/VM 侧看有无权威实现可复用)、改被多处调用的共享 helper 时(grep 数清影响面 + 改后定向 fuzz)、写差分类修复回归测试定期望值时(先跑真 `FuzzOracleDiff` 核对再固化)、放宽某接受面后(按 [[prove-the-path-under-test]] §5 跑 oracle fuzz smoke 重探)看这篇。
- `memory/reflections/2026-07-24-issue173-oracle-nan-known-diff-round.md` — 2026-07-24 issue #173 处理轮(分支 `fix/173-oracle-nan-known-diff`,3 精细 commit `2daf5c0..8b9b324`):承 #170/#171 → PR #172,把「wangshu 与 PUC 在 NaN 符号 spelling 上的双向差异」定性为 PUC/x86/libc 已知平台差异(IEEE 754 不赋 NaN 符号数值语义),不改 VM 值层 NaN-boxing 追一个 CPU-libc 组合的显示,改在 oracle diff harness 里窄口径识别并跳过。**机制三件套**:① `internal/stdlib/stringlib.go` `cFormatSpecialFloat` godoc 定性(把「暂时对齐」的软承诺换成「已知差异 + harness 跳过」稳定契约);② `internal/oracle/prelude.go` 加 `__nan_output` 证据链——`print`/`io.write` 收到 `v ~= v` 数字置位,`string.format` 跑完后检查 `out` 含 `nan`/`NAN` 且入参含 NaN 才置位(证据来自**渲染出口**不是输入侧);`__oracle_readout()` 首字节 header `"0"`/`"1"` 传给 Go 侧;③ `internal/oracle/compare.go` `CompareOutput` 三态归类(Equal / KnownNaNSign / Different),仅在两端都有 NaN 证据时对单一 nan/NAN token 做精细 spelling 匹配(含 printf width 一列偿还的对偶),字面量 / 案例 / 对齐 / 其他字节差异一律回到 `OutputDifferent` 正常失败;`NormalizeOutput` 严格只做地址归一(#173 明文禁止在此层全局替换 `NAN`/`-NAN`,避免误吞脚本自己输出的普通字符串)。**过程雷**:分支中途 WIP commit(80f7b09)把精细方案回退为 `strings.ReplaceAll(s, "-nan", "nan")` 全局替换 + 移除负例回归,直接违反 #173 明文约束 + 漏大写 -NAN + 静默吞掉脚本字面量,本轮 revert 回精细方案。**四条教训**:① 接受已知平台差异 = 定性 + 证据链 + 窄口径归类三件套(是 [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义 / libc 定义」的**退让侧**候选,承 [[2026-07-22-oracle-format-nan-inf-round]] 那轮的「追到 IEEE 无语义 / 只能追一个 CPU-libc 组合时」对偶,首次样本暂留);② `NormalizeOutput` 是「无损归一」层,跳过判定分层放在 `CompareOutput` 归类层不进归一层(否则失去「什么被视作等价」的可解释性,承 [[design-claims-vs-codebase-physics]] §2 邻接维度,首次样本暂留);③ 证据来自渲染出口不是输入侧(输入侧证据会漏掉「NaN 参数被 UB guard 抛错 / 走 tostring」等静默路径,是 [[prove-the-path-under-test]]「证据位定位」家族候选,首次样本暂留);④(过程)WIP commit 不该混进 issue-tracking 分支——`fix(...)` 讲清方向的分支上探索简化版应开独立分支或至少给 WIP commit 说明性 message(首次样本 process-level 暂留)。**触发场景**:与 PUC byte-equal 分歧追到 IEEE 无语义 / 只能追一个 CPU-libc 组合 / 修 VM 值层 ROI 过低时(教训 1:文档定性 + harness 证据链 + 三态归类,不追)、想在测试比对里跳过某类差异时(教训 2:跳过判定放归类层不放归一层 + 负例回归钉住字面量场景)、写「证明差异来自某语义路径」的证据位时(教训 3:从渲染出口置位不从输入侧猜)、一个已经用 `fix(...)` 讲清方向的分支中途要探索简化版时(教训 4)看这篇。**此轮机制已被整体替换(2026-07-26,分支 `proto/oracle-nan-render`):NaN 符号差异改在 oracle 渲染处消除(`internal/oracle/lua515.c` 覆盖 `lua_number2str` + 对 `lstrlib.c` 局部 shadow `sprintf`),span 分类层与整层入口拦截全部删除,`CompareOutput` 回到「归一化地址后逐字节比较」且没有任何接受差异的路径。旧机制不可能收敛的原因是符号字节进入字符串后就是普通数据(`string.len(0/0)` 是 4 对 3、`string.len(0/0)*100` 是 400 对 300,输出里没有可锚定的 nan token),任何下游识别规则都要读一个两侧不相等的量,做不到对称。教训 1 的「窄口径归类」结论因此要反过来读:先问差异能不能在产生处消除,只有「两侧本来就是不同的东西」(地址、实现常数护栏)才在比较侧处理;教训 2/3 关于分层与证据位的判断仍成立。现状见 `docs/design/engineering.md` §3.2 与 `docs/design/p1-interpreter/12-testing-difftest.md` §4.2。**
- `memory/reflections/2026-07-26-issue184-185-nan-coercion-noncomparable-round.md` — 2026-07-26 issue #184/#185 处理轮(分支 `fix/184-185-oracle-diff-crashers`,**43 commit、26 轮独立盲审**;e245c17 先入 crash corpus 让下一个 commit 的效果可单独观察,0122e51 `fix(oracle): treat NaN text coercion as non-comparable` 是第一版机制,其后 26 轮盲审逐步收敛到当前设计):2026-07-25 nightly-diff-fuzz p1 腿撞出两个 `FuzzOracleDiff` crasher(#184 `print(string.format("NA%E",-(0%0)))` → oracle `NANAN` / wangshu `NA-NAN`;#185 `print(tostring(0%0))` → oracle `-nan` / wangshu `nan`),都落在 PR #181(issue #173)刚机制化的 NaN 符号已知差异区域,第一反应是「#173 的 span 机制有两个漏洞要补」,探针把问题性质改了。**探针两个发现**:① `tostring`/`..`/`%s`/`%q`/`string.rep`/`table.concat` 等 7 种写法完全没有 span 证据(`preludeCapture` 只在 print/io.write 直接见到数值 NaN 时记 span,NaN 提前被转成 string 的路径一格都不记);② **NaN 符号差异会泄漏成纯数值差异**——符号字符改变了强制转换后字符串的长度和内容,`string.len(0%0)` oracle `4` / wangshu `3`、`string.byte(0%0)` `45` vs `110`、`string.sub(0%0,1,1)` `-` vs `n`、`string.len(0%0)*100` `400` vs `300`,`print(400)` vs `print(300)` 里没有任何 NaN token,span-anchored 拼写分类在结构上覆盖不到,而且 4 和 3 是有语义的 Lua 数值,不像它来源的符号位那样「IEEE 754 不赋数值语义」。**结论:#173 的「已知平台差异」定性适用边界比当时认定的窄**——对浮点转换(`%e/%f/%g` 家族,走各引擎自己的 printf 代码)成立,对文本 coercion 不成立,本轮是**收窄**不是扩展。用户在三方案里选「整类判不可比」而非「让 wangshu 对齐 x86/glibc 拼写」:arm64 glibc 对 `-(0/0)` 打 `-NAN` 与 x86 不同(#173 的 CI 已实测过),对齐一个必然偏离另一个,且不应为满足 oracle 把产品可见语义绑定到某个 libc。**实现**:`internal/oracle/prelude.go` 新增 `preludeNaNCoercion` 层追加在 `Prelude` 最后(`writeTrim` 与 `preludeSortedIter` 之后)、对称跑在两侧,见到 NaN 就抬共享的 `LimitSentinel`、两侧同时判 `VerdictLimit` 跳过;能在源头拦的是走全局函数的出口(`tostring` / `string.len`、`sub`、`byte`、`upper`、`lower`、`reverse`、`rep` / `table.concat` / `string.format` 的 `%s`/`%q` / fmt 本身是 NaN 数值),拦不住的是 `..` 操作符(读 vendored `_lua515/src/lvm.c` 的 `luaV_concat` 核实它对 `ttisnumber` 操作数直接走 `tostring` 宏、不触发元方法,wangshu 侧探针实测同样绕过全局 `tostring`,`debug` 不在白名单所以也无法用 `debug.setmetatable` 给 number 挂 `__concat`),改在输出端由 `__emit_nan_gate` 兜底——对没有 span provenance 的字符串用本引擎自己的 NaN 拼写(`__tostring(0/0)`)做大小写敏感的 plain 查找(不用 pattern),所以 `print("BANANA")` 只含大写 `NAN` 不会被误伤,代价是脚本字面量恰好含本引擎小写拼写时也会被跳过(丢一个输入,不会产生错误判定);另修 #184 的 span straddle(fmt 字面尾部恰为 `[nN][aA]` 且紧接浮点转换时会与转换结果首字符拼成假 token 使锚点前移,oracle `NANAN` 匹配在 offset 0 / wangshu `NA-NAN` 在 offset 3、gap 段 `""` vs `"NA"` 不等 → `OutputDifferent`;错位天然不对称,所以跳过判据只从两侧完全相同的 fmt 字符串推导才对称)。**过程雷(值得记住的实现陷阱)**:第一版在门里用 Lua pattern 匹配 `%s`/`%q` verb,但门层跑在 `preludeGuards` 之后、`string.find` 已被 `__patcheck` 包装(量词字符超过 2 个即报 pattern too branchy),模式里 3 个量词字符触发上限 → 两侧都报错、输出都成空串 → `CompareOutput` 判 `equal`,门完全没生效但测试显示相等,是假绿;修法 verb 检测改手写逐字节扫描完全不碰 pattern 引擎 + 需要 plain 查找处把包装前捕获的原始 `string.find` 经 harness 私有全局 `__oracle_sfindraw` 传给门层(`writeTrim` keep 列表加该名字,门层用完立刻置 nil)。**验证**:新增 `TestExec_NaNCoercionGate`(20 个必须跳过 + 13 个必须保持可比,后者含 `print("BANANA")` / `print("NAN")`)与 `TestExec_NaNGateSymmetry`(钉住跳过决定不依赖单侧拼写);原 `TestExec_NaNSpans` 里 `print(string.format(0/0))` 用例移除(fmt 本身是 NaN 现归入被拦类),其约束以显式跳过预期保留在新测试;corpus 入库 `7dd267cdb3e0b40a` 与 `f988b149f1af8375`;反向验证摘掉门层后 `f988b149f1af8375` 恢复原样失败(证明门是 load-bearing);90 秒 fuzz smoke 46840 execs 零 crash、`new interesting: 12`;邻近但不该跳的形式(`"A%E"`/`"N%E"`/`"NAx%E"`/`"%E NA"`/`"%En"`/`"%Ena"`/`"%E%E"`/`"n%f"`/`"a%f"`)实测仍走 knownNaN 通道,证明 straddle 判据精确、没有过度牺牲覆盖面。产品代码零改动。**四条教训**:① **头条 —— 在被包装过的环境里做检查,不能依赖会被那层包装改写的原语**(门自己的 pattern 被 `__patcheck` 的量词上限判掉,两侧**对称**报错 → 输出都是空串 → 比较器判等,检查静默失效而测试显示相等;判据:门/检查逻辑只依赖不会被它所保护的那层改写的原语,要么手写实现、要么在包装之前捕获原语并以私有名字传进来且用完置 nil;推论:凡「两侧共享同一层 harness 代码」的差分测试,这层代码自身出错的默认表现就是假绿不是红灯);与 [[prove-the-path-under-test]] + [[2026-07-25-issue179-test-go-fuzz-retry-revive-round]] 教训 1 同族,是该家族**第 17 个实例、第 2 个非 Go 测试 harness 载体**,反思里**建议真的升进 guide 正文**(加一节「harness 自身失效的默认表现是假绿」,把 #179 shell stub 与本轮 prelude 分层两个实例一起引进去);② 已知平台差异的定性有适用边界,收窄比扩展更常见——给一类差异做「已知/可忽略」定性时要问「这个差异能否经由某个操作转化成有语义的值」,能转化就说明边界比看上去窄,必须在定性文本里写清适用的出口范围(是 [[2026-07-24-issue173-oracle-nan-known-diff-round]] 教训 1「定性 + 证据链 + 窄口径归类」三件套的**边界补充**,跨 2 实例,建议下次再遇到「定性需要修边界」时并入作第四件「可转化性检查」);③ 修 fuzz 撞到的写法之前先把同族出口面整个测一遍(撞到 2 种、探针测出 7 种无证据 + 5 种数值泄漏;只修撞到的两种就是 benchmark 形状的窄修复,nightly 会在同一个根因上继续撞新 corpus 且每次都长得像新的边界情况;判据:拿到 crasher 先定位产生差异的机制、再枚举该机制的所有出口逐个探针,然后才决定修法粒度,与仓库既有「不做 benchmark 形状的窄修复」纪律一致);④ 宿主语言的能力缺失会强制方案分两套手法——`..` 在 Lua 5.1 层面没有 hook,「拦源头」的统一方案做不到,必须配「输出端检测」兜底,而两套的误伤面不同(源头拦精确,输出端检测会误伤含该拼写的脚本字面量),设计拦截类机制时先确认宿主语言在**所有**入口都给了 hook,缺一个就要准备第二套手法并明确写下误伤面与误伤后果,选「丢覆盖」而不是「判错」那一侧(首次样本暂留,再遇到「拦截机制缺一个入口」即可升成小 guide)。**26 轮盲审之后的最终设计与另外八条教训**(反思第 7–26 节与教训 1/2/8/11/12):输出端检查已整套删除(它必须按字符串**值**查 provenance 才能豁免浮点转换,而那个值恰恰就是两侧的差异,四轮四个缺陷都是同一个不可对称 key 的变体),只留**入口拦截** + #173 的 span 通道 + 只从 fmt 推导的 straddle 规则,`..` 直送输出改报**对称差异**;harness 里**不再有任何长度阈值、没有 script 规模的循环**——「限制扫描规模」失败五次(输出长度 / 输入长度 / 分桶 / 放宽容差 / 带内容例外的输入长度,每次都是比较了一个两侧不相等的长度),最终解法是把扫描做成不需要上限的(plain find 两侧都无 matcher 预算,需要 pattern 处只在固定长度切片上跑),八处扫描点全部转换;span 吸收逻辑改了**六版**才收敛(逐字节 walk 1m45s vs 384ms → offset 上限使两侧锚点差 1 → 换基准点仍差 1 → 无锚定量词是平方、1024 字节撞 `maxMatchSteps` → `$` 锚定同样平方 → 无量词字符类查找 + 反转后按前缀一次匹配),收敛那版的做法是**先把无重试 / 无迭代 / 无 offset 运算三条约束列出来**再找同时满足的写法;**四个不对称维度**各配守护测试(gate 测试共 **9 个**):运行成本(一侧解释执行一侧 C)、分配量(PUC 的 GC 回收临时串、wangshu 的 arena 不回收,103 字节脚本耗尽 arena 而 PUC 706ms 跑完)、matcher 步数预算(只有 wangshu 计步,**成本比值测不出来**因为触发时失败那侧反而更快)、长度阈值(`#` 与 `==` 能把符号字节搬进一个数字,所以没有哪个长度两侧相等);`table.concat` 改成无条件先扫元素(自 1 起、忽略脚本 i/j、上限只限制循环次数不改变判定)再查返回值,把判定入口从「tconcat 是否失败」这个引擎相关条件上摘下来(早先版本在上限处抬 sentinel,把超 4096 元素的表整类切掉);产品侧一处改动 `string.find` 的 plain 路径改用 `bytes.Index`(去掉每次调用复制剩余 subject,30 倍长输入曾表现为 205 倍成本),两次产品侧尝试实测否决并 revert(`formatLuaNumber` 无效因 `NumberValue` 把 NaN 规范化成 `canonNaN`;NaN-boxing 保留符号位不可行因 x86 上每个 NaN 运算都产出与 `TagNil` 逐位相同的编码)。**两条新教训已升进 [[prove-the-path-under-test]] §9**:**同一处代码改到第三版还在出同类缺陷 = 该处抽象选错,不是实现没写对**(先把全部约束列出来再找同时满足的写法,别针对刚暴露的那一个打补丁;信号 = 第 N 版改动是「加上限 / 加特殊情况 / 换基准点」);**「不比较那个量」往往比「把比较做对」更可行**(量在两侧不可能相等时,容差与分桶都只是把窗口挪走,要问「能不能不读这个量」)。**触发场景**:在已经被包装/打过桩的运行环境里写检查、门、断言逻辑时(教训 1:只用不会被那层改写的原语;差分 harness 里两侧共享代码出错的默认表现是判等,必须额外打印实际 output 与 err 确认检查真的跑了)、给一类差异做「已知/可忽略」定性时(教训 2:问一句这个差异能否经由某操作转化成有语义的值,据此写清适用的出口范围)、处理 fuzz crasher 时(教训 3:先定位机制、枚举全部出口、逐个探针,再决定修法粒度)、设计拦截类机制时(教训 4:先确认宿主语言在所有入口都有 hook,缺口要配第二套手法并写明误伤面)、**给差分 harness 写跳过判据或加任何随输入规模增长的扫描时**(判据的每个输入都必须两侧相同,含它花掉的时间、分配量、matcher 步数预算;后果是单侧 skip,静默吞掉同一次运行的真差异)、**准备给一个两侧不相等的量设计容差或分桶时**(先问能不能不读这个量)、**发现同一处代码改到第三版还在出同类缺陷时**(把全部约束列出来再找同时满足的写法)看这篇。**此轮机制已被整体放弃(2026-07-26 转向,分支 `proto/oracle-nan-render`,最终方向见 [[2026-07-26-oracle-nan-render-redesign]])**:这个机制装错了位置 —— NaN 符号差异应该在**产生它的地方**消除(oracle 是我们自己 vendor 进 `internal/oracle/_lua515/` 并用 cgo 编译的,这件事 31 轮里从未查证过),而不是在下游的 harness 里识别并豁免;下游识别必须依赖判据,而那个符号字节一进字符串就是普通数据,判据的输入迟早会被污染。最终方案在 `internal/oracle/lua515.c` 里覆盖 `lua_number2str` 并对 `lstrlib.c` 局部 shadow `sprintf`,本轮的 span 机制与 `preludeNaNCoercion` 整层全部删除,`CompareOutput` 回到「归一化地址后逐字节比较」,相对 master 约 −550 行,而覆盖面反而**增加**(两个 crasher 与整个「泄漏成数字」的家族从 skip 变成普通的 equal)。本文档保留,因为它是**下游识别为何不可能收敛的完整证据**:31 轮盲审、13 轮抓到的缺陷是修上一轮时引入的、同一处吸收逻辑六个版本、「限制扫描规模」失败五次。教训 2(判据的每个输入都必须两侧相同,含时间 / 分配量 / matcher 步数预算)与教训 3/8/9/10/11/12 在「已经决定在比较侧处理」的前提下仍然成立;**教训 1 的作用域要按新反思的教训 1 读** —— 它管「一个机制内部该不该继续修」,管不到「这个机制装在哪一层」。**
- `memory/reflections/2026-07-26-oracle-nan-render-redesign.md` — 2026-07-26 issue #184/#185 的**最终方向**(分支 `proto/oracle-nan-render`,5 个 commit `07e2808..7dd1cb1`):把「wangshu 与 PUC 的 NaN 符号差异」从「在 harness 侧识别并豁免」改成「在 oracle 的**渲染处**消除」。**问题本身是一个字节**:`0/0` 转文本时 glibc 的 printf 按符号位打 `-nan`、wangshu 打 `nan`,IEEE 754 不赋 NaN 符号位数值语义,两种输出都合规,所以这是渲染写法的选择不是语义分歧;围绕这一个字节写出来的机制最后有 800 多行。**旧路走了多远**:分支 `fix/184-185-oracle-diff-crashers` 共 43+ commit、31 轮独立盲审,其中 **13 轮抓到的缺陷是我修上一轮时引入的**,同一处吸收逻辑改了**六个版本**、「限制扫描规模」失败**五次**,每一轮我都在把机制做得更精细、没有一轮去问这个机制该不该存在(完整记录见 [[2026-07-26-issue184-185-nan-coercion-noncomparable-round]],保留)。**转向的触发不是新的技术发现**,是用户在第 31 轮之后问的一个问题:「这个事情已经跑了很久很久了对吧?好像一直有 edge case 冒出来?这是不是暗示着,这么做可能是不对的?甚至上一个 PR 也是不对的?」——它没有提供任何新的技术信息,但促使我去查 oracle 到底是什么。**被漏掉的事实**:那份 PUC 5.1.5 源码在 `internal/oracle/_lua515/`,是仓库自己 vendor 进来的,`internal/oracle/lua515.c` 是单编译单元(官方 `etc/all.c` 手法)经 cgo 编译进测试二进制,且已经为确定性 stub 掉 `os.time`/`math.random`/`pairs` 顺序 —— 也就是说 **oracle 侧的 NaN 文本一直在我们控制之内**,这一整轮它都是。此前实测确认过的两件事是对的(改 `formatLuaNumber` 无效:`value.NumberValue` 把所有 NaN 规范化成单一 `canonNaN`,符号位在值层就丢;改 NaN-boxing 保留符号位不可行:x86 上每个 NaN 运算都产出与 `TagNil` 逐位相同的位模式,负 signaling NaN 要在 P1/P3/P4 四条代码生成路径上给每个浮点结果重映射、漏一处那个 NaN 就被读成 nil),**错的是我把它外推成「只能在 harness 侧容忍」** —— 正确的推论只有「wangshu 侧改不动」,它没有排除「改 oracle 侧」,而这个第三选项 31 轮里一次都没被列出来过。**新方案**:在 `internal/oracle/lua515.c` 里(include vendored 源码之前)覆盖 `lua_number2str`(覆盖 `tostring` / `..` / `io.write` 的数字渲染)、并对 `lstrlib.c` 的 include **局部** shadow `sprintf`(`string.format` 的 `%e`/`%f`/`%g` 族直接调 `sprintf` 不走 `lua_number2str`,include 后立刻 `#undef`),把 NaN 渲染的符号去掉;vendored 源码保持与 `_lua515/README` 记录的 sha256 逐字节一致(配置只在 `lua515.c` 与 CFLAGS 里做,仓库既有纪律)。三个实现要点:① **保持字段宽度** —— `sprintf` 已经补过 padding,直接删符号会让字段短一位,而 buffer 本身分不清前导空格是 padding(`%5E`)还是空格 flag 的符号(`% E`),所以把 format spec 传进去按**声明宽度**重新补齐,两种读法于是落在同一个答案上;② **剥任何符号字符不只是 `-`** —— 符号去掉后 glibc 的 `+` 与空格 flag 开始作用于 NaN,产生 `+NAN` / ` NAN`;③ **Inf 保留符号**(那个符号有数值意义),脚本自己写的 `"-nan"` 字面量不动。真值表见 `internal/oracle/oracle_test.go` 的 `TestExec_NaNRenderedWithoutSign`。**顺带清掉产品侧两处 glibc 模仿**(`internal/stdlib/stringlib.go` `cFormatSpecialFloat`):大写 verb 硬编码 `-NAN`(导致 `%e` 与 `%E` 自相矛盾)、小写 NaN 按「声明宽度减一」补齐以复现 glibc 保留但不显示的符号列(导致 `%5f` 与 `%5E` 补齐方式不一致)—— 两处都只为让 oracle 一致而存在,而 arm64 的 glibc 与 x86 还不同、模仿本来就不可移植;现在 NaN 在所有 verb 下都不带符号、都按完整声明宽度补齐,wangshu 自身也一致了。**删掉的东西**:span 机制整套(`NaNSpan`、`DecodeOutput`、多行 readout header、`knownNaNSignDifference` 及四个辅助、`OutputKnownNaNSign`、prelude 的 span recorder 与 provenance FIFO)+ `preludeNaNCoercion` 整层(`tostring`/`string.len`/`sub`/`byte`/`upper`/`lower`/`reverse`/`rep`/`find`/`match`/`gmatch`/`gsub`/`table.concat`/`%s`/`%q` 的入口拦截、straddle 规则、八种拼写表、所有 cap 与 trigger),`CompareOutput` 回到「归一化地址后逐字节比较」、**没有任何接受差异的路径**,测试明确断言 NaN 符号差异**不被豁免**。**净效果**:相对 master 约 **−550 行**(31 文件 325+/841−,`internal/oracle` 一处 230+/779−);两个 crasher 与整个「泄漏成数字」的家族(`string.len(0/0)` 4 对 3、`string.len(0/0)*100` 400 对 300、`#(""..(0/0))`、`(""..(0/0))=="nan"`)现在都是**普通的 equal 而不是 skip** —— 这些输入在旧机制下会连同同一次运行里的真差异一起被丢掉,所以**覆盖面是增加的**;16 个 corpus 入 `testdata/fuzz/FuzzOracleDiff/`(含 Inf 对照与脚本自出 `print("-nan","-NAN")` 负例),60 秒 fuzz 无 crash、全套测试绿。**五条教训**:① **头条 —— 「反复出现 edge case」是选址错误的信号,不是实现不完善的信号**(31 轮、13 轮自引入、同一处六个版本都不是「再仔细一点」能解决的量;判据 = **缺陷密度长期不下降本身就是信号**,去问「这个机制装在了正确的位置吗」;差分类问题的可用形式 = **差异应该在产生它的地方消除,不在下游识别** —— 下游识别必须依赖判据,而判据的输入迟早会被污染,本轮的证明是可构造的:符号字节进字符串后就是普通数据,`string.len(0/0)*100` 的输出里连一个 nan 字节都不剩。**与旧反思教训 1 是两个尺度**:那条管一个机制内部该不该继续修,这条管整个方向该不该继续走 —— 那条第 5 轮起真的用上了也确实一步收敛了那一处,但作用域是「这个机制」,所以只会让机制修得更好、不会让人去问装错了层,**作用域比判据本身更要紧**);② **一个否证结论的适用范围必须显式界定,否则会被悄悄外推**(实测「改 wangshu 值层不可行」是对的,外推成「只能在 harness 侧容忍」是错的;外推是**静默**发生的 —— 我从没写下过那句话也从没列过候选清单,它以「这件事已经查过了」的形式留在上下文里、后面 31 轮都建立在它上面。判据 = 写下否证结论时同时写下它**没有**排除什么,两行:被否证的是哪个**具体**选项 + 与它并列的其他选项各是什么状态(未查证 / 已查证不可行 / **未想到** —— 最后这格写不出来正是要靠列清单逼出来的)。**「我实测过了」保护的是那一个选项的结论,不保护由它推出的范围**);③ **先查清被测系统的构成,再设计对策**(「这个基准是谁提供的,我们能改吗」是第一轮就能问出来的事实;当时对 oracle 的心智模型是「外部真值」,在**语义**上对、在**物理**上错,模型错在物理层于是把一整类方案排除在视野外**且不留痕迹**。判据 = 先查参照实现的**供给链**:源码在哪、谁编译的、编译时已打过什么补丁、改它会不会破坏它作为基准的意义 —— 本轮最后一问答案是「不会」,而**先例就在源码里**(确定性 stub 三处),31 轮都没去看);④ **用户的「这是不是方向错了」值得当作技术信号处理**(那个问题没有任何新技术信息,但促使我去查一个一直假设为不可变的前提;判据 = 当一件事的进展长期与投入不成正比时,重新检查前提比继续推进更有价值,正确的第一动作是**把方案依赖的前提逐条列出来、标出哪些查证过哪些是假设**再去查那些假设,不是解释当前方案为什么合理、也不是罗列已经解决了多少问题。**「已经投入了 31 轮」是继续走下去的最差理由,也是最容易被当成理由的那一个**);⑤ **区分两类平台差异(可复用判据)** —— 「同一个抽象值的不同书写方式」(NaN 符号位)→ 在**渲染处**消除;「两侧本来就是不同的东西」(引用值地址:两个引擎堆布局本来不同、没有「正确值」可对齐 → 归一;两侧独立选定的实现上限 `stack overflow` / `too many syntax levels` / 200 local variables:触发点差几个输入、改 oracle 的常数去凑会让它不再是独立基准 → 跳过)→ 在**比较时**归一或跳过。**前者放到比较侧就会像本轮一样不收敛**,因为那类差异会流入普通数据(同一抽象值的不同书写会被 `#` / `==` / `..` / 算术搬进长度、布尔和数字,输出里不再留下可锚定的标记);后者不会 —— 地址永远带 `table: 0x` 前缀、护栏永远是固定错误消息,标记是**结构性的**。**Promotion**:教训 1/2/5 已升进 [[prove-the-path-under-test]] 新增 **§9.0**(放在 §9 那套判据对称性纪律**之前**,因为它决定后面那套要不要用;§9 现有内容全是「已经决定在比较侧处理之后」的纪律,缺的正是上一层的选址判断);教训 3 候选进 [[design-claims-vs-codebase-physics]](心智模型错在**物理层**而非语义层),本轮先在 §9.0 里以一句判据带过;教训 4 是 process-level 首次样本,暂留。**触发场景**:发现自己在为同一个机制反复补 edge case 时(先数缺陷密度降没降,没降就问选址)、写下「我实测过了这条路不行」时(同时写下它没排除什么)、要处理与某个参照实现 / 外部真值的差异之前(先查供给链)、用户或外部视角问「这个方向是不是不对」时(先列前提标假设再去查,别先解释方案为什么合理)、设计任何「接受某类平台差异」的机制之前(先判该在渲染处消除还是比较时跳过 —— 判在渲染侧就不要设计判据)、一次拿到多个 crasher issue 时(教训 6:先在 HEAD 上重放分组,不要按 issue 编号分头开工)、说「这个 crasher 已经修好了」之前(教训 7:断逐字节 equal 不是断绿 + 双向验证)、确认一条 fuzz 输入不再有差异之后(教训 8:扫它所属的维度再收工)看这篇。**后续验证(§9,分支 `fix/187-190-nan-crasher-corpus`,commit `b9ac433`,一行代码没改)**:nightly 在四个不同日期自动开出的 crasher #187 `print(string.format("%q",0%0))` / #188 `print(string.format((0))<string.format((0%0)))` / #189 `print(string.format("%q",(0%0)))` / #190 `print(string.format("+% E",-(0%0)))` **全部是这同一个根因的表现**,已被本轮修复(PR #191)一起解决;**双向验证**确认它们在 redesign 之前的 base `c982610` 上全部 FAIL、在当前 master 上全部以**真正的逐字节 equal** 通过而不是 skip(区分这两者很重要:skip 等于把它们又藏起来了而不是修好了);四条 reproducer 加 10 条延伸 seed 入 `testdata/fuzz/FuzzOracleDiff/`(共 73 个 seed),入库理由是它们触到三个已有 seed 到不了的写法、而每一个都是符号字节逃逸的不同路径 —— `%q` 作用于 NaN(引用渲染结果而不做浮点格式化,不走 `%e`/`%f`/`%g` 那条 `sprintf` 通道)、比较两个 `string.format` 的**返回值**(差异变成一个 boolean,输出里根本没有 NaN 文本可锚定)、多个符号 flag 同时出现(`"+% E"`,负号去掉后 glibc 的 `+` 与空格 flag 开始作用于 NaN);**主动扫描**不满足于那四条输入 —— 236 种写法(`%q` × 宽度、6 个比较运算符 × format 结果与 tostring 结果、10 种 flag 组合 × 5 个浮点 verb)+ 140 种(7 种产生 NaN 的方式含 `math.huge-math.huge` 与 `tonumber("nan")` × 20 种消费其文本的方式:concat、长度、byte、sub、reverse、gsub、find、match、upper、rep、`table.concat`、作为 table key),零差异。**追加三条教训**:⑥ **同一个根因可以生成任意多个「独立」的 crasher issue**(四个由 nightly 在四个不同日期开出、标题与 hash 各不相同,看起来是四件事;判据 = 拿到一批 crasher 时**先看它们会不会被同一个改动一起解决,再决定要不要分头查根因**,否则会把一个根因查四遍。与教训 1 互为印证:**正是因为根因被在产生处消除了,四个表现才一起消失**,所以「一批 crasher 能被同一个改动一起解决」本身就是「根因修在了正确位置」的事后确认信号);⑦ **「现在通过了」不等于「修好了」,必须区分 equal 与 skip**(差分 fuzz 里 skip 也让测试变绿但含义相反,绿灯本身不携带这个区别的信息;判据 = 断它以 equal 通过 + 在修复前的 base 上确实 FAIL,两个方向各排除一种误判);⑧ **拿到 crasher 后应主动扫描它所属的维度,而不是只修那一条输入**(维度从 reproducer 的结构里读:哪个 verb / 哪些 flag / 哪个运算符 / 值从哪来 / 文本被谁消费;配套 = **入库判据是「触到的写法是否为新」,与有没有改代码无关**,写法与既有 seed 等价的入库只是增加重放成本)。**Promotion**:教训 6/8 已写进 [[unreproducible-crasher-triage]] 新增两节(「一批 crasher 先问会不会被同一个改动一起解决」接在版本核对之后 + 「修完一条 fuzz 输入之后扫它所属的维度」),教训 7 已写进 [[prove-the-path-under-test]] **§9.6**。
- `memory/reflections/2026-07-25-issue179-test-go-fuzz-retry-revive-round.md` — 2026-07-25 issue #179 处理轮(分支 `fix/179-test-go-fuzz-retry-revive`):`scripts/test-go-fuzz-retry.sh`(issue #63 / golang/go#75804 retry 修复配套的回归 harness)长期失效——`scripts/go-fuzz.sh` 后来加了 `go test -list` fuzz 目标存在性 probe,但 stub `go` 只按 `$STATE_FILE` 返回 deadline-fail 输出,probe 阶段被 go-fuzz.sh 判为「probe FAILED(编译错?)」提前退出,`spurious-then-pass`/`always-spurious` 两 case 从没走到重试逻辑;第三个 case `real-crash` 表面 OK 其实也是 probe 阶段假绿(probe fail rc=1 + 无「retrying once」字样,凑巧同时满足「rc nonzero + 无重试」两条判据);且脚本没接 Makefile/CI,失效期至少半年。**修**:① stub 加两条前置分支 —— 参数含 `-list` 直接 `echo FuzzX; exit 0`(probe 短路),未匹配 `-fuzz` 就 `exit 2` 让未来 stub 与 go-fuzz.sh 不同步立刻浮出红灯;② real-crash 判据加 positive marker `grep -q "Failing input written to"` 治假绿(反向验证:删掉 stub 补丁后三 case 全 FAIL 包括 real-crash);③ 接 Makefile `test-scripts` 目标 + 挂进 `all` + `.github/workflows/ci.yml` 加独立 `test-scripts` job(`ubuntu-latest`, <5s,不合并进 lint 因为出问题排查目标不同,单独 rerun 更清)。**四教训**:① **头条 — 回归测试判据只用 rc + 反向 grep 会假绿,必须配 positive marker 证明目标路径的字节串真出现在 log 里**(real-crash 唯一表面 OK 是判据集恰好被 probe-fail 短路满足;负断言/rc 断言在 probe 阶段短路下是弱证据);跨过 [[prove-the-path-under-test]] 家族实例阈值但**载体新**(shell 集成测试 + stub,现有 15 实例基本围绕 Go 测试 harness / 白盒计数器),建议下一次 shell 层再撞类似问题时把该维度加进 guide 正文,本轮暂留观察;② stub 内含双通道(probe + fuzz)时未匹配调用必须 fail-closed(本轮 `exit 2` + stderr 提示),防未来 go-fuzz.sh 再加新 caller 形式把 stub 悄悄打瞎;是 [[cross-backend-semantic-fix-sweep]]「新通道加入现有 harness 时同步防护」在 stub 侧的对偶,首次样本暂留;③ tooling 类回归脚本不接门禁会静默失效(失效期正比于「没人手工跑」的时间),接入 Makefile `all` + CI 独立 job 双门禁;与 [[2026-07-12-cgo-oracle-fuzz-round]] 教训 4「shell 探针静默 skip」是同族对偶,下一实例合成小 guide「tooling 脚本失效模式与配套门禁纪律」;④(过程)issue clist 里「测试通过」是弱证据(通过不代表其他判据到位,本轮若只按脚本 exit code 判定完成会漏 real-crash 假绿),写 clist 时应把「通过」拆成对应各判据的具体 positive assertion。**触发场景**:写 shell/集成测试 harness 且用 rc + grep 组合做判据时(教训 1:至少一个 positive marker)、写测试 stub/mock 时(教训 2:unmatched-case fail-closed 不通配兜底)、引入 tooling 回归脚本时(教训 3:接门禁)、写 issue completion clist 时(教训 4:测试通过是弱证据要拆具体断言)看这篇。
- `memory/reflections/2026-07-24-p4-template-forprep-deopt-round.md` — 2026-07-24 nightly-fuzz 巡检轮(PR #178,修 `FuzzAutoPromote` crasher #177):触发脚本 `function sum(n) for A=0,n do end end sum "7"`,auto run 2 报「'for' initial value must be a number」,P1 解释器正确 coerce string `"7"` 通过。**根因**:p4Code legacy shape-template 的 MOVE-limit FORLOOP fast-path 把 init/step 烧成 imm64、limit 只进 XMM 从不写回 slot,R(A)/R(A+1)/R(A+2) 从未被写;deopt 路径(string limit 时快路径拒收)直接调 `host.ForPrep`,helper 读到全 Nil,报错位落在 init 而不该报的 limit。**分诊关键**:初判怀疑 PJ10 native emit 里 FORPREP 漏 coerce,加 `NativeRunCount` 探针一次读数(auto 两个 run 都是 0)证明 sum 走的是 p4Code 不是 nativeCode,一步把定位面从 PerOpCode/nativeCode/p4Code 三大类 code kind 收窄到 1 类。**修法**:p4Code 加 `forLoopInitK` / `forLoopStepK` 两个 uint64 NaN-box 字段(从 `shapeInfo.forInitK` / `forStepK` 直传),deopt 前 `SetReg(A/A+1/A+2)` 显式恢复三个 slot 到 interpreter-shape 再调 host.ForPrep,helper 于是能正确 coerce string limit 或对真非 number limit 报正确的「limit」错误(byte-equal 于 P1)。**合入前外部审查 BLOCKER 附记**(commit b00cb00):初版修复调完 host.ForPrep 后直接 DoReturn,空 body FORLOOP 的迭代根本没跑,`preempt()` (step budget + cancel ctx) 全被跳过——`sum "1000000"` + 小 budget 下 P1 报「instruction budget exceeded」而 P4 直接返回、`SetContext` 超时同样被跳过;修法追加 host.ForLoop helper 复刻 execute.go FORLOOP 语义(idx += step / 比较 limit / preempt / 写 R(A) 与 R(A+3) / 循环),code.go deopt 分支改 `ForPrep → ForLoop → DoReturn`;回归测试加 budget-exceeded + context-canceled 两个场景,以 PromotionCount + SpecForLoopDeoptHits 双探针防静默替身(避免促升门 / shape 匹配变化让 auto 静默留在 P1 也拿到同样错误信息通过)。**五教训**:① P4 build 有 PerOpCode/nativeCode/p4Code 三大类 code kind,分诊必须先用探针确认走哪条(承 [[prove-the-path-under-test]] §7 分诊侧对偶);② fast-path template 省略 spill 的 slot,deopt 路径必须显式 SetReg 恢复到 interpreter-shape 再调 host helper — helper 报意料之外的错误信息是 slot 被读到默认值的红旗(承 [[cross-backend-semantic-fix-sweep]]「fast-path template 与 host helper 的 slot-shape 契约」小节);③「A 场景通过 + B 场景失败」只定位触发前提,不能推「bug 在哪条代码路径」;④ 改深层 JIT 数据流前,先读所选字段的定义 + 全部消费点,确认语义/编码与新用途一致;⑤ deopt 分支替原字节码 return 前,必须逐条列出被替代字节码的可观察副作用(preempt / cancel ctx / body 写 / stack 变化)并在 helper 里补齐,回归至少覆盖 budget + ctx 两条通用副作用而非只测结果 + 用双探针防静默替身(承 [[prove-the-path-under-test]] §7.1 复现侧纪律)。**触发场景**:分诊 P4 tier bug 时(先用探针分类 code kind)、审 fast-path template 的 deopt 路径时(把「快路径省写的 slot」与「helper 期望的 slot」两集合交集列出、deopt 前显式恢复)、host helper 报意料之外的错误信息时(先怀疑 slot shape 错配,不是 helper 有 bug)、改深层 JIT 数据流前(读字段定义 + 全部消费点)、deopt 分支想「省一段字节码直接 DoReturn」时(先列被省字节码的可观察副作用清单再决定 helper 该做什么,并加 budget + ctx 通用守门 + 双探针防静默替身)看这篇。
- `memory/reflections/2026-07-28-four-diff-divergence-issues.md` — 2026-07-28 四个既有差分歧 issue 处理轮(分支 `fix/192-194-196-diff-divergences`,8 个 commit):**四个 issue(#192/#193/#194/#196)修出六个根因**,其中两个是扫描过程中发现的、没有任何 issue 记录,还有一个是本分支自己 45 秒 fuzz 冒烟撞出来的。六个根因:① C99 `nan(n-char-sequence)`——strtod 前缀匹配到裸 `nan` 就返回,不消费 7.20.1.3 允许的可选组,只在 `)` 真的存在时才消费整组、未闭合的 `nan(` 保留裸词照旧被拒(`internal/crescent/number.go`);② **`tonumber(x, 10)` 整条路由错(无 issue)**——PUC 的 `luaB_tonumber` 在校验范围之前、也在读 arg 1 之前就判 `base == 10` 并路由到与「无 base」同一条标准转换分支,只有非 10 的 base 才走 `strtoul` 逐字符解析;wangshu 把 base 10 送进逐字符循环,于是**凡是那个循环表达不了的写法全部返回 nil**(`"1.5"`/`"0x10"`/`"1e3"`/`"inf"`/`"nan"`/number 实参),一个结构性原因造出六条分歧、五条没有 issue;③ `%d`/`%i` 精度 0 配值 0 丢符号(#196)——C99 7.19.6.1 转换出零个数字但仍输出 `+`/空格 flag 的符号(符号不属于被转换的数字),Go 连符号一起丢(`%+.0d` C 得 `"+"`、Go 得 `""`),新增 `cSignedFormat` 只在这个角落手写,与隔壁 `cUnsignedFormat` 同一手法;④ `table.insert` 的 5.1 语义**本来没有边界检查**(#194)——`position out of bounds` 属 5.2+,5.1 的 `tinsert` 无检查、`e = #t+1`、`pos > e` 时抬 e,加 `table.concat` 错误文本(PUC 的 `addfield` 带元素的 `luaL_typename`,原先括号括错范围且丢了类型名);⑤ `string.char` 的两步转换(#193)——**不是范围检查**:`luaL_checkint` 是 `(int)luaL_checkinteger`,double 先变 `lua_Integer`(64 位)**再**窄化成 int,x86-64 `cvttsd2si` 把 NaN 映射成 `INT64_MIN`、低 32 位是 0 所以 PUC 接受得 byte 0;这是 C UB 且两个官方 build 互不一致(arm64 `FCVTZS` 把 `+inf` 饱和到 `INT64_MAX`、低 32 位 -1 而报错),按 `cUnsignedCast` 先例产品侧钉 x86-64 + harness 侧跳该区间,**只跳 UB 区间**(`2^53` 大但 int64 可表示、截断有定义,照旧比对);⑥ **`strtoul` 的无符号取反与溢出饱和(无 issue)**——`tonumber("-7",8)` 得 2^64-7、`("-ff",16)` 得 2^64-255、20 个 `f` 配 base 16 饱和到 2^64-1,这是**有定义的** C 所以对齐而非跳过,而原有注释声称「已登记为 diff 豁免」却**没有任何代码实现它**、分歧一直是活的。另一处 fuzz 冒烟发现的是 table 库缺整个 `, got no value` 从句(`luaL_typerror` 是 `"%s expected, got %s"`,`lua_typename` 把 `LUA_TNONE` 映射成字面量 `"no value"`;显式 `nil` 与「没传」是两种情况)。**扫描规模**:8640 种带符号 verb 写法 + 164 种 tonumber/char/insert/concat 组合 + 45 种负数×base 组合,全部零差异;60 秒 fuzz 无 crash;23 条 seed 入库并**故意包含负例**(只钉住「修好的方向」的 seed 分不出「正确的修复」和「什么都接受的修复」)。**五条教训全部已 promote**:教训 1(一个 issue 报的往往是某个结构性原因的一个实例——修完 reported case 后要枚举所有到达同一段代码的通道并逐个验证它真的执行)→ [[prove-the-path-under-test]] §4.1;教训 2(「已登记为豁免」这类声明必须能指向执行它的代码;一个没有执行体的「已豁免」比一个已知 bug 更糟,因为它读起来像已经处理过了)→ 同篇 §4.2;教训 5(**测试期望写错的方向值得单独记**——本轮三处期望错,每次都是代码对、测试错;这个方向更危险因为它诱导你去「修」一个本来正确的实现)→ 同篇 §4.3;教训 3(对参照实现要照它实际的代码路径推,把每一次隐式类型转换都展开——第一次探测漏了 `lua_Integer` 那一步,得出与真值相反的结论)→ [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义」节的更细刻度;教训 4(**「对齐 PUC」不总是对的,先分清有定义还是 UB**:有定义的对齐,UB 且跨 arch 不一致的产品侧钉参照平台 + harness 侧跳该区间)→ 该 guide 新节。**触发场景**:修完 issue 报的那个输入准备收工时、读到/写下「已知差异/已登记豁免」时、调查涉及 C 类型转换的分歧时、决定一处 C 行为要对齐还是跳过时、回归测试期望与实现冲突时看这篇。
- `memory/reflections/2026-07-28-issue197-199-stdlib-semantics.md` — 2026-07-28 三个 issue(#197/#198/#199)处理轮(分支 `fix/197-199-stdlib-semantics`,7 个 commit)。**#197 是上一轮主动填成 issue 而不是硬凑的那一条,这一轮真去做证明当时判断是对的**:`error(msg, level)` 解析了 level 却从不用它选帧,每个 level ≥ 2 都拿到最内层位置;PUC 的 `luaB_error` 调 `luaL_where(L, level)`,走到 C 帧时前缀为**空**。修法需要两层认识——① **host 边界是一个 level 不是终点**(PUC 对 `pcall(f)` 的栈是 `[f, pcall(C), caller]`,level 2 落在 C 帧上是空前缀、level 3 走到 caller 有前缀;第一版把边界当终止使 level 3 及以上全错成裸消息);② **一个边界可能代表多个叠起来的 host 帧**(`pcall(pcall, f)` 在 PUC 是两个 C 帧、在 wangshu 只有一次 re-entry,而 `st.nCcalls` 是运行总数区分不了它与 `pcall(function() return pcall(f) end)`)——所以**每帧的 host 帧计数进了 `callInfo` 的打包位**(`state.go` word2 bit 51-54,四位、饱和;由 `meta.go` 的 `st.pendingHostFrames` 在 host re-entry 时递增、下一次 Lua 帧压栈时消费清零,`verifyCISeg` 的 round-trip 等值自检覆盖新字段)。与 `lua5.1` 比对 30 种写法(五种嵌套 × level 0-5,含 `pcall(pcall,f)`)全部一致;**连带撤掉 harness 里为它加的 skip**,24 种 error level 写法现在**零 skip** 参与比对,只剩非有限 level 跳过(那是 `luaL_checkint` 窄化的真 UB)。**#198**:`%g` 用了 Go 的最短往返而不是 C 的默认精度 6(`string.format("%g",1/3)` 得 `0.3333333333333333`、C 得 `0.333333`),**显式精度本来就对**、`%e`/`%f` 的默认也本来就对,只有 `%g` 的默认不同,修法是没有显式精度时补 `.6`(13 种写法验证)。**#199 七处**:gmatch 的前导 `^` 是普通字符(PUC 的 `gmatch_aux` 直接调 `match()`、没有 anchor 处理,`gmatch("ab","^a")` 什么都不产出;给 `patternFind` 加选项而不是写第二份实现,find/match/gsub 保持 anchor)/ `assert` 第二参数走 `luaL_optstring`(只收 string 或 number,table / boolean 消息是**参数错误**而不是被 stringify)/ `math.modf` 对无穷(C 给带符号的 0,Go 的 `math.Modf` 给 NaN)/ `math.frexp` 在 DBL_MAX 附近(`floor(log2)+1` 推指数报 1025 而 C 报 1024,改用 `math.Frexp`)/ `math.rad` 溢出(`x*pi/180` 的乘法先溢出成 inf 而结果本可表示,改乘单个常数 `pi/180` 如 PUC 的 `RADIANS_PER_DEGREE`)/ `io.write` 返回**布尔**成功标志(`lua_pushboolean(L, status)`;原来一个值都不返回使 `type(io.write(""))` 报 `value expected`,**issue 里写的「返回文件句柄」是错的**,那是 5.2+)/ `os.date` 指令覆盖面(原来用 `strings.Replacer` 处理六个指令、其余原样透出使 `os.date("%j")` 返回 `"%j"`,且 `*t` 与前导 `!` 压根不认;改成指令循环覆盖 `Y y m d e H M S I p j a A b B c x X Z w n t %%`,未定义指令原样输出如 glibc,`*t`/`!*t` 返回九字段表)。另有一处**故意不对齐**:`print` 对内嵌 NUL 不截断——PUC 的 `luaB_print` 用 `fputs` 停在第一个 NUL,而 PUC **自己的** `io.write` 用带长度的 `fwrite` 不截断,**参照实现内部就不一致**,5.1 手册明确字符串 8-bit clean,对齐那个产物等于故意丢用户数据(理由记进代码)。**主动清扫**生成 296 个探针直接与系统 `lua5.1` 比对(`%g`/浮点 verb × 12 值 × 13 spec、gmatch/find/gsub × 9 pattern、assert × 7 消息、math × 9 值 × 7 函数、os.date × 16 格式、io.write),**289 个可比对项零真实分歧**(4 个差异是探针自身产物)。**五条教训**:① 主动填成 issue 的判断得到正向验证(判据 = 一个修法只在「恰好如此」时成立就填 issue 并写下**哪个前提不成立**,那份说明是下一轮的起点;process-level 首次样本暂留)/ ② **为某个 bug 加的 harness skip 必须随 bug 一起撤掉**(一个还活着的 skip 与一句无执行体的豁免声明读起来一样,是 [[prove-the-path-under-test]] §4.2 的**时间对偶**)→ 已进该 guide **§4.6**;③ **参照实现的默认值差异只在「没有显式参数」时暴露**(按参数值扫矩阵会把默认路径整条漏掉)→ 已进该 guide **§4.7**;④ **同一函数族里「参照实现自己不一致」是不该对齐的强信号**(去查语言规范、按规范选、把偏离记进注释)→ 已进 [[cross-backend-semantic-fix-sweep]]「有定义 vs UB」节作**第四格**(与「C 未指定」第三格同轮补上);⑤ **issue 里的事实描述也可能是错的,要独立核对**(哪怕那个 issue 是自己开的)→ 已进 [[prove-the-path-under-test]] §4.3 末尾。**触发场景**:一个修法只在「恰好如此」时成立、要决定硬凑还是填 issue 时、修完一个曾经被 skip 绕过的 bug 准备收工时、测格式化/转换/解析类接口时、发现参照实现在同类操作上自相矛盾时、修 issue 要照它陈述的参照行为实现时看这篇。
- `memory/reflections/2026-07-28-issue201-203-unpack-skip-thresholds.md` — 2026-07-28 三个 issue(#201/#203 是 nightly 自动开的 go-fuzz crasher,#202 是我自己上一轮开的一组预存缺口)处理轮(分支 `fix/201-203-unpack-and-skip`,3 个 commit)。**#201**:`unpack` 的上限是 `LUAI_MAXCSTACK` **减去参数个数**而不是固定 8000——PUC 的 `luaB_unpack` 调 `lua_checkstack(L, n)`,拒绝条件里有 `(L->top - L->base + size) > LUAI_MAXCSTACK`,而对 C 函数来说 `L->top - L->base` 就是参数个数,所以 `unpack({0,"",1},1,7998)` 在 PUC 抬错而 wangshu 接受;**这个结论是量出来的**:三参数调用接受 7997、拒绝 7998,而**阈值随参数个数变化**正是排除「硬编码 7997」的那条证据(只测一种写法时两者数据上不可区分),四种调用环境阈值一致、84 种写法与 oracle 一致。**#203** 是同一写法的**第三个** nightly crasher:`table.insert` 的位置窄化成约 100M 的移位跨度、刚好在产品侧 2^27 上限之下,三个都**正确且对称**、只是耗数秒,而 fuzz coordinator 启动时并行重放整个 corpus 会被这样的 seed 弄死;前两个都按既有判据挪进了 `test/regression/`,**第三次说明该修的不是再挪一个 seed 而是被接受的区间宽到 fuzzer 会持续探索它**。所以两个阈值现在**故意不同,因为它们回答不同的问题**:产品侧 `tableInsertShiftCap` 留在 2^27(**正确性**边界——在它之下 wangshu 必须做这个移位,因为 lua5.1 会做,上一轮四轮审计都在反对拒绝参照实现能完成的工作),harness 侧 skip(`internal/oracle/prelude.go`)降到 2^20(纯粹是「什么输入能待在并行 corpus 重放里」的资源问题);实测 2^20-1 以下照旧比对、2^20 及以上两侧对称跳过、普通位置不受影响,`test/regression` 仍串行跑一次真实的 100M 元素移位。**#202 七项里只有两项是真缺陷**:`pcall(error,"m",2)` 缺前缀(**已修**)、`os.time{isdst=}` 被忽略(**已修**);`coroutine.wrap` 缺前缀 / `%#g` 指数交界 / `math.deg` 差 1 ulp **三项实测与 lua5.1 一致、根本不成立**;`io.stdout` 缺失与 `debug.traceback` 缺 `[C]` 帧挪到 #205(后者因为 `debug` 整个库都不存在,无从谈起)——**那个 issue 是我自己开的**。`pcall(error,...)` 的修法:host 函数抛错经 `callHost` 直接返回、不再进解释器循环,所以 `execute.go` 的 `annotateError` 从来看不到它(上一轮给 #197 做的按帧 host 计数在这条路径上一次都没被调用),改成在 `meta.go` 的边界上标注;除了调用标注器还要对两件事——① **level 映射不同**(host raiser 不占 Lua 帧,level 1 是它的**调用者**即第一个 pending host 边界,这正是 level 1 该裸、level 2 该带前缀的原因;第一版从 `Level-1` 开始消费,每个 level 都偏一格)② **只对显式 `level >= 2` 生效**(给所有 host 错误标注是错的,PUC 的库错误经 pcall 抛出时不带位置,`TestTableConcat_ErrorTextMatchesPUC` 立刻抓到 `table.concat({1},",",0)` 报的是裸的 `invalid value (nil) at index 0 ...`);66 种 error level 写法重新比对全部一致。**`io.stdout` 为什么退出本轮**:已经做出来了(三个标准流做成 file-handle userdata + 共享 metatable,并补了三处 VM 缺口——`metaFieldOfValue` 不认 userdata、`indexWithMeta` 没有 userdata 分支、`getmetatable` 对 userdata 无条件返回 nil),但随后 `TestGCStress_RandomScripts` 失败、创建句柄之后**一个** `collectgarbage("collect")` 就以 arena 索引越界 panic——句柄在仍从 `io` 表可达的情况下被清扫,说明构造方式没有正确进入 GC 的根/追踪路径(`internal/gc/mark.go` 确实会把 `OBJ_USERDATA` 追到它的 meta 与 env ref,所以问题在构造侧不在收集器);这是运行时里第一个在 `__gc` finalizer 之外创建的 userdata,**整段撤回并开 #205,而不是带着一个会破坏 arena 的改动继续**。**主动清扫** 85 个探针直接与系统 `lua5.1` 比对(unpack 边界 × 12 值 × 4 写法、error level × 5 × 5 写法含 host raiser、`os.time` 的 isdst 六种组合、六种「host callee 抛的库错误必须保持裸」),81 个可比对项零差异。**四条教训**:① **同一写法第 N 次(N>=3)被 fuzzer 开成 issue,说明该改的是「被接受的区间有多宽」而不是再挪一个 seed**(挪 seed 只让这一条输入退出并行重放,不改变 fuzzer 下次还能生成同样的输入;是 [[prove-the-path-under-test]] §4.1「reported case 只是接受面的一个采样」在**处置侧**的形式)→ 已进 [[unreproducible-crasher-triage]]「同一写法第三次被开成 issue 时」新节;② **集合式 issue 的错误率明显更高**(「顺手记下的一组」与「专门查证过的一条」在 issue 列表里长得一样,但前者每行只经过「看起来不对」这一层;判据 = 逐项**重新核对**而不是逐项直接修,自己开这类 issue 要写明未逐项核实)→ 已进 [[prove-the-path-under-test]] §4.3 末尾作教训 5 的第二个样本 + 一条区分;③ **一个「够用的上限」必须区分它约束的是正确性还是资源**(数值由「它防的东西」决定,防分歧看参照实现的能力、防 harness 付不起看 harness 的运行方式,两者无关;与「skip 与产品规则的形状必须逐字对应」不冲突——形状一致、数值分开)→ 已进该 guide **§4.5b**;④ **知道什么时候停下来开 issue,和知道怎么修一样重要**(改动让**既有**压力测试失败、失败点在不熟悉的子系统契约上时,「再调一会儿」的成本不可估而撤回的成本是确定的;与「填 issue 而不是硬凑」是同一纪律的两个时点,#197 上一轮的正向结算证明这条判据可执行)→ 已进该 guide **§4.3b**。**触发场景**:同一写法第三次被 fuzzer 开成 issue 时、修一个「顺手记下的一组」式 issue 时、给一个阈值定数值或发现两处读同一个常数时、一个改动功能上做对了但破坏了别的东西时、量参照实现的边界时(让决定它的那个量变化一格再测)看这篇。
- `memory/reflections/2026-07-29-issue205-206-208-io-userdata-debug.md` — 2026-07-29 三个 issue(#205 是上一轮自己开的、#206 是顺着 vendored 源码查 `lua_checkstack` 调用点发现的、#208 是 nightly 自动开的 go-fuzz crasher)处理轮(分支 `fix/205-206-208-io-stack`,3 个 commit)。**#206**:`string.byte` 完全没有 `lua_checkstack` 上限,`string.byte(string.rep("a",9000),1,8000)` 返回 8000 个值而 PUC 抬错;上限与 `unpack` 一样是 `8000 - nargs`(PUC 的 `str_byte` 调 `luaL_checkstack(L, n, "string slice too long")`),但**消息要包一层**——`luaL_checkstack` 把调用者给的文本套成 `stack overflow (%s)`,所以 PUC 输出的是 `stack overflow (string slice too long)` 而不是裸串;第一版上限对了、文本错了,65 个 oracle 用例里仍有 12 个分歧(`unpack` 那边不暴露这件事,因为 `luaB_unpack` 用的是**直出**的 `luaL_error`)。顺着源码把其余 `lua_checkstack` 调用点都查了:`string.find`/`match` 的 `too many captures` 与 `coroutine.resume` 的参数路径都已正确。**#208 一行代码没改**——它的 fuzz run 跑在 `cbd0512` 上,**早于**上一轮把 harness skip 降到 2^20 的 `c07ba58`,现在这个 seed 0.00 秒就跳过,作为回归防线留在 corpus 里(上一轮教训 1「该改的是被接受的区间」的正向结算:区间改窄后同族的下一个 crasher 不需要任何新动作就消失了)。**#205 是上一轮撤回的那一块,根因只有一行**:原型直接调 `object.AllocUserdata`、**跳过了 collector 的 `LinkSweep`**,对象 header 里既没有颜色也没有 sweep 链,收集器根本看不见它,创建句柄后一个 `collectgarbage()` 就以 arena 索引越界 panic 而句柄仍从 `io` 表可达;`crescent/alloc.go` 里三个现成分配器每一个都是 `AllocX` + `LinkSweep` + `AllocCharge` **三件一起**,现在加 `State.NewUserdata` 把它固定成唯一入口,`TestIOHandles_SurviveGC` 在反复收集与分配压力下防住。**必须是真 userdata 而不是表**(PUC 报的是 `userdata`、`type()` 会露馅),为此补了两处 VM 缺口(`metaFieldOfValue` 不认 userdata 所以 `__index` 从没被查过、`indexWithMeta` 压根没有 userdata 分支),`getmetatable` 对 userdata 也开始遵守 `__metatable`;`FrameInfo` 那里还有一个 off-by-one(`getinfo` 是 host 函数、host 帧不进 `cis`,所以 level 1 是最内层 cis 帧,直接减 level 会让最常见的 `getinfo(1)` 返回 nil)。**56 个探针与 `lua5.1` 比对抓到四条细节,每一条第一版都写错**:非句柄 receiver 报 `FILE* expected, got X` 不是通用消息 / 读一个只写句柄返回 PUC 的 errno 三元组 `(nil,"Bad file descriptor",9)` 不是单个 nil / `debug.traceback` 传**显式 nil** 返回 nil(显式 nil 是一个值、不是「参数缺失」)/ `getinfo` 的消息是 `function or level expected`(这个词序);现在 54/56 一致,剩下 2 个是故意不提供的 `debug.sethook`/`getlocal`。`getinfo` 只填 P1 能诚实回答的字段(`currentline`/`source`/`short_src`/`what`/`func`),`nups`/`activelines`/`namewhat` 宁缺不假造。仍缺需要真实文件的部分(`io.open`/`popen`/`tmpfile`/`f:seek`),所以 `io.lines(filename)` 抬错而不是静默返回空迭代器。**harness 侧**:加了 `io.stdout` 之后脚本可以经一条 harness **没有捕获**的路径输出,那段文本在**两侧**捕获里都不存在——两侧仍然一致、不报分歧,而比较已经不覆盖这条路径写出的任何东西,**这是「因为错误的原因而变绿」,与一条掩盖整类输入的 skip 是同一种失效方式**;而第一版 wrapper 调了一个 prelude 里**并不存在**的 helper `__ts`,每个碰到 wrapper 的脚本都死在 `attempt to call global '__ts'`、捕获输出变成**空串**,比较照样报「一致」(两侧以完全相同的方式失败),靠打印 verdict 与实际输出而不是看 diff 才发现。**`io.read` 必须用真实二进制验证**:`go test` 不把 stdin 转发给测试进程,最初八个探针全部返回 nil、量到的是 harness 不是代码,换真实 binary 后八种写法(`*l`/`*n`/`*a`/字节数/EOF/默认/`io.stdin:read`/`io.lines`)全部与 `lua5.1` 一致。**五条教训全部已 promote**:① 一个 `AllocX` 必须与本仓的注册步骤配对,先读同族分配器再写新的(同族里出现 N 次的动作就是契约;缺一步的症状离原因很远——越界 panic 在 GC 里而错误在分配处,所以「功能全对」与「会破坏 arena」可以同时成立)→ [[design-claims-vs-codebase-physics]] **§4.1**;② `go test` 不转发 stdin,凡依赖进程级输入/输出/环境的功能都要用真实二进制验(探针结果全是同一个「什么都没有」的值时先怀疑输入没到、再怀疑实现)→ [[prove-the-path-under-test]] **§4.10**;③ 新增一条输出路径时必须同时接进差分 harness 的捕获,否则它静默退出比较范围(失效表现最安静:两侧都不捕获、都一致、都不报错,连 skip 都不留)→ 同篇 **§9.7**;④ 两侧在同一个错误上一致不等于行为一致(共享 harness 代码的 bug 会对称地打坏两侧;空输出与错误文本是两个红旗)→ 同篇 **§9.8**;⑤ 参照实现的错误消息可能被 `luaL_*` 包了一层(`luaL_error` 直出,`luaL_checkstack`/`luaL_argerror`/`luaL_typerror` 再套格式;同族函数公式一样不代表消息一样)→ [[cross-backend-semantic-fix-sweep]]「PUC 语义由 C 实现定义」节第四刻度。另有两处**正向结算**已就地补进对应 guide:#208 是 [[unreproducible-crasher-triage]]「第一步永远是版本核对」第一档的又一次命中,同时是上一轮「该改的是被接受的区间」的事后确认(区间改窄后同族的下一个 crasher 不需要任何新动作就消失,这本身就是改对了位置的信号);#205 的根因只有一行,验证了 [[prove-the-path-under-test]] §4.3b「卡在哪要具体到哪个约定」可执行——撤回时写下的「非 finalizer 路径创建 userdata 的分配与 rooting 约定」正好把下一轮的第一个动作指向同族分配器,已在该节补一段正向结算。**触发场景**:给一个新对象类型加分配路径时、被测功能依赖 stdin/tty/信号/工作目录时、给运行时加任何写外部世界的 API 之后、差分比较报一致而输出是空的或是错误文本时、抄一条参照实现的错误消息时、修一个与已修函数「公式相同」的兄弟函数时看这篇。
- `memory/reflections/2026-07-29-issue209-stale-crasher-threshold-pin.md` — 2026-07-29 issue #209(nightly 自动开的 `FuzzOracleDiff` crasher)处理轮(分支 `fix/209-fuzz-crasher`,1 个 commit,**零产品代码改动**)。**#209 本身是过期的**:seed 是 `t={0,0,0}table.insert(t,4194679298,"")print(t,(t))`、又是 insert 移位那一类,而它的 nightly run 跑在 `cbd0512` 上、**早于**把 harness skip 从 2^27 降到 2^20 的 `c07ba58`(`git merge-base --is-ancestor c07ba58 cbd0512` 返回 1),现在这个 seed 0.00 秒就跳过——按 [[unreproducible-crasher-triage]]「第一步永远是版本核对」属第一档(修法已在仓库里,issue 只是慢了一步),seed 入 corpus 作回归防线。**但同一写法第三次(#203/#208/#209 三个不同夜晚)值得一个结构性动作**:根因上一轮已修对(一个常数同时回答「产品该不该做这个移位」这个正确性问题与「一个 seed 能不能待在并行 corpus 重放里」这个成本问题,拆成产品侧 2^27 + harness skip 2^20),**但修完之后没有任何东西固定住「这两个阈值是两个数」**——回归 seed 只能表达「这个输入不崩」,表达不了「那个决定还在」:两个数被合回一个之后现有 seed 全都照旧通过(合到 2^20 则产品开始拒绝一段 lua5.1 能完成的移位而昂贵区 seed 只是被 skip;合到 2^27 则昂贵那一段重新进入并行重放而现有 seed 恰好都在跳过区,`test/regression` 原有两条又只覆盖 2^27 之下)。所以这一轮加了 `test/regression/insert_shift_cost_test.go::TestInsertShiftThresholdsStayDistinct`,**按行为断言而不是比对字面量**(两个常数一个在 `internal/stdlib` 一个在 `internal/oracle/prelude.go`、都不导出,写死 `1<<20`/`1<<27` 只会与任一侧各自漂移而测试照旧绿):`table.insert(t,-2097151,"")` 跨度约 2M、落在两个阈值**之间**,它①必须由产品**执行**(`Run` 不返错)②必须**便宜**(耗时上界,因为 skip 的取值就建立在这一段便宜这个假设上);两个方向都断是必要的,只断①时两个数合到 2^27 仍然全绿、只断②时合到 2^20 也全绿。**兄弟写法覆盖确认**:五个 seed 同一写法,没有只确认 #209 那一个——大的**正**位置、`table.remove` 的两个方向、中等跨度实测都是 1-2ms 且两侧对称,所以「昂贵的那一段」确实只在**远低于索引 1** 这一侧,skip 覆盖的就是真实的那一类。**差点按文件名模式误删一个 corpus seed**:四个 `table.insert(t,4...` 看起来同类,逐个算窄化值才发现 `4294967298 = 2^32 + 2` 模 2^32 之后是 **+2**(普通正位置插入、根本不走 skip),其余三个都是约 -100M——按模式合并会删掉唯一用例,最后全部保留(corpus 全量重放 0.62 秒,留着不花代价)。**验证**:120 秒引导式 fuzz(17.7 万次执行)干净 + corpus 全量重放 0.62 秒(此前单个这类 seed 就要数秒,正是压垮 worker 的原因)+ 全套测试 / `test/` / oracle 单测 / corpus 全绿;另记两次跑在修复之后的 nightly run(`921d3ec` 与 `5383aec`)**当时还在 in_progress**,所以「修法已在 nightly 里被验证过」这句话当时不能说,支撑结论的是本地 fuzz 与 corpus 重放。**四条教训**:① 修掉根因之后要问「有什么东西固定住这个修法吗」(自查:假设本次改动被整段 revert,现有测试会不会红?不会就说明那个决定只存在于代码与 commit message 里)→ [[unreproducible-crasher-triage]]「改完区间之后要问有什么东西固定住这个改动吗」;② 断言不导出的、跨包的阈值要按**行为**不要复制字面量(把区间做成用例、两个方向都断)→ [[prove-the-path-under-test]] **§4.5c**;③ 判断 corpus seed 是否重复要算它**实际走的分支**,不能看文件名或源码模式(文本与分支之间隔着窄化/归一化/折叠这层变换;算不出来就先留着,重放代价可量、删错用例的代价量不出来)→ 同篇 triage guide「corpus 入库常驻回归」节;④ 过期 issue 也值得看它的**分布**(版本核对回答「这一条还需不需要动作」,不回答「这一类为什么还在被开」;三种答案里「修法对但没被固定住」最容易漏,因为它长得完全像「修法还没进 nightly 跑的那个 commit」)→ 同上第 ⑨ 条同节。**触发场景**:修完一个反复出现的问题(同一写法 N ≥ 3 个 issue)准备收工时、要钉住两个不导出/跨包阈值的关系时、准备合并或删除 corpus seed 时、处理一个已经被修掉的 issue 时、说「这个修法已经在 nightly 里被验证过」之前看这篇。
- `memory/reflections/2026-08-02-issue212-219-fuzz-crasher-batch.md` — 2026-08-02 八个 nightly 自动开的 fuzz crasher issue(#212–#219)处理轮(分支 `fix/212-219-fuzz-crashers`,1 个 commit)。**版本核对的结论与上一轮相反**:八个 run 的 headSha 全是 `5383aec`,字面事实与 #209 那一轮几乎一样(全落在一个旧 commit 上),但**实际重放之后五个如实复现、三个是真产品缺陷**——[[unreproducible-crasher-triage]] 那三档回答的是「要不要在这个 commit 上复现一遍」这个**成本**问题,不回答「有没有缺陷」;nightly 每晚跑的就是当时的 master,所以「全落在旧 commit 上」是常态而不是线索。另外八个 issue 只有**六个不同的 seed**(#212/#215 是同一个 hash `1d42242f157c9754`,#217/#219 是同一个 `0150a245c776b8ed`——同一个输入在两个夜晚各被最小化一次就会开出两个 issue,先算 hash 分组比逐条查根因便宜)。**三个真缺陷**:① `error()` 的 level 参数没做类型检查(`internal/stdlib/stdlib.go`)——原先写成「转换成功才用,失败静默保留默认值 1」,而 PUC 的 `luaL_optint` 对**显式传了一个转不动的值**是抬错的,于是 `error("", 0>0)` 报空消息而 lua5.1 报 `bad argument #2 to 'error' (number expected, got boolean)`;`luaL_opt*` 是**两条**规则(缺省 / 显式 nil 取默认值 + 显式非法值抬错),而数字字符串仍然强制转换;差分细节:同一个错误在 Lua 函数**内部**抬出时带位置前缀、经 `pcall(error,...)` 直接调用时不带(第一版测试期望写错了,是查 lua5.1 才改对的)。② 调用的行号取的是**被调用表达式**那一行而不是**参数列表**那一行(`internal/frontend/parse/expr.go`)——`(0\n)()` lua5.1 报第 2 行、望舒报第 1 行,**只有跨行的被调用表达式才有差别**,单行时两者相同,所以只能靠 fuzz 撞出来;`MethodCallExpr` 本来就用方法名那一行、一并钉住。③ `string.gsub` 的 repl 类型是**惰性**校验的(`internal/stdlib/stringlib.go`)——不是少了一个检查,是**检查放错了位置**:类型判断写在替换循环里面,而第 4 个参数把循环次数压成 0,于是循环一次都没跑、非法参数从来没被看到,`gsub("", "", nil, .0)` 成功返回而 lua5.1 抬 `bad argument #3`;PUC 是在循环**之前**用 `luaL_argcheck(tr)` 校验的,**是第 4 个参数让这条路径可达的**。**两个非缺陷**:④ `math.mod` 是 harness 的**别名漏网**(`internal/oracle/prelude.go`)——`mathFn2` 报第一个缺失参数是刻意的、有注释记录的决定(两个官方构建互相不一致,x86-64 报 #2、arm64 报 #1,C 不规定 `luaL_checknumber` 实参求值顺序,没有可对齐的对象),但 `__wrapArgOrder` 只包了 `math.fmod`、没包它的 `LUA_COMPAT_MOD` 别名 `math.mod`(同一个 C 函数),于是完全相同的写法仍然可报、被 fuzzer 报了**两次**,`math.atan2` 也从来没被包过;现在按「math 表里所有取两个数的入口」全部包上。⑤ #218 是 p4 层的**重 workload 进了 corpus**(`test/regression/p4_hot_loop_promote_test.go`)——一亿次迭代、既不崩也不分歧,只是 p4 corpus 里最重的一个 seed(1.8 秒,其余都在 0.3 秒以内,约 6 倍),按 triage guide 挪进显式回归测试;**第一版做错了**:只抄了脚本、没抄 harness 的 `SetStepBudget(1 << 20)`,跑到循环结束耗 **87 秒**(harness 的 50 倍)——那 1.8 秒不是一亿次迭代的代价、是一百万步预算的代价,镜像 budget 与 arena cap 之后是 1.82 秒;按 guide 规定没有自建 in-test deadline(失败模式是「永不返回」,交给包级 `go test -timeout`)。**验证**:全套测试 + `-race`、`test/`、p3/p4 两个 build tag 全 0 失败、oracle 单测、corpus 全绿、45 秒引导式 fuzz 干净,五个 seed 全部从 FAIL 变 PASS。**四条教训**:① 版本核对的三档是**成本档不是缺陷档**,不要因为上一轮是「过期」就默认这一轮也是——无论落在哪一档都要在当前 HEAD 上实际重放一遍再分档(自查:说「这一批是过期的」之前先看有没有一条命令的输出支持这句话)→ [[unreproducible-crasher-triage]]「第一步永远是版本核对」节;② 一个刻意的豁免判据必须覆盖被豁免函数的**所有别名**(别名在 C 侧根本不是另一个函数,`LUA_COMPAT_*` 只是给同一个 C 函数多绑一个 Lua 名字;用「所有具备该性质的入口」这句可枚举的话写清单,不要往清单里补名字)→ [[cross-backend-semantic-fix-sweep]] 执行体纪律段 + `12-testing-difftest.md` §4.9e;③ 参照实现在**循环之前**做的校验不能挪到循环里面(两种写法只在「参数非法 + 循环零次」这一种输入上分岔,而 fuzzer 只要找到另一个参数能把次数压到 0;对每个懒校验问「有没有输入能让这段执行零次」)→ 同篇 guide「PUC 语义由 C 实现定义」第五个刻度;④ 把一个 fuzz seed 转成显式回归测试时要**连 harness 的边界条件一起复现**(seed 的代价由「脚本 + 该 target 的全部限制」共同决定,而 seed 文件里只有前一半;写完量一次耗时与 harness 对一下,量级不同就说明漏抄了,而且会把「因为太重而搬出 corpus」的问题原封不动搬进每次 `make` 都跑的常规套件)→ 同篇 guide「corpus 入库位置的取舍」节。**触发场景**:拿到一批 nightly crasher 时(先算 seed hash 分组、再核版本、然后无论哪一档都实际重放一遍)、给某个函数加豁免 / skip / 包装时(先查别名与 `LUA_COMPAT_*`)、把参照实现的校验搬进望舒时(记下它在控制流里的位置)、把 fuzz seed 转成回归测试时(连每一个 limit 一起抄)、实现 `luaL_opt*` 语义时(缺省与非法值是两条规则)看这篇。
- `memory/reflections/2026-08-03-issue221-222-bulk-builder-budget.md` — 2026-08-03 两个 nightly 自动开的 fuzz crasher(#221、#222)处理轮(分支 `fix/221-222-fuzz-crashers`,1 个 commit `7119391`)。**#221 过期**:seed 是 `print(pcall(math.mod))`,正是上一轮(PR #220 / `ac21b91`)给 `__wrapArgOrder` 补上 `math.mod` 别名之后修掉的那个,它的 run 跑在 `947fbda` 上、早于那个修复;**但没有因为 sha 旧就下结论**,按上一轮教训 1 实际重放确认 PASS(第一档,一行代码没改)。**#222 是真的**,而且是 concat storm 家族(#123–#167)的下一批:seed 是一个 777777776 次迭代的字符串拼接循环,target `FuzzAutoPromote`(p4 tier),run 跑在上一轮的合并提交 `093f7d1` 上。**我先走错了两次方向**——① 怀疑「这个 seed 太重」,而本地重放只要 **0.77 秒**、在自己 corpus 里只是**第三重**的(1.22s / 1.21s / 0.77s),两个更重的早该先死;② 怀疑内存,而单 seed 峰值 RSS 只有 **106 MB**(CI 是 `GOMEMLIMIT=512MiB` 软限制)、整个 corpus 并行重放峰值 525 MB。两条都被自己的测量否掉。**真正定下来的方式是去读 [[unreproducible-crasher-triage]] 里这个家族自己的结论**:#166 那轮已经查清死因是 **CPU wall-clock 撞 go-fuzz 的 10 秒 per-input 看门狗**(`internal/fuzz/worker.go` 的 `time.AfterFunc(10*time.Second, panic)`)、**不是内存 OOM**,而那份 guide 的「范围」段**点名列出了剩下的候选**:`string.rep` / `string.format` / `table.concat`「尚未按工作量记账,是同类风险的候选」。**实测证实了这个预测**:在 1<<20 step budget 内、且**完全没有触发预算**的情况下,三者各自的紧循环分别跑 **21 秒 / 20 秒 / 53 秒**,单次 `prog.Run` 就已超过看门狗而 harness 每输入跑**四次** Run。**修法**:`internal/crescent/state.go` 导出 `ChargeBulkWork`(包装既有 `chargeBulkWork`),三个 stdlib 函数各自按**产出字节数**记账,21/20/53 秒变 **46/90/70 毫秒**且预算正确触发,八种普通写法(含 1 MiB 的 `string.rep`、10000 元素的 `table.concat`)与 lua5.1 逐字节一致,回归在 `test/regression/issue222_bulk_builder_test.go`。**`table.concat` 的细节**:记账必须按**字节**而不是元素个数——它的遍历本来就被表长界住,「256 个 2 KB 的元素」按个数看很便宜、实际 53 秒。**四条教训**:① 一个家族的第 N 次复发先去读那个家族自己的结论而不是从现象重新推(答案可能在 issue 被开出来之前就写好了;与「第一步永远是版本核对」同层级,都是先用最便宜的检查排除最便宜的解释)② 记账的计量单位要与真实成本同量纲,别用「个数」代替「字节」③ 新增的资源判据要复用既有的那个计量器,不要自建第二个阈值(否则互相漂移、哪个先触发难以预测,而预算是可加的量)④「本地重放干净」对这个家族天然无效(落盘的必然是最小化后的**轻**输入,所以这是必然属性、不含缺陷信息)。**promotion**:教训 1/4 已入 [[unreproducible-crasher-triage]] 新节「那三个候选算子已结算」(同时把该家族「后续候选」那句结算成已完成),教训 2/3 已入 [[prove-the-path-under-test]] 新增 §4.5d。**触发场景**:拿到一个明显属于已知 crasher 家族的新 issue 时(先 grep 那个家族的既有结论)、给一个批量操作定预算时(先写清「代价 ≈ 什么的函数」)、给一个新算子加资源限制时(先找同类资源已有的计量器)、看到 `hung or terminated unexpectedly` / `panic: deadlocked!` 类死因时(别用单 seed 本地重放干净结案,把写法抽成不最小化的紧循环量单次 Run 耗时)、给 stdlib 加新的字符串构造函数时(产出字节数要经 `ChargeBulkWork` 记账)看这篇。
- `memory/reflections/2026-08-04-issue224-225-watchdog-margin.md` — 2026-08-04 两个 nightly 自动开的 p4 fuzz crasher(#224、#225)处理轮(分支 `fix/224-225-fuzz-crashers`,3 个 commit:`4c89799` 第一版减半、`f844bf1` 文档、`d3928f5` 审计之后改按最贵写法定值)。**产品代码零改动,这一轮的价值全在诊断上。** 两个 seed 都是 concat storm 家族(#123–#167)的第七、第八次被开:一个约 90 字节的字面量在 `for i=1,777777776` 循环里拼接,而且赋值目标被 fuzzer 写错(`qut` 而不是 `out`,所以累加器根本不增长——最小化后的 seed 天然是轻的)。两个都**既不崩也不分歧**,字节记账正常界住,本地重放各约 1 秒就正确抬「instruction budget exceeded」。**关键发现:「重放干净」这次不是全部答案。** 我照惯例先做版本核对(两个 run 都在 `093f7d1`,早于 #222 那一轮的合并),但接着做了一件之前没做的事——**把 seed 也拿到 `093f7d1` 上跑**,结果它们在**那里也已经被界住**,所以 #222 那一轮**不是**修好它们的原因,「过期、已修复」这个惯常结论**不成立**;run 时间确实早于合并,但那个事实与本轮无关。**真正的问题是余量**:`1<<20` 的 step budget 允许约 **64 MiB** 的 concat,本地每个 fuzz 子测试 **0.7–1.3 秒**,而 CI 运行器约慢 **10 倍**——这一点**仓库自己早已记在** `internal/crescent/state.go` 的 `chargeBulkWork` 注释里(`>>6` 那个比率本身就是按最慢的 CI runner 收紧出来的),于是最慢的家族 seed 投射到 CI 是 **12–13 秒**,撞上 go-fuzz 的 **10 秒 per-input 看门狗**:六个家族 seed 里**两个已经超过**、另外四个余量不到 **1.4 倍**;机制是 nightly 日志直接印证的而不是推断——`panic: deadlocked`,正是那个看门狗。**修法**:新增 `internal/fuzzbudget` 包的 `Steps` 常量(带与消费方相同的 build tag `(wangshu_p3 || wangshu_p4) && wangshu_profile`),`fuzz_auto_test.go` 与 `fuzz_p4_test.go` 四处 `SetStepBudget` 改用它,**终值 `1<<16`**,corpus 全量重放 **5.5 秒 → 0.85 秒**。**这个值第一版取错过,一次独立审计推翻了它**:我先按被开成 issue 的六个 seed 把预算**减半到 `1<<19`**,那六个 seed 确实都拿到 ≥1.8 倍余量,而**它们的邻域仍然在看门狗之上**——按四次 Run × 慢 10 倍投射,`1<<19` 下 `out=out.."x"` 循环 **14 秒**(余量 0.70 倍)、`t[tostring(i)]=i` 循环 **18 秒**(0.56 倍)、`s:gsub("%a","x")` 循环 **41 秒**(0.24 倍,是看门狗的四倍、比被修的 seed 还糟)。根因:`gsub` 每次调用按大约两倍主串计费,可实际工作远多于等额计费的 concat——**等额计费不等于等额 wall-clock**,所以**预算要由「计费相同时最贵的写法」定,而不是由「恰好被开成 issue 的那个写法」定**;`1<<16` 让最坏那条降到 **5.2 秒**、余量 **1.93 倍**,是第一个满足「差一个数量级」这个我自己文档里写下的判据的值。**「不损失覆盖」这次是实测的而不是断言的**:harness 自己的 seed corpus 在 `1<<20` / `1<<19` / `1<<16` 三个预算下 `PromotionCount` **完全相同**——那些写法在几次调用之后就升层、从不接近任何一个上限。**检测能力也验证过**:注入一个真实的 P1-vs-P4 分歧(把升层侧的返回值截断),在 `1<<16` 下仍然 FAIL、恢复后通过;90 秒引导式 fuzz 干净。**审计的第二条发现,据实记录**:`1<<20` 时 corpus 里有两个 seed 会把 arena 推到上限、`1<<16` 时没有,所以那条 arena-cap 错误分支在这里覆盖到的写法变少了——这是**收窄而不是空洞**,`test/regression/issue144_regression_test.go` 直接覆盖 arena cap,而且实测两个预算下 step budget 都**先于** arena cap 触发,这条路径本来就不是靠 step budget 到达的。**回归测试原先并不防它声称防的东西**:`test/regression/issue224_watchdog_margin_test.go` 在 harness 自己的预算设定下量代价(不是量 corpus 的耗时),但审计实测把预算调回 `1<<20`(正是那个回归)时它**照旧通过**——上界写了 1 秒而它自己的注释里推导出的是 **250 毫秒**(`10 秒 / 10 倍 / 4 次 Run`),而且它拿「四次 Run 的投射」比「一次 Run 的测量」;只用被开成 issue 的那两个 seed 也分辨不出 `1<<16` 与 `1<<19`。现在上界改成推导出的 250 毫秒、用例表加进上面那三个真正约束预算的写法,能同时抓住 `1<<19` 与 `1<<20`,**已用变异实测确认**。**一个过程细节**:第一版把 `fuzzbudget.Steps` 写成没有 build tag 的文件,golangci-lint 报 unused——消费它的两个文件都在 tag 之后、默认构建看不见;加上相同 tag 后五种构建组合都干净。**六条教训**:① 版本核对之后还要把 seed 拿到那个「旧 commit」上跑一遍,确认修法与现象之间**真有因果**(在旧 commit 上也不复现就说明归因错了;与 [[prove-the-path-under-test]] §9.6 双向验证同一纪律换了时点)② 一个上限只要「界住」还不够,**它允许的量必须与外部看门狗差一个数量级**(算法 = 上限允许的量 × 单位量的本地耗时 × 目标机器的慢速倍率,再与那台机器上每个外部超时比余量;余量不够时输入从「无界」变成「只是太慢」而看门狗分不出这两者)③ **上限要由「计费相同时最贵的写法」定,不能由「恰好被开成 issue 的那个写法」定**——判据是定上限之前把同一计费额度下最贵的几种写法都量一遍、用最坏那条定值,找候选沿着计费口(本仓 `ChargeBulkWork`)枚举;②里的「最坏」指的是这个上限之下**可达**的最坏,不是手里恰好有的最坏,我第一次用②就是在这里用错的 ④ **一个「防住某个数值」的回归测试必须用变异实测确认它真的会因为那个数值变化而变红**——判据是写完立刻把被防的值改回去跑一次,不变红就是没有防住任何东西;另外两个自查点是上界要就是推导出来的那个数(不是它的若干倍)、被比较的两个量要同量纲(投射比投射、测量比测量)⑤ 降低 fuzz 预算不等于降低覆盖,但要用**注入一个必须被发现的缺陷**证明检测能力、用一个**白盒计数器**证明覆盖,不能只看现有用例仍然通过(那是恒真观测),并且把变窄的地方如实记下 ⑥ 常量要与它的消费方共享 build tag,并按消费方的每一种组合各 vet 一次。**promotion**:教训 1 已入 [[unreproducible-crasher-triage]]「第一步永远是版本核对」第四步,教训 2/3/4/5 已入同篇新节「上限的余量:界住不等于够快」(教训 3 与 4 各占一个子节,它们是那条余量判据的两个使用前提:一个管「最坏」怎么取,一个管「防住了」怎么确认),教训 5 另在 [[prove-the-path-under-test]] §9.2 补记,教训 6 已入同篇新增 §4.10d;文档对齐落在 `docs/design/p1-interpreter/12-testing-difftest.md` §4.9a2、`docs/design/p4-method-jit/08-testing-strategy.md` §3.4、`README.md` 与 `docs/design/p1-interpreter/implementation-progress.md`。**触发场景**:判定一个 nightly crasher「已被上一轮修好」之前(去旧 commit 上也跑一遍)、给 fuzz harness 或任何跑在 CI 上的东西定资源上限时(算余量,并按同一计费额度下最贵的写法量「可达的最坏」)、写完一个「防住某个数值」的测试之后(把被防的值改回去跑一次,不变红就是没有防住任何东西)、调紧 harness 的资源限制之后(注入缺陷验证检测能力 + 白盒计数器验证覆盖)、把字面量提成常量而消费方在 build tag 之后时、看到 `panic: deadlocked` 时(那是 10 秒看门狗、不是内存,下一步量「每输入几次 Run × 单次耗时 × CI 慢速倍率」)看这篇。
- `memory/reflections/2026-08-04-issue228-229-lazy-capture-and-nested-tailcall-top.md` — 2026-08-04 两个 nightly 自动开的 fuzz crasher(#228 走 `FuzzOracleDiff`、#229 走 `FuzzP4ForceAllPromote`)处理轮(分支 `fix/228-229-fuzz-crashers`,1 个 commit `a11334b`)。**两个都在当前 master 上如实复现、都是真缺陷,而且互不相关**——两个 run 同在 `efa9aa6`,但版本核对这次不是关键。**#228:捕获的错误抬得太早。** seed 是 `print(string.gsub("","(",0))`,lua5.1 返回 `0 1` 而望舒抬 `unfinished capture`。根因是 PUC 在 `push_onecapture`(捕获**真的被读出来**的时候)才报,而 `add_value` 只有表替换 / 函数替换 / `%n` 展开三条路径走到那里——纯字符串或数字替换走 `add_s`、只展开 `%n`、从不读捕获,所以 `gsub("abc","(","r")` 正常返回 `"rarbrcr", 4` 而同一个 pattern 给 `match`/`find` 抬错;望舒在 `collectCaptures`(生产侧)就抬使所有消费者都早抬。修法:`capResult` 加 `unfinished` 标记,由 `capsToValues`(本文件对应 `push_onecapture` 的函数)在**读取时**抬。**我第一版修错了**:只在 `%n` 展开那条路径加检查,而**表替换会无条件读捕获 1**(PUC 的 `add_value` 在 `lua_gettable` 之前就调 `push_onecapture`),于是 `gsub("alo","(.",{})` 本该抬错却成功——**抓到它的是官方测试套 `pm.lua:193`,不是 oracle seed**(那个 seed 只覆盖「不该抬错」这一侧)。**#229:嵌套尾调用返回后没恢复调用方的 top。** seed 最小化到 `o2={n=function() return 0 end} function f(b) return b:n() end for A=0,70 do f(o2) A={0} end`,P1 成功、P4 抬 `SETLIST: not a table`。**诊断过程本身是教训**:我先后排除了三个「看着很像」的方向——peroptranslator 里那段注释逐字写着 `NEWTABLE head + SETLIST → "SETLIST: not a table"`(探针一行没打出来、那条路径根本没走到)、stale base(每个副作用后都 `RefreshJitCtxAddrs`,不解决)、最后在 `doSetList` 抬错点打栈迹拿到 `executeLoop -> doSetList`、**没有任何 JIT 帧**,才明白抬错的是普通解释器、破坏在更早的已升层调用里。真因三步链:gibbous 的 `TailCall` helper 以 `entryDepth = ciDepth-1` 开一层**嵌套** `executeFrom`,「离开这一层的 entry 帧」**不等于**「离开最后一个 Lua 帧」;`doReturn` 在终止分支仍按 `dst + wantedN` 收窄 top,caller 的活寄存器留在 top 之上;GC 的 `visitThreadValues` 把 `[top, size)` 当陈旧残留清成 nil,`A={0}` 的 NEWTABLE 结果被清掉、紧随的 SETLIST 报「not a table」。`tag=65528` 就是 `value.TagNil`,直接指向 GC 清理。修法对照 PUC `lvm.c` `OP_RETURN` 的 `if (b) L->top = L->ci->top`(PUC 从不为 Lua callee 重进一层 `luaV_execute`,所以这笔恢复天然归 RETURN 自己做),**是同一条纪律的第四处**——`doReturn` 非终止分支 / gibbous `DoReturn` / `callHost` 三处早就在做。两个回归测试都用「把修复还原掉」实测确认会变红。五条教训:把错误从早抬改成懒抬要在**每个** materialize 点各加一次检查、两侧都要有用例 / fuzz seed 只覆盖它自己那一侧,改参照实现的语义边界之后必须跑官方测试套(seed 单向、官方套双向)/ 「有一段既有注释正好描述了这个症状」是最容易走错的线索,先用探针证明那条路径真的被执行 / 抬错的位置不是缺陷的位置,栈迹里**缺少**哪一层本身就是信息、具体坏值往往能指认写入者 / 一个共享层的恢复动作已有 N 处兄弟路径在做时,第 N+1 处漏做就是缺陷(grep 数一数,数量不齐就是信号)。**想把一个错误从早抬改成懒抬时、改动了参照实现的某个语义边界之后、在仓里读到一段既有注释精确描述手头症状时、拿到「X 报错」形式的缺陷时、给共享调用层加一条新的返回路径时看这篇。**
- `memory/reflections/2026-08-05-issue232-234-address-width-and-gsub-escape.md` — 2026-08-05 三个 nightly `FuzzOracleDiff` crasher(#232、#233、#234)处理轮,**定时巡检任务(每天 03:07,前一天刚建)的第一次实际处理轮**,1 个 commit `2b1aeb4`(在 `master` 上,不是任务描述里的 `fix/232-234-oracle-diff`)。**三个都在当前 HEAD 上如实复现**,而 **#232 与 #233 是同一个根因家族**(引用值地址的宽度),所以实际是两个修复加一个既有兄弟缺陷。**#232:地址逃过了归一化**——seed 是 `io.write(0)print(print)`,`io.write` 不带换行于是输出是 `0function: 0x...`,`compare.go` 的 `addrRe` 用 `\b` 锚定类型名而 `0` 与 `function` **都是 word 字符、`\b` 不匹配**,地址整段逃过归一化,凡是这种写法都必然分歧;改成用「不是字母」锚定左边,既修好又保留了那个锚点原本的用途(让脚本自己打印的 hex 仍可比,这是当初 PR review 要求加锚点的理由),替换随之改成只重写匹配内部的 `0x...` 再把前导字符放回去。**#233:脚本测量地址的长度,归一化物理上到不了**——seed 是 `t={0}print(#tostring(t))`,PUC 是 21(`%p` 在本平台给 12 个十六进制位)、望舒是 17(`0x%08x` 给 8 位),**长度在归一化之前就已经分歧**,`NormalizeOutput` 拿到的字节流里只有 `21` 和 `17`(与 `string.len(0/0)` 4 对 3 完全一样的机制);修在 **oracle 侧**——prelude 包一层 `tostring` 把 PUC 自己的地址渲染成望舒的 8 位宽度,与仓库已有的 NaN 符号位处理是同一个选择(**在渲染处消除差异**),望舒侧的宽度于是成为契约由 `TestAddressLengthIsComparable` 钉住。这也解释了 **#233 为什么看起来是间歇的**:#232 那种「原始形式」是否分歧取决于真实地址恰好需要几位十六进制,而 #233 那种「测量长度」是稳定分歧的——同一个根因、两种可见度。**#234:gsub 替换串里的 `%` 转义(引擎侧 `internal/stdlib/stringlib.go`)**——PUC 的 `add_s` 两个后果都没跟上:① 末尾的 `%` 让它 `i++` 然后**越过长度读 `news[i]`**,读到 `lua_tolstring` 保证的 NUL、于是每次匹配吐一个 NUL 字节(`gsub("aaa","a","x%")` 是 `x\0x\0x\0`,hexdump 实测),望舒要求 `i+1 < len(rb)` 把末尾的 `%` 当成了字面量;② `%` 后面**任何非数字**都被原样吐出,`gsub("a","a","%z")` 是 `"z"` 而不是报错——这一半是**既有缺陷**,在 base 上同样分歧,只修第一半会留一个已知的洞。「照抄一个越界读的怪癖」本身不显然正确,但 oracle 比较的是字节:不照抄,凡是以 `%` 结尾的替换串就永远不可比。三处修复各自用「还原该修复」实测确认会变红,三个 seed 入 `testdata/fuzz/FuzzOracleDiff/`。**五条教训**:① 一批 crasher 里同一个根因可能以不同的**可见度**出现,不要按症状分成两件事(判据:同一批里两条都涉及同一种引擎相关的值时先假设同源)→ [[unreproducible-crasher-triage]];② 归一化只能救「被打印出来」的差异,救不了「被测量」的差异 → [[prove-the-path-under-test]] §9.0a;③ 一个正则锚点的**反例集合**要用它自己的失败输入去检验,不能靠读它的注释(注释写意图、正则按字符类语义执行)→ 同篇 §9.1a;④ 修参照实现的语义时「只修被报的那一半」会留下已知的洞(一个分支的**所有出口**都要对)→ [[cross-backend-semantic-fix-sweep]] 第七个刻度;⑤ 照抄参照实现的越界读怪癖是可以的,前提是判据来自「**比较的是什么**」(逐字节就必须抄,只比语义可以不抄)→ 同篇「有定义 vs UB」表第五格「有定义但可疑」。**拿到一批 nightly crasher 准备分头查时、想给差分 harness 加一条归一化时、改一个带锚点的正则时、修完 fuzz 报的那条参照实现语义分歧时、看到参照实现里一个明显可疑的行为时看这篇。**
- `memory/reflections/2026-08-09-issue236-241-curl-timeout-misread-as-enospc.md` — 2026-08-09 六个 nightly 自动开的 issue(#236–#241,标签 `ci` 不是 `bug`)处理轮,分支 `fix/236-241-nightly-enospc`,1 个 commit `81acb57`,**产品代码零改动,改动全在工程侧**(`scripts/fetch-lua-tarball.sh` + `scripts/test-fetch-lua-tarball.sh` 新增,`ci.yml` / `bench-acceptance.yml` / `nightly-diff-fuzz.yml` 四处 call site,`Makefile` 的 `test-scripts`)。**一个根因,而且我一开始判错了 —— 头条是那个误判。** 当天早上的定时巡检里我把 `exit code 28` 读成 **ENOSPC(磁盘写满)**,据此在飞书报告里写了「B 类:我们自己的 workflow 暴露,磁盘写满」,还建议「在 oracle 源码构建前腾空间或缓存构建产物」。**28 是 curl 的 `CURLE_OPERATION_TIMEDOUT`。** 我看到 `28` 想起 `ENOSPC` 的 errno 是 28 就直接接上了,而**那一步是 `curl`,shell 的退出码来自 curl 的退出码表、不是 errno 表** —— 两张表恰好在 28 这个数字上都有条目,而且都在讲一种资源类失败(空间 / 时间),所以错的那个解释读起来完全自然。**证据一直在日志里,两条都免费**:① `apt` 在 16:55:03 成功结束,然后**沉默 2 分 15 秒**才报 exit 28,而磁盘写满是**立刻**失败的、不会先卡两分钟;② 另外两个 run 的日志里 `no space left` / `ENOSPC` / `disk full` 出现次数都是 **0**。**真根因**:失败步骤 `install lua 5.1.5 oracle (source build)` 里是裸的 `curl -sLO https://www.lua.org/ftp/lua-5.1.5.tar.gz`,**没有 `--max-time`、没有 `--connect-timeout`、没有 retry**,上游一次可达性抖动就让它挂到 shell 放弃。**后果比「一次红」更糟**:这一步失败让后面三个差分 fuzz 步骤被 **skip**(step 5/7/9 是 `skipped`),于是 nightly 报 failure 而**实际什么都没测**,那一晚的探索预算是零 —— 而红色的默认含义是「跑了并且发现了问题」,两者在 Actions 页面上是同一个红叉。**而且一次抖动开了六个 issue**:infra issue 标题里嵌了 `${{ matrix.variant }}`,p1/p3/p4 生成三个不同标题,现有的按标题去重看不出它们是同一件事,**两次抖动 × 三个 tier = 六个 issue**;而 infra 失败天然横跨所有 tier(divergence 失败才天然属于某个 tier,同一个标题模板服务了这两类性质不同的事件)。**修法**:四处 call site(`ci.yml` ×2 的 macOS cache-miss 分支、`bench-acceptance.yml` ×1、`nightly-diff-fuzz.yml` ×1)统一改用 `scripts/fetch-lua-tarball.sh`,四个性质 —— **限时**(`--connect-timeout 15 --max-time 120`)、**重试**(`--retry 4 --retry-delay 5 --retry-all-errors`,**不加 `--retry-all-errors` 的话 curl 不重试超时**,而这一轮的失败方式恰好是裸 `--retry` 重试不了的那一种)、**解包之前校验官方 SHA-256**(截断或被替换的下载不能被静默编译进差分 oracle,那种失效是无声的)、**复用已校验的 tarball**(cache 命中完全跳过网络);**不做镜像**是明确取舍 —— `github.com/lua/lua` 没有 5.1.5 tag,任何重新打包的副本都过不了官方 checksum,而多一个源降低的是取包失败率(重试已经在降它)、checksum 防的是「取到了错的东西并且编译了它」。infra issue 标题改成按 `${{ github.run_id }}` 去重(三个 job 共享同一个 run id),第二、三个 job 改为评论,tier 从标题挪进 body 与评论(**去重键与信息量是两件事**)。新增 `scripts/test-fetch-lua-tarball.sh` 挂进 `make test-scripts`(该目标同时在 `make all` 与 `ci.yml` 独立 job 里跑,门禁纪律来自 #179),五个用例(checksum 不符须拒绝且不解包 / checksum 正确须解包 / 已有校验通过的 tarball 而 URL 指向不存在的文件时仍成功=走了缓存 / curl 调用须带三个标志)**全部离线**(用 `file://` origin),于是这个测试自己不会变成一个会抖的 CI 步骤 —— 而这正是本轮改动的用意;每个用例都用变异实测过。**五条教训**:① **头条 —— 同一个数字在不同的退出码表里含义不同,先确认这个退出码是「谁的」**(shell 报的是最后一条命令的退出码、不是 errno;自查:说出「28 是 X」之前先说出「28 是**谁的** 28」,说不出主语就是在猜)→ [[unreproducible-crasher-triage]]「静默死亡先做两步分类」的前置一格;② **时间形式是区分故障类别的免费证据**(立刻失败 vs 沉默几分钟后失败能一刀切开好几类;错误码是显式的、时间戳是隐式的,注意力天然落在码上;自查:说出「这一步花了多久」与「这类失败应该花多久」,量级不符就是分类错了,并反向搜一次那一类的特征字符串)→ 同篇,与「上限的余量」是同一个量的前瞻 / 回溯两个方向;③ **一个失败步骤让后续步骤 skip,会造出「报红但什么都没测」**,比直接失败更坏(判据:给 CI 加依赖步骤时问「这一步挂了之后,后面被 skip 的步骤里有没有本来该产生结论的」,有的话失败信息要写「未执行」;**本轮如实记下这半没做完** —— infra issue 的 body 说的是失败原因类别,不是「本轮探索预算为零」)→ 同篇新节「CI 自动化本身的失败信号」;④ **自动开 issue 的去重键必须与「一次事件」的粒度对齐**(判据:先问这类事件的**天然单位**是什么,再对每一维问「这一维变化时是同一件事还是两件事」,答「同一件事」的不进键;多加一维只会让分组更细、永远不会报错,所以这个错误没有任何自动信号)→ 同节;⑤ **检查「某个标志存在」的测试不能 grep 整个文件**(第一版 grep 文件,`--max-time` 从 curl 调用里删掉后照旧通过,因为那个词在注释里还在;判据:检查代码属性的测试要作用在**代码本身**上,一个注释就能满足的测试没有在测代码;自查:把属性删掉、保留一切注释跑一次)→ [[prove-the-path-under-test]] §9.1b,给 [[2026-07-25-issue179-test-go-fuzz-retry-revive-round]] 教训 1 补上「**positive marker 的作用域也要证明**」这一半(本轮那个用例是有 marker 的,假绿的原因是 marker 的搜索范围里有一个非代码的来源)。**看到一个数字退出码时(先说出它是谁的)、分类一个 CI 失败时(先把时间戳减一遍,再反向搜那一类的特征串)、给 CI 加依赖步骤时、给自动开 issue / 告警加去重键时、给 shell 脚本或配置写「某个标志存在」的自测时、给一个「防住外部抖动」的东西写测试时(让它离线)看这篇。**
- **`2026-08-09-issue236-241-curl-timeout-misread-as-enospc.md` 路由的后续更正**（对应上面那条 #236–#241）：最终 infra issue 去重键是**日期**，不是 `run_id`；`--retry` 本身已覆盖超时，`--retry-all-errors` 只是额外放宽；infra issue body 已明确报告差分步骤是否执行，不再有「未说明本轮未测」的缺口。详细更正已同步进原反思与 [[unreproducible-crasher-triage]]。
- `memory/reflections/2026-08-09-runner-wall-clock-assertion-audit.md` — 共享 CI runner 将本地约 1.5 秒的 bulk-budget 用例放大到 24.2 秒（约 16 倍），证明原有 wall-clock 上限测到的是机器负载而不是 step-budget 语义；删除后审计同包兄弟断言，又发现 3.63 秒对 5 秒、仅 1.38 倍余量的潜在假失败。稳定判据：优先直接断言预算触发、路径执行、阈值分离等离散性质；只有机器间波动小于余量时才让 wall-clock 决定成败，否则仅记录耗时；发现一处后立即搜索同包、同机制的兄弟计时断言。详见 [[unreproducible-crasher-triage]]。
- `memory/reflections/2026-08-11-issue244-oracle-segv-unpack-int32.md` — 2026-08-11 定时巡检发现的 issue #244(`bug` 标签,p1 `FuzzOracleDiff`)处理轮,seed 是 `A(unpack({},0X80000000))`,分支 `fix/244-unpack-int32-oracle-crash`,1 个 commit `a22de53`。**版本核对这次很干净**:失败 run 的 headSha 正好**就是当时的 master**(`e0e7b34`),所以「撞的是已修复代码」这条最便宜的解释一步就排除,不可能是过期 seed;在当前 HEAD 上重放确实复现(第二档)。**崩的是 oracle,不是望舒**:重放拿到 **SIGSEGV 且栈迹在 cgo 里**(`_Cfunc_wangshu_oracle_exec`),拿真的 `lua5.1` 二进制直接试同样 **dumped core**,而望舒对同一输入抬 `too many results to unpack`、行为正确、从不崩,所以望舒侧没有任何东西要修。**机制**在 `luaB_unpack`(`internal/oracle/_lua515/src/lbaselib.c`):`n = e - i + 1` 是 int 运算,`i` 窄化之后那个 这个减法本身是**有符号溢出 UB**,gcc `-O2` 把 `n <= 0` 检查当不可达删掉,`lua_checkstack` 随后接受一个**负的** `size`(拒绝条件对负值两个都为假)→ 段错误;**同一份源码在 -O0 下干净抬错,所以崩溃依赖优化等级**。**修法**是在 oracle prelude 里包一层 `unpack`、索引落在崩溃窗口时抬 `LimitSentinel`,让差分**跳过**这一类输入,与仓库既有的几个 PUC UB range 处理方式一致(**会死的 oracle 不能当参照**)。**但本轮复核推翻了那个窗口的口径,而且两个方向都不对**:注释与 commit message 都写「窗口是实测出来的,只有 `-2147483648` 与 `-2147483647` 会崩」,那些测量本身都是真的,**但全部取自 `unpack({}, i)` 这一种写法**,而 `{}` 让 `e = #t = 0`;机制里带着 `e`,所以窗口是一条**随 `e` 平移的界线**——`unpack({1,2,3}, i)` 崩到 `-2147483644`、`unpack({}, i, 2147483647)` 崩到 `i = 0`。补上 `e` 之后规则完全可推导(`crash ⟺ i32 <= e32 且 (e32-i32+1) > INT_MAX`,窄化后的 int32 值),**10 组「先写下预测再实测」全部吻合**。两个方向的失效:**太窄**——`e > 0` 时窗口整段挪出守卫范围,`A(unpack({1,2,3},-2147483646))` 与 `A(unpack({},-1,2147483647))` 经真实 `FuzzOracleDiff` 实测让**整个测试二进制被 SIGSEGV 拿下**(#244 的 crash 家族还活着);**太宽**——`i >= 2147483648` 一律跳过,而 `unpack({1,2,3},4294967297)` 窄化成 `i32 = 1`、两侧都返回 3,实测现在被 **SKIP**(静默削覆盖面,窄化之后的值才是判据的输入)。**本轮是文档轮 + 复核轮,没有改任何代码或测试**,区间修正记在反思的「遗留」一节。**三条教训**:① **差分 harness 报的 crash,第一个要问的是「谁崩了」**——看到 SIGSEGV 先读栈迹属于哪一侧再决定往哪边查,「fuzz 报了 crash」不等于「被测实现有 bug」,参照实现也会崩而且更容易被误判成我们的问题(拿真的参照实现二进制跑一次最便宜、单独就能定性)→ [[unreproducible-crasher-triage]] 分诊入口那一族的新一格;② **「实测」与「推导」不是互斥的:一个被称为「实测得来」的边界仍然需要机制来确定它的形状**,否则实测只覆盖到采样点所在的那一格,而且这个错误会自我确认(在 `e = 0` 上扫得再密也发现不了缺了一个维度)→ [[cross-backend-semantic-fix-sweep]] 第八个刻度 + [[prove-the-path-under-test]] §4.5 正向结算(同一条判据在同一个函数上第二次被需要);③ **限制类守卫的两侧用例要沿「机制」的每个维度取,不能只沿被报的那个写法取**——两侧检查做了仍然两个方向都错,因为它防住的只是「在已采样的那一格里宽了或窄了」;两种失效反馈不对称(太宽表现为测试通过、太窄表现为下一次 nightly 再开一个 issue),自查办法是把守卫条件与机制条件的**自由变量逐字对照** → [[prove-the-path-under-test]] **§9.6a**。**差分 harness 报 crash / SIGSEGV 时(先读栈迹属于哪一侧、拿真参照实现二进制跑一次)、给一个 UB / 溢出类行为划边界时(先写出决定它的表达式,让每一项各变化一格)、加 skip / 豁免守卫时(守卫条件与机制条件逐字对照自由变量)、读到一句「这个边界是实测出来的不是推导的」时(问一句实测沿哪些维度取的样)看这篇。**

---

## 设计文档集路由(llmdoc 之外的源文档)

- **入口**:`docs/design/architecture.md` §0 是文档集地图(包布局/组件图/tier 映射);`docs/design/p1-interpreter/00-overview.md` 是 P1 施工计划与**跨文档定稿决策速查**(§4)。
- **战略层**:`docs/design/roadmap.md`(引用时用 `docs/design/roadmap.md` (§N))。
- **验收口径**:`docs/design/p1-interpreter/12-testing-difftest.md` §10 是 26 条验收口径总表(所有「待定口径」的收口点;评审轮新增第 26 条 ColInt64,勿引用旧版 25 条说法)。
- **实现现状**:`docs/design/p1-interpreter/implementation-progress.md`——P1 里程碑进度、验收数字、收尾轮完成表与**设计文档对账表**(原「已知简化」清单已在收尾轮全部完成;值栈/CallInfo arena 化等 P3 迁移留口见对账表)。**问「实现到哪了、实现形式与设计文档的差异」看这篇。**
- **P2 文档集**:`docs/design/p2-bridge/`(子目录 8 文件)——从 `00-overview.md` 进入(文档地图 §0、P-Bridge 里程碑 §4、跨文档定稿决策速查 §6);PB0-PB7 + 后续优化轮 #1-#4 全过线,实施现状对账见 `p2-bridge/implementation-progress.md`。
- **P3 文档集**:`docs/design/p3-wasm-tier/`(子目录 10 文件,2026-06-13 从单文件 380 行扩展)——从 `00-overview.md` 进入(文档地图 §0、P-Wasm 里程碑 §4、跨文档定稿决策速查 §6、四项税 wazero 外包 §8)。子文档分工:`01-spike-gate`(开工检查 <150ns)/ `02-translation`(全 opcode 翻译表 + WAT)/ `03-memory-model`(arena 收养 wazero memory)/ `04-trampoline`(跨层互调 + CallInfo bit50)/ `05-safepoint-gc`(三类 safepoint 收口 P1 06 §12)/ `06-ic-feedback-consume`(非投机消费)/ `07-coroutine-thread-rule`(线程级 tier 规则)/ `08-testing-strategy`(V1-V18 验收)。
- **P4 文档集**:`docs/design/p4-method-jit/`(子目录 10 文件 ~8200 行,2026-06-24 从单文件 360 行扩展,与 P2/P3 同档详细深度)——从 `00-overview.md` 进入(文档地图 §0、P-JIT 里程碑 §4、跨文档定稿决策速查 §6、四项税收回 wazero 外包 §8、不变式 §9、风险 §10)。子文档分工:`01-launch-judgment`(立项检查 + luajc 档锚点 + 两条进入路径)/ `02-template-direction`(方向裁决 = JSC Baseline 风格 per-function 模板编译,候选谱系否决)/ `03-speculation-ic`(IC 反馈消费 + f64 快路径 + guard 显式比较硬约束 + P2 三态不变 + P4 内部 p4SpecState 子状态机叠加,方案 A)/ `04-osr-deopt`(OSR exit 协议 + 物化 = memmove + 栈槽真相不变式 + 再训练防 deopt 风暴)/ `05-system-pipeline`(四项税自付 + W^X / icache / trampoline 四件套 + JIT 世界边界 + arena base 重载)/ `06-backends`(amd64 + arm64 双后端共享骨架 + per-arch 发射器,寄存器约定,模板族分级,双架构 CI 双跑)/ `07-p3-retirement`(P3 去留决策框架,缺省退役)/ `08-testing-strategy`(luajc 档验收 + V1-V22 P4 增项 + deopt 注入 + 双架构差分套)。**当前状态:多 PJ 已完成**(2026-06-28):PJ0 包骨架 + bridge 注入 / PJ1 spike 检查 + amd64 工程组件 / PJ2 投机三形式(reg-reg/reg-K/chain-KK)/ PJ3 FORLOOP 字节级 inline 实测 7.15-25.41x over gopher-lua(超 luajc 档 4.4x)/ PJ4 表 IC 完整六路径 + 严密 IsTable guard / **PJ5 CALL void 220 子 + TAILCALL 102 子 + SELF inline 完整 0..7 参 + 嵌套 + 错误冒泡 + V18 -race + p4SpecState 子状态机骨架**(2026-06-28 单会话 16 commits 累计交付)/ PJ7 接上 ~25 类形式 byte-equal / PJ8 arm64 字节级模板矩阵完整 + Compile 端接上 / **PJ10 通用 per-opcode 翻译器初版交付**(2026-06-30,21 commits `a94bcec..HEAD`,80 e2e 全过):Go 端回放 + 占位 mmap stub 物理路径(设计稿 §3 native amd64 multi-BB CFG emit 是性能前提非语义前提,留作 followup);35/38 opcode 接住——PJ10a 直线(MOVE/LOADK/LOADBOOL/LOADNIL/GETUPVAL/SETUPVAL/RETURN) + PJ10b 算术/一元/CONCAT(ADD/SUB/MUL/DIV/MOD/POW/UNM/LEN/NOT/CONCAT) + PJ10c 比较 diamond + and/or diamond + FORPREP/FORLOOP + TFORLOOP + PJ10d 表/调用(GETTABLE/SETTABLE/GETGLOBAL/SETGLOBAL/NEWTABLE/SETLIST/CALL/TAILCALL/SELF/CLOSURE/CLOSE),不接 VARARG(设计永不接) + JMP/TEST/LOADBOOL C!=0(需真 CFG 留 followup);设计稿 §14 新加进度章诚实记录物理路径分歧;反思见 [[2026-06-30-p4-pj10-perop-translator-round]] / **PJ10 native emit 接上**(2026-07-01,分支 `feat/pj10-native`,`ed2235b..0c9db3a` ~17 commits):真 amd64 / arm64 原生码 emit(CFG builder + 两遍 label resolver + 35 opcode 每 arch 一份 emit)+ inline 18 op mmap-safe 子集(MOVE/LOADK/LOADBOOL/LOADNIL/ADD/SUB/MUL/DIV/NOT/EQ/LT/LE/TEST/TESTSET/JMP/FORPREP/FORLOOP/RETURN,mmap + Go morestack 物理不兼容硬约束)+ arch-aware `fixupKind` label resolver(amd64 rel32 / arm64 B26 / arm64 Cond19)+ `PreferNative` 多 BB + big-BB 收窄门(精确匹配 shape-spec 触达不到的形式)+ tail-call gibbous dispatch;**V15b heavy 三本 P4 native > P3 wasm 达标**(HeavyArith 2.3x / HeavyRecursion 2.3x / HeavyFloatloop 3.0x,10 iter × 3 samples,amd64);V14 luajc 档无回归;arm64 runtime e2e 留 CI followup(需 linux/arm64 host,QEMU 不支持 mmap RWX→RX);反思见 [[2026-07-01-p4-pj10-native-round]] / **PJ10 must-beat-P3 op 集扩面(2026-07-02,分支 `feat/p4-beat-p3`,30 commits `dc9baf0..79bd6dc`)** ✅:承 PJ10 native emit,通过 **exit-reason 协议**(`jitCtx.exitArg0` 打包 `(helperCode, a, b, c, pc)` + `ExitInlineHelper` RET + Go 端 `nativeCode.Run` dispatcher 分派 host 方法 + `RefreshJitCtxAddrs` + 从 `codePage + resumeOff` 重入)解 mmap+morestack 物理不兼容,把 opSupported 从 18 op(mmap-safe 子集)扩到覆盖 amd64 上的 CALL(密度门 `totalOps/callCount>=16`,B=0/C=0 拒)/ SETTABLE / NEWTABLE(IC ArrayHit/NodeHit gated)/ UNM(inline sign-flip + guard)/ GETUPVAL / SETUPVAL / GETGLOBAL / SETGLOBAL(NodeHit-IC gated + inline gen-check 快路径 + 18-bit Bx 拆两 9-bit payload slot)/ 多值 RETURN(HelperReturn 终结不重入);**正确性收口三 fuzz 种子驱动**:(a) `f7f0bb1a` x86 SSE arith 结果 NaN(`0xFFF8...`)与 NaN-box tag 空间别名 → arith inline 加 result guard 路由 `host.Arith` canonicalize / (b) `4b3d10ff` **`insertNewKey` Brent 式重定位改 slot 但没 BumpGen**(`internal/crescent/rawtable.go`)——解释器 `icGetTable` per-access 复验 NodeKey 掩盖多年,gen-only inline 快路径(P4 native GETGLOBAL NodeHit + P3 wasm `emitGetGlobal` 一样的)立刻 UAF;**关键 invariant**:key→slot 语义变化必须 BumpGen(producer 契约由最严 consumer 定义) / (c) `d9bce2e` LT/LE inline UCOMISD 缺 IsNumber 守卫使 reg operands boolean 时静默比较位模式;三新种子入 corpus `testdata/fuzz/FuzzP4ForceAllPromote/`;**bridge/analyzer 三改**:(i) safe stdlib alias dataflow 追踪——`funcState.localAliasAsts` 记 `local sqrt = math.sqrt` 类 AST(非 name),`AnalyzeProtoWithOuter` 加 `outerAliases` 参数,visitor 的 `safeAliases` 处理 reassignment/shadowing/scope 失效(`internal/bridge/analyzer.go` + `internal/frontend/compile/{funcstate,stmt,analyze_on}.go`)/ (ii) `MinPromotableLener` 可选接口——后端可覆盖短 proto 升层地板,`SetP3Compiler` 时快照(P4 实测 fixed Run 成本 ~111ns > 解释器 78ns tiny proto → 地板停 10)/ (iii) `considerPromotion` 加 forceAll retry window(前几次进入保持 TierInterp 让 IC-gated 后端见到 warmed IC);**性能结果**:25/27 P4-vs-P3 对基准 ≥ +2%(多在 +10%~+85%),fannkuch 5.8ms→0.6ms,nbody 升层后 P4 44.2ms 赢,2 对 CallOnly-auto 是解释器-vs-解释器噪声平手;README perf 表整节更新(f736c92 + 79bd6dc「≥1.5x 加粗」规则一致化);**已知 followup**:issue #37 arm64 exit-reason port(arm64 shim-free 设计天然适合)、issue #39 P3 nbody 回归(alias 追踪副作用,43.5→89.7ms 慢 2x;共享分析器改进可能是 per-backend 回归的实证)、task #57;反思见 [[2026-07-02-p4-beat-p3-opset-round]] / PJ11 luajc 档突破(loop 7.15-25.41x)。**arm64 exit-reason 端口已收口(2026-07-03,issue #37/#40 阶段 2,分支 `feat/p4-arm64-exit-reason` 8 commits)**:7 步移植(dispatcher 循环 + GETUPVAL/SETUPVAL → CALL 密度门 → GETGLOBAL/SETGLOBAL NodeHit inline → GETTABLE/SETTABLE ArrayHit inline + NEWTABLE → UNM(顺带修双 arch -NaN 静默变 nil 既有 bug)→ 多值 RETURN → arith/LT/LE 恢复(比较条件码换 FP 安全族 MI/PL/LS/HI)),两 arch native 接受面重新同构,darwin/arm64 M5 Pro 实测 heavy+realworld P4 全面 ≥ P3(HeavyArith 2.03× / HeavyFloatloop 2.47× over P3),详 `p4-method-jit/implementation-progress.md` §14.6 + 反思 [[2026-07-03-issue37-arm64-exit-reason-port-round]];**arm64 inline EQ 接受 K 操作数(2026-07-03,issue #56/#52,`3c4d4ce`)**:`inlineRawEqArm64` 镜像 amd64 `inlineRawEq`——K 操作数把 boxed 常量烧成 `mov imm64`(string K 槽在 LoadProgram 期 intern、升层前 bits 稳定 ⟹ ptr-equal ↔ string-equal),reg 操作数保留 ldr 路径,`AnalyzeNative` 的 EQ 严格 reg-reg 门放宽到接受任意 in-range K,补齐上轮「两 arch 接受面同构」最后一处缺口——这是 arm64 fannkuch 93-op 热 proto 的唯一静态升层阻塞项,放开后 M5 Pro 实测 force 3.62ms→0.33ms(10.9x over interp / ~7.5x over gopher-lua),auto 与 force 一致;**amd64 段到段(seg2seg)CALL 分派已交付(2026-07-06,分支 `feat/issue50-emit-call-inline`)**:段内 GETUPVAL inline + 双语义 RETURN 扩到 MultiReturn proto + arith/compare/GETUPVAL/嵌套 CALL 接受面,消掉每次 gibbous→gibbous 调用的 exit-reason 往返,call 密集内核反超 gopher-lua(fib 10.5x / fannkuch 6.9x / binary-trees 1.35x over gopher-lua,详 `p4-method-jit/implementation-progress.md` §15.7);arm64 段到段 CALL 镜像实现已写完 + 交叉编译过(真机验收经 CI 挂 issue #61);**deopt 机制现状(issue #66 裁决,2026-07-07)**:OSR 骨架经调查确认**非死代码**,区分三套 deopt——① 函数级 OSR 物化(重建解释器帧续跑,04 正文设想)从未实现,已由 #50 虚拟帧 + deopt-redo 取代(裁决 `spike/p4callinline/DECISION.md`)不再需要;② `internal/gibbous/jit/p4state.go` 的 spec-template guard-miss 记账(`p4SpecState` / `onOSRExit` / `P4Deoptimized`)在 amd64/arm64 **已激活**(`SpecP4DeoptHits` 实跑增长);③ #50 的 seg2seg deopt-redo(`segCallDeopt`)活跃,#66 已补 `SegToSegDeoptCount` 探针 + 注入测试;**P4 native opSupported 接受面扩展(2026-07-07,issue #52/#68/#69)**:三项能力面(非收益)扩展,均双 arch + prove-the-path e2e byte-equal——**#52 SELF + CONCAT 纳入 opSupported**(amd64 白名单 30→32 op,一次 `obj:method()` 或一次数值 `..` 不再拖垮整函数升层;整条 emit/dispatch/host 流水线本已存在走 exit-reason dispatch,唯一缺口是 `AnalyzeNative` 见到 SELF/CONCAT 就拒整个 proto;arm64 侧发现旧 `emitSELFArm64`/`emitCONCATArm64` 挂在 legacy shim 通道故重写走 exit-reason + 清孤儿 shim)/ **#68 SETLIST 纳入 opSupported**(binary-trees `return{left,right}` 的 NEWTABLE+SETLIST proto 得以升层,拒 B=0 fill-to-top / C=0 批量计数在下条指令字两形式,同 CALL B=0/C=0 先例;arm64 `emitSETLISTArm64` 一并从 legacy shim 重写走 exit-reason)/ **#69 字符串常量 LOADK 放开 F7-a 门**(P4 接受面最大隐性缺口——此前含活跃字符串字面量 LOADK 的 proto 被整体拒收,一个 `s="x"`/`t.name.."-"`/`obj:m("key")` 就让整函数连同算术/循环/CALL 掉回解释器;spike 方向 A 把字符串槽 GCRef 作 imm64 烤进 mmap 段,镜像 EQ-K #56,三前提 per-State 归属 / Consts 编译期已 intern / arena 非移动;emit 零改动,`emitLOADK` godoc 登记 #12 copy-compact GC 的烤死 GCRef 重定位面依赖);**三项均为接受面而非逐 op 加速**——字符串/表 op 密集 kernel 仍约等于解释器,价值是不再因一个 gap op 拖垮整函数;详 `p4-method-jit/implementation-progress.md` §16-§17;**issue #52 已收口(2026-07-09,PR #99)**:补齐最后四个 op——TAILCALL(HelperTailCall 终止 run,镜像 HelperReturn;host 臂经尾随死 RETURN B=0 multret 收尾)/ TFORLOOP(HelperTForLoop verdict 经 exitArg0 回段分支,镜像 HelperCompareSlow)/ CLOSURE(HelperClosure,upvalue 伪指令字是数据非 op,全部翻译器 pc 走查统一经共享 `nextRealPC` 步进,Bx 走 GETGLOBAL 18-bit b|c 拆分)/ CLOSE(HelperClose 普通往返),双架构同步,`opSupported` 白名单自此只剩 VARARG 一个永久设计门;**顺带修复共享三层的存量 bug**:`doTailCall` 尾调用 host 函数传父帧定长 nresults(应为 -1 multret,PUC `luaD_call(L, ra, LUA_MULTRET)`),`return unpack(t)` 丢尾部返回值 P1/P3/P4 全中,独立 commit + conformance `tailcall_host_multret` 锁三层;详 `p4-method-jit/implementation-progress.md` §21 + 反思 [[2026-07-09-issue52-close-round]];**跨 arch CI**(2026-06-29,PR #27 本批 14 commits):linux/arm64 检查翻 true ✅ + darwin/arm64 codepage 真实现(`internal/jit/jitcgo/` 子包 forward 模式保主库零 cgo)+ macos-latest M1 接入 CI 安全子集(到达 CI 绿,真 execute SIGSEGV 留 followup PR Mac 物理机调试,darwin/arm64 检查暂 false);**P3 列内核 loop 形式已超 luajc 档:loop 2.95x over P1 = 7.2x over gopher-lua > luajc 档 4.4x over gopher-lua;但 table 0.88x / call 0.52x / mixed 0.99x 仍 ≪ luajc 档列内核形式——P4 立项动机不在 loop 而在非 loop 形式**;实施现状对账 + 34 项跨文档回填请求(RJ-1~34,撤回 RJ-8/9/10,方案 A)见 `p4-method-jit/implementation-progress.md`。**P4 状态由原「架构决策深度」升级为「详细设计深度 + 多 PJ 已完成」**,与 P2/P3 同档。**PR #83(2026-07-08,fixes #78/#80/#81)修复两个 issue #67 auto-mode 覆盖轮(PR #75)`FuzzAutoPromote` 上线数天内抓到的 P4 native-tier miscompile**:FORPREP 两 arch 共有的缺 `IsNumber` 数字守卫(nil limit 静默零迭代退出而非报错,新 `HelperForPrep` exit-reason 退 `host.ForPrep`)+ 深度 native 递归破坏值栈的叠加 bug(`nativeCode.Run` 入口捕获的 `base` 在 host 调用 growStack 重定位值栈段后变悬垂 [[design-claims-vs-codebase-physics]] §2 第二实例 + seg2seg 段内直调无边界检查溢出初始栈段,新 `jitCtx.valueStackEnd` + 两 arch fast-body 守卫修复);反思见 [[2026-07-08-pr83-forprep-stackgrow-fallout-round]]。**amd64 GETTABLE/SETTABLE NodeHit 跨 Run inline 补齐(2026-07-08,issue #67,PR #82)**:把 arm64 上已修正的 identity-free NodeHit inline(hmask 边界 `Index <= hmask` + `nodeRef != 0` guard,替换烤进段的 TableRef 身份 guard)移植到 amd64,共用 `emitTableNodeHitPrelude`,推翻 PR #74「amd64 NodeHit ~3% 回归故 arm64-only」历史裁决(那 ~3% 是身份 guard 跨 Run 100% 落空的白费开销、inline 从没真生效);实测 amd64 n-body auto 43.5ms→7.0ms(~6.2×),跨 Run exit-reason dispatch 875k→50k,fib/binary-trees/spectral-norm/fannkuch 无回归,新增 `TestPJ10_TableNodeHit_CrossRunInline` 断言每 Run dispatch<500;反思见 [[2026-07-08-issue67-amd64-nodehit-crossrun-round]]。**math.* intrinsic emission(2026-07-08,issue #77,PR #87)**:CALL 站点 IC 观察到被调是已知纯数值 host closure(sqrt/floor/ceil/abs/max/min)时段内直接发射硬件指令(amd64 SQRTSD/ROUNDSD/sign-clear/UCOMISD-select;arm64 FSQRT/FRINTM/FRINTP/FABS/FCMP+FCSEL),不再 exit-reason 往返 Go host closure;intrinsic 检查挂在 host closure 必走的 protoID guard-miss 慢路径故热 Lua 调用零开销,byte-equal 依据在 `value.NumberValue` 的 canonNaN 规范化(result-NaN guard 抓走负 indefinite 路由 host);是 #67 NodeHit inline 后 n-body 剩余 dispatch(几乎全是 sqrt)的直接后续;反思见 [[2026-07-08-issue77-math-intrinsics-round]]。**PR #86(fixes #85,files #89)深递归双 bug 修复**:#85 深升层递归误报假 "C stack overflow"(P4 路径超 seg2seg 深度上限后每层递归是真 Go 重入链撞 `nCcalls` 上限 200),修法 `gibbousReentryCCallCap = maxCCallDepth/2` 软水位线、四个 gibbous 派发入口超线回落解释器平坦执行,深度约束回到 20000 与 P1 逐字节一致;#89(存量 bug,被 #85 修复暴露)seg2seg 段内直调 `sub sp` 在 morestack 不可触发的 NOSPLIT 窗口穿透栈守卫踩坏相邻堆对象,`segToSegDepthCap` 128→16(worst case 608B<`StackNosplitBase` 800B),提回需接 05 §3.4 自管 spill 栈;反思见 [[2026-07-08-pr86-deep-recursion-nosplit-round]]。

**P3 PW0-PW10 全卷已收口**(spike 检查过线 → 包骨架 → 直线 opcode + trampoline + VS0 → 算术/比较 → 控制流 relooper + FORPREP/FORLOOP → TFORLOOP 泛型 for → 表 IC inline 固化跳哈希 → CALL/TAILCALL 跨层三向互调(base 刷新解 growStack 重定位 UAF)→ CLOSURE/CLOSE → 线程级 tier 规则(协程不升层)→ PW9 端到端总验收(正确性轴 V1-V13 三方逐字节 + V17 四 build + V18 -race + 性能轴 V14 loop 核 2.58x≥2x 全过));全 38 opcode 除 VARARG 外全部可翻译,**PW10 消除 gibbous→gibbous 跨层调用税:R1/R2/R3/R3.5 + 零跨界 ①/基建-a/基建-b/③a/③b/④-i 骨架/顶层升层 callOnStack 全过线**(Phase 0 spike `spike/p3indirect/` 裁定 Arch-2 共享 imported funcref 表而非 rebuild-all,决策报告 `spike/p3indirect/DECISION.md` + R1 共享 funcref 表基建 + R2 CallInfo→linear-memory 迁移 = 延后的 VS0-e,4 word/帧 + R3 `call_indirect` 直接分派消 `code.Run` 重入 + R3c-fix + R3.5 host helper 零分配 + 零跨界 ① top mirror 字基建 + 基建-a closure slot 缓存 + 基建-b proto cache 段 + ③a savedTop + ③b emitReturn 守卫快路径 + ④-i emitCall 守卫骨架 + fastCallHits mirror 字 + callOnStack 顶层升层 + TopLevelUplift 探针 + emit 原语 i64.add/i64.or 保留供未来 ④-ii);**本机 Xeon 6982P 2s×3 count 实测基线(2026-06-16)**:**loop 2.95x(+10% over R3.5 2.67x,③ RETURN 拆帧真实收益)/ table 0.88x / call 0.52x / mixed 0.99x**;**call 0.52x 是 bench kernel 结构性架构边界**(profile `/tmp/call.prof` 证四 kernel body 含 ReasonUnknownCall F2-b 不可升 ⟹ 顶层升层 + ④ emitCall fast body 均对 bench kernel 无显著效果);**④-ii fast body 未交付**(预估上限 0.57x 仍 <1x,ROI/UAF 不利留 followup);**PW10 收口为「已完成子里程碑 + 架构边界文档化」**,旧文档所述「四核全翻面」+「剩 R4/R5 待实现」失实须替换;实施现状对账(PW0-PW9 ✅ + §12 PW10 R1-R2 + §13 R3+R3.5 + §14 零跨界 RETURN 拆帧 + §14.8 ④-i + §14.9 顶层升层 + §14.10 ④-ii 架构边界 + §7-§11 实现 vs 设计对账表)+ 跨文档回填请求收口表(RW-1~11)见 `p3-wasm-tier/implementation-progress.md`。
- **工程化机制**:`docs/design/engineering.md`——Git hooks(三件套含 commit-msg 强制 `type(scope):`)/CI workflows(ci + nightly-diff-fuzz 自动开 issue + nightly-benchmark)/Makefile 唯一任务入口/`-race` 硬门禁/oracle 供给(lua5.1=5.1.5 校验 fail-fast)/M0 工程地基里程碑。与 12 的分工:12 管「测什么」,engineering 管「机制怎么搭」。其中 nightly-diff-fuzz 已完成(`.github/workflows/nightly-diff-fuzz.yml`,triage 内联非独立脚本,差异对账见 doc-gaps;2026-07-10 起每 4 小时一轮 × 小时级 epoch 滚动种子,加频动因与重叠无害分析见 engineering.md §3.2);nightly-benchmark 未建。**`internal/oracle` cgo-embedded 官方 Lua 5.1.5 in-process oracle(2026-07-12,PR #128)**:vendored 5.1.5 源码放在 `_lua515/` 下划线目录(go tool 默认忽略),走上游 `etc/all.c` 惯例单编译单元 `luaall_c`,build tag `wangshu_oracle_cgo && cgo` 门控,默认 build 仍完全零 cgo。`FuzzOracleDiff` 用 go-fuzz 变异的任意源码在同进程内经 wangshu P1 crescent 与 PUC 5.1.5 做逐字节差分测试,上线首轮 fuzz + argsweep 扫出并修复 35 处此前不被覆盖的 P1 vs PUC 语义分歧(stdlib / 词法 / 前端 codegen / 值语义四域,含字符串共享 metatable / `tonumber` C99 `strtod` 接受面 / 表构造器字段源码顺序 / `string.format` C sprintf 语义 / 常量折叠拒 div-by-zero 与 NaN 结果等)。PR 门禁挂 oracle-smoke job + nightly 加 p1 腿 45 分钟长时间跑,详见 `docs/design/engineering.md` §3.2 与 README 正确性第 5 层。
- llmdoc 不搬运设计文档内容,只做压缩与路由;深入实现细节一律回源文档。
