feat(aot): add backpropagation pass to CollectionsHot for improved type inference
Implement call-site type signal backpropagation to reduce Unknown receiver types and increase Array/Map get/set/has externcall conversion coverage. **Implementation:** - New function: tmap_backprop (collections_hot.hako:82-164) - Propagates type signals from call sites: push→Array, stringy key→Map, linear index→Array - Fixpoint iteration (max 2 rounds) - Control: NYASH_AOT_CH_BACKPROP=1 (default ON) - Enhanced is_stringy_key_in_block - Detects toString method, StringBox const, binop + StringBox const - Diagnostic logging with NYASH_AOT_CH_TRACE=1 - "[aot/collections_hot] backprop recv=<vid> => arr|map via method=<mname>" **Results:** Test case: /tmp/arraymap_min.hako - ORIG: 7 boxcalls, 0 externcalls - PREP: 1 boxcall, 6 externcalls (86% reduction) - jsonfrag: 0 (structure preserved) Benchmark: tools/perf/microbench.sh --case arraymap --exe - ORIG: 8 boxcalls - PREP: 2 boxcalls, 6 externcalls (75% reduction) - Array: push(1), get(1), set(1) = 3 externcalls - Map: set(2), get(1) = 3 externcalls - Remaining: toString(2) = 2 boxcalls (expected) **Benefits:** - Unknown receiver type reduction via call-site analysis - Improved optimization coverage for Array/Map operations - Opt-in design, CFG unchanged, jsonfrag=0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -16,13 +16,20 @@
|
||||
- CollectionsHot(`NYASH_AOT_COLLECTIONS_HOT=1`)
|
||||
- Array/Map の単純 `boxcall` を `externcall`(`nyash.array.*` / `nyash.map.*`)へ張替え
|
||||
- Map キー戦略は `NYASH_AOT_MAP_KEY_MODE={h|i64|hh|auto}`(既定: `h`/`i64`)
|
||||
- **型確定強化(NEW!)**:
|
||||
- **Backpropagation Pass**(`tmap_backprop`): コールサイトからの型シグナル逆伝播
|
||||
- `push` メソッド → 受信=Array と確定
|
||||
- `get`/`set`/`has` + stringy key(toString/StringBox const/文字列連結)→ 受信=Map
|
||||
- `get`/`set`/`has` + linear index → 受信=Array
|
||||
- fixpoint反復(max 2周)で新事実を追加
|
||||
- ENV制御: `NYASH_AOT_CH_BACKPROP=1`(既定=1)
|
||||
- 型確定の優先順位(上から順に試行):
|
||||
1. 型テーブル(`type_table.has(bvid)`)
|
||||
2. PHI型推論(`peek_phi_type`)
|
||||
3. **後方MIR解析(`resolve_recv_type_backward`)** - NEW!
|
||||
4. メソッド専用判別(push は Array のみ)
|
||||
3. 後方MIR解析(`resolve_recv_type_backward`)
|
||||
4. **メソッド専用判別(push は Array のみ)** - 強化!
|
||||
5. 直接マップ(`arr_recv`/`map_recv`)
|
||||
6. キー文脈ヒューリスティック(`is_stringy_key_in_block`)
|
||||
6. **キー文脈ヒューリスティック(`is_stringy_key_in_block`)** - 強化!
|
||||
7. newbox 逆スキャン
|
||||
- BinopCSE(`NYASH_MIR_LOOP_HOIST=1`)
|
||||
- 同一 binop(加算/乗算を含む)を1回だけ発行し、`copy` で再利用することで線形インデックス(例: `i*n+k`)の再計算を抑制
|
||||
@ -33,7 +40,8 @@ ENV トグル一覧
|
||||
- `NYASH_AOT_COLLECTIONS_HOT=1` … Array/Map hot-path 置換を有効化
|
||||
- `NYASH_AOT_MAP_KEY_MODE` … `h`/`i64`(既定), `hh`, `auto`(将来拡張)
|
||||
- `auto` モードでは `_is_const_or_linear` で args を走査し、単純な i64 定数/線形表現と判定できる場合に `nyash.map.*_h` を選ぶ(それ以外は `*_hh`)。
|
||||
- `NYASH_AOT_CH_TRACE=1` … CollectionsHot の詳細診断出力(型推論・書き換え情報)
|
||||
- `NYASH_AOT_CH_BACKPROP=1` … Backpropagation Pass有効化(既定=1、0で無効化)
|
||||
- `NYASH_AOT_CH_TRACE=1` … CollectionsHot の詳細診断出力(型推論・書き換え情報・backprop)
|
||||
- `NYASH_VERIFY_RET_PURITY=1` … Return 直前の副作用を Fail-Fast で検出。ベンチはこのトグルをオンにした状態で回しています。
|
||||
|
||||
使い方(例)
|
||||
@ -47,3 +55,14 @@ tools/perf/microbench.sh --case arraymap --exe --runs 3
|
||||
注意
|
||||
- すべて opt‑in。CI/既定ユーザ挙動は従来どおりです。
|
||||
- Return 純化ガード(`NYASH_VERIFY_RET_PURITY=1`)と併用してください。
|
||||
|
||||
Stage-3 キーワード要件
|
||||
- AotPrep 各パスは Stage-B(Nyash自身で書かれた自己ホストコード)のため、`local`/`flow`/`try`/`catch`/`throw` などの Stage-3 キーワードを使用します。
|
||||
- これらのキーワードは `NYASH_PARSER_STAGE3=1` および `HAKO_PARSER_STAGE3=1` が必要です。
|
||||
- **推奨**: `tools/hakorune_emit_mir.sh` を使うこと。このスクリプトは必要なENVを自動設定します。
|
||||
- **手動実行時**: 以下のENVを明示的に付与してください。
|
||||
```bash
|
||||
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
|
||||
./target/release/hakorune --backend vm your_file.hako
|
||||
```
|
||||
- **診断**: `NYASH_TOK_TRACE=1` を追加すると、Stage-3キーワードが識別子に降格される様子が `[tok-stage3]` ログで確認できます。
|
||||
|
||||
@ -78,6 +78,90 @@ static box AotPrepCollectionsHotBox {
|
||||
}
|
||||
return tmap
|
||||
}
|
||||
|
||||
// Backpropagation: Infer receiver types from boxcall method names
|
||||
// Adds type signals from call sites back to receivers (max 2 iterations)
|
||||
tmap_backprop(text, tmap, copy_src) {
|
||||
local enabled = env.get("NYASH_AOT_CH_BACKPROP")
|
||||
if enabled != null && ("" + enabled) == "0" { return }
|
||||
local trace = env.get("NYASH_AOT_CH_TRACE")
|
||||
local iter = 0
|
||||
loop(iter < 2) {
|
||||
local before = tmap.size()
|
||||
local i = 0
|
||||
loop(true) {
|
||||
local os = text.indexOf("{", i)
|
||||
if os < 0 { break }
|
||||
local oe = AotPrepHelpers._seek_object_end(text, os)
|
||||
if oe < 0 { break }
|
||||
local inst = text.substring(os, oe+1)
|
||||
local op = AotPrepCollectionsHotBox.read_field(inst, "op")
|
||||
if op == "boxcall" {
|
||||
local kbox = inst.indexOf("\"box\":")
|
||||
if kbox >= 0 {
|
||||
local recv_vid = StringHelpers.read_digits(inst, kbox+6)
|
||||
if recv_vid != "" {
|
||||
recv_vid = AotPrepCollectionsHotBox.resolve_copy(copy_src, recv_vid)
|
||||
if !tmap.has(recv_vid) {
|
||||
local mname = AotPrepCollectionsHotBox.read_field(inst, "method")
|
||||
if mname == "push" {
|
||||
tmap.set(recv_vid, "arr")
|
||||
if trace != null && (""+trace) == "1" {
|
||||
print("[aot/collections_hot] backprop recv=" + recv_vid + " => arr via method=" + mname)
|
||||
}
|
||||
} else if mname == "get" || mname == "set" || mname == "has" {
|
||||
local kargs = inst.indexOf("\"args\":[")
|
||||
if kargs >= 0 {
|
||||
local a0 = StringHelpers.read_digits(inst, kargs+8)
|
||||
if a0 != "" {
|
||||
a0 = AotPrepCollectionsHotBox.resolve_copy(copy_src, a0)
|
||||
// Check if a0 is stringy (const string or linear index)
|
||||
local is_stringy = 0
|
||||
local needle_a0 = "\"dst\":" + a0
|
||||
local pos_a0 = text.indexOf(needle_a0)
|
||||
if pos_a0 >= 0 {
|
||||
local os_a0 = text.substring(0, pos_a0).lastIndexOf("{")
|
||||
if os_a0 >= 0 {
|
||||
local oe_a0 = AotPrepHelpers._seek_object_end(text, os_a0)
|
||||
if oe_a0 > os_a0 {
|
||||
local inst_a0 = text.substring(os_a0, oe_a0+1)
|
||||
if inst_a0.indexOf("\"box_type\":\"StringBox\"") >= 0 { is_stringy = 1 }
|
||||
if inst_a0.indexOf("\"method\":\"toString\"") >= 0 { is_stringy = 1 }
|
||||
if inst_a0.indexOf("\"op\":\"binop\"") >= 0 {
|
||||
if inst_a0.indexOf("\"operation\":\"+\"") >= 0 || inst_a0.indexOf("\"operation\":\"add\"") >= 0 {
|
||||
local scan_pos = os_a0 - 100
|
||||
if scan_pos < 0 { scan_pos = 0 }
|
||||
local window = text.substring(scan_pos, os_a0 + 200)
|
||||
if window.indexOf("\"box_type\":\"StringBox\"") >= 0 { is_stringy = 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_stringy == 1 {
|
||||
tmap.set(recv_vid, "map")
|
||||
if trace != null && (""+trace) == "1" {
|
||||
print("[aot/collections_hot] backprop recv=" + recv_vid + " => map via method=" + mname)
|
||||
}
|
||||
} else if AotPrepHelpers.is_const_or_linear(text, a0) {
|
||||
tmap.set(recv_vid, "arr")
|
||||
if trace != null && (""+trace) == "1" {
|
||||
print("[aot/collections_hot] backprop recv=" + recv_vid + " => arr via method=" + mname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i = oe + 1
|
||||
}
|
||||
if tmap.size() == before { break }
|
||||
iter = iter + 1
|
||||
}
|
||||
}
|
||||
// Static helpers replacing local fun
|
||||
find_block_span(text, p) {
|
||||
local key = "\"instructions\":["
|
||||
@ -268,8 +352,11 @@ static box AotPrepCollectionsHotBox {
|
||||
local oe = AotPrepHelpers._seek_object_end(text, os)
|
||||
if oe < 0 || oe >= k { return 0 }
|
||||
local inst = text.substring(os, oe+1)
|
||||
// Quick checks
|
||||
// Enhanced detection: toString, binop with StringBox const, direct string const
|
||||
if inst.indexOf("\"method\":\"toString\"") >= 0 { return 1 }
|
||||
if inst.indexOf("\"op\":\"const\"") >= 0 {
|
||||
if inst.indexOf("\"box_type\":\"StringBox\"") >= 0 { return 1 }
|
||||
}
|
||||
if inst.indexOf("\"op\":\"binop\"") >= 0 {
|
||||
// If block contains a StringBox const before k, treat as stringy join
|
||||
local head = text.substring(block_lb, k)
|
||||
@ -389,6 +476,8 @@ static box AotPrepCollectionsHotBox {
|
||||
}
|
||||
// Build lightweight SSA type table (arr/map) on out text for consistency with rewrite scan
|
||||
local type_table = AotPrepCollectionsHotBox.build_type_table(out)
|
||||
// Backpropagation: infer types from call sites (default enabled)
|
||||
AotPrepCollectionsHotBox.tmap_backprop(out, type_table, copy_src)
|
||||
{
|
||||
// Optional trace
|
||||
local tr = env.get("NYASH_AOT_CH_TRACE")
|
||||
|
||||
Reference in New Issue
Block a user