diff --git a/lang/src/llvm_ir/boxes/aot_prep/README.md b/lang/src/llvm_ir/boxes/aot_prep/README.md index 9fc47ba6..d9d2f49e 100644 --- a/lang/src/llvm_ir/boxes/aot_prep/README.md +++ b/lang/src/llvm_ir/boxes/aot_prep/README.md @@ -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]` ログで確認できます。 diff --git a/lang/src/llvm_ir/boxes/aot_prep/passes/collections_hot.hako b/lang/src/llvm_ir/boxes/aot_prep/passes/collections_hot.hako index 781a01b1..9eae349f 100644 --- a/lang/src/llvm_ir/boxes/aot_prep/passes/collections_hot.hako +++ b/lang/src/llvm_ir/boxes/aot_prep/passes/collections_hot.hako @@ -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")