🔧 Hotfix 7 (Enhanced): ValueId receiver alias tracking for nested loops

- 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)
This commit is contained in:
nyash-codex
2025-11-19 00:02:41 +09:00
parent 263affe379
commit 80f8a7bc8c
17 changed files with 983 additions and 277 deletions

View File

@ -1,5 +1,15 @@
# Current Task — Phase 21.8 / 25 / 25.1 / 25.2Numeric 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 ベースで安定。
- StageB 最小ハーネス `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` で StageB Test2/3 実行時に loop/exit/body の構造が観測できるようにし、undefined ValueId / `%0` の原因追跡に備える。
Update (2025-11-18 — Phase 25.1g: Conservative PHI ↔ ControlForm 統合の準備)
- Context:
- 25.1f までに Rust 側の ControlFormLoopShape / IfShape + ControlFormと .hako 側の ControlFormBox を定義し、観測レイヤーは安定した。

View File

@ -1,6 +1,6 @@
# Phase 25.1g — Conservative PHI ↔ ControlForm 統合(設計+小さな導線
# Phase 25.1g — Conservative PHI ↔ ControlForm 統合(Rust統合完了
Status: planning設計・導線追加既存挙動は変えない
Status: completedControlForm 導線統合+レガシー縮退/挙動は不変
## ゴール
@ -42,77 +42,163 @@ Status: planning設計・導線追加既存挙動は変えない
- Loop 用: `build_exit_phis_for_control(form: &ControlForm, ...)` を追加。
- 最初のステップでは「ControlForm から必要な BlockId を取り出して、既存の関数に委譲するだけ」の薄いラッパにする。
## タスク粒度
## 実施内容
### G1: If 用 Conservative PHI への ControlForm 導線
### G1: 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<O: PhiMergeOps>(
ops: &mut O,
form: &crate::mir::control_form::ControlForm,
pre_if_snapshot: &HashMap<String, ValueId>,
then_map_end: &HashMap<String, ValueId>,
else_map_end_opt: &Option<HashMap<String, ValueId>>,
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<O: PhiMergeOps>(
ops: &mut O,
form: &crate::mir::control_form::ControlForm,
pre_if_snapshot: &HashMap<String, ValueId>,
then_map_end: &HashMap<String, ValueId>,
else_map_end_opt: &Option<HashMap<String, ValueId>>,
skip_var: Option<&str>,
then_pred_opt: Option<crate::mir::BasicBlockId>,
else_pred_opt: Option<crate::mir::BasicBlockId>,
) -> Result<(), String> {
use crate::mir::control_form::ControlKind;
### G2: 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`
- いずれも PASSConservative PHI の挙動変化なし)。
### G2: 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<O: LoopFormOps>(
loopform: &LoopFormBuilder,
ops: &mut O,
form: &crate::mir::control_form::ControlForm,
exit_snapshots: &[(BasicBlockId, HashMap<String, ValueId>)],
) -> 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 が ControlFormLoopShapeを入口に使えるようにする。
- 実装:
- `src/mir/phi_core/loopform_builder.rs` に ControlForm 用ラッパ追加:
```rust
pub fn build_exit_phis_for_control<O: LoopFormOps>(
loopform: &LoopFormBuilder,
ops: &mut O,
form: &crate::mir::control_form::ControlForm,
exit_snapshots: &[(BasicBlockId, HashMap<String, ValueId>)],
branch_source_block: BasicBlockId,
) -> Result<(), String> {
use crate::mir::control_form::ControlKind;
### G3: 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 ベースでも構造が崩れていないことを確認。
### G3: 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 / StageB パイプラインはまだ ControlFormBox 未統合のまま。
- Test 3StageB MIR verifyの `%0` 問題や、BreakFinderBox 周辺の undefined ValueId は次フェーズLoopSSA 実装側)で扱う。
- LoopBuilder/If 降下そのものを ControlForm ベースに再構成する「Option C」は、Phase 25.2 以降の大きめリファクタとして残しておく

View File

@ -0,0 +1,114 @@
# Phase 25.1i — LoopSSA v2 (.hako) & ControlFormBox 統合StageB 入口)
Status: in-progress.hako 側 LoopSSA 設計足場実装Rust挙動は変えない
## ゴール
- Rust 側で整えた LoopForm v2 / ControlFormLoopShape / IfShapeを、.hako 側 LoopSSA にも導入し、
StageB 最小ハーネス(`tools/test_stageb_min.sh`で見えている「exit PHI / break 周りの赤ログ」に構造的にアプローチできる足場を作る。
- 25.1i のスコープでは、まず:
- LoopSSA の責務を ControlFormBox 中心の設計に書き換える(設計+薄い実装)。
- 既存の文字列ベース `_find_loops` / `_collect_phi_vars` を温存しつつ、
ControlFormBox に loop/if の形を写し取るための API を導入する。
- StageB Test 2/3 の赤ログundefined ValueId / `%0`)は「再現と観測」まで(このフェーズで「必ず全部直す」とはしない)。
## 現状25.1g/25.1i 途中時点)
- Rust:
- Loop/If:
- LoopForm v2 + Conservative PHI Box が ControlFormLoopShape/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 2StageB 経由): `BreakFinderBox.find_breaks/1` まわりで undefined ValueId(96)。
- Test 3MIR 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 形を写し取るための足場として利用開始
- StageB ハーネス:
- `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. StageB Test2/3 undefined ValueId / `%0` の原因箇所を観測し次フェーズで本格修正
## タスク粒度25.1i
### I1: LoopSSA / ControlFormBox の責務整理(設計)
- 目的:
- LoopSSA フェーズで何をやるべきかRust LoopForm v2 / ControlForm の設計に揃えて文章化する
- ステップ:
- `LoopSSA.stabilize_merges` のコメントを拡張し
- input: Stage1 JSON v0単一関数 or Program
- output: exit PHI が入った JSON v0
- 内部ステップ: BreakFinderBox LoopForm v2 的な Exit PHI パターンを .hako で再現
を書く
- ControlFormBox に対応する Rust LoopShape/IfShape のフィールドと責務を README で対応づける
### I2: 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 の簡易版 OKpreheader/latch は後から詰める)。
- `from_if(cond_block, then_block, else_block, merge_block)`:
- IfShape に対応する形で `kind_name = "if"` と各フィールドを設定
- これらはまだ LoopSSA からは呼ばずユニットテスト小さな JSON or 擬似値で構築が通ることだけ確認
### I3: 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 のまま)。
### I4: StageB 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 で完全再現するのは次フェーズの大仕事として分ける
- StageB 最小ハーネスの赤ログを必ずゼロにすること:
- 25.1i はあくまで LoopSSA v2 / ControlFormBox を設計組み込み始めるフェーズ
- undefined ValueId / `%0` 問題の根治は 25.1j 以降のターゲットにする

