feat(loop-phi): Phase 25.1c/k - continue_merge PHI生成完了

## 実装内容

### 1. continue_merge ブロックで PHI ノード生成
- `src/mir/loop_builder.rs` (422-557行)
- 複数の continue パスからの変数値を PHI でマージ
- 全て同じ値なら PHI 省略(最適化)
- merged_snapshot を seal_phis に渡す構造

### 2. ValueId::INVALID GUARD 修正
- `src/mir/phi_core/loopform_builder.rs` (111行)
- 誤った `value.0 == 0` チェックを `value == ValueId::INVALID` に修正
- ValueId::INVALID は u32::MAX なので、ValueId(0) は有効な値

### 3. test_loopform_builder_separation を構造ベースに改善
- 具体的な ValueId(100..105) を期待するアサーションを削除
- pinned/carrier の分離、ValueId の有効性、衝突チェックに変更
- HashMap の反復順序や内部の割り当て順に依存しないテストに改善

## テスト結果

 **既存テスト全て PASS**:
- `test_loopform_builder_separation` - 構造ベース修正で PASS
- 既存ループ関連テスト15個 - 全て PASS
- `mir_stageb_loop_break_continue::*` - PASS
- `mir_loopform_exit_phi::*` - PASS

 **実行確認**:
- 基本的なループ実行 - 正常動作(sum=10)
- continue を含むループ実行 - 正常動作(sum=8)
- continue_merge ブロック生成確認(BasicBlockId表示)

⚠️ **残存バグ**:
- FuncScannerBox.scan_all_boxes/1: ValueId(1283) undefined
- 13個の continue を持つ複雑なループで発生
- Phase 25.2 リファクタリングで解決予定

## 今後の予定

Phase 25.2 として以下のリファクタリングを実施予定:
1. LoopSnapshotMergeBox 実装(優先度1)
2. LoopVarClassifyBox 実装(優先度2)
3. LoopDebugLogBox 実装(優先度3)
4. TextScanRegionBox 実装(優先度4)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-20 01:41:17 +09:00
parent 7c3f1eafde
commit 7373fa265b
3 changed files with 190 additions and 24 deletions

View File

