feat(mir): Phase 63-6-1/2 MIR Phi type_hint field & JoinIR propagation

Phase 63-6-1: MirInstruction::Phi に type_hint フィールド追加
- Added `type_hint: Option<MirType>` field to Phi instruction
- Updated 21 files with type_hint initialization (all set to None for legacy paths)
- Pattern matching updated across codebase (11 files)
- Test code updated (basic_block.rs)

Phase 63-6-2: JoinIR→MIR Bridge で型ヒント伝播実装
- Modified convert.rs: Select → MIR now creates PHI with type_hint
- Removed Copy instructions from then/else blocks
- PHI instruction at merge block receives type_hint from JoinIR Select
- Test verification:  Type hint propagation successful (Some(Integer))

Modified files:
- instruction.rs: Added type_hint field definition
- join_ir_vm_bridge/convert.rs: Select lowering with PHI + type_hint
- 19 other files: type_hint field initialization

Test results:
-  test_type_hint_propagation_simple: Type hint = Some(Integer) confirmed
-  7/8 if_select tests passing (1 race condition, passes individually)

Next: Phase 63-6-3 (lifecycle.rs で型ヒント使用)

🤖 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-30 04:35:40 +09:00
parent 360ad48d93
commit c6edbaaf3a
21 changed files with 55 additions and 33 deletions

View File

