refactor(mir): Phase 25.1o - do_break/continue 共通化(LoopExitKind型統一)

【変更内容】
1. LoopExitKind enum定義
   - Break / Continue の型安全な区別

2. do_loop_exit() 共通メソッド作成(47行)
   - スナップショット取得(共通処理)
   - kind別のスナップショット保存
   - kind別のジャンプターゲット
   - unreachable ブロック切り替え(共通処理)

3. do_break/continue をthin wrapperに変換
   - do_break: 13行 → 4行
   - do_continue: 12行 → 4行
   - 合計21行削減

【効果】
- 構造改善: break/continue の共通ロジック一箇所に集約
- 保守性向上: デバッグログなどの共通処理が統一管理
- 拡張性向上: labeled break/continue等の将来拡張が容易

【検証結果】
- ビルド成功(警告なし)
- mir_stageb_loop_break_continue_verifies: PASS
- /tmp/loop_continue_fixed.hako: RC=3(期待通り)

関連: Phase 25.1m (continue PHI修正), Phase 25.1n (レガシー削除)
This commit is contained in:
nyash-codex
2025-11-19 08:56:44 +09:00
parent 77fc670cd3
commit 75f3df2505
11 changed files with 92 additions and 89 deletions

View File

@ -70,7 +70,7 @@
- `src/tests/mir_stageb_loop_break_continue_verifies` - `src/tests/mir_stageb_loop_break_continue_verifies`
- StageB 風の `loop + break/continue` パターンで MirVerifier 緑。 - StageB 風の `loop + break/continue` パターンで MirVerifier 緑。
- `src/tests/mir_stage1_using_resolver_verify.rs::mir_stage1_using_resolver_full_collect_entries_verifies` - `src/tests/mir_stage1_using_resolver_verify.rs::mir_stage1_using_resolver_full_collect_entries_verifies`
- `NYASH_LOOPFORM_PHI_V2=1` で Stage1 UsingResolver フル版が LoopForm v2/PHI v2 経路で MirVerifier 緑。 - LoopForm v2/PHI v2 経路(現在は既定実装)で Stage1 UsingResolver フル版が MirVerifier 緑。
- 実行観測: - 実行観測:
- 開発用 Hako `loop_continue_fixed.hako`: - 開発用 Hako `loop_continue_fixed.hako`:
- 期待 `RC=3` / 実測 `RC=3`、PHI pred mismatch なし。 - 期待 `RC=3` / 実測 `RC=3`、PHI pred mismatch なし。

View File

