@calcit/procs 0.12.18 → 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.
- package/.yarn/install-state.gz +0 -0
- package/editing-history/202504160117-wasm-codegen-and-catalog-update.md +40 -0
- package/editing-history/202507160207-wasm-encoder-migration.md +48 -0
- package/editing-history/202507161633-wasm-data-structures.md +61 -0
- package/editing-history/202604151211-record-nth-optimization.md +44 -0
- package/editing-history/20260416-1936-predicate-narrowing-expansion.md +31 -0
- package/editing-history/202604160131-wasm-math-and-test-integration.md +28 -0
- package/editing-history/202604161507-wasm-data-structures-and-rfc-rename.md +27 -0
- package/editing-history/202604161520-wasm-bitwise-and-match.md +21 -0
- package/editing-history/202604161542-wasm-cross-ns-host-imports.md +38 -0
- package/editing-history/20260417-0026-wasm-rest-args.md +62 -0
- package/editing-history/202604170048-wasm-type-of.md +70 -0
- package/editing-history/202604170051-wasm-derived-predicates.md +34 -0
- package/editing-history/202604170132-monomorphize-map-filter.md +50 -0
- package/editing-history/202604170135-monomorphize-includes-reverse.md +31 -0
- package/editing-history/202604170140-fold-type-predicates.md +44 -0
- package/editing-history/202604170154-generic-dispatch-records-tuples.md +52 -0
- package/lib/calcit.procs.mjs +39 -6
- package/lib/package.json +4 -3
- package/package.json +4 -3
- package/rfc/02-04-runtime-traits-plan.md +613 -0
- package/rfc/02-14-project-modernization-roadmap.md +229 -0
- package/rfc/02-17-register-platform-api-rfc.md +115 -0
- package/rfc/02-18-language-theory-evolution-plan.md +367 -0
- package/rfc/02-23-optional-record-macro-plan.md +30 -0
- package/rfc/03-05-function-schema-dual-track-rfc.md +162 -0
- package/rfc/03-16-runtime-boundary-refactor-plan.md +546 -0
- package/rfc/03-18-query-def-tree-show-chunked-display-plan.md +301 -0
- package/rfc/04-13-call-arg-literal-rewrite-rfc.md +205 -0
- package/rfc/04-13-type-slot-mechanism-rfc.md +194 -0
- package/rfc/04-15-match-syntax-rfc.md +175 -0
- package/rfc/04-15-type-directed-optimization-catalog.md +170 -0
- package/rfc/04-15-wasm-compilation-feasibility.md +236 -0
- package/rfc/04-16-wasm-data-structures.md +192 -0
- package/rfc/README.md +40 -0
- package/ts-src/calcit.procs.mts +33 -6
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# RFC: Native `match` Syntax with Exhaustiveness Checking
|
|
2
|
+
|
|
3
|
+
状态:Implemented
|
|
4
|
+
日期:2026-04-15
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 概要
|
|
9
|
+
|
|
10
|
+
引入新的原生语法 `match`,用于对 enum(tagged union)进行模式匹配。与现有 `tag-match` 宏不同,`match` 在预处理阶段执行穷尽性检查(exhaustiveness checking),在运行时直接执行分支匹配,在 JS 代码生成中输出高效的 if-else 链。
|
|
11
|
+
|
|
12
|
+
## 2. 动机
|
|
13
|
+
|
|
14
|
+
### 问题
|
|
15
|
+
|
|
16
|
+
`tag-match` 是定义在 `calcit-core.cirru` 中的宏,展开后变成嵌套的 `if` + `identical?` 表达式。宏展开后:
|
|
17
|
+
|
|
18
|
+
- **丢失结构信息**:预处理器只看到 `if`/`identical?`,无法知道"这是一个多分支枚举匹配"
|
|
19
|
+
- **无法做穷尽性检查**:展开后的代码无法回溯到原始的分支列表
|
|
20
|
+
- **无法校验变体存在性**:拼错 tag 名只会得到运行时错误
|
|
21
|
+
- **无法校验 arity**:绑定数量与变体定义不匹配只会运行时报错
|
|
22
|
+
|
|
23
|
+
### 解决方案
|
|
24
|
+
|
|
25
|
+
将 `match` 作为原生语法(`CalcitSyntax::Match`),在编译器各阶段都能看到完整的分支结构:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
源码 → 预处理(穷尽性+类型推断) → 运行时(直接分支匹配) → JS(if-else 链)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. 语法设计
|
|
32
|
+
|
|
33
|
+
### 基本形式
|
|
34
|
+
|
|
35
|
+
```cirru
|
|
36
|
+
match <value>
|
|
37
|
+
(<pattern1>) <body1>
|
|
38
|
+
(<pattern2> <binding1> <binding2>) <body2>
|
|
39
|
+
_ <default-body>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
在 Cirru 的缩进规则下,每一行的 `pattern` 和 `body` 构成一个二元组 `(pattern body)`,自然映射为 AST 中的 pair。
|
|
43
|
+
|
|
44
|
+
### Pattern 形式
|
|
45
|
+
|
|
46
|
+
| Pattern | 含义 | 示例 |
|
|
47
|
+
| ------------------ | -------------------------- | -------------------------- |
|
|
48
|
+
| `(:tag)` | 零载荷变体 | `(:ok) :success` |
|
|
49
|
+
| `(:tag b1 b2 ...)` | 带载荷变体,绑定到局部变量 | `(:err msg) (println msg)` |
|
|
50
|
+
| `_` | 通配符(匹配所有) | `_ :default` |
|
|
51
|
+
|
|
52
|
+
### 完整示例
|
|
53
|
+
|
|
54
|
+
```cirru
|
|
55
|
+
defenum Result0 (:ok) (:err :string)
|
|
56
|
+
|
|
57
|
+
let
|
|
58
|
+
r $ %:: Result0 :err |oops
|
|
59
|
+
match r
|
|
60
|
+
(:ok) :success
|
|
61
|
+
(:err msg) msg
|
|
62
|
+
; => |oops
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 与 `tag-match` 的对比
|
|
66
|
+
|
|
67
|
+
`match` 和 `tag-match` 的分支书写格式完全相同(都是 `(pattern body)` 对),迁移仅需将 `tag-match` 关键字替换为 `match`:
|
|
68
|
+
|
|
69
|
+
```cirru
|
|
70
|
+
; Before
|
|
71
|
+
tag-match r
|
|
72
|
+
(:ok) :success
|
|
73
|
+
(:err msg) msg
|
|
74
|
+
|
|
75
|
+
; After
|
|
76
|
+
match r
|
|
77
|
+
(:ok) :success
|
|
78
|
+
(:err msg) msg
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 4. 预处理阶段
|
|
82
|
+
|
|
83
|
+
### 类型推断
|
|
84
|
+
|
|
85
|
+
通过 `infer_type_from_expr` 从值表达式推断枚举类型:
|
|
86
|
+
|
|
87
|
+
1. 直接类型标注 → `CalcitTypeAnnotation::Tuple(enum_ref)` → 得到 `CalcitEnum`
|
|
88
|
+
2. TypeSlot 引用 → 解析后得到 `CalcitTypeAnnotation::Enum(e, _)` → 得到 `CalcitEnum`
|
|
89
|
+
3. 推断失败 → 跳过穷尽性检查(仍可在运行时匹配)
|
|
90
|
+
|
|
91
|
+
### 变体校验
|
|
92
|
+
|
|
93
|
+
对每个 `(:tag binding ...)` pattern:
|
|
94
|
+
|
|
95
|
+
- 检查 `tag` 是否存在于枚举定义的 variants 中
|
|
96
|
+
- 检查 binding 数量是否匹配 variant 的 `arity()`
|
|
97
|
+
- 不匹配时产生 `[Warn]` 预处理警告
|
|
98
|
+
|
|
99
|
+
### 穷尽性检查
|
|
100
|
+
|
|
101
|
+
收集所有分支覆盖的 tag → 与枚举的全部 variant tag 做集合差:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
missing = all_variants - covered_tags
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
若 `missing` 非空且无 `_` 通配符,产生警告:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
[Warn] match on `Result0` is not exhaustive. Missing variant(s): [:ok]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 绑定类型推断
|
|
114
|
+
|
|
115
|
+
对 pattern 中的每个 binding symbol,从 `EnumVariant::payload_types()` 获取对应位置的类型标注,创建 `CalcitLocal` 并注入 body 的作用域类型表。
|
|
116
|
+
|
|
117
|
+
## 5. 运行时行为
|
|
118
|
+
|
|
119
|
+
`syntax_match` 的执行流程:
|
|
120
|
+
|
|
121
|
+
1. 求值 `value` 表达式
|
|
122
|
+
2. 解构为 `CalcitTuple { tag, extra, .. }`
|
|
123
|
+
3. 遍历分支对 `(pattern body)`:
|
|
124
|
+
- 若 pattern 是 `_` → 匹配成功,求值 body 返回
|
|
125
|
+
- 若 pattern 是 `(:tag b1 b2 ...)` 且 tag 相等且 arity 匹配 → 创建作用域绑定 `b1=extra[0], b2=extra[1], ...`,求值 body 返回
|
|
126
|
+
4. 所有分支都不匹配 → 运行时错误 `"match: no matching branch for tag :xxx"`
|
|
127
|
+
|
|
128
|
+
## 6. JS 代码生成
|
|
129
|
+
|
|
130
|
+
生成立即执行函数表达式(IIFE):
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
(() => {
|
|
134
|
+
let match_v = <value>;
|
|
135
|
+
let match_t = _$n_tuple_$o_nth(match_v, 0);
|
|
136
|
+
if (match_t === _kn_ok && _$n_tuple_$o_count(match_v) === 1) {
|
|
137
|
+
return kwd_$o_success; // (:ok) :success
|
|
138
|
+
} else if (match_t === _kn_err && _$n_tuple_$o_count(match_v) === 2) {
|
|
139
|
+
let msg = _$n_tuple_$o_nth(match_v, 1);
|
|
140
|
+
return msg; // (:err msg) msg
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error("match: no matching branch for tag");
|
|
143
|
+
}
|
|
144
|
+
})()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
通配符 `_` 分支生成无条件 `else` 块。
|
|
148
|
+
|
|
149
|
+
## 7. 实现文件
|
|
150
|
+
|
|
151
|
+
| 文件 | 变更 |
|
|
152
|
+
| ------------------------------ | -------------------------------------------------- |
|
|
153
|
+
| `src/calcit/syntax_name.rs` | `CalcitSyntax::Match` 变体 + `SyntaxTypeSignature` |
|
|
154
|
+
| `src/builtins/syntax.rs` | `syntax_match()` 运行时处理器 |
|
|
155
|
+
| `src/builtins.rs` | dispatch 入口 |
|
|
156
|
+
| `src/runner/preprocess/mod.rs` | `preprocess_match()` 预处理+穷尽性检查 |
|
|
157
|
+
| `src/codegen/emit_js.rs` | `gen_match_code()` JS 代码生成 |
|
|
158
|
+
| `calcit/test-enum.cirru` | 测试用例 |
|
|
159
|
+
|
|
160
|
+
## 8. 迁移指南
|
|
161
|
+
|
|
162
|
+
`tag-match` 和 `match` 的分支语法完全一致,迁移只需:
|
|
163
|
+
|
|
164
|
+
1. 将 `tag-match` 替换为 `match`
|
|
165
|
+
2. 确保被匹配的值有明确的枚举类型标注(以启用穷尽性检查)
|
|
166
|
+
3. 根据编译器的穷尽性警告补充遗漏的分支或添加 `_` 通配符
|
|
167
|
+
|
|
168
|
+
`tag-match` 仍然可用,不会被移除。
|
|
169
|
+
|
|
170
|
+
## 9. 限制与未来工作
|
|
171
|
+
|
|
172
|
+
- **当前**:类型推断依赖函数 schema 或显式 `%::` 构造,局部变量的枚举类型有时无法推断
|
|
173
|
+
- **当前**:不支持嵌套 pattern(如 `(:ok (:inner x))`)
|
|
174
|
+
- **未来**:可扩展为支持 struct/record 的解构匹配
|
|
175
|
+
- **未来**:可添加 `match` 表达式的返回类型推断(从所有分支 body 计算 union type)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# 类型导向优化机会目录
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
随着 `&record:nth` 的 O(1) 索引重写(commit `2fd2776` 及后续 JS field-tag 修复)落地,Calcit 预处理阶段已具备"在类型已知时改写 AST 以提升运行效率"的基础设施。本文档系统梳理各数据结构的现状与可做的同类优化,供后续逐项推进。
|
|
6
|
+
|
|
7
|
+
## 数据结构现状
|
|
8
|
+
|
|
9
|
+
| 类型 | 内部表示 | 查询复杂度 | 更新复杂度 | 已有编译期优化 |
|
|
10
|
+
| ------ | ----------------------------------------------- | ----------------------------- | ------------------- | ---------------------------------------------------------------------------- |
|
|
11
|
+
| Record | `Vec<Calcit>` + 字段按字母排序的 `CalcitStruct` | O(log n) 二分 | O(n) clone Vec | `&record:nth` 索引重写 ✅, `&record:assoc-at` ✅ P1, `&record:with-at` ✅ P2 |
|
|
12
|
+
| Map | `rpds::HashTrieMapSync` | O(1) hash | O(1) persistent | 无 |
|
|
13
|
+
| List | `Vec` / `TernaryTreeList` 自动切换 | O(1) 或 O(log n) | O(1) prepend/append | 结构自动选择 |
|
|
14
|
+
| Tuple | tag + `Vec<Calcit>` | O(1) index | O(n) clone | enum variant HashMap 查找 |
|
|
15
|
+
| Set | `rpds::HashTrieSetSync` | O(1) hash | O(1) persistent | 无 |
|
|
16
|
+
| Scope | `Vec<ScopePair>` | O(n) 反向线性扫描(缓存友好) | O(1) push | ✅ P5 — 从 `TernaryTreeList` 改为 `Vec` |
|
|
17
|
+
|
|
18
|
+
## 优化项目
|
|
19
|
+
|
|
20
|
+
### P0: Tag 调用 Record 的运行时 fallback ✅ `0116e54`
|
|
21
|
+
|
|
22
|
+
**问题**: `(:field x)` 在 runner.rs 只处理 `Calcit::Map`。当 `x` 是 `Calcit::Record` 时直接报错 `"expected a hashmap"`。预处理阶段的 `(:tag record)` → `&record:nth` 重写仅在类型已知时生效;类型未知时走 fallback 触发运行时错误。
|
|
23
|
+
|
|
24
|
+
**位置**: `src/runner.rs` L318-336,`Calcit::Tag(k)` 分支。
|
|
25
|
+
|
|
26
|
+
**方案**: 在 Map 分支后增加 Record 处理:
|
|
27
|
+
|
|
28
|
+
```rust
|
|
29
|
+
} else if let Calcit::Record(record) = &v {
|
|
30
|
+
Ok(record.get(k.ref_str()).cloned().unwrap_or(Calcit::Nil))
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**性质**: 正确性修复,非纯性能优化。是渐进式类型策略的前提。
|
|
35
|
+
|
|
36
|
+
**影响**: 高 — 允许 `(:field x)` 在类型未知时也能正确运行。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### P1: `&record:assoc` 编译期索引化 ✅ `44a8bfa`
|
|
41
|
+
|
|
42
|
+
**问题**: `&record:assoc record :field value` 运行时做 `index_of` 二分查找定位字段,然后 clone 整个 `Vec<Calcit>` 再改一个位置。
|
|
43
|
+
|
|
44
|
+
**位置**: `src/builtins/records.rs` L1020-1055。
|
|
45
|
+
|
|
46
|
+
**方案**: 模式与 `&record:nth` 完全一致 — 预处理阶段类型已知时,算出字段索引,emit `NativeRecordAssocAt(record, idx, :tag, value)`:
|
|
47
|
+
|
|
48
|
+
- 新增 `CalcitProc::NativeRecordAssocAt` variant
|
|
49
|
+
- `proc_name.rs` 添加签名 `(record, number, tag, any)`
|
|
50
|
+
- `records.rs` 添加 `record_assoc_at` 运行时函数(跳过 `index_of`,直接 `values[idx] = value`)
|
|
51
|
+
- `emit_js.rs` 添加 codegen(JS 端同样省掉运行时字段查找)
|
|
52
|
+
- `preprocess/mod.rs` 在检测到 `NativeRecordAssoc` + 已知类型时做重写
|
|
53
|
+
|
|
54
|
+
**收益**: 每次 Record 字段更新省一次 O(log n) 二分查找。
|
|
55
|
+
|
|
56
|
+
**影响**: 中高 — Record 更新在 Cumulo updater 和 Respo state 中是高频操作。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### P2: `&record:with` 批量更新索引化 ✅ `0e705f1`
|
|
61
|
+
|
|
62
|
+
**问题**: `&record:with record :a 1 :b 2` 每个字段都做一次 `index_of` 二分查找。
|
|
63
|
+
|
|
64
|
+
**位置**: `src/builtins/records.rs` L630-720。
|
|
65
|
+
|
|
66
|
+
**方案**: 类型已知时,编译期预计算所有字段索引,emit 一个携带 `[(idx, value)]` 的批量更新指令。
|
|
67
|
+
|
|
68
|
+
**收益**: k 个字段更新从 k×O(log n) 降到 O(k)。
|
|
69
|
+
|
|
70
|
+
**影响**: 中 — 构造新 Record variant 或批量 state 更新时受益。
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### P3: `tag-match` 分支整数化 ⬜ 推迟(侵入性大,CalcitTuple 12+ 构造点需改动)
|
|
75
|
+
|
|
76
|
+
**问题**: `tag-match` 运行时用 tag 字符串逐一比较(线性扫描分支)。JS codegen 也是 if-else 链。
|
|
77
|
+
|
|
78
|
+
**位置**: `src/builtins/syntax.rs` L933-1050(runner),`src/codegen/emit_js.rs` L897-978(JS codegen)。
|
|
79
|
+
|
|
80
|
+
**方案**:
|
|
81
|
+
|
|
82
|
+
- **Rust 端**: enum 类型已知时预处理阶段把 tag 比较替换为 `variant_index` 的整数比较
|
|
83
|
+
- **JS 端**: emit `switch (tag.idx)` 替代 if-else 链
|
|
84
|
+
|
|
85
|
+
**收益**: 5+ 分支时 O(n) → O(1)。
|
|
86
|
+
|
|
87
|
+
**影响**: 中 — 状态机、消息路由场景(Cumulo updater dispatcher)明显加速。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### P4: Method dispatch 静态绑定 ⬜ 推迟(复杂度高,待类型覆盖率提高)
|
|
92
|
+
|
|
93
|
+
**问题**: `.method obj` 运行时路径:① 匹配 receiver 类型 → ② 查 impl 列表(builtin 还要 evaluate symbol)→ ③ 线性遍历 `impls` 数组找方法名。
|
|
94
|
+
|
|
95
|
+
**位置**: `src/builtins/meta.rs` L1016-1095,`method_call_impls` 函数。
|
|
96
|
+
|
|
97
|
+
**方案**: 预处理阶段 receiver 类型和 trait 均已知时,直接解析到 `CalcitFn`/`CalcitProc`,把 `.method obj args` 重写为 `(resolved-fn obj args)`。
|
|
98
|
+
|
|
99
|
+
**收益**: 消除 symbol resolution + linear impl search。
|
|
100
|
+
|
|
101
|
+
**影响**: 中 — `.map`, `.filter`, `.show` 等核心 API 全部走 method dispatch。
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### P5: Scope 变量查找优化 ✅ `07d6dfc`
|
|
106
|
+
|
|
107
|
+
**问题**: `CalcitScope` 用 `TernaryTreeList<ScopePair>` 存变量,lookup 向后线性扫描 O(n)。每次变量引用(每个表达式节点)都付出这个代价。
|
|
108
|
+
|
|
109
|
+
**位置**: `src/calcit/fns.rs`。
|
|
110
|
+
|
|
111
|
+
**实际方案**: 采用 **Vec 方案** — 把 `CalcitScope` 内部从 `TernaryTreeList<ScopePair>` 改为 `Vec<ScopePair>`,`get()` 用 `.iter().rev()` 反向线性扫描。对于典型 2-3 变量的小 scope,Vec 的缓存局部性远优于 tree 结构。
|
|
112
|
+
|
|
113
|
+
**注**: HashMap 方案实测有 ~1% 回退(hash 开销大于小 scope 的线性扫描),Vec 方案反而带来 ~13% fibo 基准提升。
|
|
114
|
+
|
|
115
|
+
**收益**: fibo 基准从 ~836ms 降到 ~728ms(~13% 提升)。
|
|
116
|
+
|
|
117
|
+
**影响**: 高 — 变量查找是最热操作,对解释器整体吞吐量有根本性影响。
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### P6: `get-in` / `assoc-in` 静态路径展开
|
|
122
|
+
|
|
123
|
+
**问题**: `get-in base [:a :b :c]` 是 Calcit 编写的递归函数(`calcit-core.cirru`),每层递归拆列表 + 动态 `get`。
|
|
124
|
+
|
|
125
|
+
**方案**: 路径是字面量列表且 base 类型已知 Record 时,编译期展开为嵌套 `&record:nth`。`assoc-in` 同理展开为嵌套的 `&record:assoc-at`。
|
|
126
|
+
|
|
127
|
+
**收益**: 消除递归、list 分解、运行时字段查找。
|
|
128
|
+
|
|
129
|
+
**影响**: 中低 — 频率中等,但 Cumulo updater 中 `assoc-in db [:users user-id :field] value` 是核心模式。
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### P7: `if` 条件常量折叠 ✅ `fa325c6`
|
|
134
|
+
|
|
135
|
+
**问题**: `if true x y` 运行时仍求值条件。
|
|
136
|
+
|
|
137
|
+
**方案**: 预处理阶段条件是字面量 `true`/`false`/`nil` 时直接消除分支。在 `preprocess_if()` 中,预处理完 cond/true/false 三个子表达式后,检查 `match &cond_form { Calcit::Bool(true) => return Ok(true_form), Calcit::Bool(false) | Calcit::Nil => return Ok(false_form), _ => {} }`。
|
|
138
|
+
|
|
139
|
+
**影响**: 低 — 手写代码少见,但宏展开后常见。
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 实现模式参考
|
|
144
|
+
|
|
145
|
+
所有 Record 相关优化遵循 `&record:nth` 已验证的模式:
|
|
146
|
+
|
|
147
|
+
1. `proc_name.rs` — 新增 `CalcitProc` variant + `ProcTypeSignature`
|
|
148
|
+
2. `records.rs` — 新增运行时函数(接受已解析的索引参数)
|
|
149
|
+
3. `preprocess/mod.rs` — 检测原始 proc + 类型已知时重写 AST
|
|
150
|
+
4. `emit_js.rs` — 添加 codegen 分支(JS 可能需要不同策略,参考 `&record:nth` 的 field-tag 方案)
|
|
151
|
+
5. `type_checking.rs` / `type_inference.rs` — 确保新 proc 参与类型检查
|
|
152
|
+
|
|
153
|
+
## 执行记录
|
|
154
|
+
|
|
155
|
+
### 已完成(fibo 基准: 836ms → 718ms,总提升 ~14%)
|
|
156
|
+
|
|
157
|
+
| 顺序 | 项目 | Commit | 关键变更 | 基准影响 |
|
|
158
|
+
| ---- | ---- | --------- | --------------------------------------- | ---------------------------------- |
|
|
159
|
+
| 1 | P0 | `0116e54` | runner.rs Tag-call Record fallback | 正确性修复 |
|
|
160
|
+
| 2 | P1 | `44a8bfa` | NativeRecordAssocAt + preprocess 重写 | Record 更新加速(fibo 不涉及) |
|
|
161
|
+
| 3 | P5 | `07d6dfc` | Scope 从 TernaryTreeList 改为 Vec | ~13% fibo 提升(最大单项收益) |
|
|
162
|
+
| 4 | P2 | `0e705f1` | NativeRecordWithAt 批量索引化 | Record 批量更新加速(fibo 不涉及) |
|
|
163
|
+
| 5 | P7 | `fa325c6` | if 常量折叠 | 宏展开场景受益 |
|
|
164
|
+
| - | 额外 | `7c04880` | runtime def resolution 去重复 RwLock 读 | 微优化 |
|
|
165
|
+
|
|
166
|
+
### 待定
|
|
167
|
+
|
|
168
|
+
- **P3**: tag-match 整数化 — 推迟,CalcitTuple 12+ 构造点需改动,侵入性大
|
|
169
|
+
- **P4**: Method dispatch 静态绑定 — 推迟,实现复杂度高
|
|
170
|
+
- **P6**: get-in/assoc-in 静态路径展开 — 未开始,视实际瓶颈决定
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# WASM 编译可行性评估
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
评估 Calcit 子集编译到 WASM 执行的可行性,分三个层面:
|
|
6
|
+
|
|
7
|
+
1. **解释器本身编译为 WASM**(在浏览器中运行 Calcit 解释器)
|
|
8
|
+
2. **Calcit 源码 AOT 编译到 WASM**(把 Calcit 程序直接翻译为 WASM 字节码)
|
|
9
|
+
3. **利用 WASM GC proposal** 的远期路径
|
|
10
|
+
|
|
11
|
+
## 依赖兼容性
|
|
12
|
+
|
|
13
|
+
### 核心依赖(Library crate)
|
|
14
|
+
|
|
15
|
+
| 依赖 | 用途 | WASM 兼容 | 说明 |
|
|
16
|
+
|------|------|----------|------|
|
|
17
|
+
| `rpds` 1.1 | Map/Set persistent DS | ✅ | 支持 `no_std`,纯 Rust |
|
|
18
|
+
| `im_ternary_tree` 0.0.20 | CalcitList 后端 | ✅ | 纯 Rust,无平台依赖 |
|
|
19
|
+
| `cirru_edn` | EDN 解析 | ✅ | 纯 Rust |
|
|
20
|
+
| `cirru_parser` | Cirru 语法解析 | ✅ | 纯 Rust |
|
|
21
|
+
| `rmp-serde` | MessagePack 序列化 | ✅ | 纯 Rust |
|
|
22
|
+
|
|
23
|
+
### CLI-only 依赖(已有或需要 cfg 门控)
|
|
24
|
+
|
|
25
|
+
| 依赖 | WASM | 现状 |
|
|
26
|
+
|------|------|------|
|
|
27
|
+
| `libloading` 0.9 | ❌ | 已 `cfg(not(wasm32))` 排除 |
|
|
28
|
+
| `ctrlc` 3.4 | ❌ | 已 `cfg(not(wasm32))` 排除 |
|
|
29
|
+
| `notify` 8.0 | ❌ | 仅 `src/bin/cr.rs` 使用,需新增门控 |
|
|
30
|
+
| `walkdir` 2.5 | ❌ | 仅 CLI 路径扫描,需新增门控 |
|
|
31
|
+
| `ureq` 3.2 | ⚠️ | 有 `wasm` feature 但需验证 |
|
|
32
|
+
|
|
33
|
+
**结论**: 核心数据结构和运行时无阻塞性依赖。
|
|
34
|
+
|
|
35
|
+
## 全局状态审计
|
|
36
|
+
|
|
37
|
+
WASM 是单线程的,以下全局状态需要关注:
|
|
38
|
+
|
|
39
|
+
### RwLock 包装(兼容,无需改动)
|
|
40
|
+
|
|
41
|
+
- `PROGRAM_CODE_DATA` — `LazyLock<RwLock<ProgramCodeData>>`
|
|
42
|
+
- `PROGRAM_RUNTIME_DATA_STATE` — `LazyLock<RwLock<ProgramRuntimeData>>`
|
|
43
|
+
- `PROGRAM_COMPILED_DATA_STATE` — `LazyLock<RwLock<ProgramCompiledData>>`
|
|
44
|
+
- `PROGRAM_DEF_ID_INDEX` — `LazyLock<RwLock<ProgramDefIdIndex>>`
|
|
45
|
+
|
|
46
|
+
WASM 单线程下 `RwLock` 退化为无竞争路径,开销极低。
|
|
47
|
+
|
|
48
|
+
### thread_local!(需评估)
|
|
49
|
+
|
|
50
|
+
| 位置 | 变量 | 影响 |
|
|
51
|
+
|------|------|------|
|
|
52
|
+
| `type_annotation.rs` | `TYPE_ANNOTATION_WARNING_CONTEXT` | WASM 下变全局,语义不变 |
|
|
53
|
+
| `type_annotation.rs` | `TYPE_SLOTS` | 同上 |
|
|
54
|
+
| `preprocess/mod.rs` | `PREPROCESS_COMPILE_GUARD` | 同上 |
|
|
55
|
+
| `preprocess/mod.rs` | `EXPECTED_FN_TYPE` | 同上 |
|
|
56
|
+
| `preprocess/mod.rs` | `EXPECTED_STRUCT_TYPE` | 同上 |
|
|
57
|
+
| `emit_js.rs` | `INLINE_ALL_ARGS` | codegen 专用,WASM target 可能不需要 |
|
|
58
|
+
|
|
59
|
+
**结论**: `thread_local!` 在 WASM 下退化为进程全局(因为只有一个线程),语义上没有破坏。不需要大规模重构,但如果追求整洁可改为显式参数传递(约 15 处)。
|
|
60
|
+
|
|
61
|
+
### AtomicBool(兼容)
|
|
62
|
+
|
|
63
|
+
- `CODEGEN_MODE`, `CODEGEN_SKIP_ARITY_CHECK` — 单线程下原子操作退化为普通读写。
|
|
64
|
+
|
|
65
|
+
## 路径一:解释器编译为 WASM
|
|
66
|
+
|
|
67
|
+
### 方案
|
|
68
|
+
|
|
69
|
+
- 目标: `wasm32-unknown-unknown`
|
|
70
|
+
- 仅编译 lib crate(排除 `src/bin/` 全部)
|
|
71
|
+
- 通过 `wasm-bindgen` 或 `wasm-pack` 导出 API
|
|
72
|
+
- 内嵌 `calcit-core.rmp` snapshot 作为 WASM 数据段(`include_bytes!` 天然支持)
|
|
73
|
+
|
|
74
|
+
### 导出接口设计
|
|
75
|
+
|
|
76
|
+
```rust
|
|
77
|
+
#[wasm_bindgen]
|
|
78
|
+
pub fn eval_calcit(code: &str) -> Result<String, String> {
|
|
79
|
+
// 1. 加载内嵌 core snapshot
|
|
80
|
+
// 2. 解析 code 为 Cirru
|
|
81
|
+
// 3. 预处理 + 执行
|
|
82
|
+
// 4. 返回序列化结果
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 需要的改动
|
|
87
|
+
|
|
88
|
+
1. 补充 `cfg` 门控:`notify`, `walkdir` 相关代码
|
|
89
|
+
2. `Cargo.toml` 添加 `[lib]` crate-type 含 `cdylib`
|
|
90
|
+
3. 添加 `wasm-bindgen` 可选依赖
|
|
91
|
+
4. 处理 `std::fs::read_to_string` 等 IO 调用(lib 内应已无直接调用,需验证)
|
|
92
|
+
|
|
93
|
+
### 工作量
|
|
94
|
+
|
|
95
|
+
小 — 主要是补门控和写胶水代码。
|
|
96
|
+
|
|
97
|
+
### 用途
|
|
98
|
+
|
|
99
|
+
- 浏览器端 Calcit REPL / playground
|
|
100
|
+
- 在线文档中的交互式代码示例
|
|
101
|
+
- 嵌入到 VS Code WebView 中执行
|
|
102
|
+
|
|
103
|
+
## 路径二:AOT 编译 Calcit 子集到 WASM
|
|
104
|
+
|
|
105
|
+
### 子集定义
|
|
106
|
+
|
|
107
|
+
能编译到 WASM 的 Calcit 特性:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
✅ 可静态编译:
|
|
111
|
+
- defn / fn(纯函数)
|
|
112
|
+
- Record(defstruct / %{})— 映射到线性内存 struct
|
|
113
|
+
- Enum + tag-match — 映射到整数 tag + switch
|
|
114
|
+
- Number / Bool / Tag — 映射到 WASM 原生类型
|
|
115
|
+
- let 绑定
|
|
116
|
+
- if / cond 条件
|
|
117
|
+
- recur(尾递归 → WASM loop)
|
|
118
|
+
- 算术运算(+, -, *, /, mod, 比较)
|
|
119
|
+
|
|
120
|
+
⚠️ 需要 host runtime bridge:
|
|
121
|
+
- String 操作 — WASM 无原生字符串,需 host 分配
|
|
122
|
+
- persistent Map / Set — 需 GC 或 host call
|
|
123
|
+
- List 高阶操作(map / filter / foldl)— 回调需函数引用
|
|
124
|
+
|
|
125
|
+
❌ 排除(留给解释器 / JS codegen):
|
|
126
|
+
- 宏系统(编译期展开,不进入 WASM)
|
|
127
|
+
- eval / quasiquote(需完整解释器)
|
|
128
|
+
- 动态 method dispatch(无法静态解析)
|
|
129
|
+
- Atom / Ref(可变状态需 host 端管理)
|
|
130
|
+
- FFI / dylib 调用
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 编译策略
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Calcit 源码
|
|
137
|
+
→ 预处理(宏展开 + 类型推导 + 重写优化)
|
|
138
|
+
→ IR(复用 gen_ir.rs 的 Cirru EDN IR)
|
|
139
|
+
→ emit_wasm.rs(新模块,IR → WAT/WASM)
|
|
140
|
+
→ .wasm 二进制
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
新增 `src/codegen/emit_wasm.rs` 与 `emit_js.rs` 并列:
|
|
144
|
+
|
|
145
|
+
- 函数 → `(func $name (param ...) (result ...) ...)`
|
|
146
|
+
- Record → 线性内存布局,字段按排序索引直接偏移
|
|
147
|
+
- Tag → 整数常量(编译期分配)
|
|
148
|
+
- tag-match → `br_table`(WASM 跳转表)
|
|
149
|
+
- let → `local.set` / `local.get`
|
|
150
|
+
- recur → `loop` + `br`
|
|
151
|
+
|
|
152
|
+
### 类型要求
|
|
153
|
+
|
|
154
|
+
**必须全局类型已知** — 这是 AOT 路径的硬前提:
|
|
155
|
+
|
|
156
|
+
- 所有函数签名完整(schema 覆盖)
|
|
157
|
+
- Record 字段类型确定(`CalcitStruct.field_types` 非 Dynamic)
|
|
158
|
+
- Enum variant payload 类型确定
|
|
159
|
+
|
|
160
|
+
当前类型系统已支持这些标注,但实际代码库覆盖率取决于用户代码。
|
|
161
|
+
|
|
162
|
+
### 内存模型
|
|
163
|
+
|
|
164
|
+
| Calcit 类型 | WASM 表示 | 大小 |
|
|
165
|
+
|-------------|----------|------|
|
|
166
|
+
| Number (f64) | f64 | 8 bytes |
|
|
167
|
+
| Bool | i32 (0/1) | 4 bytes |
|
|
168
|
+
| Tag | i32 (编译期整数) | 4 bytes |
|
|
169
|
+
| Nil | i32 (sentinel) | 4 bytes |
|
|
170
|
+
| Record | 线性内存 struct(字段连续排列) | Σ field sizes |
|
|
171
|
+
| Tuple | tag(i32) + payload(线性内存) | 4 + Σ payload sizes |
|
|
172
|
+
| String | host ref (externref / i32 handle) | 4 bytes |
|
|
173
|
+
| Map/Set/List | host ref | 4 bytes |
|
|
174
|
+
|
|
175
|
+
### 工作量
|
|
176
|
+
|
|
177
|
+
大 — 需要完整的类型推导验证、内存布局设计、WAT 代码生成、host bridge 规范。
|
|
178
|
+
|
|
179
|
+
## 路径三:WASM GC(远期)
|
|
180
|
+
|
|
181
|
+
### 背景
|
|
182
|
+
|
|
183
|
+
WASM GC proposal(2024 年起 V8/SpiderMonkey 已发布)提供:
|
|
184
|
+
|
|
185
|
+
- `struct.new` / `struct.get` — 结构体
|
|
186
|
+
- `array.new` / `array.get` — 数组
|
|
187
|
+
- `ref` 类型 — GC 自动管理的引用
|
|
188
|
+
|
|
189
|
+
### 映射
|
|
190
|
+
|
|
191
|
+
| Calcit | WASM GC |
|
|
192
|
+
|--------|---------|
|
|
193
|
+
| Record | `(type $Person (struct (field $age f64) (field $name (ref string))))` |
|
|
194
|
+
| List | `(type $List (array (ref any)))` |
|
|
195
|
+
| Tuple | `(type $Tuple (struct (field $tag i32) (field $extra (ref $List))))` |
|
|
196
|
+
| Map | 需要 host runtime 或自实现 hash trie |
|
|
197
|
+
|
|
198
|
+
### 优势
|
|
199
|
+
|
|
200
|
+
- 无需手动内存管理
|
|
201
|
+
- Persistent data structure 的 structural sharing 天然映射(GC 管理引用)
|
|
202
|
+
- 与 JS 互操作更自然(`externref` 直接传递)
|
|
203
|
+
|
|
204
|
+
### 工作量
|
|
205
|
+
|
|
206
|
+
取决于 WASM GC 生态成熟度。2026 年浏览器支持已基本到位,但工具链(assembler、debugger)仍在完善中。
|
|
207
|
+
|
|
208
|
+
## 可行性矩阵
|
|
209
|
+
|
|
210
|
+
| 维度 | 路径一(解释器→WASM) | 路径二(AOT→WASM) | 路径三(WASM GC) |
|
|
211
|
+
|------|---------------------|-------------------|-----------------|
|
|
212
|
+
| 语言覆盖 | 100%(完整解释器) | ~40%(纯函数子集) | ~70%(加 GC 后扩展) |
|
|
213
|
+
| 性能 | 中(解释开销) | 高(原生 WASM) | 高 |
|
|
214
|
+
| 工作量 | 小 | 大 | 大 |
|
|
215
|
+
| 类型要求 | 无 | 全量类型标注 | 大部分类型标注 |
|
|
216
|
+
| 依赖 | wasm-bindgen | 无额外 | WASM GC runtime |
|
|
217
|
+
| 适用场景 | playground/REPL | 热路径加速 | 通用 WASM 编译目标 |
|
|
218
|
+
|
|
219
|
+
## 建议路线
|
|
220
|
+
|
|
221
|
+
1. **近期**: 路径一 — 尝试 `cargo build --target wasm32-unknown-unknown --lib` 确认编译通过,排查剩余链接错误
|
|
222
|
+
2. **中期**: 路径二的子集 — 从纯数值计算函数开始,生成 WASM 模块作为 JS codegen 的"加速岛"(hot island),由 JS 运行时按需调用
|
|
223
|
+
3. **远期**: 路径三 — 随 WASM GC 工具链成熟,逐步扩展可编译子集
|
|
224
|
+
|
|
225
|
+
## 验证第一步
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# 在 Cargo.toml 添加:
|
|
229
|
+
# [lib]
|
|
230
|
+
# crate-type = ["cdylib", "rlib"]
|
|
231
|
+
|
|
232
|
+
# 然后尝试:
|
|
233
|
+
cargo build --target wasm32-unknown-unknown --lib 2>&1 | head -50
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
输出的链接错误即为需要 `cfg` 门控的最终清单。
|