@ -35,9 +35,14 @@ pub struct LoopBuilder<'a> {
#[allow(dead_code)]
block_var_maps: HashMap<BasicBlockId, HashMap<String, ValueId>>,
/// ループヘッダーIDcontinue使用)
/// ループヘッダーIDcontinue 先の既定値として使用)
loop_header: Option<BasicBlockId>,
/// continue 文がジャンプするターゲットブロック
/// - 既定: header と同一
/// - 将来: canonical continue merge ブロックに差し替えるためのフック
continue_target: Option<BasicBlockId>,
/// continue文からの変数スナップショット
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
@ -114,8 +119,10 @@ impl<'a> LoopBuilder<'a> {
}
}
LoopExitKind::Continue => {
if let Some(header) = self.loop_header {
self.jump_with_pred(header)?;
// 既定では header にジャンプするが、canonical continue merge を導入した場合は
// continue_target 側を優先する。
if let Some(target) = self.continue_target.or(self.loop_header) {
self.jump_with_pred(target)?;
}
}
}
@ -145,6 +152,7 @@ impl<'a> LoopBuilder<'a> {
parent_builder: parent,
block_var_maps: HashMap::new(),
loop_header: None,
continue_target: None,
continue_snapshots: Vec::new(),
exit_snapshots: Vec::new(), // exit PHI用のスナップショット
}
@ -210,6 +218,9 @@ impl<'a> LoopBuilder<'a> {
let body_id = self.new_block();
let latch_id = self.new_block();
let exit_id = self.new_block();
// Phase 25.1q: canonical continue merge block
// All continue 文は一度このブロックに集約してから header へ戻る。
let continue_merge_id = self.new_block();
// Jump from current block to preheader
let entry_block = self.current_block()?;
@ -273,6 +284,9 @@ impl<'a> LoopBuilder<'a> {
// Set up loop context for break/continue
crate::mir::builder::loops::push_loop_context(self.parent_builder, header_id, exit_id);
self.loop_header = Some(header_id);
// 既定の continue 先を canonical continue_merge ブロックにする。
// ここを差し替えることで do_loop_exit(Continue) のターゲットを一元化する。
self.continue_target = Some(continue_merge_id);
self.continue_snapshots.clear();
self.exit_snapshots.clear();
@ -401,11 +415,90 @@ impl<'a> LoopBuilder<'a> {
}
}
// Pass 4: Seal PHIs with latch + continue values
let continue_snaps = self.continue_snapshots.clone();
// Pass 4: Generate continue_merge PHIs first, then seal header PHIs
// Phase 25.1c/k: canonical continue_merge ブロックで PHI を生成してから seal_phis を呼ぶ
let raw_continue_snaps = self.continue_snapshots.clone();
// Step 1: continue_merge ブロックで PHI 生成merged_snapshot を作る)
self.set_current_block(continue_merge_id)?;
let merged_snapshot: HashMap<String, ValueId> = if !raw_continue_snaps.is_empty() {
if trace_loop_phi {
eprintln!("[loop-phi/continue-merge] Generating PHI nodes for {} continue paths",
raw_continue_snaps.len());
}
// すべての continue snapshot に現れる変数を収集
let mut all_vars: HashMap<String, Vec<(BasicBlockId, ValueId)>> = HashMap::new();
for (continue_bb, snapshot) in &raw_continue_snaps {
for (var_name, &value) in snapshot {
all_vars.entry(var_name.clone())
.or_default()
.push((*continue_bb, value));
}
}
// 各変数について PHI ノードを生成
let mut merged = HashMap::new();
for (var_name, inputs) in &all_vars {
// 値が全て同じなら PHI は不要(最適化)
let values: Vec<ValueId> = inputs.iter().map(|(_, v)| *v).collect();
let all_same = values.windows(2).all(|w| w[0] == w[1]);
let result_value = if all_same && !values.is_empty() {
// すべて同じ値なら PHI なしで直接使用
values[0]
} else if inputs.len() == 1 {
// 入力が1つだけなら PHI なしで直接使用
inputs[0].1
} else {
// 異なる値を持つ場合は PHI ノードを生成
let phi_id = self.new_value();
if let Some(ref mut func) = self.parent_builder.current_function {
if let Some(merge_block) = func.blocks.get_mut(&continue_merge_id) {
merge_block.add_instruction(MirInstruction::Phi {
dst: phi_id,
inputs: inputs.clone(),
});
}
}
if trace_loop_phi {
eprintln!("[loop-phi/continue-merge] Generated PHI for '{}': {:?} inputs={:?}",
var_name, phi_id, inputs);
}
phi_id
};
merged.insert(var_name.clone(), result_value);
}
// Note: 変数マップへの反映は seal_phis に委譲(干渉を避ける)
if trace_loop_phi {
eprintln!("[loop-phi/continue-merge] Generated {} PHI nodes, merged snapshot has {} vars",
all_vars.iter().filter(|(_, inputs)| inputs.len() > 1).count(),
merged.len());
}
merged
} else {
HashMap::new()
};
self.emit_jump(header_id)?;
crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, continue_merge_id)?;
// Step 2: merged_snapshot を使って seal_phis を呼ぶ
let continue_snaps: Vec<(BasicBlockId, HashMap<String, ValueId>)> =
if merged_snapshot.is_empty() {
vec![]
} else {
vec![(continue_merge_id, merged_snapshot.clone())]
};
loopform.seal_phis(self, actual_latch_id, &continue_snaps)?;
// Phase 25.1c/k: seal body-local PHIs (complete the inputs)
// Step 3: seal body-local PHIs (complete the inputs)
if !body_local_vars.is_empty() {
if trace_loop_phi {
eprintln!("[loop-phi/body-local] Sealing {} body-local PHIs", body_local_vars.len());
@ -464,15 +557,17 @@ impl<'a> LoopBuilder<'a> {
// Phase 25.1h: ControlForm統合版に切り替え
// continue / break のターゲットブロックをユニーク化して収集
use std::collections::HashSet;
let mut cont_set: HashSet<BasicBlockId> = HashSet::new();
let mut break_set: HashSet<BasicBlockId> = HashSet::new();
for (bb, _) in &self.continue_snapshots {
cont_set.insert(*bb);
}
for (bb, _) in &self.exit_snapshots {
break_set.insert(*bb);
}
let continue_targets: Vec<BasicBlockId> = cont_set.into_iter().collect();
// LoopShape の continue_targets は「header への canonical backedge」を表す。
// continue が一つ以上存在する場合は continue_merge_id を 1 つだけ登録する。
let continue_targets: Vec<BasicBlockId> = if self.continue_snapshots.is_empty() {
Vec::new()
} else {
vec![continue_merge_id]
};
let break_targets: Vec<BasicBlockId> = break_set.into_iter().collect();
let loop_shape = LoopShape {