@calcit/procs 0.12.20 → 0.12.21
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/202604170520-simplify-generic-defns-via-method-dispatch.md +49 -0
- package/editing-history/202604172316-wasm-do-println-fixes.md +41 -0
- package/editing-history/202604181208-split-emit-wasm-and-bump-version.md +15 -0
- package/editing-history/202604181430-wasm-list-match-and-method-dispatch.md +5 -0
- package/lib/calcit.procs.mjs +53 -0
- package/lib/package.json +2 -1
- package/package.json +2 -1
- package/ts-src/calcit.procs.mts +54 -0
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Simplify generic core defns via `.method` dispatch
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
将 `calcit-core.cirru` 中的泛型 defn(`assoc` / `contains?` / `count` / `empty` / `empty?` / `filter` / `first` / `get` / `includes?` / `nth` / `rest` / `map`)从多分支 `if (list? x) ... if (map? x) ... if (record? x) ...` 链简化为 `list?` 快速路径 + `.method` 动态分发。
|
|
6
|
+
|
|
7
|
+
## 动机
|
|
8
|
+
|
|
9
|
+
- 用户要求:优先使用 `.method` 这套已有概念承担多态分发,避免每新增类型(record / tuple / set 等)都要在每个 defn 中追加分支。
|
|
10
|
+
- 所有 built-in 类型已在 `&core-*-methods` 中注册了对应的 `.assoc` `.count` `.empty?` `.get` `.nth` 等方法条目,编译期 `try_inline_method_call` 已能在静态类型已知时把方法调用内联为直接 proc 调用,runtime 则由 `invoke_method` 处理。这使得 defn 自身不再需要枚举类型。
|
|
11
|
+
|
|
12
|
+
## 实现
|
|
13
|
+
|
|
14
|
+
以 `empty?` 为例:
|
|
15
|
+
|
|
16
|
+
```cirru
|
|
17
|
+
defn empty? (x)
|
|
18
|
+
if (nil? x) true $ if (list? x) (&list:empty? x) (.empty? x)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
保留 `nil?` 与 `list?` 两个前置分支,其余类型全部交给 `.empty?`。
|
|
22
|
+
|
|
23
|
+
**为什么保留 `list?` 快速路径**:
|
|
24
|
+
`ensure_ns_def_compiled(CORE_NS, &init-builtin-impls!)` 在预处理期会展开 `do` 等 macro,其中 `(empty? body)` 会在 impls 还未注册到 runtime 时被调用;若此时 `empty?` 体里就走 `.empty?` → `invoke_method` → `evaluate_symbol_from_program("&core-list-impls", ...)` 会命中 quick-path 失败(循环依赖),导致 panic:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
preprocess builtin impls: CalcitErr { kind: Var, msg: "expected symbol `&core-list-impls` from path `calcit.core`, this is a quick path, should succeed", ... }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`list?` 本身只用 `(&= (type-of x) :list)` 等 proc,不依赖 impl 注册,能在 bootstrap 期安全使用,打破循环;其余类型的 methods 此时已构建完毕,可顺利走方法派发。
|
|
31
|
+
|
|
32
|
+
## 波及范围
|
|
33
|
+
|
|
34
|
+
- `src/cirru/calcit-core.cirru`:12 个 defn 的 body 瘦身。
|
|
35
|
+
- 预处理期的 `try_specialize_polymorphic_call`(已有)照常将 `(assoc x k v)` 等静态可推断调用折叠为 `&list:assoc`/`&map:assoc` 等 proc。
|
|
36
|
+
- runtime 侧未变:仍由 `invoke_method` + `&core-*-impls` 完成最终派发。
|
|
37
|
+
|
|
38
|
+
## 验证
|
|
39
|
+
|
|
40
|
+
- `cargo fmt`
|
|
41
|
+
- `cargo clippy --release -- -D warnings`
|
|
42
|
+
- `cargo test --release`:179 + 67 通过
|
|
43
|
+
- `yarn check-all`:全部 WASM/JS/解释执行测试通过
|
|
44
|
+
- recollect (`cr --entry test` + `cr --entry test js` + `node test.mjs`) ✓
|
|
45
|
+
- respo (`cr --check-only` + `cr js` + `yarn vite build`) ✓
|
|
46
|
+
|
|
47
|
+
## 备注
|
|
48
|
+
|
|
49
|
+
- 下一步可以考虑:把那些只剩 `list?` 单分支的 defn 再收拢到一个运行时 primitive,或在 preprocess 阶段对 macro 展开期调用的 `empty?` 等函数强制内联 proc,从而彻底去掉这条 fast path。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 202604172316 — WASM `do` body 与 println host import 修复
|
|
2
|
+
|
|
3
|
+
## 修复内容
|
|
4
|
+
|
|
5
|
+
### 1. `do` 在 defn body 中的 WASM 编译问题
|
|
6
|
+
|
|
7
|
+
**现象**:`defn test-println () do (println 42) 1` 编译后生成 `f64.const 0`(函数体为空)。
|
|
8
|
+
|
|
9
|
+
**根因**:Cirru 语法中 `defn f () do expr1 expr2` 解析为 `(defn f () do expr1 expr2)`,body 数组为 `[do, expr1, expr2]` 三个独立元素(不是 `[(do expr1 expr2)]`)。`do` 作为裸 Import 节点出现在 `emit_expr` 时,触发 "unsupported WASM expression" 错误,导致函数被跳过并 fallback 为 `f64.const 0`。
|
|
10
|
+
|
|
11
|
+
**修复**:在 `emit_expr` 中为 `Calcit::Import { def: "do" }` 添加特例,将其视为 no-op(emit 0.0 后由 `emit_body` 的 Drop 消耗掉)。
|
|
12
|
+
|
|
13
|
+
```rust
|
|
14
|
+
// `do` as bare body expression is a no-op sequencer
|
|
15
|
+
Calcit::Import(import) if import.def.as_ref() == "do" => {
|
|
16
|
+
ctx.emit(f64_const(0.0));
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. io.log_value host import(WASM println)
|
|
21
|
+
|
|
22
|
+
- WASM codegen 对 `println`/`eprintln`/`echo` 调用 host import `io.log_value`。
|
|
23
|
+
- `test-wasm.mjs` 提供 `io.log_value` 实现:读取堆内存判断类型,字符串解码 UTF-8,数字直接输出。
|
|
24
|
+
- 添加 `test-println` 测试用例验证功能。
|
|
25
|
+
|
|
26
|
+
### 3. TypeScript 运行时 BufList 修复
|
|
27
|
+
|
|
28
|
+
`_$n_buf_list_$o_concat` 中 `CalcitSliceList` 的 `items` 是方法而非属性,需调用 `xs.items()` 并手动迭代 Generator(TS 配置不支持 `for...of` Generator)。
|
|
29
|
+
|
|
30
|
+
## 受影响文件
|
|
31
|
+
|
|
32
|
+
- `src/codegen/emit_wasm.rs`:`emit_expr` 新增 `do` no-op 特例;`emit_call_expr` Import 分支也保留 `do` 处理(用于 `(do ...)` 调用形式)
|
|
33
|
+
- `ts-src/calcit.procs.mts`:修正 `_$n_buf_list_$o_concat` 迭代逻辑
|
|
34
|
+
- `calcit/test-wasm.cirru`:新增 `test-println`
|
|
35
|
+
- `scripts/test-wasm.mjs`:新增 `io.log_value` 实现与 `test-println` 断言
|
|
36
|
+
|
|
37
|
+
## 关键经验
|
|
38
|
+
|
|
39
|
+
- `do` 在 Calcit defn body 中是语法糖/占位符,预处理后展开为多个独立 body 表达式,不是 `(do ...)` 调用形式。WASM codegen 需将其视为 no-op。
|
|
40
|
+
- WASM 编译错误时 fallback 为 `f64.const 0`(静默),调试时需检查 stderr 中的 `[wasm] skipping` 日志。
|
|
41
|
+
- TS 的 Generator 不能直接 `for...of`,需手动调用 `.next()`。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Summary
|
|
2
|
+
|
|
3
|
+
- Split the WASM code generator so `src/codegen/emit_wasm.rs` keeps orchestration while runtime helpers, method dispatch, and record/tuple emitters live in dedicated submodules.
|
|
4
|
+
- Bumped the Calcit patch version from `0.12.20` to `0.12.21` for the next release.
|
|
5
|
+
|
|
6
|
+
## Module Layout
|
|
7
|
+
|
|
8
|
+
- `src/codegen/emit_wasm/runtime.rs` now owns host import definitions, module assembly, and internal runtime helper builders.
|
|
9
|
+
- `src/codegen/emit_wasm/methods.rs` now owns dynamic method dispatch and rest-arg call argument packing.
|
|
10
|
+
- `src/codegen/emit_wasm/records.rs` now owns record and tuple emission helpers.
|
|
11
|
+
|
|
12
|
+
## Notes
|
|
13
|
+
|
|
14
|
+
- The split is intended to be behavior-preserving; no WASM semantics were changed as part of this refactor.
|
|
15
|
+
- Version strings were updated in `Cargo.toml`, `package.json`, and `lib/package.json`, while generated JS version text will be refreshed by the validation build.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
- WASM export names now stay unique by qualifying only colliding defs with `ns/def`.
|
|
2
|
+
- `list-match` macro now uses `&list:empty?` after the existing list guard, avoiding the unsupported generic `empty?` invoke path in WASM.
|
|
3
|
+
- Added minimal `Calcit::Method` handling in WASM for dynamic `.empty?`, `.count`, `.nth`, and `.get` calls.
|
|
4
|
+
- `&tuple:nth` in WASM now accepts dynamic indices instead of literal-only indices.
|
|
5
|
+
- Verified `yarn try-all` still passes in calcit after the WASM changes.
|
package/lib/calcit.procs.mjs
CHANGED
|
@@ -283,6 +283,56 @@ export function _$n_record_$o_count(x) {
|
|
|
283
283
|
return x.fields.length;
|
|
284
284
|
throw new Error(`expected a record ${x}`);
|
|
285
285
|
}
|
|
286
|
+
export function _$n_record_$o_field_tag(x, idx) {
|
|
287
|
+
if (!(x instanceof CalcitRecord))
|
|
288
|
+
throw new Error(`&record:field-tag expected a record, got ${x}`);
|
|
289
|
+
const i = idx;
|
|
290
|
+
if (i < 0 || i >= x.fields.length)
|
|
291
|
+
throw new Error(`&record:field-tag index ${i} out of bounds (${x.fields.length})`);
|
|
292
|
+
return x.fields[i];
|
|
293
|
+
}
|
|
294
|
+
// === BufList — mutable append-only list ===
|
|
295
|
+
// Wrapper around a plain JS Array for O(1) push.
|
|
296
|
+
export class CalcitBufList {
|
|
297
|
+
constructor(buf) {
|
|
298
|
+
this.buf = buf ?? [];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
export function _$n_buf_list_$o_new() {
|
|
302
|
+
return new CalcitBufList();
|
|
303
|
+
}
|
|
304
|
+
export function _$n_buf_list_$o_push(buf, item) {
|
|
305
|
+
if (!(buf instanceof CalcitBufList))
|
|
306
|
+
throw new Error(`&buf-list:push expected a buf-list, got ${buf}`);
|
|
307
|
+
buf.buf.push(item);
|
|
308
|
+
return buf;
|
|
309
|
+
}
|
|
310
|
+
export function _$n_buf_list_$o_concat(buf, xs) {
|
|
311
|
+
if (!(buf instanceof CalcitBufList))
|
|
312
|
+
throw new Error(`&buf-list:concat expected a buf-list, got ${buf}`);
|
|
313
|
+
if (xs instanceof CalcitSliceList || xs instanceof CalcitList) {
|
|
314
|
+
const gen = xs.items();
|
|
315
|
+
let next = gen.next();
|
|
316
|
+
while (!next.done) {
|
|
317
|
+
buf.buf.push(next.value);
|
|
318
|
+
next = gen.next();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
throw new Error(`&buf-list:concat expected a list, got ${xs}`);
|
|
323
|
+
}
|
|
324
|
+
return buf;
|
|
325
|
+
}
|
|
326
|
+
export function _$n_buf_list_$o_to_list(buf) {
|
|
327
|
+
if (!(buf instanceof CalcitBufList))
|
|
328
|
+
throw new Error(`&buf-list:to-list expected a buf-list, got ${buf}`);
|
|
329
|
+
return new CalcitSliceList([...buf.buf]);
|
|
330
|
+
}
|
|
331
|
+
export function _$n_buf_list_$o_count(buf) {
|
|
332
|
+
if (!(buf instanceof CalcitBufList))
|
|
333
|
+
throw new Error(`&buf-list:count expected a buf-list, got ${buf}`);
|
|
334
|
+
return buf.buf.length;
|
|
335
|
+
}
|
|
286
336
|
export function _$n_set_$o_count(x) {
|
|
287
337
|
if (x instanceof CalcitSet)
|
|
288
338
|
return x.len();
|
|
@@ -1387,6 +1437,9 @@ export let nil_$q_ = (x) => {
|
|
|
1387
1437
|
export let tag_$q_ = (x) => {
|
|
1388
1438
|
return x instanceof CalcitTag;
|
|
1389
1439
|
};
|
|
1440
|
+
export let symbol_$q_ = (x) => {
|
|
1441
|
+
return x instanceof CalcitSymbol;
|
|
1442
|
+
};
|
|
1390
1443
|
export let map_$q_ = (x) => {
|
|
1391
1444
|
return x instanceof CalcitSliceMap || x instanceof CalcitMap;
|
|
1392
1445
|
};
|
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calcit/procs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.21",
|
|
4
4
|
"main": "./lib/calcit.procs.mjs",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^25.0.9",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"bench-recur-smoke": "cargo run --bin cr -- calcit/test.cirru js && node --input-type=module -e \"import { test_loop } from './js-out/test-recursion.main.mjs'; const n=3000; const t0=process.hrtime.bigint(); for(let i=0;i<n;i++) test_loop(); const dt=Number(process.hrtime.bigint()-t0)/1e6; console.log('test_loop_ms='+dt.toFixed(3));\"",
|
|
20
20
|
"check-smooth": "yarn fmt-rs && yarn lint-rs && yarn test-rs && yarn check-all",
|
|
21
21
|
"check-all": "yarn compile && yarn try-rs && yarn try-js && yarn try-ir && yarn try-wasm",
|
|
22
|
+
"try-all": "yarn check-all",
|
|
22
23
|
"try-rs": "cargo run --bin cr -- calcit/test.cirru",
|
|
23
24
|
"warn-dyn-method": "cargo run --bin cr -- calcit/test.cirru --warn-dyn-method",
|
|
24
25
|
"try-js-brk": "cargo run --bin cr -- calcit/test.cirru js && node --inspect-brk js-out/main.mjs",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calcit/procs",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.21",
|
|
4
4
|
"main": "./lib/calcit.procs.mjs",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^25.0.9",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"bench-recur-smoke": "cargo run --bin cr -- calcit/test.cirru js && node --input-type=module -e \"import { test_loop } from './js-out/test-recursion.main.mjs'; const n=3000; const t0=process.hrtime.bigint(); for(let i=0;i<n;i++) test_loop(); const dt=Number(process.hrtime.bigint()-t0)/1e6; console.log('test_loop_ms='+dt.toFixed(3));\"",
|
|
20
20
|
"check-smooth": "yarn fmt-rs && yarn lint-rs && yarn test-rs && yarn check-all",
|
|
21
21
|
"check-all": "yarn compile && yarn try-rs && yarn try-js && yarn try-ir && yarn try-wasm",
|
|
22
|
+
"try-all": "yarn check-all",
|
|
22
23
|
"try-rs": "cargo run --bin cr -- calcit/test.cirru",
|
|
23
24
|
"warn-dyn-method": "cargo run --bin cr -- calcit/test.cirru --warn-dyn-method",
|
|
24
25
|
"try-js-brk": "cargo run --bin cr -- calcit/test.cirru js && node --inspect-brk js-out/main.mjs",
|
package/ts-src/calcit.procs.mts
CHANGED
|
@@ -316,6 +316,57 @@ export function _$n_record_$o_count(x: CalcitValue): number {
|
|
|
316
316
|
|
|
317
317
|
throw new Error(`expected a record ${x}`);
|
|
318
318
|
}
|
|
319
|
+
|
|
320
|
+
export function _$n_record_$o_field_tag(x: CalcitValue, idx: CalcitValue): CalcitTag {
|
|
321
|
+
if (!(x instanceof CalcitRecord)) throw new Error(`&record:field-tag expected a record, got ${x}`);
|
|
322
|
+
const i = idx as number;
|
|
323
|
+
if (i < 0 || i >= x.fields.length) throw new Error(`&record:field-tag index ${i} out of bounds (${x.fields.length})`);
|
|
324
|
+
return x.fields[i];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// === BufList — mutable append-only list ===
|
|
328
|
+
// Wrapper around a plain JS Array for O(1) push.
|
|
329
|
+
export class CalcitBufList {
|
|
330
|
+
buf: CalcitValue[];
|
|
331
|
+
constructor(buf?: CalcitValue[]) {
|
|
332
|
+
this.buf = buf ?? [];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function _$n_buf_list_$o_new(): CalcitBufList {
|
|
337
|
+
return new CalcitBufList();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function _$n_buf_list_$o_push(buf: CalcitValue, item: CalcitValue): CalcitBufList {
|
|
341
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:push expected a buf-list, got ${buf}`);
|
|
342
|
+
buf.buf.push(item);
|
|
343
|
+
return buf;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function _$n_buf_list_$o_concat(buf: CalcitValue, xs: CalcitValue): CalcitBufList {
|
|
347
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:concat expected a buf-list, got ${buf}`);
|
|
348
|
+
if (xs instanceof CalcitSliceList || xs instanceof CalcitList) {
|
|
349
|
+
const gen = xs.items();
|
|
350
|
+
let next = gen.next();
|
|
351
|
+
while (!next.done) {
|
|
352
|
+
buf.buf.push(next.value);
|
|
353
|
+
next = gen.next();
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
throw new Error(`&buf-list:concat expected a list, got ${xs}`);
|
|
357
|
+
}
|
|
358
|
+
return buf;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function _$n_buf_list_$o_to_list(buf: CalcitValue): CalcitSliceList {
|
|
362
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:to-list expected a buf-list, got ${buf}`);
|
|
363
|
+
return new CalcitSliceList([...buf.buf]);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function _$n_buf_list_$o_count(buf: CalcitValue): number {
|
|
367
|
+
if (!(buf instanceof CalcitBufList)) throw new Error(`&buf-list:count expected a buf-list, got ${buf}`);
|
|
368
|
+
return buf.buf.length;
|
|
369
|
+
}
|
|
319
370
|
export function _$n_set_$o_count(x: CalcitValue): number {
|
|
320
371
|
if (x instanceof CalcitSet) return x.len();
|
|
321
372
|
|
|
@@ -1504,6 +1555,9 @@ export let nil_$q_ = (x: CalcitValue): boolean => {
|
|
|
1504
1555
|
export let tag_$q_ = (x: CalcitValue): boolean => {
|
|
1505
1556
|
return x instanceof CalcitTag;
|
|
1506
1557
|
};
|
|
1558
|
+
export let symbol_$q_ = (x: CalcitValue): boolean => {
|
|
1559
|
+
return x instanceof CalcitSymbol;
|
|
1560
|
+
};
|
|
1507
1561
|
export let map_$q_ = (x: CalcitValue): boolean => {
|
|
1508
1562
|
return x instanceof CalcitSliceMap || x instanceof CalcitMap;
|
|
1509
1563
|
};
|