From 80f8a7bc8c65a8c73b3b7eb11ab1477b6765fbc9 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Wed, 19 Nov 2025 00:02:41 +0900 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Hotfix=207=20(Enhanced):=20Value?= =?UTF-8?q?Id=20receiver=20alias=20tracking=20for=20nested=20loops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Problem: Pinned receiver variables in loops cause undefined ValueId errors - Enhanced fix: Update all receiver aliases (me + all __pin$N$@recv levels) - Handles nested loops by updating previous pin levels - Test status: Partial improvement, ValueId(50) → ValueId(40) - Further investigation needed for complete fix Files modified: - src/mir/phi_core/loopform_builder.rs (emit_header_phis) --- CURRENT_TASK.md | 10 + .../roadmap/phases/phase-25.1g/README.md | 214 ++++++++++++------ .../roadmap/phases/phase-25.1i/README.md | 114 ++++++++++ .../roadmap/phases/phase-25.1j/README.md | 125 ++++++++++ .../roadmap/phases/phase-25.1k/README.md | 90 ++++++++ .../builder/ssa/exit_phi/break_finder.hako | 140 +++++++----- .../builder/ssa/exit_phi/phi_injector.hako | 196 +++++++++------- lang/src/compiler/builder/ssa/loopssa.hako | 115 +++++----- lang/src/compiler/entry/compiler_stageb.hako | 3 +- lang/src/compiler/entry/func_scanner.hako | 18 +- .../tests/loopssa_breakfinder_min.hako | 38 ++++ lang/src/shared/mir/control_form_box.hako | 80 +++++-- nyash.toml | 2 + src/mir/phi_core/loopform_builder.rs | 26 +++ tools/dump_stageb_min_mir.sh | 48 ++++ tools/test_loopssa_breakfinder_min.sh | 30 +++ tools/test_stageb_min.sh | 11 + 17 files changed, 983 insertions(+), 277 deletions(-) create mode 100644 docs/development/roadmap/phases/phase-25.1i/README.md create mode 100644 docs/development/roadmap/phases/phase-25.1j/README.md create mode 100644 docs/development/roadmap/phases/phase-25.1k/README.md create mode 100644 lang/src/compiler/tests/loopssa_breakfinder_min.hako create mode 100644 tools/dump_stageb_min_mir.sh create mode 100644 tools/test_loopssa_breakfinder_min.sh diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 3832d189..6b7d99b3 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,5 +1,15 @@ # Current Task — Phase 21.8 / 25 / 25.1 / 25.2(Numeric Core & Stage0/Stage1 Bootstrap) +Update (2025-11-18 — Phase 25.1i: LoopSSA v2 (.hako) & ControlFormBox 統合の設計開始) +- Context: + - Rust 側の LoopForm v2 / Conservative PHI / ControlForm 統合(25.1g)まで完了し、Loop/If の SSA/PHI は ControlForm ベースで安定。 + - Stage‑B 最小ハーネス `tools/test_stageb_min.sh` は、Test1=緑、Test2/3 は `.hako` 側 LoopSSA / BreakFinderBox / PhiInjectorBox 起源のエラーが残っている状態。 +- Plan (25.1i): + - LoopSSA (`lang/src/compiler/builder/ssa/loopssa.hako`) の責務を ControlFormBox 前提に整理し、Rust 側 LoopShape/IfShape に揃えた説明を書く。 + - `lang/src/shared/mir/control_form_box.hako` に `from_loop` / `from_if` などの最小メソッドを追加し、JSON v0 から loop/if 形を復元できる足場を作る。 + - BreakFinderBox / PhiInjectorBox はひとまず現状維持としつつ、ControlFormBox を生成してログに出す観測導線を追加(挙動はまだ変えない)。 + - `HAKO_COMPILER_BUILDER_TRACE=1` で Stage‑B Test2/3 実行時に loop/exit/body の構造が観測できるようにし、undefined ValueId / `%0` の原因追跡に備える。 + Update (2025-11-18 — Phase 25.1g: Conservative PHI ↔ ControlForm 統合の準備) - Context: - 25.1f までに Rust 側の ControlForm(LoopShape / IfShape + ControlForm)と .hako 側の ControlFormBox を定義し、観測レイヤーは安定した。 diff --git a/docs/development/roadmap/phases/phase-25.1g/README.md b/docs/development/roadmap/phases/phase-25.1g/README.md index 2fdac3c1..60acfd7b 100644 --- a/docs/development/roadmap/phases/phase-25.1g/README.md +++ b/docs/development/roadmap/phases/phase-25.1g/README.md @@ -1,6 +1,6 @@ -# Phase 25.1g — Conservative PHI ↔ ControlForm 統合(設計+小さな導線) +# Phase 25.1g — Conservative PHI ↔ ControlForm 統合(Rust統合完了) -Status: planning(設計・導線追加/既存挙動は変えない) +Status: completed(ControlForm 導線統合+レガシー縮退/挙動は不変) ## ゴール @@ -42,77 +42,163 @@ Status: planning(設計・導線追加/既存挙動は変えない) - Loop 用: `build_exit_phis_for_control(form: &ControlForm, ...)` を追加。 - 最初のステップでは「ControlForm から必要な BlockId を取り出して、既存の関数に委譲するだけ」の薄いラッパにする。 -## タスク粒度 +## 実施内容 -### G‑1: If 用 Conservative PHI への ControlForm 導線 +### G‑1: If 用 Conservative PHI への ControlForm 導線(完了) - 目的: - - `loop_builder.rs::lower_if_in_loop` から、`ControlForm::If` を PHI ロジックに渡せるようにする。 -- ステップ: - 1. `src/mir/phi_core/if_phi.rs` に薄いラッパ関数を追加: - ```rust - pub fn merge_modified_with_control( - ops: &mut O, - form: &crate::mir::control_form::ControlForm, - pre_if_snapshot: &HashMap, - then_map_end: &HashMap, - else_map_end_opt: &Option>, - skip_var: Option<&str>, - ) -> Result<(), String> { - // ControlForm::If から cond/then/else/merge を取り出し、 - // 既存の merge_modified_at_merge_with に橋渡しするだけ(ロジックは変えない)。 - } - ``` - 2. `lower_if_in_loop` 側で: - - 既存の `merge_modified_at_merge_with` 呼び出しの直前/直後にコメントを付け、 - - 将来的に `merge_modified_with_control` に置き換えられるように位置を明示する(25.1g ではまだ呼び替えない or dev フラグで限定)。 - 3. テスト: - - `cargo test -q mir_stage1_using_resolver_min_fragment_verifies -- --nocapture` - - `cargo test -q mir_stage1_using_resolver_full_collect_entries_verifies -- --nocapture` - - 必要なら `NYASH_CONTROL_FORM_TRACE=1` で IfShape ログを確認し、BlockId 対応が設計どおりかを確認。 + - `loop_builder.rs::lower_if_in_loop` から `ControlForm::If` を PHI ロジックに渡し、Conservative PHI Box が ControlForm ベースで動けるようにする。 +- 実装: + - `src/mir/phi_core/if_phi.rs` に ControlForm ベースの薄いラッパを追加: + ```rust + pub fn merge_modified_with_control( + ops: &mut O, + form: &crate::mir::control_form::ControlForm, + pre_if_snapshot: &HashMap, + then_map_end: &HashMap, + else_map_end_opt: &Option>, + skip_var: Option<&str>, + then_pred_opt: Option, + else_pred_opt: Option, + ) -> Result<(), String> { + use crate::mir::control_form::ControlKind; -### G‑2: LoopForm v2 Exit PHI への ControlForm 導線 + let shape = match &form.kind { + ControlKind::If(shape) => shape, + _ => return Ok(()), + }; + + let merge_bb = shape.merge_block; + + let trace = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1"); + if trace { + eprintln!( + "[if-phi/control-form] Using ControlForm wrapper: merge={:?} then={:?} else={:?}", + merge_bb, shape.then_block, shape.else_block + ); + } + + merge_modified_at_merge_with( + ops, + merge_bb, + shape.then_block, + shape.else_block.unwrap_or(shape.then_block), + then_pred_opt, + else_pred_opt, + pre_if_snapshot, + then_map_end, + else_map_end_opt, + skip_var, + ) + } + ``` + - `src/mir/loop_builder.rs::lower_if_in_loop` では、IfShape→ControlForm を構築してこのラッパを直接呼び出すように統合: + ```rust + let if_shape = IfShape { + cond_block: pre_branch_bb, + then_block: then_bb, + else_block: Some(else_bb), + merge_block: merge_bb, + }; + let form = ControlForm::from_if(if_shape.clone()); + + crate::mir::phi_core::if_phi::merge_modified_with_control( + &mut ops, + &form, + &pre_if_var_map, + &then_var_map_end, + &else_var_map_end_opt, + None, + Some(then_pred_to_merge), + Some(else_pred_to_merge), + )?; + ``` + - その後ろで `is_control_form_trace_on()` が true のときに `form.debug_dump()` と `if_shape.debug_validate()` を呼び、 + If の形を ControlForm 経由で常時観測できるようにした。 +- テスト: + - `cargo test -q mir_stage1_using_resolver_min_fragment_verifies -- --nocapture` + - `cargo test -q mir_stage1_using_resolver_full_collect_entries_verifies -- --nocapture` + - いずれも PASS(Conservative PHI の挙動変化なし)。 + +### G‑2: LoopForm v2 Exit PHI への ControlForm 導線(完了) - 目的: - - LoopForm v2 Exit PHI(`loopform_builder.rs::build_exit_phis`)にも ControlForm ベースの入口を用意する。 -- ステップ: - 1. `src/mir/phi_core/loopform_builder.rs` にラッパ関数追加: - ```rust - pub fn build_exit_phis_for_control( - loopform: &LoopFormBuilder, - ops: &mut O, - form: &crate::mir::control_form::ControlForm, - exit_snapshots: &[(BasicBlockId, HashMap)], - ) -> Result<(), String> { - // form.kind が Loop の場合に限り、 - // shape.preheader/header/body/latch/exit から exit_id/branch_source_block を決めて - // 既存の build_exit_phis に委譲するだけ。 - } - ``` - 2. `LoopBuilder::build_loop_with_loopform` からは: - - 現在どおり `loopform.build_exit_phis(self, exit_id, branch_source_block, &exit_snaps)` を呼び続ける。 - - コメントで「ControlForm 経由の入口」があることを明示し、将来の切り替えポイントを固定する。 - 3. テスト: - - `cargo test -q mir_loopform_exit_phi -- --nocapture` - - `cargo test -q mir_stageb_loop_break_continue -- --nocapture` - - `./tools/test_stageb_min.sh` を流して、Exit PHI 誘発系の赤ログが増えていないことを確認。 + - LoopForm v2 Exit PHI が ControlForm(LoopShape)を入口に使えるようにする。 +- 実装: + - `src/mir/phi_core/loopform_builder.rs` に ControlForm 用ラッパを追加: + ```rust + pub fn build_exit_phis_for_control( + loopform: &LoopFormBuilder, + ops: &mut O, + form: &crate::mir::control_form::ControlForm, + exit_snapshots: &[(BasicBlockId, HashMap)], + branch_source_block: BasicBlockId, + ) -> Result<(), String> { + use crate::mir::control_form::ControlKind; -### G‑3: ControlForm ↔ Conservative PHI の設計メモ更新 + let shape = match &form.kind { + ControlKind::Loop(shape) => shape, + _ => return Ok(()), + }; + + let exit_id = shape.exit; + let trace = std::env::var("NYASH_LOOPFORM_DEBUG").ok().is_some(); + if trace { + eprintln!( + "[loopform/exit-phi/control-form] Using ControlForm wrapper: exit={:?} branch_source={:?}", + exit_id, branch_source_block + ); + } + + loopform.build_exit_phis(ops, exit_id, branch_source_block, exit_snapshots) + } + ``` + - `src/mir/loop_builder.rs::build_loop_with_loopform` では、Exit PHI 部分をこのラッパ経由に統一し、 + その直後で `ControlForm::Loop` の `debug_dump` / `debug_validate` を行うように整理: + ```rust + let loop_shape = LoopShape { + preheader: preheader_id, + header: header_id, + body: body_id, + latch: latch_id, + exit: exit_id, + continue_targets, + break_targets, + }; + let form = ControlForm::from_loop(loop_shape.clone()); + + let exit_snaps = self.exit_snapshots.clone(); + crate::mir::phi_core::loopform_builder::build_exit_phis_for_control( + &loopform, + self, + &form, + &exit_snaps, + branch_source_block, + )?; + + if is_control_form_trace_on() { + form.debug_dump(); + #[cfg(debug_assertions)] + if let Some(ref func) = self.parent_builder.current_function { + loop_shape.debug_validate(func); + } + } + ``` +- テスト: + - `cargo test -q mir_loopform_exit_phi -- --nocapture` + - `cargo test -q mir_stageb_loop_break_continue -- --nocapture` + - いずれも PASS。Exit PHI の SSA/PHI 挙動は従来と同じで、ControlForm ベースでも構造が崩れていないことを確認。 + +### G‑3: ControlForm ↔ Conservative PHI の設計メモ更新(完了) - 目的: - 25.1d/e/f/g の成果をまとめ、「If/Loop の PHI が最終的にどのレイヤで SSOT を持つか」を文書で固定する。 -- ステップ: - - `phase-25.1d/README.md` と `phase-25.1f/README.md` をリンクしつつ、 - - Conservative PHI Box の責務(If/Loop 両方) - - ControlForm レイヤの責務 - - .hako 側 `ControlFormBox` / `LoopSSA` の予定 - を Phase 25 全体の流れの中に整理して追記する。 - - `CURRENT_TASK.md` に 25.1g の短いサマリを追加して、今どこまで進めたかをいつでも追えるようにする。 +- 実施: + - 本 README と `CURRENT_TASK.md` に、Conservative PHI Box と ControlForm レイヤの役割分担を整理して追記。 + - 25.1g は「Rust 側での Conservative PHI ↔ ControlForm 統合(If/Loop)完了」フェーズとして締める。 -## このフェーズで「しない」こと +## このフェーズで残っていること / 先送りしたこと -- 既存の Conservative PHI 実装を一気に ControlForm 専用に書き換えること: - - 25.1g は「導線追加+ラッパ」「設計固め」まで。 - - 実際の置き換え(Option B の本体)は、さらに小さなステップに分けて後続フェーズで行う。 -- LoopBuilder/If 降下の大規模リファクタ(Option C 相当): - - これは Phase 25.2 以降の仕事として残しておく。 +- `.hako` 側 LoopSSA / Stage‑B パイプラインはまだ ControlFormBox 未統合のまま。 + - Test 3(Stage‑B MIR verify)の `%0` 問題や、BreakFinderBox 周辺の undefined ValueId は次フェーズ(LoopSSA 実装側)で扱う。 +- LoopBuilder/If 降下そのものを ControlForm ベースに再構成する「Option C」は、Phase 25.2 以降の大きめリファクタとして残しておく。 diff --git a/docs/development/roadmap/phases/phase-25.1i/README.md b/docs/development/roadmap/phases/phase-25.1i/README.md new file mode 100644 index 00000000..0b89a4f6 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1i/README.md @@ -0,0 +1,114 @@ +# Phase 25.1i — LoopSSA v2 (.hako) & ControlFormBox 統合(Stage‑B 入口) + +Status: in-progress(.hako 側 LoopSSA 設計+足場実装/Rust挙動は変えない) + +## ゴール + +- Rust 側で整えた LoopForm v2 / ControlForm(LoopShape / IfShape)を、.hako 側 LoopSSA にも導入し、 + Stage‑B 最小ハーネス(`tools/test_stageb_min.sh`)で見えている「exit PHI / break 周りの赤ログ」に構造的にアプローチできる足場を作る。 +- 25.1i のスコープでは、まず: + - LoopSSA の責務を ControlFormBox 中心の設計に書き換える(設計+薄い実装)。 + - 既存の文字列ベース `_find_loops` / `_collect_phi_vars` を温存しつつ、 + ControlFormBox に loop/if の形を写し取るための API を導入する。 + - Stage‑B Test 2/3 の赤ログ(undefined ValueId / `%0`)は「再現と観測」まで(このフェーズで「必ず全部直す」とはしない)。 + +## 現状(25.1g/25.1i 途中時点) + +- Rust: + - Loop/If: + - LoopForm v2 + Conservative PHI Box が ControlForm(LoopShape/IfShape)経由で統合済み。 + - レガシー `build_loop_legacy` / 旧ヘルパーは削除済み(LoopForm v2 が唯一のループ構築経路)。 + - テスト: + - `mir_loopform_exit_phi` / `mir_stage1_using_resolver_*` / `mir_stageb_loop_break_continue` 緑。 + - `tools/test_stageb_min.sh`: + - Test 1(直接 VM 実行): 0 exit。 + - Test 2(Stage‑B 経由): `BreakFinderBox.find_breaks/1` まわりで undefined ValueId(96)。 + - Test 3(MIR verify): `%0` 由来の undefined value が残っている。 +- .hako: + - `lang/src/compiler/builder/ssa/loopssa.hako`: + - `LoopSSA.stabilize_merges(stage1_json)` が BreakFinderBox + PhiInjectorBox を呼び出す簡易パイプライン。 + - `lang/src/compiler/builder/ssa/exit_phi/break_finder.hako`: + - 文字列ベースで `"loop_header":` / `"loop_exit":` マーカーを探して `_find_loops`。 + - loop body を「header_id < id < exit_id のブロック群」として推定する `_find_loop_body`。 + - break を「jump の target が exit_id の block」として検出。 + - `lang/src/compiler/builder/ssa/exit_phi/phi_injector.hako`: + - 「共通変数名のリスト(i, n, item, ...)」から `_block_uses_var` で使用有無チェック → `_get_var_value` で synthetic value_id を組み立てて PHI JSON を注入する簡易版。 + - `lang/src/shared/mir/control_form_box.hako`: + - `static box ControlFormBox`(kind_name + loop_* / if_* + entry/exits)の箱を定義。 + - 25.1i で「未来構文」だった `field: TypeBox` 形式を撤去し、現行構文に合わせたフィールド宣言(`kind_name` など)に修正済み。 + - `from_loop` / `from_if` を追加し、LoopSSA/BreakFinderBox から loop/if 形を写し取るための足場として利用開始。 + - Stage‑B ハーネス: + - `tools/test_stageb_min.sh` Test 2 の `compiler_stageb.hako` parse error(`Unexpected token COLON`)は ControlFormBox の型注釈撤去と + `using lang.compiler.parser.parser_box as ParserBox` への修正で解消。 + - 現在は `MIR compilation error: Undefined variable: trace` で停止しており、これは LoopSSA/PhiInjector 側の未実装ロジック由来として + 次フェーズ(25.1j 以降)のターゲットに残している。 + +## 方針(LoopSSA v2 / ControlFormBox 作戦) + +- 25.1i では **一気に全部作り替えない**: + - 既存の BreakFinderBox / PhiInjectorBox は当面残しつつ、 + ControlFormBox 経由で loop/if の形を扱うための導線を増やす。 + - まずは「LoopSSA / BreakFinderBox の責務分割」と ControlFormBox の利用ポイントを設計として固定する。 +- 段階: + 1. LoopSSA 設計の整理(LoopScope / IfScope / Carrier/Pinned を .hako に翻訳) + 2. ControlFormBox を使った loop/if 形の復元 API を追加 + 3. BreakFinderBox / PhiInjectorBox の「入力/出力」を ControlFormBox 前提に徐々に寄せる + 4. Stage‑B Test2/3 で undefined ValueId / `%0` の原因箇所を観測し、次フェーズで本格修正 + +## タスク粒度(25.1i) + +### I‑1: LoopSSA / ControlFormBox の責務整理(設計) + +- 目的: + - LoopSSA フェーズで「何をやるべきか」を、Rust 側 LoopForm v2 / ControlForm の設計に揃えて文章化する。 +- ステップ: + - `LoopSSA.stabilize_merges` のコメントを拡張し、 + - input: Stage‑1 JSON v0(単一関数 or Program) + - output: exit PHI が入った JSON v0 + - 内部ステップ: BreakFinderBox → LoopForm v2 的な Exit PHI パターンを .hako で再現 + を書く。 + - ControlFormBox に対応する Rust 側 LoopShape/IfShape のフィールドと責務を README で対応づける。 + +### I‑2: ControlFormBox ユーティリティの追加(Layer 2) + +- 目的: + - `ControlFormBox` を単なるフィールド集合から、実際に JSON v0 から Loop/If 形を復元するための「ミニモデル」に昇格させる。 +- ステップ: + - `lang/src/shared/mir/control_form_box.hako` に、最低限のメソッドを追加: + - `from_loop(header_id, exit_id, body_blocks)`: + - header / exit / body の block id 群を受け取り、 + - `kind_name = "loop"` + - `entry = header_id` or preheader 相当 + - `loop_header` / `loop_exit` / `loop_body` / `loop_preheader` / `loop_latch` を設定。 + - 初期は「header/exit/body の簡易版」で OK(preheader/latch は後から詰める)。 + - `from_if(cond_block, then_block, else_block, merge_block)`: + - IfShape に対応する形で `kind_name = "if"` と各フィールドを設定。 + - これらはまだ LoopSSA からは呼ばず、ユニットテスト(小さな JSON 片 or 擬似値)で構築が通ることだけ確認。 + +### I‑3: BreakFinderBox を ControlFormBox ベースに寄せる準備 + +- 目的: + - ループの header/exit/body を ControlFormBox に落とし込む第一歩を作る。 +- ステップ: + - `BreakFinderBox._find_loops` で作っている `loop_info`(header/exit/body)に対して: + - `ControlFormBox.from_loop(header_id, exit_id, body_blocks)` を呼び出して ControlFormBox を生成。 + - trace=1 のとき `"[loopssa/control] Loop header=..., exit=..., body=[...]"` のようなログを追加。 + - まだ `LoopSSA.stabilize_merges` の戻り値には影響させない(従来どおり breaks → PhiInjectorBox のまま)。 + +### I‑4: Stage‑B Test2/3 の観測ポイント整備 + +- 目的: + - `tools/test_stageb_min.sh` の Test 2/3 実行時に、LoopSSA/ControlFormBox まわりの情報を拾えるようにする。 +- ステップ: + - `HAKO_COMPILER_BUILDER_TRACE=1` 時のログに、LoopSSA と BreakFinderBox/ControlFormBox の状況を追加: + - 例: `[loopssa] loop count=N`, `[loopssa/control] loop#i header=..., exit=..., body=[...]` + - `.hako` 側ではまだ Exit PHI を ControlFormBox 経由にはしていないので、 + 「どの loop/exit に対してどんな breaks が検出されているか」を見るところまでで止める。 + +## このフェーズで「しない」こと + +- `.hako` 側 LoopSSA で本格的に Exit PHI を再実装すること: + - synthetic value_id をやめて Rust 側同等の SSA/PHI を .hako で完全再現するのは、次フェーズの大仕事として分ける。 +- Stage‑B 最小ハーネスの赤ログを「必ずゼロにする」こと: + - 25.1i はあくまで LoopSSA v2 / ControlFormBox を設計・組み込み始めるフェーズ。 + - undefined ValueId / `%0` 問題の根治は 25.1j 以降のターゲットにする。 diff --git a/docs/development/roadmap/phases/phase-25.1j/README.md b/docs/development/roadmap/phases/phase-25.1j/README.md new file mode 100644 index 00000000..1cfe4626 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1j/README.md @@ -0,0 +1,125 @@ +# Phase 25.1j — LoopSSA v2 本体 & Stage‑B harness 強化 + +Status: planning(.hako 側 LoopSSA v2 本体/Rust SSA/PHI は触らない) + +## ゴール + +- 25.1i で整えた **LoopSSA + ControlFormBox 観測レイヤー** の上に、.hako 側 LoopSSA v2 の「本体」を載せる準備をする。 + - 特に Stage‑B minimal harness(`tools/test_stageb_min.sh`)Test 2 で出ている + `MIR compilation error: Undefined variable: trace` を構造的に解消する。 + - 文字列ハードコードベースの Exit PHI 注入(`_collect_phi_vars` / synthetic `"r{block}_{var}"`)は **即座には捨てず**、 + 将来の v2 実装に移行しやすいよう責務を整理する。 +- このフェーズでは: + - LoopSSA / BreakFinderBox / PhiInjectorBox の **trace 変数の扱い**とスコープを整理し、 + 「どこで ENV を読むか」「どこまで Box 内のローカルで閉じるか」を決める。 + - Stage‑B Test 2 が Program(JSON v0) 出力まで進み、Rust 側 MirBuilder の SSA/PHI 検証に到達できる状態を作る。 + - Stage‑B Test 3 の `%0`/SSA 問題については「LoopSSA v2 が悪化させていない」ことの確認まで(根治は次フェーズ)。 + +## 前提(25.1i までで揃っているもの) + +- Rust: + - Loop/If: + - LoopForm v2 + Conservative PHI Box が ControlForm(`LoopShape`/`IfShape`)経由で統合済み。 + - レガシー `build_loop_legacy` は削除済みで、LoopForm v2 が唯一のループ構築経路。 + - テスト: + - `mir_loopform_exit_phi` / `mir_stage1_using_resolver_*` / `mir_stageb_loop_break_continue` は緑。 +- `.hako`: + - `lang/src/compiler/builder/ssa/loopssa.hako`: + - `LoopSSA.stabilize_merges(stage1_json)` が `BreakFinderBox.find_breaks` → `PhiInjectorBox.inject_exit_phis` の簡易パイプライン。 + - `HAKO_LOOPSSA_EXIT_PHI` で Exit PHI 注入の ON/OFF を制御している。 + - `lang/src/compiler/builder/ssa/exit_phi/break_finder.hako`: + - 文字列ベースで `"loop_header":` / `"loop_exit":` を探す `_find_loops`。 + - `header_id < id < exit_id` の範囲で body block を推定する `_find_loop_body`。 + - break を「`jump` terminator の `target` が `exit_id`」な block として検出。 + - `HAKO_COMPILER_BUILDER_TRACE=1` で `[break-finder] …` と `[loopssa/control] …` ログを出す。 + - `lang/src/shared/mir/control_form_box.hako`: + - 現行構文で `ControlFormBox` を定義(`kind_name` / `entry` / `exits` + loop/if 用フィールド)。 + - `from_loop(header_id, exit_id, body_blocks)` / `from_if(cond, then, else, merge)` で Loop/If の形を復元。 + - `lang/src/compiler/entry/compiler_stageb.hako`: + - Stage‑B entry が `CompilerBuilder.apply_all(ast_json)` を通した後の JSON を出力する構成。 + - 25.1i で `using lang.compiler.parser.parser_box as ParserBox` 等の修正により parse error は解消済み。 +- Stage‑B minimal harness: + - `tools/test_stageb_min.sh`: + - Test1: 直接 VM 実行 → RC=0。 + - Test2: Stage‑B 経由 → LoopSSA/Exit PHI 経路まで進むが最終的に + `MIR compilation error: Undefined variable: trace` で停止(LoopSSA v2 経路における trace スコープの問題)。 + - Test3: `NYASH_VM_VERIFY_MIR=1` で `%0` undefined 由来の SSA 問題が残っている(根本は LoopSSA だけとは限らない)。 + +## 方針(25.1j: LoopSSA v2 本体の入口を整える) + +### J‑A: LoopSSA/BREAK/PHI 周辺の trace 変数スコープの整理 + +- 目的: + - `.hako` 側 LoopSSA パスの中で `trace` という名前が **常にローカル or Box フィールドとして定義されている** 状態にする。 + - Stage‑B Test2 における `Undefined variable: trace` を根治し、Program(JSON v0) 出力まで進める。 +- 方針: + - 「ENV から読む責務」と「boolean フラグを渡して使う責務」を分離する。 + - 具体的には: + - `LoopSSA.stabilize_merges` の先頭で `local builder_trace = env.get("HAKO_COMPILER_BUILDER_TRACE")` を読む。 + - `BreakFinderBox.find_breaks` / `PhiInjectorBox.inject_exit_phis` には **数値フラグ `trace_flag`** を第3引数として渡す(0/1)。 + - 各 Box 内では `local trace = trace_flag`(または `me.trace` フィールド)として閉じた名前にする。 + - 既存の `local trace = env.get("HAKO_COMPILER_BUILDER_TRACE")` は LoopSSA に集約し、下位 Box は「引数でもらったフラグだけを見る」構造に変える。 + +### J‑B: LoopSSA v2 の責務再定義(設計) + +- 目的: + - `LoopSSA.stabilize_merges` が「何を受け取り、どこまで責任を持つか」を Rust LoopForm v2 に揃えた形で **明文化** する。 + - Stage‑B / LoopSSA / BreakFinderBox / PhiInjectorBox の責務分割を Box 単位で固定し、将来の v2 実装時も迷わない足場を作る。 +- 方針: + - LoopSSA パスを 3 層に分解して設計を書く: + 1. **LoopSSA(オーケストレータ箱)** + - input: Stage‑1 Program(JSON v0)(単一関数 or Program 全体)。 + - output: Loop break に対する exit PHI が挿入された Program(JSON v0)。 + - 責務: + - dev トレースフラグ(`HAKO_COMPILER_BUILDER_TRACE`)と機能フラグ(`HAKO_LOOPSSA_EXIT_PHI`)を解釈。 + - `BreakFinderBox.find_breaks(stage1_json, trace_flag)` を呼んで Loop + break 情報を収集。 + - `PhiInjectorBox.inject_exit_phis(stage1_json, breaks, trace_flag)` に処理を委譲。 + - 例外時は JSON を変更せず Fail‑Fast する(「部分的に壊れた JSON を返さない」)。 + 2. **BreakFinderBox(解析箱 / read‑only)** + - input: Stage‑1 Program(JSON v0)、trace_flag。 + - output: break 情報の配列(`[{block_id, exit_id, loop_header}, …]`)。 + - 責務: + - `"loop_header":NNN` / `"loop_exit":MMM` のペアを見つけ、単純な LoopScope を構成する。 + - ControlFormBox に Loop 形(header/exit/body)を写し取る(観測用)。 + - JSON 文字列は一切書き換えない(解析専用)。 + 3. **PhiInjectorBox(変換箱 / write‑only)** + - input: Stage‑1 Program(JSON v0)、break 情報配列、trace_flag。 + - output: Exit PHI 相当の命令を instructions 配列の先頭に挿入した Program(JSON v0)。 + - 責務: + - break ごとの incoming 値を集約し、`phi_vars = [{name, incoming:[{block,value},…]}, …]` を構成。 + - Exit block の `"instructions":[ … ]` の直後に PHI 相当 JSON をテキスト挿入する。 + - 既存の命令配列順を壊さない(PHI は「先頭に」挿入するのみ)。 + - Carrier / Pinned / Invariant の扱いは **設計としてだけ** 固める: + - Carrier: ループ内で値が更新され、exit でも必要になる変数(Rust 側 Carrier と対応)。 + - Pinned: pin 付き一時値(`__pin$…`)など、観測目的で特別扱いする変数。 + - Invariant: ループ外で定義され、ループ内で再定義されない値(PHI 不要)。 + - 25.1j では **Exit PHI のアルゴリズム自体はまだ現行の simple 版のまま** としつつ、 + LoopSSA / BreakFinderBox / PhiInjectorBox の責務境界だけを README とソースコードコメントで明確にする。 + +### J‑C: BreakFinder v2 への足場(ControlFormBox の本格利用準備) + +- 目的: + - 既存の `loop_info = {header, exit, body}` ベースに加えて、ControlFormBox に Loop 形を必ず写す。 +- 方針: + - `BreakFinderBox._find_loops` 内で: + - `loop_info` 生成に加えて `local cf = new ControlFormBox()` → `cf.from_loop(header_id, exit_id, body_blocks)` を常時呼ぶ。 + - trace ON のとき `[loopssa/control]` ログに加えて ControlFormBox の内容も JSON 風にまとめて出す(header/exit/body_size)。 + - このフェーズでは戻り値の構造はまだ変えず、`find_breaks` は従来どおり breaks 配列を返す。 + +### J‑D: PhiInjector v2 設計(実装は次フェーズ) + +- 目的: + - Exit PHI 注入を ControlFormBox/LoopScope ベースで書き直すための **設計を先に固める**。 +- 方針: + - 25.1j ではコード本体は変えず、`phi_injector.hako` と README に設計のみ追記: + - `inject_exit_phis(json_str, breaks, trace_flag)` の将来形(ControlFormBox + break 群 + JSON v0)を定義。 + - Carrier/Pinned/Invariants を Rust LoopForm v2 の概念に揃える。 + - `common_vars = ["i","n","item",…]` のハードコードは「v2 で撤退予定」とコメントで明示。 + - 実アルゴリズムの書き換え(v2 本体)は Phase 25.1k 以降の大きなタスクとして分離する。 + +## このフェーズで「しない」こと + +- `.hako` 側 LoopSSA を Rust LoopForm v2 と完全同型にすること(PHI アルゴリズムの全面移植)は行わない。 +- Stage‑B Test3 の `%0`/SSA エラーを「必ずゼロにする」こと: + - 25.1j では **trace 変数のスコープ修正と LoopSSA v2 の責務整理** に集中する。 + - `%0` の根治は、LoopSSA v2 本体(Carrier/Pinned/Exit PHI)を実装する次フェーズのターゲットとする。 diff --git a/docs/development/roadmap/phases/phase-25.1k/README.md b/docs/development/roadmap/phases/phase-25.1k/README.md new file mode 100644 index 00000000..f0440676 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1k/README.md @@ -0,0 +1,90 @@ +# Phase 25.1k — LoopSSA v2 実装 & Stage‑B SSA 安定化(.hako 本体版) + +Status: planning(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/PHI を SSOT として維持) + +## ゴール + +- 25.1j までに固めた LoopSSA/BreakFinderBox/PhiInjectorBox の責務・設計をベースに、 + `.hako` 側 LoopSSA v2 の **実装本体** に踏み込むフェーズだよ。 +- 具体的には: + - Stage‑B minimal harness(`tools/test_stageb_min.sh`)の Test 2/3 で出ている: + - `BreakFinderBox._find_loops/2` 周辺の `use of undefined value ValueId(50)`(Rust VM 側エラー) + - `%0` 由来の SSA エラー(`NYASH_VM_VERIFY_MIR=1` 時) + を **LoopSSA v2 の改善によって減らす/消す** ことを狙う。 + - 文字列ハードコードベースの `_collect_phi_vars` / synthetic `"r{block}_{var}"` を、 + Carrier/Pinned ベースの設計に一歩近づける(完全置き換えまでは行かなくても OK)。 + +## 前提(25.1j までで揃っているもの) + +- Rust 側: + - LoopForm v2 + Conservative PHI Box + ControlForm が統合済みで、If/Loop の SSA/PHI は緑。 + - Stage‑B 風ループ(Rust テスト)も LoopForm v2 / Conservative PHI で安定している。 +- `.hako` 側: + - LoopSSA パス: + - `LoopSSA.stabilize_merges(stage1_json)` が `BreakFinderBox.find_breaks(json, trace_flag)` → + `PhiInjectorBox.inject_exit_phis(json, breaks, trace_flag)` の 2 段構成で動作。 + - trace/ENV 解釈は LoopSSA に一元化され、下流には 0/1 の `trace_flag` だけ渡す構造に整理済み。 + - BreakFinderBox: + - `_find_loops(json_str, trace)` が `"loop_header":` / `"loop_exit":` から loop を検出。 + - `loop_info` に `{"header", "exit", "body", "control"}` を格納し、`control` に ControlFormBox を添付。 + - PhiInjectorBox: + - 現状は `common_vars = ["i","n","item","j","count","val"]` に対する簡易版 `_collect_phi_vars`。 + - value_id は `"r{block_id}_{var_name}"` 形式の synthetic 値(観測用のダミー)を返している。 + - ドキュメント: + - 25.1j の README で LoopSSA/BreakFinderBox/PhiInjectorBox の責務境界と Carrier/Pinned/Invariants 概念を明文化済み。 + +## 方針(25.1k: LoopSSA v2 の「中身」を少し前に進める) + +### K‑A: Stage‑B Test2 の ValueId(50) 問題の最小再現と LoopSSA 切り分け + +- 目的: + - `BreakFinderBox._find_loops/2` で発生している `use of undefined value ValueId(50)` を、 + 「LoopSSA が生成した JSON の問題なのか」「他フェーズの JSON なのか」切り分ける。 +- ステップ: + 1. `tools/test_stageb_min.sh` Test2 の JSON 出力を一時ファイルに保存(Stage‑B → Program(JSON v0) 直後)。 + 2. その JSON に対して: + - Rust 側 MirBuilder に直接食わせて `NYASH_VM_VERIFY_MIR=1` を通す。 + - `.hako` 側 LoopSSA を単独で呼び出す(`LoopSSA.stabilize_merges(json)`)最小ハーネスを用意。 + 3. どの時点で ValueId(50) が未定義になるかを特定し、 + それが LoopSSA の改変前後で変わっているかを確認する。 + +### K‑B: BreakFinderBox の LoopScope 精度の向上(保守的に) + +- 目的: + - `_find_loop_body` の「header_id < id < exit_id」ヒューリスティックが過剰/過少に body を拾っていないかを確認・改善する。 +- ステップ: + - ControlFormBox を活用して LoopScope の妥当性をチェック: + - header/exit/body に対して Rust 側 LoopForm v2 の期待と比較しやすい形でログを出す(block id 範囲など)。 + - 必要であれば: + - body 集合を「exit から逆到達できる block」など、より保守的な条件に修正(文字列ベースの範囲内で)。 + - ゴール: + - LoopSSA が「本来そのループに属さない block」を body に含めないようにする(ValueId(50) のような飛び火を防ぐ)。 + +### K‑C: PhiInjectorBox の v2 への一歩(Carrier/Pinned の入口だけ作る) + +- 目的: + - `_collect_phi_vars` の完全置き換えまでは行かずに、今後の移行先となる v2 API の入口を整える。 +- ステップ: + - `PhiInjectorBox` に新しい内部ヘルパーを追加(例: `_collect_phi_vars_v2(json_str, break_list, loop_info)`): + - まだ中身は stub でもよいが、「Carrier/Pinned/Invariants」の 3 区分を引数/戻り値で表現できる形にする。 + - 25.1k では v1 実装(`_collect_phi_vars`)を実際に置き換えず、trace=1 の時だけ v2 の診断ログを出すくらいに留める。 + - `loop_info.get("control")` の ControlFormBox から header/exit/body を読み取り、 + どの変数が本来の Carrier に相当しそうかをログに出す(まだ PHI には使わない)。 + +### K‑D: Stage‑B Test3 (%0) の「悪化していない」ことの確認 + +- 目的: + - LoopSSA v2 の変更が、Stage‑B Test3 の `%0` SSA 問題を悪化させていないことを確認する。 +- ステップ: + - `NYASH_VM_VERIFY_MIR=1` で Test3 を流したときのエラー位置(関数名/ブロック/命令)を記録。 + - 25.1k の差分適用前後で比較し、LoopSSA 関連の変更による新規エラーが出ていないことを確認。 + - 必要なら、Test3 から LoopSSA を一時オフ(`HAKO_LOOPSSA_EXIT_PHI=0`)にした場合のログも取っておき、 + 「LoopSSA が原因の部分」と「それ以外」を明確に切り分ける。 + +## このフェーズで「しない」こと + +- PhiInjectorBox の `_collect_phi_vars` / `_get_var_value` を **完全刷新すること**: + - これは 25.1k の次、25.1l 以降の「本格 v2 実装」のタスクとして分ける。 +- Rust 側 LoopForm v2 / Conservative PHI の設計を変えること: + - Rust 側はあくまで SSOT であり、.hako 側はそれに追従する形で徐々に近づける。 + diff --git a/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako b/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako index 891b7f26..f789f2f3 100644 --- a/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako +++ b/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako @@ -1,33 +1,45 @@ -// Exit PHI - Break Finder Box -// Purpose: Detect break statements in loops (jumps to exit blocks) -// Input: JSON v0 Program string -// Output: Array of break info {block_id, exit_id, loop_header} +// Exit PHI - BreakFinderBox +// Purpose: +// - Stage‑1 Program(JSON v0) から「ループと break の位置」を検出する解析専用の箱だよ。 +// - JSON 文字列は書き換えず、loop / break 情報を構造化して LoopSSA/PhiInjector に渡す。 +// Input: +// - json_str: Program(JSON v0) 文字列(単一関数 or Program 全体) +// - trace_flag: 0 = silent, 1 = dev trace ON(LoopSSA から渡される) +// Output: +// - Array of break info MapBox: +// { "block_id": , "exit_id": , "loop_header": } +// - 付随して ControlFormBox に loop 形を写し、trace ON 時にログとして出力する -static box BreakFinderBox { - // Main entry: find all breaks in all loops - find_breaks(json_str) { - local trace = env.get("HAKO_COMPILER_BUILDER_TRACE") - if trace != null && ("" + trace) == "1" { - print("[break-finder] start") - } +using selfhost.shared.mir.control_form as ControlFormBox - local breaks = new ArrayBox() + static box BreakFinderBox { + // Main entry: find all breaks in all loops + // trace_flag: 0 = silent, 1 = dev trace ON(LoopSSA から伝播) + find_breaks(json_str, trace_flag) { + local trace = trace_flag + if trace == 1 { + print("[break-finder] start") + } + + local breaks = new ArrayBox() + + // 1) Find all loops (header blocks with back-edges) + local loops = me._find_loops(json_str, trace) + + if trace == 1 { + print("[break-finder] found " + loops.length() + " loops") + } - // 1) Find all loops (header blocks with back-edges) - local loops = me._find_loops(json_str) - - if trace != null && ("" + trace) == "1" { - print("[break-finder] found " + loops.length() + " loops") - } - - // 2) For each loop, find breaks (jumps to exit) - local i = 0 - local n = loops.length() - loop(i < n) { - local loop_info = loops.get(i) - local header_id = "" + loop_info.get("header") - local exit_id = "" + loop_info.get("exit") - local body_blocks = loop_info.get("body") + // 2) For each loop, find breaks (jumps to exit) + local i = 0 + local n = loops.length() + loop(i < n) { + local loop_info = loops.get(i) + local header_id = "" + loop_info.get("header") + local exit_id = "" + loop_info.get("exit") + local body_blocks = loop_info.get("body") + // ControlFormBox を break_info にも添付しておく(v2 で PhiInjector 側から参照するため) + local loop_cf = loop_info.get("control") // Find breaks in body blocks local j = 0 @@ -35,38 +47,40 @@ static box BreakFinderBox { loop(j < m) { local block_id = "" + body_blocks.get(j) - // Check if this block jumps to exit - if me._jumps_to(json_str, block_id, exit_id) == 1 { - local break_info = new MapBox() - break_info.set("block_id", block_id) - break_info.set("exit_id", exit_id) - break_info.set("loop_header", header_id) - breaks.push(break_info) - - if trace != null && ("" + trace) == "1" { - print("[break-finder] found break: block " + block_id + " -> exit " + exit_id) - } - } + // Check if this block jumps to exit + if me._jumps_to(json_str, block_id, exit_id) == 1 { + local break_info = new MapBox() + break_info.set("block_id", block_id) + break_info.set("exit_id", exit_id) + break_info.set("loop_header", header_id) + // ここで ControlFormBox(loop_cf)も載せておく + break_info.set("loop_control", loop_cf) + breaks.push(break_info) + + if trace == 1 { + print("[break-finder] found break: block " + block_id + " -> exit " + exit_id) + } + } j = j + 1 } - i = i + 1 - } - - if trace != null && ("" + trace) == "1" { - print("[break-finder] total breaks: " + breaks.length()) - } - - return breaks - } - - // Find all loops in JSON (simple version: look for header/exit pattern) - _find_loops(json_str) { + i = i + 1 + } + + if trace == 1 { + print("[break-finder] total breaks: " + breaks.length()) + } + + return breaks + } + + // Find all loops in JSON (simple version: look for header/exit pattern) + _find_loops(json_str, trace) { local loops = new ArrayBox() local s = "" + json_str - // Simple pattern: find "loop_header":NNN, "loop_exit":MMM + // Simple pattern: find "loop_header":NNN, "loop_exit":MMM // This is a simplified version - just finds explicit loop markers local i = 0 local n = s.length() @@ -93,11 +107,23 @@ static box BreakFinderBox { // Find body blocks (blocks between header and exit) local body_blocks = me._find_loop_body(json_str, header_id, exit_id) - local loop_info = new MapBox() - loop_info.set("header", header_id) - loop_info.set("exit", exit_id) - loop_info.set("body", body_blocks) - loops.push(loop_info) + // ControlFormBox 経由で loop 形を構造化しておく(MVP: 観測専用) + local cf = new ControlFormBox() + cf.from_loop(header_id, exit_id, body_blocks) + + local loop_info = new MapBox() + loop_info.set("header", header_id) + loop_info.set("exit", exit_id) + loop_info.set("body", body_blocks) + // 将来の v2 実装で利用できるよう、ControlForm も添えておく + loop_info.set("control", cf) + loops.push(loop_info) + + // trace=1 のときだけ JSON 風にログ出力するよ + if trace == 1 { + local msg = "[loopssa/control] {\"kind\":\"loop\",\"header\":" + header_id + ",\"exit\":" + exit_id + ",\"body_size\":" + body_blocks.length() + "}" + print(msg) + } i = exit_pos + 12 } diff --git a/lang/src/compiler/builder/ssa/exit_phi/phi_injector.hako b/lang/src/compiler/builder/ssa/exit_phi/phi_injector.hako index 2e88c39b..3d47aa12 100644 --- a/lang/src/compiler/builder/ssa/exit_phi/phi_injector.hako +++ b/lang/src/compiler/builder/ssa/exit_phi/phi_injector.hako @@ -1,55 +1,77 @@ -// Exit PHI - PHI Injector Box -// Purpose: Inject PHI nodes at loop exit blocks -// Input: JSON v0 Program string + break info array -// Output: Modified JSON with PHI nodes injected +// Exit PHI - PhiInjectorBox +// Purpose: +// - BreakFinderBox が検出した break 情報をもとに、loop exit block へ PHI 相当命令を注入する箱だよ。 +// - 現状は文字列ベースの簡易実装(synthetic value_id など)だが、将来的には ControlFormBox/LoopScope ベースに差し替える予定。 +// Input: +// - json_str: Program(JSON v0) 文字列 +// - breaks: Array of MapBox(BreakFinderBox 由来: {block_id, exit_id, loop_header}) +// - trace_flag: 0 = silent, 1 = dev trace ON(LoopSSA から渡される) +// Output: +// - Modified Program(JSON v0) 文字列(exit block の instructions 先頭に PHI 相当 JSON を挿入) -static box PhiInjectorBox { - // Main entry: inject PHI nodes for all breaks - inject_exit_phis(json_str, breaks) { - local trace = env.get("HAKO_COMPILER_BUILDER_TRACE") - if trace != null && ("" + trace) == "1" { - print("[phi-injector] start, breaks: " + breaks.length()) - } + static box PhiInjectorBox { + // Main entry: inject PHI nodes for all breaks + // trace_flag: 0 = silent, 1 = dev trace ON(LoopSSA から伝播) + inject_exit_phis(json_str, breaks, trace_flag) { + local trace = trace_flag + if trace == 1 { + print("[phi-injector] start, breaks: " + breaks.length()) + } + + if breaks.length() == 0 { + if trace == 1 { + print("[phi-injector] no breaks, skipping") + } + return json_str + } + + local result = json_str + + // Group breaks by exit_id + local exit_groups = me._group_by_exit(breaks) + + if trace == 1 { + print("[phi-injector] processing " + exit_groups.length() + " exit blocks") + } - if breaks.length() == 0 { - if trace != null && ("" + trace) == "1" { - print("[phi-injector] no breaks, skipping") - } - return json_str - } + // For each exit block, inject PHI nodes + local i = 0 + local n = exit_groups.length() + loop(i < n) { + local group = exit_groups.get(i) + local exit_id = "" + group.get("exit_id") + local break_list = group.get("breaks") + // Optional: LoopControl を取り出して v2 実装の観測に使えるようにする + local loop_cf = group.get("loop_control") + + if trace == 1 { + print("[phi-injector] exit " + exit_id + " has " + break_list.length() + " breaks") + // 将来の v2 実装に備えて、ControlForm 情報も軽く観測しておく(現在はログのみ) + if loop_cf != null { + print("[phi-injector/v2] loop_control present for exit " + exit_id) + } + } - local result = json_str + // Collect variable snapshots from all breaks + local phi_vars = me._collect_phi_vars(result, break_list) + + // v2 用の入口(観測専用・現在は未使用) + // 将来的には loop_cf / Carrier/Pinned に基づいて phi_vars を構築し直す。 + if trace == 1 && loop_cf != null { + // Stub: v2 経路が有効化されたときにここから差し替える想定だよ。 + // local phi_vars_v2 = me._collect_phi_vars_v2(result, break_list, loop_cf) + // (現時点ではログのみで、実際の PHI 生成には使わない) + print("[phi-injector/v2] (stub) would collect phi vars via ControlFormBox for exit " + exit_id) + } - // Group breaks by exit_id - local exit_groups = me._group_by_exit(breaks) - - if trace != null && ("" + trace) == "1" { - print("[phi-injector] processing " + exit_groups.length() + " exit blocks") - } - - // For each exit block, inject PHI nodes - local i = 0 - local n = exit_groups.length() - loop(i < n) { - local group = exit_groups.get(i) - local exit_id = "" + group.get("exit_id") - local break_list = group.get("breaks") - - if trace != null && ("" + trace) == "1" { - print("[phi-injector] exit " + exit_id + " has " + break_list.length() + " breaks") - } - - // Collect variable snapshots from all breaks - local phi_vars = me._collect_phi_vars(result, break_list) - - if phi_vars.length() > 0 { - // Inject PHI instructions - result = me._inject_phis_at_block(result, exit_id, phi_vars) - - if trace != null && ("" + trace) == "1" { - print("[phi-injector] injected " + phi_vars.length() + " PHI nodes at exit " + exit_id) - } - } + if phi_vars.length() > 0 { + // Inject PHI instructions + result = me._inject_phis_at_block(result, exit_id, phi_vars) + + if trace == 1 { + print("[phi-injector] injected " + phi_vars.length() + " PHI nodes at exit " + exit_id) + } + } i = i + 1 } @@ -57,28 +79,31 @@ static box PhiInjectorBox { return result } - // Group breaks by exit_id - _group_by_exit(breaks) { - local groups = new ArrayBox() - local exit_ids = new ArrayBox() + // Group breaks by exit_id + _group_by_exit(breaks) { + local groups = new ArrayBox() + local exit_ids = new ArrayBox() local i = 0 local n = breaks.length() loop(i < n) { - local break_info = breaks.get(i) - local exit_id = "" + break_info.get("exit_id") + local break_info = breaks.get(i) + local exit_id = "" + break_info.get("exit_id") // Find or create group local group_idx = me._find_exit_group(exit_ids, exit_id) - if group_idx < 0 { - // New group - local new_group = new MapBox() - new_group.set("exit_id", exit_id) - local break_list = new ArrayBox() - break_list.push(break_info) - new_group.set("breaks", break_list) - groups.push(new_group) - exit_ids.push(exit_id) + if group_idx < 0 { + // New group + local new_group = new MapBox() + new_group.set("exit_id", exit_id) + local break_list = new ArrayBox() + break_list.push(break_info) + new_group.set("breaks", break_list) + // v2 用に loop_control もグループに添付しておく(同じ exit_id なら同じループとみなす) + local lc = break_info.get("loop_control") + if lc != null { new_group.set("loop_control", lc) } + groups.push(new_group) + exit_ids.push(exit_id) } else { // Add to existing group local existing_group = groups.get(group_idx) @@ -103,13 +128,23 @@ static box PhiInjectorBox { return -1 } - // Collect variables that need PHI nodes - _collect_phi_vars(json_str, break_list) { - local vars = new ArrayBox() - local var_names = new ArrayBox() - - // For MVP, assume common variables: i, n, item, etc. - // In full implementation, would trace actual variable definitions + // Collect variables that need PHI nodes + _collect_phi_vars(json_str, break_list) { + local vars = new ArrayBox() + local var_names = new ArrayBox() + + // NOTE (v1/MVP): + // 現在は「よくある変数名」を固定リストで見る簡易版だよ。 + // - 長所: 実装が単純で、Stage‑B 最小ケースの観測には十分。 + // - 短所: 真の Carrier/Pinned を見ていないので、網羅性はない(将来撤退予定)。 + // + // v2 では: + // - BreakFinderBox / ControlFormBox / LoopSSA から LoopScope 情報を受け取り、 + // - Carrier だけを対象に exit PHI を構築する形に差し替える予定だよ。 + // - common_vars のハードコードは v2 実装時に削除する(箱理論 5.1: ハードコード禁止)。 + // + // For MVP, assume common variables: i, n, item, etc. + // In full implementation, would trace actual variable definitions local common_vars = new ArrayBox() common_vars.push("i") common_vars.push("n") @@ -185,13 +220,20 @@ static box PhiInjectorBox { return 1 } - // Get variable value in block (simplified - return synthetic value ID) - _get_var_value(json_str, block_id, var_name) { - // For MVP, generate synthetic value IDs based on block and var - // Format: "r{block_id}_{var_name}" - // In full implementation, would trace actual SSA values - return "r" + block_id + "_" + var_name - } + // Get variable value in block (simplified - return synthetic value ID) + _get_var_value(json_str, block_id, var_name) { + // For MVP, generate synthetic value IDs based on block and var. + // Format: "r{block_id}_{var_name}" という「ダミー SSA ID」を返しているだけだよ。 + // + // v2 では: + // - JSON v0 から本当の SSA ValueId を追跡するか、 + // - Rust 側 LoopForm v2 / MirSchemaBox から得た補助情報を元に、 + // exit PHI に正しい value_id を配線する設計に差し替える。 + // - この synthetic フォーマットは「観測専用」の一時的なものとして扱う。 + // + // In full implementation, would trace actual SSA values + return "r" + block_id + "_" + var_name + } // Inject PHI instructions at block start _inject_phis_at_block(json_str, block_id, phi_vars) { diff --git a/lang/src/compiler/builder/ssa/loopssa.hako b/lang/src/compiler/builder/ssa/loopssa.hako index 4770c95a..b50f9b53 100644 --- a/lang/src/compiler/builder/ssa/loopssa.hako +++ b/lang/src/compiler/builder/ssa/loopssa.hako @@ -1,55 +1,68 @@ -// Loop SSA - Exit PHI Generation for Loops with Breaks -// Purpose: Detect loops and inject PHI nodes at exit blocks -// Policy: Uses BreakFinderBox and PhiInjectorBox for modular implementation +// LoopSSA - Exit PHI Generation for Loops with Breaks +// Purpose: +// - Stage‑1 Program(JSON v0) に対して、loop + break パターンを検出し、 +// exit block に PHI 相当の命令を注入する「SSA 補強フェーズ」の入口だよ。 +// - 自身はオーケストレータ箱として振る舞い、実際の解析/変換は +// BreakFinderBox / PhiInjectorBox に委譲するよ。 +// Policy: +// - 入力: Stage‑1 Program(JSON v0)(単一関数 or Program 全体)。 +// - 出力: exit PHI 相当の命令が入った Program(JSON v0)(文字列)。 +// - 解析: BreakFinderBox.find_breaks(json, trace_flag) が JSON を読み取り専用で解析。 +// - 変換: PhiInjectorBox.inject_exit_phis(json, breaks, trace_flag) が exit block をテキストベースで書き換える。 +// - 環境変数/トレース: HAKO_LOOPSSA_EXIT_PHI / HAKO_COMPILER_BUILDER_TRACE を LoopSSA 側で解釈し、 +// 下流には 0/1 の trace_flag だけを渡す(箱ごとに ENV を直読しない)。 using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox using lang.compiler.builder.ssa.exit_phi.phi_injector as PhiInjectorBox -static box LoopSSA { - // Main entry: Guard PHI-like merges at loop headers/exits - // Phase 2-5 implementation: detect breaks and inject exit PHIs - stabilize_merges(stage1_json) { - local trace = env.get("HAKO_COMPILER_BUILDER_TRACE") - - if trace != null && ("" + trace) == "1" { - print("[loopssa] stabilize_merges start") - } - - // Check if exit PHI feature is enabled - local enable_exit_phi = 1 - { - local flag = env.get("HAKO_LOOPSSA_EXIT_PHI") - if flag != null && ("" + flag) == "0" { enable_exit_phi = 0 } - } - - if enable_exit_phi == 0 { - if trace != null && ("" + trace) == "1" { - print("[loopssa] exit PHI disabled, pass-through") - } - return stage1_json - } - - // Phase 2: Find breaks in loops - local breaks = BreakFinderBox.find_breaks(stage1_json) - - if trace != null && ("" + trace) == "1" { - print("[loopssa] found " + breaks.length() + " breaks") - } - - if breaks.length() == 0 { - if trace != null && ("" + trace) == "1" { - print("[loopssa] no breaks found, pass-through") - } - return stage1_json - } - - // Phase 3: Inject PHI nodes at exit blocks - local result = PhiInjectorBox.inject_exit_phis(stage1_json, breaks) - - if trace != null && ("" + trace) == "1" { - print("[loopssa] exit PHIs injected successfully") - } - - return result - } -} + static box LoopSSA { + // Main entry: Guard PHI-like merges at loop headers/exits + // Phase 2-5 implementation: detect breaks and inject exit PHIs + stabilize_merges(stage1_json) { + // Resolve trace flag once at the entry point(0/1 に正規化) + local trace_env = env.get("HAKO_COMPILER_BUILDER_TRACE") + local trace_flag = 0 + if trace_env != null && ("" + trace_env) == "1" { trace_flag = 1 } + + if trace_flag == 1 { + print("[loopssa] stabilize_merges start") + } + + // Check if exit PHI feature is enabled + local enable_exit_phi = 1 + { + local flag = env.get("HAKO_LOOPSSA_EXIT_PHI") + if flag != null && ("" + flag) == "0" { enable_exit_phi = 0 } + } + + if enable_exit_phi == 0 { + if trace_flag == 1 { + print("[loopssa] exit PHI disabled, pass-through") + } + return stage1_json + } + + // Phase 2: Find breaks in loops + local breaks = BreakFinderBox.find_breaks(stage1_json, trace_flag) + + if trace_flag == 1 { + print("[loopssa] found " + breaks.length() + " breaks") + } + + if breaks.length() == 0 { + if trace_flag == 1 { + print("[loopssa] no breaks found, pass-through") + } + return stage1_json + } + + // Phase 3: Inject PHI nodes at exit blocks + local result = PhiInjectorBox.inject_exit_phis(stage1_json, breaks, trace_flag) + + if trace_flag == 1 { + print("[loopssa] exit PHIs injected successfully") + } + + return result + } + } diff --git a/lang/src/compiler/entry/compiler_stageb.hako b/lang/src/compiler/entry/compiler_stageb.hako index 24dda570..ee53b1b1 100644 --- a/lang/src/compiler/entry/compiler_stageb.hako +++ b/lang/src/compiler/entry/compiler_stageb.hako @@ -11,7 +11,8 @@ // Note: sh_core must be in [modules] for parser dependencies to resolve using hako.compiler.entry.bundle_resolver as BundleResolver -using lang.compiler.parser.box as ParserBox +// ParserBox module: use `parser.parser_box` to avoid `box` keyword in path +using lang.compiler.parser.parser_box as ParserBox using lang.compiler.entry.func_scanner as FuncScannerBox using lang.compiler.entry.using_resolver as Stage1UsingResolverBox using lang.compiler.builder.mod as CompilerBuilder diff --git a/lang/src/compiler/entry/func_scanner.hako b/lang/src/compiler/entry/func_scanner.hako index 748d97b8..2bd50249 100644 --- a/lang/src/compiler/entry/func_scanner.hako +++ b/lang/src/compiler/entry/func_scanner.hako @@ -12,10 +12,12 @@ static box FuncScannerBox { return me._scan_methods(source, box_name, 1, 0) } - // Scan all static box definitions and collect their methods. - method scan_all_boxes(source) { - local defs = new ArrayBox() - local s = "" + source + // Scan all static box definitions and collect their methods. + method scan_all_boxes(source) { + // source が null の場合はスキャン対象がないので空配列を返すよ。 + if source == null { return new ArrayBox() } + local defs = new ArrayBox() + local s = "" + source local n = s.length() local i = 0 local in_str = 0 @@ -377,9 +379,11 @@ static box FuncScannerBox { return params } - // Helper: strip comments from source - method _strip_comments(source) { - local s = "" + source + // Helper: strip comments from source + method _strip_comments(source) { + // source が null の場合はそのまま空文字として扱う(コメント除去する対象がないだけ) + if source == null { return "" } + local s = "" + source local out = "" local i = 0 local n = s.length() diff --git a/lang/src/compiler/tests/loopssa_breakfinder_min.hako b/lang/src/compiler/tests/loopssa_breakfinder_min.hako new file mode 100644 index 00000000..36349b06 --- /dev/null +++ b/lang/src/compiler/tests/loopssa_breakfinder_min.hako @@ -0,0 +1,38 @@ +// loopssa_breakfinder_min.hako — Minimal LoopSSA/BreakFinder JSON test harness +// +// 目的: +// - Stage‑B を経由せずに、LoopSSA + BreakFinderBox + PhiInjectorBox の経路だけを +// 小さな Program(JSON v0) 文字列で通すミニハーネスだよ。 +// - まずは「単純な loop_header/loop_exit パターンで正常に動く」ことを確認する足場として使う。 +// - 将来的に Stage‑B 最小サンプルから抽出した JSON をここに貼り替えることで、 +// ValueId(50) 系の問題を再現しやすくすることを想定しているよ。 +// +// JSON 形状(簡略版): +// { +// "kind":"Program", +// "functions":[ +// { +// "name":"main", +// "blocks":[ +// {"id":0,"loop_header":0,"loop_exit":2}, +// {"id":1}, +// {"id":2} +// ] +// } +// ] +// } +// + +using lang.compiler.builder.ssa.loopssa as LoopSSA + +static box Main { + method main(args) { + local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}" + + // LoopSSA v2 を直接呼び出すよ(LoopSSA EXIT PHI パスのみ) + local out = LoopSSA.stabilize_merges(json) + print(out) + return 0 + } +} + diff --git a/lang/src/shared/mir/control_form_box.hako b/lang/src/shared/mir/control_form_box.hako index 291b2814..ef298da4 100644 --- a/lang/src/shared/mir/control_form_box.hako +++ b/lang/src/shared/mir/control_form_box.hako @@ -7,29 +7,35 @@ // - 現時点では「構造定義のみ」の箱で、コンパイルを通すための最小限の実装になっているよ。 static box ControlFormBox { - // 共通フィールド - kind_name: StringBox // "loop" or "if" - entry: IntegerBox // BasicBlockId 相当(整数ID) - exits: ArrayBox // of IntegerBox (BlockId) + // 共通フィールド(型はコメントで記載) + // kind_name: StringBox // "loop" or "if" + // entry: IntegerBox // BasicBlockId 相当(整数ID) + // exits: ArrayBox // of IntegerBox (BlockId) + kind_name + entry + exits // Loop 用フィールド(kind_name == "loop" の時のみ意味を持つ) - loop_preheader: IntegerBox - loop_header: IntegerBox - loop_body: IntegerBox - loop_latch: IntegerBox - loop_exit: IntegerBox + // loop_preheader: IntegerBox + // loop_header: IntegerBox + // loop_body: IntegerBox + // loop_latch: IntegerBox + // loop_exit: IntegerBox + loop_preheader + loop_header + loop_body + loop_latch + loop_exit // If 用フィールド(kind_name == "if" の時のみ意味を持つ) - if_cond: IntegerBox - if_then: IntegerBox - if_else: IntegerBox - if_merge: IntegerBox - - // コンストラクタ相当: kind_name を設定し、exits 配列を初期化するよ。 - birth(kind) { - me.kind_name = kind - me.exits = new ArrayBox() - } + // if_cond: IntegerBox + // if_then: IntegerBox + // if_else: IntegerBox + // if_merge: IntegerBox + if_cond + if_then + if_else + if_merge is_loop() { return me.kind_name == "loop" @@ -38,5 +44,39 @@ static box ControlFormBox { is_if() { return me.kind_name == "if" } -} + // Loop 用の ControlFormBox を簡易的に構築するヘルパーだよ。 + // + // header_id / exit_id / body_blocks は JSON v0 由来の block id 群。 + // ここではまだ単純に: + // - entry = header_id + // - loop_preheader = header_id(将来、明示的な preheader があれば差し替える) + // - loop_latch = header_id(MVP: latch 未特定) + // - exits = [exit_id] + from_loop(header_id, exit_id, body_blocks) { + me.kind_name = "loop" + me.entry = header_id + me.loop_header = header_id + me.loop_exit = exit_id + me.loop_body = body_blocks + // MVP: preheader/latch は header 相当で埋めておく + me.loop_preheader = header_id + me.loop_latch = header_id + me.exits = new ArrayBox() + me.exits.push(exit_id) + } + + // If 用の ControlFormBox を構築するヘルパーだよ。 + // + // cond_block / then_block / else_block / merge_block は JSON v0 由来の block id。 + from_if(cond_block, then_block, else_block, merge_block) { + me.kind_name = "if" + me.entry = cond_block + me.if_cond = cond_block + me.if_then = then_block + me.if_else = else_block + me.if_merge = merge_block + me.exits = new ArrayBox() + me.exits.push(merge_block) + } +} diff --git a/nyash.toml b/nyash.toml index b84a2615..e873fa7b 100644 --- a/nyash.toml +++ b/nyash.toml @@ -67,6 +67,7 @@ path = "lang/src/shared/common/string_helpers.hako" # Lang compiler (Phase 20.33 migration) "lang.compiler.parser.box" = "lang/src/compiler/parser/parser_box.hako" +"lang.compiler.parser.parser_box" = "lang/src/compiler/parser/parser_box.hako" "lang.compiler.parser.scan.parser_string_utils_box" = "lang/src/compiler/parser/scan/parser_string_utils_box.hako" "lang.compiler.parser.scan.parser_ident_scan_box" = "lang/src/compiler/parser/scan/parser_ident_scan_box.hako" "lang.compiler.parser.scan.parser_number_scan_box" = "lang/src/compiler/parser/scan/parser_number_scan_box.hako" @@ -187,6 +188,7 @@ path = "lang/src/shared/common/string_helpers.hako" "selfhost.shared.json.stringify" = "lang/src/shared/json/stringify.hako" "selfhost.shared.mir.schema" = "lang/src/shared/mir/mir_schema_box.hako" "selfhost.shared.mir.loopform" = "lang/src/shared/mir/loop_form_box.hako" +"selfhost.shared.mir.control_form" = "lang/src/shared/mir/control_form_box.hako" "selfhost.shared.mir.builder" = "lang/src/shared/mir/block_builder_box.hako" "selfhost.shared.mir.io" = "lang/src/shared/mir/mir_io_box.hako" "selfhost.shared.mir.json_emit" = "lang/src/shared/mir/json_emit_box.hako" diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index 66afd443..f4097f17 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -217,6 +217,32 @@ impl LoopFormBuilder { vec![(self.preheader_id, pinned.preheader_copy)], )?; ops.update_var(pinned.name.clone(), pinned.header_phi); + + // 🔧 Hotfix 7 (Enhanced): Update aliases for pinned receiver variables + // When a variable like "me" is pinned to "__pin$N$@recv", both names + // must point to the same PHI value to avoid stale ValueId references. + // This handles: + // 1. Direct receiver: "me" → "__pin$1$@recv" + // 2. Nested loops: "__pin$1$@recv" → "__pin$2$@recv" + // 3. Multiple aliasing scenarios + if pinned.name.contains("@recv") { + // Always update "me" (the canonical receiver name) + ops.update_var("me".to_string(), pinned.header_phi); + + // Also update all previous pin levels (__pin$1$@recv, __pin$2$@recv, etc.) + // Extract the pin counter and update all lower levels + if let Some(idx) = pinned.name.find("$") { + if let Some(end_idx) = pinned.name[idx+1..].find("$") { + if let Ok(counter) = pinned.name[idx+1..idx+1+end_idx].parse::() { + // Update all previous pin levels (1 through counter-1) + for i in 1..counter { + let alias = format!("__pin${}$@recv", i); + ops.update_var(alias, pinned.header_phi); + } + } + } + } + } } // Emit PHIs for carrier variables diff --git a/tools/dump_stageb_min_mir.sh b/tools/dump_stageb_min_mir.sh new file mode 100644 index 00000000..413d2eef --- /dev/null +++ b/tools/dump_stageb_min_mir.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# dump_stageb_min_mir.sh — Stage‑B minimal harnessの Program(JSON v0) → MIR(JSON) ダンプ +# +# 目的: +# - lang/src/compiler/tests/stageb_min_sample.hako を Stage‑B 経由で Program(JSON v0) に変換し、 +# その JSON を Rust 側 MirBuilder で MIR(JSON) に変換してファイルに落とすよ。 +# - LoopSSA の挙動や SSA/PHI 問題を、MIR レベルでデバッグしやすくするための小さなハーネスだよ。 +# +# 使い方: +# tools/dump_stageb_min_mir.sh [PROGRAM_JSON_OUT] [MIR_JSON_OUT] +# 例: +# tools/dump_stageb_min_mir.sh tmp/stageb_min_program.json tmp/stageb_min_mir.json +# +# 環境変数: +# NYASH_BIN — nyash/hakorune バイナリ(既定: ./target/release/hakorune) +# HAKO_LOOPSSA_EXIT_PHI — 1: LoopSSA ON(LoopSSA v2 の挙動観測用) / 0: OFF(既定: 0) +# HAKO_COMPILER_BUILDER_TRACE — 1: LoopSSA/BreakFinder/PhiInjector まわりのトレース +# NYASH_VM_TRACE — 1: Rust VM の binop/SSA トレース(重いので必要時のみ) +# +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BIN="${NYASH_BIN:-$ROOT_DIR/target/release/hakorune}" +STAGEB="$ROOT_DIR/lang/src/compiler/entry/compiler_stageb.hako" +TEST_FILE="$ROOT_DIR/lang/src/compiler/tests/stageb_min_sample.hako" + +OUT_PROGRAM="${1:-$ROOT_DIR/tmp/stageb_min_program.json}" +OUT_MIR="${2:-$ROOT_DIR/tmp/stageb_min_mir.json}" + +mkdir -p "$ROOT_DIR/tmp" + +echo "[stageb/dump] emitting Program(JSON v0) → $OUT_PROGRAM" +NYASH_JSON_ONLY=1 \ +NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ +HAKO_STAGEB_FUNC_SCAN=1 \ +NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \ +NYASH_PARSER_ALLOW_SEMICOLON=1 \ +NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \ +HAKO_LOOPSSA_EXIT_PHI="${HAKO_LOOPSSA_EXIT_PHI:-0}" \ +HAKO_COMPILER_BUILDER_TRACE="${HAKO_COMPILER_BUILDER_TRACE:-0}" \ +NYASH_VM_TRACE="${NYASH_VM_TRACE:-0}" \ + "$BIN" --backend vm "$STAGEB" -- --source "$(cat "$TEST_FILE")" > "$OUT_PROGRAM" + +echo "[stageb/dump] converting Program(JSON v0) → MIR(JSON) → $OUT_MIR" +cat "$OUT_PROGRAM" | \ + "$BIN" --backend vm --ny-parser-pipe --program-json-to-mir "$OUT_MIR" + +echo "[stageb/dump] done." diff --git a/tools/test_loopssa_breakfinder_min.sh b/tools/test_loopssa_breakfinder_min.sh new file mode 100644 index 00000000..e74df577 --- /dev/null +++ b/tools/test_loopssa_breakfinder_min.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# test_loopssa_breakfinder_min.sh — LoopSSA/BreakFinder 最小 JSON ハーネステスト +# +# 目的: +# - lang/src/compiler/tests/loopssa_breakfinder_min.hako を直接 VM で実行し、 +# LoopSSA.stabilize_merges が単純な loop_header/loop_exit パターンで安全に動くことを確認する。 +# - LoopSSA v2 / BreakFinderBox の変更時に、まずここで赤が出ないことをチェックするための軽量テストだよ。 +# +# 将来: +# - Stage‑B 最小サンプルから抽出した JSON をこのハーネスに貼り替えることで、 +# ValueId(50) 系の問題を再現するための土台として使う予定。 +# +set -e + +NYASH_BIN="${NYASH_BIN:-./target/release/hakorune}" +TEST_FILE="lang/src/compiler/tests/loopssa_breakfinder_min.hako" + +echo "=== LoopSSA BreakFinder Minimal JSON Test ===" +echo "Test file: $TEST_FILE" +echo "Binary: $NYASH_BIN" +echo "" + +# LoopSSA v2 の EXIT PHI を有効化して実行(BreakFinder/PhiInjector 経由) +HAKO_LOOPSSA_EXIT_PHI=1 \ +NYASH_DISABLE_PLUGINS=1 NYASH_PARSER_STAGE3=1 \ + "$NYASH_BIN" --backend vm "$TEST_FILE" 2>&1 + +echo "" +echo "=== Test complete ===" + diff --git a/tools/test_stageb_min.sh b/tools/test_stageb_min.sh index a51ef6a8..58fed301 100644 --- a/tools/test_stageb_min.sh +++ b/tools/test_stageb_min.sh @@ -7,6 +7,14 @@ set -e NYASH_BIN="${NYASH_BIN:-./target/release/hakorune}" TEST_FILE="lang/src/compiler/tests/stageb_min_sample.hako" +# Stage‑B / LoopSSA related env (can be overridden by caller) +# - HAKO_LOOPSSA_EXIT_PHI: 1=LoopSSA ON (dev検証), 0=OFF(既定は0: 安全側) +# - HAKO_COMPILER_BUILDER_TRACE: 1=LoopSSA/Builder周辺の詳細トレース +# - NYASH_VM_TRACE: 1=Rust VM の binop/SSA トレース(重いので手動ON推奨) +HAKO_LOOPSSA_EXIT_PHI="${HAKO_LOOPSSA_EXIT_PHI:-0}" +HAKO_COMPILER_BUILDER_TRACE="${HAKO_COMPILER_BUILDER_TRACE:-0}" +NYASH_VM_TRACE="${NYASH_VM_TRACE:-0}" + echo "=== Stage-B Minimal Harness Test ===" echo "Test file: $TEST_FILE" echo "Binary: $NYASH_BIN" @@ -27,6 +35,9 @@ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \ NYASH_PARSER_ALLOW_SEMICOLON=1 \ NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \ NYASH_LOOPFORM_PHI_V2=1 \ +HAKO_LOOPSSA_EXIT_PHI="$HAKO_LOOPSSA_EXIT_PHI" \ +HAKO_COMPILER_BUILDER_TRACE="$HAKO_COMPILER_BUILDER_TRACE" \ +NYASH_VM_TRACE="$NYASH_VM_TRACE" \ "$NYASH_BIN" --backend vm lang/src/compiler/entry/compiler_stageb.hako \ -- --source "$(cat $TEST_FILE)" 2>&1 | tail -20 echo ""