@calcit/procs 0.12.19 → 0.12.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/editing-history/202507161633-wasm-data-structures.md +61 -0
  3. package/editing-history/20260416-1936-predicate-narrowing-expansion.md +31 -0
  4. package/editing-history/202604161507-wasm-data-structures-and-rfc-rename.md +27 -0
  5. package/editing-history/202604161520-wasm-bitwise-and-match.md +21 -0
  6. package/editing-history/202604161542-wasm-cross-ns-host-imports.md +38 -0
  7. package/editing-history/20260417-0026-wasm-rest-args.md +62 -0
  8. package/editing-history/202604170048-wasm-type-of.md +70 -0
  9. package/editing-history/202604170051-wasm-derived-predicates.md +34 -0
  10. package/editing-history/202604170132-monomorphize-map-filter.md +50 -0
  11. package/editing-history/202604170135-monomorphize-includes-reverse.md +31 -0
  12. package/editing-history/202604170140-fold-type-predicates.md +44 -0
  13. package/editing-history/202604170154-generic-dispatch-records-tuples.md +52 -0
  14. package/lib/calcit.procs.mjs +39 -6
  15. package/lib/package.json +1 -1
  16. package/package.json +1 -1
  17. package/rfc/02-04-runtime-traits-plan.md +613 -0
  18. package/rfc/02-14-project-modernization-roadmap.md +229 -0
  19. package/rfc/02-17-register-platform-api-rfc.md +115 -0
  20. package/rfc/02-18-language-theory-evolution-plan.md +367 -0
  21. package/rfc/02-23-optional-record-macro-plan.md +30 -0
  22. package/rfc/03-05-function-schema-dual-track-rfc.md +162 -0
  23. package/rfc/03-16-runtime-boundary-refactor-plan.md +546 -0
  24. package/rfc/03-18-query-def-tree-show-chunked-display-plan.md +301 -0
  25. package/rfc/04-13-call-arg-literal-rewrite-rfc.md +205 -0
  26. package/rfc/04-13-type-slot-mechanism-rfc.md +194 -0
  27. package/rfc/04-15-match-syntax-rfc.md +175 -0
  28. package/rfc/04-15-type-directed-optimization-catalog.md +170 -0
  29. package/rfc/04-15-wasm-compilation-feasibility.md +236 -0
  30. package/rfc/04-16-wasm-data-structures.md +192 -0
  31. package/rfc/README.md +40 -0
  32. package/ts-src/calcit.procs.mts +33 -6
