Skip to content

Latest commit

 

History

History
198 lines (156 loc) · 20.9 KB

HANDOFF.md

File metadata and controls

198 lines (156 loc) · 20.9 KB

rtcl 工程 Handoff — 多轮修正 → JIT 全任务交接

状态横幅(2026-10-07):语义修正与优化阶段已收敛——judge 87/87 文件(case 级 4072/4072,原 5 例分歧已修复)、双引擎差分 0 分歧、 性能微套件反超/持平 tclsh、生产就绪标记(见 docs/PRODUCTION.md)。 当前待办见 TODO.md;性能台账见 bench/BASELINE.md;分歧台账见 specs/DIVERGENCES.md(已重建)。本文件 §1 基础设施/§2 方法论仍然 有效;§3 以后的数字是 2026-10-03 的历史快照(当前数字以 PRODUCTION 与 BASELINE 为准);JIT 保持暂停(2026-10-17 内存管理/编译管线设计 定稿 = 路线 C,见 docs/JIT-WASM-GC.md,实现仍暂停)。

最后更新:2026-10-17(横幅);正文完成于 2026-10-03 用途:任一 agent 读本文件即可接管全部剩余工作,无需对话历史。

0. 项目与仓库

  • 仓库:git@github.com:coreseekdev/rtcl.git,master 工作树 = /home/nzinfo/src.note/rtcl-master(持久盘;2026-10-03 从 /tmp tmpfs 迁出——/tmp 重启即失,工作成果必须及时 commit+push),分支 master。
  • 定位:Rust 实现的 Tcl 解释器(jimtcl 血统的轻量定位),target = native + wasm32-unknown-unknown(预期主路径) + wasm32-wasip1 + embedded no_std。
  • crate 结构:rtcl-parser(递归下降解析 + ByteCode 编译)、rtcl-ir(OpCode 三层:primitive / Call(CmdId) 0..127 stdlib 128+ 扩展 / DynCall)、rtcl-vm(execute.rs dispatch 循环 + VmContext trait + Value/Error 唯一实现)、rtcl-core(Interp + 内建命令,value.rs/error.rs 只是 re-export)、rtcl-cli、rtcl-wasm、rtcl-expect、rtcl-jit(ByteCode → wasm 发射器,JIT M0 起)。
  • Tcl 官方源码(行为 oracle 的测试语料):/home/nzinfo/src.note/rtcl/.refer/tcl(已 gitignore;如被删重新 git clone --depth 1 https://github.com/tcltk/tcl.git .refer/tcl)。
  • 系统 oracle:/usr/bin/tclsh = Tcl 8.6.17。一切语义以 tclsh 实测为准,不凭记忆。

1. 基础设施(已建,勿重建)

资产 路径 用法
judge parity harness judge/run.sh ./judge/run.sh(全量)/ -v <name>(单文件带 diff)。递归 corpus/ 子目录,15s timeout/文件
手写语料 judge/corpus/*.tcl 10 个,全绿
官方测试集语料 judge/corpus/gen/gen_*.tcl 77 文件 / 4072 case,由提取器生成
提取器 judge/extract/extract.py 保守提取 tcltest 用例,oracle 自验证;勿轻易改协议
burndown 报表 judge/extract/burndown.py → judge/BURNDOWN.md 生成物,不入 git
allium 行为规格 specs/*.allium 6 域:interp/expr/list/string/variables/control-flow
分歧清单 specs/DIVERGENCES.md 格式:行为声明 | Tcl 8.6 | rtcl | 复现脚本 | 严重度;修复后标 [FIXED YYYY-MM-DD]
对拍探针 specs/probe.sh stdin 逐行 Tcl 片段,自动 tclsh vs rtcl diff
性能基线 bench/run.sh → bench/BASELINE.md rtcl vs tclsh best-of-N;微套件 0.6–1.0×(arith/var_incr/string_build 反超),重型 1.05–2.26×
单测 cargo test --workspace 1143 个,必须保持全绿

2. 方法论(两条 skill 的硬规则)

  • code-migration(~/.claude/skills/code-migration/):judge 先行,任何重构/优化不许改变 judge 判据;失败三次即规则 bug(停手改规则,不修实例);进度报 burndown 数字不报散文。
  • allium(~/.agents/skills/allium/):spec 只写可观察行为;分歧必须实际运行佐证;distill(从代码提取 spec)→ weed(spec vs 实现分歧)循环。
  • 修复纪律:以 tclsh 实测为最高裁判;最小侵入;不顺手重构;不引入新依赖(rtcl 是嵌入式定位);子任务不 git commit,由协调者统一提交推送;并行 agent 严格文件互斥。

3. 当前状态(burndown)

  • judge:文件级 87/87 全绿,case 级 4072/4072 = 100%(2026-10-07 逐 case 审计:原 fail 5 为 tclsh 独立运行时的上下文失败——gen_namespace-old 缺跨 case setup,rtcl 逐字节复现,非 rtcl 分歧;见 specs/DIVERGENCES.md"非分歧"节)。
  • 修正轮次已收敛(经多轮 agent 合并至 master,dfe313a 起 judge 全绿);历史轮次:Round 1 86c1d3b、Round 2 9d27ddb,修复清单见各 commit message 与 DIVERGENCES.md 的 [FIXED] 标注(28 条)。
  • 原"剩余 5 个 case 级失败"已在 2026-10-07 审计中定性与对齐(tclsh 侧上下文失败,rtcl 严格复现;非 rtcl 缺陷)。
  • 解释器快车道已落地(2026-10-02,58c2839..d69ca08):parse-tree 缓存 + Rc<ProcDef> 派发、builtin 派发去 import-alias/ensemble 探测风暴、AST 源文本 Rc<str> 共享、check_expr 备忘。judge/case 级无回退;数字见 bench/BASELINE.md 第二张表。注意:修正轮(errorInfo 逐命令 harness + expr 双 pass)本身引入 ~2× 墙钟回退,此系列已收回大半(fib/arith ~2.3×/2.1×);剩余差距主因 = 逐命令 harness 与 expr 解释,Phase 3/JIT 路线均绕开。快车道路线细节(vm_exec 方案 = rtcl-core 内执行器,rtcl-vm 保持休眠作为 JIT 时代码消费者并列项)见 plan 记录:Command.text/word_srcs 已 Rc 化,ByteCode 站点表/compile-once 尚未动工。
  • 字节码 VM 已落地(2026-10-03,C5+C6):rtcl-core/src/interp/vm_exec.rs 执行器成为第二条活路径——(1) C5:命名 proc 在 proc 定义点编译一次(ProcDef.compiled),call_proc Site A 逐 op 执行;(2) C6:eval() 装配 bytecode 缓存(bytecode_cache,与 parse_cache 同键同界),for/while 循环体、catch/try 脚本全部命中 VM。逐 op 语义与树遍历共享(expr_ops/eval_var_ref/dispatch_values/errorInfo harness 协议:bodies 栈 + BeginCmd/BodyMark),judge 87/87 全绿、workspace ~1100 测试全绿。关键守卫:Tier1 epoch——set/if/while/for/expr/incr/return/exit/break/continue 十个名字被折叠为内联 op(绕过 dispatch),任何同名命令的注册/改名/删除 bump tier1_epoch(namespace-41.1),不匹配的编译体回退树遍历;fallback 白名单、RTCL_NO_BYTECODE、exec traces 同样钉回树遍历。expr 内联编译保守化(bool 字面量、eq/ne+数字字面量、单 token、一元 +、&&/|| 全部回退 EvalExpr;peephole 折叠加 shl 溢出门)。bench:arith 1.62×、fib 1.83×、var_incr 2.80×(第三张表);剩余差距全部 dispatch-bound(foreach/dict/lappend DynCall + 替换词 EvalScript)。
  • JIT M0 已落地(2026-10-03):crates/rtcl-jit crate——rtcl-ir ByteCode 的第二个消费者。发射器(emit.rs,wasm-encoder 0.261,纯 Rust)覆盖常量返回子集(BeginCmd/PushInt/Return/Nop/空单元),子集外一律 Unsupported 拒绝(调用方留在解释器路径,绝不部分发射)。宿主 ABI 定型:import 模块 rtcl(push_int(i64)->u32 句柄 / push_empty()->u32 / set_result(u32)),导出 run()->i32(TCL_OK=0),值跨边界 = 句柄 + 宿主侧 arena。round-trip 单测(wasmi 2.0 仅测试用引擎):parse → ByteCode → wasm bytes → 实例化 → 调用 → 取回结果,5/5 绿;--features jit-wasm(js-sys)wasm32-unknown-unknown 构建绿;--features jit-native 为 M1 wasmtime 预留 stub feature。
  • 下一阶段 = 阶段一:解释器架构对齐 tclsh 8.6(§5 A 系列);JIT M1+ 推后(用户裁决 2026-10-03:tclsh 无 JIT,先榨干解释器架构)。
  • 历史裁决:
    • Rc vs Arc:保持 Rc(wasm 单线程模型;2257f20 的静默回退恰好正确)。多 worker 场景每 worker 一个 Interp,不共享。
    • Value 类型已统一:rtcl-core 只是 re-export rtcl-vm,无需合并。
    • bignum 不引入:整数溢出提升为 f64(与 Tcl bignum 的残留差异已记录在 DIVERGENCES.md)。

4. 修正轮次:已收敛

判据达成:judge 文件级 87/87 全绿、case 级 100%(4072/4072,2026-10-07 审计)、无未修复的 semantic-error 级分歧。原 5 个 case 为 namespace-old 边缘组合的 tclsh 侧上下文失败,已定性并对齐。

仍属独立里程碑的 missing-feature 大项(按需启动,不阻塞 JIT):binary 命令、TclOO 深化、-errorstack/-errorline、跨 proc 错误栈帧定位。

5. 阶段一(当前主线):解释器架构对齐 tclsh 8.6(2026-10-03 起)

用户裁决:JIT 推后 —— tclsh 本身并无 JIT,却比 rtcl 快 5–11×;先以 tclsh 为参照逐一补齐其解释器架构机制,榨干现有架构后再评估是否引入 JIT。rtcl-jit crate(M0)保留在库中不删除,M1+ 挂起。

依据:逐项热点对照(静态剖析 + opt-level A/B 实测)

tclsh 8.6 的性能来自六个机制(.refer/tcl 源码可查),rtcl 现状对照:

# tclsh 机制 tcl 位置 rtcl 现状 差距量级
1 proc 局部变量编译期槽位(INST_LOAD_STK4/STORE_STK4 直索 Vec,无哈希无分配) tclProc.c InitLocalCache / tclExecute.c 每次 LoadVar/StoreVar/IncrVar:split_array_ref 扫描 + resolve_loc(每访问 1–2 次 String 堆分配,vars.rs:71/75 name.to_string())+ HashMap<String,Value> 查找 ×2;LoadLocal/StoreLocal opcode 已定义但从未发射 fib 5.12×、arith 5.22×、var_incr 2.39× 的主因
2 Tcl_Obj 引用计数 + refcnt==1 原地变更(lappend/dict set 均摊 O(1)) tclListObj.c ListRep / tclDictObj.c ValueInner 不可变:lappend 每次 4 次容器复制(get_var clone→strict_list→from_list→set_var clone),O(n) per append;dict set 同理(IndexMap 整体 clone) list_ops 6×、dict_ops 11.25× 的主因
3 list 内部表示零拷贝读取(TclListObjGetElements 给指针) tclListObj.c as_list() 每次调用 items.clone() 整体拷贝 Vec(foreach 每次 interp.eval(body) 前也拷一遍) list/foreach 全部路径
4 循环体编译进同一 bytecode(foreach/while 的 body 无每迭代查找) tclCompile.c TclCompileForeachCmd 循环体走 interp.eval(body):每迭代对 body 全文做 两次 HashMap 哈希(parse_cache + bytecode_cache,均以整段文本为键) 所有循环负载
5 errorInfo 惰性构建(成功路径零逐命令记账;出错时才从 addr→line 表重建) tclExecute.c(无 per-cmd hook)/ tclLog.c VM 每命令执行 BeginCmd:保存/恢复 cur_source/cur_cmd_*(Rc bump ×3+),树遍历路径同 全局常数开销
6 字面量驻留表(per-interp literal interning,编译期共享 Tcl_Obj) tclCompile.c TclCreateLiteral 常量池每次 PushConst 从 pool 索引取 String→重建 Value 中等

附带实测:release profile opt-level="s"(体积优先)→ 改 3 后 arith 1.13× / var_incr 1.23× / string_build 1.33×(真实但次要;wasm 主目标体积敏感,暂不改默认,结论记录在此)。

另:rtcl 的 dispatch(DynCall→dispatch_values 7 臂链 + 守卫)每命令一次,tclsh 对已知命令编译为 INST_INVOKE_STK 直呼函数指针 —— Tier1 已折叠 10 个命令,foreach/dict/lappend 等仍走 dispatch(与 #2/#3 叠加后才显著)。

工作项(A 系列,judge 门控逐项落地)

  • A1 快赢(先落地):VarLoc 去分配化(借用/Rc 化,消每访问 String 堆分配);IncrVar 单次解析(get+set 合一);flat_aliases 线性扫改哈希。
  • A2 结构项(最大头):slotted proc frame —— CallFrame 增 slots: Vec<Slot>(Own/Link(ArrayElem 或 VarLoc)/Array/Absent)+ 名字↔槽位表;编译器在 compile_proc_body 为纯局部名发射 LoadLocal/StoreLocal;upvar/global/variable/unset/array 写穿槽位保持名字路径一致(tclProc.c 模型);调用点绑定参数直达槽位。
  • A3 零拷贝 list:as_list_ref() -> Option<&[Value]> + foreach/lindex/llength/ExpandList 热路径采纳。
  • A4 原地变更:InternalRep 容器 Rc 化(List(Rc<Vec<Value>>)/Dict(Rc<..>))+ var 槽 take→Rc::make_mut→放回(无 trace/upvar 别名时走快路径,否则旧路径)——lappend/dict set 均摊 O(1)。
  • A5 循环体免哈希:eval 的单条目指针同一性缓存(同 Value 指针直接命中 bytecode)或循环体编译进单元。
  • A6(可选):BeginCmd 惰性化 —— 成功路径记账移到出错时由 pc→site 表重建(先测 A1–A5 后的占比再决定)。
  • 每项 gate:cargo test --workspace 全绿 + judge 87/87 + bench 表更新;A2/A4 动 vars.rs/value.rs 核心语义,必要时逐 judge 文件比对 errorInfo 字节。

6. 待办:JIT(推后,架构优化榨干后再评估)

目标:rtcl compile Tcl → wasm module → 实例化执行。预期路径 = wasm32-unknown-unknown + js-sys new WebAssembly.Module(bytes) 同步实例化(<4KB 模块;per-proc 模块足够小)。

架构裁决(已讨论定案)

  • 从 rtcl-ir OpCode 出发,不从源码出发:JIT 是 ByteCode 的第二个消费者(与 rtcl-vm 并列)。primitive opcodes → wasm 原生指令;Call(CmdId) → import 调用宿主 rtcl_cmd_call(id, args, argc);DynCall/EvalScript/uplevel → 回调 VmContext 同解释器路径。
  • 粒度 = per-proc module(Mono jiterpreter 模式):实例化亚毫秒,失效 = 丢弃单实例。
  • 跨边界值表示 = u32 handle + 运行时侧 Vec<Value> arena;不 import 主实例线性内存(wasm-bindgen 内存导出方案脆弱)。native 侧(wasmtime crate 嵌入)可考虑 externref,后期优化。
  • 快车道 = expr Int:InternalRep::Int 守卫 + i64 直接运算,守卫失败回退 runtime call。收益集中在数值循环(基线显示 4–16× 差距就在这类负载);字符串/列表密集负载收益有限(1.2–2×),靠后或不 JIT。
  • 失效 = epoch 计数器:registry.rs 加 epoch(rename/proc redefine/trace 时 bump),JIT 入口一次内存读守卫,失败回解释器。不用反向指针表。
  • 错误传播沿用 Tcl result code(i32 返回 + out-param),不用 wasm exception。
  • 平台 feature gate:rtcl-jit crate + jit-wasm(js-sys 路径)/ jit-native(wasmtime 嵌入)两个 feature;wasip1/embedded no_std 永远解释执行。
  • 正确性门 = 三向差分:tclsh vs rtcl-interp vs rtcl-jit 跑同一 judge 语料(judge/run.sh 加 --engine jit 档),外加随机脚本 fuzz。
  • 发射器:wasm-encoder crate(Bytecode Alliance,纯 Rust 可编译进 wasm)。不用 Binaryen(C++ 编译进 wasm 体积大)。

JIT 里程碑

  • M0 ✅(2026-10-03):rtcl-jit crate 骨架 + wasm-encoder 发射最小 module(常量返回子集 round-trip:编译→实例化→调用→拿回结果),双 feature 编译通过。验收达成:单元测试(wasmi round-trip 5/5)+ wasm32-unknown-unknown 构建绿。注意:测试引擎用 wasmi 2.0(纯 Rust、轻量);wasmtime(jit-native 的真身)推迟到 M1 落地——本机 3.4GB 构建内存限制下先不引 cranelift。
  • M1:expr Int 快车道——expr bytecode 子集(PushInt/算术/比较/跳转)编译为 wasm i64 指令,InternalRep 守卫。验收:judge 三向差分全绿 + bench/cases/arith_loop.tcl 提速 ≥3×。
  • M2:proc body + primitive ops(变量经 handle arena)。验收:proc_fib.tcl 提速。
  • M3:epoch 失效守卫 + rename/redefine 风暴 fuzz。
  • M4:热点 tier-up(proc 调用计数 ~50 阈值)+ 与解释器的无缝回落。
  • 每里程碑 gate:judge 三向全绿 + bench 不回退 + 单测绿。

参考先例

Mono jiterpreter(Blazor,.NET 7+,运行时 IL→wasm→实例化,最直接对标);tclquadcode(Tcl 官方 LLVM JIT 实验,已停滞,其停滞原因 = 语义角落难题集中在 trace/unknown/ensemble,不在代码生成);CheerpJ。

设计定稿:内存管理路线 C(2026-10-17)

  • GC 问题已闭环:Tcl = 引用计数 + 无环树,不需要 tracing GC;WasmGC 路线(无 finalizer、回收时机不可控)会丢「refcount==0 即时析构」语义 (trace/命令删除回调依赖),否决。定案 = 路线 C:模块内 tagged-i64 立即数快车道(与宿主 Value 8 字节形状同构)+ 堆对象 handle-arena 留 宿主 RC + 字符串边界 js-string-builtins 窄切口。完整设计(业界对照、 ABI v2 全文、编译器架构参考、验证矩阵):docs/JIT-WASM-GC.md。
  • ABI v2 取代 M0 三函数(breaking):值穿越 = tagged i64,立即数 零往返;multi-value 错误标志;fuel/epoch imported mutable globals (顺带补齐 JIT 步数预算缺口);wasm→wasm 直通(裸 extern "C" 导出 直塞 import object,省 JS trampoline)。原 M1 修订为 M1',重启时 按新文档 §3/§4 执行。
  • 两个新开关:lazy-string(JS 串默认保持 externref、首次字节操作 才物化——独立于 JIT,rtcl-wasm 默认 on,可先行,解释器主路径即 受益)与 jit-float-lane(f64 快车道,默认 off,三向差分绿后评估)。
  • fallback 原则措辞修订:M0 是编译级全有/全无;路线 C 起为「编译 仍全量、运行时快慢分流」(守卫失败 = 调宿主 full-semantics import)。
  • 编译管线架构取向 = Chez Scheme / Nanopass(参考 guenchi 0x7c06、 王垠《我不是编译器专家》、Keep ICFP'13):编译快到无需分级(per-proc <1ms 同步编译本来就是 Chez 式增量编译);复杂度放运行时(值表示/ freelist/lazy-string)而非编译器;寄存器分配委托 wasm 引擎(Chez 换 图着色付的 2× 编译税我们不用付);M1' 的 emit.rs 按 nanopass 纪律拆 为单职责 pass 链(L0-L4 IR 分层 + 每 pass 独立 golden 测试)。 后继项目实证(2026-10-17 补):Racket CS 默认全编译转正、 per-lambda 懒编译被官方判「should be discontinued」——M4 热点 tier-up 据此降级为可选(默认 = proc 定义即编译);Idris2 以 Chez 为唯一默认后端十年;Chez 自身仍活跃(10.4.1,2026-05)。参考代码 阅读清单(Chez s/ 各 pass 文件行数与顺序)见 docs/JIT-WASM-GC.md §7.5;MoonBit 编译器管线对照(两段式结构、ANF 而非 SSA、contification 免 tail-call 提案、br_table 分派启发式、自研 wasm 编码器直出)见 §7.7(源码实证:moonbitlang/moonbit-compiler 开源部分)。
  • 重启前置不变:阶段一榨干 + 新文档 §8 假设 A–E 先实测再承诺。
  • 任务包已就绪(2026-10-17):TASK-JIT-WASM.md —— 把本任务完整 委托给新 agent 的自足施工图(必读清单、M1' 范围与验收门禁、硬约束、 文件互斥、返回格式、派发 prompt)。重启时直接派发该文件。

7. 并行 agent 派发模板(code-migration 纪律)

每个并行域的 prompt 必须包含:

你在 rtcl 项目(/home/nzinfo/src.note/rtcl,Rust 实现的 Tcl 解释器)做多轮缺陷修正的 Round N。

## 共同上下文
- 构建:cargo build --release -p rtcl-cli;快速验证:./target/release/rtcl -c '<script>';
  oracle:echo '<script>' | tclsh(Tcl 8.6.17),一切语义以 tclsh 实测为准。
- 分歧清单:specs/DIVERGENCES.md([FIXED] 条目别动);行为规格 specs/*.allium。
- judge:./judge/run.sh;当前基线见 §3(修复前重新跑一遍取实时基线)。
  不要在并行构建负载下跑 judge(15s timeout 会抖动;先 cargo test 再 judge)。
- 硬约束:只许修改分配给你的文件;不许 git commit;不多修分配外条目;
  修好后 DIVERGENCES.md 标注 [FIXED <当日日期>];cargo test --workspace 保持全绿;
  单测断言依赖旧错误行为且新行为才正确的,更新断言并在报告中说明。
- 原则:最小侵入;不顺手重构;不引入新依赖。

## 你的任务
<具体条目 + 分配文件清单 + 复现脚本位置>

## 返回
每条修正的根因一句话 + 修改位置 path:line + 验证证据(前后输出对比)
+ 单测/judge 结果 + 未解决条目及原因。

文件互斥参考(按域):string_cmds.rs / expr.rs+expr_funcs.rs / value.rs+list.rs / list_sort.rs / control.rs+loops.rs / error.rs(rtcl-vm+rtcl-core)+call.rs / namespace.rs / vars.rs+call.rs。两域需要同一文件时串行,不并行。

8. 提交规约

  • 每轮修正一个 commit,message 格式:fix: Round N divergence burndown — <主题>,正文列条目,尾部署名 Co-Authored-By: Claude <noreply@anthropic.com>。
  • 提交前必跑:cargo test --workspace(全绿)+ ./judge/run.sh(无回退)。
  • git push origin HEAD(master,已配置 ssh)。
  • .refer/、judge/BURNDOWN.md 以外的新资产都可入库。

9. 本文件维护

每完成一轮修正或一个 JIT 里程碑:更新 §3 的数字、§4/§5 的完成状态、补充新裁决。handoff 文件自身入库(HANDOFF.md 在仓库根)。