@ -172,7 +172,7 @@ impl MirInterpreter {
self.last_block = Some(block.id); self.last_block = Some(block.id);
self.last_inst_index = Some(idx); self.last_inst_index = Some(idx);
self.last_inst = Some(inst.clone()); self.last_inst = Some(inst.clone());
if let MirInstruction::Phi { dst, inputs } = inst { if let MirInstruction::Phi { dst, inputs, .. } = inst {
let dst_id = *dst; let dst_id = *dst;
if trace_phi { if trace_phi {
let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect(); let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect();

View File

@ -324,7 +324,7 @@ impl BasicBlock {
incoming: (BasicBlockId, ValueId), incoming: (BasicBlockId, ValueId),
) -> Result<(), String> { ) -> Result<(), String> {
for inst in &mut self.instructions { for inst in &mut self.instructions {
if let MirInstruction::Phi { dst, inputs } = inst { if let MirInstruction::Phi { dst, inputs, .. } = inst {
if *dst == phi_dst { if *dst == phi_dst {
inputs.push(incoming); inputs.push(incoming);
return Ok(()); return Ok(());
@ -580,6 +580,7 @@ mod tests {
let phi_inst = MirInstruction::Phi { let phi_inst = MirInstruction::Phi {
dst: ValueId::new(0), dst: ValueId::new(0),
inputs: vec![(BasicBlockId::new(1), ValueId::new(1))], inputs: vec![(BasicBlockId::new(1), ValueId::new(1))],
type_hint: None, // Phase 63-6: Test code, no type hint
}; };
bb.add_instruction(phi_inst); bb.add_instruction(phi_inst);

View File

@ -604,7 +604,7 @@ impl MirBuilder {
.map(|f| f.signature.name.clone()); .map(|f| f.signature.name.clone());
let _dbg_region_id = self.debug_current_region_id(); let _dbg_region_id = self.debug_current_region_id();
// P0: PHI の軽量補強と観測は、関数ブロック取得前に実施して借用競合を避ける // P0: PHI の軽量補強と観測は、関数ブロック取得前に実施して借用競合を避ける
if let MirInstruction::Phi { dst, inputs } = &instruction { if let MirInstruction::Phi { dst, inputs, .. } = &instruction {
origin::phi::propagate_phi_meta(self, *dst, inputs); origin::phi::propagate_phi_meta(self, *dst, inputs);
observe::ssa::emit_phi(self, *dst, inputs); observe::ssa::emit_phi(self, *dst, inputs);
} }
@ -815,7 +815,7 @@ impl MirBuilder {
if let Some(block_data) = function.get_block_mut(block) { if let Some(block_data) = function.get_block_mut(block) {
// Find PHI instruction with matching dst // Find PHI instruction with matching dst
for inst in &mut block_data.instructions { for inst in &mut block_data.instructions {
if let MirInstruction::Phi { dst, inputs } = inst { if let MirInstruction::Phi { dst, inputs, .. } = inst {
if *dst == phi_id { if *dst == phi_id {
*inputs = new_inputs; *inputs = new_inputs;
return Ok(()); return Ok(());

View File

@ -542,7 +542,7 @@ impl super::MirBuilder {
values.insert(*v); values.insert(*v);
} }
} }
MirInstruction::Phi { dst, inputs } => { MirInstruction::Phi { dst, inputs, type_hint: None } => {
values.insert(*dst); values.insert(*dst);
for (_, val) in inputs { for (_, val) in inputs {
values.insert(*val); values.insert(*val);
@ -651,12 +651,13 @@ impl super::MirBuilder {
MirInstruction::Return { value } => MirInstruction::Return { MirInstruction::Return { value } => MirInstruction::Return {
value: value.map(remap_value), value: value.map(remap_value),
}, },
MirInstruction::Phi { dst, inputs } => MirInstruction::Phi { MirInstruction::Phi { dst, inputs, type_hint: None } => MirInstruction::Phi {
dst: remap_value(*dst), dst: remap_value(*dst),
inputs: inputs inputs: inputs
.iter() .iter()
.map(|(bb, val)| (remap_block(*bb), remap_value(*val))) .map(|(bb, val)| (remap_block(*bb), remap_value(*val)))
.collect(), .collect(),
type_hint: None, // Phase 63-6: Preserve no type hint during remapping
}, },
MirInstruction::Copy { dst, src } => MirInstruction::Copy { MirInstruction::Copy { dst, src } => MirInstruction::Copy {
dst: remap_value(*dst), dst: remap_value(*dst),

View File

@ -130,6 +130,7 @@ impl super::MirBuilder {
self.emit_instruction(super::MirInstruction::Phi { self.emit_instruction(super::MirInstruction::Phi {
dst: result_val, dst: result_val,
inputs: phi_inputs, inputs: phi_inputs,
type_hint: None, // Phase 63-6: Legacy path, no type hint
})?; })?;
} }
Ok(result_val) Ok(result_val)

View File

@ -32,7 +32,7 @@ impl<'a> PhiBuilderOps for ToplevelOps<'a> {
self.0.current_span, self.0.current_span,
); );
} else { } else {
self.0.emit_instruction(MirInstruction::Phi { dst, inputs })?; self.0.emit_instruction(MirInstruction::Phi { dst, inputs, type_hint: None })?;
} }
Ok(()) Ok(())
} }

View File

@ -91,6 +91,7 @@ impl MirBuilder {
self.emit_instruction(MirInstruction::Phi { self.emit_instruction(MirInstruction::Phi {
dst: merged, dst: merged,
inputs, inputs,
type_hint: None, // Phase 63-6: Legacy path, no type hint
})?; })?;
} }
self.variable_map.insert(pin_name.clone(), merged); self.variable_map.insert(pin_name.clone(), merged);

View File

@ -129,9 +129,17 @@ pub enum MirInstruction {
// === SSA Phi Function === // === SSA Phi Function ===
/// SSA phi function for merging values from different paths /// SSA phi function for merging values from different paths
/// `%dst = phi [%val1 from %bb1, %val2 from %bb2, ...]` /// `%dst = phi [%val1 from %bb1, %val2 from %bb2, ...]`
///
/// # Phase 63-6: Type Hint Support
///
/// `type_hint` field stores type information from JoinIR (Select/IfMerge)
/// to enable type inference without scanning PHI inputs.
/// - `Some(MirType)`: Type is known from JoinIR (P1 cases: IfSelectTest.simple/local)
/// - `None`: Type must be inferred from PHI inputs (legacy behavior)
Phi { Phi {
dst: ValueId, dst: ValueId,
inputs: Vec<(super::BasicBlockId, ValueId)>, inputs: Vec<(super::BasicBlockId, ValueId)>,
type_hint: Option<super::MirType>, // Phase 63-6: JoinIR type hint
}, },
// === Box Operations === // === Box Operations ===

View File

@ -541,7 +541,7 @@ inst_meta! {
inst_meta! { inst_meta! {
pub struct PhiInst { dst: ValueId, inputs: Vec<(BasicBlockId, ValueId)> } pub struct PhiInst { dst: ValueId, inputs: Vec<(BasicBlockId, ValueId)> }
=> { => {
from_mir = |i| match i { MirInstruction::Phi { dst, inputs } => Some(PhiInst { dst: *dst, inputs: inputs.clone() }), _ => None }; from_mir = |i| match i { MirInstruction::Phi { dst, inputs, .. } => Some(PhiInst { dst: *dst, inputs: inputs.clone() }), _ => None };
effects = |_: &Self| EffectMask::PURE; effects = |_: &Self| EffectMask::PURE;
dst = |s: &Self| Some(s.dst); dst = |s: &Self| Some(s.dst);
used = |s: &Self| s.inputs.iter().map(|(_, v)| *v).collect(); used = |s: &Self| s.inputs.iter().map(|(_, v)| *v).collect();

View File

@ -94,7 +94,7 @@ pub(crate) fn intake_loop_form(
let mut carrier_hint: Vec<String> = Vec::new(); let mut carrier_hint: Vec<String> = Vec::new();
for inst in query.insts_in_block(loop_form.header) { for inst in query.insts_in_block(loop_form.header) {
if let MirInstruction::Phi { dst, inputs } = inst { if let MirInstruction::Phi { dst, inputs, .. } = inst {
let name = inputs let name = inputs
.iter() .iter()
.find_map(|(_, v)| value_to_name.get(v).cloned()) .find_map(|(_, v)| value_to_name.get(v).cloned())

View File

@ -491,17 +491,18 @@ Call {{\n\
cond, cond,
then_val, then_val,
else_val, else_val,
type_hint: _, // Phase 63-3: MIR変換では現時点で未使用 type_hint, // Phase 63-6: Type hint propagated to MIR Phi
} => { } => {
// Phase 33-2: Select を MIR の if/phi に変換 // Phase 33-2: Select を MIR の if/phi に変換
// 最小実装: cond/then/else/merge の 4 ブロック構造 // Phase 63-6: PHI instruction with type hint from JoinIR
debug_log!( debug_log!(
"[joinir_vm_bridge] Converting Select: dst={:?}, cond={:?}, then={:?}, else={:?}", "[joinir_vm_bridge] Converting Select: dst={:?}, cond={:?}, then={:?}, else={:?}, type_hint={:?}",
dst, dst,
cond, cond,
then_val, then_val,
else_val else_val,
type_hint
); );
// 1. cond ブロック(現在のブロック) // 1. cond ブロック(現在のブロック)
@ -532,32 +533,29 @@ Call {{\n\
branch_terminator, branch_terminator,
); );
// 6. then ブロック: dst = then_val; jump merge // 6. then ブロック: jump merge (no Copy, PHI will handle it)
let mut then_block_obj = crate::mir::BasicBlock::new(then_block); let mut then_block_obj = crate::mir::BasicBlock::new(then_block);
then_block_obj.instructions.push(MirInstruction::Copy {
dst: *dst,
src: *then_val,
});
then_block_obj.instruction_spans.push(Span::unknown());
then_block_obj.terminator = Some(MirInstruction::Jump { then_block_obj.terminator = Some(MirInstruction::Jump {
target: merge_block, target: merge_block,
}); });
mir_func.blocks.insert(then_block, then_block_obj); mir_func.blocks.insert(then_block, then_block_obj);
// 7. else ブロック: dst = else_val; jump merge // 7. else ブロック: jump merge (no Copy, PHI will handle it)
let mut else_block_obj = crate::mir::BasicBlock::new(else_block); let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
else_block_obj.instructions.push(MirInstruction::Copy {
dst: *dst,
src: *else_val,
});
else_block_obj.instruction_spans.push(Span::unknown());
else_block_obj.terminator = Some(MirInstruction::Jump { else_block_obj.terminator = Some(MirInstruction::Jump {
target: merge_block, target: merge_block,
}); });
mir_func.blocks.insert(else_block, else_block_obj); mir_func.blocks.insert(else_block, else_block_obj);
// 8. merge ブロック作成(空) // 8. merge ブロック: PHI instruction with type hint
let merge_block_obj = crate::mir::BasicBlock::new(merge_block); let mut merge_block_obj = crate::mir::BasicBlock::new(merge_block);
// Phase 63-6: Create PHI with type hint from JoinIR Select
merge_block_obj.instructions.push(MirInstruction::Phi {
dst: *dst,
inputs: vec![(then_block, *then_val), (else_block, *else_val)],
type_hint: type_hint.clone(), // Phase 63-6: Propagate type hint from JoinIR
});
merge_block_obj.instruction_spans.push(Span::unknown());
mir_func.blocks.insert(merge_block, merge_block_obj); mir_func.blocks.insert(merge_block, merge_block_obj);
// 9. merge ブロックに移動 // 9. merge ブロックに移動

View File

@ -395,6 +395,7 @@ impl<'a> LoopBuilder<'a> {
merge_block.add_instruction(MirInstruction::Phi { merge_block.add_instruction(MirInstruction::Phi {
dst: phi_id, dst: phi_id,
inputs: final_inputs, inputs: final_inputs,
type_hint: None, // Phase 63-6
}); });
} }
} }

View File

@ -36,6 +36,7 @@ impl<'a> LoopBuilder<'a> {
MirInstruction::Phi { MirInstruction::Phi {
dst: d, dst: d,
inputs: ins, inputs: ins,
.. // Phase 63-6: Ignore type_hint in pattern matching
} if *d == dst => { } if *d == dst => {
*ins = inputs.clone(); *ins = inputs.clone();
if block.instruction_spans.len() <= idx { if block.instruction_spans.len() <= idx {
@ -58,6 +59,7 @@ impl<'a> LoopBuilder<'a> {
let phi_inst = MirInstruction::Phi { let phi_inst = MirInstruction::Phi {
dst, dst,
inputs: inputs.clone(), inputs: inputs.clone(),
type_hint: None, // Phase 63-6: Legacy path, no type hint
}; };
block.instructions.insert(0, phi_inst); block.instructions.insert(0, phi_inst);
block.instruction_spans.insert(0, span); block.instruction_spans.insert(0, span);

View File

@ -136,7 +136,8 @@ pub fn infer_type_from_phi(
) -> Option<MirType> { ) -> Option<MirType> {
for (_bid, bb) in function.blocks.iter() { for (_bid, bb) in function.blocks.iter() {
for inst in bb.instructions.iter() { for inst in bb.instructions.iter() {
if let MirInstruction::Phi { dst, inputs } = inst { if let MirInstruction::Phi { dst, inputs, .. } = inst {
// Phase 63-6: type_hint is ignored here, will be used in lifecycle.rs
if *dst == ret_val { if *dst == ret_val {
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v)); let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
if let Some(first) = it.next() { if let Some(first) = it.next() {

View File

@ -248,7 +248,7 @@ pub fn format_instruction(
} }
} }
MirInstruction::Phi { dst, inputs } => { MirInstruction::Phi { dst, inputs, .. } => {
let inputs_str = inputs let inputs_str = inputs
.iter() .iter()
.map(|(bb, val)| format!("[{}, {}]", val, bb)) .map(|(bb, val)| format!("[{}, {}]", val, bb))

View File

@ -72,7 +72,7 @@ pub fn insert_phi_at_head_spanned(
inputs.sort_by_key(|(bb, _)| bb.0); inputs.sort_by_key(|(bb, _)| bb.0);
if let Some(bb) = f.get_block_mut(bb_id) { if let Some(bb) = f.get_block_mut(bb_id) {
bb.insert_spanned_after_phis(SpannedInstruction { bb.insert_spanned_after_phis(SpannedInstruction {
inst: MirInstruction::Phi { dst, inputs }, inst: MirInstruction::Phi { dst, inputs, type_hint: None },
span, span,
}); });
} }

View File

@ -84,6 +84,7 @@ impl MirBuilder {
self.emit_instruction(MirInstruction::Phi { self.emit_instruction(MirInstruction::Phi {
dst: phi_val, dst: phi_val,
inputs, inputs,
type_hint: None, // Phase 63-6: Legacy path, no type hint
})?; })?;
} }
@ -146,7 +147,11 @@ impl MirBuilder {
); );
} else { } else {
// フォールバック: 直接emit主にテストや特殊ケース用 // フォールバック: 直接emit主にテストや特殊ケース用
self.emit_instruction(MirInstruction::Phi { dst, inputs })?; self.emit_instruction(MirInstruction::Phi {
dst,
inputs,
type_hint: None, // Phase 63-6: Legacy path, no type hint
})?;
} }
Ok(()) Ok(())

View File

@ -163,6 +163,7 @@ impl LoopFormOps for LoopFormJsonOps<'_> {
if let MirInstruction::Phi { if let MirInstruction::Phi {
dst, dst,
inputs: phi_inputs, inputs: phi_inputs,
.. // Phase 63-6: Ignore type_hint
} = inst } = inst
{ {
if *dst == phi_id { if *dst == phi_id {

View File

@ -273,6 +273,7 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
block_ref.add_instruction(MirInstruction::Phi { block_ref.add_instruction(MirInstruction::Phi {
dst: ValueId::new(dst), dst: ValueId::new(dst),
inputs: pairs, inputs: pairs,
type_hint: None, // Phase 63-6
}); });
max_value_id = max_value_id.max(dst + 1); max_value_id = max_value_id.max(dst + 1);
} }

View File

@ -227,7 +227,7 @@ pub fn emit_mir_json_for_harness(
} }
continue; continue;
} }
if let I::Phi { dst, inputs } = inst { if let I::Phi { dst, inputs, .. } = inst {
let incoming: Vec<_> = inputs let incoming: Vec<_> = inputs
.iter() .iter()
.map(|(b, v)| json!([v.as_u32(), b.as_u32()])) .map(|(b, v)| json!([v.as_u32(), b.as_u32()]))
@ -642,7 +642,7 @@ pub fn emit_mir_json_for_harness_bin(
let mut emitted_defs: std::collections::HashSet<u32> = let mut emitted_defs: std::collections::HashSet<u32> =
std::collections::HashSet::new(); std::collections::HashSet::new();
for inst in &bb.instructions { for inst in &bb.instructions {
if let I::Phi { dst, inputs } = inst { if let I::Phi { dst, inputs, .. } = inst {
let incoming: Vec<_> = inputs let incoming: Vec<_> = inputs
.iter() .iter()
.map(|(b, v)| json!([v.as_u32(), b.as_u32()])) .map(|(b, v)| json!([v.as_u32(), b.as_u32()]))

View File

@ -179,6 +179,7 @@ pub fn parse_mir_v0_to_module(json: &str) -> Result<MirModule, String> {
block_ref.add_instruction(MirInstruction::Phi { block_ref.add_instruction(MirInstruction::Phi {
dst: ValueId::new(dst), dst: ValueId::new(dst),
inputs: pairs, inputs: pairs,
type_hint: None, // Phase 63-6
}); });
max_value_id = max_value_id.max(dst + 1); max_value_id = max_value_id.max(dst + 1);
} }