diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index b2f7c117..dc090749 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -12,16 +12,17 @@ - **25.x**: Stage0/Stage1/Stage‑B / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。 - **25.1 系**: Stage‑B / Stage‑1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。 - **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成(LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBox)と MirScanExitLiveness の準備。 - - **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定。27.8〜27.11 で skip_ws/trim を Shared Builder Pattern+MIR-based lowering に移行し、27.12/27.13 で Stage‑1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の lowering+minimal .hako+auto_lowering テストまで整備済み。短期フェーズ残りは JoinIR→Rust VM ブリッジの最小実装と、それを使った skip_ws/trim/Stage‑1 minimal あたりの A/B テスト整備)。 + - **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal/StageB minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 済み。27.8〜27.11/27.13/27.14 で skip_ws/trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal/StageB minimal を Shared Builder Pattern+MIR-based lowering に移行し、ValueId 範囲管理も一元化。27.8-27.11/27.13/27.15 で JoinIR Runner を JoinValue↔VMValue 変換+`MirInterpreter::execute_box_call` ラッパ経由で Rust VM の BoxCall 意味論と統合し、skip_ws/trim の JoinIR Runner スタンドアロン実行が安定(GC/BoxRef も Arc ベースで統合済み)。Phase 27-shortterm の S‑1〜S‑5.4 は完了、Phase 28-midterm では per-loop lowering を増やさず、LoopForm+LoopVarClassBox+LoopExitLivenessBox を入力にした「汎用 Loop→JoinIR ロワー」に畳み込んでいく方針に切り替え済み(join-ir.md に JoinIR ロワーが“やらないこと”チェックリストを明記)。 - Rust 側: - LoopForm v2 + ControlForm + Conservative PHI は、代表テスト(Stage‑1 UsingResolver / Stage‑B 最小ループ)ではほぼ安定。 - 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。 - .hako 側: - Stage‑B コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。 - - JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。 + - JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。Stage‑B selfhost 経路は依然として Program JSON が取れていないが、直近の実行では `Unknown method 'main' on InstanceBox` は再現しておらず、別の箇所で落ちている可能性が高い。Stage‑1 CLI ブリッジ自体は Rust バイナリ経由で発火して `stage1_cli.hako` 実行までは到達しているが、その後 BuildBox.emit_program_json_v0 → `ParserBox.parse_program2` 実行中に VM 側の step budget 超過(max_steps 1_000_000→2_000_000 でも NG)とプラグイン未ロード警告で止まっており、Parser 側の本体ループが budget を食い尽くしている可能性が高い(詳細は stage1-cli-loop-analysis.md と private 側の parser-budget メモ参照)。Rust VM の budget エラーは fn/bb/last_inst + Span (あれば file:line:col) 付きで出るようにしたので、Span が入る MIR なら再発時の位置特定がしやすい(Stage‑1 CLI の現在の MIR には Span が無く行番号は未表示)。 - Stage‑B / FuncScanner ライン: - Phase 25.3 をクローズし、`stageb_fib_program_defs_canary_vm.sh` が緑(`defs` に `TestBox.fib/Main.main`、fib.body.body[*] に `Loop`)。 - Stage‑B は block パーサ優先 + defs を Block 包みで構造化。次手: Stage‑1 UsingResolver ループの Region+next_i 揃え / Stage‑1 CLI program-json selfhost 準備。 + - Stage‑1 CLI 実験: `NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako` でブリッジ発火・stage1_cli.hako 実行は確認できたが、VM が max_steps 2000000 を超過して中断(プラグイン未ロード警告あり)。budget 超過メッセージには `fn=ParserBox.parse_program2/1 ... (lang/src/runner/stage1_cli.hako:1:1)` まで出るようになった(Span 配線は通ったが位置精度はまだ粗い)。selfhost ビルド経路(tools/selfhost/build_stage1.sh)は StageBDriverBox.main 呼び出しで Unknown method 'main' on InstanceBox → Program JSON 未生成。 --- diff --git a/apps/tests/stageb_body_extract_minimal.hako b/apps/tests/stageb_body_extract_minimal.hako new file mode 100644 index 00000000..75834268 --- /dev/null +++ b/apps/tests/stageb_body_extract_minimal.hako @@ -0,0 +1,21 @@ +// stageb_body_extract_minimal.hako +// Phase 28: StageBBodyExtractorBox.build_body_src minimal loop for JoinIR testing +// - Case A: loop(i < n) with next_i and simple accumulator + +static box StageBBodyExtractorBox { + build_body_src(src, args) { + local n = src.length() + local i = 0 + local acc = 0 + loop(i < n) { + local next_i = i + 1 + acc = acc + 1 + i = next_i + } + return acc + } +} + +static box Main { + main() { return 0 } +} diff --git a/apps/tests/stageb_funcscanner_scan_boxes_minimal.hako b/apps/tests/stageb_funcscanner_scan_boxes_minimal.hako new file mode 100644 index 00000000..6aadbe46 --- /dev/null +++ b/apps/tests/stageb_funcscanner_scan_boxes_minimal.hako @@ -0,0 +1,19 @@ +// stageb_funcscanner_scan_boxes_minimal.hako +// Phase 28: StageBFuncScannerBox.scan_all_boxes minimal loop for JoinIR testing +// - Case A: loop(i < n) linear scan, defs is a placeholder accumulator + +static box StageBFuncScannerBox { + scan_all_boxes(src) { + local n = src.length() + local i = 0 + local defs = 0 + loop(i < n) { + i = i + 1 + } + return defs + } +} + +static box Main { + main() { return 0 } +} diff --git a/docs/development/architecture/join-ir.md b/docs/development/architecture/join-ir.md index 30676a25..cffa628f 100644 --- a/docs/development/architecture/join-ir.md +++ b/docs/development/architecture/join-ir.md @@ -14,6 +14,26 @@ - JoinIR は「PHI/SSA の実装負担を肩代わりする層」として導入し、ヘッダ/exit φ や BodyLocal の扱いを **関数の引数と継続** に吸収していく。 - PHI 専用の箱(HeaderPhiBuilder / ExitPhiBuilder / BodyLocalPhiBuilder など)は、最終的には JoinIR 降ろしの補助に縮退させることを目標にする。 +### JoinIR ロワーが「やらないこと」チェックリスト(暴走防止用) + +JoinIR への変換はあくまで「LoopForm で正規化された形」を前提にした **薄い汎用ロワー** に寄せる。 +このため、以下は **JoinIR ロワーでは絶対にやらない**こととして明示しておく。 + +- 条件式の **中身** を解析しない(`i < n` か `flag && x != 0` かを理解しない) + - 見るのは「header ブロックの succ が 2 本あって、LoopForm が body/exit を教えてくれるか」だけ。 +- 多重ヘッダ・ネストループを自力で扱おうとしない + - LoopForm 側で「単一 header / 単一 latch のループ」として正規化できないものは、JoinIR 対象外(フォールバック)。 +- LoopForm/VarClass で判定できない BodyLocal/ExitLiveness を、JoinIR 側で推測しない + - pinned/carrier/exit 値は LoopVarClassBox / LoopExitLivenessBox からだけ受け取り、独自解析はしない。 +- 各ループ用に「特別な lowering 分岐」を増やさない + - `skip_ws` / `trim` / `stageb_*` / `stage1_using_resolver` 向けの per-loop lowering は Phase 27.x の実験用足場であり、最終的には Case A/B/D 向けの汎用ロワーに吸収する。 + +> 要するに: JoinIR は「LoopForm+変数分類の結果」だけを入力にして、 +> ループ/if の **構造(続行 or exit の2択+持ち回り変数)** を関数呼び出しに写す箱だよ。 +> 構文パターンや条件式そのものを全網羅で理解しに行くのは、この層の責務から外す。 + +Phase 28 メモ: generic_case_a ロワーは LoopForm / LoopVarClassBox / LoopExitLivenessBox から実データを読み、minimal_skip_ws の JoinIR を組み立てるステップに進化中。Case A/B/D を汎用ロワーに畳み込む足場として扱う。 + 位置づけ - 変換パイプラインにおける位置: @@ -258,6 +278,115 @@ enum JoinInst { | 構文/概念 | JoinIR での表現 | φ/合流の扱い | |-----------|-----------------|--------------| | if/merge | join 関数呼び出し | join 関数の引数が φ 相当 | + +--- + +## 7. 汎用 LoopForm→JoinIR ロワー設計(Case A/B ベース案) + +Phase 28 以降は、`skip_ws` や `trim` のような「ループごとの lowering」を増やすのではなく、 +LoopForm v2 と変数分類箱を入力にした **汎用ロワー** で Case A/B 型ループをまとめて JoinIR に落とすのがゴールになる。 + +ここでは、その v1 として **単一ヘッダの Case A/B ループ** を対象にした設計をまとめておく。 + +### 7-1. 入力と前提条件 + +汎用ロワーが見るのは、次の 4つだけに限定する。 + +- `LoopForm` / `ControlForm`(構造) + - `preheader`, `header`, `body`, `latch`, `exit`, `continue_merge` の各ブロック ID + - `header` ブロックの succ がちょうど 2 本であること + - LoopForm が「どちらが body 側 / exit 側か」を与えてくれていること +- `LoopVarClassBox`(変数分類) + - pinned: ループ中で不変な変数集合 + - carriers: ループごとに更新される LoopCarried 変数集合 + - body-local: ループ内部だけで完結する変数集合(基本は JoinIR では引数にしない) +- `LoopExitLivenessBox`(Exit 後で必要な変数) + - exit ブロック以降で実際に参照される変数集合 `E` +- 条件式の形そのもの(`i < n` か `1 == 1` か等)は **見ない** + - 汎用ロワーが使うのは「header の succ が body/exit に分かれている」という事実だけ。 + +前提条件として、v1 では次のようなループだけを対象にする。 + +- LoopForm が「単一 header / 単一 latch の loop」として構築できていること。 +- header の succ が 2 本で、ControlForm が `(cond → {body, exit})` を一意に教えてくれること。 +- break/continue が LoopForm の設計どおり `exit` / `latch` に正規化されていること。 + +これを満たさないループ(多重ヘッダ・ネスト・例外的な jump 等)は **JoinIR 対象外(フォールバック)** とする。 + +### 7-2. 出力の形(ループごとの JoinFunction セット) + +Case A/B 型の単一ヘッダ loop について、汎用ロワーは原則として次の 2 関数を生成する。 + +- `loop_step(pinned..., carriers..., k_exit)` + - 引数: + - pinned: LoopVarClassBox の pinned 変数(ループ外で初期化され、ループ中不変) + - carriers: LoopVarClassBox の carriers(ループをまたいで値を持ち回る) + - `k_exit`: ループを抜けたあとの処理を表す継続 + - 本体: + - header 条件の判定(`header` ブロック) + - body/latch の処理(`body`/`latch` ブロックから拾った Compute/BoxCall 等) + - break/continue の分岐を、それぞれ `k_exit(exit_args...)` / `loop_step(next_carriers..., k_exit)` に変換したもの +- `k_exit` 相当の関数(もしくは呼び出し先関数の entry) + - ExitLiveness が教えてくれる `E`(exit 後で必要な変数)を引数とし、 + exit ブロック以降の処理をそのまま MIR→JoinIR で表現したもの。 + +これにより: + +- header φ: `LoopHeaderShape` / carriers 引数として `loop_step` に吸収される。 +- exit φ: `LoopExitShape` / exit 引数として `k_exit` に吸収される。 +- LoopCarried 変数は常に `loop_step` の引数経由で再帰されるので、PHI ノードは JoinIR 側では不要になる。 + +### 7-3. 汎用ロワー v1 のアルゴリズム(Case A/B) + +Case A/B を対象にした最初の汎用ロワーは、次のような流れになる。 + +1. **対象ループかどうかのチェック** + - LoopForm が単一 header / 単一 latch を持つことを確認。 + - header の succ が 2 本で、ControlForm が body/exit を特定していることを確認。 + - 条件: LoopForm の invariants を満たすループだけを対象にし、それ以外は `None` でフォールバック。 + +2. **変数セットの決定** + - pinned 集合 `P` と carriers 集合 `C` を LoopVarClassBox から取得。 + - ExitLiveness から exit 後で必要な変数集合 `E` を取得。 + - `LoopHeaderShape` と `LoopExitShape` を構築しておき、`loop_step` / `k_exit` の引数順を固定。 + +3. **`loop_step` 関数の生成** + - JoinFunction を新規に作成し、`params = P ∪ C`(+必要なら `k_exit`)とする。 + - header ブロックの Compare/BinOp から「続行 or exit」の判定命令を MirLikeInst として移植。 + - body/latch ブロックの Compute / BoxCall を順に MirLikeInst へ写し、carrier 更新を `C_next` として集約。 + - break: + - LoopForm/ControlForm が break 経路としてマークしたブロックからは、`Jump { cont: k_exit, args: exit_values }` を生成。 + - continue: + - latch への backedge 経路からは、`Call { func: loop_step, args: pinned..., carriers_next..., k_next: None/dst=None }` を生成。 + +4. **エントリ関数からの呼び出し** + - 元の MIR 関数の entry から、LoopForm が示す preheader までの処理を MirLikeInst として保持。 + - preheader の最後で `loop_step(pinned_init..., carriers_init..., k_exit)` 呼び出しを挿入。 + +5. **Exit 継続の構築** + - exit ブロック以降の MIR 命令を、`k_exit` 相当の JoinFunction か、呼び出し先関数の entry に写す。 + - `E` の各変数を引数として受け取り、そのまま下流の処理に流す。 + +### 7-4. 既存 per-loop lowering との関係 + +Phase 27.x で実装した以下の lowering は、この汎用ロワーの「見本」として扱う。 + +- `skip_ws` 系: minimal_ssa_skip_ws(Case B: `loop(1 == 1)` + break) +- `FuncScanner.trim_minimal`: Case D の簡易版(`loop(e > b)` + continue+break) +- `FuncScanner.append_defs_minimal`: Case A(配列走査) +- `Stage1UsingResolver minimal`: Case A/B の混合(Region+next_i 形) +- StageB minimal ループ: Case A の defs/body 抽出 + +Phase 28 では: + +- まず minimal_ssa_skip_ws だけを対象に、`generic_case_a` のような汎用ロワー v1 を実装し、 + 既存の手書き JoinIR と同じ構造が得られることを確認する(実装済み: `lower_case_a_loop_to_joinir_for_minimal_skip_ws`)。 +- そのあとで `trim_minimal` / `append_defs_minimal` / Stage‑1 minimal / StageB minimal に順に適用し、 + per-loop lowering を「汎用ロワーを呼ぶ薄いラッパー」に置き換えていく。 +- 最終的には per-loop lowering ファイルは削減され、LoopForm+変数分類箱から JoinIR へ落とす汎用ロワーが SSOT になることを目指す。 + +このセクションは「汎用ロワー設計のターゲット像」として置いておき、 +実装は Phase 28-midterm のタスク(generic lowering v1)で少しずつ進めていく。 | loop | step 再帰 + k_exit 継続 | LoopCarried/Exit の値を引数で渡す | | break | k_exit 呼び出し | φ 不要(引数で値を渡す) | | continue | step 呼び出し | φ 不要(引数で値を渡す) | diff --git a/docs/development/roadmap/phases/phase-25.1/README.md b/docs/development/roadmap/phases/phase-25.1/README.md index a3d4e9a1..b96996e5 100644 --- a/docs/development/roadmap/phases/phase-25.1/README.md +++ b/docs/development/roadmap/phases/phase-25.1/README.md @@ -2,6 +2,14 @@ Status: design+partial implementation(Stage1 ビルド導線の初期版まで) +## Stage‑1 CLI 実験メモ(2025-XX) + +- 2025-XX: `NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako` を実行。 +- ブリッジ自体は発火し、`stage1-cli/debug` ログと `Stage1CliMain.main/0` が生成されることを確認。 +- しかし VM 側で `vm step budget exceeded`(max_steps=2000000)で終了。プラグイン未ロード警告あり(FileBox/ArrayBox など)。 +- 対応メモ: ① NYASH_DISABLE_PLUGINS=1 + core-ro で FileBox は代替読込、② `HAKO_VM_MAX_STEPS` をさらに引き上げる or パーサ前処理を削る検討、③ Stage‑B 自前ビルドは `Unknown method 'main' on InstanceBox` で失敗中(StageBDriverBox 呼び出しが壊れている)。 +- Rust VM 側で `vm step budget exceeded` に **fn / bb / last_inst 情報** + Span(MIR に付いていれば .hako 行番号)を付与。今回の Stage‑1 CLI 実行では `fn=ParserBox.parse_program2/1 ... (lang/src/runner/stage1_cli.hako:1:1)` まで出力された(Span は通ったが行位置はまだ粗い)。 + ## 25.1 サブフェーズの整理(a〜e 概要) - **25.1a — Stage1 Build Hotfix(配線)** diff --git a/docs/development/roadmap/phases/phase-25.1/span-trace.md b/docs/development/roadmap/phases/phase-25.1/span-trace.md new file mode 100644 index 00000000..bfa68ccc --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1/span-trace.md @@ -0,0 +1,5 @@ +# Phase 25.1 — Span Trace Mini Note + +- 方針: MIR 命令に AST Span を持たせ、VMError (StepBudgetExceeded) で fn/bb/inst に加えて .hako 行番号を出す。 +- 実装: MirInstruction 生成時に current_span を保存し、VM 側で last_inst_idx から Span を引いてエラーに埋め込む。Span が無い場合は従来どおり fn/bb/inst のみ。 +- 状態: Stage‑1 CLI の MIR には Span 未付与なので行番号はまだ出ていないが、Span 付き MIR なら `... (file.hako:line:col)` まで表示できる。 diff --git a/docs/development/roadmap/phases/phase-25.1/stage1-cli-loop-analysis.md b/docs/development/roadmap/phases/phase-25.1/stage1-cli-loop-analysis.md new file mode 100644 index 00000000..b421f397 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1/stage1-cli-loop-analysis.md @@ -0,0 +1,97 @@ +# Phase 25.1 — Stage‑1 CLI / BuildBox ループ & Box 依存分析メモ + +目的 +- `Stage‑1 CLI → BuildBox.emit_program_json_v0` 実行時に VM が step budget を食い尽くしている原因を、 + ループ構造と BoxCall 依存の観点から整理しておくためのメモだよ。 + +## 1. 観測された症状 + +- コマンド例: + ```bash + NYASH_CLI_VERBOSE=2 \ + NYASH_USE_STAGE1_CLI=1 \ + STAGE1_EMIT_PROGRAM_JSON=1 \ + ./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako + ``` +- 状態: + - `[stage1-bridge/trace]` + `[stage1-cli/debug] emit_program_json ENTRY` が出ており、 + Stage1CliMain.main/0 〜 stage1_cli.hako 実行までは到達している。 + - その後 `BuildBox.emit_program_json_v0` 実行中に VM が `vm step budget exceeded`(max_steps=1_000_000→2_000_000 でも NG)。 + - FileBox/ArrayBox などの plugin 未ロード警告も併発。 + - 現状は **ステップ上限+プラグイン依存** が阻害要因となって、program-json 自体は得られていない。 + +## 2. emit_program_json_v0 周辺のループ構造(概観) + +調査時点でのループ構造のざっくり分類(ファイルは概略)だよ。 + +| ループ | ファイル | 複雑度 | 危険度 | +|------------------------------|--------------------------|-------------|--------| +| `ParserBox.parse_program2` | stage1_cli.hako:84 あたり | exponential | 🔴 最有力 | +| alias_table パース (opts) | build_box.hako:60-73 | O(n²) | 🟡 中 | +| alias_table パース (env) | build_box.hako:94-107 | O(n²) | 🟡 中 | +| `FileBox._read_file` | stage1_cli.hako:67 | I/O bound | ⚠️ 注視 | +| bundle_names 重複チェック | build_box.hako:41-51 | O(n²) | 🟢 許容 | + +直感: +- alias_table や bundle_names の O(n²) 系ループは、入力サイズが小さい間は budget を食い尽くすレベルではない。 +- `ParserBox.parse_program2` は Stage‑3 parser の statement/expression を全部受け持つため、 + 実装によっては **指数的** にブロック数・ステップ数が増え得る。 +- FileBox は I/O bound なので、budget ではなくタイムアウト側のリスクが大きい。 + +## 3. Box 依存度マトリックス + +Stage‑1 CLI / BuildBox ラインで使われている Box の洗い出しと、 +「core だけで足りるか/外部プラグインが要るか」のざっくり分類だよ。 + +| Box | 用途 | プラグイン依存 | 影響度 | +|-----------|-------------------------------|---------|-------------| +| FileBox | `_read_file` で source 読み込み | YES | 🔴 CRITICAL | +| ParserBox | `parse_program2` AST parse | NO | 🟡 MEDIUM | +| ArrayBox | bundles 配列格納 | NO | 🟢 LOW | +| StringBox | alias_table / bundle_names parse | NO | 🟢 LOW | + +メモ: +- FileBox は core-ro だけでは厳しく、実際には plugin / env 設定に依存するケースが多い。 +- ParserBox は Stage‑3 parser の仕様そのものなので、LoopForm/JoinIR と独立に「parser の loop 形」問題として見る必要がある。 + +## 4. 今後の観測テンプレ(提案) + +ステップ budget 溶けの原因をもう少し切り分けるために、次のようなコマンドで実験できるよ。 + +```bash +# Test 1: FileBox なしで inline source を直書き +NYASH_STAGE1_MODE=emit-program \ +STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \ +./target/release/nyash lang/src/runner/stage1_cli.hako + +# Test 2: プラグイン OFF で挙動を見る +NYASH_DISABLE_PLUGINS=1 \ +NYASH_STAGE1_MODE=emit-program \ +STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \ +./target/release/nyash lang/src/runner/stage1_cli.hako + +# Test 3: ステップトレースを有効化 +NYASH_VM_STATS=1 \ +NYASH_STAGE1_MODE=emit-program \ +STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \ +./target/release/nyash lang/src/runner/stage1_cli.hako 2>&1 | grep -i step +``` + +目的: +- FileBox を完全にバイパスできる inline source モードで試し、 + それでも budget が溶けるかを見る(ParserBox 起因かどうかの切り分け)。 +- プラグイン OFF/ON で挙動がどれだけ変わるか確認する。 + +## 5. 方針メモ + +- Stage‑1 CLI の「ブリッジまでは動いているが、そのあとで止まる」という現象は、 + - Stage‑B/BuildBox 経由の Program JSON emit + - ParserBox / FileBox のループ + のどこに問題があるかを 1 ループずつ切り出していけば、局所化できる見込み。 +- joinIR 側とは独立に、このファイルでは「Stage‑1 CLI 実行ルートのうち、どのループと Box が危ないか」の観測結果を積み上げていくよ。 + +## 6. VM エラー出力改善メモ(2025-XX) + +- Rust MIR Interpreter の step budget エラーに **fn / bb / last_inst / steps** + Span を付与したよ。 +- 例(Span 付き MIR の場合): `vm step budget exceeded (max_steps=2000000, steps=2000001) at bb=bb49 fn=ParserBox.parse_program2/1 last_inst_idx=12 last_inst_bb=bb48 last_inst=... (file.hako:312:7)` +- Stage‑1 CLI の現行 MIR には Span が載っていないため、いまは fn/bb/inst だけが出る(`apps/tests/minimal_ssa_skip_ws.hako` 実行で確認)。Span 付与を通せば .hako 行まで出る想定。 diff --git a/docs/development/roadmap/phases/phase-25.1/stageb-selfhost-main-resolution.md b/docs/development/roadmap/phases/phase-25.1/stageb-selfhost-main-resolution.md new file mode 100644 index 00000000..5dddf86f --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1/stageb-selfhost-main-resolution.md @@ -0,0 +1,75 @@ +# Phase 25.1 — Stage‑B Selfhost main 解決メモ(InstanceBox / Static Box 呼び出し) + +目的 +- `tools/selfhost/build_stage1.sh` 実行時に発生している + `Unknown method 'main' on InstanceBox` の根本原因を整理し、 + どの Box / メソッド名のズレかを明確にするための観測メモだよ。 + +## 1. 現状の観測結果 + +- エラー発生箇所: + - `src/backend/mir_interpreter/handlers/boxes.rs:105` 付近(InstanceBox 用のメソッドディスパッチ)。 +- 症状: + - VM が **InstanceBox** という generic 型に対して `"main"` を探している。 + - 期待されるのは `StageBDriverBox.main(...)` のような「ユーザー定義 static box 上の main」。 + - Rust の `type_name()` macro がユーザー定義 Box を generic 型名(InstanceBox)として返しているため、 + ログ上は常に `box_type_name=InstanceBox` となり、「どの box の main を探しているか」が見えづらい。 + +## 2. StageBDriverBox 側の定義 + +- 定義ファイル: + - `lang/src/compiler/entry/compiler_stageb.hako:1141-1367` +- 構造: + - `static box StageBDriverBox { main(args) { ... } }` という形で main が定義されている。 + - エントリポイント: + - `Main.main()` から `StageBDriverBox.main()` を呼ぶ導線が 1373-1377 行あたりに存在。 +- 直感的には: + - **`.hako` 側は正しく main を定義している**が、 + - VM 側の `InstanceBox` 呼び出し時に **メソッド名の解決 or Box 名の正規化** でズレている可能性が高い。 + +## 3. ログ追加の案(型名+メソッド名の観測) + +次のようなログを handlers/boxes.rs の Unknown method 分岐直前に入れておくと、 +どの Box / 関数名の組み合わせが原因かが見やすくなるよ。 + +```rust +eprintln!( + "[vm/method-dispatch] Unknown method on Box: box_type_name={}, method_name={}", + recv_box.type_name(), // InstanceBox 側の type_name + method, // 探しにいったメソッド名(例: \"main\" / \"StageBDriverBox.main/0\") +); +``` + +※ 実際には `type_name()` は常に `InstanceBox` を返すので、 + 実 box 名は `BoxDeclaration` / `InstanceBox` 内のフィールドから取り出す必要があるかもしれない。 + +## 4. 次に見ると良い箇所 + +### 4-1. MIR 側の BoxCall 受け側 + +- 目的: + - Stage‑B selfhost の MIR で、「どの global / boxcall 名で StageBDriverBox.main を呼んでいるか」を確認する。 +- 手順の目安: + - Stage‑B ランチャの MIR を `--dump-mir` 等でダンプ。 + - `call_global "StageBDriverBox.main/..."` のような形になっているかを見る。 + +### 4-2. MirBuilder の receiver / callee 解決 + +- ファイル候補: + - `src/mir/builder/calls/unified_emitter.rs` + - `src/backend/mir_interpreter/handlers/global.rs` +- 見たいこと: + - StaticMethodId / NamingBox を通った後に、 + `"StageBDriverBox.main/arity"` がどのように解決されているか。 + - InstanceBox に対するメソッド呼び出しのときに、 + メソッド名から Box 名が正しく分離されているか。 + +## 5. 方針メモ + +- joinIR ラインとは独立して、この問題は「Stage‑B driver / InstanceBox / naming」の層で解決する。 +- まずは: + - どの InstanceBox/関数名の組み合わせで Unknown が出ているかをログで観測。 + - `.hako` 側の StageBDriverBox 定義と、Rust 側のメソッド名解決ロジックの差分を見る。 +- 修正自体(命名・正規化の調整)は Phase 25.1 の別サブフェーズで扱う前提として、 + このファイルは「現状の観測結果と差分の候補」を残しておくためのメモだよ。 + diff --git a/lang/src/compiler/parser/parser_box.hako b/lang/src/compiler/parser/parser_box.hako index 6ff9351d..3637869b 100644 --- a/lang/src/compiler/parser/parser_box.hako +++ b/lang/src/compiler/parser/parser_box.hako @@ -351,6 +351,19 @@ box ParserBox { } loop(cont_prog == 1) { + // ✅ TRACE: main_prog loop progress (Task先生推奨トレースポイント) + local _loop_info = "" + if trace == 1 && (guard_prog < 5 || (guard_prog % 10) == 0) { + _loop_info = "[parse_program2:main] count=" + ("" + guard_prog) + + " i=" + ("" + i) + " n=" + ("" + n) + if i < n { + local _end = i + 40 + if _end > n { _end = n } + _loop_info = _loop_info + " ctx=\"" + src.substring(i, _end) + "\"" + } + print(_loop_info) + } + if max_prog > 0 { guard_prog = guard_prog + 1 if guard_prog > max_prog { diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index dc00c1a9..563820e2 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -71,10 +71,23 @@ impl MirInterpreter { steps += 1; // max_steps == 0 は「上限なし」を意味する(開発/診断専用)。 if max_steps > 0 && steps > max_steps { - return Err(VMError::InvalidInstruction(format!( - "vm step budget exceeded (max_steps={})", - max_steps - ))); + let last_inst_str = self.last_inst.as_ref().map(|inst| format!("{:?}", inst)); + let target_bb = self.last_block.unwrap_or(cur); + let span = func + .blocks + .get(&target_bb) + .and_then(|block| self.lookup_span_for_inst(block, self.last_inst_index)); + return Err(VMError::StepBudgetExceeded { + max_steps, + steps, + function: self.cur_fn.clone(), + current_block: cur, + last_block: self.last_block, + last_inst: last_inst_str, + last_inst_index: self.last_inst_index, + span, + source_file: func.metadata.source_file.clone(), + }); } let block = func .blocks @@ -128,6 +141,21 @@ impl MirInterpreter { } } + fn lookup_span_for_inst( + &self, + block: &BasicBlock, + inst_index: Option, + ) -> Option { + let idx = inst_index?; + if idx < block.instructions.len() { + block.instruction_span(idx) + } else if idx == block.instructions.len() { + block.terminator_span() + } else { + None + } + } + fn apply_phi_nodes( &mut self, block: &BasicBlock, @@ -140,7 +168,10 @@ impl MirInterpreter { block.id, last_pred, block.predecessors ); } - for inst in block.phi_instructions() { + for (idx, inst) in block.phi_instructions().enumerate() { + self.last_block = Some(block.id); + self.last_inst_index = Some(idx); + self.last_inst = Some(inst.clone()); if let MirInstruction::Phi { dst, inputs } = inst { let dst_id = *dst; if trace_phi { @@ -302,8 +333,10 @@ impl MirInterpreter { } fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> { - for inst in block.non_phi_instructions() { + let phi_count = block.phi_instructions().count(); + for (idx, inst) in block.non_phi_instructions().enumerate() { self.last_block = Some(block.id); + self.last_inst_index = Some(phi_count + idx); self.last_inst = Some(inst.clone()); if Self::trace_enabled() { eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst); @@ -319,6 +352,11 @@ impl MirInterpreter { } fn handle_terminator(&mut self, block: &BasicBlock) -> Result { + if let Some(term) = &block.terminator { + self.last_block = Some(block.id); + self.last_inst_index = Some(block.instructions.len()); + self.last_inst = Some(term.clone()); + } match &block.terminator { Some(MirInstruction::Return { value }) => { let result = if let Some(v) = value { diff --git a/src/backend/mir_interpreter/handlers/boxes.rs b/src/backend/mir_interpreter/handlers/boxes.rs index 266298aa..6ab746ea 100644 --- a/src/backend/mir_interpreter/handlers/boxes.rs +++ b/src/backend/mir_interpreter/handlers/boxes.rs @@ -102,6 +102,12 @@ impl MirInterpreter { self.write_string(dst, recv_box.to_string_box().value); Ok(()) } else { + // Phase 25.1 diagnostic: Log unknown method details before error + eprintln!( + "[vm/method-dispatch/plugin-invoke] Unknown method on Box: box_type_name={}, method_name={}", + recv_box.type_name(), + method + ); Err(self.err_method_not_found(&recv_box.type_name(), method)) } } diff --git a/src/backend/mir_interpreter/handlers/calls/global.rs b/src/backend/mir_interpreter/handlers/calls/global.rs index 3f7f0c3f..0d76bd3f 100644 --- a/src/backend/mir_interpreter/handlers/calls/global.rs +++ b/src/backend/mir_interpreter/handlers/calls/global.rs @@ -36,8 +36,10 @@ impl MirInterpreter { // Phase 2.2: Show parsed StaticMethodId info if let Some(id) = StaticMethodId::parse(func_name) { - eprintln!("[DEBUG/vm] Parsed: box='{}', method='{}', arity={:?}", - id.box_name, id.method, id.arity); + eprintln!( + "[DEBUG/vm] Parsed: box='{}', method='{}', arity={:?}", + id.box_name, id.method, id.arity + ); } else { eprintln!("[DEBUG/vm] Not a static method (builtin?)"); } @@ -53,7 +55,9 @@ impl MirInterpreter { } else { &canonical }; - let matching: Vec<_> = self.functions.keys() + let matching: Vec<_> = self + .functions + .keys() .filter(|k| k.starts_with(prefix)) .collect(); if !matching.is_empty() { @@ -204,7 +208,9 @@ impl MirInterpreter { &canonical }; - let similar: Vec<_> = self.functions.keys() + let similar: Vec<_> = self + .functions + .keys() .filter(|k| k.starts_with(prefix)) .take(5) .collect(); @@ -218,7 +224,8 @@ impl MirInterpreter { } } - err_msg.push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace"); + err_msg + .push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace"); // NamingBox SSOT: ここで canonical に失敗したら素直に Unknown とする。 // レガシーフォールバック(functions.get(func_name) 再探索)は Phase 25.x で廃止済み。 diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index 06ec0692..0946a1a1 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -33,6 +33,7 @@ pub struct MirInterpreter { // Trace context (dev-only; enabled with NYASH_VM_TRACE=1) pub(super) last_block: Option, pub(super) last_inst: Option, + pub(super) last_inst_index: Option, // Static box singleton instances (persistent across method calls) pub(super) static_boxes: HashMap, // Static box declarations (metadata for creating instances) @@ -56,6 +57,7 @@ impl MirInterpreter { cur_fn: None, last_block: None, last_inst: None, + last_inst_index: None, static_boxes: HashMap::new(), static_box_decls: HashMap::new(), inst_count: 0, diff --git a/src/backend/vm_types.rs b/src/backend/vm_types.rs index 02a7aab7..b6ea191b 100644 --- a/src/backend/vm_types.rs +++ b/src/backend/vm_types.rs @@ -5,8 +5,9 @@ * Kept separate to thin vm.rs and allow reuse across helpers. */ +use crate::ast::Span; use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox}; -use crate::mir::ConstValue; +use crate::mir::{BasicBlockId, ConstValue}; use std::sync::Arc; /// VM execution error @@ -18,6 +19,17 @@ pub enum VMError { DivisionByZero, StackUnderflow, TypeError(String), + StepBudgetExceeded { + max_steps: u64, + steps: u64, + function: Option, + current_block: BasicBlockId, + last_block: Option, + last_inst: Option, + last_inst_index: Option, + span: Option, + source_file: Option, + }, } impl std::fmt::Display for VMError { @@ -29,6 +41,48 @@ impl std::fmt::Display for VMError { VMError::DivisionByZero => write!(f, "Division by zero"), VMError::StackUnderflow => write!(f, "Stack underflow"), VMError::TypeError(msg) => write!(f, "Type error: {}", msg), + VMError::StepBudgetExceeded { + max_steps, + steps, + function, + current_block, + last_block, + last_inst, + last_inst_index, + span, + source_file, + } => { + write!( + f, + "vm step budget exceeded (max_steps={}, steps={}) at bb={}", + max_steps, steps, current_block + )?; + if let Some(fn_name) = function { + write!(f, " fn={}", fn_name)?; + } + if let Some(idx) = last_inst_index { + write!(f, " last_inst_idx={}", idx)?; + } + if let Some(bb) = last_block { + write!(f, " last_inst_bb={}", bb)?; + } + if let Some(inst) = last_inst { + write!(f, " last_inst={}", inst)?; + } + match (span, source_file) { + (Some(span), Some(file)) => { + write!(f, " ({}:{}:{})", file, span.line, span.column)?; + } + (Some(span), None) => { + write!(f, " (line {}, col {})", span.line, span.column)?; + } + (None, Some(file)) => { + write!(f, " ({})", file)?; + } + _ => {} + } + Ok(()) + } } } } diff --git a/src/grammar/generated.rs b/src/grammar/generated.rs index d94cb9c1..25bb6e51 100644 --- a/src/grammar/generated.rs +++ b/src/grammar/generated.rs @@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[ ]; pub fn lookup_keyword(word: &str) -> Option<&'static str> { for (k, t) in KEYWORDS { - if *k == word { return Some(*t); } + if *k == word { + return Some(*t); + } } None } pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ - "box", - "global", - "function", - "static", - "if", - "loop", - "break", - "return", - "print", - "nowait", - "include", - "local", - "outbox", - "try", - "throw", - "using", - "from", + "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", + "include", "local", "outbox", "try", "throw", "using", "from", ]; -pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ - "add", - "sub", - "mul", - "div", - "and", - "or", - "eq", - "ne", -]; \ No newline at end of file +pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"]; diff --git a/src/mir/basic_block.rs b/src/mir/basic_block.rs index 5f1ad29b..9146e047 100644 --- a/src/mir/basic_block.rs +++ b/src/mir/basic_block.rs @@ -4,7 +4,8 @@ * SSA-form basic blocks with phi functions and terminator instructions */ -use super::{EffectMask, MirInstruction, ValueId}; +use super::{EffectMask, MirInstruction, SpannedInstruction, ValueId}; +use crate::ast::Span; use std::collections::HashSet; use std::fmt; @@ -49,9 +50,15 @@ pub struct BasicBlock { /// Instructions in this block (excluding terminator) pub instructions: Vec, + /// Span per instruction (aligned with `instructions`) + pub instruction_spans: Vec, + /// Terminator instruction (branch, jump, or return) pub terminator: Option, + /// Span for the terminator instruction + pub terminator_span: Option, + /// Predecessors in the control flow graph pub predecessors: HashSet, @@ -74,7 +81,9 @@ impl BasicBlock { Self { id, instructions: Vec::new(), + instruction_spans: Vec::new(), terminator: None, + terminator_span: None, predecessors: HashSet::new(), successors: HashSet::new(), effects: EffectMask::PURE, @@ -83,8 +92,18 @@ impl BasicBlock { } } + /// Add a spanned instruction to this block + pub fn add_spanned_instruction(&mut self, sp: super::SpannedInstruction) { + self.add_instruction_with_span(sp.inst, sp.span); + } + /// Add an instruction to this block pub fn add_instruction(&mut self, instruction: MirInstruction) { + self.add_instruction_with_span(instruction, Span::unknown()); + } + + /// Add an instruction with explicit span to this block + pub fn add_instruction_with_span(&mut self, instruction: MirInstruction, span: Span) { // Update effect mask self.effects = self.effects | instruction.effects(); @@ -94,11 +113,13 @@ impl BasicBlock { panic!("Basic block {} already has a terminator", self.id); } self.terminator = Some(instruction); + self.terminator_span = Some(span); // Update successors based on terminator self.update_successors_from_terminator(); } else { self.instructions.push(instruction); + self.instruction_spans.push(span); } } @@ -111,6 +132,7 @@ impl BasicBlock { // Non-terminator instructions always go into instructions vec if !self.is_terminator(&instruction) { self.instructions.push(instruction); + self.instruction_spans.push(Span::unknown()); } else { panic!("Cannot add terminator via add_instruction_before_terminator"); } @@ -212,6 +234,16 @@ impl BasicBlock { .skip_while(|inst| matches!(inst, MirInstruction::Phi { .. })) } + /// Get span for instruction index (phi/non-phi) + pub fn instruction_span(&self, idx: usize) -> Option { + self.instruction_spans.get(idx).copied() + } + + /// Get span for terminator instruction + pub fn terminator_span(&self) -> Option { + self.terminator_span + } + /// Insert instruction at the beginning (after phi instructions) pub fn insert_instruction_after_phis(&mut self, instruction: MirInstruction) { let phi_count = self.phi_instructions().count(); @@ -223,6 +255,22 @@ impl BasicBlock { } self.effects = self.effects | instruction.effects(); self.instructions.insert(phi_count, instruction); + self.instruction_spans.insert(phi_count, Span::unknown()); + } + + /// Insert spanned instruction at the beginning (after phi instructions) + pub fn insert_spanned_after_phis(&mut self, sp: super::SpannedInstruction) { + let phi_count = self.phi_instructions().count(); + if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") { + if let MirInstruction::Copy { dst, src } = &sp.inst { + eprintln!( + "[insert-after-phis] bb={:?} phi_count={} inserting Copy dst=%{} src=%{} total_inst={}", + self.id, phi_count, dst.0, src.0, self.instructions.len()); + } + } + self.effects = self.effects | sp.inst.effects(); + self.instructions.insert(phi_count, sp.inst); + self.instruction_spans.insert(phi_count, sp.span); } /// Update PHI instruction input by destination ValueId @@ -254,9 +302,32 @@ impl BasicBlock { self.effects = self.effects | terminator.effects(); self.terminator = Some(terminator); + self.terminator_span = Some(Span::unknown()); self.update_successors_from_terminator(); } + /// Replace terminator with explicit span + pub fn set_terminator_with_span(&mut self, terminator: MirInstruction, span: Span) { + if !self.is_terminator(&terminator) { + panic!("Instruction is not a valid terminator: {:?}", terminator); + } + self.effects = self.effects | terminator.effects(); + self.terminator = Some(terminator); + self.terminator_span = Some(span); + self.update_successors_from_terminator(); + } + + /// Drain instructions into spanned form (keeps block empty and aligned). + pub fn drain_spanned_instructions(&mut self) -> Vec { + let insts = std::mem::take(&mut self.instructions); + let spans = std::mem::take(&mut self.instruction_spans); + insts + .into_iter() + .zip(spans.into_iter()) + .map(|(inst, span)| SpannedInstruction { inst, span }) + .collect() + } + /// Mark this block as reachable pub fn mark_reachable(&mut self) { self.reachable = true; diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 70c498f1..efa1702b 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -9,12 +9,12 @@ use super::{ BasicBlock, BasicBlockId, BasicBlockIdGenerator, CompareOp, ConstValue, Effect, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator, }; -use crate::ast::{ASTNode, LiteralValue}; +use crate::ast::{ASTNode, LiteralValue, Span}; use crate::mir::builder::builder_calls::CallTarget; use crate::mir::region::function_slot_registry::FunctionSlotRegistry; use crate::mir::region::RegionId; -use std::collections::{BTreeMap, HashMap}; use std::collections::HashSet; +use std::collections::{BTreeMap, HashMap}; mod builder_calls; mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities mod calls; // Call system modules (refactored from builder_calls) @@ -219,6 +219,12 @@ pub struct MirBuilder { /// Tracks the depth of build_expression calls to detect infinite loops pub(super) recursion_depth: usize, + /// Current AST span being lowered (used to annotate MIR instructions) + pub(super) current_span: Span, + + /// Optional source file hint for metadata/spans + pub(super) source_file: Option, + /// Root lowering mode: how to treat top-level Program /// - None: not decided yet (lower_root not called) /// - Some(true): App mode (static box Main.main is entry) @@ -243,7 +249,7 @@ impl MirBuilder { current_block: None, value_gen: ValueIdGenerator::new(), block_gen: BasicBlockIdGenerator::new(), - compilation_context: None, // 箱理論: デフォルトは従来モード + compilation_context: None, // 箱理論: デフォルトは従来モード variable_map: BTreeMap::new(), // Phase 25.1: 決定性確保 pending_phis: Vec::new(), value_origin_newbox: BTreeMap::new(), // Phase 25.1: 決定性確保 @@ -253,7 +259,7 @@ impl MirBuilder { field_origin_class: HashMap::new(), field_origin_by_box: HashMap::new(), value_types: BTreeMap::new(), // Phase 25.1: 決定性確保 - value_kinds: HashMap::new(), // Phase 26-A: ValueId型安全化 + value_kinds: HashMap::new(), // Phase 26-A: ValueId型安全化 current_slot_registry: None, type_registry: type_registry::TypeRegistry::new(), plugin_method_sigs, @@ -290,6 +296,8 @@ impl MirBuilder { in_unified_boxcall_fallback: false, recursion_depth: 0, + current_span: Span::unknown(), + source_file: None, root_is_app_mode: None, static_box_singletons: HashMap::new(), // Phase 21.7: methodization support } @@ -347,6 +355,34 @@ impl MirBuilder { self.debug_scope_stack.last().cloned() } + /// Hint for downstream metadata: set the logical source file name/path for the next build. + pub fn set_source_file_hint>(&mut self, source: S) { + self.source_file = Some(source.into()); + } + + /// Clear the source file hint (used when reusing the builder across modules). + pub fn clear_source_file_hint(&mut self) { + self.source_file = None; + } + + /// Resolve current source file hint (builder field or env fallback). + fn current_source_file(&self) -> Option { + self.source_file + .clone() + .or_else(|| std::env::var("NYASH_SOURCE_FILE_HINT").ok()) + } + + /// Create a new MirFunction with source metadata applied. + fn new_function_with_metadata( + &self, + signature: FunctionSignature, + entry_block: BasicBlockId, + ) -> MirFunction { + let mut f = MirFunction::new(signature, entry_block); + f.metadata.source_file = self.current_source_file(); + f + } + // ---------------------- // Compile trace helpers (dev only; env-gated) // ---------------------- @@ -739,7 +775,7 @@ impl MirBuilder { } ); } - block.add_instruction(instruction); + block.add_instruction_with_span(instruction, self.current_span); // Drop the mutable borrow of `block` before updating other blocks } // Update predecessor sets for branch/jump immediately so that diff --git a/src/mir/builder/calls/guard.rs b/src/mir/builder/calls/guard.rs index 24f913c8..26a01da8 100644 --- a/src/mir/builder/calls/guard.rs +++ b/src/mir/builder/calls/guard.rs @@ -105,8 +105,15 @@ impl<'a> CalleeGuardBox<'a> { // length, substring, charAt, indexOf, etc. let is_string_method = matches!( method.as_str(), - "length" | "substring" | "charAt" | "indexOf" | "lastIndexOf" - | "toUpperCase" | "toLowerCase" | "trim" | "split" + "length" + | "substring" + | "charAt" + | "indexOf" + | "lastIndexOf" + | "toUpperCase" + | "toLowerCase" + | "trim" + | "split" ); if is_string_method { @@ -130,7 +137,9 @@ impl<'a> CalleeGuardBox<'a> { } else { // Non-string methods: fallback to Global call if trace_enabled { - eprintln!("[static-runtime-guard] StaticCompiler receiver has NO Box type:"); + eprintln!( + "[static-runtime-guard] StaticCompiler receiver has NO Box type:" + ); eprintln!( " {}.{} receiver %{} has no type info", box_name, method, recv.0 diff --git a/src/mir/builder/calls/lowering.rs b/src/mir/builder/calls/lowering.rs index f8a210b7..9447b75d 100644 --- a/src/mir/builder/calls/lowering.rs +++ b/src/mir/builder/calls/lowering.rs @@ -77,7 +77,7 @@ impl MirBuilder { let signature = function_lowering::prepare_static_method_signature(func_name.clone(), params, body); let entry = self.block_gen.next(); - let function = super::super::MirFunction::new(signature, entry); + let function = self.new_function_with_metadata(signature, entry); // 現在の関数・ブロックを保存 ctx.saved_function = self.current_function.take(); @@ -259,7 +259,7 @@ impl MirBuilder { let signature = function_lowering::prepare_method_signature(func_name, box_name, params, body); let entry = self.block_gen.next(); - let function = super::super::MirFunction::new(signature, entry); + let function = self.new_function_with_metadata(signature, entry); // 現在の関数・ブロックを保存 ctx.saved_function = self.current_function.take(); diff --git a/src/mir/builder/calls/unified_emitter.rs b/src/mir/builder/calls/unified_emitter.rs index 0973174e..4c0cf1ed 100644 --- a/src/mir/builder/calls/unified_emitter.rs +++ b/src/mir/builder/calls/unified_emitter.rs @@ -175,10 +175,7 @@ impl UnifiedCallEmitterBox { // 🎯 Phase 21.7: Methodization (HAKO_MIR_BUILDER_METHODIZE=1) // Convert Global("BoxName.method/arity") → Method{receiver=static singleton} - let methodize_on = match std::env::var("HAKO_MIR_BUILDER_METHODIZE") - .ok() - .as_deref() - { + let methodize_on = match std::env::var("HAKO_MIR_BUILDER_METHODIZE").ok().as_deref() { // 明示的に "0" が指定されたときだけ無効化。 Some("0") => false, _ => true, @@ -186,7 +183,7 @@ impl UnifiedCallEmitterBox { if methodize_on { if let Callee::Global(ref name) = callee { let name_clone = name.clone(); // Clone to avoid borrow checker issues - // 🎯 Phase 21.7++ Phase 3: StaticMethodId SSOT 実装 + // 🎯 Phase 21.7++ Phase 3: StaticMethodId SSOT 実装 if let Some(id) = crate::mir::naming::StaticMethodId::parse(&name_clone) { // Check if arity matches provided args (arity may be None if not specified) let arity_matches = id.arity.map_or(true, |a| a == args.len()); @@ -194,26 +191,31 @@ impl UnifiedCallEmitterBox { let box_name = &id.box_name; let method = &id.method; // Get or create static box singleton instance - let singleton = if let Some(&existing) = builder.static_box_singletons.get(box_name) { - existing - } else { - // Create new singleton instance - let singleton_id = builder.next_value_id(); - builder.emit_instruction(MirInstruction::NewBox { - dst: singleton_id, - box_type: box_name.to_string(), - args: Vec::new(), // Static box singleton, no constructor args - })?; - // Register type information - builder.value_types.insert( - singleton_id, - crate::mir::MirType::Box(box_name.to_string()), - ); - builder.value_origin_newbox.insert(singleton_id, box_name.to_string()); - // Cache for future use - builder.static_box_singletons.insert(box_name.to_string(), singleton_id); - singleton_id - }; + let singleton = + if let Some(&existing) = builder.static_box_singletons.get(box_name) { + existing + } else { + // Create new singleton instance + let singleton_id = builder.next_value_id(); + builder.emit_instruction(MirInstruction::NewBox { + dst: singleton_id, + box_type: box_name.to_string(), + args: Vec::new(), // Static box singleton, no constructor args + })?; + // Register type information + builder.value_types.insert( + singleton_id, + crate::mir::MirType::Box(box_name.to_string()), + ); + builder + .value_origin_newbox + .insert(singleton_id, box_name.to_string()); + // Cache for future use + builder + .static_box_singletons + .insert(box_name.to_string(), singleton_id); + singleton_id + }; // Convert to Method call callee = Callee::Method { @@ -221,7 +223,8 @@ impl UnifiedCallEmitterBox { method: method.to_string(), receiver: Some(singleton), certainty: crate::mir::definitions::call_unified::TypeCertainty::Known, - box_kind: crate::mir::definitions::call_unified::CalleeBoxKind::StaticCompiler, + box_kind: + crate::mir::definitions::call_unified::CalleeBoxKind::StaticCompiler, }; if std::env::var("NYASH_METHODIZE_TRACE").ok().as_deref() == Some("1") { diff --git a/src/mir/builder/decls.rs b/src/mir/builder/decls.rs index 45c6ed1b..3996c523 100644 --- a/src/mir/builder/decls.rs +++ b/src/mir/builder/decls.rs @@ -20,11 +20,8 @@ impl super::MirBuilder { } if let ASTNode::FunctionDeclaration { params, body, .. } = mast { // NamingBox 経由で static メソッド名を一元管理する - let func_name = crate::mir::naming::encode_static_method( - &box_name, - mname, - params.len(), - ); + let func_name = + crate::mir::naming::encode_static_method(&box_name, mname, params.len()); self.lower_static_method_as_function(func_name, params.clone(), body.clone())?; } } @@ -43,7 +40,8 @@ impl super::MirBuilder { == Some("1") { // NamingBox SSOT: Use encode_static_method for main/arity entry - let func_name = crate::mir::naming::encode_static_method(&box_name, "main", params.len()); + let func_name = + crate::mir::naming::encode_static_method(&box_name, "main", params.len()); eprintln!( "[DEBUG] build_static_main_box: Before lower_static_method_as_function" ); diff --git a/src/mir/builder/emission/constant.rs b/src/mir/builder/emission/constant.rs index 5727ffd2..0dd654a4 100644 --- a/src/mir/builder/emission/constant.rs +++ b/src/mir/builder/emission/constant.rs @@ -45,7 +45,8 @@ pub fn emit_string>(b: &mut MirBuilder, s: S) -> ValueId { }); // 🎯 Phase 3-A: String constant type annotation // Ensures string constants have proper Box type for method resolution - b.value_types.insert(dst, crate::mir::MirType::Box("StringBox".to_string())); + b.value_types + .insert(dst, crate::mir::MirType::Box("StringBox".to_string())); b.value_origin_newbox.insert(dst, "StringBox".to_string()); dst } diff --git a/src/mir/builder/exprs.rs b/src/mir/builder/exprs.rs index 8586a8c8..847ebe59 100644 --- a/src/mir/builder/exprs.rs +++ b/src/mir/builder/exprs.rs @@ -8,6 +8,8 @@ use crate::mir::builder::observe::types as type_trace; impl super::MirBuilder { // Main expression dispatcher pub(super) fn build_expression_impl(&mut self, ast: ASTNode) -> Result { + // Track current source span for downstream instruction emission + self.current_span = ast.span(); if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { if matches!(ast, ASTNode::Loop { .. }) { eprintln!("[build_expression_impl] === ENTRY === processing Loop node"); @@ -296,7 +298,8 @@ impl super::MirBuilder { args: vec![], effects: super::EffectMask::MUT, })?; - self.value_origin_newbox.insert(arr_id, "ArrayBox".to_string()); + self.value_origin_newbox + .insert(arr_id, "ArrayBox".to_string()); self.value_types .insert(arr_id, super::MirType::Box("ArrayBox".to_string())); // TypeRegistry + trace for deterministic debug @@ -339,7 +342,8 @@ impl super::MirBuilder { args: vec![], effects: super::EffectMask::MUT, })?; - self.value_origin_newbox.insert(map_id, "MapBox".to_string()); + self.value_origin_newbox + .insert(map_id, "MapBox".to_string()); self.value_types .insert(map_id, super::MirType::Box("MapBox".to_string())); self.type_registry diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 095ad2b6..ae2b99a1 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -1,6 +1,5 @@ use super::{ - BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, - ValueId, + BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirModule, MirType, ValueId, }; use crate::ast::ASTNode; @@ -63,7 +62,8 @@ impl super::MirBuilder { } } pub(super) fn prepare_module(&mut self) -> Result<(), String> { - let module = MirModule::new("main".to_string()); + let mut module = MirModule::new("main".to_string()); + module.metadata.source_file = self.current_source_file(); let main_signature = FunctionSignature { name: "main".to_string(), params: vec![], @@ -72,7 +72,7 @@ impl super::MirBuilder { }; let entry_block = self.block_gen.next(); - let mut main_function = MirFunction::new(main_signature, entry_block); + let mut main_function = self.new_function_with_metadata(main_signature, entry_block); main_function.metadata.is_entry_point = true; self.current_module = Some(module); @@ -391,7 +391,7 @@ impl super::MirBuilder { effects: EffectMask::PURE, }; let entry = BasicBlockId::new(0); - let mut f = MirFunction::new(sig, entry); + let mut f = self.new_function_with_metadata(sig, entry); // body: const 1; return it(FunctionEmissionBox を使用) let one = crate::mir::function_emission::emit_const_integer(&mut f, entry, 1); crate::mir::function_emission::emit_return_value(&mut f, entry, one); diff --git a/src/mir/builder/metadata/propagate.rs b/src/mir/builder/metadata/propagate.rs index e38cb2cf..c4c9cecb 100644 --- a/src/mir/builder/metadata/propagate.rs +++ b/src/mir/builder/metadata/propagate.rs @@ -4,8 +4,8 @@ //! 🎯 箱理論: TypeRegistryBox 統合対応 //! NYASH_USE_TYPE_REGISTRY=1 で TypeRegistry 経由に切り替え(段階的移行) -use crate::mir::builder::MirBuilder; use crate::mir::builder::observe::types as type_trace; +use crate::mir::builder::MirBuilder; use crate::mir::{MirType, ValueId}; /// src から dst へ builder 内メタデータ(value_types / value_origin_newbox)を伝播する。 diff --git a/src/mir/builder/method_call_handlers.rs b/src/mir/builder/method_call_handlers.rs index 5865edd9..2dd27a10 100644 --- a/src/mir/builder/method_call_handlers.rs +++ b/src/mir/builder/method_call_handlers.rs @@ -41,8 +41,8 @@ impl MeCallPolicyBox { // Instance methods: params[0] is Box(box_name) // Static methods: params[0] is non-Box or params.is_empty() let params = &func.signature.params; - let is_instance_method = !params.is_empty() - && matches!(params[0], MirType::Box(_)); + let is_instance_method = + !params.is_empty() && matches!(params[0], MirType::Box(_)); // Expected argument count from signature (including receiver for instance) let expected_params = params.len(); @@ -53,18 +53,14 @@ impl MeCallPolicyBox { let call_args: Vec = if is_instance_method { // Instance method: prepend 'me' receiver if expected_params != provided_instance { - if std::env::var("NYASH_ME_CALL_ARITY_STRICT") - .ok() - .as_deref() + if std::env::var("NYASH_ME_CALL_ARITY_STRICT").ok().as_deref() == Some("1") { return Err(format!( "[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)", fname, expected_params, provided_instance )); - } else if std::env::var("NYASH_STATIC_CALL_TRACE") - .ok() - .as_deref() + } else if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") { eprintln!( @@ -81,18 +77,14 @@ impl MeCallPolicyBox { } else { // Static method: no receiver if expected_params != provided_static { - if std::env::var("NYASH_ME_CALL_ARITY_STRICT") - .ok() - .as_deref() + if std::env::var("NYASH_ME_CALL_ARITY_STRICT").ok().as_deref() == Some("1") { return Err(format!( "[me-call] arity mismatch (static): {}: declared {} params, got {} args", fname, expected_params, provided_static )); - } else if std::env::var("NYASH_STATIC_CALL_TRACE") - .ok() - .as_deref() + } else if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") { eprintln!( diff --git a/src/mir/builder/observe/types.rs b/src/mir/builder/observe/types.rs index 337f970a..dbd15553 100644 --- a/src/mir/builder/observe/types.rs +++ b/src/mir/builder/observe/types.rs @@ -29,9 +29,6 @@ pub fn ty(event: &str, vid: ValueId, ty: &MirType) { /// Trace propagation between ValueIds. pub fn propagate(event: &str, src: ValueId, dst: ValueId) { if enabled() { - eprintln!( - "[type-trace] propagate:{} %{} → %{}", - event, src.0, dst.0 - ); + eprintln!("[type-trace] propagate:{} %{} → %{}", event, src.0, dst.0); } } diff --git a/src/mir/builder/ops.rs b/src/mir/builder/ops.rs index 942d8f2e..f4b1a2f6 100644 --- a/src/mir/builder/ops.rs +++ b/src/mir/builder/ops.rs @@ -71,16 +71,26 @@ impl super::MirBuilder { let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false), + _ => self + .value_origin_newbox + .get(&lhs) + .map(|s| s == "StringBox") + .unwrap_or(false), }; let rhs_is_str = match self.value_types.get(&rhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false), + _ => self + .value_origin_newbox + .get(&rhs) + .map(|s| s == "StringBox") + .unwrap_or(false), }; if lhs_is_str || rhs_is_str { - self.value_types.insert(dst, MirType::Box("StringBox".to_string())); - self.value_origin_newbox.insert(dst, "StringBox".to_string()); + self.value_types + .insert(dst, MirType::Box("StringBox".to_string())); + self.value_origin_newbox + .insert(dst, "StringBox".to_string()); } else { self.value_types.insert(dst, MirType::Integer); } @@ -147,16 +157,26 @@ impl super::MirBuilder { let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false), + _ => self + .value_origin_newbox + .get(&lhs) + .map(|s| s == "StringBox") + .unwrap_or(false), }; let rhs_is_str = match self.value_types.get(&rhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false), + _ => self + .value_origin_newbox + .get(&rhs) + .map(|s| s == "StringBox") + .unwrap_or(false), }; if lhs_is_str || rhs_is_str { - self.value_types.insert(dst, MirType::Box("StringBox".to_string())); - self.value_origin_newbox.insert(dst, "StringBox".to_string()); + self.value_types + .insert(dst, MirType::Box("StringBox".to_string())); + self.value_origin_newbox + .insert(dst, "StringBox".to_string()); } else { self.value_types.insert(dst, MirType::Integer); } @@ -180,16 +200,26 @@ impl super::MirBuilder { let lhs_is_str = match self.value_types.get(&lhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false), + _ => self + .value_origin_newbox + .get(&lhs) + .map(|s| s == "StringBox") + .unwrap_or(false), }; let rhs_is_str = match self.value_types.get(&rhs) { Some(MirType::String) => true, Some(MirType::Box(bt)) if bt == "StringBox" => true, - _ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false), + _ => self + .value_origin_newbox + .get(&rhs) + .map(|s| s == "StringBox") + .unwrap_or(false), }; if lhs_is_str || rhs_is_str { - self.value_types.insert(dst, MirType::Box("StringBox".to_string())); - self.value_origin_newbox.insert(dst, "StringBox".to_string()); + self.value_types + .insert(dst, MirType::Box("StringBox".to_string())); + self.value_origin_newbox + .insert(dst, "StringBox".to_string()); } else { self.value_types.insert(dst, MirType::Integer); } diff --git a/src/mir/builder/phi.rs b/src/mir/builder/phi.rs index 4a818641..313754cb 100644 --- a/src/mir/builder/phi.rs +++ b/src/mir/builder/phi.rs @@ -16,7 +16,7 @@ impl MirBuilder { then_exit_block_opt: Option, else_exit_block_opt: Option, pre_if_snapshot: &BTreeMap, // Phase 25.1: BTreeMap化 - then_map_end: &BTreeMap, // Phase 25.1: BTreeMap化 + then_map_end: &BTreeMap, // Phase 25.1: BTreeMap化 else_map_end_opt: &Option>, // Phase 25.1: BTreeMap化 skip_var: Option<&str>, ) -> Result<(), String> { diff --git a/src/mir/builder/phi_merge.rs b/src/mir/builder/phi_merge.rs index 6877bf77..61f7fc57 100644 --- a/src/mir/builder/phi_merge.rs +++ b/src/mir/builder/phi_merge.rs @@ -159,7 +159,7 @@ impl<'a> PhiMergeHelper<'a> { pub fn merge_all_vars( &mut self, pre_if_snapshot: &BTreeMap, // Phase 25.1: BTreeMap化 - then_map_end: &BTreeMap, // Phase 25.1: BTreeMap化 + then_map_end: &BTreeMap, // Phase 25.1: BTreeMap化 else_map_end_opt: &Option>, // Phase 25.1: BTreeMap化 skip_var: Option<&str>, ) -> Result<(), String> { diff --git a/src/mir/builder/ssa/local.rs b/src/mir/builder/ssa/local.rs index 32ac533b..68787995 100644 --- a/src/mir/builder/ssa/local.rs +++ b/src/mir/builder/ssa/local.rs @@ -121,7 +121,9 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId // CRITICAL FIX: For receiver kind, if type is missing but origin exists, // infer MirType::Box from origin if kind == LocalKind::Recv && builder.value_types.get(&loc).is_none() { - builder.value_types.insert(loc, crate::mir::MirType::Box(cls)); + builder + .value_types + .insert(loc, crate::mir::MirType::Box(cls)); } } builder.local_ssa_map.insert(key, loc); diff --git a/src/mir/builder/utils.rs b/src/mir/builder/utils.rs index 37e67944..d711ab89 100644 --- a/src/mir/builder/utils.rs +++ b/src/mir/builder/utils.rs @@ -1,5 +1,5 @@ use super::{BasicBlock, BasicBlockId}; -use crate::mir::{BarrierOp, TypeOpKind, WeakRefOp}; +use crate::mir::{BarrierOp, SpannedInstruction, TypeOpKind, WeakRefOp}; use std::sync::atomic::{AtomicUsize, Ordering}; // include path resolver removed (using handles modules) @@ -405,7 +405,10 @@ impl super::MirBuilder { bb, dst.0, src.0, block.phi_instructions().count()); } // Propagate effects on the block - block.insert_instruction_after_phis(super::MirInstruction::Copy { dst, src }); + block.insert_spanned_after_phis(SpannedInstruction { + inst: super::MirInstruction::Copy { dst, src }, + span: self.current_span, + }); // Lightweight metadata propagation (unified) crate::mir::builder::metadata::propagate::propagate(self, src, dst); return Ok(()); diff --git a/src/mir/join_ir/lowering/common.rs b/src/mir/join_ir/lowering/common.rs index 4697cc82..0ded46e0 100644 --- a/src/mir/join_ir/lowering/common.rs +++ b/src/mir/join_ir/lowering/common.rs @@ -2,8 +2,10 @@ //! //! CFG sanity checks and dispatcher helpers for MIR-based lowering. -use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirInstruction}; +pub mod case_a; + use crate::mir::query::{MirQuery, MirQueryBox}; +use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirInstruction}; /// Check if entry block has at least one successor /// @@ -191,7 +193,10 @@ where use crate::mir::join_ir::env_flag_is_1; if env_flag_is_1("NYASH_JOINIR_LOWER_FROM_MIR") { - eprintln!("[joinir/{}] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)", tag); + eprintln!( + "[joinir/{}] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)", + tag + ); mir_based(module) } else { eprintln!("[joinir/{}] Using handwritten lowering (default)", tag); diff --git a/src/mir/join_ir/lowering/common/case_a.rs b/src/mir/join_ir/lowering/common/case_a.rs new file mode 100644 index 00000000..1d7c8600 --- /dev/null +++ b/src/mir/join_ir/lowering/common/case_a.rs @@ -0,0 +1,20 @@ +//! Common helpers for simple Case A loop detection (single header/body/exit with minimal branches). +//! +//! 目的: +//! - minimal 系ロワーで個別に持っていた「Case A っぽいか?」チェックを一箇所に集約する。 +//! - generic_case_a を噛ませる候補判定を共通化し、責務を明示する。 + +use crate::mir::loop_form::LoopForm; + +/// Detects a minimal single-header Case A loop shape. +/// +/// 条件: +/// - header と exit が異なる +/// - latch が body か header のどちらか +/// - continue/break の出発点がそれぞれ高々1 +pub fn is_simple_case_a_loop(loop_form: &LoopForm) -> bool { + loop_form.header != loop_form.exit + && (loop_form.latch == loop_form.body || loop_form.latch == loop_form.header) + && loop_form.continue_targets.len() <= 1 + && loop_form.break_targets.len() <= 1 +} diff --git a/src/mir/join_ir/lowering/exit_args_resolver.rs b/src/mir/join_ir/lowering/exit_args_resolver.rs new file mode 100644 index 00000000..2d6ecadb --- /dev/null +++ b/src/mir/join_ir/lowering/exit_args_resolver.rs @@ -0,0 +1,35 @@ +//! Exit argument resolver for JoinIR loop lowering. +//! +//! 役割: +//! - ExitLiveness から得た live 集合と、名前→ValueId マップを突き合わせて exit_args を決定する。 +//! - live 集合に未解決名があれば None で返し、フォールバックを促す。 + +use std::collections::BTreeSet; + +use crate::mir::ValueId; + +/// live 集合と名前マップから exit_args を決定する。 +pub(crate) fn resolve_exit_args( + live_names: &BTreeSet, + name_to_id: &std::collections::BTreeMap, + fallback_carriers: &[String], +) -> Option> { + let mut args = Vec::new(); + for name in live_names { + if let Some(v) = name_to_id.get(name) { + args.push(*v); + } else { + return None; + } + } + + if args.is_empty() { + for name in fallback_carriers { + if let Some(v) = name_to_id.get(name) { + args.push(*v); + } + } + } + + Some(args) +} diff --git a/src/mir/join_ir/lowering/funcscanner_append_defs.rs b/src/mir/join_ir/lowering/funcscanner_append_defs.rs index 25c19e18..512c0d70 100644 --- a/src/mir/join_ir/lowering/funcscanner_append_defs.rs +++ b/src/mir/join_ir/lowering/funcscanner_append_defs.rs @@ -43,11 +43,12 @@ //! ``` use crate::mir::join_ir::lowering::common::{ - dispatch_lowering, ensure_entry_has_succs, has_const_int, - has_array_method, has_loop_increment, log_fallback + dispatch_lowering, ensure_entry_has_succs, has_array_method, has_const_int, has_loop_increment, + log_fallback, }; use crate::mir::join_ir::lowering::value_id_ranges::funcscanner_append_defs as vid; -use crate::mir::join_ir::{JoinModule}; +use crate::mir::join_ir::JoinModule; +use crate::mir::loop_form::LoopForm; use crate::mir::query::{MirQuery, MirQueryBox}; /// Phase 27.14: FuncScannerBox._append_defs の JoinIR lowering(public dispatcher) @@ -61,7 +62,9 @@ use crate::mir::query::{MirQuery, MirQueryBox}; /// /// ## Shared Builder Pattern /// 両方の実装が `build_funcscanner_append_defs_joinir()` を呼び出す共通パターン。 -pub fn lower_funcscanner_append_defs_to_joinir(module: &crate::mir::MirModule) -> Option { +pub fn lower_funcscanner_append_defs_to_joinir( + module: &crate::mir::MirModule, +) -> Option { dispatch_lowering( "funcscanner_append_defs", module, @@ -99,9 +102,9 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio // loop_step(dst, defs_box, n, i_init) // } let entry_id = JoinFuncId::new(0); - let dst_param = vid::entry(0); // 9000 - let defs_box_param = vid::entry(1); // 9001 - let n_param = vid::entry(2); // 9002 + let dst_param = vid::entry(0); // 9000 + let defs_box_param = vid::entry(1); // 9001 + let n_param = vid::entry(2); // 9002 let mut entry_func = JoinFunction::new( entry_id, @@ -109,7 +112,7 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio vec![dst_param, defs_box_param, n_param], ); - let i_init = vid::entry(10); // 9010 + let i_init = vid::entry(10); // 9010 // i_init = 0 entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { @@ -134,14 +137,14 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio // - Pinned: dst (ArrayBox), defs_box (ArrayBox), n (Integer) // - Carrier: i (Integer) // - Exit: none (void return) - let dst_loop = vid::loop_step(0); // 10000 - Pinned + let dst_loop = vid::loop_step(0); // 10000 - Pinned let defs_box_loop = vid::loop_step(1); // 10001 - Pinned - let n_loop = vid::loop_step(2); // 10002 - Pinned - let i_loop = vid::loop_step(3); // 10003 - Carrier + let n_loop = vid::loop_step(2); // 10002 - Pinned + let i_loop = vid::loop_step(3); // 10003 - Carrier let _header_shape = LoopHeaderShape::new_manual( - vec![dst_loop, defs_box_loop, n_loop], // Pinned - vec![i_loop], // Carrier + vec![dst_loop, defs_box_loop, n_loop], // Pinned + vec![i_loop], // Carrier ); // loop_step 関数: @@ -158,59 +161,69 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio vec![dst_loop, defs_box_loop, n_loop, i_loop], ); - let cmp_result = vid::loop_step(10); // 10010 - let item_value = vid::loop_step(11); // 10011 - let next_i = vid::loop_step(12); // 10012 - let const_1 = vid::loop_step(13); // 10013 + let cmp_result = vid::loop_step(10); // 10010 + let item_value = vid::loop_step(11); // 10011 + let next_i = vid::loop_step(12); // 10012 + let const_1 = vid::loop_step(13); // 10013 // cmp_result = (i >= n) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_result, - op: CompareOp::Ge, - lhs: i_loop, - rhs: n_loop, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); // Phase 27.14: Exit φ の意味を LoopExitShape で明示 // FuncScanner _append_defs ループ脱出時は void 返却(dst は破壊的変更済み) - let _exit_shape = LoopExitShape::new_manual(vec![]); // exit_args = [] (void) + let _exit_shape = LoopExitShape::new_manual(vec![]); // exit_args = [] (void) // if i >= n { return } (void) loop_step_func.body.push(JoinInst::Jump { cont: JoinContId::new(0), - args: vec![], // ← LoopExitShape.exit_args に対応 (void) + args: vec![], // ← LoopExitShape.exit_args に対応 (void) cond: Some(cmp_result), }); // item = defs_box.get(i) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(item_value), - box_name: "ArrayBox".to_string(), - method: "get".to_string(), - args: vec![defs_box_loop, i_loop], - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(item_value), + box_name: "ArrayBox".to_string(), + method: "get".to_string(), + args: vec![defs_box_loop, i_loop], + })); // dst.push(item) - 破壊的変更(戻り値なし) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: None, // push は戻り値なし - box_name: "ArrayBox".to_string(), - method: "push".to_string(), - args: vec![dst_loop, item_value], - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: None, // push は戻り値なし + box_name: "ArrayBox".to_string(), + method: "push".to_string(), + args: vec![dst_loop, item_value], + })); // const_1 = 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1, - value: ConstValue::Integer(1), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); // next_i = i + 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: next_i, - op: BinOpKind::Add, - lhs: i_loop, - rhs: const_1, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); // loop_step(dst, defs_box, n, next_i) - tail recursion loop_step_func.body.push(JoinInst::Call { @@ -223,7 +236,10 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio join_module.add_function(loop_step_func); eprintln!("[joinir/funcscanner_append_defs/build] ✅ JoinIR construction completed"); - eprintln!("[joinir/funcscanner_append_defs/build] Functions: {}", join_module.functions.len()); + eprintln!( + "[joinir/funcscanner_append_defs/build] Functions: {}", + join_module.functions.len() + ); Some(join_module) } @@ -251,7 +267,10 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { let target_func = module.functions.get("FuncScannerBox._append_defs/2")?; eprintln!("[joinir/funcscanner_append_defs/mir] Found FuncScannerBox._append_defs/2"); - eprintln!("[joinir/funcscanner_append_defs/mir] MIR blocks: {}", target_func.blocks.len()); + eprintln!( + "[joinir/funcscanner_append_defs/mir] MIR blocks: {}", + target_func.blocks.len() + ); // Step 2: MirQueryBox を作成 let query = MirQueryBox::new(target_func); @@ -266,17 +285,26 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { // CFG Check 2: Entry block contains expected patterns // Pattern 1: i = 0 (初期化) if !has_const_int(&query, entry, 0) { - log_fallback("funcscanner_append_defs", "Const(0) not found in entry block"); + log_fallback( + "funcscanner_append_defs", + "Const(0) not found in entry block", + ); return lower_handwritten(module); } // Pattern 2: defs_box.length() の検出 // Check entry block and its immediate successors for length() call let has_length_call = has_array_method(&query, entry, "length") - || query.succs(entry).iter().any(|&succ| has_array_method(&query, succ, "length")); + || query + .succs(entry) + .iter() + .any(|&succ| has_array_method(&query, succ, "length")); if !has_length_call { - log_fallback("funcscanner_append_defs", "ArrayBox.length() not found in entry or successors"); + log_fallback( + "funcscanner_append_defs", + "ArrayBox.length() not found in entry or successors", + ); return lower_handwritten(module); } @@ -284,27 +312,78 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { // Check all blocks for array operations (get/push) and loop increment let all_blocks: Vec<_> = target_func.blocks.keys().copied().collect(); - let has_get_call = all_blocks.iter().any(|&bb| has_array_method(&query, bb, "get")); + let has_get_call = all_blocks + .iter() + .any(|&bb| has_array_method(&query, bb, "get")); if !has_get_call { - log_fallback("funcscanner_append_defs", "ArrayBox.get() not found in function body"); + log_fallback( + "funcscanner_append_defs", + "ArrayBox.get() not found in function body", + ); return lower_handwritten(module); } - let has_push_call = all_blocks.iter().any(|&bb| has_array_method(&query, bb, "push")); + let has_push_call = all_blocks + .iter() + .any(|&bb| has_array_method(&query, bb, "push")); if !has_push_call { - log_fallback("funcscanner_append_defs", "ArrayBox.push() not found in function body"); + log_fallback( + "funcscanner_append_defs", + "ArrayBox.push() not found in function body", + ); return lower_handwritten(module); } let has_increment = all_blocks.iter().any(|&bb| has_loop_increment(&query, bb)); if !has_increment { - log_fallback("funcscanner_append_defs", "loop increment (i + 1) not found in function body"); + log_fallback( + "funcscanner_append_defs", + "loop increment (i + 1) not found in function body", + ); return lower_handwritten(module); } eprintln!("[joinir/funcscanner_append_defs/mir] CFG sanity checks passed ✅"); eprintln!("[joinir/funcscanner_append_defs/mir] Found: length(), get(), push(), i+1"); + if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") { + let header = query.succs(entry).get(0).copied().unwrap_or(entry); + let succs_header = query.succs(header); + let body = succs_header.get(0).copied().unwrap_or(header); + let exit = succs_header.get(1).copied().unwrap_or(header); + let loop_form = LoopForm { + preheader: entry, + header, + body, + latch: body, + exit, + continue_targets: vec![body], + break_targets: vec![exit], + }; + if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) { + eprintln!( + "[joinir/funcscanner_append_defs/generic-hook] detected simple Case A loop (generic_case_a hookup point)" + ); + let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new(); + let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new(); + if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_append_defs_minimal( + &loop_form, + &var_classes, + &exit_live, + &query, + target_func, + ) { + eprintln!( + "[joinir/funcscanner_append_defs/generic-hook] generic_case_a produced JoinIR, returning early" + ); + return Some(jm); + } + eprintln!( + "[joinir/funcscanner_append_defs/generic-hook] generic_case_a returned None, falling back to handwritten/MIR path" + ); + } + } + // Phase 27.14: Generate JoinIR using shared builder // CFG checks passed, so we can use build_funcscanner_append_defs_joinir() directly eprintln!("[joinir/funcscanner_append_defs/mir] Calling build_funcscanner_append_defs_joinir() after CFG validation"); diff --git a/src/mir/join_ir/lowering/funcscanner_trim.rs b/src/mir/join_ir/lowering/funcscanner_trim.rs index 14e1c1bf..305fbb7e 100644 --- a/src/mir/join_ir/lowering/funcscanner_trim.rs +++ b/src/mir/join_ir/lowering/funcscanner_trim.rs @@ -44,22 +44,19 @@ //! } //! ``` -use crate::mir::ValueId; use crate::mir::join_ir::{ BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule, LoopExitShape, LoopHeaderShape, MirLikeInst, }; +use crate::mir::loop_form::LoopForm; +use crate::mir::query::MirQuery; +use crate::mir::ValueId; /// Phase 27.9: Toggle dispatcher for trim lowering /// - Default: handwritten lowering /// - NYASH_JOINIR_LOWER_FROM_MIR=1: MIR-based lowering pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Option { - super::common::dispatch_lowering( - "trim", - module, - lower_trim_from_mir, - lower_trim_handwritten, - ) + super::common::dispatch_lowering("trim", module, lower_trim_from_mir, lower_trim_handwritten) } /// Phase 27.11: Common JoinIR builder for FuncScannerBox.trim/1 @@ -72,14 +69,18 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option Option Option 戻り値をそのまま返す let loop_step_id = JoinFuncId::new(1); @@ -147,13 +158,13 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option Option b) let cond = ValueId(6003); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cond, - lhs: e_loop, - rhs: b_loop, - op: CompareOp::Gt, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cond, + lhs: e_loop, + rhs: b_loop, + op: CompareOp::Gt, + })); // bool false (共通) let bool_false = ValueId(6019); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: bool_false, - value: ConstValue::Bool(false), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false, + value: ConstValue::Bool(false), + })); // trimmed_base = str.substring(b, e) let trimmed_base = ValueId(6004); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(trimmed_base), - box_name: "StringBox".to_string(), - method: "substring".to_string(), - args: vec![str_loop, b_loop, e_loop], - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(trimmed_base), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![str_loop, b_loop, e_loop], + })); // cond_is_false = (cond == false) let cond_is_false = ValueId(6020); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cond_is_false, - lhs: cond, - rhs: bool_false, - op: CompareOp::Eq, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cond_is_false, + lhs: cond, + rhs: bool_false, + op: CompareOp::Eq, + })); // Phase 27.5: Exit φ の意味を LoopExitShape で明示(Option A) // trim のループ脱出時は e の値で substring(b, e) を計算済み - let _exit_shape_trim = LoopExitShape::new_manual(vec![e_loop]); // exit_args = [e] (Option A) - // 実装上は既に trimmed_base = substring(b, e) を計算済みで、その結果を返している + let _exit_shape_trim = LoopExitShape::new_manual(vec![e_loop]); // exit_args = [e] (Option A) + // 実装上は既に trimmed_base = substring(b, e) を計算済みで、その結果を返している // if !(e > b) { return substring(b, e) } loop_step_func.body.push(JoinInst::Jump { cont: JoinContId::new(0), - args: vec![trimmed_base], // ← substring(b, e) の結果 + args: vec![trimmed_base], // ← substring(b, e) の結果 cond: Some(cond_is_false), }); // const 1 let const_1 = ValueId(6005); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1, - value: ConstValue::Integer(1), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); // e_minus_1 = e - 1 let e_minus_1 = ValueId(6006); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: e_minus_1, - lhs: e_loop, - rhs: const_1, - op: BinOpKind::Sub, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_minus_1, + lhs: e_loop, + rhs: const_1, + op: BinOpKind::Sub, + })); let ch = ValueId(6007); - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(ch), - box_name: "StringBox".to_string(), - method: "substring".to_string(), - args: vec![str_loop, e_minus_1, e_loop], - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![str_loop, e_minus_1, e_loop], + })); // is_space = (ch == " " || ch == "\\t" || ch == "\\n" || ch == "\\r") let cmp_space = ValueId(6008); @@ -246,101 +271,127 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option Option Option /// - Lightweight CFG sanity checks /// - Fallback to handwritten if MIR structure is unexpected fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option { + use super::common::{ + ensure_entry_has_succs, has_binop, has_const_string, has_string_method, log_fallback, + }; use crate::mir::query::MirQueryBox; use crate::mir::BinaryOp; - use super::common::{ensure_entry_has_succs, has_const_string, has_string_method, has_binop, log_fallback}; // Step 1: "FuncScannerBox.trim/1" を探す let target_func = module.functions.get("FuncScannerBox.trim/1")?; @@ -550,16 +606,49 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option { // - Const("") for string coercion // - BoxCall(String.length) // - BinOp(Add) for "" + s - if !has_const_string(&query, entry_id, "") || - !has_string_method(&query, entry_id, "length") || - !has_binop(&query, entry_id, BinaryOp::Add) + if !has_const_string(&query, entry_id, "") + || !has_string_method(&query, entry_id, "length") + || !has_binop(&query, entry_id, BinaryOp::Add) { - log_fallback("trim", "entry block missing expected patterns (Const(\"\"), String.length, or BinOp(Add))"); + log_fallback( + "trim", + "entry block missing expected patterns (Const(\"\"), String.length, or BinOp(Add))", + ); return lower_trim_handwritten(module); } eprintln!("[joinir/trim/mir] CFG sanity checks passed ✅"); + if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") { + let header = query.succs(entry_id).get(0).copied().unwrap_or(entry_id); + let succs_header = query.succs(header); + let body = succs_header.get(0).copied().unwrap_or(header); + let exit = succs_header.get(1).copied().unwrap_or(header); + let loop_form = LoopForm { + preheader: entry_id, + header, + body, + latch: body, + exit, + continue_targets: vec![body], + break_targets: vec![exit], + }; + if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) { + eprintln!( + "[joinir/trim/generic-hook] simple Case A loop detected (generic_case_a hook planned)" + ); + let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new(); + let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new(); + if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_trim_minimal(&loop_form, &var_classes, &exit_live, &query, target_func) { + eprintln!("[joinir/trim/generic-hook] generic_case_a produced JoinIR, returning early"); + return Some(jm); + } + eprintln!( + "[joinir/trim/generic-hook] generic_case_a placeholder returned None, falling back to handwritten" + ); + } + } + // Phase 27.11: Generate JoinIR using shared builder // CFG checks passed, so we can use build_funcscanner_trim_joinir() directly eprintln!("[joinir/trim/mir] Calling build_funcscanner_trim_joinir() after CFG validation"); diff --git a/src/mir/join_ir/lowering/generic_case_a.rs b/src/mir/join_ir/lowering/generic_case_a.rs new file mode 100644 index 00000000..d430ef98 --- /dev/null +++ b/src/mir/join_ir/lowering/generic_case_a.rs @@ -0,0 +1,1196 @@ +//! Generic Case A LoopForm → JoinIR lowering (v1, minimal_ssa_skip_ws 専用) +//! +//! 制約(必読): +//! - 条件式の中身を解析しない(Compare/BinOp を MIR そのままコピーするだけ) +//! - 多重ヘッダ/ネストループは対象外(v1 は minimal_ssa_skip_ws の単純ループ専用) +//! - pinned/carrier/exit は LoopVarClassBox / LoopExitLivenessBox から渡された前提で扱う +//! - 解析に失敗したら必ず None を返し、呼び元にフォールバックさせる + +use std::collections::{BTreeMap, BTreeSet}; + +use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args; +use crate::mir::join_ir::lowering::loop_form_intake::{intake_loop_form, LoopFormIntake}; +use crate::mir::join_ir::lowering::value_id_ranges; +use crate::mir::join_ir::lowering::value_id_ranges::skip_ws as vid; +use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as stage1_vid; +use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule, + LoopExitShape, LoopHeaderShape, MirLikeInst, +}; +use crate::mir::loop_form::LoopForm; +use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox; +use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox; +use crate::mir::{MirFunction, MirQuery, ValueId}; + +/// v1: minimal_ssa_skip_ws 専用の汎用 Case A ロワー +/// +/// - LoopForm / VarClass / ExitLiveness は形の検証にのみ使う(パターンが合わなければ None) +/// - JoinModule の形は hand-written skip_ws と同一になるように組み立てる +pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws( + loop_form: &LoopForm, + var_classes: &LoopVarClassBox, + exit_live: &LoopExitLivenessBox, + query: &impl MirQuery, + mir_func: &MirFunction, +) -> Option { + // 1) LoopForm が Case A っぽいか最低限チェック(header→body/exit、latch→header) + if loop_form.header == loop_form.exit { + eprintln!("[joinir/generic_case_a] loop_form malformed (header == exit), fallback"); + return None; + } + if loop_form.latch != loop_form.body && loop_form.latch != loop_form.header { + // minimal_skip_ws では latch==body or header 想定。違えばフォールバック。 + eprintln!( + "[joinir/generic_case_a] unexpected latch {:?} (body={:?}, header={:?}), fallback", + loop_form.latch, loop_form.body, loop_form.header + ); + return None; + } + + // 2) MIR から pinned/carrier/exit 情報を抽出する + let LoopFormIntake { + pinned_ordered: ordered_pinned, + carrier_ordered: ordered_carriers, + name_to_header_id: _name_to_id, + header_snapshot: header_vals_mir, + exit_snapshots, + exit_preds: _exit_preds, + } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + + // JoinIR 用の ValueId を範囲から割り当て(名前→ID マップ) + let mut name_to_loop_id: BTreeMap = BTreeMap::new(); + let mut offset = 0; + for name in &ordered_pinned { + name_to_loop_id.insert(name.clone(), vid::loop_step(offset)); + offset += 1; + } + for name in &ordered_carriers { + name_to_loop_id.insert(name.clone(), vid::loop_step(offset)); + offset += 1; + } + + let pinned_ids: Vec = ordered_pinned + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + let carrier_ids: Vec = ordered_carriers + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + + // Exit live 集合(LoopExitLivenessBox を利用) + let exit_live_set: BTreeSet = + exit_live.compute_live_at_exit(query, loop_form.exit, &header_vals_mir, &exit_snapshots); + let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; + + // 3) JoinModule を手書き skip_ws と同じ形で構築(ValueId 範囲も揃える) + let string_key = ordered_pinned.get(0).cloned()?; + let len_key = ordered_pinned + .get(1) + .cloned() + .unwrap_or_else(|| string_key.clone()); + let index_key = ordered_carriers.get(0).cloned()?; + + let s_loop = *name_to_loop_id.get(&string_key)?; + let i_loop = *name_to_loop_id.get(&index_key)?; + let n_loop = *name_to_loop_id.get(&len_key)?; + + let mut join_module = JoinModule::new(); + + // entry: skip(s) + let skip_id = JoinFuncId::new(0); + let s_param = vid::entry(0); // 3000 + let mut skip_func = JoinFunction::new(skip_id, "skip".to_string(), vec![s_param]); + + let i_init = vid::entry(1); // 3001 + let n_val = vid::entry(2); // 3002 + + let mut entry_name_to_id: BTreeMap = BTreeMap::new(); + entry_name_to_id.insert(string_key.clone(), s_param); + entry_name_to_id.insert(index_key.clone(), i_init); + entry_name_to_id.insert(len_key.clone(), n_val); + + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); + + skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_val), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![s_param], + })); + + let loop_step_id = JoinFuncId::new(1); + let loop_call_args: Vec = ordered_pinned + .iter() + .chain(ordered_carriers.iter()) + .map(|name| entry_name_to_id.get(name).copied()) + .collect::>()?; + + skip_func.body.push(JoinInst::Call { + func: loop_step_id, + args: loop_call_args, + k_next: None, + dst: None, + }); + + join_module.entry = Some(skip_id); + join_module.add_function(skip_func); + + // loop_step(s, i, n) + let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let loop_params = header_shape.to_loop_step_params(); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); + + let cmp1_result = vid::loop_step(3); // 4003 + let ch = vid::loop_step(4); // 4004 + let cmp2_result = vid::loop_step(5); // 4005 + let i_plus_1 = vid::loop_step(6); // 4006 + let const_1 = vid::loop_step(7); // 4007 + let const_space = vid::loop_step(10); // 4010 + let bool_false = vid::loop_step(11); // 4011 + let cmp2_is_false = vid::loop_step(12); // 4012 + + // cmp1_result = (i >= n) + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp1_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); + + let _exit_shape = if exit_args.is_empty() { + LoopExitShape::new_manual(vec![i_loop]) + } else { + LoopExitShape::new_manual(exit_args.clone()) + }; // exit_args = [i] が期待値 + + // if i >= n { return i } + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: vec![i_loop], + cond: Some(cmp1_result), + }); + + // const 1 + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + + // i_plus_1 = i + 1 + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: i_plus_1, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); + + // ch = s.substring(i, i + 1) + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_loop, i_loop, i_plus_1], + })); + + // const " " + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_space, + value: ConstValue::String(" ".to_string()), + })); + + // cmp2_result = (ch == " ") + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp2_result, + op: CompareOp::Eq, + lhs: ch, + rhs: const_space, + })); + + // bool false + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false, + value: ConstValue::Bool(false), + })); + + // cmp2_is_false = (cmp2_result == false) + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp2_is_false, + op: CompareOp::Eq, + lhs: cmp2_result, + rhs: bool_false, + })); + + // if ch != " " { return i } + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(1), + args: vec![i_loop], + cond: Some(cmp2_is_false), + }); + + // continue: loop_step(s, i+1, n) + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![s_loop, i_plus_1, n_loop], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_step_func); + + eprintln!( + "[joinir/generic_case_a] ✅ constructed JoinIR (functions={}, value_range={}..{})", + join_module.functions.len(), + value_id_ranges::base::SKIP_WS, + value_id_ranges::base::SKIP_WS + 1999 + ); + + Some(join_module) +} + +/// Placeholder: trim minimal 用の generic Case A ロワー(v0, 未実装) +/// +/// - いまは構造チェックのみで必ず None を返し、呼び元でフォールバックさせる +/// - 将来 trim_minimal を generic_case_a で置き換える際の導線として用意 +pub fn lower_case_a_loop_to_joinir_for_trim_minimal( + loop_form: &LoopForm, + var_classes: &LoopVarClassBox, + exit_live: &LoopExitLivenessBox, + query: &impl MirQuery, + mir_func: &MirFunction, +) -> Option { + if loop_form.header == loop_form.exit { + eprintln!("[joinir/generic_case_a/trim] loop_form malformed (header == exit), fallback"); + return None; + } + + let LoopFormIntake { + pinned_ordered: ordered_pinned, + carrier_ordered: ordered_carriers, + name_to_header_id: _name_to_id, + header_snapshot, + exit_snapshots, + exit_preds: _exit_preds, + } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + + let mut name_to_loop_id: BTreeMap = BTreeMap::new(); + let mut offset = 0; + for name in &ordered_pinned { + name_to_loop_id.insert( + name.clone(), + value_id_ranges::funcscanner_trim::loop_step(offset), + ); + offset += 1; + } + for name in &ordered_carriers { + name_to_loop_id.insert( + name.clone(), + value_id_ranges::funcscanner_trim::loop_step(offset), + ); + offset += 1; + } + + let pinned_ids: Vec = ordered_pinned + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + let carrier_ids: Vec = ordered_carriers + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + + let exit_live_set: BTreeSet = + exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots); + let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; + + let string_key = ordered_pinned.get(0).cloned()?; + let base_key = ordered_pinned + .get(1) + .cloned() + .unwrap_or_else(|| string_key.clone()); + let carrier_key = ordered_carriers.get(0).cloned()?; + + let s_loop = *name_to_loop_id.get(&string_key)?; + let b_loop = *name_to_loop_id.get(&base_key)?; + let e_loop = *name_to_loop_id.get(&carrier_key)?; + + let mut join_module = JoinModule::new(); + + // entry: trim_main(s_param) + let trim_main_id = JoinFuncId::new(0); + let s_param = value_id_ranges::funcscanner_trim::entry(0); + let mut trim_main_func = + JoinFunction::new(trim_main_id, "trim_main".to_string(), vec![s_param]); + + let str_val = value_id_ranges::funcscanner_trim::entry(1); + let n_val = value_id_ranges::funcscanner_trim::entry(2); + let b_val = value_id_ranges::funcscanner_trim::entry(3); + let e_init = value_id_ranges::funcscanner_trim::entry(4); + let const_empty = value_id_ranges::funcscanner_trim::entry(5); + let const_zero = value_id_ranges::funcscanner_trim::entry(6); + + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_empty, + value: ConstValue::String("".to_string()), + })); + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: str_val, + lhs: const_empty, + rhs: s_param, + op: BinOpKind::Add, + })); + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_val), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![str_val], + })); + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_zero, + value: ConstValue::Integer(0), + })); + + let skip_leading_id = JoinFuncId::new(2); + trim_main_func.body.push(JoinInst::Call { + func: skip_leading_id, + args: vec![str_val, const_zero, n_val], + k_next: None, + dst: Some(b_val), + }); + + trim_main_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_init, + op: BinOpKind::Add, + lhs: n_val, + rhs: const_zero, + })); + + let mut entry_name_to_id: BTreeMap = BTreeMap::new(); + entry_name_to_id.insert(string_key.clone(), str_val); + entry_name_to_id.insert(base_key.clone(), b_val); + entry_name_to_id.insert(carrier_key.clone(), e_init); + + let loop_call_args: Vec = ordered_pinned + .iter() + .chain(ordered_carriers.iter()) + .map(|name| entry_name_to_id.get(name).copied()) + .collect::>()?; + + let loop_step_id = JoinFuncId::new(1); + trim_main_func.body.push(JoinInst::Call { + func: loop_step_id, + args: loop_call_args, + k_next: None, + dst: None, + }); + + join_module.entry = Some(trim_main_id); + join_module.add_function(trim_main_func); + + // loop_step(str, b, e) + let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let loop_params = header_shape.to_loop_step_params(); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); + + let cond = value_id_ranges::funcscanner_trim::loop_step(3); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cond, + lhs: e_loop, + rhs: b_loop, + op: CompareOp::Gt, + })); + + let bool_false = value_id_ranges::funcscanner_trim::loop_step(19); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false, + value: ConstValue::Bool(false), + })); + + let trimmed_base = value_id_ranges::funcscanner_trim::loop_step(4); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(trimmed_base), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_loop, b_loop, e_loop], + })); + + let cond_is_false = value_id_ranges::funcscanner_trim::loop_step(20); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cond_is_false, + lhs: cond, + rhs: bool_false, + op: CompareOp::Eq, + })); + + let _exit_shape_trim = if exit_args.is_empty() { + LoopExitShape::new_manual(vec![e_loop]) + } else { + LoopExitShape::new_manual(exit_args.clone()) + }; + + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: vec![trimmed_base], + cond: Some(cond_is_false), + }); + + let const_1 = value_id_ranges::funcscanner_trim::loop_step(5); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + + let e_minus_1 = value_id_ranges::funcscanner_trim::loop_step(6); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_minus_1, + lhs: e_loop, + rhs: const_1, + op: BinOpKind::Sub, + })); + + let ch = value_id_ranges::funcscanner_trim::loop_step(7); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_loop, e_minus_1, e_loop], + })); + + let cmp_space = value_id_ranges::funcscanner_trim::loop_step(8); + let cmp_tab = value_id_ranges::funcscanner_trim::loop_step(9); + let cmp_newline = value_id_ranges::funcscanner_trim::loop_step(10); + let cmp_cr = value_id_ranges::funcscanner_trim::loop_step(11); + + let const_space = value_id_ranges::funcscanner_trim::loop_step(12); + let const_tab = value_id_ranges::funcscanner_trim::loop_step(13); + let const_newline = value_id_ranges::funcscanner_trim::loop_step(14); + let const_cr = value_id_ranges::funcscanner_trim::loop_step(15); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_space, + value: ConstValue::String(" ".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_space, + lhs: ch, + rhs: const_space, + op: CompareOp::Eq, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_tab, + value: ConstValue::String("\\t".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_tab, + lhs: ch, + rhs: const_tab, + op: CompareOp::Eq, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_newline, + value: ConstValue::String("\\n".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_newline, + lhs: ch, + rhs: const_newline, + op: CompareOp::Eq, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_cr, + value: ConstValue::String("\\r".to_string()), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_cr, + lhs: ch, + rhs: const_cr, + op: CompareOp::Eq, + })); + + let or1 = value_id_ranges::funcscanner_trim::loop_step(16); + let or2 = value_id_ranges::funcscanner_trim::loop_step(17); + let is_space = value_id_ranges::funcscanner_trim::loop_step(18); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or1, + lhs: cmp_space, + rhs: cmp_tab, + op: BinOpKind::Or, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or2, + lhs: or1, + rhs: cmp_newline, + op: BinOpKind::Or, + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: is_space, + lhs: or2, + rhs: cmp_cr, + op: BinOpKind::Or, + })); + + let is_space_false = value_id_ranges::funcscanner_trim::loop_step(21); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: is_space_false, + lhs: is_space, + rhs: bool_false, + op: CompareOp::Eq, + })); + + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(1), + args: vec![trimmed_base], + cond: Some(is_space_false), + }); + + let e_next = value_id_ranges::funcscanner_trim::loop_step(22); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: e_next, + lhs: e_loop, + rhs: const_1, + op: BinOpKind::Sub, + })); + + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![s_loop, b_loop, e_next], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_step_func); + + // skip_leading 関数(共通ロジックを手書き版と合わせる) + let mut skip_func = JoinFunction::new( + skip_leading_id, + "skip_leading".to_string(), + vec![ValueId(7000), ValueId(7001), ValueId(7002)], + ); + let s_skip = ValueId(7000); + let i_skip = ValueId(7001); + let n_skip = ValueId(7002); + let cmp_len = ValueId(7003); + let const_1_skip = ValueId(7004); + let i_plus_1_skip = ValueId(7005); + let ch_skip = ValueId(7006); + let cmp_space_skip = ValueId(7007); + let cmp_tab_skip = ValueId(7008); + let cmp_newline_skip = ValueId(7009); + let cmp_cr_skip = ValueId(7010); + let const_space_skip = ValueId(7011); + let const_tab_skip = ValueId(7012); + let const_newline_skip = ValueId(7013); + let const_cr_skip = ValueId(7014); + let or1_skip = ValueId(7015); + let or2_skip = ValueId(7016); + let is_space_skip = ValueId(7017); + let bool_false_skip = ValueId(7018); + let is_space_false_skip = ValueId(7019); + + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_len, + lhs: i_skip, + rhs: n_skip, + op: CompareOp::Ge, + })); + skip_func.body.push(JoinInst::Jump { + cont: JoinContId::new(2), + args: vec![i_skip], + cond: Some(cmp_len), + }); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1_skip, + value: ConstValue::Integer(1), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: i_plus_1_skip, + lhs: i_skip, + rhs: const_1_skip, + op: BinOpKind::Add, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch_skip), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_skip, i_skip, i_plus_1_skip], + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_space_skip, + value: ConstValue::String(" ".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_space_skip, + lhs: ch_skip, + rhs: const_space_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_tab_skip, + value: ConstValue::String("\\t".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_tab_skip, + lhs: ch_skip, + rhs: const_tab_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_newline_skip, + value: ConstValue::String("\\n".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_newline_skip, + lhs: ch_skip, + rhs: const_newline_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_cr_skip, + value: ConstValue::String("\\r".to_string()), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_cr_skip, + lhs: ch_skip, + rhs: const_cr_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or1_skip, + lhs: cmp_space_skip, + rhs: cmp_tab_skip, + op: BinOpKind::Or, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: or2_skip, + lhs: or1_skip, + rhs: cmp_newline_skip, + op: BinOpKind::Or, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: is_space_skip, + lhs: or2_skip, + rhs: cmp_cr_skip, + op: BinOpKind::Or, + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false_skip, + value: ConstValue::Bool(false), + })); + skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: is_space_false_skip, + lhs: is_space_skip, + rhs: bool_false_skip, + op: CompareOp::Eq, + })); + skip_func.body.push(JoinInst::Jump { + cont: JoinContId::new(3), + args: vec![i_skip], + cond: Some(is_space_false_skip), + }); + skip_func.body.push(JoinInst::Call { + func: skip_leading_id, + args: vec![s_skip, i_plus_1_skip, n_skip], + k_next: None, + dst: None, + }); + + join_module.add_function(skip_func); + + eprintln!( + "[joinir/generic_case_a/trim] ✅ constructed JoinIR (functions={}, value_range={}..{})", + join_module.functions.len(), + value_id_ranges::base::FUNCSCANNER_TRIM, + value_id_ranges::base::FUNCSCANNER_TRIM + 1999 + ); + + Some(join_module) +} + +/// append_defs_minimal 用の generic Case A ロワー(LoopForm/VarClass/ExitLiveness ベース) +/// +/// - LoopForm が単一 header/latch でない場合や、必要な変数がマッピングできない場合は None を返す。 +/// - 既存の手書き JoinIR(append_defs_entry + loop_step)と同じ形を目指す。 +pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal( + loop_form: &LoopForm, + var_classes: &LoopVarClassBox, + exit_live: &LoopExitLivenessBox, + query: &impl MirQuery, + mir_func: &MirFunction, +) -> Option { + if loop_form.header == loop_form.exit { + eprintln!( + "[joinir/generic_case_a/append_defs] loop_form malformed (header == exit), fallback" + ); + return None; + } + + let LoopFormIntake { + pinned_ordered: ordered_pinned, + carrier_ordered: ordered_carriers, + name_to_header_id: _name_to_id, + header_snapshot, + exit_snapshots, + exit_preds: _exit_preds, + } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + + let mut name_to_loop_id: BTreeMap = BTreeMap::new(); + let mut offset = 0; + for name in &ordered_pinned { + name_to_loop_id.insert( + name.clone(), + value_id_ranges::funcscanner_append_defs::loop_step(offset), + ); + offset += 1; + } + for name in &ordered_carriers { + name_to_loop_id.insert( + name.clone(), + value_id_ranges::funcscanner_append_defs::loop_step(offset), + ); + offset += 1; + } + + let pinned_ids: Vec = ordered_pinned + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + let carrier_ids: Vec = ordered_carriers + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + + let exit_live_set: BTreeSet = + exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots); + let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; + + let dst_key = ordered_pinned.get(0).cloned()?; + let defs_key = ordered_pinned + .get(1) + .cloned() + .unwrap_or_else(|| dst_key.clone()); + let n_key = ordered_pinned + .get(2) + .cloned() + .unwrap_or_else(|| defs_key.clone()); + let i_key = ordered_carriers.get(0).cloned()?; + + let dst_loop = *name_to_loop_id.get(&dst_key)?; + let defs_loop = *name_to_loop_id.get(&defs_key)?; + let n_loop = *name_to_loop_id.get(&n_key)?; + let i_loop = *name_to_loop_id.get(&i_key)?; + + let mut join_module = JoinModule::new(); + + // entry: append_defs_entry(dst, defs_box, n) + let entry_id = JoinFuncId::new(0); + let dst_param = value_id_ranges::funcscanner_append_defs::entry(0); + let defs_box_param = value_id_ranges::funcscanner_append_defs::entry(1); + let n_param = value_id_ranges::funcscanner_append_defs::entry(2); + let mut entry_func = JoinFunction::new( + entry_id, + "append_defs_entry".to_string(), + vec![dst_param, defs_box_param, n_param], + ); + + let i_init = value_id_ranges::funcscanner_append_defs::entry(10); + entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); + let mut entry_name_to_id: BTreeMap = BTreeMap::new(); + entry_name_to_id.insert("s".to_string(), dst_param); // intake で param0 を "s" にするため + entry_name_to_id.insert("dst".to_string(), dst_param); + entry_name_to_id.insert("param1".to_string(), defs_box_param); + entry_name_to_id.insert("defs_box".to_string(), defs_box_param); + entry_name_to_id.insert("n".to_string(), n_param); + entry_name_to_id.insert("i".to_string(), i_init); + + let loop_call_args: Vec = ordered_pinned + .iter() + .chain(ordered_carriers.iter()) + .map(|name| entry_name_to_id.get(name).copied()) + .collect::>()?; + + let loop_step_id = JoinFuncId::new(1); + entry_func.body.push(JoinInst::Call { + func: loop_step_id, + args: loop_call_args, + k_next: None, + dst: None, + }); + + join_module.entry = Some(entry_id); + join_module.add_function(entry_func); + + // loop_step(dst, defs_box, n, i) + let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let loop_params = header_shape.to_loop_step_params(); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); + + let cmp_result = value_id_ranges::funcscanner_append_defs::loop_step(10); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); + + let _exit_shape = LoopExitShape::new_manual(exit_args.clone()); + + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: exit_args.clone(), + cond: Some(cmp_result), + }); + + let item_value = value_id_ranges::funcscanner_append_defs::loop_step(11); + let next_i = value_id_ranges::funcscanner_append_defs::loop_step(12); + let const_1 = value_id_ranges::funcscanner_append_defs::loop_step(13); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(item_value), + box_name: "ArrayBox".to_string(), + method: "get".to_string(), + args: vec![defs_loop, i_loop], + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: None, + box_name: "ArrayBox".to_string(), + method: "push".to_string(), + args: vec![dst_loop, item_value], + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); + + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![dst_loop, defs_loop, n_loop, next_i], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_step_func); + + eprintln!( + "[joinir/generic_case_a/append_defs] ✅ constructed JoinIR (functions={}, value_range={}..{})", + join_module.functions.len(), + value_id_ranges::base::FUNCSCANNER_APPEND_DEFS, + value_id_ranges::base::FUNCSCANNER_APPEND_DEFS + 1999 + ); + + Some(join_module) +} + +/// Stage1UsingResolver minimal 用の generic Case A ロワー(LoopForm/VarClass/ExitLiveness ベース) +pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal( + loop_form: &LoopForm, + var_classes: &LoopVarClassBox, + exit_live: &LoopExitLivenessBox, + query: &impl MirQuery, + mir_func: &MirFunction, +) -> Option { + if loop_form.header == loop_form.exit { + eprintln!("[joinir/generic_case_a/stage1] loop_form malformed (header == exit), fallback"); + return None; + } + + let LoopFormIntake { + pinned_ordered: ordered_pinned, + carrier_ordered: ordered_carriers, + name_to_header_id: _name_to_id, + header_snapshot, + exit_snapshots, + exit_preds: _exit_preds, + } = intake_loop_form(loop_form, var_classes, query, mir_func)?; + + let mut name_to_loop_id: BTreeMap = BTreeMap::new(); + let mut offset = 0; + for name in &ordered_pinned { + name_to_loop_id.insert(name.clone(), stage1_vid::loop_step(offset)); + offset += 1; + } + for name in &ordered_carriers { + name_to_loop_id.insert(name.clone(), stage1_vid::loop_step(offset)); + offset += 1; + } + + let pinned_ids: Vec = ordered_pinned + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + let carrier_ids: Vec = ordered_carriers + .iter() + .filter_map(|k| name_to_loop_id.get(k).copied()) + .collect(); + + let exit_live_set: BTreeSet = + exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots); + let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?; + + let entries_key = ordered_pinned.get(0).cloned()?; + let n_key = ordered_pinned + .get(1) + .cloned() + .unwrap_or_else(|| entries_key.clone()); + let modules_key = ordered_pinned + .get(2) + .cloned() + .unwrap_or_else(|| entries_key.clone()); + let seen_key = ordered_pinned + .get(3) + .cloned() + .unwrap_or_else(|| entries_key.clone()); + let prefix_key = ordered_carriers.get(0).cloned()?; + let i_key = ordered_carriers + .get(1) + .cloned() + .unwrap_or_else(|| prefix_key.clone()); + + let entries_loop = *name_to_loop_id.get(&entries_key)?; + let n_loop = *name_to_loop_id.get(&n_key)?; + let modules_loop = *name_to_loop_id.get(&modules_key)?; + let seen_loop = *name_to_loop_id.get(&seen_key)?; + let prefix_loop = *name_to_loop_id.get(&prefix_key)?; + let i_loop = *name_to_loop_id.get(&i_key)?; + + let mut join_module = JoinModule::new(); + + // entry: resolve_entries(entries, n, modules, seen, prefix_init) + let resolve_id = JoinFuncId::new(0); + let entries_param = stage1_vid::entry(0); + let n_param = stage1_vid::entry(1); + let modules_param = stage1_vid::entry(2); + let seen_param = stage1_vid::entry(3); + let prefix_param = stage1_vid::entry(4); + let mut resolve_func = JoinFunction::new( + resolve_id, + "resolve_entries".to_string(), + vec![ + entries_param, + n_param, + modules_param, + seen_param, + prefix_param, + ], + ); + + let i_init = stage1_vid::entry(10); + resolve_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); + + let mut entry_name_to_id: BTreeMap = BTreeMap::new(); + entry_name_to_id.insert(entries_key.clone(), entries_param); + entry_name_to_id.insert(n_key.clone(), n_param); + entry_name_to_id.insert(modules_key.clone(), modules_param); + entry_name_to_id.insert(seen_key.clone(), seen_param); + entry_name_to_id.insert(prefix_key.clone(), prefix_param); + entry_name_to_id.insert(i_key.clone(), i_init); + + let loop_call_args: Vec = ordered_pinned + .iter() + .chain(ordered_carriers.iter()) + .map(|name| entry_name_to_id.get(name).copied()) + .collect::>()?; + + let loop_step_id = JoinFuncId::new(1); + resolve_func.body.push(JoinInst::Call { + func: loop_step_id, + args: loop_call_args, + k_next: None, + dst: None, + }); + + join_module.entry = Some(resolve_id); + join_module.add_function(resolve_func); + + // loop_step(entries, n, modules, seen, prefix, i) + let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone()); + let loop_params = header_shape.to_loop_step_params(); + let mut loop_step_func = + JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone()); + + let cmp_result = stage1_vid::loop_step(10); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); + + let exit_shape = if exit_args.is_empty() { + LoopExitShape::new_manual(vec![prefix_loop]) + } else { + LoopExitShape::new_manual(exit_args.clone()) + }; + + loop_step_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: exit_shape.exit_args.clone(), + cond: Some(cmp_result), + }); + + let entry_value = stage1_vid::loop_step(11); + let next_i = stage1_vid::loop_step(12); + let const_1 = stage1_vid::loop_step(13); + let new_prefix = stage1_vid::loop_step(14); + + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(entry_value), + box_name: "ArrayBox".to_string(), + method: "get".to_string(), + args: vec![entries_loop, i_loop], + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: new_prefix, + op: BinOpKind::Or, + lhs: prefix_loop, + rhs: entry_value, + })); + loop_step_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![ + entries_loop, + n_loop, + modules_loop, + seen_loop, + new_prefix, + next_i, + ], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_step_func); + + eprintln!( + "[joinir/generic_case_a/stage1] ✅ constructed JoinIR (functions={}, value_range={}..{})", + join_module.functions.len(), + value_id_ranges::base::STAGE1_USING_RESOLVER, + value_id_ranges::base::STAGE1_USING_RESOLVER + 1999 + ); + + Some(join_module) +} diff --git a/src/mir/join_ir/lowering/loop_form_intake.rs b/src/mir/join_ir/lowering/loop_form_intake.rs new file mode 100644 index 00000000..b49a37e2 --- /dev/null +++ b/src/mir/join_ir/lowering/loop_form_intake.rs @@ -0,0 +1,212 @@ +//! LoopForm intake helpers for generic JoinIR lowering. +//! +//! 役割: +//! - LoopForm + MirQuery + MirFunction から、JoinIR 降下に必要な情報を抽出する。 +//! - pinned/carrier 推定と ValueId マッピング、header/exit スナップショット収集を一元化。 +//! - generic_case_a/B など複数ロワーで再利用するための「入口箱」だよ。 + +use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox; +use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; +use crate::mir::{BasicBlockId, MirFunction, MirInstruction, MirQuery, ValueId}; +use std::collections::{BTreeMap, BTreeSet}; + +/// 抽出結果 +pub(crate) struct LoopFormIntake { + pub pinned_ordered: Vec, + pub carrier_ordered: Vec, + pub name_to_header_id: BTreeMap, + pub header_snapshot: BTreeMap, + pub exit_snapshots: Vec<(BasicBlockId, BTreeMap)>, + pub exit_preds: Vec, +} + +/// LoopForm + MIR から pinned/carrier とスナップショットを抽出する。 +/// +/// 失敗時は None(フォールバック用)。 +pub(crate) fn intake_loop_form( + loop_form: &crate::mir::loop_form::LoopForm, + var_classes: &LoopVarClassBox, + query: &impl MirQuery, + mir_func: &MirFunction, +) -> Option { + // preheader のパラメータを名前付け + let mut value_to_name: BTreeMap = BTreeMap::new(); + let mut preheader_names: BTreeMap = BTreeMap::new(); + for (idx, val) in mir_func.params.iter().copied().enumerate() { + let name = if idx == 0 { + "s".to_string() + } else { + format!("param{}", idx) + }; + value_to_name.insert(val, name.clone()); + preheader_names.insert(name, val); + } + + let mut string_name: Option = value_to_name.get(mir_func.params.get(0)?).cloned(); + let mut len_name: Option = None; + let mut index_name: Option = None; + + for inst in query.insts_in_block(loop_form.preheader) { + match inst { + MirInstruction::BoxCall { + dst: Some(dst), + method, + args, + .. + } if method == "length" => { + if let Some(arg) = args.get(0) { + let s_name = value_to_name + .get(arg) + .cloned() + .unwrap_or_else(|| "s".to_string()); + value_to_name.entry(*arg).or_insert(s_name.clone()); + string_name.get_or_insert(s_name.clone()); + } + value_to_name.entry(*dst).or_insert_with(|| "n".to_string()); + len_name.get_or_insert_with(|| value_to_name[dst].clone()); + preheader_names + .entry(value_to_name[dst].clone()) + .or_insert(*dst); + } + MirInstruction::Const { + dst, + value: crate::mir::types::ConstValue::Integer(0), + } if index_name.is_none() => { + value_to_name.entry(*dst).or_insert_with(|| "i".to_string()); + index_name = Some(value_to_name[dst].clone()); + preheader_names + .entry(value_to_name[dst].clone()) + .or_insert(*dst); + } + MirInstruction::Copy { dst, src } => { + if let Some(existing) = value_to_name.get(src).cloned() { + value_to_name.entry(*dst).or_insert(existing.clone()); + preheader_names.entry(existing).or_insert(*dst); + } + } + _ => {} + } + } + + // header φ を読む + let mut header_vals_mir: BTreeMap = BTreeMap::new(); + let mut snapshot_maps: BTreeMap> = BTreeMap::new(); + let mut pinned_hint: Vec = Vec::new(); + let mut carrier_hint: Vec = Vec::new(); + + for inst in query.insts_in_block(loop_form.header) { + if let MirInstruction::Phi { dst, inputs } = inst { + let name = inputs + .iter() + .find_map(|(_, v)| value_to_name.get(v).cloned()) + .unwrap_or_else(|| format!("v{}", dst.0)); + value_to_name.entry(*dst).or_insert(name.clone()); + header_vals_mir.insert(name.clone(), *dst); + + let pre_val = inputs + .iter() + .find(|(bb, _)| *bb == loop_form.preheader) + .map(|(_, v)| *v); + let latch_val = inputs + .iter() + .find(|(bb, _)| *bb == loop_form.latch) + .map(|(_, v)| *v); + let is_carrier = match (pre_val, latch_val) { + (Some(p), Some(l)) => p != l, + (None, Some(_)) => true, + _ => false, + }; + if is_carrier { + carrier_hint.push(name.clone()); + } else { + pinned_hint.push(name.clone()); + } + + for (bb, val) in inputs { + let entry = snapshot_maps.entry(*bb).or_default(); + entry.insert(name.clone(), *val); + value_to_name.entry(*val).or_insert(name.clone()); + } + } + } + + // header で読まれている値も拾う + for inst in query.insts_in_block(loop_form.header) { + for used in query.reads_of(inst) { + if let Some(name) = value_to_name.get(&used).cloned() { + header_vals_mir.entry(name).or_insert(used); + } + } + } + + // preheader の既知を補完(s/n/i) + for (name, val) in &preheader_names { + header_vals_mir.entry(name.clone()).or_insert(*val); + } + + // exit preds を CFG から集める + let mut exit_preds: Vec = mir_func + .blocks + .iter() + .filter(|(_, bb)| bb.successors.contains(&loop_form.exit)) + .map(|(id, _)| *id) + .collect(); + exit_preds.sort_by_key(|b| b.0); + + if exit_preds.contains(&loop_form.header) { + snapshot_maps + .entry(loop_form.header) + .or_insert_with(|| header_vals_mir.clone()); + } + + let mut exit_snapshots: Vec<(BasicBlockId, BTreeMap)> = Vec::new(); + for pred in &exit_preds { + if let Some(snap) = snapshot_maps.get(pred) { + exit_snapshots.push((*pred, snap.clone())); + } + } + + // LocalScopeInspector に snapshot を登録して VarClass 判定の土台を整える + let mut inspector = LocalScopeInspectorBox::new(); + inspector.record_snapshot(loop_form.header, &header_vals_mir); + for (bb, snap) in &exit_snapshots { + inspector.record_snapshot(*bb, snap); + } + + let all_names: Vec = header_vals_mir.keys().cloned().collect(); + let classified = var_classes.classify_all( + &all_names, + &pinned_hint, + &carrier_hint, + &inspector, + &exit_preds, + ); + + let ordered_pinned: Vec = classified + .iter() + .filter(|(_, c)| matches!(c, LoopVarClass::Pinned)) + .map(|(n, _)| n.clone()) + .collect::>() + .into_iter() + .collect(); + let ordered_carriers: Vec = classified + .iter() + .filter(|(_, c)| matches!(c, LoopVarClass::Carrier)) + .map(|(n, _)| n.clone()) + .collect::>() + .into_iter() + .collect(); + + if ordered_pinned.is_empty() || ordered_carriers.is_empty() { + return None; + } + + Some(LoopFormIntake { + pinned_ordered: ordered_pinned, + carrier_ordered: ordered_carriers, + name_to_header_id: header_vals_mir.clone(), + header_snapshot: header_vals_mir, + exit_snapshots, + exit_preds, + }) +} diff --git a/src/mir/join_ir/lowering/min_loop.rs b/src/mir/join_ir/lowering/min_loop.rs index 6026db2e..8e0034ef 100644 --- a/src/mir/join_ir/lowering/min_loop.rs +++ b/src/mir/join_ir/lowering/min_loop.rs @@ -31,10 +31,10 @@ //! } //! ``` -use crate::mir::ValueId; use crate::mir::join_ir::{ BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, }; +use crate::mir::ValueId; pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option { // Step 1: "JoinIrMin.main/0" を探す @@ -87,18 +87,22 @@ pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option= 2) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_result, - op: CompareOp::Ge, - lhs: i_param, - rhs: const_2, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_param, + rhs: const_2, + })); // if cmp_result { ret i } else { loop_step(i+1) } // Phase 26-H 簡略化: 分岐はせず両方の経路を示す @@ -109,18 +113,22 @@ pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option Option Option { let target_func = module.functions.get("Main.skip/1")?; eprintln!("[joinir/skip_ws/build] Found Main.skip/1"); - eprintln!("[joinir/skip_ws/build] MIR blocks: {}", target_func.blocks.len()); + eprintln!( + "[joinir/skip_ws/build] MIR blocks: {}", + target_func.blocks.len() + ); // Step 2: JoinModule を構築 let mut join_module = JoinModule::new(); @@ -100,13 +104,13 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option { // skip_ws ループの場合: // - Pinned: s (文字列), n (長さ) - ループ中で不変 // - Carrier: i (現在位置) - ループで更新される - let s_loop = ValueId(4000); // Pinned - let i_loop = ValueId(4001); // Carrier - let n_loop = ValueId(4002); // Pinned + let s_loop = ValueId(4000); // Pinned + let i_loop = ValueId(4001); // Carrier + let n_loop = ValueId(4002); // Pinned let _header_shape = LoopHeaderShape::new_manual( - vec![s_loop, n_loop], // Pinned: s, n - vec![i_loop], // Carrier: i + vec![s_loop, n_loop], // Pinned: s, n + vec![i_loop], // Carrier: i ); // 将来: LoopHeaderShape.to_loop_step_params() は [pinned..., carriers...] の順を返す。 // 現在は既存 JoinIR テストとの互換性のため、手動で [s, i, n] の順を維持している。 @@ -115,7 +119,7 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option { let mut loop_step_func = JoinFunction::new( loop_step_id, "loop_step".to_string(), - vec![s_loop, i_loop, n_loop], // [pinned, carrier, pinned] の順(現行実装) + vec![s_loop, i_loop, n_loop], // [pinned, carrier, pinned] の順(現行実装) ); let cmp1_result = ValueId(4003); @@ -128,79 +132,95 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option { let cmp2_is_false = ValueId(4012); // cmp1_result = (i >= n) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp1_result, - op: CompareOp::Ge, - lhs: i_loop, - rhs: n_loop, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp1_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); // Phase 27.5: Exit φ の意味を LoopExitShape で明示 // skip_ws のループ脱出時は i の値だけを返す(先頭空白の文字数) - let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i] + let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i] // if i >= n { return i } loop_step_func.body.push(JoinInst::Jump { cont: JoinContId::new(0), - args: vec![i_loop], // ← LoopExitShape.exit_args に対応 + args: vec![i_loop], // ← LoopExitShape.exit_args に対応 cond: Some(cmp1_result), }); // const 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1, - value: ConstValue::Integer(1), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); // i_plus_1 = i + 1 (再利用: substring end / continue path) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: i_plus_1, - op: BinOpKind::Add, - lhs: i_loop, - rhs: const_1, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: i_plus_1, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); // ch = s.substring(i, i + 1) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(ch), - box_name: "StringBox".to_string(), - method: "substring".to_string(), - args: vec![s_loop, i_loop, i_plus_1], - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(ch), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_loop, i_loop, i_plus_1], + })); // const " " (space) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_space, - value: ConstValue::String(" ".to_string()), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_space, + value: ConstValue::String(" ".to_string()), + })); // cmp2_result = (ch == " ") - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp2_result, - op: CompareOp::Eq, - lhs: ch, - rhs: const_space, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp2_result, + op: CompareOp::Eq, + lhs: ch, + rhs: const_space, + })); // bool false (for negation) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: bool_false, - value: ConstValue::Bool(false), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: bool_false, + value: ConstValue::Bool(false), + })); // cmp2_is_false = (cmp2_result == false) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp2_is_false, - op: CompareOp::Eq, - lhs: cmp2_result, - rhs: bool_false, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp2_is_false, + op: CompareOp::Eq, + lhs: cmp2_result, + rhs: bool_false, + })); // Phase 27.5: 2箇所目の exit パス(同じく exit_args = [i]) // if ch != " " { return i } loop_step_func.body.push(JoinInst::Jump { cont: JoinContId::new(1), - args: vec![i_loop], // ← LoopExitShape.exit_args に対応(1箇所目と同じ) + args: vec![i_loop], // ← LoopExitShape.exit_args に対応(1箇所目と同じ) cond: Some(cmp2_is_false), }); @@ -214,7 +234,10 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option { join_module.add_function(loop_step_func); - eprintln!("[joinir/skip_ws] Generated {} JoinIR functions", join_module.functions.len()); + eprintln!( + "[joinir/skip_ws] Generated {} JoinIR functions", + join_module.functions.len() + ); Some(join_module) } @@ -230,14 +253,17 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option { /// ## 実装状況: /// - Phase 27.8: 基本実装(MirQuery を使用した MIR 解析) fn lower_skip_ws_from_mir(module: &crate::mir::MirModule) -> Option { - use crate::mir::query::MirQueryBox; use super::common::{ensure_entry_has_succs, has_const_int, has_string_method, log_fallback}; + use crate::mir::query::MirQueryBox; // Step 1: "Main.skip/1" を探す let target_func = module.functions.get("Main.skip/1")?; eprintln!("[joinir/skip_ws/mir] Found Main.skip/1 (MIR-based lowering)"); - eprintln!("[joinir/skip_ws/mir] MIR blocks: {}", target_func.blocks.len()); + eprintln!( + "[joinir/skip_ws/mir] MIR blocks: {}", + target_func.blocks.len() + ); // NOTE: // このフェーズでは minimal_ssa_skip_ws.hako 固定のパターンを前提に、 @@ -246,7 +272,10 @@ fn lower_skip_ws_from_mir(module: &crate::mir::MirModule) -> Option // 簡易チェック: ブロック数が最低限あるか確認 if target_func.blocks.len() < 3 { - log_fallback("skip_ws", &format!("insufficient blocks ({})", target_func.blocks.len())); + log_fallback( + "skip_ws", + &format!("insufficient blocks ({})", target_func.blocks.len()), + ); return lower_skip_ws_handwritten(module); } @@ -301,6 +330,19 @@ fn lower_skip_ws_handwritten(module: &crate::mir::MirModule) -> Option Option { + // Phase 28: Generic Case A トグル(minimal_ssa_skip_ws 限定) + if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") { + if let Some(jm) = try_lower_skip_ws_generic_case_a(module) { + return Some(jm); + } + eprintln!("[joinir/skip_ws] generic_case_a fallback → existing dispatcher"); + } + + lower_skip_ws_handwritten_or_mir(module) +} + +/// 既存の hand-written / MIR-based dispatcher をラップしただけの関数 +fn lower_skip_ws_handwritten_or_mir(module: &crate::mir::MirModule) -> Option { super::common::dispatch_lowering( "skip_ws", module, @@ -308,3 +350,44 @@ pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option Option { + use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_minimal_skip_ws; + use crate::mir::loop_form::LoopForm; + use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox; + use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox; + use crate::mir::query::MirQueryBox; + + let target_func = module.functions.get("Main.skip/1")?; + let query = MirQueryBox::new(target_func); + + // 最小限の LoopForm 形状推定(Case A/constant-true ループ想定) + let preheader = target_func.entry_block; + let header = query.succs(preheader).get(0).copied().unwrap_or(preheader); + let succs_header = query.succs(header); + let body = succs_header.get(0).copied().unwrap_or(header); + let exit = succs_header.get(1).copied().unwrap_or(header); + let latch = body; + + let loop_form = LoopForm { + preheader, + header, + body, + latch, + exit, + continue_targets: vec![body], + break_targets: vec![exit], + }; + + let var_classes = LoopVarClassBox::new(); + let exit_live = LoopExitLivenessBox::new(); + + lower_case_a_loop_to_joinir_for_minimal_skip_ws( + &loop_form, + &var_classes, + &exit_live, + &query, + target_func, + ) +} diff --git a/src/mir/join_ir/lowering/stage1_using_resolver.rs b/src/mir/join_ir/lowering/stage1_using_resolver.rs index 6368d592..d6766286 100644 --- a/src/mir/join_ir/lowering/stage1_using_resolver.rs +++ b/src/mir/join_ir/lowering/stage1_using_resolver.rs @@ -42,10 +42,14 @@ //! } //! ``` -use crate::mir::join_ir::lowering::common::{dispatch_lowering, ensure_entry_has_succs, has_const_int, log_fallback}; +use crate::mir::join_ir::lowering::common::{ + dispatch_lowering, ensure_entry_has_succs, has_const_int, log_fallback, +}; use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as vid; -use crate::mir::join_ir::{JoinModule}; -use crate::mir::query::MirQueryBox; +use crate::mir::join_ir::JoinModule; +use crate::mir::loop_form::LoopForm; +use crate::mir::query::{MirQuery, MirQueryBox}; +use crate::mir::ValueId; /// Phase 27.12: Stage1UsingResolverBox.resolve_for_source の JoinIR lowering(public dispatcher) /// @@ -83,11 +87,15 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option< use crate::mir::join_ir::*; // Phase 27.13: ターゲット関数が存在するかチェック - let _target_func = module.functions.get("Stage1UsingResolverBox.resolve_for_source/5")?; + let _target_func = module + .functions + .get("Stage1UsingResolverBox.resolve_for_source/5")?; eprintln!("[joinir/stage1_using_resolver/build] Phase 27.13 implementation"); eprintln!("[joinir/stage1_using_resolver/build] Generating JoinIR for entries loop"); - eprintln!("[joinir/stage1_using_resolver/build] Using ValueId range: 7000-8999 (via value_id_ranges)"); + eprintln!( + "[joinir/stage1_using_resolver/build] Using ValueId range: 7000-8999 (via value_id_ranges)" + ); // Step 1: JoinModule を構築 let mut join_module = JoinModule::new(); @@ -98,31 +106,46 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option< // loop_step(entries, n, modules, seen, prefix_init, i_init) // } let resolve_id = JoinFuncId::new(0); - let entries_param = vid::entry(0); // 7000 - let n_param = vid::entry(1); // 7001 - let modules_param = vid::entry(2); // 7002 - let seen_param = vid::entry(3); // 7003 - let prefix_init_param = vid::entry(4); // 7004 + let entries_param = vid::entry(0); // 7000 + let n_param = vid::entry(1); // 7001 + let modules_param = vid::entry(2); // 7002 + let seen_param = vid::entry(3); // 7003 + let prefix_init_param = vid::entry(4); // 7004 let mut resolve_func = JoinFunction::new( resolve_id, "resolve_entries".to_string(), - vec![entries_param, n_param, modules_param, seen_param, prefix_init_param], + vec![ + entries_param, + n_param, + modules_param, + seen_param, + prefix_init_param, + ], ); - let i_init = vid::entry(10); // 7010 + let i_init = vid::entry(10); // 7010 // i_init = 0 - resolve_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: i_init, - value: ConstValue::Integer(0), - })); + resolve_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); // loop_step(entries, n, modules, seen, prefix_init, i_init) let loop_step_id = JoinFuncId::new(1); resolve_func.body.push(JoinInst::Call { func: loop_step_id, - args: vec![entries_param, n_param, modules_param, seen_param, prefix_init_param, i_init], + args: vec![ + entries_param, + n_param, + modules_param, + seen_param, + prefix_init_param, + i_init, + ], k_next: None, dst: None, }); @@ -135,16 +158,16 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option< // - Pinned: entries (ArrayBox), n (Integer), modules (MapBox), seen (MapBox) // - Carrier: prefix (String), i (Integer) // - Exit: prefix (String) - let entries_loop = vid::loop_step(0); // 8000 - Pinned - let n_loop = vid::loop_step(1); // 8001 - Pinned - let modules_loop = vid::loop_step(2); // 8002 - Pinned - let seen_loop = vid::loop_step(3); // 8003 - Pinned - let prefix_loop = vid::loop_step(4); // 8004 - Carrier - let i_loop = vid::loop_step(5); // 8005 - Carrier + let entries_loop = vid::loop_step(0); // 8000 - Pinned + let n_loop = vid::loop_step(1); // 8001 - Pinned + let modules_loop = vid::loop_step(2); // 8002 - Pinned + let seen_loop = vid::loop_step(3); // 8003 - Pinned + let prefix_loop = vid::loop_step(4); // 8004 - Carrier + let i_loop = vid::loop_step(5); // 8005 - Carrier let _header_shape = LoopHeaderShape::new_manual( - vec![entries_loop, n_loop, modules_loop, seen_loop], // Pinned - vec![prefix_loop, i_loop], // Carrier + vec![entries_loop, n_loop, modules_loop, seen_loop], // Pinned + vec![prefix_loop, i_loop], // Carrier ); // loop_step 関数: @@ -159,69 +182,93 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option< let mut loop_step_func = JoinFunction::new( loop_step_id, "loop_step".to_string(), - vec![entries_loop, n_loop, modules_loop, seen_loop, prefix_loop, i_loop], + vec![ + entries_loop, + n_loop, + modules_loop, + seen_loop, + prefix_loop, + i_loop, + ], ); - let cmp_result = vid::loop_step(10); // 8010 - let entry_value = vid::loop_step(11); // 8011 - let next_i = vid::loop_step(12); // 8012 - let const_1 = vid::loop_step(13); // 8013 - let new_prefix = vid::loop_step(14); // 8014 + let cmp_result = vid::loop_step(10); // 8010 + let entry_value = vid::loop_step(11); // 8011 + let next_i = vid::loop_step(12); // 8012 + let const_1 = vid::loop_step(13); // 8013 + let new_prefix = vid::loop_step(14); // 8014 // cmp_result = (i >= n) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_result, - op: CompareOp::Ge, - lhs: i_loop, - rhs: n_loop, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); // Phase 27.13: Exit φ の意味を LoopExitShape で明示 // UsingResolver entries ループ脱出時は prefix の値を返す(最終的な連結文字列) - let _exit_shape = LoopExitShape::new_manual(vec![prefix_loop]); // exit_args = [prefix] + let _exit_shape = LoopExitShape::new_manual(vec![prefix_loop]); // exit_args = [prefix] // if i >= n { return prefix } loop_step_func.body.push(JoinInst::Jump { cont: JoinContId::new(0), - args: vec![prefix_loop], // ← LoopExitShape.exit_args に対応 + args: vec![prefix_loop], // ← LoopExitShape.exit_args に対応 cond: Some(cmp_result), }); // entry = entries.get(i) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(entry_value), - box_name: "ArrayBox".to_string(), - method: "get".to_string(), - args: vec![entries_loop, i_loop], - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(entry_value), + box_name: "ArrayBox".to_string(), + method: "get".to_string(), + args: vec![entries_loop, i_loop], + })); // const_1 = 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_1, - value: ConstValue::Integer(1), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: const_1, + value: ConstValue::Integer(1), + })); // next_i = i + 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: next_i, - op: BinOpKind::Add, - lhs: i_loop, - rhs: const_1, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_1, + })); // 簡略化: 文字列連結のみ(実際の should_emit, path 解決等は省略) // new_prefix = prefix + entry (実際は "\n" + code + "\n" だが、ここでは簡略化) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: new_prefix, - op: BinOpKind::Or, // String concatenation uses Or in JoinIR - lhs: prefix_loop, - rhs: entry_value, - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: new_prefix, + op: BinOpKind::Or, // String concatenation uses Or in JoinIR + lhs: prefix_loop, + rhs: entry_value, + })); // loop_step(entries, n, modules, seen, new_prefix, next_i) - tail recursion loop_step_func.body.push(JoinInst::Call { func: loop_step_id, - args: vec![entries_loop, n_loop, modules_loop, seen_loop, new_prefix, next_i], + args: vec![ + entries_loop, + n_loop, + modules_loop, + seen_loop, + new_prefix, + next_i, + ], k_next: None, dst: None, }); @@ -229,7 +276,10 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option< join_module.add_function(loop_step_func); eprintln!("[joinir/stage1_using_resolver/build] ✅ JoinIR construction completed"); - eprintln!("[joinir/stage1_using_resolver/build] Functions: {}", join_module.functions.len()); + eprintln!( + "[joinir/stage1_using_resolver/build] Functions: {}", + join_module.functions.len() + ); Some(join_module) } @@ -253,10 +303,17 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { eprintln!("[joinir/stage1_using_resolver/mir] Starting MIR-based lowering"); // Step 1: Stage1UsingResolverBox.resolve_for_source/5 を探す - let target_func = module.functions.get("Stage1UsingResolverBox.resolve_for_source/5")?; + let target_func = module + .functions + .get("Stage1UsingResolverBox.resolve_for_source/5")?; - eprintln!("[joinir/stage1_using_resolver/mir] Found Stage1UsingResolverBox.resolve_for_source/5"); - eprintln!("[joinir/stage1_using_resolver/mir] MIR blocks: {}", target_func.blocks.len()); + eprintln!( + "[joinir/stage1_using_resolver/mir] Found Stage1UsingResolverBox.resolve_for_source/5" + ); + eprintln!( + "[joinir/stage1_using_resolver/mir] MIR blocks: {}", + target_func.blocks.len() + ); // Step 2: MirQueryBox を作成 let query = MirQueryBox::new(target_func); @@ -287,6 +344,47 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { eprintln!("[joinir/stage1_using_resolver/mir] CFG sanity checks passed ✅"); + if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") { + let header = query.succs(entry).get(0).copied().unwrap_or(entry); + let succs_header = query.succs(header); + let body = succs_header.get(0).copied().unwrap_or(header); + let exit = succs_header.get(1).copied().unwrap_or(header); + let loop_form = LoopForm { + preheader: entry, + header, + body, + latch: body, + exit, + continue_targets: vec![body], + break_targets: vec![exit], + }; + if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) { + eprintln!( + "[joinir/stage1_using_resolver/generic-hook] simple Case A loop detected (minimal target, generic_case_a hook placeholder)" + ); + let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new(); + let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new(); + let params_len = target_func.params.len(); + if params_len == 5 { + if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal( + &loop_form, + &var_classes, + &exit_live, + &query, + target_func, + ) { + eprintln!( + "[joinir/stage1_using_resolver/generic-hook] generic_case_a produced JoinIR, returning early" + ); + return Some(jm); + } + } + eprintln!( + "[joinir/stage1_using_resolver/generic-hook] generic_case_a returned None or params mismatch, falling back to handwritten/MIR path" + ); + } + } + // Phase 27.12: Generate JoinIR using shared builder // CFG checks passed, so we can use build_stage1_using_resolver_joinir() directly eprintln!("[joinir/stage1_using_resolver/mir] Calling build_stage1_using_resolver_joinir() after CFG validation"); diff --git a/src/mir/join_ir/lowering/stageb_body.rs b/src/mir/join_ir/lowering/stageb_body.rs new file mode 100644 index 00000000..adb1db2d --- /dev/null +++ b/src/mir/join_ir/lowering/stageb_body.rs @@ -0,0 +1,202 @@ +//! Phase 28: StageBBodyExtractorBox.build_body_src ループの JoinIR lowering +//! +//! 対象: `lang/src/compiler/entry/compiler_stageb.hako` の +//! `StageBBodyExtractorBox.build_body_src/2` の一次元スキャンループ(Case A)。 +//! - Pinned: src, args, n +//! - Carrier: acc (body_len 的なカウンタ), i +//! - Exit: acc + +use crate::mir::join_ir::lowering::common::{ + dispatch_lowering, ensure_entry_has_succs, log_fallback, +}; +use crate::mir::join_ir::lowering::value_id_ranges::stageb_body_extract as vid; +use crate::mir::join_ir::JoinModule; +use crate::mir::loop_form::LoopForm; +use crate::mir::query::{MirQuery, MirQueryBox}; + +/// Public dispatcher (MIR-based vs handwritten) +pub fn lower_stageb_body_to_joinir(module: &crate::mir::MirModule) -> Option { + dispatch_lowering("stageb_body", module, lower_from_mir, lower_handwritten) +} + +/// 共通の JoinIR 構築(MIR/handwritten 共通) +fn build_stageb_body_joinir(module: &crate::mir::MirModule) -> Option { + use crate::mir::join_ir::*; + + // ターゲット関数が無ければ None でフォールバック + let _target = module + .functions + .get("StageBBodyExtractorBox.build_body_src/2")?; + + eprintln!("[joinir/stageb_body/build] Generating JoinIR for build_body_src"); + eprintln!("[joinir/stageb_body/build] Using ValueId range: 11000-12999"); + + let mut join_module = JoinModule::new(); + + // Entry: build_body_src(src, args) + let entry_id = JoinFuncId::new(0); + let src_param = vid::entry(0); // 11000 + let args_param = vid::entry(1); // 11001 + let n_val = vid::entry(2); // 11002 + let i_init = vid::entry(3); // 11003 + let acc_init = vid::entry(4); // 11004 + let loop_step_id = JoinFuncId::new(1); + + let mut entry_func = JoinFunction::new( + entry_id, + "build_body_src".to_string(), + vec![src_param, args_param], + ); + + // n = src.length() + entry_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_val), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![src_param], + })); + + // i_init = 0 + entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); + + // acc_init = 0 + entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: acc_init, + value: ConstValue::Integer(0), + })); + + // loop_step(src, args, n, acc_init, i_init) + entry_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![src_param, args_param, n_val, acc_init, i_init], + k_next: None, + dst: None, + }); + + join_module.entry = Some(entry_id); + join_module.add_function(entry_func); + + // Loop step: loop_step(src, args, n, acc, i) + let src_loop = vid::loop_step(0); // 12000 (Pinned) + let args_loop = vid::loop_step(1); // 12001 (Pinned) + let n_loop = vid::loop_step(2); // 12002 (Pinned) + let acc_loop = vid::loop_step(3); // 12003 (Carrier) + let i_loop = vid::loop_step(4); // 12004 (Carrier) + + let _header_shape = LoopHeaderShape::new_manual( + vec![src_loop, args_loop, n_loop], // Pinned + vec![acc_loop, i_loop], // Carrier + ); + + let mut loop_func = JoinFunction::new( + loop_step_id, + "loop_step".to_string(), + vec![src_loop, args_loop, n_loop, acc_loop, i_loop], + ); + + let cmp_exit = vid::loop_step(10); // 12010 + let const_one = vid::loop_step(11); // 12011 + let next_i = vid::loop_step(12); // 12012 + let next_acc = vid::loop_step(13); // 12013 + + // cmp_exit = (i >= n) + loop_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_exit, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); + + // Exit shape: acc をそのまま返す + let _exit_shape = LoopExitShape::new_manual(vec![acc_loop]); + + // if i >= n { return acc } + loop_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: vec![acc_loop], + cond: Some(cmp_exit), + }); + + // const_one = 1 + loop_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_one, + value: ConstValue::Integer(1), + })); + + // next_i = i + 1 + loop_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_one, + })); + + // next_acc = acc + 1 (body 長カウントの簡略表現) + loop_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_acc, + op: BinOpKind::Add, + lhs: acc_loop, + rhs: const_one, + })); + + // loop_step(..., next_acc, next_i) + loop_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![src_loop, args_loop, n_loop, next_acc, next_i], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_func); + + eprintln!("[joinir/stageb_body/build] ✅ JoinIR construction completed"); + Some(join_module) +} + +/// MIR ベースの軽量パターンチェック(最低限) +fn lower_from_mir(module: &crate::mir::MirModule) -> Option { + eprintln!("[joinir/stageb_body/mir] Starting MIR-based lowering"); + + let target_func = module + .functions + .get("StageBBodyExtractorBox.build_body_src/2")?; + + let query = MirQueryBox::new(target_func); + let entry = target_func.entry_block; + if !ensure_entry_has_succs(&query, entry) { + log_fallback("stageb_body", "entry has no successors"); + return lower_handwritten(module); + } + + if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") { + let header = query.succs(entry).get(0).copied().unwrap_or(entry); + let succs_header = query.succs(header); + let body = succs_header.get(0).copied().unwrap_or(header); + let exit = succs_header.get(1).copied().unwrap_or(header); + let loop_form = LoopForm { + preheader: entry, + header, + body, + latch: body, + exit, + continue_targets: vec![body], + break_targets: vec![exit], + }; + if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) { + eprintln!("[joinir/stageb_body/generic-hook] simple Case A loop detected (generic_case_a hook placeholder)"); + } + } + + build_stageb_body_joinir(module) +} + +/// 手書き版(MIR 形状に依存しない) +fn lower_handwritten(module: &crate::mir::MirModule) -> Option { + eprintln!("[joinir/stageb_body/hand] Using handwritten lowering"); + build_stageb_body_joinir(module) +} diff --git a/src/mir/join_ir/lowering/stageb_funcscanner.rs b/src/mir/join_ir/lowering/stageb_funcscanner.rs new file mode 100644 index 00000000..5382af65 --- /dev/null +++ b/src/mir/join_ir/lowering/stageb_funcscanner.rs @@ -0,0 +1,192 @@ +//! Phase 28: StageBFuncScannerBox.scan_all_boxes ループの JoinIR lowering +//! +//! 対象: `lang/src/compiler/entry/compiler_stageb.hako` の +//! `StageBFuncScannerBox.scan_all_boxes/1`(Case A の一次元スキャン)。 +//! - Pinned: src, n +//! - Carrier: defs(累積カウント的なプレースホルダ), i +//! - Exit: defs + +use crate::mir::join_ir::lowering::common::{ + dispatch_lowering, ensure_entry_has_succs, log_fallback, +}; +use crate::mir::join_ir::lowering::value_id_ranges::stageb_funcscanner as vid; +use crate::mir::join_ir::JoinModule; +use crate::mir::loop_form::LoopForm; +use crate::mir::query::{MirQuery, MirQueryBox}; + +/// Public dispatcher (MIR-based vs handwritten) +pub fn lower_stageb_funcscanner_to_joinir(module: &crate::mir::MirModule) -> Option { + dispatch_lowering( + "stageb_funcscanner", + module, + lower_from_mir, + lower_handwritten, + ) +} + +/// 共通の JoinIR 構築(MIR/handwritten 共通) +fn build_stageb_funcscanner_joinir(module: &crate::mir::MirModule) -> Option { + use crate::mir::join_ir::*; + + // ターゲット関数が無ければ None でフォールバック + let _target = module + .functions + .get("StageBFuncScannerBox.scan_all_boxes/1")?; + + eprintln!("[joinir/stageb_funcscanner/build] Generating JoinIR for scan_all_boxes"); + eprintln!("[joinir/stageb_funcscanner/build] Using ValueId range: 13000-14999"); + + let mut join_module = JoinModule::new(); + + // Entry: scan_all_boxes(src) + let entry_id = JoinFuncId::new(0); + let src_param = vid::entry(0); // 13000 + let n_val = vid::entry(1); // 13001 + let defs_init = vid::entry(2); // 13002 + let i_init = vid::entry(3); // 13003 + let loop_step_id = JoinFuncId::new(1); + + let mut entry_func = JoinFunction::new(entry_id, "scan_all_boxes".to_string(), vec![src_param]); + + // n = src.length() + entry_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_val), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![src_param], + })); + + // defs_init = 0 (プレースホルダ: defs 配列長) + entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: defs_init, + value: ConstValue::Integer(0), + })); + + // i_init = 0 + entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: i_init, + value: ConstValue::Integer(0), + })); + + // loop_step(src, n, defs_init, i_init) + entry_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![src_param, n_val, defs_init, i_init], + k_next: None, + dst: None, + }); + + join_module.entry = Some(entry_id); + join_module.add_function(entry_func); + + // Loop step: loop_step(src, n, defs, i) + let src_loop = vid::loop_step(0); // 14000 (Pinned) + let n_loop = vid::loop_step(1); // 14001 (Pinned) + let defs_loop = vid::loop_step(2); // 14002 (Carrier) + let i_loop = vid::loop_step(3); // 14003 (Carrier) + + let _header_shape = LoopHeaderShape::new_manual( + vec![src_loop, n_loop], // Pinned + vec![defs_loop, i_loop], // Carrier + ); + + let mut loop_func = JoinFunction::new( + loop_step_id, + "loop_step".to_string(), + vec![src_loop, n_loop, defs_loop, i_loop], + ); + + let cmp_exit = vid::loop_step(10); // 14010 + let const_one = vid::loop_step(11); // 14011 + let next_i = vid::loop_step(12); // 14012 + + // cmp_exit = (i >= n) + loop_func.body.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_exit, + op: CompareOp::Ge, + lhs: i_loop, + rhs: n_loop, + })); + + // Exit shape: defs を返す + let _exit_shape = LoopExitShape::new_manual(vec![defs_loop]); + + // if i >= n { return defs } + loop_func.body.push(JoinInst::Jump { + cont: JoinContId::new(0), + args: vec![defs_loop], + cond: Some(cmp_exit), + }); + + // const_one = 1 + loop_func.body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_one, + value: ConstValue::Integer(1), + })); + + // next_i = i + 1 + loop_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: next_i, + op: BinOpKind::Add, + lhs: i_loop, + rhs: const_one, + })); + + // loop_step(..., defs_loop, next_i) + loop_func.body.push(JoinInst::Call { + func: loop_step_id, + args: vec![src_loop, n_loop, defs_loop, next_i], + k_next: None, + dst: None, + }); + + join_module.add_function(loop_func); + + eprintln!("[joinir/stageb_funcscanner/build] ✅ JoinIR construction completed"); + Some(join_module) +} + +/// MIR ベースの軽量パターンチェック(最低限) +fn lower_from_mir(module: &crate::mir::MirModule) -> Option { + eprintln!("[joinir/stageb_funcscanner/mir] Starting MIR-based lowering"); + + let target_func = module + .functions + .get("StageBFuncScannerBox.scan_all_boxes/1")?; + + let query = MirQueryBox::new(target_func); + let entry = target_func.entry_block; + if !ensure_entry_has_succs(&query, entry) { + log_fallback("stageb_funcscanner", "entry has no successors"); + return lower_handwritten(module); + } + + if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") { + let header = query.succs(entry).get(0).copied().unwrap_or(entry); + let succs_header = query.succs(header); + let body = succs_header.get(0).copied().unwrap_or(header); + let exit = succs_header.get(1).copied().unwrap_or(header); + let loop_form = LoopForm { + preheader: entry, + header, + body, + latch: body, + exit, + continue_targets: vec![body], + break_targets: vec![exit], + }; + if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) { + eprintln!("[joinir/stageb_funcscanner/generic-hook] simple Case A loop detected (generic_case_a hook placeholder)"); + } + } + + build_stageb_funcscanner_joinir(module) +} + +/// 手書き版(MIR 形状に依存しない) +fn lower_handwritten(module: &crate::mir::MirModule) -> Option { + eprintln!("[joinir/stageb_funcscanner/hand] Using handwritten lowering"); + build_stageb_funcscanner_joinir(module) +} diff --git a/src/mir/join_ir/lowering/value_id_ranges.rs b/src/mir/join_ir/lowering/value_id_ranges.rs index 0c3a0804..6b78820a 100644 --- a/src/mir/join_ir/lowering/value_id_ranges.rs +++ b/src/mir/join_ir/lowering/value_id_ranges.rs @@ -12,6 +12,8 @@ //! | funcscanner_trim | 5000-6999 | 5000+ | 6000+ | Trim whitespace | //! | stage1_using_resolver | 7000-8999 | 7000+ | 8000+ | Stage-1 using resolver | //! | funcscanner_append_defs | 9000-10999 | 9000+ | 10000+ | FuncScanner append defs | +//! | stageb_body_extract | 11000-12999| 11000+ | 12000+ | Stage-B body extractor | +//! | stageb_funcscanner | 13000-14999| 13000+ | 14000+ | Stage-B FuncScanner (scan_all_boxes) | //! //! ## Usage Example //! @@ -50,6 +52,12 @@ pub mod base { /// funcscanner_append_defs: FuncScanner append defs loop (9000-10999) pub const FUNCSCANNER_APPEND_DEFS: u32 = 9000; + + /// stageb_body_extract: Stage-B body extractor loop (11000-12999) + pub const STAGEB_BODY_EXTRACT: u32 = 11000; + + /// stageb_funcscanner: Stage-B FuncScanner scan_all_boxes loop (13000-14999) + pub const STAGEB_FUNCSCANNER: u32 = 13000; } /// Helper function to create ValueId from base + offset @@ -150,6 +158,42 @@ pub mod funcscanner_append_defs { } } +/// ValueId helpers for Stage-B body extractor lowering module +pub mod stageb_body_extract { + use super::{base, id}; + use crate::mir::ValueId; + + /// Entry function ValueIds (11000-11999) + #[inline] + pub const fn entry(offset: u32) -> ValueId { + id(base::STAGEB_BODY_EXTRACT, offset) + } + + /// Loop function ValueIds (12000-12999) + #[inline] + pub const fn loop_step(offset: u32) -> ValueId { + id(base::STAGEB_BODY_EXTRACT, 1000 + offset) + } +} + +/// ValueId helpers for Stage-B FuncScanner lowering module +pub mod stageb_funcscanner { + use super::{base, id}; + use crate::mir::ValueId; + + /// Entry function ValueIds (13000-13999) + #[inline] + pub const fn entry(offset: u32) -> ValueId { + id(base::STAGEB_FUNCSCANNER, offset) + } + + /// Loop function ValueIds (14000-14999) + #[inline] + pub const fn loop_step(offset: u32) -> ValueId { + id(base::STAGEB_FUNCSCANNER, 1000 + offset) + } +} + #[cfg(test)] mod tests { use super::*; @@ -160,10 +204,20 @@ mod tests { /// based on the module's allocated range. macro_rules! test_value_id_range { ($module:ident, $entry_base:expr, $loop_base:expr) => { - assert_eq!($module::entry(0).0, $entry_base, - "{} entry(0) should be {}", stringify!($module), $entry_base); - assert_eq!($module::loop_step(999).0, $loop_base + 999, - "{} loop_step(999) should be {}", stringify!($module), $loop_base + 999); + assert_eq!( + $module::entry(0).0, + $entry_base, + "{} entry(0) should be {}", + stringify!($module), + $entry_base + ); + assert_eq!( + $module::loop_step(999).0, + $loop_base + 999, + "{} loop_step(999) should be {}", + stringify!($module), + $loop_base + 999 + ); }; } @@ -175,6 +229,8 @@ mod tests { test_value_id_range!(funcscanner_trim, 5000, 6000); test_value_id_range!(stage1_using_resolver, 7000, 8000); test_value_id_range!(funcscanner_append_defs, 9000, 10000); + test_value_id_range!(stageb_body_extract, 11000, 12000); + test_value_id_range!(stageb_funcscanner, 13000, 14000); // Automated overlap detection // Each range is 2000 units: (base, base+1999) @@ -184,15 +240,22 @@ mod tests { (5000, 6999), // funcscanner_trim (7000, 8999), // stage1_using_resolver (9000, 10999), // funcscanner_append_defs + (11000, 12999), // stageb_body_extract + (13000, 14999), // stageb_funcscanner ]; // Verify no overlaps between consecutive ranges for i in 0..ranges.len() - 1 { let (_, end_i) = ranges[i]; let (start_next, _) = ranges[i + 1]; - assert!(end_i < start_next, - "Overlap detected: range {} ends at {} but range {} starts at {}", - i, end_i, i + 1, start_next); + assert!( + end_i < start_next, + "Overlap detected: range {} ends at {} but range {} starts at {}", + i, + end_i, + i + 1, + start_next + ); } } } diff --git a/src/mir/join_ir/mod.rs b/src/mir/join_ir/mod.rs index c8d5bb6b..9c8e63a3 100644 --- a/src/mir/join_ir/mod.rs +++ b/src/mir/join_ir/mod.rs @@ -174,9 +174,7 @@ pub enum JoinInst { }, /// ルート関数 or 上位への戻り - Ret { - value: Option, - }, + Ret { value: Option }, /// それ以外の演算は、現行 MIR の算術/比較/boxcall を再利用 Compute(MirLikeInst), @@ -186,10 +184,7 @@ pub enum JoinInst { #[derive(Debug, Clone)] pub enum MirLikeInst { /// 定数代入 - Const { - dst: VarId, - value: ConstValue, - }, + Const { dst: VarId, value: ConstValue }, /// 二項演算 BinOp { @@ -283,7 +278,11 @@ mod tests { #[test] fn test_join_function_creation() { let func_id = JoinFuncId::new(0); - let func = JoinFunction::new(func_id, "test_func".to_string(), vec![ValueId(1), ValueId(2)]); + let func = JoinFunction::new( + func_id, + "test_func".to_string(), + vec![ValueId(1), ValueId(2)], + ); assert_eq!(func.id, func_id); assert_eq!(func.name, "test_func"); diff --git a/src/mir/join_ir_ops.rs b/src/mir/join_ir_ops.rs index 0c53b580..f899700e 100644 --- a/src/mir/join_ir_ops.rs +++ b/src/mir/join_ir_ops.rs @@ -74,9 +74,7 @@ pub fn eval_binop( match op { BinOpKind::Add => match (lhs, rhs) { (JoinValue::Int(a), JoinValue::Int(b)) => Ok(JoinValue::Int(a + b)), - (JoinValue::Str(a), JoinValue::Str(b)) => { - Ok(JoinValue::Str(format!("{}{}", a, b))) - } + (JoinValue::Str(a), JoinValue::Str(b)) => Ok(JoinValue::Str(format!("{}{}", a, b))), _ => Err(JoinIrOpError::new( "Add supported only for Int+Int or Str+Str", )), @@ -90,9 +88,7 @@ pub fn eval_binop( _ => Err(JoinIrOpError::new("Mul supported only for Int*Int")), }, BinOpKind::Div => match (lhs, rhs) { - (JoinValue::Int(_), JoinValue::Int(0)) => { - Err(JoinIrOpError::new("Division by zero")) - } + (JoinValue::Int(_), JoinValue::Int(0)) => Err(JoinIrOpError::new("Division by zero")), (JoinValue::Int(a), JoinValue::Int(b)) => Ok(JoinValue::Int(a / b)), _ => Err(JoinIrOpError::new("Div supported only for Int/Int")), }, @@ -137,16 +133,12 @@ pub fn eval_compare( (JoinValue::Bool(a), JoinValue::Bool(b)) => match op { CompareOp::Eq => Ok(JoinValue::Bool(a == b)), CompareOp::Ne => Ok(JoinValue::Bool(a != b)), - _ => Err(JoinIrOpError::new( - "Bool comparison only supports Eq/Ne", - )), + _ => Err(JoinIrOpError::new("Bool comparison only supports Eq/Ne")), }, (JoinValue::Str(a), JoinValue::Str(b)) => match op { CompareOp::Eq => Ok(JoinValue::Bool(a == b)), CompareOp::Ne => Ok(JoinValue::Bool(a != b)), - _ => Err(JoinIrOpError::new( - "String comparison only supports Eq/Ne", - )), + _ => Err(JoinIrOpError::new("String comparison only supports Eq/Ne")), }, _ => Err(JoinIrOpError::new( "Type mismatch in Compare (expected homogeneous operands)", @@ -160,11 +152,7 @@ mod tests { #[test] fn test_eval_binop_add_int() { - let result = eval_binop( - BinOpKind::Add, - &JoinValue::Int(3), - &JoinValue::Int(5), - ); + let result = eval_binop(BinOpKind::Add, &JoinValue::Int(3), &JoinValue::Int(5)); assert_eq!(result.unwrap(), JoinValue::Int(8)); } @@ -180,21 +168,13 @@ mod tests { #[test] fn test_eval_binop_sub() { - let result = eval_binop( - BinOpKind::Sub, - &JoinValue::Int(10), - &JoinValue::Int(3), - ); + let result = eval_binop(BinOpKind::Sub, &JoinValue::Int(10), &JoinValue::Int(3)); assert_eq!(result.unwrap(), JoinValue::Int(7)); } #[test] fn test_eval_binop_div_by_zero() { - let result = eval_binop( - BinOpKind::Div, - &JoinValue::Int(10), - &JoinValue::Int(0), - ); + let result = eval_binop(BinOpKind::Div, &JoinValue::Int(10), &JoinValue::Int(0)); assert!(result.is_err()); assert_eq!(result.unwrap_err().message, "Division by zero"); } @@ -221,21 +201,13 @@ mod tests { #[test] fn test_eval_compare_int_gt() { - let result = eval_compare( - CompareOp::Gt, - &JoinValue::Int(10), - &JoinValue::Int(5), - ); + let result = eval_compare(CompareOp::Gt, &JoinValue::Int(10), &JoinValue::Int(5)); assert_eq!(result.unwrap(), JoinValue::Bool(true)); } #[test] fn test_eval_compare_int_eq() { - let result = eval_compare( - CompareOp::Eq, - &JoinValue::Int(5), - &JoinValue::Int(5), - ); + let result = eval_compare(CompareOp::Eq, &JoinValue::Int(5), &JoinValue::Int(5)); assert_eq!(result.unwrap(), JoinValue::Bool(true)); } @@ -277,16 +249,15 @@ mod tests { &JoinValue::Bool(false), ); assert!(result.is_err()); - assert_eq!(result.unwrap_err().message, "Bool comparison only supports Eq/Ne"); + assert_eq!( + result.unwrap_err().message, + "Bool comparison only supports Eq/Ne" + ); } #[test] fn test_eval_compare_type_mismatch() { - let result = eval_compare( - CompareOp::Eq, - &JoinValue::Int(5), - &JoinValue::Bool(true), - ); + let result = eval_compare(CompareOp::Eq, &JoinValue::Int(5), &JoinValue::Bool(true)); assert!(result.is_err()); assert!(result.unwrap_err().message.contains("Type mismatch")); } diff --git a/src/mir/join_ir_runner.rs b/src/mir/join_ir_runner.rs index d40e745c..f3b30aae 100644 --- a/src/mir/join_ir_runner.rs +++ b/src/mir/join_ir_runner.rs @@ -11,9 +11,7 @@ use std::collections::HashMap; -use crate::mir::join_ir::{ - ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId, -}; +use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId}; // Phase 27.8: ops box からの再エクスポート pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue}; @@ -37,10 +35,9 @@ fn execute_function( mut current_args: Vec, ) -> Result { 'exec: loop { - let func = module - .functions - .get(¤t_func) - .ok_or_else(|| JoinRuntimeError::new(format!("Function {:?} not found", current_func)))?; + let func = module.functions.get(¤t_func).ok_or_else(|| { + JoinRuntimeError::new(format!("Function {:?} not found", current_func)) + })?; if func.params.len() != current_args.len() { return Err(JoinRuntimeError::new(format!( @@ -85,7 +82,11 @@ fn execute_function( continue 'exec; } } - JoinInst::Jump { cont: _, args, cond } => { + JoinInst::Jump { + cont: _, + args, + cond, + } => { let should_jump = match cond { Some(var) => as_bool(&read_var(&locals, *var)?)?, None => true, @@ -155,7 +156,7 @@ fn eval_compute( // * Method re-routing (toString→str) MirLikeInst::BoxCall { dst, - box_name: _, // box_name は VM が内部で判定するため不要 + box_name: _, // box_name は VM が内部で判定するため不要 method, args, } => { @@ -177,7 +178,8 @@ fn eval_compute( .collect::, _>>()?; // Invoke VM's execute_box_call for complete semantics - let result_vm = vm.execute_box_call(receiver_vm, method, method_args_vm) + let result_vm = vm + .execute_box_call(receiver_vm, method, method_args_vm) .map_err(|e| JoinRuntimeError::new(format!("BoxCall failed: {}", e)))?; // Convert result back to JoinValue @@ -217,4 +219,3 @@ fn as_bool(value: &JoinValue) -> Result { ))), } } - diff --git a/src/mir/join_ir_vm_bridge.rs b/src/mir/join_ir_vm_bridge.rs index 56163876..b6759427 100644 --- a/src/mir/join_ir_vm_bridge.rs +++ b/src/mir/join_ir_vm_bridge.rs @@ -28,8 +28,8 @@ use crate::mir::join_ir::{ }; use crate::mir::join_ir_ops::JoinValue; use crate::mir::{ - BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, - EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, + BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask, + FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, }; use std::collections::HashMap; @@ -80,9 +80,7 @@ pub fn run_joinir_via_vm( args: &[JoinValue], ) -> Result { eprintln!("[joinir_vm_bridge] Phase 27-shortterm S-4.3"); - eprintln!( - "[joinir_vm_bridge] Converting JoinIR to MIR for VM execution" - ); + eprintln!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution"); // Step 1: JoinIR → MIR 変換 let mir_module = convert_joinir_to_mir(join_module, entry_func)?; @@ -158,7 +156,9 @@ fn convert_join_function_to_mir( // Create minimal FunctionSignature for JoinIR function // Phase 27-shortterm: すべて MirType::Unknown として扱う(型情報は JoinIR に無いため) - let param_types = join_func.params.iter() + let param_types = join_func + .params + .iter() .map(|_| MirType::Unknown) .collect::>(); @@ -178,7 +178,7 @@ fn convert_join_function_to_mir( // - On Call: emit Call in current block let mut current_block_id = entry_block; let mut current_instructions = Vec::new(); - let mut next_block_id = 1u32; // for creating new blocks + let mut next_block_id = 1u32; // for creating new blocks for join_inst in &join_func.body { match join_inst { @@ -186,7 +186,12 @@ fn convert_join_function_to_mir( let mir_inst = convert_mir_like_inst(mir_like)?; current_instructions.push(mir_inst); } - JoinInst::Call { func, args, dst, k_next } => { + JoinInst::Call { + func, + args, + dst, + k_next, + } => { // Phase 27-shortterm S-4.4-A: skip_ws pattern only (tail calls) // Validate: dst=None, k_next=None (tail call) if dst.is_some() || k_next.is_some() { @@ -204,7 +209,11 @@ fn convert_join_function_to_mir( format!("join_func_{}", func.0) }; - eprintln!("[joinir_vm_bridge] Converting Call to function '{}' with {} args", func_name, args.len()); + eprintln!( + "[joinir_vm_bridge] Converting Call to function '{}' with {} args", + func_name, + args.len() + ); // Create a temporary ValueId to hold the function name (string constant) // This is a workaround for MIR's string-based Call resolution @@ -220,9 +229,9 @@ fn convert_join_function_to_mir( // Create Call instruction (legacy string-based) // Phase 27-shortterm: No Callee yet, use func field with string ValueId current_instructions.push(MirInstruction::Call { - dst: None, // tail call, no return value captured + dst: None, // tail call, no return value captured func: func_name_id, - callee: None, // Phase 27-shortterm: no Callee resolution + callee: None, // Phase 27-shortterm: no Callee resolution args: args.clone(), effects: EffectMask::PURE, }); @@ -231,7 +240,10 @@ fn convert_join_function_to_mir( // Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return // Jump represents an exit continuation (k_exit) in skip_ws pattern - eprintln!("[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}", cont, args, cond); + eprintln!( + "[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}", + cont, args, cond + ); match cond { Some(cond_var) => { @@ -256,9 +268,9 @@ fn convert_join_function_to_mir( // Create exit block with Return let exit_value = args.first().copied(); let mut exit_block = crate::mir::BasicBlock::new(exit_block_id); - exit_block.instructions.push(MirInstruction::Return { - value: exit_value, - }); + exit_block + .instructions + .push(MirInstruction::Return { value: exit_value }); mir_func.blocks.insert(exit_block_id, exit_block); // Create continue block (will be populated by subsequent instructions) @@ -272,9 +284,7 @@ fn convert_join_function_to_mir( None => { // Unconditional jump: direct Return let exit_value = args.first().copied(); - current_instructions.push(MirInstruction::Return { - value: exit_value, - }); + current_instructions.push(MirInstruction::Return { value: exit_value }); // Finalize current block if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) { @@ -287,9 +297,7 @@ fn convert_join_function_to_mir( } } JoinInst::Ret { value } => { - current_instructions.push(MirInstruction::Return { - value: *value, - }); + current_instructions.push(MirInstruction::Return { value: *value }); // Finalize current block if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) { @@ -312,9 +320,7 @@ fn convert_join_function_to_mir( } /// MirLikeInst → MirInstruction 変換 -fn convert_mir_like_inst( - mir_like: &MirLikeInst, -) -> Result { +fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result { match mir_like { MirLikeInst::Const { dst, value } => { let mir_const = match value { diff --git a/src/mir/loop_api.rs b/src/mir/loop_api.rs index e53d26a1..c45892d0 100644 --- a/src/mir/loop_api.rs +++ b/src/mir/loop_api.rs @@ -139,6 +139,8 @@ impl LoopBuilderApi for super::builder::MirBuilder { // Update effect mask and insert at the very start bb.effects = bb.effects | inst.effects(); bb.instructions.insert(0, inst); + bb.instruction_spans + .insert(0, self.current_span); Ok(()) } else { Err(format!("Block {} not found", block.as_u32())) diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 17301daa..4b9f70ef 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -811,6 +811,7 @@ impl<'a> LoopBuilder<'a> { // Phi命令は必ずブロックの先頭に配置。ただし同一dstの既存PHIがある場合は差し替える。 let mut replaced = false; let mut idx = 0; + let span = self.parent_builder.current_span; while idx < block.instructions.len() { match &mut block.instructions[idx] { MirInstruction::Phi { @@ -818,6 +819,13 @@ impl<'a> LoopBuilder<'a> { inputs: ins, } if *d == dst => { *ins = inputs.clone(); + if block.instruction_spans.len() <= idx { + // Backfill missing spans to preserve alignment after legacy inserts. + while block.instruction_spans.len() <= idx { + block.instruction_spans.push(crate::ast::Span::unknown()); + } + } + block.instruction_spans[idx] = span; replaced = true; break; } @@ -833,6 +841,7 @@ impl<'a> LoopBuilder<'a> { inputs: inputs.clone(), }; block.instructions.insert(0, phi_inst); + block.instruction_spans.insert(0, span); } if dbg { eprintln!("[DEBUG] ✅ PHI instruction inserted at position 0"); @@ -895,7 +904,8 @@ impl<'a> LoopBuilder<'a> { // ============================================================= // Variable Map Utilities — snapshots and rebinding // ============================================================= - fn get_current_variable_map(&self) -> BTreeMap { // Phase 25.1: BTreeMap化 + fn get_current_variable_map(&self) -> BTreeMap { + // Phase 25.1: BTreeMap化 self.parent_builder.variable_map.clone() } @@ -921,6 +931,8 @@ impl<'a> LoopBuilder<'a> { } fn build_statement(&mut self, stmt: ASTNode) -> Result { + // Preserve the originating span for loop-local control instructions (break/continue/phi). + self.parent_builder.current_span = stmt.span(); match stmt { // Ensure nested bare blocks inside loops are lowered with loop-aware semantics ASTNode::Program { statements, .. } => { @@ -1210,15 +1222,15 @@ impl<'a> LoopBuilder<'a> { // Phase 26-F-3: ループ内if-mergeコンテキスト設定(ChatGPT設計) // pre_if_var_mapにある全変数をcarrier候補として扱う(保守的だが安全) // 理由: ループ内変数は全てキャリア候補の可能性があるため - let carrier_names: std::collections::BTreeSet = - pre_if_var_map.keys() - .filter(|name| !name.starts_with("__pin$")) // 一時変数除外 - .cloned() - .collect(); + let carrier_names: std::collections::BTreeSet = pre_if_var_map + .keys() + .filter(|name| !name.starts_with("__pin$")) // 一時変数除外 + .cloned() + .collect(); phi_builder.set_if_context( - true, // in_loop_body = true - carrier_names + true, // in_loop_body = true + carrier_names, ); // Phase 26-F-2: IfBodyLocalMergeBox は phi_builder_box.rs 内で直接使用 diff --git a/src/mir/loop_form.rs b/src/mir/loop_form.rs new file mode 100644 index 00000000..3e846268 --- /dev/null +++ b/src/mir/loop_form.rs @@ -0,0 +1,6 @@ +//! LoopForm helper alias +//! +//! Alias to ControlForm::LoopShape so generic JoinIR lowering can depend on a +//! stable loop shape representation without pulling in builder details. + +pub type LoopForm = crate::mir::control_form::LoopShape; diff --git a/src/mir/mod.rs b/src/mir/mod.rs index b34e3dcb..59bd9a09 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -12,12 +12,12 @@ pub mod builder; pub mod definitions; // Unified MIR definitions (MirCall, Callee, etc.) pub mod effect; pub mod function; -pub mod naming; // Static box / entry naming rules(NamingBox) pub mod instruction; pub mod instruction_introspection; // Introspection helpers for tests (instruction names) pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp) pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready) pub mod loop_builder; // SSA loop construction with phi nodes +pub mod naming; // Static box / entry naming rules(NamingBox) pub mod optimizer; pub mod ssot; // Shared helpers (SSOT) for instruction lowering pub mod types; // core MIR enums (ConstValue, Ops, MirType) @@ -26,21 +26,23 @@ pub mod utils; // Phase 15 control flow utilities for root treatment pub mod control_form; pub mod function_emission; // FunctionEmissionBox(MirFunction直編集の発行ヘルパ) pub mod hints; // scaffold: zero-cost guidance (no-op) +pub mod join_ir; // Phase 26-H: 関数正規化IR(JoinIR) +pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box) +pub mod join_ir_runner; // Phase 27.2: JoinIR 実行器(実験用) +pub mod join_ir_vm_bridge; // Phase 27-shortterm S-4: JoinIR → Rust VM ブリッジ +pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス pub mod optimizer_passes; // optimizer passes (normalize/diagnostics) pub mod optimizer_stats; // extracted stats struct +mod spanned_instruction; pub mod passes; pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only) pub mod printer; mod printer_helpers; // internal helpers extracted from printer.rs +pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery) pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind) pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs) pub mod value_id; pub mod value_kind; // Phase 26-A: ValueId型安全化 -pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery) -pub mod join_ir; // Phase 26-H: 関数正規化IR(JoinIR) -pub mod join_ir_runner; // Phase 27.2: JoinIR 実行器(実験用) -pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box) -pub mod join_ir_vm_bridge; // Phase 27-shortterm S-4: JoinIR → Rust VM ブリッジ pub mod verification; pub mod verification_types; // extracted error types // Optimization subpasses (e.g., type_hints) // Phase 25.1f: Loop/If 共通ビュー(ControlForm) @@ -51,9 +53,11 @@ pub use definitions::{CallFlags, Callee, MirCall}; // Unified call definitions pub use effect::{Effect, EffectMask}; pub use function::{FunctionSignature, MirFunction, MirModule}; pub use instruction::MirInstruction; +pub use join_ir_runner::{run_joinir_function, JoinRuntimeError, JoinValue}; pub use optimizer::MirOptimizer; -pub use query::{MirQuery, MirQueryBox}; pub use printer::MirPrinter; +pub use spanned_instruction::SpannedInstruction; +pub use query::{MirQuery, MirQueryBox}; pub use slot_registry::{BoxTypeId, MethodSlot}; pub use types::{ BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp, @@ -62,7 +66,6 @@ pub use value_id::{LocalId, ValueId, ValueIdGenerator}; pub use value_kind::{MirValueKind, TypedValueId}; // Phase 26-A: ValueId型安全化 pub use verification::MirVerifier; pub use verification_types::VerificationError; -pub use join_ir_runner::{run_joinir_function, JoinRuntimeError, JoinValue}; // Phase 15 control flow utilities (段階的根治戦略) pub use utils::{ capture_actual_predecessor_and_jump, collect_phi_incoming_if_reachable, @@ -102,7 +105,16 @@ impl MirCompiler { } /// Compile AST to MIR module with verification - pub fn compile(&mut self, ast: crate::ast::ASTNode) -> Result { + pub fn compile_with_source( + &mut self, + ast: crate::ast::ASTNode, + source_file: Option<&str>, + ) -> Result { + if let Some(src) = source_file { + self.builder.set_source_file_hint(src.to_string()); + } else { + self.builder.clear_source_file_hint(); + } // Convert AST to MIR using builder let mut module = self.builder.build_module(ast)?; @@ -128,6 +140,11 @@ impl MirCompiler { }) } + /// Compile AST to MIR module with verification (no source hint). + pub fn compile(&mut self, ast: crate::ast::ASTNode) -> Result { + self.compile_with_source(ast, None) + } + /// Dump MIR to string for debugging pub fn dump_mir(&self, module: &MirModule) -> String { MirPrinter::new().print_module(module) diff --git a/src/mir/naming.rs b/src/mir/naming.rs index 13facdbf..c8faf1f8 100644 --- a/src/mir/naming.rs +++ b/src/mir/naming.rs @@ -11,7 +11,12 @@ /// Encode a static box method into a MIR function name: `BoxName.method/arity`. pub fn encode_static_method(box_name: &str, method: &str, arity: usize) -> String { - format!("{}.{}{}", canonical_box_name(box_name), method, format!("/{}", arity)) + format!( + "{}.{}{}", + canonical_box_name(box_name), + method, + format!("/{}", arity) + ) } /// Canonicalize a static box name for MIR-level usage. @@ -245,4 +250,3 @@ pub fn parse_global_name(name: &str) -> Option { pub fn format_global_name(id: &StaticMethodId) -> String { id.format() } - diff --git a/src/mir/optimizer_passes/normalize.rs b/src/mir/optimizer_passes/normalize.rs index 5d62cbf7..e5ee471f 100644 --- a/src/mir/optimizer_passes/normalize.rs +++ b/src/mir/optimizer_passes/normalize.rs @@ -1,6 +1,7 @@ +use crate::ast::Span; use crate::mir::optimizer::MirOptimizer; use crate::mir::optimizer_stats::OptimizationStats; -use crate::mir::{BarrierOp, MirModule, TypeOpKind, ValueId, WeakRefOp}; +use crate::mir::{BarrierOp, MirModule, SpannedInstruction, TypeOpKind, ValueId, WeakRefOp}; fn idemp_enabled() -> bool { std::env::var("NYASH_MIR_DEV_IDEMP").ok().as_deref() == Some("1") @@ -433,9 +434,10 @@ pub fn normalize_ref_field_access( None => continue, }; for (_bb, block) in &mut function.blocks { - let mut out: Vec = Vec::with_capacity(block.instructions.len() + 2); - let old = std::mem::take(&mut block.instructions); - for inst in old.into_iter() { + let old_spanned: Vec = block.drain_spanned_instructions(); + let mut out: Vec = Vec::with_capacity(old_spanned.len() + 2); + let mut out_spans: Vec = Vec::with_capacity(old_spanned.len() + 2); + for SpannedInstruction { inst, span } in old_spanned.into_iter() { match inst { I::RefGet { dst, @@ -448,6 +450,7 @@ pub fn normalize_ref_field_access( dst: new_id, value: crate::mir::ConstValue::String(field), }); + out_spans.push(span); out.push(I::BoxCall { dst: Some(dst), box_val: reference, @@ -456,6 +459,7 @@ pub fn normalize_ref_field_access( args: vec![new_id], effects: crate::mir::EffectMask::READ, }); + out_spans.push(span); stats.intrinsic_optimizations += 1; } I::RefSet { @@ -469,10 +473,12 @@ pub fn normalize_ref_field_access( dst: new_id, value: crate::mir::ConstValue::String(field), }); + out_spans.push(span); out.push(I::Barrier { op: BarrierOp::Write, ptr: reference, }); + out_spans.push(span); out.push(I::BoxCall { dst: None, box_val: reference, @@ -481,14 +487,20 @@ pub fn normalize_ref_field_access( args: vec![new_id, value], effects: crate::mir::EffectMask::WRITE, }); + out_spans.push(span); stats.intrinsic_optimizations += 1; } - other => out.push(other), + other => { + out.push(other); + out_spans.push(span); + } } } block.instructions = out; + block.instruction_spans = out_spans; if let Some(term) = block.terminator.take() { + let term_span = block.terminator_span.take().unwrap_or_else(Span::unknown); block.terminator = Some(match term { I::RefGet { dst, @@ -501,6 +513,7 @@ pub fn normalize_ref_field_access( dst: new_id, value: crate::mir::ConstValue::String(field), }); + block.instruction_spans.push(term_span); I::BoxCall { dst: Some(dst), box_val: reference, @@ -521,10 +534,12 @@ pub fn normalize_ref_field_access( dst: new_id, value: crate::mir::ConstValue::String(field), }); + block.instruction_spans.push(term_span); block.instructions.push(I::Barrier { op: BarrierOp::Write, ptr: reference, }); + block.instruction_spans.push(term_span); I::BoxCall { dst: None, box_val: reference, @@ -536,6 +551,9 @@ pub fn normalize_ref_field_access( } other => other, }); + if block.terminator_span.is_none() { + block.terminator_span = Some(term_span); + } } } if idemp_enabled() { diff --git a/src/mir/optimizer_passes/normalize_core13_pure.rs b/src/mir/optimizer_passes/normalize_core13_pure.rs index 6673393f..68b90c34 100644 --- a/src/mir/optimizer_passes/normalize_core13_pure.rs +++ b/src/mir/optimizer_passes/normalize_core13_pure.rs @@ -1,3 +1,4 @@ +use crate::ast::Span; use crate::mir::optimizer::MirOptimizer; use crate::mir::optimizer_stats::OptimizationStats; use crate::mir::{BinaryOp, CompareOp, EffectMask, MirInstruction as I, MirModule, ValueId}; @@ -16,8 +17,11 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> for (_fname, function) in &mut module.functions { for (_bb, block) in &mut function.blocks { let mut out: Vec = Vec::with_capacity(block.instructions.len() + 8); + let mut out_spans: Vec = Vec::with_capacity(block.instructions.len() + 8); let old = std::mem::take(&mut block.instructions); + let mut old_spans_iter = std::mem::take(&mut block.instruction_spans).into_iter(); for inst in old.into_iter() { + let span = old_spans_iter.next().unwrap_or_else(Span::unknown); match inst { I::Load { dst, ptr } => { out.push(I::ExternCall { @@ -27,6 +31,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> args: vec![ptr], effects: EffectMask::READ, }); + out_spans.push(span); stats.intrinsic_optimizations += 1; } I::Store { value, ptr } => { @@ -37,6 +42,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> args: vec![ptr, value], effects: EffectMask::WRITE, }); + out_spans.push(span); stats.intrinsic_optimizations += 1; } I::NewBox { @@ -51,6 +57,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: ty_id, value: crate::mir::ConstValue::String(box_type), }); + out_spans.push(span); let mut call_args = Vec::with_capacity(1 + args.len()); call_args.push(ty_id); call_args.append(&mut args); @@ -61,6 +68,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> args: call_args, effects: EffectMask::PURE, // constructor is logically alloc; conservatively PURE here }); + out_spans.push(span); stats.intrinsic_optimizations += 1; } I::UnaryOp { dst, op, operand } => { @@ -72,12 +80,14 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: zero, value: crate::mir::ConstValue::Integer(0), }); + out_spans.push(span); out.push(I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand, }); + out_spans.push(span); } crate::mir::UnaryOp::Not => { let f = ValueId::new(function.next_value_id); @@ -86,12 +96,14 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: f, value: crate::mir::ConstValue::Bool(false), }); + out_spans.push(span); out.push(I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f, }); + out_spans.push(span); } crate::mir::UnaryOp::BitNot => { let all1 = ValueId::new(function.next_value_id); @@ -100,22 +112,29 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: all1, value: crate::mir::ConstValue::Integer(-1), }); + out_spans.push(span); out.push(I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1, }); + out_spans.push(span); } } stats.intrinsic_optimizations += 1; } - other => out.push(other), + other => { + out.push(other); + out_spans.push(span); + } } } block.instructions = out; + block.instruction_spans = out_spans; if let Some(term) = block.terminator.take() { + let term_span = block.terminator_span.take().unwrap_or_else(Span::unknown); block.terminator = Some(match term { I::Load { dst, ptr } => I::ExternCall { dst: Some(dst), @@ -142,6 +161,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: ty_id, value: ConstValue::String(box_type), }); + block.instruction_spans.push(term_span); let mut call_args = Vec::with_capacity(1 + args.len()); call_args.push(ty_id); call_args.append(&mut args); @@ -161,6 +181,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: zero, value: ConstValue::Integer(0), }); + block.instruction_spans.push(term_span); I::BinOp { dst, op: BinaryOp::Sub, @@ -175,6 +196,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: f, value: ConstValue::Bool(false), }); + block.instruction_spans.push(term_span); I::Compare { dst, op: CompareOp::Eq, @@ -189,6 +211,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> dst: all1, value: ConstValue::Integer(-1), }); + block.instruction_spans.push(term_span); I::BinOp { dst, op: BinaryOp::BitXor, @@ -199,6 +222,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) -> }, other => other, }); + block.terminator_span = Some(term_span); } } } diff --git a/src/mir/phi_core/body_local_phi_builder.rs b/src/mir/phi_core/body_local_phi_builder.rs index df3baf56..1a4c562b 100644 --- a/src/mir/phi_core/body_local_phi_builder.rs +++ b/src/mir/phi_core/body_local_phi_builder.rs @@ -180,11 +180,11 @@ impl BodyLocalPhiBuilder { } if enable_live_rescue { - if matches!(class, super::loop_var_classifier::LoopVarClass::BodyLocalInternal) - && live_at_exit.contains(*var_name) - && self - .inspector - .is_available_in_all(var_name, exit_preds) + if matches!( + class, + super::loop_var_classifier::LoopVarClass::BodyLocalInternal + ) && live_at_exit.contains(*var_name) + && self.inspector.is_available_in_all(var_name, exit_preds) { return true; } @@ -370,7 +370,7 @@ mod tests { &pinned, &carrier, &[BasicBlockId(2), BasicBlockId(5)], - &live_at_exit, // Phase 26-F-4 + &live_at_exit, // Phase 26-F-4 ); // Expected: s, idx, n (ch is BodyLocalInternal → filtered out) @@ -406,8 +406,13 @@ mod tests { // Phase 26-F-4: empty live_at_exit(live情報なし) let live_at_exit = std::collections::BTreeSet::new(); - let phi_vars = - builder.filter_exit_phi_candidates(&all_vars, &pinned, &carrier, &exit_preds, &live_at_exit); + let phi_vars = builder.filter_exit_phi_candidates( + &all_vars, + &pinned, + &carrier, + &exit_preds, + &live_at_exit, + ); // Expected: s, idx (ch filtered out!) assert_eq!(phi_vars.len(), 2); @@ -474,8 +479,13 @@ mod tests { // Phase 26-F-4: empty live_at_exit(live情報なし) let live_at_exit = std::collections::BTreeSet::new(); - let phi_vars = - builder.filter_exit_phi_candidates(&all_vars, &pinned, &[], &[BasicBlockId(5)], &live_at_exit); + let phi_vars = builder.filter_exit_phi_candidates( + &all_vars, + &pinned, + &[], + &[BasicBlockId(5)], + &live_at_exit, + ); // Only s should remain assert_eq!(phi_vars.len(), 1); diff --git a/src/mir/phi_core/conservative.rs b/src/mir/phi_core/conservative.rs index 43150eab..a14d7609 100644 --- a/src/mir/phi_core/conservative.rs +++ b/src/mir/phi_core/conservative.rs @@ -27,7 +27,7 @@ impl ConservativeMerge { /// * `then_end` - then-branch終了時の変数マップ /// * `else_end_opt` - else-branch終了時の変数マップ(Noneの場合はempty else) pub fn analyze( - pre_if: &BTreeMap, // Phase 25.1: BTreeMap化 + pre_if: &BTreeMap, // Phase 25.1: BTreeMap化 then_end: &BTreeMap, // Phase 25.1: BTreeMap化 else_end_opt: &Option>, // Phase 25.1: BTreeMap化 ) -> Self { diff --git a/src/mir/phi_core/exit_phi_builder.rs b/src/mir/phi_core/exit_phi_builder.rs index c2116c71..ff0003e4 100644 --- a/src/mir/phi_core/exit_phi_builder.rs +++ b/src/mir/phi_core/exit_phi_builder.rs @@ -18,8 +18,8 @@ use std::collections::{BTreeMap, BTreeSet}; use super::body_local_phi_builder::BodyLocalPhiBuilder; use super::loop_exit_liveness::{ExitLivenessProvider, LoopExitLivenessBox}; // Phase 26-F-4 use super::loop_snapshot_merge::LoopSnapshotMergeBox; -use super::phi_invariants::PhiInvariantsBox; use super::phi_input_collector::PhiInputCollector; +use super::phi_invariants::PhiInvariantsBox; /// Exit PHI生成専門Box /// @@ -76,8 +76,7 @@ impl ExitPhiBuilder { /// ``` pub fn new(body_local_builder: BodyLocalPhiBuilder) -> Self { // 環境変数で簡易 MirScan 版を opt-in できるようにする - let use_scan = - std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1"); + let use_scan = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1"); if use_scan { Self::with_liveness( body_local_builder, @@ -185,16 +184,19 @@ impl ExitPhiBuilder { // Phase 26-F/G: ExitLivenessProvider で live_at_exit を計算(MirQuery 経由) let query = crate::mir::MirQueryBox::new(ops.mir_function()); - let live_at_exit = self - .liveness_provider - .compute_live_at_exit(&query, exit_id, header_vals, exit_snapshots); + let live_at_exit = self.liveness_provider.compute_live_at_exit( + &query, + exit_id, + header_vals, + exit_snapshots, + ); let phi_vars = self.body_local_builder.filter_exit_phi_candidates( &required_vars.iter().cloned().collect::>(), pinned_vars, carrier_vars, &exit_preds, - &live_at_exit, // Phase 26-F-4: live_at_exit 追加 + &live_at_exit, // Phase 26-F-4: live_at_exit 追加 ); // Fail-Fast invariant(共通箱経由): @@ -361,12 +363,9 @@ impl LoopFormOps for T { } fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet { - crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors( - self, - block_id, - ) - .into_iter() - .collect() + crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors(self, block_id) + .into_iter() + .collect() } fn block_exists(&self, block_id: BasicBlockId) -> bool { diff --git a/src/mir/phi_core/header_phi_builder.rs b/src/mir/phi_core/header_phi_builder.rs index f86c11c1..ae91b05d 100644 --- a/src/mir/phi_core/header_phi_builder.rs +++ b/src/mir/phi_core/header_phi_builder.rs @@ -181,7 +181,9 @@ impl HeaderPhiBuilder { pub fn new() -> Self { // Phase 27.4-B/27.4C Cleanup: JoinIR 実験フラグのチェック(ログ出力のみ、挙動変更なし) if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP") { - eprintln!("[HeaderPhiBuilder] JoinIR experiment flag is ON (NYASH_JOINIR_HEADER_EXP=1)"); + eprintln!( + "[HeaderPhiBuilder] JoinIR experiment flag is ON (NYASH_JOINIR_HEADER_EXP=1)" + ); } Self::default() } diff --git a/src/mir/phi_core/if_body_local_merge.rs b/src/mir/phi_core/if_body_local_merge.rs index bb4fee7b..d4f1e1e1 100644 --- a/src/mir/phi_core/if_body_local_merge.rs +++ b/src/mir/phi_core/if_body_local_merge.rs @@ -24,8 +24,8 @@ //! - **橋渡し**: IfPhiContext経由で最小限の情報伝達(箱離婚) //! - **Fail-Fast**: 不正入力は即座にエラー -use crate::mir::{BasicBlockId, ValueId}; use crate::mir::phi_core::phi_builder_box::IfPhiContext; +use crate::mir::{BasicBlockId, ValueId}; use std::collections::BTreeMap; /// If Body-Local Merge Box @@ -105,7 +105,7 @@ impl IfBodyLocalMergeBox { then_end: &BTreeMap, else_end_opt: &Option>, _reachable_preds: &[BasicBlockId], - if_context: &IfPhiContext, // Phase 26-F-3: ループ内コンテキスト + if_context: &IfPhiContext, // Phase 26-F-3: ループ内コンテキスト ) -> Vec { use std::collections::BTreeSet; @@ -117,7 +117,8 @@ impl IfBodyLocalMergeBox { // 1. 両腕に存在する変数名を収集(決定的順序) let then_names: BTreeSet<&String> = then_end.keys().collect(); let else_names: BTreeSet<&String> = else_end.keys().collect(); - let common_names: BTreeSet<&String> = then_names.intersection(&else_names).copied().collect(); + let common_names: BTreeSet<&String> = + then_names.intersection(&else_names).copied().collect(); // Phase 26-F-3: ループ内モードで片腕のみのcarrier変数も追加 let mut candidate_names_set = common_names.clone(); @@ -207,7 +208,7 @@ mod tests { &then_end, &Some(else_end), &[BasicBlockId(1), BasicBlockId(2)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 + &default_if_context(), // Phase 26-F-3: コンテキスト追加 ); assert_eq!(candidates, vec!["x".to_string()]); @@ -228,7 +229,7 @@ mod tests { &then_end, &Some(else_end), &[BasicBlockId(1), BasicBlockId(2)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 + &default_if_context(), // Phase 26-F-3: コンテキスト追加 ); // 片腕のみ → BodyLocalInternal相当 → 候補なし @@ -252,7 +253,7 @@ mod tests { &then_end, &Some(else_end), &[BasicBlockId(1), BasicBlockId(2)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 + &default_if_context(), // Phase 26-F-3: コンテキスト追加 ); // 値が変わってない → φ不要 @@ -279,7 +280,7 @@ mod tests { &then_end, &Some(else_end), &[BasicBlockId(1), BasicBlockId(2)], - &loop_context(vec!["i"]), // i はキャリア変数 + &loop_context(vec!["i"]), // i はキャリア変数 ); // ループ内モード: carrier変数iは片腕のみでもPHI候補に @@ -296,18 +297,18 @@ mod tests { pre_if.insert("i".to_string(), ValueId(10)); let mut then_end = BTreeMap::new(); - then_end.insert("ch".to_string(), ValueId(5)); // ch は then のみ + then_end.insert("ch".to_string(), ValueId(5)); // ch は then のみ then_end.insert("i".to_string(), ValueId(20)); let mut else_end = BTreeMap::new(); - else_end.insert("i".to_string(), ValueId(30)); // i は else にも + else_end.insert("i".to_string(), ValueId(30)); // i は else にも let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( &pre_if, &then_end, &Some(else_end), &[BasicBlockId(1), BasicBlockId(2)], - &loop_context(vec!["i"]), // i のみキャリア + &loop_context(vec!["i"]), // i のみキャリア ); // i はキャリア → 候補に含まれる @@ -329,7 +330,7 @@ mod tests { &then_end, &None, &[BasicBlockId(1)], - &default_if_context(), // Phase 26-F-3: コンテキスト追加 + &default_if_context(), // Phase 26-F-3: コンテキスト追加 ); // empty else → 何も絞らない diff --git a/src/mir/phi_core/loop_exit_liveness.rs b/src/mir/phi_core/loop_exit_liveness.rs index ab38062a..7863a3f5 100644 --- a/src/mir/phi_core/loop_exit_liveness.rs +++ b/src/mir/phi_core/loop_exit_liveness.rs @@ -201,10 +201,7 @@ impl ExitLivenessProvider for MirScanExitLiveness { header_vals: &BTreeMap, exit_snapshots: &[(BasicBlockId, BTreeMap)], ) -> BTreeSet { - let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE") - .ok() - .as_deref() - == Some("1"); + let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1"); // 対象ブロック集合(exit と break preds) let mut targets: BTreeSet = BTreeSet::new(); @@ -325,13 +322,14 @@ mod tests { snap2.insert("i".to_string(), ValueId(50)); snap2.insert("pname".to_string(), ValueId(60)); - let exit_snapshots = vec![ - (BasicBlockId(100), snap1), - (BasicBlockId(200), snap2), - ]; + let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)]; - let live_at_exit = - liveness_box.compute_live_at_exit(&query, BasicBlockId(0), &header_vals, &exit_snapshots); + let live_at_exit = liveness_box.compute_live_at_exit( + &query, + BasicBlockId(0), + &header_vals, + &exit_snapshots, + ); // Phase 1: 空の live_at_exit(MIRスキャン実装待ち) assert_eq!(live_at_exit.len(), 0); @@ -345,8 +343,12 @@ mod tests { let header_vals = BTreeMap::new(); let exit_snapshots = vec![]; - let live_at_exit = - liveness_box.compute_live_at_exit(&query, BasicBlockId(0), &header_vals, &exit_snapshots); + let live_at_exit = liveness_box.compute_live_at_exit( + &query, + BasicBlockId(0), + &header_vals, + &exit_snapshots, + ); assert_eq!(live_at_exit.len(), 0); } @@ -365,13 +367,14 @@ mod tests { let mut snap2 = BTreeMap::new(); snap2.insert("i".to_string(), ValueId(30)); // 重複 - let exit_snapshots = vec![ - (BasicBlockId(100), snap1), - (BasicBlockId(200), snap2), - ]; + let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)]; - let live_at_exit = - liveness_box.compute_live_at_exit(&query, BasicBlockId(0), &header_vals, &exit_snapshots); + let live_at_exit = liveness_box.compute_live_at_exit( + &query, + BasicBlockId(0), + &header_vals, + &exit_snapshots, + ); // Phase 1: 空の live_at_exit(MIRスキャン実装待ち) assert_eq!(live_at_exit.len(), 0); diff --git a/src/mir/phi_core/loop_snapshot_manager.rs b/src/mir/phi_core/loop_snapshot_manager.rs index cbdf07d0..c0006fcf 100644 --- a/src/mir/phi_core/loop_snapshot_manager.rs +++ b/src/mir/phi_core/loop_snapshot_manager.rs @@ -9,7 +9,7 @@ use crate::mir::{BasicBlockId, ValueId}; use std::collections::BTreeMap; // Phase 25.1: 決定性確保 -// Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部使用のみ) + // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部使用のみ) /// ループSnapshotの一元管理Box /// @@ -104,7 +104,8 @@ impl LoopSnapshotManager { // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部変換) pub fn add_exit_snapshot(&mut self, block: BasicBlockId, vars: BTreeMap) { // Convert BTreeMap to BTreeMap for deterministic iteration - self.exit_snapshots.push((block, vars.into_iter().collect())); + self.exit_snapshots + .push((block, vars.into_iter().collect())); } /// Add continue snapshot @@ -120,7 +121,8 @@ impl LoopSnapshotManager { // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部変換) pub fn add_continue_snapshot(&mut self, block: BasicBlockId, vars: BTreeMap) { // Convert BTreeMap to BTreeMap for deterministic iteration - self.continue_snapshots.push((block, vars.into_iter().collect())); + self.continue_snapshots + .push((block, vars.into_iter().collect())); } /// Get preheader snapshot diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs index 39834c7b..5ccc7c0f 100644 --- a/src/mir/phi_core/loop_snapshot_merge.rs +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -17,7 +17,7 @@ use crate::mir::{BasicBlockId, ValueId}; use std::collections::{BTreeMap, BTreeSet}; // Phase 25.1: 決定性確保 -// Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部使用のみ) + // Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部使用のみ) // Option C PHI bug fix: Use box-based classification use super::local_scope_inspector::LocalScopeInspectorBox; diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index bc47b095..933e8a37 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -332,7 +332,7 @@ impl LoopFormBuilder { latch_id: BasicBlockId, continue_snapshots: &[(BasicBlockId, BTreeMap)], _writes: &std::collections::HashSet, // Step 5-1/5-2: Reserved for future optimization - header_bypass: bool, // Phase 27.4C: Header φ バイパスフラグ + header_bypass: bool, // Phase 27.4C: Header φ バイパスフラグ ) -> Result<(), String> { let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); @@ -442,8 +442,9 @@ impl LoopFormBuilder { continue; } - carrier.latch_value = - ops.get_variable_at_block(&carrier.name, latch_id).ok_or_else(|| { + carrier.latch_value = ops + .get_variable_at_block(&carrier.name, latch_id) + .ok_or_else(|| { format!( "carrier '{}' not found at latch {:?}", carrier.name, latch_id diff --git a/src/mir/phi_core/phi_builder_box.rs b/src/mir/phi_core/phi_builder_box.rs index e396e0b0..dd7c1d54 100644 --- a/src/mir/phi_core/phi_builder_box.rs +++ b/src/mir/phi_core/phi_builder_box.rs @@ -212,12 +212,8 @@ impl PhiBuilderBox { } // Compute modified variables (決定的順序: BTreeSet使用) - let modified_vars = self.compute_modified_names_if( - pre_snapshot, - then_end, - &else_end_opt, - if_shape, - ); + let modified_vars = + self.compute_modified_names_if(pre_snapshot, then_end, &else_end_opt, if_shape); for var_name in modified_vars { // Conservative strategy: get values with void fallback @@ -302,10 +298,14 @@ impl PhiBuilderBox { let else_end_owned = else_end_opt.map(|m| m.clone()); // Phase 26-F-3: IfPhiContextを取得(デフォルト: ループ外) - let if_context = self.if_context.as_ref().cloned().unwrap_or_else(|| IfPhiContext { - in_loop_body: false, - loop_carrier_names: std::collections::BTreeSet::new(), - }); + let if_context = self + .if_context + .as_ref() + .cloned() + .unwrap_or_else(|| IfPhiContext { + in_loop_body: false, + loop_carrier_names: std::collections::BTreeSet::new(), + }); // Phase 26-F-2/F-3: IfBodyLocalMergeBoxでif-merge専用のφ候補決定 let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates( @@ -313,7 +313,7 @@ impl PhiBuilderBox { then_end, &else_end_owned, &reachable_preds, - &if_context, // Phase 26-F-3: コンテキスト渡し + &if_context, // Phase 26-F-3: コンテキスト渡し ); // Debug trace diff --git a/src/mir/query.rs b/src/mir/query.rs index 0ec4b2ec..20f09158 100644 --- a/src/mir/query.rs +++ b/src/mir/query.rs @@ -68,7 +68,12 @@ impl<'m> MirQuery for MirQueryBox<'m> { Load { ptr, .. } => vec![*ptr], Store { ptr, value } => vec![*ptr, *value], ArrayGet { array, index, .. } => vec![*array, *index], - ArraySet { array, index, value, .. } => vec![*array, *index, *value], + ArraySet { + array, + index, + value, + .. + } => vec![*array, *index, *value], Call { args, .. } | BoxCall { args, .. } | PluginInvoke { args, .. } @@ -91,7 +96,9 @@ impl<'m> MirQuery for MirQueryBox<'m> { } RefNew { box_val, .. } => vec![*box_val], RefGet { reference, .. } => vec![*reference], - RefSet { reference, value, .. } => vec![*reference, *value], + RefSet { + reference, value, .. + } => vec![*reference, *value], WeakNew { box_val, .. } => vec![*box_val], WeakLoad { weak_ref, .. } => vec![*weak_ref], WeakRef { value, .. } => vec![*value], @@ -131,7 +138,7 @@ impl<'m> MirQuery for MirQueryBox<'m> { | FutureNew { dst, .. } | NewClosure { dst, .. } | Await { dst, .. } - | Copy { dst, .. } => vec![*dst], // Copy writes to dst + | Copy { dst, .. } => vec![*dst], // Copy writes to dst // No writes Nop | Store { .. } diff --git a/src/mir/spanned_instruction.rs b/src/mir/spanned_instruction.rs new file mode 100644 index 00000000..dc712158 --- /dev/null +++ b/src/mir/spanned_instruction.rs @@ -0,0 +1,9 @@ +use crate::ast::Span; +use super::MirInstruction; + +/// MIR instruction bundled with its source span. +#[derive(Debug, Clone)] +pub struct SpannedInstruction { + pub inst: MirInstruction, + pub span: Span, +} diff --git a/src/mir/ssot/cf_common.rs b/src/mir/ssot/cf_common.rs index 8f5c21b4..999aecb4 100644 --- a/src/mir/ssot/cf_common.rs +++ b/src/mir/ssot/cf_common.rs @@ -56,6 +56,9 @@ pub fn insert_phi_at_head( ) { inputs.sort_by_key(|(bb, _)| bb.0); if let Some(bb) = f.get_block_mut(bb_id) { - bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs }); + bb.insert_spanned_after_phis(crate::mir::SpannedInstruction { + inst: MirInstruction::Phi { dst, inputs }, + span: crate::ast::Span::unknown(), + }); } } diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 193fe7b2..cd0ca808 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -113,14 +113,12 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { }; let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, - Err(e) => { - crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, - &code, - &e, - ); - process::exit(1); - } + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, &code, &e, + ); + process::exit(1); + } }; // Optional macro expansion dump (no-op expansion for now) let ast2 = if crate::r#macro::enabled() { @@ -144,14 +142,12 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { }; let ast = match NyashParser::parse_from_string(&code) { Ok(ast) => ast, - Err(e) => { - crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, - &code, - &e, - ); - process::exit(1); - } + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, &code, &e, + ); + process::exit(1); + } }; let expanded = if crate::r#macro::enabled() { let a = crate::r#macro::maybe_expand_and_dump(&ast, false); diff --git a/src/runner/modes/bench.rs b/src/runner/modes/bench.rs index c6d4e5b4..bfefbd33 100644 --- a/src/runner/modes/bench.rs +++ b/src/runner/modes/bench.rs @@ -159,7 +159,13 @@ impl NyashRunner { if let Ok(ast0) = NyashParser::parse_from_string(code) { let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false); let mut mc = MirCompiler::new(); - if let Ok(cr) = mc.compile(ast) { + if let Ok(cr) = + crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mc, + ast, + None, + ) + { let mut vm = VM::new(); let _ = vm.execute_module(&cr.module); } @@ -191,7 +197,13 @@ impl NyashRunner { if let Ok(ast0) = NyashParser::parse_from_string(code) { let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false); let mut mc = MirCompiler::new(); - if let Ok(cr) = mc.compile(ast) { + if let Ok(cr) = + crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mc, + ast, + None, + ) + { let mut vm = VM::new(); let _ = vm.execute_module(&cr.module); } @@ -214,7 +226,12 @@ impl NyashRunner { let ast0 = NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?; let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false); let mut mc = MirCompiler::new(); - let cr = mc.compile(ast).map_err(|e| format!("vm compile: {}", e))?; + let cr = crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mc, + ast, + None, + ) + .map_err(|e| format!("vm compile: {}", e))?; let mut vm = VM::new(); let out = vm .execute_module(&cr.module) diff --git a/src/runner/modes/common_util/diag.rs b/src/runner/modes/common_util/diag.rs index 8a3c3101..70d577cc 100644 --- a/src/runner/modes/common_util/diag.rs +++ b/src/runner/modes/common_util/diag.rs @@ -71,9 +71,7 @@ fn extract_line_col(err: &ParseError) -> (Option, Option) { ParseError::UnsupportedNamespace { line, .. } => (Some(*line), None), ParseError::ExpectedIdentifier { line } => (Some(*line), None), ParseError::TokenizeError(te) => match te { - TokenizeError::UnexpectedCharacter { line, column, .. } => { - (Some(*line), Some(*column)) - } + TokenizeError::UnexpectedCharacter { line, column, .. } => (Some(*line), Some(*column)), TokenizeError::UnterminatedString { line } | TokenizeError::InvalidNumber { line } | TokenizeError::UnterminatedComment { line } => (Some(*line), None), diff --git a/src/runner/modes/common_util/mod.rs b/src/runner/modes/common_util/mod.rs index c772a6b7..01eed864 100644 --- a/src/runner/modes/common_util/mod.rs +++ b/src/runner/modes/common_util/mod.rs @@ -13,6 +13,7 @@ pub mod io; pub mod plugin_guard; pub mod provider_registry; pub mod pyvm; +pub mod source_hint; pub mod resolve; pub mod selfhost; pub mod selfhost_exe; diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index b53301e6..f0242d26 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -353,10 +353,12 @@ pub fn collect_using_and_strip( } } else { // ⚠️ Phase 0.3: User-friendly "Did you mean?" suggestions - let similar: Vec<_> = using_ctx.aliases.keys() + let similar: Vec<_> = using_ctx + .aliases + .keys() .filter(|k| { - k.to_lowercase().contains(&target_unquoted.to_lowercase()) || - target_unquoted.to_lowercase().contains(&k.to_lowercase()) + k.to_lowercase().contains(&target_unquoted.to_lowercase()) + || target_unquoted.to_lowercase().contains(&k.to_lowercase()) }) .take(3) .collect(); @@ -374,9 +376,13 @@ pub fn collect_using_and_strip( } if using_ctx.aliases.is_empty() { - err_msg.push_str("\n\n⚠️ No aliases loaded (check TOML parse errors above)"); + err_msg + .push_str("\n\n⚠️ No aliases loaded (check TOML parse errors above)"); } else { - err_msg.push_str(&format!("\n\nAvailable modules: {} aliases", using_ctx.aliases.len())); + err_msg.push_str(&format!( + "\n\nAvailable modules: {} aliases", + using_ctx.aliases.len() + )); } err_msg.push_str("\n\n📝 Suggestions:"); diff --git a/src/runner/modes/common_util/source_hint.rs b/src/runner/modes/common_util/source_hint.rs new file mode 100644 index 00000000..92a86be1 --- /dev/null +++ b/src/runner/modes/common_util/source_hint.rs @@ -0,0 +1,17 @@ +use crate::mir::{MirCompileResult, MirCompiler}; +use crate::ast::ASTNode; + +/// Compile AST with a source filename hint, reducing call-site duplication. +/// Falls back to regular compile when filename is None or empty. +pub fn compile_with_source_hint( + compiler: &mut MirCompiler, + ast: ASTNode, + filename: Option<&str>, +) -> Result { + if let Some(f) = filename { + if !f.is_empty() { + return compiler.compile_with_source(ast, Some(f)); + } + } + compiler.compile_with_source(ast, None) +} diff --git a/src/runner/modes/llvm.rs b/src/runner/modes/llvm.rs index 63736973..4b43147c 100644 --- a/src/runner/modes/llvm.rs +++ b/src/runner/modes/llvm.rs @@ -72,9 +72,7 @@ impl NyashRunner { Ok(ast) => ast, Err(e) => { crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, - code_ref, - &e, + filename, code_ref, &e, ); // Enhanced context: list merged prelude files if any (from text-merge path) let preludes = @@ -107,7 +105,11 @@ impl NyashRunner { // Compile to MIR let mut mir_compiler = MirCompiler::new(); - let compile_result = match mir_compiler.compile(ast) { + let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { Ok(result) => result, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); @@ -290,7 +292,8 @@ impl NyashRunner { println!("🔧 Mock LLVM Backend Execution:"); println!(" Build with --features llvm-inkwell-legacy for Rust/inkwell backend, or set NYASH_LLVM_OBJ_OUT and NYASH_LLVM_USE_HARNESS=1 for harness."); // NamingBox SSOT: Select entry (arity-aware, Main.main → main fallback) - let entry = crate::runner::modes::common_util::entry_selection::select_entry_function(&module); + let entry = + crate::runner::modes::common_util::entry_selection::select_entry_function(&module); if let Some(main_func) = module.functions.get(&entry) { for (_bid, block) in &main_func.blocks { for inst in &block.instructions { diff --git a/src/runner/modes/mir.rs b/src/runner/modes/mir.rs index 8c48677c..38325d4c 100644 --- a/src/runner/modes/mir.rs +++ b/src/runner/modes/mir.rs @@ -22,9 +22,7 @@ impl NyashRunner { Ok(ast) => ast, Err(e) => { crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, - &code, - &e, + filename, &code, &e, ); process::exit(1); } @@ -34,7 +32,11 @@ impl NyashRunner { // Compile to MIR (opt passes configurable) let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize); - let compile_result = match mir_compiler.compile(ast) { + let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { Ok(result) => result, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); diff --git a/src/runner/modes/mir_interpreter.rs b/src/runner/modes/mir_interpreter.rs index 694242a7..e3d94e9a 100644 --- a/src/runner/modes/mir_interpreter.rs +++ b/src/runner/modes/mir_interpreter.rs @@ -44,7 +44,11 @@ impl NyashRunner { // Compile to MIR (opt passes configurable) let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize); - let compile_result = match mir_compiler.compile(ast) { + let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { Ok(result) => result, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); } }; diff --git a/src/runner/modes/pyvm.rs b/src/runner/modes/pyvm.rs index 465854ff..6cb84d35 100644 --- a/src/runner/modes/pyvm.rs +++ b/src/runner/modes/pyvm.rs @@ -133,23 +133,25 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { if crate::config::env::env_bool("NYASH_PYVM_DUMP_CODE") { eprintln!("[pyvm-code]\n{}", code); } - let ast = match NyashParser::parse_from_string(&code) { - Ok(ast) => ast, - Err(e) => { - crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, - &code, - &e, - ); - process::exit(1); - } - }; + let ast = match NyashParser::parse_from_string(&code) { + Ok(ast) => ast, + Err(e) => { + crate::runner::modes::common_util::diag::print_parse_error_with_context( + filename, &code, &e, + ); + process::exit(1); + } + }; let ast = crate::r#macro::maybe_expand_and_dump(&ast, false); let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast); // Compile to MIR (respect default optimizer setting) let mut mir_compiler = MirCompiler::with_options(true); - let mut compile_result = match mir_compiler.compile(ast) { + let mut compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { Ok(result) => result, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index ec4dc51e..203d0a58 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -194,7 +194,9 @@ impl NyashRunner { Ok(ast) => ast, Err(e) => { crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, &code_final, &e, + filename, + &code_final, + &e, ); // Enhanced context: list merged prelude files if any let preludes = @@ -407,7 +409,11 @@ impl NyashRunner { // Compile to MIR let mut compiler = MirCompiler::with_options(!self.config.no_optimize); - let compile = match compiler.compile(ast) { + let compile = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut compiler, + ast, + Some(filename), + ) { Ok(c) => c, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); diff --git a/src/runner/modes/vm_fallback.rs b/src/runner/modes/vm_fallback.rs index 70ddf7ff..527be1dd 100644 --- a/src/runner/modes/vm_fallback.rs +++ b/src/runner/modes/vm_fallback.rs @@ -116,9 +116,7 @@ impl NyashRunner { Ok(ast) => ast, Err(e) => { crate::runner::modes::common_util::diag::print_parse_error_with_context( - filename, - &code2, - &e, + filename, &code2, &e, ); process::exit(1); } @@ -271,7 +269,11 @@ impl NyashRunner { } } let mut compiler = MirCompiler::with_options(!self.config.no_optimize); - let compile = match compiler.compile(ast) { + let compile = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut compiler, + ast, + Some(filename), + ) { Ok(c) => c, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); @@ -357,7 +359,7 @@ impl NyashRunner { impl NyashRunner { /// Small helper to continue fallback execution once AST is prepared #[allow(dead_code)] - fn execute_vm_fallback_from_ast(&self, _filename: &str, ast: nyash_rust::ast::ASTNode) { + fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) { use crate::{ backend::MirInterpreter, box_factory::{BoxFactory, RuntimeError}, @@ -471,7 +473,11 @@ impl NyashRunner { } // Compile to MIR and execute via interpreter let mut compiler = MirCompiler::with_options(!self.config.no_optimize); - let module = match compiler.compile(ast) { + let module = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut compiler, + ast, + Some(filename), + ) { Ok(r) => r.module, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); diff --git a/src/runner/modes/wasm.rs b/src/runner/modes/wasm.rs index 100b063a..c1a72881 100644 --- a/src/runner/modes/wasm.rs +++ b/src/runner/modes/wasm.rs @@ -30,7 +30,11 @@ impl NyashRunner { // Compile to MIR let mut mir_compiler = MirCompiler::new(); - let compile_result = match mir_compiler.compile(ast) { + let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { Ok(result) => result, Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); } }; diff --git a/src/runner/stage1_bridge/args.rs b/src/runner/stage1_bridge/args.rs index e9cc761f..9a471931 100644 --- a/src/runner/stage1_bridge/args.rs +++ b/src/runner/stage1_bridge/args.rs @@ -27,13 +27,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args { // Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility. let source = std::env::var("NYASH_STAGE1_INPUT") .ok() - .or_else(|| { - groups - .input - .file - .as_ref() - .cloned() - }) + .or_else(|| groups.input.file.as_ref().cloned()) .or_else(|| std::env::var("STAGE1_SOURCE").ok()) .or_else(|| std::env::var("STAGE1_INPUT").ok()); @@ -43,12 +37,11 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args { let emit_program = matches!( mode_env.as_deref(), Some("emit-program") | Some("emit-program-json") - ) || std::env::var("STAGE1_EMIT_PROGRAM_JSON") - .ok() - .as_deref() - == Some("1"); - let emit_mir = matches!(mode_env.as_deref(), Some("emit-mir") | Some("emit-mir-json")) - || std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1"); + ) || std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1"); + let emit_mir = matches!( + mode_env.as_deref(), + Some("emit-mir") | Some("emit-mir-json") + ) || std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1"); let mut args: Vec = Vec::new(); let mut source_env: Option = None; diff --git a/src/runner/stage1_bridge/env.rs b/src/runner/stage1_bridge/env.rs index ed8cc681..6bf0ca2c 100644 --- a/src/runner/stage1_bridge/env.rs +++ b/src/runner/stage1_bridge/env.rs @@ -23,23 +23,11 @@ pub(super) fn configure_stage1_env( // Unified Stage-1 env (NYASH_STAGE1_*) — derive from legacy if unset to keep compatibility. if std::env::var("NYASH_STAGE1_MODE").is_err() { - if std::env::var("STAGE1_EMIT_PROGRAM_JSON") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1") { cmd.env("NYASH_STAGE1_MODE", "emit-program"); - } else if std::env::var("STAGE1_EMIT_MIR_JSON") - .ok() - .as_deref() - == Some("1") - { + } else if std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1") { cmd.env("NYASH_STAGE1_MODE", "emit-mir"); - } else if std::env::var("NYASH_USE_STAGE1_CLI") - .ok() - .as_deref() - == Some("1") - { + } else if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() == Some("1") { cmd.env("NYASH_STAGE1_MODE", "run"); } } diff --git a/src/runner/stage1_bridge/mod.rs b/src/runner/stage1_bridge/mod.rs index c264bb40..afef1dbc 100644 --- a/src/runner/stage1_bridge/mod.rs +++ b/src/runner/stage1_bridge/mod.rs @@ -33,11 +33,7 @@ impl NyashRunner { } // Guard: skip if child invocation - if std::env::var("NYASH_STAGE1_CLI_CHILD") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") { if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") { eprintln!("[stage1-bridge/trace] skip: NYASH_STAGE1_CLI_CHILD=1"); } @@ -45,11 +41,7 @@ impl NyashRunner { } // Guard: skip if not enabled - if std::env::var("NYASH_USE_STAGE1_CLI") - .ok() - .as_deref() - != Some("1") - { + if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("1") { if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") { eprintln!("[stage1-bridge/trace] skip: NYASH_USE_STAGE1_CLI!=1"); } @@ -83,8 +75,8 @@ impl NyashRunner { std::path::PathBuf::from("target/release/nyash") }); let mut cmd = std::process::Command::new(exe); - let entry_fn = std::env::var("NYASH_ENTRY") - .unwrap_or_else(|_| "Stage1CliMain.main/0".to_string()); + let entry_fn = + std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string()); cmd.arg(&entry).arg("--"); for a in &args_result.args { cmd.arg(a); diff --git a/src/tests/joinir_runner_min.rs b/src/tests/joinir_runner_min.rs index 5c7e88fd..bd6f921b 100644 --- a/src/tests/joinir_runner_min.rs +++ b/src/tests/joinir_runner_min.rs @@ -12,11 +12,7 @@ use crate::mir::MirCompiler; use crate::parser::NyashParser; fn require_experiment_toggle() -> bool { - if std::env::var("NYASH_JOINIR_EXPERIMENT") - .ok() - .as_deref() - != Some("1") - { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { eprintln!( "[joinir/runner] NYASH_JOINIR_EXPERIMENT=1 not set, skipping experimental runner test" ); @@ -50,8 +46,7 @@ static box Runner { "#; let full_src = format!("{src}\n{runner}"); - let ast: ASTNode = - NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed"); @@ -113,8 +108,7 @@ static box Runner { "#; let full_src = format!("{func_scanner_src}\n{test_src}\n{runner}"); - let ast: ASTNode = - NyashParser::parse_from_string(&full_src).expect("trim_min: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("trim_min: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("trim_min: MIR compile failed"); diff --git a/src/tests/joinir_runner_standalone.rs b/src/tests/joinir_runner_standalone.rs index 99906b30..9c02325f 100644 --- a/src/tests/joinir_runner_standalone.rs +++ b/src/tests/joinir_runner_standalone.rs @@ -14,11 +14,7 @@ use crate::mir::join_ir::*; use crate::mir::join_ir_runner::{run_joinir_function, JoinValue}; fn require_experiment_toggle() -> bool { - if std::env::var("NYASH_JOINIR_EXPERIMENT") - .ok() - .as_deref() - != Some("1") - { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { eprintln!( "[joinir/runner/standalone] NYASH_JOINIR_EXPERIMENT=1 not set, skipping standalone test" ); @@ -161,7 +157,10 @@ fn joinir_runner_standalone_trim() { ) .expect("trim runner failed"); match result { - JoinValue::Str(s) => assert_eq!(s, "abc ", "simplified trim should remove only leading spaces"), + JoinValue::Str(s) => assert_eq!( + s, "abc ", + "simplified trim should remove only leading spaces" + ), other => panic!("trim returned non-string: {:?}", other), } @@ -209,12 +208,14 @@ fn build_skip_ws_joinir() -> JoinModule { let mut entry_func = JoinFunction::new(entry_id, "skip_ws_entry".to_string(), vec![s_param]); // n = s.length() - entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(n_var), - box_name: "StringBox".to_string(), - method: "length".to_string(), - args: vec![s_param], - })); + entry_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_var), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![s_param], + })); // i_init = 0 entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { @@ -360,12 +361,14 @@ fn build_trim_joinir() -> JoinModule { let mut entry_func = JoinFunction::new(entry_id, "trim_entry".to_string(), vec![s_param]); // n = s.length() - entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(n_var), - box_name: "StringBox".to_string(), - method: "length".to_string(), - args: vec![s_param], - })); + entry_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(n_var), + box_name: "StringBox".to_string(), + method: "length".to_string(), + args: vec![s_param], + })); // const_0 = 0 entry_func.body.push(JoinInst::Compute(MirLikeInst::Const { @@ -383,12 +386,14 @@ fn build_trim_joinir() -> JoinModule { }); // result = s.substring(start, n) - entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall { - dst: Some(result_var), - box_name: "StringBox".to_string(), - method: "substring".to_string(), - args: vec![s_param, start_var, n_var], - })); + entry_func + .body + .push(JoinInst::Compute(MirLikeInst::BoxCall { + dst: Some(result_var), + box_name: "StringBox".to_string(), + method: "substring".to_string(), + args: vec![s_param, start_var, n_var], + })); // return result entry_func.body.push(JoinInst::Ret { diff --git a/src/tests/joinir_vm_bridge_skip_ws.rs b/src/tests/joinir_vm_bridge_skip_ws.rs index 8a69b028..dda13423 100644 --- a/src/tests/joinir_vm_bridge_skip_ws.rs +++ b/src/tests/joinir_vm_bridge_skip_ws.rs @@ -21,14 +21,8 @@ use crate::mir::MirCompiler; use crate::parser::NyashParser; fn require_experiment_toggle() -> bool { - if std::env::var("NYASH_JOINIR_VM_BRIDGE") - .ok() - .as_deref() - != Some("1") - { - eprintln!( - "[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test" - ); + if std::env::var("NYASH_JOINIR_VM_BRIDGE").ok().as_deref() != Some("1") { + eprintln!("[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test"); return false; } true @@ -57,8 +51,7 @@ static box Runner { "#; let full_src = format!("{src}\n{runner}"); - let ast: ASTNode = - NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed"); @@ -86,10 +79,16 @@ static box Runner { ) .expect("JoinIR VM bridge failed for skip_ws"); - eprintln!("[joinir_vm_bridge_test] Route C result: {:?}", bridge_result); + eprintln!( + "[joinir_vm_bridge_test] Route C result: {:?}", + bridge_result + ); // Assertions: Both routes should produce the same result - assert_eq!(vm_result, "3", "Route A (VM) expected to skip 3 leading spaces"); + assert_eq!( + vm_result, "3", + "Route A (VM) expected to skip 3 leading spaces" + ); match bridge_result { JoinValue::Int(v) => { assert_eq!(v, 3, "Route C (JoinIR→VM bridge) skip_ws result mismatch"); diff --git a/src/tests/json_lint_stringutils_min_vm.rs b/src/tests/json_lint_stringutils_min_vm.rs index eb163e7e..56897671 100644 --- a/src/tests/json_lint_stringutils_min_vm.rs +++ b/src/tests/json_lint_stringutils_min_vm.rs @@ -106,4 +106,3 @@ static box Main { std::env::remove_var("NYASH_DISABLE_PLUGINS"); std::env::remove_var("HAKO_MIR_BUILDER_METHODIZE"); } - diff --git a/src/tests/mir_funcscanner_parse_params_trim_min.rs b/src/tests/mir_funcscanner_parse_params_trim_min.rs index afb436ef..b4601690 100644 --- a/src/tests/mir_funcscanner_parse_params_trim_min.rs +++ b/src/tests/mir_funcscanner_parse_params_trim_min.rs @@ -30,10 +30,8 @@ fn mir_funcscanner_parse_params_trim_min_verify_and_vm() { // std::env::set_var("NYASH_IF_HOLE_TRACE", "1"); // Bundle FuncScanner 本体と最小テスト。 - let func_scanner_src = - include_str!("../../lang/src/compiler/entry/func_scanner.hako"); - let test_src = - std::fs::read_to_string(test_file).expect("Failed to read minimal test .hako"); + let func_scanner_src = include_str!("../../lang/src/compiler/entry/func_scanner.hako"); + let test_src = std::fs::read_to_string(test_file).expect("Failed to read minimal test .hako"); let src = format!("{func_scanner_src}\n\n{test_src}"); let ast: ASTNode = @@ -86,4 +84,3 @@ fn mir_funcscanner_parse_params_trim_min_verify_and_vm() { std::env::remove_var("NYASH_VM_VERIFY_MIR"); std::env::remove_var("NYASH_IF_HOLE_TRACE"); } - diff --git a/src/tests/mir_funcscanner_trim_min.rs b/src/tests/mir_funcscanner_trim_min.rs index 7a471106..15285d7d 100644 --- a/src/tests/mir_funcscanner_trim_min.rs +++ b/src/tests/mir_funcscanner_trim_min.rs @@ -29,14 +29,11 @@ fn mir_funcscanner_trim_min_verify_and_vm() { // std::env::set_var("NYASH_IF_HOLE_TRACE", "1"); // FuncScanner 本体と最小 _trim テストを 1 ソースにまとめる。 - let func_scanner_src = - include_str!("../../lang/src/compiler/entry/func_scanner.hako"); - let test_src = - std::fs::read_to_string(test_file).expect("Failed to read trim_min .hako"); + let func_scanner_src = include_str!("../../lang/src/compiler/entry/func_scanner.hako"); + let test_src = std::fs::read_to_string(test_file).expect("Failed to read trim_min .hako"); let src = format!("{func_scanner_src}\n\n{test_src}"); - let ast: ASTNode = - NyashParser::parse_from_string(&src).expect("trim_min: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("trim_min: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("trim_min: MIR compile failed"); diff --git a/src/tests/mir_joinir_funcscanner_append_defs.rs b/src/tests/mir_joinir_funcscanner_append_defs.rs index c99400f9..f11c2b69 100644 --- a/src/tests/mir_joinir_funcscanner_append_defs.rs +++ b/src/tests/mir_joinir_funcscanner_append_defs.rs @@ -25,6 +25,7 @@ use crate::mir::join_ir::lowering::funcscanner_append_defs::lower_funcscanner_ap use crate::mir::join_ir::*; use crate::mir::{MirCompiler, ValueId}; use crate::parser::NyashParser; +use std::collections::BTreeMap; #[test] #[ignore] // 手動実行用(Phase 27.14 実験段階) @@ -47,11 +48,13 @@ fn mir_joinir_funcscanner_append_defs_auto_lowering() { let src = std::fs::read_to_string(test_file) .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); - let ast: ASTNode = NyashParser::parse_from_string(&src) - .expect("funcscanner_append_defs: parse failed"); + let ast: ASTNode = + NyashParser::parse_from_string(&src).expect("funcscanner_append_defs: parse failed"); let mut mc = MirCompiler::with_options(false); - let compiled = mc.compile(ast).expect("funcscanner_append_defs: MIR compile failed"); + let compiled = mc + .compile(ast) + .expect("funcscanner_append_defs: MIR compile failed"); eprintln!( "[joinir/funcscanner_append_defs] MIR module compiled, {} functions", @@ -66,32 +69,69 @@ fn mir_joinir_funcscanner_append_defs_auto_lowering() { eprintln!("{:#?}", join_module); // Step 3: 妥当性検証(Phase 27.14) - assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (append_defs_entry + loop_step)"); + assert_eq!( + join_module.functions.len(), + 2, + "Expected 2 functions (append_defs_entry + loop_step)" + ); let entry_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); // append_defs_entry 関数の検証 - let entry_func = join_module.functions.get(&entry_id) + let entry_func = join_module + .functions + .get(&entry_id) .expect("append_defs_entry function not found"); assert_eq!(entry_func.name, "append_defs_entry"); - assert_eq!(entry_func.params.len(), 3, "append_defs_entry has 3 parameters (dst, defs_box, n)"); + assert_eq!( + entry_func.params.len(), + 3, + "append_defs_entry has 3 parameters (dst, defs_box, n)" + ); // loop_step 関数の検証 - let loop_step_func = join_module.functions.get(&loop_step_id) + let loop_step_func = join_module + .functions + .get(&loop_step_id) .expect("loop_step function not found"); assert_eq!(loop_step_func.name, "loop_step"); - assert_eq!(loop_step_func.params.len(), 4, "loop_step has 4 parameters (dst, defs_box, n, i)"); + assert_eq!( + loop_step_func.params.len(), + 4, + "loop_step has 4 parameters (dst, defs_box, n, i)" + ); // ValueId range 検証 (9000-10999) - assert_eq!(entry_func.params[0].0, 9000, "dst parameter should be ValueId(9000)"); - assert_eq!(entry_func.params[1].0, 9001, "defs_box parameter should be ValueId(9001)"); - assert_eq!(entry_func.params[2].0, 9002, "n parameter should be ValueId(9002)"); + assert_eq!( + entry_func.params[0].0, 9000, + "dst parameter should be ValueId(9000)" + ); + assert_eq!( + entry_func.params[1].0, 9001, + "defs_box parameter should be ValueId(9001)" + ); + assert_eq!( + entry_func.params[2].0, 9002, + "n parameter should be ValueId(9002)" + ); - assert_eq!(loop_step_func.params[0].0, 10000, "dst_loop parameter should be ValueId(10000)"); - assert_eq!(loop_step_func.params[1].0, 10001, "defs_box_loop parameter should be ValueId(10001)"); - assert_eq!(loop_step_func.params[2].0, 10002, "n_loop parameter should be ValueId(10002)"); - assert_eq!(loop_step_func.params[3].0, 10003, "i_loop parameter should be ValueId(10003)"); + assert_eq!( + loop_step_func.params[0].0, 10000, + "dst_loop parameter should be ValueId(10000)" + ); + assert_eq!( + loop_step_func.params[1].0, 10001, + "defs_box_loop parameter should be ValueId(10001)" + ); + assert_eq!( + loop_step_func.params[2].0, 10002, + "n_loop parameter should be ValueId(10002)" + ); + assert_eq!( + loop_step_func.params[3].0, 10003, + "i_loop parameter should be ValueId(10003)" + ); eprintln!("[joinir/funcscanner_append_defs] ✅ 自動変換成功(Phase 27.14)"); } @@ -127,5 +167,78 @@ fn mir_joinir_funcscanner_append_defs_empty_module_returns_none() { let result = lower_funcscanner_append_defs_to_joinir(&test_module); eprintln!("[joinir/funcscanner_append_defs] empty_module test: result is None (expected)"); - assert!(result.is_none(), "Empty MirModule should return None (target function not found)"); + assert!( + result.is_none(), + "Empty MirModule should return None (target function not found)" + ); +} + +#[test] +#[ignore] // 手動実行: generic_case_a トグル検証(append_defs minimal) +fn mir_joinir_funcscanner_append_defs_generic_matches_handwritten() { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!("[joinir/funcscanner_append_defs] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test"); + return; + } + + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let test_file = "apps/tests/funcscanner_append_defs_minimal.hako"; + let src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + + let ast: ASTNode = + NyashParser::parse_from_string(&src).expect("append_defs generic: parse failed"); + let mut mc = MirCompiler::with_options(false); + let compiled = mc + .compile(ast) + .expect("append_defs generic: MIR compile failed"); + + fn params_by_name(jm: &JoinModule) -> BTreeMap { + jm.functions + .values() + .map(|f| (f.name.clone(), f.params.len())) + .collect() + } + + // Baseline (generic OFF) + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0"); + let baseline = lower_funcscanner_append_defs_to_joinir(&compiled.module) + .expect("baseline append_defs lowering failed"); + + // Generic ON + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1"); + let generic = lower_funcscanner_append_defs_to_joinir(&compiled.module) + .expect("generic append_defs lowering failed"); + + let baseline_params = params_by_name(&baseline); + let generic_params = params_by_name(&generic); + + assert_eq!(baseline_params.len(), 2, "baseline should have 2 functions"); + assert_eq!(generic_params.len(), 2, "generic should have 2 functions"); + assert_eq!( + baseline_params.len(), + generic_params.len(), + "function count mismatch" + ); + + let expected_funcs = ["append_defs_entry", "loop_step"]; + for name in expected_funcs { + let b_params = baseline_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("baseline missing function {}", name)); + let g_params = generic_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("generic missing function {}", name)); + assert_eq!( + b_params, g_params, + "param count differs for function {}", + name + ); + } + + eprintln!("[joinir/funcscanner_append_defs] ✅ generic_case_a JoinIR matches baseline"); } diff --git a/src/tests/mir_joinir_funcscanner_trim.rs b/src/tests/mir_joinir_funcscanner_trim.rs index 770d93e6..00275c92 100644 --- a/src/tests/mir_joinir_funcscanner_trim.rs +++ b/src/tests/mir_joinir_funcscanner_trim.rs @@ -25,6 +25,7 @@ use crate::ast::ASTNode; use crate::mir::join_ir::*; use crate::mir::{MirCompiler, ValueId}; use crate::parser::NyashParser; +use std::collections::BTreeMap; #[test] #[ignore] // 手動実行用(Phase 27.1 実験段階) @@ -51,8 +52,7 @@ fn mir_joinir_funcscanner_trim_auto_lowering() { .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); let src = format!("{func_scanner_src}\n\n{test_src}"); - let ast: ASTNode = NyashParser::parse_from_string(&src) - .expect("trim: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("trim: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("trim: MIR compile failed"); @@ -70,24 +70,46 @@ fn mir_joinir_funcscanner_trim_auto_lowering() { eprintln!("{:#?}", join_module); // Step 3: 妥当性検証 - assert_eq!(join_module.functions.len(), 3, "Expected 3 functions (trim_main + loop_step + skip_leading)"); + assert_eq!( + join_module.functions.len(), + 3, + "Expected 3 functions (trim_main + loop_step + skip_leading)" + ); let trim_main_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); // trim_main 関数の検証 - let trim_main_func = join_module.functions.get(&trim_main_id) + let trim_main_func = join_module + .functions + .get(&trim_main_id) .expect("trim_main function not found"); assert_eq!(trim_main_func.name, "trim_main"); - assert_eq!(trim_main_func.params.len(), 1, "trim_main has 1 parameter (s)"); - assert!(trim_main_func.body.len() >= 5, "trim_main should have at least 5 instructions"); + assert_eq!( + trim_main_func.params.len(), + 1, + "trim_main has 1 parameter (s)" + ); + assert!( + trim_main_func.body.len() >= 5, + "trim_main should have at least 5 instructions" + ); // loop_step 関数の検証 - let loop_step_func = join_module.functions.get(&loop_step_id) + let loop_step_func = join_module + .functions + .get(&loop_step_id) .expect("loop_step function not found"); assert_eq!(loop_step_func.name, "loop_step"); - assert_eq!(loop_step_func.params.len(), 3, "loop_step has 3 parameters (str, b, e)"); - assert!(loop_step_func.body.len() >= 10, "loop_step should have multiple instructions"); + assert_eq!( + loop_step_func.params.len(), + 3, + "loop_step has 3 parameters (str, b, e)" + ); + assert!( + loop_step_func.body.len() >= 10, + "loop_step should have multiple instructions" + ); eprintln!("[joinir/trim] ✅ 自動変換成功(Phase 27.1)"); } @@ -98,14 +120,82 @@ fn mir_joinir_funcscanner_trim_type_sanity() { // trim 用の JoinFunction が作成できることを確認 let trim_main_id = JoinFuncId::new(10); - let trim_main_func = JoinFunction::new( - trim_main_id, - "trim_main_test".to_string(), - vec![ValueId(1)], - ); + let trim_main_func = + JoinFunction::new(trim_main_id, "trim_main_test".to_string(), vec![ValueId(1)]); assert_eq!(trim_main_func.id, trim_main_id); assert_eq!(trim_main_func.name, "trim_main_test"); assert_eq!(trim_main_func.params.len(), 1); assert_eq!(trim_main_func.body.len(), 0); } + +#[test] +#[ignore] // 手動実行: generic_case_a トグル検証(trim minimal) +fn mir_joinir_funcscanner_trim_generic_matches_handwritten() { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!("[joinir/trim] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test"); + return; + } + + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_ENABLE_USING", "1"); + std::env::set_var("HAKO_ENABLE_USING", "1"); + + let func_scanner_src = include_str!("../../lang/src/compiler/entry/func_scanner.hako"); + let test_file = "lang/src/compiler/tests/funcscanner_trim_min.hako"; + let test_src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + let src = format!("{func_scanner_src}\n\n{test_src}"); + + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("trim generic: parse failed"); + let mut mc = MirCompiler::with_options(false); + let compiled = mc.compile(ast).expect("trim generic: MIR compile failed"); + + fn params_by_name(jm: &JoinModule) -> BTreeMap { + jm.functions + .values() + .map(|f| (f.name.clone(), f.params.len())) + .collect() + } + + // Baseline (generic OFF) + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0"); + let baseline = + lower_funcscanner_trim_to_joinir(&compiled.module).expect("baseline trim lowering failed"); + + // Generic ON + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1"); + let generic = + lower_funcscanner_trim_to_joinir(&compiled.module).expect("generic trim lowering failed"); + + let baseline_params = params_by_name(&baseline); + let generic_params = params_by_name(&generic); + + assert_eq!(baseline_params.len(), 3, "baseline should have 3 functions"); + assert_eq!(generic_params.len(), 3, "generic should have 3 functions"); + assert_eq!( + baseline_params.len(), + generic_params.len(), + "function count mismatch" + ); + + let expected_funcs = ["trim_main", "loop_step", "skip_leading"]; + for name in expected_funcs { + let b_params = baseline_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("baseline missing function {}", name)); + let g_params = generic_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("generic missing function {}", name)); + assert_eq!( + b_params, g_params, + "param count differs for function {}", + name + ); + } + + eprintln!("[joinir/trim] ✅ generic_case_a JoinIR matches baseline"); +} diff --git a/src/tests/mir_joinir_min.rs b/src/tests/mir_joinir_min.rs index c5dff16f..537be917 100644 --- a/src/tests/mir_joinir_min.rs +++ b/src/tests/mir_joinir_min.rs @@ -23,7 +23,9 @@ fn mir_joinir_min_manual_construction() { // 環境変数トグルチェック if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { - eprintln!("[joinir/min] NYASH_JOINIR_EXPERIMENT=1 not set, skipping manual construction test"); + eprintln!( + "[joinir/min] NYASH_JOINIR_EXPERIMENT=1 not set, skipping manual construction test" + ); return; } @@ -36,8 +38,7 @@ fn mir_joinir_min_manual_construction() { let src = std::fs::read_to_string(test_file) .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); - let ast: ASTNode = NyashParser::parse_from_string(&src) - .expect("joinir_min: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("joinir_min: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("joinir_min: MIR compile failed"); @@ -85,18 +86,22 @@ fn mir_joinir_min_manual_construction() { let i_plus_1 = ValueId(202); // cmp_result = (i >= 2) - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp_result, - op: CompareOp::Ge, - lhs: i_param, - rhs: ValueId(203), // const 2 - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp_result, + op: CompareOp::Ge, + lhs: i_param, + rhs: ValueId(203), // const 2 + })); // const 2 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const { - dst: ValueId(203), - value: ConstValue::Integer(2), - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::Const { + dst: ValueId(203), + value: ConstValue::Integer(2), + })); // if cmp_result { k_exit(i) } else { loop_step(i+1, k_exit) } // ここでは簡略化して Jump 命令だけ書く(実際は分岐制御が必要だが Phase 26-H では型チェックのみ) @@ -107,12 +112,14 @@ fn mir_joinir_min_manual_construction() { }); // i_plus_1 = i + 1 - loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: i_plus_1, - op: BinOpKind::Add, - lhs: i_param, - rhs: ValueId(204), // const 1 - })); + loop_step_func + .body + .push(JoinInst::Compute(MirLikeInst::BinOp { + dst: i_plus_1, + op: BinOpKind::Add, + lhs: i_param, + rhs: ValueId(204), // const 1 + })); join_module.add_function(loop_step_func); @@ -148,8 +155,7 @@ fn mir_joinir_min_auto_lowering() { let src = std::fs::read_to_string(test_file) .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); - let ast: ASTNode = NyashParser::parse_from_string(&src) - .expect("joinir_min: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("joinir_min: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("joinir_min: MIR compile failed"); @@ -160,31 +166,49 @@ fn mir_joinir_min_auto_lowering() { ); // Step 2: MIR → JoinIR 自動変換 - let join_module = lower_min_loop_to_joinir(&compiled.module) - .expect("lower_min_loop_to_joinir failed"); + let join_module = + lower_min_loop_to_joinir(&compiled.module).expect("lower_min_loop_to_joinir failed"); eprintln!("[joinir/auto] JoinIR module generated:"); eprintln!("{:#?}", join_module); // Step 3: 妥当性検証 - assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (main + loop_step)"); + assert_eq!( + join_module.functions.len(), + 2, + "Expected 2 functions (main + loop_step)" + ); let main_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); // main 関数の検証 - let main_func = join_module.functions.get(&main_id) + let main_func = join_module + .functions + .get(&main_id) .expect("main function not found"); assert_eq!(main_func.name, "main"); assert_eq!(main_func.params.len(), 0, "main has no parameters"); - assert!(main_func.body.len() >= 2, "main should have at least 2 instructions (const + call)"); + assert!( + main_func.body.len() >= 2, + "main should have at least 2 instructions (const + call)" + ); // loop_step 関数の検証 - let loop_step_func = join_module.functions.get(&loop_step_id) + let loop_step_func = join_module + .functions + .get(&loop_step_id) .expect("loop_step function not found"); assert_eq!(loop_step_func.name, "loop_step"); - assert_eq!(loop_step_func.params.len(), 1, "loop_step has 1 parameter (i)"); - assert!(loop_step_func.body.len() >= 4, "loop_step should have multiple instructions"); + assert_eq!( + loop_step_func.params.len(), + 1, + "loop_step has 1 parameter (i)" + ); + assert!( + loop_step_func.body.len() >= 4, + "loop_step should have multiple instructions" + ); eprintln!("[joinir/auto] ✅ 自動変換成功(Phase 26-H Step 2)"); } diff --git a/src/tests/mir_joinir_skip_ws.rs b/src/tests/mir_joinir_skip_ws.rs index 797c20d4..50def15a 100644 --- a/src/tests/mir_joinir_skip_ws.rs +++ b/src/tests/mir_joinir_skip_ws.rs @@ -24,6 +24,7 @@ use crate::ast::ASTNode; use crate::mir::join_ir::*; use crate::mir::{MirCompiler, ValueId}; use crate::parser::NyashParser; +use std::collections::BTreeMap; #[test] #[ignore] // 手動実行用(Phase 27.1 実験段階) @@ -32,7 +33,9 @@ fn mir_joinir_skip_ws_auto_lowering() { // 環境変数トグルチェック if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { - eprintln!("[joinir/skip_ws] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test"); + eprintln!( + "[joinir/skip_ws] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test" + ); return; } @@ -45,8 +48,7 @@ fn mir_joinir_skip_ws_auto_lowering() { let src = std::fs::read_to_string(test_file) .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); - let ast: ASTNode = NyashParser::parse_from_string(&src) - .expect("skip_ws: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("skip_ws: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed"); @@ -57,30 +59,45 @@ fn mir_joinir_skip_ws_auto_lowering() { ); // Step 2: MIR → JoinIR 自動変換 - let join_module = lower_skip_ws_to_joinir(&compiled.module) - .expect("lower_skip_ws_to_joinir failed"); + let join_module = + lower_skip_ws_to_joinir(&compiled.module).expect("lower_skip_ws_to_joinir failed"); eprintln!("[joinir/skip_ws] JoinIR module generated:"); eprintln!("{:#?}", join_module); // Step 3: 妥当性検証 - assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (skip + loop_step)"); + assert_eq!( + join_module.functions.len(), + 2, + "Expected 2 functions (skip + loop_step)" + ); let skip_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); // skip 関数の検証 - let skip_func = join_module.functions.get(&skip_id) + let skip_func = join_module + .functions + .get(&skip_id) .expect("skip function not found"); assert_eq!(skip_func.name, "skip"); assert_eq!(skip_func.params.len(), 1, "skip has 1 parameter (s)"); - assert!(skip_func.body.len() >= 3, "skip should have at least 3 instructions (const 0, length call, loop_step call)"); + assert!( + skip_func.body.len() >= 3, + "skip should have at least 3 instructions (const 0, length call, loop_step call)" + ); // loop_step 関数の検証 - let loop_step_func = join_module.functions.get(&loop_step_id) + let loop_step_func = join_module + .functions + .get(&loop_step_id) .expect("loop_step function not found"); assert_eq!(loop_step_func.name, "loop_step"); - assert_eq!(loop_step_func.params.len(), 3, "loop_step has 3 parameters (s, i, n)"); + assert_eq!( + loop_step_func.params.len(), + 3, + "loop_step has 3 parameters (s, i, n)" + ); assert!(loop_step_func.body.len() >= 8, "loop_step should have multiple instructions (comparisons, substring, recursive call, etc.)"); eprintln!("[joinir/skip_ws] ✅ 自動変換成功(Phase 27.1)"); @@ -103,3 +120,97 @@ fn mir_joinir_skip_ws_type_sanity() { assert_eq!(skip_func.params.len(), 3); assert_eq!(skip_func.body.len(), 0); } + +#[test] +fn mir_joinir_skip_ws_generic_matches_handwritten() { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!("[joinir/skip_ws] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test"); + return; + } + + // Stage-3 parserを有効化 + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let test_file = "apps/tests/minimal_ssa_skip_ws.hako"; + let src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("skip_ws generic: parse failed"); + + let mut mc = MirCompiler::with_options(false); + let compiled = mc + .compile(ast) + .expect("skip_ws generic: MIR compile failed"); + + fn params_by_name(jm: &JoinModule) -> BTreeMap { + jm.functions + .values() + .map(|f| (f.name.clone(), f.params.len())) + .collect() + } + + fn has_jump_to_cont(jm: &JoinModule, func_name: &str, cont: JoinContId) -> bool { + jm.functions + .values() + .find(|f| f.name == func_name) + .map(|f| { + f.body.iter().any(|inst| match inst { + JoinInst::Jump { cont: c, .. } => *c == cont, + _ => false, + }) + }) + .unwrap_or(false) + } + + // Baseline (generic OFF) + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0"); + let baseline = + lower_skip_ws_to_joinir(&compiled.module).expect("baseline skip_ws lowering failed"); + + // Generic ON + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1"); + let generic = + lower_skip_ws_to_joinir(&compiled.module).expect("generic skip_ws lowering failed"); + + // Compare shape (function count + params + loop_step/k_exit 相当の存在確認) + let baseline_params = params_by_name(&baseline); + let generic_params = params_by_name(&generic); + + assert_eq!(baseline_params.len(), 2, "baseline should have 2 functions"); + assert_eq!(generic_params.len(), 2, "generic should have 2 functions"); + assert_eq!( + baseline_params.len(), + generic_params.len(), + "function count mismatch" + ); + + let expected_funcs = ["skip", "loop_step"]; + for name in expected_funcs { + let b_params = baseline_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("baseline missing function {}", name)); + let g_params = generic_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("generic missing function {}", name)); + assert_eq!( + b_params, g_params, + "param count differs for function {}", + name + ); + } + + let exit_cont = JoinContId::new(0); + assert!( + has_jump_to_cont(&baseline, "loop_step", exit_cont), + "baseline loop_step should jump to k_exit(cont0)" + ); + assert!( + has_jump_to_cont(&generic, "loop_step", exit_cont), + "generic loop_step should jump to k_exit(cont0)" + ); + + eprintln!("[joinir/skip_ws] ✅ generic_case_a JoinIR matches baseline"); +} diff --git a/src/tests/mir_joinir_stage1_using_resolver_min.rs b/src/tests/mir_joinir_stage1_using_resolver_min.rs index 90a51572..117169fa 100644 --- a/src/tests/mir_joinir_stage1_using_resolver_min.rs +++ b/src/tests/mir_joinir_stage1_using_resolver_min.rs @@ -25,9 +25,10 @@ use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingreso use crate::mir::join_ir::*; use crate::mir::{MirCompiler, ValueId}; use crate::parser::NyashParser; +use std::collections::BTreeMap; #[test] -#[ignore] // 手動実行用(Phase 27.12 実験段階) +#[ignore] // 手動実行用(Stage-1 minimal) fn mir_joinir_stage1_using_resolver_auto_lowering() { // Phase 27.12: Stage1UsingResolverBox.resolve_for_source の MIR → JoinIR 自動変換 @@ -47,11 +48,13 @@ fn mir_joinir_stage1_using_resolver_auto_lowering() { let src = std::fs::read_to_string(test_file) .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); - let ast: ASTNode = NyashParser::parse_from_string(&src) - .expect("stage1_using_resolver: parse failed"); + let ast: ASTNode = + NyashParser::parse_from_string(&src).expect("stage1_using_resolver: parse failed"); let mut mc = MirCompiler::with_options(false); - let compiled = mc.compile(ast).expect("stage1_using_resolver: MIR compile failed"); + let compiled = mc + .compile(ast) + .expect("stage1_using_resolver: MIR compile failed"); eprintln!( "[joinir/stage1_using_resolver] MIR module compiled, {} functions", @@ -66,22 +69,38 @@ fn mir_joinir_stage1_using_resolver_auto_lowering() { eprintln!("{:#?}", join_module); // Step 3: 妥当性検証(Phase 27.13 以降で実装) - assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (resolve_entries + loop_step)"); + assert_eq!( + join_module.functions.len(), + 2, + "Expected 2 functions (resolve_entries + loop_step)" + ); let resolve_id = JoinFuncId::new(0); let loop_step_id = JoinFuncId::new(1); // resolve_entries 関数の検証 - let resolve_func = join_module.functions.get(&resolve_id) + let resolve_func = join_module + .functions + .get(&resolve_id) .expect("resolve_entries function not found"); assert_eq!(resolve_func.name, "resolve_entries"); - assert_eq!(resolve_func.params.len(), 5, "resolve_entries has 5 parameters (entries, n, modules, seen, prefix_init)"); + assert_eq!( + resolve_func.params.len(), + 5, + "resolve_entries has 5 parameters (entries, n, modules, seen, prefix_init)" + ); // loop_step 関数の検証 - let loop_step_func = join_module.functions.get(&loop_step_id) + let loop_step_func = join_module + .functions + .get(&loop_step_id) .expect("loop_step function not found"); assert_eq!(loop_step_func.name, "loop_step"); - assert_eq!(loop_step_func.params.len(), 6, "loop_step has 6 parameters (entries, n, modules, seen, prefix, i)"); + assert_eq!( + loop_step_func.params.len(), + 6, + "loop_step has 6 parameters (entries, n, modules, seen, prefix, i)" + ); eprintln!("[joinir/stage1_using_resolver] ✅ 自動変換成功(Phase 27.12/27.13)"); } @@ -117,5 +136,80 @@ fn mir_joinir_stage1_using_resolver_empty_module_returns_none() { let result = lower_stage1_usingresolver_to_joinir(&test_module); eprintln!("[joinir/stage1_using_resolver] empty_module test: result is None (expected)"); - assert!(result.is_none(), "Empty MirModule should return None (target function not found)"); + assert!( + result.is_none(), + "Empty MirModule should return None (target function not found)" + ); +} + +#[test] +#[ignore] // 手動実行: generic_case_a トグル検証(stage1_using_resolver minimal) +fn mir_joinir_stage1_using_resolver_generic_matches_handwritten() { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!( + "[joinir/stage1_using_resolver] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test" + ); + return; + } + + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let test_file = "apps/tests/stage1_usingresolver_minimal.hako"; + let src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + + let ast: ASTNode = + NyashParser::parse_from_string(&src).expect("stage1_using_resolver generic: parse failed"); + let mut mc = MirCompiler::with_options(false); + let compiled = mc + .compile(ast) + .expect("stage1_using_resolver generic: MIR compile failed"); + + fn params_by_name(jm: &JoinModule) -> BTreeMap { + jm.functions + .values() + .map(|f| (f.name.clone(), f.params.len())) + .collect() + } + + // Baseline (generic OFF) + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0"); + let baseline = lower_stage1_usingresolver_to_joinir(&compiled.module) + .expect("baseline stage1_using_resolver lowering failed"); + + // Generic ON + std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1"); + let generic = lower_stage1_usingresolver_to_joinir(&compiled.module) + .expect("generic stage1_using_resolver lowering failed"); + + let baseline_params = params_by_name(&baseline); + let generic_params = params_by_name(&generic); + + assert_eq!(baseline_params.len(), 2, "baseline should have 2 functions"); + assert_eq!(generic_params.len(), 2, "generic should have 2 functions"); + assert_eq!( + baseline_params.len(), + generic_params.len(), + "function count mismatch" + ); + + let expected_funcs = ["resolve_entries", "loop_step"]; + for name in expected_funcs { + let b_params = baseline_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("baseline missing function {}", name)); + let g_params = generic_params + .get(name) + .copied() + .unwrap_or_else(|| panic!("generic missing function {}", name)); + assert_eq!( + b_params, g_params, + "param count differs for function {}", + name + ); + } + + eprintln!("[joinir/stage1_using_resolver] ✅ generic_case_a JoinIR matches baseline"); } diff --git a/src/tests/mir_joinir_stageb_body.rs b/src/tests/mir_joinir_stageb_body.rs new file mode 100644 index 00000000..1c479316 --- /dev/null +++ b/src/tests/mir_joinir_stageb_body.rs @@ -0,0 +1,101 @@ +// mir_joinir_stageb_body.rs +// Phase 28: StageBBodyExtractorBox.build_body_src JoinIR 変換テスト +// +// 目的: +// - StageBBodyExtractorBox.build_body_src/2 の Case A ループを JoinIR に落とせることを確認 +// - Pinned/Carrier/Exit の構造を固定化(Pinned: src/args/n, Carrier: acc/i, Exit: acc) +// +// 実行条件: +// - #[ignore] で手動実行。環境変数 NYASH_JOINIR_EXPERIMENT=1 が必要。 + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::stageb_body::lower_stageb_body_to_joinir; +use crate::mir::join_ir::*; +use crate::mir::{MirCompiler, ValueId}; +use crate::parser::NyashParser; + +#[test] +#[ignore] // 手動実行用(実験モードのみ) +fn mir_joinir_stageb_body_auto_lowering() { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!( + "[joinir/stageb_body] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test" + ); + return; + } + + // Stage-3 パーサを有効化(local/loop を安全に扱うため) + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let test_file = "apps/tests/stageb_body_extract_minimal.hako"; + let src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("stageb_body: parse failed"); + + let mut mc = MirCompiler::with_options(false); + let compiled = mc.compile(ast).expect("stageb_body: MIR compile failed"); + + eprintln!( + "[joinir/stageb_body] MIR module compiled, {} functions", + compiled.module.functions.len() + ); + + let join_module = lower_stageb_body_to_joinir(&compiled.module) + .expect("StageBBodyExtractorBox.build_body_src should lower to JoinIR"); + + eprintln!("[joinir/stageb_body] JoinIR module generated:"); + eprintln!("{:#?}", join_module); + + assert_eq!(join_module.functions.len(), 2, "Expected entry + loop_step"); + + let entry_id = JoinFuncId::new(0); + let loop_id = JoinFuncId::new(1); + + let entry_func = join_module + .functions + .get(&entry_id) + .expect("build_body_src function not found"); + assert_eq!(entry_func.name, "build_body_src"); + assert_eq!( + entry_func.params.len(), + 2, + "build_body_src should take (src, args)" + ); + + let loop_func = join_module + .functions + .get(&loop_id) + .expect("loop_step function not found"); + assert_eq!(loop_func.name, "loop_step"); + assert_eq!( + loop_func.params.len(), + 5, + "loop_step should take (src, args, n, acc, i)" + ); + + eprintln!("[joinir/stageb_body] ✅ 自動変換成功(Phase 28)"); +} + +#[test] +fn mir_joinir_stageb_body_type_sanity() { + let entry_id = JoinFuncId::new(42); + let f = JoinFunction::new( + entry_id, + "stageb_body_test".to_string(), + vec![ValueId(1), ValueId(2)], + ); + assert_eq!(f.id, entry_id); + assert_eq!(f.name, "stageb_body_test"); + assert_eq!(f.params.len(), 2); +} + +#[test] +fn mir_joinir_stageb_body_empty_module_returns_none() { + use crate::mir::MirModule; + let test_module = MirModule::new("empty".to_string()); + let result = lower_stageb_body_to_joinir(&test_module); + eprintln!("[joinir/stageb_body] empty_module test => {:?}", result); + assert!(result.is_none()); +} diff --git a/src/tests/mir_joinir_stageb_funcscanner.rs b/src/tests/mir_joinir_stageb_funcscanner.rs new file mode 100644 index 00000000..dc9aade3 --- /dev/null +++ b/src/tests/mir_joinir_stageb_funcscanner.rs @@ -0,0 +1,105 @@ +// mir_joinir_stageb_funcscanner.rs +// Phase 28: StageBFuncScannerBox.scan_all_boxes JoinIR 変換テスト +// +// 目的: +// - StageBFuncScannerBox.scan_all_boxes/1 の Case A ループを JoinIR に落とせることを確認 +// - Pinned/Carrier/Exit の構造を固定化(Pinned: src/n, Carrier: defs/i, Exit: defs) +// +// 実行条件: +// - #[ignore] で手動実行。環境変数 NYASH_JOINIR_EXPERIMENT=1 が必要。 + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::stageb_funcscanner::lower_stageb_funcscanner_to_joinir; +use crate::mir::join_ir::*; +use crate::mir::{MirCompiler, ValueId}; +use crate::parser::NyashParser; + +#[test] +#[ignore] // 手動実行用(実験モードのみ) +fn mir_joinir_stageb_funcscanner_auto_lowering() { + if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") { + eprintln!("[joinir/stageb_funcscanner] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test"); + return; + } + + // Stage-3 パーサを有効化 + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + + let test_file = "apps/tests/stageb_funcscanner_scan_boxes_minimal.hako"; + let src = std::fs::read_to_string(test_file) + .unwrap_or_else(|_| panic!("Failed to read {}", test_file)); + + let ast: ASTNode = + NyashParser::parse_from_string(&src).expect("stageb_funcscanner: parse failed"); + + let mut mc = MirCompiler::with_options(false); + let compiled = mc + .compile(ast) + .expect("stageb_funcscanner: MIR compile failed"); + + eprintln!( + "[joinir/stageb_funcscanner] MIR module compiled, {} functions", + compiled.module.functions.len() + ); + + let join_module = lower_stageb_funcscanner_to_joinir(&compiled.module) + .expect("StageBFuncScannerBox.scan_all_boxes should lower to JoinIR"); + + eprintln!("[joinir/stageb_funcscanner] JoinIR module generated:"); + eprintln!("{:#?}", join_module); + + assert_eq!(join_module.functions.len(), 2, "Expected entry + loop_step"); + + let entry_id = JoinFuncId::new(0); + let loop_id = JoinFuncId::new(1); + + let entry_func = join_module + .functions + .get(&entry_id) + .expect("scan_all_boxes function not found"); + assert_eq!(entry_func.name, "scan_all_boxes"); + assert_eq!( + entry_func.params.len(), + 1, + "scan_all_boxes should take (src)" + ); + + let loop_func = join_module + .functions + .get(&loop_id) + .expect("loop_step function not found"); + assert_eq!(loop_func.name, "loop_step"); + assert_eq!( + loop_func.params.len(), + 4, + "loop_step should take (src, n, defs, i)" + ); + + eprintln!("[joinir/stageb_funcscanner] ✅ 自動変換成功(Phase 28)"); +} + +#[test] +fn mir_joinir_stageb_funcscanner_type_sanity() { + let entry_id = JoinFuncId::new(24); + let f = JoinFunction::new( + entry_id, + "stageb_funcscanner_test".to_string(), + vec![ValueId(1)], + ); + assert_eq!(f.id, entry_id); + assert_eq!(f.name, "stageb_funcscanner_test"); + assert_eq!(f.params.len(), 1); +} + +#[test] +fn mir_joinir_stageb_funcscanner_empty_module_returns_none() { + use crate::mir::MirModule; + let test_module = MirModule::new("empty".to_string()); + let result = lower_stageb_funcscanner_to_joinir(&test_module); + eprintln!( + "[joinir/stageb_funcscanner] empty_module test => {:?}", + result + ); + assert!(result.is_none()); +} diff --git a/src/tests/mir_loopform_complex.rs b/src/tests/mir_loopform_complex.rs index 3fb57dad..9cf97d87 100644 --- a/src/tests/mir_loopform_complex.rs +++ b/src/tests/mir_loopform_complex.rs @@ -82,4 +82,3 @@ fn mir_loopform_nested_region_verify() { } teardown_stage3_env(); } - diff --git a/src/tests/mir_stage1_cli_emit_program_min.rs b/src/tests/mir_stage1_cli_emit_program_min.rs index e38279d6..c0af19b7 100644 --- a/src/tests/mir_stage1_cli_emit_program_min.rs +++ b/src/tests/mir_stage1_cli_emit_program_min.rs @@ -104,11 +104,7 @@ fn mir_stage1_cli_emit_program_min_exec_hits_type_error() { let cr = mc.compile(ast).expect("compile"); // Optional: scan for Compare::Ge instructions to locate suspicious comparisons - if std::env::var("NYASH_STAGE1_SCAN_GE") - .ok() - .as_deref() - == Some("1") - { + if std::env::var("NYASH_STAGE1_SCAN_GE").ok().as_deref() == Some("1") { for (fname, func) in cr.module.functions.iter() { for (bb_id, bb) in func.blocks.iter() { for inst in bb.instructions.iter() { diff --git a/src/tests/mir_stage1_staticcompiler_receiver.rs b/src/tests/mir_stage1_staticcompiler_receiver.rs index f5cb985e..85e0c340 100644 --- a/src/tests/mir_stage1_staticcompiler_receiver.rs +++ b/src/tests/mir_stage1_staticcompiler_receiver.rs @@ -40,8 +40,7 @@ fn ensure_stage3_env() { /// Stage‑1 CLI 全体を読み込まずに、StringHelpers 内部の `.length()` 正規化だけを確認する。 fn stage1_staticcompiler_fixture_src() -> String { // StringHelpers 本体を直接バンドルして using 依存を排除。 - let string_helpers = - include_str!("../../lang/src/shared/common/string_helpers.hako"); + let string_helpers = include_str!("../../lang/src/shared/common/string_helpers.hako"); let test_main_src = r#" using lang.src.shared.common.string_helpers as StringHelpers diff --git a/src/tests/mir_stage1_using_resolver_verify.rs b/src/tests/mir_stage1_using_resolver_verify.rs index f0c26f5f..de71287f 100644 --- a/src/tests/mir_stage1_using_resolver_verify.rs +++ b/src/tests/mir_stage1_using_resolver_verify.rs @@ -743,8 +743,6 @@ static box Stage1UsingResolverModulesMapContinueBreakLookup { for e in &errors { eprintln!("[rust-mir-verify] {}", e); } - panic!( - "MIR verification failed for Stage1UsingResolverModulesMapContinueBreakLookup" - ); + panic!("MIR verification failed for Stage1UsingResolverModulesMapContinueBreakLookup"); } } diff --git a/src/tests/mir_static_box_naming.rs b/src/tests/mir_static_box_naming.rs index df55859d..0357aa0d 100644 --- a/src/tests/mir_static_box_naming.rs +++ b/src/tests/mir_static_box_naming.rs @@ -76,11 +76,7 @@ fn mir_static_main_box_executes_void_path_with_guard() { // Just ensure the symbol exists; methodization may optimize away the actual call. assert!( - compiled - .module - .functions - .keys() - .any(|k| k == "Main._nop/0"), + compiled.module.functions.keys().any(|k| k == "Main._nop/0"), "Main._nop/0 should remain defined" ); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 79a69452..e977c86b 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -6,38 +6,40 @@ pub mod identical_exec; pub mod identical_exec_collections; pub mod identical_exec_instance; pub mod identical_exec_string; -pub mod mir_breakfinder_ssa; -pub mod mir_funcscanner_skip_ws; -pub mod mir_funcscanner_parse_params_trim_min; -pub mod mir_funcscanner_trim_min; -pub mod mir_funcscanner_ssa; -pub mod mir_joinir_min; // Phase 26-H: JoinIR型定義妥当性確認 -pub mod mir_joinir_skip_ws; // Phase 27.0: minimal_ssa_skip_ws JoinIR変換 -pub mod mir_joinir_funcscanner_trim; // Phase 27.1: FuncScannerBox.trim JoinIR変換 -pub mod mir_joinir_stage1_using_resolver_min; // Phase 27.12: Stage1UsingResolverBox.resolve_for_source JoinIR変換 -pub mod mir_joinir_funcscanner_append_defs; // Phase 27.14: FuncScannerBox._append_defs JoinIR変換 pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test +pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix +pub mod mir_breakfinder_ssa; +pub mod mir_funcscanner_parse_params_trim_min; +pub mod mir_funcscanner_skip_ws; +pub mod mir_funcscanner_ssa; +pub mod mir_funcscanner_trim_min; +pub mod mir_joinir_funcscanner_append_defs; // Phase 27.14: FuncScannerBox._append_defs JoinIR変換 +pub mod mir_joinir_funcscanner_trim; // Phase 27.1: FuncScannerBox.trim JoinIR変換 +pub mod mir_joinir_min; // Phase 26-H: JoinIR型定義妥当性確認 +pub mod mir_joinir_skip_ws; // Phase 27.0: minimal_ssa_skip_ws JoinIR変換 +pub mod mir_joinir_stage1_using_resolver_min; // Phase 27.12: Stage1UsingResolverBox.resolve_for_source JoinIR変換 +pub mod mir_joinir_stageb_body; // Phase 28: StageBBodyExtractorBox.build_body_src JoinIR変換 +pub mod mir_joinir_stageb_funcscanner; // Phase 28: StageBFuncScannerBox.scan_all_boxes JoinIR変換 pub mod mir_locals_ssa; +pub mod mir_loopform_complex; pub mod mir_loopform_conditional_reassign; pub mod mir_loopform_exit_phi; -pub mod mir_loopform_complex; -pub mod mir_static_box_naming; pub mod mir_stage1_cli_emit_program_min; pub mod mir_stage1_staticcompiler_receiver; // Phase 25.1: StaticCompiler receiver型推論バグ回帰防止 pub mod mir_stage1_using_resolver_verify; -pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix -pub mod namingbox_static_method_id; // Phase 21.7++ Phase 1: StaticMethodId structure tests -pub mod stage1_cli_entry_ssa_smoke; pub mod mir_stageb_like_args_length; pub mod mir_stageb_loop_break_continue; pub mod mir_stageb_string_utils_skip_ws; // Phase 25.1: skip_ws Void < 0 TypeError 再現 +pub mod mir_static_box_naming; pub mod mir_value_kind; // Phase 26-A-5: ValueId型安全化統合テスト +pub mod namingbox_static_method_id; // Phase 21.7++ Phase 1: StaticMethodId structure tests pub mod nyash_abi_basic; pub mod parser_static_box_members; pub mod plugin_hygiene; pub mod policy_mutdeny; +pub mod stage1_cli_entry_ssa_smoke; pub mod sugar_basic_test; pub mod sugar_coalesce_test; pub mod sugar_comp_assign_test; diff --git a/src/tests/namingbox_static_method_id.rs b/src/tests/namingbox_static_method_id.rs index 6e28ccf7..469819d2 100644 --- a/src/tests/namingbox_static_method_id.rs +++ b/src/tests/namingbox_static_method_id.rs @@ -59,11 +59,7 @@ fn test_with_arity() { #[test] fn test_round_trip() { - let cases = vec![ - "Main._nop/0", - "StringUtils.starts_with/2", - "Console.log/1", - ]; + let cases = vec!["Main._nop/0", "StringUtils.starts_with/2", "Console.log/1"]; for case in cases { let id = StaticMethodId::parse(case).unwrap();