fix(mir): Phase 25.1m - Continue PHI修正 & Bug A main(args)ループ修正
**Phase 25.1m: Continue PHI修正** - seal_phis に continue_snapshots 入力を追加 (loopform_builder.rs) - LoopShape::debug_validate に continue/break エッジ検証追加 (control_form.rs) - test_seal_phis_includes_continue_snapshots テスト追加 - 実証テスト成功: balanced scan loop で 228回イテレーション確認 **Bug A修正: main(args) でループ未実行問題** - LoopBuilder::build_loop で entry → preheader への jump 追加 - decls.rs でデュアル関数作成時のブロック接続修正 - mir_static_main_args_loop.rs テスト追加 **パーサー改善**: - parser_box.hako に HAKO_PARSER_PROG_MAX ガード追加(無限ループ対策) 🎉 成果: - Continue 文の PHI predecessor mismatch エラー完全解消 - main(args) パラメータ有りループが正常動作 - Stage-B balanced scan で continue 正常動作確認 (228回イテレーション) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -259,33 +259,75 @@ impl LoopFormBuilder {
|
||||
|
||||
/// Pass 4: Seal PHI nodes after loop body lowering
|
||||
///
|
||||
/// Completes PHI nodes with latch inputs, converting them from:
|
||||
/// Completes PHI nodes with latch + continue inputs, converting them from:
|
||||
/// phi [preheader_val, preheader]
|
||||
/// to:
|
||||
/// phi [preheader_val, preheader], [latch_val, latch]
|
||||
/// phi [preheader_val, preheader], [continue_val, continue_bb]..., [latch_val, latch]
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `latch_id`: The block that closes the canonical backedge to `header`.
|
||||
/// - `continue_snapshots`: Per-`continue` block variable snapshots.
|
||||
/// Each entry represents a predecessor of `header` created by `continue`.
|
||||
pub fn seal_phis<O: LoopFormOps>(
|
||||
&mut self,
|
||||
ops: &mut O,
|
||||
latch_id: BasicBlockId,
|
||||
continue_snapshots: &[(BasicBlockId, HashMap<String, ValueId>)],
|
||||
) -> Result<(), String> {
|
||||
let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok();
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[loopform/seal_phis] header={:?} preheader={:?} latch={:?} continue_snapshots={}",
|
||||
self.header_id,
|
||||
self.preheader_id,
|
||||
latch_id,
|
||||
continue_snapshots.len()
|
||||
);
|
||||
}
|
||||
|
||||
// Seal pinned variable PHIs
|
||||
//
|
||||
// Pinned variables are loop-invariant parameters, but header has multiple
|
||||
// predecessors (preheader + continue + latch). To keep SSA well-formed,
|
||||
// we still materialize PHI inputs for all predecessors so that every edge
|
||||
// into header has a corresponding value.
|
||||
for pinned in &self.pinned {
|
||||
// Pinned variables are not modified in loop, so latch value = header phi
|
||||
let mut inputs: Vec<(BasicBlockId, ValueId)> =
|
||||
vec![(self.preheader_id, pinned.preheader_copy)];
|
||||
|
||||
// Add inputs from each continue snapshot that carries this variable.
|
||||
for (cid, snapshot) in continue_snapshots {
|
||||
if let Some(&value) = snapshot.get(&pinned.name) {
|
||||
inputs.push((*cid, value));
|
||||
}
|
||||
}
|
||||
|
||||
// Pinned variables are not modified in loop, so latch value typically
|
||||
// equals header PHI. Fallback to header_phi if lookup fails.
|
||||
let latch_value = ops
|
||||
.get_variable_at_block(&pinned.name, latch_id)
|
||||
.unwrap_or(pinned.header_phi);
|
||||
inputs.push((latch_id, latch_value));
|
||||
|
||||
ops.update_phi_inputs(
|
||||
self.header_id,
|
||||
pinned.header_phi,
|
||||
vec![
|
||||
(self.preheader_id, pinned.preheader_copy),
|
||||
(latch_id, latch_value),
|
||||
],
|
||||
)?;
|
||||
sanitize_phi_inputs(&mut inputs);
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[loopform/seal_phis] pinned '{}' phi={:?} inputs={:?}",
|
||||
pinned.name, pinned.header_phi, inputs
|
||||
);
|
||||
}
|
||||
|
||||
ops.update_phi_inputs(self.header_id, pinned.header_phi, inputs)?;
|
||||
}
|
||||
|
||||
// Seal carrier variable PHIs
|
||||
//
|
||||
// Carriers are loop-variant locals. They must merge values from:
|
||||
// - preheader (initial value before the loop),
|
||||
// - each continue block (early jump to header),
|
||||
// - latch (normal end-of-iteration backedge).
|
||||
for carrier in &mut self.carriers {
|
||||
carrier.latch_value = ops
|
||||
.get_variable_at_block(&carrier.name, latch_id)
|
||||
@ -293,14 +335,27 @@ impl LoopFormBuilder {
|
||||
format!("Carrier variable '{}' not found at latch block", carrier.name)
|
||||
})?;
|
||||
|
||||
ops.update_phi_inputs(
|
||||
self.header_id,
|
||||
carrier.header_phi,
|
||||
vec![
|
||||
(self.preheader_id, carrier.preheader_copy),
|
||||
(latch_id, carrier.latch_value),
|
||||
],
|
||||
)?;
|
||||
let mut inputs: Vec<(BasicBlockId, ValueId)> =
|
||||
vec![(self.preheader_id, carrier.preheader_copy)];
|
||||
|
||||
for (cid, snapshot) in continue_snapshots {
|
||||
if let Some(&value) = snapshot.get(&carrier.name) {
|
||||
inputs.push((*cid, value));
|
||||
}
|
||||
}
|
||||
|
||||
inputs.push((latch_id, carrier.latch_value));
|
||||
|
||||
sanitize_phi_inputs(&mut inputs);
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[loopform/seal_phis] carrier '{}' phi={:?} inputs={:?}",
|
||||
carrier.name, carrier.header_phi, inputs
|
||||
);
|
||||
}
|
||||
|
||||
ops.update_phi_inputs(self.header_id, carrier.header_phi, inputs)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -546,6 +601,7 @@ pub fn build_exit_phis_for_control<O: LoopFormOps>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_phi_inputs() {
|
||||
@ -687,4 +743,152 @@ mod tests {
|
||||
assert_eq!(builder.carriers[0].preheader_copy, ValueId::new(104)); // i copy
|
||||
assert_eq!(builder.carriers[0].header_phi, ValueId::new(105)); // i phi
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seal_phis_includes_continue_snapshots() {
|
||||
let preheader = BasicBlockId::new(0);
|
||||
let header = BasicBlockId::new(1);
|
||||
let latch = BasicBlockId::new(2);
|
||||
|
||||
// Prepare LoopFormBuilder with one pinned and one carrier variable
|
||||
let mut builder = LoopFormBuilder::new(preheader, header);
|
||||
builder.pinned.push(PinnedVariable {
|
||||
name: "p".to_string(),
|
||||
param_value: ValueId::new(1),
|
||||
preheader_copy: ValueId::new(10),
|
||||
header_phi: ValueId::new(20),
|
||||
});
|
||||
builder.carriers.push(CarrierVariable {
|
||||
name: "i".to_string(),
|
||||
init_value: ValueId::new(2),
|
||||
preheader_copy: ValueId::new(11),
|
||||
header_phi: ValueId::new(21),
|
||||
latch_value: ValueId::INVALID,
|
||||
});
|
||||
|
||||
// Mock LoopFormOps that records PHI updates
|
||||
struct MockSealOps {
|
||||
vars_at_block: HashMap<(BasicBlockId, String), ValueId>,
|
||||
phi_updates: Vec<(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)>,
|
||||
}
|
||||
|
||||
impl MockSealOps {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
vars_at_block: HashMap::new(),
|
||||
phi_updates: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LoopFormOps for MockSealOps {
|
||||
fn new_value(&mut self) -> ValueId {
|
||||
// Not used by seal_phis in this test
|
||||
ValueId::new(999)
|
||||
}
|
||||
|
||||
fn ensure_counter_after(&mut self, _max_id: u32) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn block_exists(&self, _block: BasicBlockId) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn get_block_predecessors(
|
||||
&self,
|
||||
_block: BasicBlockId,
|
||||
) -> std::collections::HashSet<BasicBlockId> {
|
||||
std::collections::HashSet::new()
|
||||
}
|
||||
|
||||
fn is_parameter(&self, _name: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_current_block(&mut self, _block: BasicBlockId) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_copy(&mut self, _dst: ValueId, _src: ValueId) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_jump(&mut self, _target: BasicBlockId) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_phi(
|
||||
&mut self,
|
||||
_dst: ValueId,
|
||||
_inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_phi_inputs(
|
||||
&mut self,
|
||||
block: BasicBlockId,
|
||||
phi_id: ValueId,
|
||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
) -> Result<(), String> {
|
||||
self.phi_updates.push((block, phi_id, inputs));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_var(&mut self, _name: String, _value: ValueId) {}
|
||||
|
||||
fn get_variable_at_block(
|
||||
&self,
|
||||
name: &str,
|
||||
block: BasicBlockId,
|
||||
) -> Option<ValueId> {
|
||||
self.vars_at_block
|
||||
.get(&(block, name.to_string()))
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
let mut ops = MockSealOps::new();
|
||||
// Latch values for p and i
|
||||
ops.vars_at_block
|
||||
.insert((latch, "p".to_string()), ValueId::new(30));
|
||||
ops.vars_at_block
|
||||
.insert((latch, "i".to_string()), ValueId::new(31));
|
||||
|
||||
// Continue snapshot from block 5: p and i have distinct values there
|
||||
let cont_bb = BasicBlockId::new(5);
|
||||
let mut cont_snapshot: HashMap<String, ValueId> = HashMap::new();
|
||||
cont_snapshot.insert("p".to_string(), ValueId::new(40));
|
||||
cont_snapshot.insert("i".to_string(), ValueId::new(41));
|
||||
let continue_snapshots = vec![(cont_bb, cont_snapshot)];
|
||||
|
||||
// Act: seal PHIs
|
||||
builder
|
||||
.seal_phis(&mut ops, latch, &continue_snapshots)
|
||||
.expect("seal_phis should succeed");
|
||||
|
||||
// We expect PHI updates for both pinned (p) and carrier (i)
|
||||
assert_eq!(ops.phi_updates.len(), 2);
|
||||
|
||||
// Helper to find inputs for a given phi id
|
||||
let find_inputs = |phi_id: ValueId,
|
||||
updates: &[(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)]| {
|
||||
updates
|
||||
.iter()
|
||||
.find(|(_, id, _)| *id == phi_id)
|
||||
.map(|(_, _, inputs)| inputs.clone())
|
||||
.expect("phi id not found in updates")
|
||||
};
|
||||
|
||||
let pinned_inputs = find_inputs(ValueId::new(20), &ops.phi_updates);
|
||||
assert!(pinned_inputs.contains(&(preheader, ValueId::new(10))));
|
||||
assert!(pinned_inputs.contains(&(cont_bb, ValueId::new(40))));
|
||||
assert!(pinned_inputs.contains(&(latch, ValueId::new(30))));
|
||||
|
||||
let carrier_inputs = find_inputs(ValueId::new(21), &ops.phi_updates);
|
||||
assert!(carrier_inputs.contains(&(preheader, ValueId::new(11))));
|
||||
assert!(carrier_inputs.contains(&(cont_bb, ValueId::new(41))));
|
||||
assert!(carrier_inputs.contains(&(latch, ValueId::new(31))));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user