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`)
|
- CollectionsHot(`NYASH_AOT_COLLECTIONS_HOT=1`)
|
||||||
- Array/Map の単純 `boxcall` を `externcall`(`nyash.array.*` / `nyash.map.*`)へ張替え
|
- Array/Map の単純 `boxcall` を `externcall`(`nyash.array.*` / `nyash.map.*`)へ張替え
|
||||||
- Map キー戦略は `NYASH_AOT_MAP_KEY_MODE={h|i64|hh|auto}`(既定: `h`/`i64`)
|
- 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)`)
|
1. 型テーブル(`type_table.has(bvid)`)
|
||||||
2. PHI型推論(`peek_phi_type`)
|
2. PHI型推論(`peek_phi_type`)
|
||||||
3. **後方MIR解析(`resolve_recv_type_backward`)** - NEW!
|
3. 後方MIR解析(`resolve_recv_type_backward`)
|
||||||
4. メソッド専用判別(push は Array のみ)
|
4. **メソッド専用判別(push は Array のみ)** - 強化!
|
||||||
5. 直接マップ(`arr_recv`/`map_recv`)
|
5. 直接マップ(`arr_recv`/`map_recv`)
|
||||||
6. キー文脈ヒューリスティック(`is_stringy_key_in_block`)
|
6. **キー文脈ヒューリスティック(`is_stringy_key_in_block`)** - 強化!
|
||||||
7. newbox 逆スキャン
|
7. newbox 逆スキャン
|
||||||
- BinopCSE(`NYASH_MIR_LOOP_HOIST=1`)
|
- BinopCSE(`NYASH_MIR_LOOP_HOIST=1`)
|
||||||
- 同一 binop(加算/乗算を含む)を1回だけ発行し、`copy` で再利用することで線形インデックス(例: `i*n+k`)の再計算を抑制
|
- 同一 binop(加算/乗算を含む)を1回だけ発行し、`copy` で再利用することで線形インデックス(例: `i*n+k`)の再計算を抑制
|
||||||
@ -33,7 +40,8 @@ ENV トグル一覧
|
|||||||
- `NYASH_AOT_COLLECTIONS_HOT=1` … Array/Map hot-path 置換を有効化
|
- `NYASH_AOT_COLLECTIONS_HOT=1` … Array/Map hot-path 置換を有効化
|
||||||
- `NYASH_AOT_MAP_KEY_MODE` … `h`/`i64`(既定), `hh`, `auto`(将来拡張)
|
- `NYASH_AOT_MAP_KEY_MODE` … `h`/`i64`(既定), `hh`, `auto`(将来拡張)
|
||||||
- `auto` モードでは `_is_const_or_linear` で args を走査し、単純な i64 定数/線形表現と判定できる場合に `nyash.map.*_h` を選ぶ(それ以外は `*_hh`)。
|
- `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 で検出。ベンチはこのトグルをオンにした状態で回しています。
|
- `NYASH_VERIFY_RET_PURITY=1` … Return 直前の副作用を Fail-Fast で検出。ベンチはこのトグルをオンにした状態で回しています。
|
||||||
|
|
||||||
使い方(例)
|
使い方(例)
|
||||||
@ -47,3 +55,14 @@ tools/perf/microbench.sh --case arraymap --exe --runs 3
|
|||||||
注意
|
注意
|
||||||
- すべて opt‑in。CI/既定ユーザ挙動は従来どおりです。
|
- すべて opt‑in。CI/既定ユーザ挙動は従来どおりです。
|
||||||
- Return 純化ガード(`NYASH_VERIFY_RET_PURITY=1`)と併用してください。
|
- 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
|
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
|
// Static helpers replacing local fun
|
||||||
find_block_span(text, p) {
|
find_block_span(text, p) {
|
||||||
local key = "\"instructions\":["
|
local key = "\"instructions\":["
|
||||||
@ -268,8 +352,11 @@ static box AotPrepCollectionsHotBox {
|
|||||||
local oe = AotPrepHelpers._seek_object_end(text, os)
|
local oe = AotPrepHelpers._seek_object_end(text, os)
|
||||||
if oe < 0 || oe >= k { return 0 }
|
if oe < 0 || oe >= k { return 0 }
|
||||||
local inst = text.substring(os, oe+1)
|
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("\"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 inst.indexOf("\"op\":\"binop\"") >= 0 {
|
||||||
// If block contains a StringBox const before k, treat as stringy join
|
// If block contains a StringBox const before k, treat as stringy join
|
||||||
local head = text.substring(block_lb, k)
|
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
|
// Build lightweight SSA type table (arr/map) on out text for consistency with rewrite scan
|
||||||
local type_table = AotPrepCollectionsHotBox.build_type_table(out)
|
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
|
// Optional trace
|
||||||
local tr = env.get("NYASH_AOT_CH_TRACE")
|
local tr = env.get("NYASH_AOT_CH_TRACE")
|
||||||
|
|||||||
Reference in New Issue
Block a user