🔧 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:
@ -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<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;
|
||||
|
||||
### 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<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 が ControlForm(LoopShape)を入口に使えるようにする。
|
||||
- 実装:
|
||||
- `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;
|
||||
|
||||
### 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 以降の大きめリファクタとして残しておく。
|
||||
|
||||
114
docs/development/roadmap/phases/phase-25.1i/README.md
Normal file
114
docs/development/roadmap/phases/phase-25.1i/README.md
Normal file
@ -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 以降のターゲットにする。
|
||||
125
docs/development/roadmap/phases/phase-25.1j/README.md
Normal file
125
docs/development/roadmap/phases/phase-25.1j/README.md
Normal file
@ -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)を実装する次フェーズのターゲットとする。
|
||||
90
docs/development/roadmap/phases/phase-25.1k/README.md
Normal file
90
docs/development/roadmap/phases/phase-25.1k/README.md
Normal file
@ -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 側はそれに追従する形で徐々に近づける。
|
||||
|
||||
Reference in New Issue
Block a user