View File

@ -0,0 +1,125 @@
# Phase 25.1j — LoopSSA v2 本体 & StageB harness 強化
Status: planning.hako 側 LoopSSA v2 本体Rust SSA/PHI は触らない)
## ゴール
- 25.1i で整えた **LoopSSA + ControlFormBox 観測レイヤー** の上に、.hako 側 LoopSSA v2 の「本体」を載せる準備をする。
- 特に StageB 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 内のローカルで閉じるか」を決める。
- StageB Test 2 が Program(JSON v0) 出力まで進み、Rust 側 MirBuilder の SSA/PHI 検証に到達できる状態を作る。
- StageB 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`:
- StageB entry が `CompilerBuilder.apply_all(ast_json)` を通した後の JSON を出力する構成。
- 25.1i で `using lang.compiler.parser.parser_box as ParserBox` 等の修正により parse error は解消済み。
- StageB minimal harness:
- `tools/test_stageb_min.sh`:
- Test1: 直接 VM 実行 → RC=0。
- Test2: StageB 経由 → 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 本体の入口を整える)
### JA: LoopSSA/BREAK/PHI 周辺の trace 変数スコープの整理
- 目的:
- `.hako` 側 LoopSSA パスの中で `trace` という名前が **常にローカル or Box フィールドとして定義されている** 状態にする。
- StageB 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 は「引数でもらったフラグだけを見る」構造に変える。
### JB: LoopSSA v2 の責務再定義(設計)
- 目的:
- `LoopSSA.stabilize_merges` が「何を受け取り、どこまで責任を持つか」を Rust LoopForm v2 に揃えた形で **明文化** する。
- StageB / LoopSSA / BreakFinderBox / PhiInjectorBox の責務分割を Box 単位で固定し、将来の v2 実装時も迷わない足場を作る。
- 方針:
- LoopSSA パスを 3 層に分解して設計を書く:
1. **LoopSSAオーケストレータ箱**
- input: Stage1 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 を変更せず FailFast する(「部分的に壊れた JSON を返さない」)。
2. **BreakFinderBox解析箱 / readonly**
- input: Stage1 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変換箱 / writeonly**
- input: Stage1 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 とソースコードコメントで明確にする。
### JC: 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 配列を返す。
### JD: 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 アルゴリズムの全面移植)は行わない。
- StageB Test3 の `%0`/SSA エラーを「必ずゼロにする」こと:
- 25.1j では **trace 変数のスコープ修正と LoopSSA v2 の責務整理** に集中する。
- `%0` の根治は、LoopSSA v2 本体Carrier/Pinned/Exit PHIを実装する次フェーズのターゲットとする。

View File

@ -0,0 +1,90 @@
# Phase 25.1k — LoopSSA v2 実装 & StageB SSA 安定化(.hako 本体版)
Status: planning.hako 側 LoopSSA v2 本体実装Rust 側は既存 SSA/PHI を SSOT として維持)
## ゴール
- 25.1j までに固めた LoopSSA/BreakFinderBox/PhiInjectorBox の責務・設計をベースに、
`.hako` 側 LoopSSA v2 の **実装本体** に踏み込むフェーズだよ。
- 具体的には:
- StageB 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 は緑。
- StageB 風ループ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 の「中身」を少し前に進める)
### KA: StageB 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 出力を一時ファイルに保存StageB → Program(JSON v0) 直後)。
2. その JSON に対して:
- Rust 側 MirBuilder に直接食わせて `NYASH_VM_VERIFY_MIR=1` を通す。
- `.hako` 側 LoopSSA を単独で呼び出す(`LoopSSA.stabilize_merges(json)`)最小ハーネスを用意。
3. どの時点で ValueId(50) が未定義になるかを特定し、
それが LoopSSA の改変前後で変わっているかを確認する。
### KB: 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) のような飛び火を防ぐ)。
### KC: 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 には使わない)。
### KD: StageB Test3 (%0) の「悪化していない」ことの確認
- 目的:
- LoopSSA v2 の変更がStageB 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 側はそれに追従する形で徐々に近づける

View File

@ -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:
// - Stage1 Program(JSON v0) から「ループと break の位置」を検出する解析専用の箱だよ。
// - JSON 文字列は書き換えず、loop / break 情報を構造化して LoopSSA/PhiInjector に渡す。
// Input:
// - json_str: Program(JSON v0) 文字列(単一関数 or Program 全体)
// - trace_flag: 0 = silent, 1 = dev trace ONLoopSSA から渡される)
// Output:
// - Array of break info MapBox:
// { "block_id": <String>, "exit_id": <String>, "loop_header": <String> }
// - 付随して 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 ONLoopSSA から伝播)
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)
// ここで ControlFormBoxloop_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
}

View File

@ -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 MapBoxBreakFinderBox 由来: {block_id, exit_id, loop_header}
// - trace_flag: 0 = silent, 1 = dev trace ONLoopSSA から渡される)
// 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 ONLoopSSA から伝播)
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):
// 現在は「よくある変数名」を固定リストで見る簡易版だよ。
// - 長所: 実装が単純で、StageB 最小ケースの観測には十分。
// - 短所: 真の 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) {

View File

@ -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:
// - Stage1 Program(JSON v0) に対して、loop + break パターンを検出し、
// exit block に PHI 相当の命令を注入する「SSA 補強フェーズ」の入口だよ。
// - 自身はオーケストレータ箱として振る舞い、実際の解析/変換は
// BreakFinderBox / PhiInjectorBox に委譲するよ。
// Policy:
// - 入力: Stage1 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 point0/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
}
}

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,38 @@
// loopssa_breakfinder_min.hako — Minimal LoopSSA/BreakFinder JSON test harness
//
// 目的:
// - StageB を経由せずに、LoopSSA + BreakFinderBox + PhiInjectorBox の経路だけを
// 小さな Program(JSON v0) 文字列で通すミニハーネスだよ。
// - まずは「単純な loop_header/loop_exit パターンで正常に動く」ことを確認する足場として使う。
// - 将来的に StageB 最小サンプルから抽出した 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
}
}

View File

@ -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_idMVP: 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)
}
}

View File

@ -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"

View File

@ -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::<u32>() {
// 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

View File

@ -0,0 +1,48 @@
#!/bin/bash
# dump_stageb_min_mir.sh — StageB minimal harnessの Program(JSON v0) → MIR(JSON) ダンプ
#
# 目的:
# - lang/src/compiler/tests/stageb_min_sample.hako を StageB 経由で 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 ONLoopSSA 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."

View File

@ -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 の変更時に、まずここで赤が出ないことをチェックするための軽量テストだよ。
#
# 将来:
# - StageB 最小サンプルから抽出した 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 ==="

View File

@ -7,6 +7,14 @@ set -e
NYASH_BIN="${NYASH_BIN:-./target/release/hakorune}"
TEST_FILE="lang/src/compiler/tests/stageb_min_sample.hako"
# StageB / 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 ""