@ -89,16 +89,9 @@ pub fn build_loop(
condition: &ASTNode, condition: &ASTNode,
body: &Vec<ASTNode>, body: &Vec<ASTNode>,
) -> Result<ValueId, String> { ) -> Result<ValueId, String> {
// Check feature flag // 設計当初は feature flagNYASH_LOOPFORM_PHI_V2で切り替える案だったが、
let use_loopform_v2 = std::env::var("NYASH_LOOPFORM_PHI_V2") // 現在は LoopForm v2 が既定実装なので常に build_loop_with_loopform を使う。
.map(|v| v == "1" || v.to_lowercase() == "true") self.build_loop_with_loopform(condition, body)
.unwrap_or(false);
if use_loopform_v2 {
self.build_loop_with_loopform(condition, body)
} else {
self.build_loop_legacy(condition, body)
}
} }
fn build_loop_with_loopform( fn build_loop_with_loopform(
@ -177,8 +170,7 @@ fn build_loop_legacy(
# Build with new implementation # Build with new implementation
cargo build --release cargo build --release
# Test fibonacci # Test fibonacci(現在はフラグ不要)
export NYASH_LOOPFORM_PHI_V2=1
./target/release/nyash local_tests/fib_multi_carrier.hako ./target/release/nyash local_tests/fib_multi_carrier.hako
# Expected output: 8 # Expected output: 8
@ -186,8 +178,7 @@ export NYASH_LOOPFORM_PHI_V2=1
**Debugging**: **Debugging**:
```bash ```bash
# Enable MIR dump # Enable MIR dump(現在はフラグ不要)
export NYASH_LOOPFORM_PHI_V2=1
./target/release/nyash --dump-mir local_tests/fib_multi_carrier.hako ./target/release/nyash --dump-mir local_tests/fib_multi_carrier.hako
# Check for: # Check for:
@ -202,9 +193,7 @@ export NYASH_LOOPFORM_PHI_V2=1
**Run existing tests**: **Run existing tests**:
```bash ```bash
export NYASH_LOOPFORM_PHI_V2=1 # Simple loopsLoopForm v2 は既定 ON
# Simple loops
tools/smokes/v2/run.sh --profile quick --filter "loop_simple" tools/smokes/v2/run.sh --profile quick --filter "loop_simple"
# Multi-carrier loops # Multi-carrier loops

View File

@ -100,11 +100,13 @@ preheader → header (PHI nodes) → body → latch → header
### Integration Points ### Integration Points
**Feature Flag**: `NYASH_LOOPFORM_PHI_V2=1` (environment variable) **Status**: 当初は `NYASH_LOOPFORM_PHI_V2=1` で opt-in する Feature Flag 案だったが、
現在は LoopFormBuilder を用いた PHI 生成が **常に既定実装** になっている。
過去の段階的移行案(参考・設計メモとして残す):
**Migration Strategy**:
```rust ```rust
// In mir/loop_builder.rs // In mir/loop_builder.rs (設計当初の案。現在は v2 が常時有効)
if std::env::var("NYASH_LOOPFORM_PHI_V2").is_ok() { if std::env::var("NYASH_LOOPFORM_PHI_V2").is_ok() {
// Use new LoopFormBuilder // Use new LoopFormBuilder
let mut loopform = LoopFormBuilder::new(preheader_id, header_id); let mut loopform = LoopFormBuilder::new(preheader_id, header_id);
@ -148,38 +150,10 @@ if std::env::var("NYASH_LOOPFORM_PHI_V2").is_ok() {
## Testing Strategy ## Testing Strategy
### Phase 1: Smoke Tests ### Phase 13: 現在の運用メモ
```bash
# Enable new implementation
export NYASH_LOOPFORM_PHI_V2=1
# Test fibonacci multi-carrier - 実装が LoopForm v2 に一本化されたため、`NYASH_LOOPFORM_PHI_V2` による A/B 比較フェーズは既に完了済み。
cargo build --release - 以降の性能比較や回帰テストでは、単に `./target/release/nyash` を直接叩けばよい(フラグ不要)。
./target/release/nyash local_tests/fib_multi_carrier.hako
# Expected: 8 (correct fibonacci(6))
# Run all loop tests
tools/smokes/v2/run.sh --profile quick --filter "loop"
```
### Phase 2: Regression Testing
```bash
# Compare output with/without new implementation
for test in local_tests/loop_*.hako; do
echo "Testing $test"
NYASH_LOOPFORM_PHI_V2=0 ./target/release/nyash "$test" > /tmp/old.out
NYASH_LOOPFORM_PHI_V2=1 ./target/release/nyash "$test" > /tmp/new.out
diff /tmp/old.out /tmp/new.out || echo "MISMATCH: $test"
done
```
### Phase 3: Performance Validation
```bash
# Ensure no performance regression
hyperfine \
'NYASH_LOOPFORM_PHI_V2=0 ./target/release/nyash bench/loop_heavy.hako' \
'NYASH_LOOPFORM_PHI_V2=1 ./target/release/nyash bench/loop_heavy.hako'
```
--- ---

View File

@ -18,7 +18,7 @@ Status: design+partial implementationStage1 ビルド導線の初期版まで
- 担当: StageB/Stage1/selfhost で見えている Undefined Value / nondominating use を、まず Rust 階層だけで止血する。 - 担当: StageB/Stage1/selfhost で見えている Undefined Value / nondominating use を、まず Rust 階層だけで止血する。
- **25.1e — LoopForm PHI v2 MigrationRust** - **25.1e — LoopForm PHI v2 MigrationRust**
- ねらい: ループの PHI 生成の「SSOT」を LoopForm v2 + `phi_core` に寄せ、Legacy LoopBuilder 経路との二重管理を解消する。 - ねらい: ループの PHI 生成の「SSOT」を LoopForm v2 + `phi_core` に寄せ、Legacy LoopBuilder 経路との二重管理を解消する。
- 担当: `NYASH_LOOPFORM_PHI_V2=1` を使って Stage1 / StageB 代表ループ(`_find_from` や stageb_minを通し、`phi pred mismatch` / ValueId 二重定義を構造的に解消する。 - 担当: 当初は `NYASH_LOOPFORM_PHI_V2=1` を使って Stage1 / StageB 代表ループ(`_find_from` や stageb_minを通し、`phi pred mismatch` / ValueId 二重定義を構造的に解消する計画だったが、現在は LoopForm v2 が既定実装となっており、フラグは不要(互換目的のみ)
ざっくりとした進行順は「25.1a/c で配線と箱分割 → 25.1d/e で Rust MIR/LoopForm を根治 → その結果を踏まえて 25.1bselfhost MirBuilder/LoopSSA側に寄せていく」というイメージだよ。 ざっくりとした進行順は「25.1a/c で配線と箱分割 → 25.1d/e で Rust MIR/LoopForm を根治 → その結果を踏まえて 25.1bselfhost MirBuilder/LoopSSA側に寄せていく」というイメージだよ。

View File

@ -30,9 +30,10 @@ Status: planningLoopForm/PHI 正規化フェーズ・挙動は変えない/
- **SSOT を決める**: - **SSOT を決める**:
- ループの PHI 生成の「正」は LoopForm v2 (`LoopFormBuilder` + `LoopFormOps` + `phi_core::loop_phi/if_phi`) に置く。 - ループの PHI 生成の「正」は LoopForm v2 (`LoopFormBuilder` + `LoopFormOps` + `phi_core::loop_phi/if_phi`) に置く。
- `build_loop_legacy` + `prepare_loop_variables_with` は「互換レイヤ/移行レイヤ」と位置づけ、最終的には LoopForm v2 の薄いラッパに縮退させる。 - `build_loop_legacy` + `prepare_loop_variables_with` は「互換レイヤ/移行レイヤ」と位置づけ、最終的には LoopForm v2 の薄いラッパに縮退させる。
- **Feature Flag で段階導入**: - **Feature Flag で段階導入(※この段階の設計メモ)**:
- `NYASH_LOOPFORM_PHI_V2=1` のときだけ LoopForm v2 経路を使い、当面は StageB / Stage1 / selfhost 用テストの中でピンポイントに有効化する。 - 当初は `NYASH_LOOPFORM_PHI_V2=1` のときだけ LoopForm v2 経路を使う案だったが、
- デフォルト挙動は変えない(フラグ未設定時はこれまでどおり legacy 経路 現在は LoopForm v2 が既定実装となっておりlegacy 経路は撤去済み
- `NYASH_LOOPFORM_PHI_V2` は互換性のために残っているが、挙動切り替えには使われない。
- **1 バグ 1 パターンで前進**: - **1 バグ 1 パターンで前進**:
- `mir_stage1_using_resolver_full_collect_entries_verifies` や StageB 最小ハーネスで見えている赤ログは、それぞれ最小 Hako に絞って LoopForm v2 側で再現・修正する。 - `mir_stage1_using_resolver_full_collect_entries_verifies` や StageB 最小ハーネスで見えている赤ログは、それぞれ最小 Hako に絞って LoopForm v2 側で再現・修正する。
- 同時に legacy 側からは同じ責務PHI 生成ロジック)を抜いていき、二重管理を減らす。 - 同時に legacy 側からは同じ責務PHI 生成ロジック)を抜いていき、二重管理を減らす。
@ -41,9 +42,9 @@ Status: planningLoopForm/PHI 正規化フェーズ・挙動は変えない/
### 1. LoopForm v2 の足場をテストで固める ### 1. LoopForm v2 の足場をテストで固める
1.1 LoopForm v2 専用テストモードの追加 1.1 LoopForm v2 専用テストモードの追加(→ 現在は「LoopForm v2 が既定」の前提で完了)
- `mir_stage1_using_resolver_full_collect_entries_verifies` をベースに、`NYASH_LOOPFORM_PHI_V2=1` を立てた状態でのみ走るサブテストを追加。 - `mir_stage1_using_resolver_full_collect_entries_verifies` は LoopForm v2 前提で緑になっており、
- 目的: v2 経路で `_find_from` ループの SSA/PHI が整合していることを検証する足場を作る(まだ赤でもよい)。 もはやフラグは不要(テスト内の `NYASH_LOOPFORM_PHI_V2` 設定も削除済み)。
1.2 StageB 最小ハーネス用ループ抜き出しテスト 1.2 StageB 最小ハーネス用ループ抜き出しテスト
- `lang/src/compiler/tests/stageb_min_sample.hako` から、代表的なループだけを抜き出した Hako を作り、LoopForm v2 経路(`NYASH_LOOPFORM_PHI_V2=1`)で `MirVerifier` を通すテストを追加。 - `lang/src/compiler/tests/stageb_min_sample.hako` から、代表的なループだけを抜き出した Hako を作り、LoopForm v2 経路(`NYASH_LOOPFORM_PHI_V2=1`)で `MirVerifier` を通すテストを追加。

View File

@ -98,7 +98,9 @@ The .hako implementation in this directory is **architecturally correct** but op
- `HAKO_LOOPSSA_EXIT_PHI=1`: Enable .hako EXIT PHI (disabled by default) - `HAKO_LOOPSSA_EXIT_PHI=1`: Enable .hako EXIT PHI (disabled by default)
- `HAKO_COMPILER_BUILDER_TRACE=1`: Show compilation pass trace - `HAKO_COMPILER_BUILDER_TRACE=1`: Show compilation pass trace
- `NYASH_LOOPFORM_DEBUG=1`: Debug Rust loopform builder - `NYASH_LOOPFORM_DEBUG=1`: Debug Rust loopform builder
- `NYASH_LOOPFORM_PHI_V2=1`: Enable Rust EXIT PHI generation - `NYASH_LOOPFORM_PHI_V2`:
- 以前は Rust 側 LoopForm PHI v2 の切り替えフラグだったが、
- 現在は **LoopForm PHI v2 が常に既定実装**のため、設定不要(存在しても挙動は変わらない)。
## File Sizes ## File Sizes

View File

@ -17,6 +17,15 @@ use super::utils::{
capture_actual_predecessor_and_jump, capture_actual_predecessor_and_jump,
}; };
/// ループ脱出の種類(箱化・共通化のための型)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LoopExitKind {
/// break文exit blockへジャンプ
Break,
/// continue文header blockへジャンプ
Continue,
}
/// ループビルダー - SSA形式でのループ構築を管理 /// ループビルダー - SSA形式でのループ構築を管理
pub struct LoopBuilder<'a> { pub struct LoopBuilder<'a> {
/// 親のMIRビルダーへの参照 /// 親のMIRビルダーへの参照
@ -67,34 +76,64 @@ impl<'a> LoopBuilder<'a> {
Ok(void_id) Ok(void_id)
} }
/// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block. /// 【箱化】ループ脱出の共通処理break/continue統一化
fn do_break(&mut self) -> Result<ValueId, String> { ///
// Snapshot variables at break point for exit PHI generation /// Phase 25.1o: break と continue の共通パターンを抽出し、
/// LoopExitKind で振る舞いを切り替える統一メソッド。
///
/// # 処理フロー
/// 1. 現在の変数マップをスナップショット
/// 2. 適切なスナップショットリストに追加Break → exit_snapshots, Continue → continue_snapshots
/// 3. ターゲットブロックへジャンプBreak → exit, Continue → header
/// 4. unreachable ブロックに切り替え
fn do_loop_exit(&mut self, kind: LoopExitKind) -> Result<ValueId, String> {
// 1. スナップショット取得(共通処理)
let snapshot = self.get_current_variable_map(); let snapshot = self.get_current_variable_map();
let cur_block = self.current_block()?; let cur_block = self.current_block()?;
eprintln!("[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}",
cur_block, snapshot.keys().collect::<Vec<_>>());
self.exit_snapshots.push((cur_block, snapshot));
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) { // 2. スナップショット保存kind別処理
self.jump_with_pred(exit_bb)?; match kind {
LoopExitKind::Break => {
if std::env::var("NYASH_LOOPFORM_DEBUG").ok().as_deref() == Some("1") {
eprintln!("[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}",
cur_block, snapshot.keys().collect::<Vec<_>>());
}
self.exit_snapshots.push((cur_block, snapshot));
}
LoopExitKind::Continue => {
self.block_var_maps.insert(cur_block, snapshot.clone());
self.continue_snapshots.push((cur_block, snapshot));
}
} }
// 3. ターゲットブロックへジャンプkind別処理
match kind {
LoopExitKind::Break => {
if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) {
self.jump_with_pred(exit_bb)?;
}
}
LoopExitKind::Continue => {
if let Some(header) = self.loop_header {
self.jump_with_pred(header)?;
}
}
}
// 4. unreachable ブロックに切り替え(共通処理)
self.switch_to_unreachable_block_with_void() self.switch_to_unreachable_block_with_void()
} }
/// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block.
/// 【箱化】do_loop_exit() への thin wrapper
fn do_break(&mut self) -> Result<ValueId, String> {
self.do_loop_exit(LoopExitKind::Break)
}
/// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block. /// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block.
/// 【箱化】do_loop_exit() への thin wrapper
fn do_continue(&mut self) -> Result<ValueId, String> { fn do_continue(&mut self) -> Result<ValueId, String> {
// Snapshot variables at current block to be considered as a predecessor input self.do_loop_exit(LoopExitKind::Continue)
let snapshot = self.get_current_variable_map();
let cur_block = self.current_block()?;
self.block_var_maps.insert(cur_block, snapshot.clone());
self.continue_snapshots.push((cur_block, snapshot));
if let Some(header) = self.loop_header {
self.jump_with_pred(header)?;
}
self.switch_to_unreachable_block_with_void()
} }
// ============================================================= // =============================================================

View File

@ -4,8 +4,8 @@
* Solves the ValueId circular dependency problem by treating loop structure * Solves the ValueId circular dependency problem by treating loop structure
* as a "Meta-Box" with explicit separation of carriers vs. pinned variables. * as a "Meta-Box" with explicit separation of carriers vs. pinned variables.
* *
* Phase: 25.1b prototype implementation * Phase: 25.1b prototype implementation → 25.1m で安定化
* Status: Feature-flagged (NYASH_LOOPFORM_PHI_V2=1) * Status: Always-onLoopForm PHI v2 は既定実装。NYASH_LOOPFORM_PHI_V2 は互換目的のみで、挙動切り替えには使われない)
*/ */
use crate::mir::{BasicBlockId, ValueId}; use crate::mir::{BasicBlockId, ValueId};

View File

@ -10,8 +10,8 @@ use crate::mir::{MirCompiler, MirVerifier};
#[test] #[test]
fn test_loopform_exit_phi_single_break() { fn test_loopform_exit_phi_single_break() {
// Enable LoopForm PHI v2 and MIR verification // LoopForm PHI v2 はデフォルト実装(フラグ不要)
std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); // Enable MIR verification and debug traces
std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); std::env::set_var("NYASH_VM_VERIFY_MIR", "1");
std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); std::env::set_var("NYASH_LOOPFORM_DEBUG", "1");
std::env::set_var("NYASH_PARSER_STAGE3", "1"); std::env::set_var("NYASH_PARSER_STAGE3", "1");
@ -70,7 +70,7 @@ static box TestExitPhi {
#[test] #[test]
fn test_loopform_exit_phi_multiple_breaks() { fn test_loopform_exit_phi_multiple_breaks() {
std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); // LoopForm PHI v2 はデフォルト実装(フラグ不要)
std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); std::env::set_var("NYASH_VM_VERIFY_MIR", "1");
std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); std::env::set_var("NYASH_LOOPFORM_DEBUG", "1");
std::env::set_var("NYASH_PARSER_STAGE3", "1"); std::env::set_var("NYASH_PARSER_STAGE3", "1");
@ -108,7 +108,7 @@ static box TestMultiBreak {
#[test] #[test]
fn test_loopform_exit_phi_nested_if_break() { fn test_loopform_exit_phi_nested_if_break() {
std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); // LoopForm PHI v2 はデフォルト実装(フラグ不要)
std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); std::env::set_var("NYASH_VM_VERIFY_MIR", "1");
std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); std::env::set_var("NYASH_LOOPFORM_DEBUG", "1");
std::env::set_var("NYASH_PARSER_STAGE3", "1"); std::env::set_var("NYASH_PARSER_STAGE3", "1");
@ -151,7 +151,7 @@ static box TestNestedBreak {
#[test] #[test]
fn test_loopform_exit_phi_multiple_vars() { fn test_loopform_exit_phi_multiple_vars() {
std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1"); // LoopForm PHI v2 はデフォルト実装(フラグ不要)
std::env::set_var("NYASH_VM_VERIFY_MIR", "1"); std::env::set_var("NYASH_VM_VERIFY_MIR", "1");
std::env::set_var("NYASH_LOOPFORM_DEBUG", "1"); std::env::set_var("NYASH_LOOPFORM_DEBUG", "1");
std::env::set_var("NYASH_PARSER_STAGE3", "1"); std::env::set_var("NYASH_PARSER_STAGE3", "1");

View File

@ -91,8 +91,7 @@ static box Stage1UsingResolverMini {
#[test] #[test]
fn mir_stage1_using_resolver_full_collect_entries_verifies() { fn mir_stage1_using_resolver_full_collect_entries_verifies() {
ensure_stage3_env(); ensure_stage3_env();
// Use LoopForm PHI v2 for this test to exercise the new SSOT経路 // LoopForm PHI v2 はデフォルト実装(フラグ不要)
std::env::set_var("NYASH_LOOPFORM_PHI_V2", "1");
let src = r#" let src = r#"
static box Stage1UsingResolverFull { static box Stage1UsingResolverFull {
// Simplified helper to find substring index (replaces JsonFragBox.index_of_from) // Simplified helper to find substring index (replaces JsonFragBox.index_of_from)

View File

@ -34,7 +34,6 @@ HAKO_STAGEB_FUNC_SCAN=1 \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \ NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \
NYASH_PARSER_ALLOW_SEMICOLON=1 \ NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \ NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \
NYASH_LOOPFORM_PHI_V2=1 \
HAKO_LOOPSSA_EXIT_PHI="$HAKO_LOOPSSA_EXIT_PHI" \ HAKO_LOOPSSA_EXIT_PHI="$HAKO_LOOPSSA_EXIT_PHI" \
HAKO_COMPILER_BUILDER_TRACE="$HAKO_COMPILER_BUILDER_TRACE" \ HAKO_COMPILER_BUILDER_TRACE="$HAKO_COMPILER_BUILDER_TRACE" \
NYASH_VM_TRACE="$NYASH_VM_TRACE" \ NYASH_VM_TRACE="$NYASH_VM_TRACE" \