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_inst_index = Some(idx);
self.last_inst = Some(inst.clone());
if let MirInstruction::Phi { dst, inputs } = inst {
if let MirInstruction::Phi { dst, inputs, .. } = inst {
let dst_id = *dst;
if trace_phi {
let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect();

View File

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

View File

@ -604,7 +604,7 @@ impl MirBuilder {
.map(|f| f.signature.name.clone());
let _dbg_region_id = self.debug_current_region_id();
// P0: PHI の軽量補強と観測は、関数ブロック取得前に実施して借用競合を避ける
if let MirInstruction::Phi { dst, inputs } = &instruction {
if let MirInstruction::Phi { dst, inputs, .. } = &instruction {
origin::phi::propagate_phi_meta(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) {
// Find PHI instruction with matching dst
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 {
*inputs = new_inputs;
return Ok(());

View File

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

View File

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

View File

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

View File

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

View File

@ -129,9 +129,17 @@ pub enum MirInstruction {
// === SSA Phi Function ===
/// SSA phi function for merging values from different paths
/// `%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 {
dst: ValueId,
inputs: Vec<(super::BasicBlockId, ValueId)>,
type_hint: Option<super::MirType>, // Phase 63-6: JoinIR type hint
},
// === Box Operations ===

View File

@ -541,7 +541,7 @@ inst_meta! {
inst_meta! {
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;
dst = |s: &Self| Some(s.dst);
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();
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
.iter()
.find_map(|(_, v)| value_to_name.get(v).cloned())

View File

@ -491,17 +491,18 @@ Call {{\n\
cond,
then_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 に変換
// 最小実装: cond/then/else/merge の 4 ブロック構造
// Phase 63-6: PHI instruction with type hint from JoinIR
debug_log!(
"[joinir_vm_bridge] Converting Select: dst={:?}, cond={:?}, then={:?}, else={:?}",
"[joinir_vm_bridge] Converting Select: dst={:?}, cond={:?}, then={:?}, else={:?}, type_hint={:?}",
dst,
cond,
then_val,
else_val
else_val,
type_hint
);
// 1. cond ブロック(現在のブロック)
@ -532,32 +533,29 @@ Call {{\n\
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);
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 {
target: merge_block,
});
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);
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 {
target: merge_block,
});
mir_func.blocks.insert(else_block, else_block_obj);
// 8. merge ブロック作成(空)
let merge_block_obj = crate::mir::BasicBlock::new(merge_block);
// 8. merge ブロック: PHI instruction with type hint
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);
// 9. merge ブロックに移動

View File

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

View File

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

View File

@ -136,7 +136,8 @@ pub fn infer_type_from_phi(
) -> Option<MirType> {
for (_bid, bb) in function.blocks.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 {
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
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
.iter()
.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);
if let Some(bb) = f.get_block_mut(bb_id) {
bb.insert_spanned_after_phis(SpannedInstruction {
inst: MirInstruction::Phi { dst, inputs },
inst: MirInstruction::Phi { dst, inputs, type_hint: None },
span,
});
}

View File

@ -84,6 +84,7 @@ impl MirBuilder {
self.emit_instruction(MirInstruction::Phi {
dst: phi_val,
inputs,
type_hint: None, // Phase 63-6: Legacy path, no type hint
})?;
}
@ -146,7 +147,11 @@ impl MirBuilder {
);
} else {
// フォールバック: 直接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(())

View File

@ -163,6 +163,7 @@ impl LoopFormOps for LoopFormJsonOps<'_> {
if let MirInstruction::Phi {
dst,
inputs: phi_inputs,
.. // Phase 63-6: Ignore type_hint
} = inst
{
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 {
dst: ValueId::new(dst),
inputs: pairs,
type_hint: None, // Phase 63-6
});
max_value_id = max_value_id.max(dst + 1);
}

View File

@ -227,7 +227,7 @@ pub fn emit_mir_json_for_harness(
}
continue;
}
if let I::Phi { dst, inputs } = inst {
if let I::Phi { dst, inputs, .. } = inst {
let incoming: Vec<_> = inputs
.iter()
.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> =
std::collections::HashSet::new();
for inst in &bb.instructions {
if let I::Phi { dst, inputs } = inst {
if let I::Phi { dst, inputs, .. } = inst {
let incoming: Vec<_> = inputs
.iter()
.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 {
dst: ValueId::new(dst),
inputs: pairs,
type_hint: None, // Phase 63-6
});
max_value_id = max_value_id.max(dst + 1);
}