@@ -0,0 +1,546 @@
1
+ # Runtime Boundary Refactor Plan
2
+
3
+ > 调整后的目标排序:先把 **compiled/runtime 边界站稳并让结构清晰**,其次争取 **热路径性能收益**,再次才考虑 **减少实体与语义收口**。watch 热更新与 JS codegen 继续保留,但不再作为继续扩张新层的理由。
4
+
5
+ ## 为什么现在做
6
+
7
+ 当前实现把 3 类本应分开的信息混在了一起:
8
+
9
+ - 源代码与预处理后的代码表示;
10
+ - 运行时求值缓存;
11
+ - 跨 reload 保留的状态。
12
+
13
+ 这直接导致几个后果:
14
+
15
+ - `preprocess` 会为初始化写入 `Nil` 占位,再递归求值/包 thunk,再写回全局状态;
16
+ - runtime 为了 JS codegen 保留 `Calcit::Thunk(Code)`,code 与 value 共存于一套值表示中;
17
+ - hot reload 过去只能依赖旧的 evaled store 做粗粒度清理,难以做精确失效;
18
+ - lookup 热点落在全局字符串查表与容器扫描上,而不是落在真正的业务执行上。
19
+
20
+ 这些问题并不是 watch 模式或 JS codegen 本身要求的,而是**当前把两者都编码进 runtime 值模型**造成的。
21
+
22
+ ## 不变约束
23
+
24
+ 本重构默认以下语义保持不变:
25
+
26
+ 1. watch 模式下,`namespace/def` 对应的状态在定义未发生实质性变化时可保留。
27
+ 2. JS codegen 必须能获得足够稳定的代码表示,不能依赖“值已经在 Rust runtime 里算完”。
28
+ 3. thunk / lazy top-level def 的用户语义保持一致。
29
+ 4. 现有宏、preprocess、trait/method 校验规则在外部行为上不主动改变。
30
+ 5. 调试能力允许暂时退化,后续由专门工具补足。
31
+
32
+ ## 核心判断
33
+
34
+ 要保留这两项能力,同时让边界变清晰,必须接受一件事:
35
+
36
+ **compile-time representation 和 runtime representation 不能再共用同一个 `Calcit` 值对象。**
37
+
38
+ 也就是说:
39
+
40
+ - codegen 看的是编译产物;
41
+ - runtime 看的是求值状态;
42
+ - 持久状态看的是稳定 identity 绑定的 state slot;
43
+ - 三者不再复用一份“既像代码又像值”的对象。
44
+
45
+ 这不是语言语义变化,而是实现层面的去同构。
46
+
47
+ ## 调整后的判断
48
+
49
+ 这份方案到现在仍然有意义,但它的意义已经变化:
50
+
51
+ - 这不再是一份适合继续扩张的“四层重构蓝图”;
52
+ - 更准确的定位,是一份 **compiled/runtime 拆边收官计划**;
53
+ - 继续推进的目标,是把已经落地的边界收紧、补齐回归测试、删除迁移期桥接;
54
+ - 暂时不继续推进的目标,是引入更多长期实体来追求理论完备。
55
+
56
+ 换句话说,当前阶段要继续的是“收尾”,不是“扩编”。
57
+
58
+ ## 当前唯一目标
59
+
60
+ 当前不再追求把文档里的四层模型一比一做完,真正执行时只守下面 4 条边界:
61
+
62
+ 1. `preprocess == compile`:只负责确保 `CompiledDef` 存在,不再承担“顺手返回一个可运行值”的通用职责。
63
+ 2. `run == runtime`:只负责从 `RuntimeCell`/compiled executable payload 取运行值,不再替 metadata/codegen 补结构。
64
+ 3. `reload == invalidation`:只负责 source change apply 与 `DefId` 依赖闭包失效,不再维护额外 package 级兼容语义。
65
+ 4. `codegen == compiled snapshot`:只读取 compiled snapshot;任何 runtime fallback 都视为过渡期兼容,而不是长期设计。
66
+
67
+ 这 4 条如果站稳,后续性能优化才会变得局部且可解释:
68
+
69
+ - preprocess 的性能只看编译与依赖收集;
70
+ - run 的性能只看 `DefId` 索引、runtime lock 和求值;
71
+ - reload 的性能只看变更种子与反向依赖图;
72
+ - codegen 的性能只看 compiled snapshot 构造与 emitter。
73
+
74
+ ## 当前不做
75
+
76
+ 为了保证进度,这一阶段明确不做:
77
+
78
+ - 不引入新的长期层级实体来“补完”理论模型;
79
+ - 不把 `PersistentStateLayer` / `StateSlotId` 推进成当前实现目标;
80
+ - 不再扩张新的 compiled/runtime 双向桥接 helper;
81
+ - 不为了照顾少量旧路径,继续保留“preprocess 顺手执行、runtime 顺手提供 codegen 结构”这种混合语义。
82
+
83
+ ## 当前收官清单
84
+
85
+ - `[已基本完成]` 把 `preprocess` 公开入口收敛为 `ensure_ns_def_compiled()`,并把只需要 ensure 的调用点迁走。
86
+ - `[进行中]` 继续缩小 runtime-derived snapshot fallback,直到它只剩明确、可解释的过渡语义,或者可以被完全删除。
87
+ - `[待继续]` 补齐 watch/reload 回归测试,特别是 changed defs、ns header、removed defs、依赖闭包 invalidation。
88
+ - `[待继续]` 清掉迁移期命名和重复 lookup helper,让 `program` 成为唯一边界聚合点。
89
+
90
+ ## 当前进度
91
+
92
+ 截至 2026-03-17,已经完成的不是“纯设计”,而是一部分边界已经落地:
93
+
94
+ - `DefId` 已经引入,并建立了 `ns/def -> DefId` 稳定索引;
95
+ - `CompiledDef` 已经存在,且开始承载 `preprocessed_code`、`codegen_form`、`deps`、`type_summary` 等编译期信息;
96
+ - JS / IR codegen 已经切到读取 compiled layer,而不是直接读取 evaled program;
97
+ - runtime 已经有并行的 `DefId -> RuntimeCell` 稠密表,作为旧 `EntryBook` 之外的新快路径;
98
+ - `CalcitImport` 已开始携带稳定 `def_id` 缓存,runtime lookup 已优先消费它;
99
+ - import/runtime lookup 兼容路径里的旧 `coord -> EntryBook` 残余已经清掉,`CalcitImport.coord` 与相关 runner 参数已删除;
100
+ - `RuntimeCell` 的最小状态机已经落下,包含 `Cold | Resolving | Ready | Errored`,且 preprocess 的循环保护已从“先写 `Nil`”切到显式 `Resolving`;
101
+ - JS codegen 现在会显式跳过 core 中仅由 runtime 提供的 placeholder 定义,以及 `syntax`/`proc` 这类本就不应按普通顶层值发射的定义;这也移除了 `calcit.core.mjs` 中形如 `eval = &runtime-inplementation` 的伪导出;
102
+ - `clone_compiled_program_snapshot()` 已开始按“仅收集缺口定义”的两阶段方式补齐 snapshot,而不是先整表 clone source/index/runtime 全局状态后再筛选;
103
+ - runtime-derived snapshot fallback 现在进一步收窄为“仅对被现有 compiled graph 实际引用到的 runtime-only defs 生效”;只要 source-backed def 仍存在,或 runtime-only def 只是未被引用的残留 runtime 值,就不会再因为旧 runtime 值而静默补出 snapshot entry;
104
+ - runtime-only snapshot fallback 也不再原样嵌入任意 `RuntimeCell::Ready` 值;当前会先把 runtime 值转成可快照的 Calcit 数据/代码形式,像 `ref` / `buffer` / `any-ref` / 运行时函数句柄这类本就不稳定的 runtime-only 值将直接被跳过;
105
+ - runtime-only snapshot fallback 已不再携带 source/schema/doc/examples 这类 source 元数据,snapshot 填充任务本身也不再为 fallback 路径 clone 整个 `ProgramDefEntry`;
106
+ - snapshot 补缺现在也只会在真正拿到 compiled def / runtime-only fallback 时才创建 namespace entry;source-backed rebuild 失败不再留下空壳 compiled file;
107
+ - `seed_runtime_lazy_from_compiled()`、`lookup_compiled_runtime_value()`、`lookup_codegen_type_hint()` 已开始按需读取 compiled 字段,而不是在热路径上先 clone 整份 `CompiledDef`;
108
+ - `runner`/`lib`/`preprocess` 主调用方已经迁到“先取 compiled executable payload,再按需求值”的边界;旧的 `lookup_compiled_runtime_value()` 兼容包装已删除。
109
+ - IR/codegen type-hint 查询已不再通过执行 compiled payload 来补信息;metadata 查询现在只依赖 compiled/source schema 与现成 runtime 值。
110
+ - runtime symbol lookup 已不再假设 compiled 执行会回填 runtime cache;执行后的二次 runtime reread 兼容分支已移除。
111
+ - `preprocess` 读取已编译定义时,lazy def 现在优先经由 `RuntimeCell::Lazy` 求值,不再绕过 runtime 状态机直接执行 compiled payload。
112
+ - `run_program_with_docs` 已切到 `ensure_ns_def_compiled() + evaluate_symbol_from_program()`,不再依赖 `preprocess` 返回入口值。
113
+ - `runner` 内部两处“runtime cell -> compiled executable fallback”逻辑已合并到统一 helper,减少了边界复制和不一致分支。
114
+ - `preprocess` 的宽松读取路径也已并到同一组 helper,不再单独复制一份 `RuntimeCell::Lazy`/compiled fallback 分支。
115
+ - 默认 scope 下的 thunk 求值入口也已收敛到 `CalcitThunk::evaluated_default()`,减少 runtime/lazy 入口上的样板逻辑。
116
+ - runtime cell 与 compiled executable fallback 的统一入口现已下沉到 `program` 层;`runner` 只保留 runtime state 到用户态错误的映射。
117
+ - `evaluate_compiled_def()` 现已退回 `program` 内部私有 helper;compiled payload 执行不再作为跨模块公共入口暴露。
118
+ - `preprocess` 已不再依赖 lenient runtime/compiled probe 作为前置判断;预处理阶段现在只检查 runtime cell / compiled metadata,读取已编译定义值则走专用 helper,不再复用通用 lookup 包装。
119
+ - compiled output 写入接口已改为 payload struct 传参,`program` 内部构造链不再依赖 11 参数长调用;现有 clippy `too_many_arguments` 噪音已被顺手收掉。
120
+ - `resolve_runtime_or_compiled_def()` 现在只负责调度;runtime cell 求值与 compiled payload 执行已拆成独立私有 helper,内部边界更接近最终形态。
121
+ - runtime-only 路径中的 `seed + lookup cell + resolve cell` 已继续收敛成单独 helper,`resolve_runtime_or_compiled_def()` 现在更明确地只做 `runtime or compiled` 两段调度。
122
+ - compiled execution 热路径已改为直接借用 compiled payload;执行时不再额外 clone 一份 `preprocessed_code`,只有测试/显式查询 executable code 时才保留复制语义。
123
+ - 仅剩测试使用的 `lookup_runtime_or_compiled_def_lenient()` 兼容包装也已删除;lenient 语义现在直接通过 `resolve_runtime_or_compiled_def(..., RuntimeResolveMode::Lenient, ...)` 覆盖。
124
+ - `clear_runtime_caches_for_reload()` 的兼容入口已改为先生成 package 范围的伪 `ns` 变更,再统一复用 `clear_runtime_caches_for_changes()` 的依赖闭包 invalidation,而不再维护独立的 package 扫描清理逻辑。
125
+ - `yarn check-all` 已经作为当前重构的主验证门槛,并且当前门槛重新保持可通过。
126
+ - 当前阶段的固定门槛 `cargo fmt && cargo test -q && yarn check-all` 已重新可通过。
127
+
128
+ 同时也要明确,当前还没有真正完成的部分是:
129
+
130
+ - 旧的 `PROGRAM_EVALED_DATA_STATE` 与 `lookup_evaled_def*` 兼容路径已经删除,`preprocess` 成功路径也不再直接写 runtime cache;compiled fallback 现在只做“读 compiled value”,不再把 compiled payload 回填成 `RuntimeCell::Ready`;
131
+ - 全局 `CompiledDef` 已不再携带 `runtime_value` 这类 runtime payload 字段;普通 preprocess 输出已经完全不构造 runtime payload,普通 compiled `Fn/Macro/Proc/Syntax/LazyValue` 都改为需要时从 `preprocessed_code` 临时 materialize;当前剩余耦合主要集中在 runtime 执行路径仍会把 compiled executable payload materialize 成公共 `Calcit` 值,而 metadata / codegen 查询已基本不再走这条路;snapshot fallback 已不再对 source-backed defs 静默兜底,也不再为失败的 source-backed rebuild 保留空壳 namespace,未被 compiled graph 引用的 runtime-only 残留值不会再进入 snapshot,而且即便被引用,像 `ref` / `any-ref` 这类不可稳定序列化的 runtime-only 值也会被跳过。
132
+ - `Calcit::Thunk` 仍存在于公开值模型中,但 runtime 主路径已经不再依赖它作为缓存载体:lazy def 的待求值占位优先放进 `RuntimeCell::Lazy`,`Ready(Thunk)` 已被禁止写入 runtime store,preprocess 与普通 lookup 也不再把 runtime lazy cell 重新包装成公共 thunk;当前剩余问题主要转向 snapshot fallback 与少量兼容语义分支;
133
+ - watch 模式已经开始利用 compiled deps 做 def 级 invalidation;CLI incremental reload 主路径与 reload 兼容入口现在都已统一复用 changes-based 依赖闭包清理,而不再维护一套独立 package 清理逻辑。剩余缺口主要是 state slot 尚未引入,以及 watch/reload 回归覆盖还可以继续加密。
134
+
135
+ 这意味着当前状态更准确的表述是:
136
+
137
+ **Phase 1 和 Phase 2 已经实装,Phase 3A/3B 已经稳定运行,Phase 3D 的主路径删除也已完成;剩余重点转向 3C 以及 preprocess/runtime 的最终拆边。**
138
+
139
+ ## 基于最近 samply 的现状补充(2026-03-17)
140
+
141
+ 已使用 `profiling/samply-once.sh` 对 `calcit/test.cirru` 与 `calcit/fibo.cirru` 进行 release 采样,当前热点特征:
142
+
143
+ - `program::materialize_compiled_executable_payload` 仍在热路径出现,说明 runtime 对 compiled payload 的过渡 materialize 仍有收缩空间;
144
+ - `CalcitTypeAnnotation::extract_schema_value/schema_key_matches_any` 频繁命中,说明 hint/schema 解析与匹配仍是可优化热点;
145
+ - `CallStackList::extend_owned` 在 fibo 采样中有可见占比,说明运行态栈构造仍可能偏积极;
146
+ - 采样中可见 `serde_json` 热点,主要来自 profiling/输出链路,不应直接等同于 steady-state runtime 核心开销。
147
+
148
+ 结论:边界拆分方向正确,但“runtime materialize 收缩 + type annotation 热点收敛”已经进入可以直接动手优化的阶段。
149
+
150
+ ## 目标边界模型
151
+
152
+ 建议保留这套分层模型作为**分析框架**,而不是要求实现层面继续一比一落四层实体。
153
+
154
+ ### 1. Source Layer
155
+
156
+ 输入资产:
157
+
158
+ - snapshot / changeset
159
+ - 原始 Cirru
160
+ - 文档、schema、examples
161
+
162
+ 特点:
163
+
164
+ - 只负责装载、diff、持久化
165
+ - 不承担 runtime lookup
166
+ - 不承担 codegen 的最终消费格式
167
+
168
+ ### 2. Compiled Layer
169
+
170
+ 新增核心对象:`CompiledDef`
171
+
172
+ 建议字段:
173
+
174
+ - `def_id`: 稳定定义 ID
175
+ - `version_id`: 本次编译版本号
176
+ - `kind`: `Fn | Macro | LazyValue | ConstValue | SyntaxProxy | NativeProxy`
177
+ - `source_meta`: `ns/def/doc/schema/examples`
178
+ - `preprocessed_code`: 预处理后的 lowered form
179
+ - `codegen_form`: 供 JS/IR 输出的稳定表示
180
+ - `deps`: 定义级依赖列表
181
+ - `type_summary`: 参数/返回值/trait 约束摘要
182
+ - `state_policy`: `Stateless | PreserveAcrossReload | ResetOnChange`
183
+
184
+ 职责:
185
+
186
+ - 承接 preprocess 的主要产出
187
+ - 为 codegen 提供输入
188
+ - 为 runtime 提供可执行 payload
189
+
190
+ 关键变化:
191
+
192
+ - `emit_js` 不再读取 evaled program,而是直接消费 `CompiledDef.codegen_form`
193
+ - thunk 不再承担“保留 code for codegen”的职责
194
+
195
+ ### 3. Runtime Layer
196
+
197
+ 新增核心对象:`RuntimeCell`
198
+
199
+ 建议状态机:
200
+
201
+ - `Cold`
202
+ - `Resolving`
203
+ - `Ready(Calcit)`
204
+ - `Errored(CalcitErr)`
205
+
206
+ 可选扩展:
207
+
208
+ - `ReadyLazy(Calcit)` 用于保留部分 lazy 语义
209
+
210
+ 职责:
211
+
212
+ - 仅管理定义的求值状态
213
+ - 只缓存运行时值,不保存 codegen 所需代码
214
+ - 通过 `DefId` 直接索引,而不是热路径上反复字符串查找
215
+
216
+ 关键变化:
217
+
218
+ - 旧的 `PROGRAM_EVALED_DATA_STATE` 已删除,runtime store 现在实际就是按 `DefId` 索引的 runtime cell table
219
+ - `evaluate_symbol_from_program` 首先解析到 `DefId`,之后只在 runtime table 中操作
220
+ - `Calcit::Thunk` 可逐步降级为 runtime 内部机制,而非通用值构造器
221
+
222
+ ### 4. Persistent State Layer
223
+
224
+ 新增核心对象:`StateSlot`
225
+
226
+ 建议形式:
227
+
228
+ - `StateSlotId`
229
+ - `owner_def_id`
230
+ - `generation`
231
+ - `payload`
232
+
233
+ 职责:
234
+
235
+ - 保存需要跨 reload 保留的状态
236
+ - 与 runtime 值缓存分离
237
+ - 在 hot reload 时按稳定 identity 决定复用或重置
238
+
239
+ 关键变化:
240
+
241
+ - “保留状态”不再等于“保留整个 def 的 evaled value”
242
+ - `Ref` / atom / runtime resource 迁移到显式 state slot 体系
243
+
244
+ 当前判断:这一层保留为后续方向,但**不作为当前阶段的执行目标**。只有在 watch/reload 语义已经被现有 compiled/runtime 边界稳定支撑、且确实遇到状态保留表达不足时,才值得继续引入。
245
+
246
+ ## 两项关键能力如何保留
247
+
248
+ ### A. 保留 watch 模式
249
+
250
+ 现状问题:
251
+
252
+ - reload 主要通过局部删除 evaled defs 实现;
253
+ - 定义与状态耦合,导致失效粒度粗;
254
+ - 同一个 def 的代码变更与状态保留难以分开判断。
255
+
256
+ 重构后:
257
+
258
+ 1. source diff 生成 changed defs 集合。
259
+ 2. 仅重编译受影响的 `CompiledDef`。
260
+ 3. 根据依赖图传播 invalidation 到 `RuntimeCell`。
261
+ 4. `PersistentStateLayer` 按 `state_policy + identity` 判断是否复用。
262
+ 5. `reload-fn` 基于新的 compiled graph 与 preserved state 执行。
263
+
264
+ 语义收益:
265
+
266
+ - 代码热更新与状态保留不再互相污染;
267
+ - 可以做更精确的 def 级失效,而不是 package 级清理;
268
+ - watch 路径的运行时开销更可控。
269
+
270
+ ### B. 保留 JS codegen
271
+
272
+ 现状问题:
273
+
274
+ - `emit_js` 直接从 evaled program 读取定义;
275
+ - value defs 必须保留成 thunk 才能拿到 code;
276
+ - runtime 值表示被 codegen 需求反向塑形。
277
+
278
+ 重构后:
279
+
280
+ - codegen 仅读 `CompiledDef.codegen_form`
281
+ - runtime 是否已经求值,与 codegen 无关
282
+ - lazy value 是否在 Rust runtime 里缓存,也与 JS 输出无关
283
+
284
+ 语义收益:
285
+
286
+ - JS codegen 与 runtime 生命周期解耦;
287
+ - thunk 从“跨层共享对象”退化为“编译/运行时的内部策略”;
288
+ - 后续 IR/JS backend 可单独优化。
289
+
290
+ ## 需要引入的稳定 identity
291
+
292
+ 这是整个重构的关键。
293
+
294
+ 建议新增:
295
+
296
+ - `NsId`
297
+ - `DefId`
298
+ - `CompiledVersionId`
299
+ - `StateSlotId`
300
+
301
+ 规则:
302
+
303
+ - `DefId` 对应语义上的“这个定义”,跨编译版本保持稳定;
304
+ - `CompiledVersionId` 对应某次重编译产物;
305
+ - `StateSlotId` 对应可保留状态的拥有者;
306
+ - `coord`/字符串查找只能作为 debug 与兼容路径,不再作为主索引。
307
+
308
+ ## 对 thunk 的重定义
309
+
310
+ 当前问题不在于 thunk 本身,而在于 thunk 暴露成了 runtime 公共值模型的一部分。
311
+
312
+ 建议分两步处理:
313
+
314
+ ### 阶段一:保留外部语义,收缩内部职责
315
+
316
+ - 用户仍可观察到 lazy top-level def 行为
317
+ - 但 codegen 不再读取 `Calcit::Thunk`
318
+ - `Calcit::Thunk` 只用于 runtime 求值过程
319
+
320
+ ### 阶段二:把 thunk 从通用值中移出
321
+
322
+ - 仅在 `RuntimeCell` 内部表达 lazy state
323
+ - runtime 对外暴露的仍是正常 `Calcit`
324
+ - codegen / preprocess 不再依赖 thunk 值分支
325
+
326
+ 这一步会显著降低 evaluator、preprocess、codegen 三方的共享复杂度。
327
+
328
+ ## 对 preprocess 的新边界定义
329
+
330
+ 建议明确:
331
+
332
+ - preprocess 的职责是把 source form 变成 `CompiledDef.preprocessed_code`
333
+ - preprocess 可以读取 `CompiledDef` 依赖与类型摘要
334
+ - preprocess 不直接写 runtime value table
335
+ - preprocess 不再通过写 `Nil` 到 evaled defs 来打断循环
336
+
337
+ 循环依赖处理改为:
338
+
339
+ - 编译层使用 `Compiling / Compiled / Failed` 状态;
340
+ - runtime 层使用 `Resolving / Ready / Errored` 状态;
341
+ - 两套状态机分别处理,不再混用。
342
+
343
+ 这里需要特别澄清一件事:
344
+
345
+ 当前 `preprocess` 已经开始产出 compiled metadata,成功路径也已经不再直接写 runtime cache;预处理前置检查也不再通过 lenient runtime/compiled probe 侧向触发执行路径。runtime 也不再在 `Cold` 状态下把 compiled payload 回填成 `RuntimeCell::Ready`。对于 lazy def,当前只会先把 compiled metadata 重新播种成 `RuntimeCell::Lazy`,而不是直接伪装成 ready runtime value。这说明边界已经继续前进一步,但还没有完全完成去同构。最终目标依然是:
346
+
347
+ - `preprocess` 负责产出 `CompiledDef`;
348
+ - runtime 在真正需要值时,才从 compiled payload 驱动求值;
349
+ - 循环检测不再依赖“先写一个 `Nil` 到 runtime 再回来填值”。
350
+
351
+ ## 数据结构建议
352
+
353
+ 本次重构不是简单把 `EntryBook` 换成 `HashMap`,但主路径的数据结构必须同步升级。
354
+
355
+ 建议:
356
+
357
+ - `DefId -> RuntimeCell` 使用稠密表或 `Vec<RuntimeCell>`
358
+ - `ns/def -> DefId` 使用只读索引表(初始化后稳定)
359
+ - source/meta 数据仍可保留 `HashMap`
360
+ - state slot table 使用稠密索引或 slab 风格容器
361
+
362
+ 原则:
363
+
364
+ - 字符串查找只发生在装载/编译边界;
365
+ - steady-state runtime 尽量只做整数索引;
366
+ - 热路径避免 `Arc<str>` 比较和容器扫描。
367
+
368
+ ## 当前执行面
369
+
370
+ 不再把后续工作定义成“继续推进到完整四层”,而是改成下面三个收官面:
371
+
372
+ ### A. 站稳 compiled/runtime 边界
373
+
374
+ - 继续删除仍停留在迁移期的桥接 helper 与兜底分支;
375
+ - 保证 metadata/codegen 查询不再借 runtime 执行补信息;
376
+ - 保证 runtime lookup 不再假设 compiled 执行会隐式回填缓存;
377
+ - 把 `program` 维持为边界聚合点,避免 `runner`/`preprocess` 再各自复制一份 fallback 逻辑。
378
+
379
+ ### B. 补齐 watch/reload 回归测试
380
+
381
+ - 直接覆盖 changed def、namespace header 变更、依赖闭包失效;
382
+ - 验证 source-backed def 不会被 runtime-derived snapshot 静默复活;
383
+ - 验证 lazy/runtime-only def 的 fallback 仍符合当前保留语义;
384
+ - 把 `cargo fmt && yarn check-all && cargo test -q` 作为固定门槛。
385
+
386
+ ### C. 做减法而不是加层
387
+
388
+ - 优先合并过渡期命名、兼容包装、重复 helper;
389
+ - 暂不引入 `PersistentStateLayer` / `StateSlotId` 这类新实体;
390
+ - 只有在现有模型无法表达真实需求时,才新增一层概念。
391
+
392
+ ## 迁移阶段
393
+
394
+ ### Phase 0: 约束冻结
395
+
396
+ - 明确哪些行为必须保持
397
+ - 给 watch / reload / js codegen 补回归测试
398
+ - 记录当前热点和基线
399
+ - 验证顺序固定为:`cargo fmt && yarn check-all && cargo test -q`
400
+
401
+ ### Phase 1: 引入 `DefId`,不改外部行为
402
+
403
+ - 建立 `ns/def -> DefId` 索引
404
+ - lookup 仍兼容旧路径
405
+ - 为后续 runtime/compiled 分层做准备
406
+
407
+ 当前状态:已完成。
408
+
409
+ ### Phase 2: 引入 `CompiledDef`
410
+
411
+ - 把 preprocess 产物显式化
412
+ - `emit_js` 切到 `CompiledDef.codegen_form`
413
+ - 仍保留旧 runtime 值缓存
414
+
415
+ 当前状态:主体已完成,但 codegen snapshot 仍允许从 runtime ready 值做 fallback 填补空洞。
416
+
417
+ ### Phase 3: 引入 `RuntimeCell`
418
+
419
+ - 将 `PROGRAM_EVALED_DATA_STATE` 重写为 runtime cell table
420
+ - thunk 改为 runtime 内部状态机的一部分
421
+ - `evaluate_symbol_from_program` 改为 `DefId` 驱动
422
+
423
+ 这里不应该一次性硬切,建议拆成 4 个小阶段:
424
+
425
+ #### Phase 3A: 并行 runtime 索引
426
+
427
+ - 建立 `DefId -> runtime slot` 稠密表;
428
+ - 先让 runtime slot 成为主快路径;当时 `write_evaled_def` / reload / changeset 仍与旧表并行同步;
429
+ - `evaluate_symbol_from_program` 优先走 `DefId`,旧路径保留 fallback。
430
+
431
+ 当前状态:已完成。
432
+
433
+ #### Phase 3B: 显式 RuntimeCell 状态机
434
+
435
+ - 把 `Option<Calcit>` 提升为 `RuntimeCell`;
436
+ - 最少先实现 `Cold | Resolving | Ready | Errored`;
437
+ - 把“循环中先写 `Nil`”替换成显式 `Resolving`。
438
+
439
+ 当前状态:已完成,且正常 runtime 路径已经不再依赖兼容 lookup。`preprocess` 已不再把 `Resolving` 暂时转成 `Calcit::Nil`,而是把“确保已 preprocess”与“读取可用值”分开;并且普通 preprocess 输出已经完全不再构造 runtime payload。compiled fallback 也已经不再把 cold def 写回 `RuntimeCell::Ready`,而只是临时读取 compiled value。全局 `CompiledDef` 已不再保存 `runtime_value`,普通 compiled `Fn/Macro/Proc/Syntax/LazyValue` 改为按需从 `preprocessed_code` materialize。codegen snapshot 现在会优先把 source-backed 缺口补成真正的 compiled def,只有补不上时才退回 runtime-derived snapshot fallback。剩余串线主要是 compiled/runtime 仍共用 `Calcit` 这套值表示,以及 snapshot fallback 仍作为最后兜底存在。
440
+
441
+ #### Phase 3C: thunk 职责内收
442
+
443
+ - `Calcit::Thunk` 继续保留外部语义;
444
+ - 但 runtime 内部缓存与 lazy 状态优先放进 `RuntimeCell`;
445
+ - 减少 thunk 对全局写回和 code 表示的承担。
446
+
447
+ 当前状态:主路径已基本收尾。thunk 仍是公开 `Calcit` 值模型的一部分,但 runtime store 已不再接受 `Ready(Thunk)` 这类形态;lazy def 的未求值占位优先放进 `RuntimeCell::Lazy`,raw fallback 若得到 `Calcit::Thunk(Code)` 也会立刻规范化回 lazy cell。`eval_symbol_from_program` 也不再把 lazy thunk 返回给调用方,preprocess 查值路径同样不再把 runtime lazy cell 重新包装成公共 thunk,且预处理前置检查已不再通过 lenient lookup 间接复用执行路径。JS codegen 还额外收掉了一层旧桥接:core 中由 runtime 提供的 placeholder 定义、以及 syntax/proc 名称,不再伪装成普通 JS 顶层导出。当前剩余工作主要不再是 thunk 主路径,而是继续压缩 snapshot fallback compiled entry 的存在范围,并视需要继续补 watch/reload 回归测试。
448
+
449
+ #### Phase 3D: 删除旧 EntryBook 热路径依赖
450
+
451
+ - `evaluate_symbol_from_program` 不再依赖 `coord -> EntryBook` 作为主快路径;
452
+ - `PROGRAM_EVALED_DATA_STATE` 从主 runtime store 降级为兼容层或被删除;
453
+ - watch/reload 改为直接操作 runtime cell table。
454
+
455
+ 当前状态:主体已完成。`coord -> EntryBook` 主快路径已经删除,`PROGRAM_EVALED_DATA_STATE` 也已删除;watch/reload 主路径与兼容 reload 入口也都已经统一基于 `DefId` 与 compiled deps 做依赖闭包失效。剩余问题不再是“还停留在 package 级清理”,而是要把剩余边界情况和回归测试补齐,并继续把状态保留语义从值缓存里彻底拆出去。
456
+
457
+ ### Phase 4: 引入 `PersistentStateLayer`
458
+
459
+ - `Ref` 与其他跨 reload 状态转入 state slot
460
+ - 定义级值缓存与状态对象彻底脱钩
461
+
462
+ 这一步的前提不是“有了 DefId 就可以做”,而是:
463
+
464
+ - runtime cell 的 identity 已经稳定;
465
+ - reload invalidation 规则已经以 `DefId` 为主;
466
+ - 不再把“值缓存是否保留”误当成“状态是否保留”。
467
+
468
+ 当前判断:**暂停**。这一步不是当前瓶颈,也不符合“先让结构更清晰、再减少实体”的目标排序。除非后续出现无法用现有 compiled/runtime 边界解释的 reload state 问题,否则不进入实现。
469
+
470
+ ### Phase 5: 删除旧耦合路径
471
+
472
+ - 删除 codegen 对 evaled program 的依赖
473
+ - 删除 preprocess 对 runtime 写入 `Nil` 的循环保护策略
474
+ - 删除或收缩 `Calcit::Thunk` 的公共职责
475
+
476
+ 进入 Phase 5 的标志应该非常明确,而不是“感觉差不多了”:
477
+
478
+ - `yarn check-all` 与 `cargo test` 在没有旧 evaled fallback 的情况下仍然稳定通过;
479
+ - JS/IR codegen 不再从 runtime 值层偷拿任何结构信息;
480
+ - runtime cycle detection 已完全基于 `RuntimeCell` 状态机;
481
+ - watch reload 可以基于 compiled deps + stable identity 做解释得通的失效。
482
+
483
+ 当前判断:保留为**收尾检查表**,不再视为一个需要继续扩展设计面的阶段。
484
+
485
+ ## 接下来应该怎么做
486
+
487
+ 下一步不再是“补完大设计”,而是按下面顺序收官:
488
+
489
+ 1. 先做热点导向减法:继续收缩 `materialize_compiled_executable_payload` 的热路径触发频率,减少 runtime 侧重复 materialize。
490
+ 2. 再收敛类型系统热路径:优先减少 `hint/schema` 解析与匹配的重复工作(缓存、去重分支、减少中间分配)。
491
+ 3. 继续收缩 runtime-derived snapshot fallback,只保留真正 runtime-only defs 需要的兜底;source-backed defs 一律优先走 compiled/source 数据。
492
+ 4. 补齐 watch/reload 回归测试,重点覆盖 changed defs、ns header、removed defs、依赖闭包 invalidation,以及 snapshot fallback 不误补 source-backed defs。
493
+ 5. 清理还停留在迁移期的 helper、命名和双份 lookup 分支,让 `program` 成为唯一边界聚合点。
494
+
495
+ 换句话说,下一步的目标是:
496
+
497
+ **把已经接进去的 runtime state machine 和 snapshot/codegen 边界,推进到职责清晰、测试充足、且不再继续引入新实体。**
498
+
499
+ ## 预期收益
500
+
501
+ ### 性能
502
+
503
+ - steady-state runtime 从字符串 lookup 转向整数索引
504
+ - preprocess 不再反复通过 runtime state 做协调
505
+ - codegen 不再污染 runtime 值模型
506
+
507
+ ### 架构
508
+
509
+ - compile / runtime / state 三层边界清晰
510
+ - hot reload 失效规则可解释、可测试
511
+ - thunk 语义从“跨层共享”变成“局部实现细节”
512
+
513
+ ### 可维护性
514
+
515
+ - 性能问题更容易定位到某一层
516
+ - 调试工具可针对不同层独立建设
517
+ - 以后若加 IR backend,不需要继续借 runtime 值做桥接
518
+
519
+ ## 主要风险
520
+
521
+ 1. 旧代码默认假设“所有定义最终都能在 `Calcit` 值层找到表示”,重构后会打破这个心智模型。
522
+ 2. reload 语义如果没有清晰 identity 规则,容易出现状态误保留或误清理。
523
+ 3. 宏与 preprocess 若隐式依赖 runtime 当前行为,需要在阶段 2 前先全面梳理。
524
+ 4. 调试输出会暂时退化,因为 call stack / source mapping 需要重新挂接到 `CompiledDef`。
525
+
526
+ ## 当前建议的第一步
527
+
528
+ 如果只做一个高 ROI 的下一步动作,建议先做:
529
+
530
+ **先基于 samply 热点收缩 runtime materialize 触发频率,并配套保留 `yarn check-all` + `cargo test` 的语义门槛。**
531
+
532
+ 理由:
533
+
534
+ - 这是当前最直接、可量化的性能收益来源;
535
+ - 这与“run == runtime”边界固化目标一致,不会引入新实体;
536
+ - 在现有测试门槛下可快速验证语义不回退。
537
+
538
+ ## 最终判断
539
+
540
+ 在保留 watch 模式与 JS codegen 的前提下,这条线仍值得继续,但应该以“收官和减法”为主,而不是继续做激进扩层。
541
+
542
+ 真正需要继续放弃的不是功能,而是这件事:
543
+
544
+ **“同一个 runtime 值对象同时承载源码、预处理结果、惰性求值状态、热更新身份与 codegen 输入。”**
545
+
546
+ 只要不再坚持这件事,并且不再为它继续引入额外层级,边界就能重新变清晰,而且实现会更可控。