feat(mir): Phase 279 P0 - Type propagation pipeline SSOT unification
Eliminate "2本のコンパイラ" problem by unifying type propagation into single SSOT entry. SSOT implementation: - src/mir/type_propagation/pipeline.rs - SSOT type propagation pipeline - TypePropagationPipeline::run() - Single entry point for all routes Pipeline steps (fixed order): 1. Copy propagation (initial) 2. BinOp re-propagation (numeric promotion: Int+Float→Float) 3. Copy propagation (propagate promoted types) 4. PHI type inference (private step - cannot bypass) Callers (both routes now use SSOT): - lifecycle.rs::finalize_module() - Builder lifecycle route - joinir_function_converter.rs::propagate_types() - JoinIR bridge route Fail-fast guard (structural guarantee): - PHI type inference is private step inside TypePropagationPipeline - lifecycle.rs and joinir_function_converter.rs cannot call PhiTypeResolver directly - Only public API: TypePropagationPipeline::run() - Order drift is structurally impossible (private encapsulation) Code reduction: - ~500 lines of duplicate BinOp re-propagation logic removed - 2 implementations consolidated into 1 SSOT Files changed: - New: src/mir/type_propagation/mod.rs - New: src/mir/type_propagation/pipeline.rs (~300 lines) - Modified: src/mir/mod.rs - Modified: src/mir/builder/lifecycle.rs (~400 lines removed) - Modified: src/mir/join_ir_vm_bridge/joinir_function_converter.rs (~100 lines removed) Regression testing: ✅ Lifecycle route (VM backend): Phase 275 fixture ✅ JoinIR route (VM backend): loop_min_while.hako ✅ LLVM harness: Phase 275 fixture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -311,92 +311,13 @@ impl super::MirBuilder {
|
||||
let mut module = self.current_module.take().unwrap();
|
||||
// Phase 136 Step 3/7: Take from scope_ctx (SSOT)
|
||||
let mut function = self.scope_ctx.current_function.take().unwrap();
|
||||
// Phase 84-2: Copy命令型伝播(return型推論の前に実行)
|
||||
// Phase 279 P0: SSOT type propagation pipeline
|
||||
//
|
||||
// Loop exit や If merge の edge copy で発生する型欠如を解消する。
|
||||
// Copy チェーン: v1 → v2 → v3 で v1 の型が既知なら v2, v3 にも伝播。
|
||||
CopyTypePropagator::propagate(&function, &mut self.type_ctx.value_types);
|
||||
|
||||
// Phase 131-9: Global PHI type inference
|
||||
//
|
||||
// Infer types for ALL PHI nodes, not just return values.
|
||||
// This fixes loop carrier PHIs that don't get inferred otherwise.
|
||||
// Uses PhiTypeResolver to infer from incoming values (not usage).
|
||||
//
|
||||
// Bug: loop_min_while.hako PHI %3 was typed as String instead of Integer
|
||||
// Cause: PHI incoming values unavailable at emission time
|
||||
// Fix: Run PhiTypeResolver after CopyTypePropagator, before return type inference
|
||||
//
|
||||
// Collect ALL PHI dsts for re-inference (not just untyped)
|
||||
// This is necessary because propagate_phi_meta may have assigned incorrect types
|
||||
// due to circular dependencies (e.g., loop carrier PHIs)
|
||||
let mut all_phi_dsts: Vec<ValueId> = Vec::new();
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in &bb.instructions {
|
||||
if let MirInstruction::Phi { dst, .. } = inst {
|
||||
if std::env::var("NYASH_PHI_GLOBAL_DEBUG").is_ok() {
|
||||
let existing_type = self.type_ctx.value_types.get(dst);
|
||||
eprintln!(
|
||||
"[lifecycle/phi-scan] {} PHI {:?} existing type: {:?}",
|
||||
function.signature.name, dst, existing_type
|
||||
);
|
||||
}
|
||||
all_phi_dsts.push(*dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if std::env::var("NYASH_PHI_GLOBAL_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[lifecycle/phi-scan] {} found {} total PHIs to re-infer",
|
||||
function.signature.name,
|
||||
all_phi_dsts.len()
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 275 P0: BinOp type re-propagation BEFORE PHI resolution
|
||||
// Critical: BinOp results must be promoted to Float BEFORE PHI nodes infer their types
|
||||
// Example: v11 = v9(Float) + v10(Integer) → v11 must be Float before v36 PHI uses it
|
||||
self.repropagate_binop_types(&mut function);
|
||||
|
||||
// Phase 275 P0: Another round of Copy propagation after BinOp re-propagation
|
||||
// This propagates promoted Float types through Copy chains (e.g., v11 → v12)
|
||||
CopyTypePropagator::propagate(&function, &mut self.type_ctx.value_types);
|
||||
|
||||
// Re-infer types for ALL PHI nodes using PhiTypeResolver
|
||||
// This fixes incorrect types assigned by propagate_phi_meta during circular dependencies
|
||||
if !all_phi_dsts.is_empty() {
|
||||
let phi_resolver = PhiTypeResolver::new(&function, &self.type_ctx.value_types);
|
||||
let mut inferred_types: Vec<(ValueId, MirType)> = Vec::new();
|
||||
for dst in all_phi_dsts {
|
||||
if let Some(mt) = phi_resolver.resolve(dst) {
|
||||
// Check if type changed
|
||||
let existing_type = self.type_ctx.value_types.get(&dst);
|
||||
if existing_type.is_none() || existing_type != Some(&mt) {
|
||||
inferred_types.push((dst, mt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now insert/update all inferred types
|
||||
for (dst, mt) in inferred_types {
|
||||
let old_type = self.type_ctx.value_types.get(&dst).cloned();
|
||||
self.type_ctx.value_types.insert(dst, mt.clone());
|
||||
if std::env::var("NYASH_PHI_GLOBAL_DEBUG").is_ok() {
|
||||
if let Some(old) = old_type {
|
||||
eprintln!(
|
||||
"[lifecycle/phi-global] {} PHI {:?} type corrected: {:?} -> {:?}",
|
||||
function.signature.name, dst, old, mt
|
||||
);
|
||||
} else {
|
||||
eprintln!(
|
||||
"[lifecycle/phi-global] {} PHI {:?} type inferred: {:?}",
|
||||
function.signature.name, dst, mt
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 全ての型伝播処理を1つの入口(SSOT)に統一。
|
||||
// 順序固定: Copy → BinOp → Copy → PHI
|
||||
// lifecycle.rs と joinir_function_converter.rs の両方がこのパイプラインを呼ぶ。
|
||||
use crate::mir::type_propagation::TypePropagationPipeline;
|
||||
TypePropagationPipeline::run(&mut function, &mut self.type_ctx.value_types)?;
|
||||
|
||||
// Phase 84-5 guard hardening: ensure call/await results are registered in `value_types`
|
||||
// before return type inference. This avoids "impossible" debug panics when the builder
|
||||
@ -684,125 +605,9 @@ impl super::MirBuilder {
|
||||
|
||||
// Phase 131-11-E: Re-propagate BinOp result types after PHI resolution
|
||||
// This fixes cases where BinOp instructions were created before PHI types were known
|
||||
fn repropagate_binop_types(&mut self, function: &mut super::MirFunction) {
|
||||
use crate::mir::MirInstruction;
|
||||
use crate::mir::MirType;
|
||||
|
||||
// Phase 275 P0: Two-pass approach
|
||||
// Pass 1: Process BinOp instructions first
|
||||
let mut binop_updates: Vec<(super::ValueId, MirType)> = Vec::new();
|
||||
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
if let MirInstruction::BinOp { dst, op, lhs, rhs } = inst {
|
||||
// Only handle Add operations (string concat vs numeric addition)
|
||||
if matches!(op, crate::mir::BinaryOp::Add) {
|
||||
// Get current lhs/rhs types after PHI resolution
|
||||
let lhs_type = self.type_ctx.value_types.get(lhs);
|
||||
let rhs_type = self.type_ctx.value_types.get(rhs);
|
||||
|
||||
// Classify types (Phase 275 P0: Added Float)
|
||||
let lhs_class = match lhs_type {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer,
|
||||
Some(MirType::Float) => OperandTypeClass::Float, // Phase 275 P0
|
||||
_ => OperandTypeClass::Unknown,
|
||||
};
|
||||
let rhs_class = match rhs_type {
|
||||
Some(MirType::String) => OperandTypeClass::String,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => OperandTypeClass::String,
|
||||
Some(MirType::Integer) => OperandTypeClass::Integer,
|
||||
Some(MirType::Bool) => OperandTypeClass::Integer,
|
||||
Some(MirType::Float) => OperandTypeClass::Float, // Phase 275 P0
|
||||
_ => OperandTypeClass::Unknown,
|
||||
};
|
||||
|
||||
use OperandTypeClass::*;
|
||||
let new_type = match (lhs_class, rhs_class) {
|
||||
(String, String) => Some(MirType::Box("StringBox".to_string())),
|
||||
(Integer, Integer) | (Integer, Unknown) | (Unknown, Integer) => {
|
||||
Some(MirType::Integer)
|
||||
}
|
||||
// Phase 275 P0 C2: Number promotion (Int+Float → Float)
|
||||
(Integer, Float) | (Float, Integer) => Some(MirType::Float),
|
||||
(Float, Float) => Some(MirType::Float),
|
||||
_ => None, // Keep Unknown for mixed/unclear cases
|
||||
};
|
||||
|
||||
if let Some(new_ty) = new_type {
|
||||
// Check if type is missing or different
|
||||
let current_type = self.type_ctx.value_types.get(dst);
|
||||
if current_type.is_none() || current_type != Some(&new_ty) {
|
||||
binop_updates.push((*dst, new_ty));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other arithmetic ops: always Integer
|
||||
if !self.type_ctx.value_types.contains_key(dst) {
|
||||
binop_updates.push((*dst, MirType::Integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply binop updates first
|
||||
for (dst, ty) in binop_updates {
|
||||
if std::env::var("NYASH_BINOP_REPROP_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[binop-reprop] {} updated {:?} -> {:?}",
|
||||
function.signature.name, dst, ty
|
||||
);
|
||||
}
|
||||
self.type_ctx.value_types.insert(dst, ty);
|
||||
}
|
||||
|
||||
// Phase 275 P0: Pass 2 - Propagate types through Copy chains
|
||||
// Iterate until no more updates (for chains like 5→6→20→23)
|
||||
for _iteration in 0..10 {
|
||||
// Max 10 iterations for safety
|
||||
let mut copy_updates: Vec<(super::ValueId, MirType)> = Vec::new();
|
||||
|
||||
for (_bid, bb) in function.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
if let MirInstruction::Copy { dst, src } = inst {
|
||||
if let Some(src_type) = self.type_ctx.value_types.get(src) {
|
||||
let current_dst_type = self.type_ctx.value_types.get(dst);
|
||||
// Propagate if dst has no type or has different type
|
||||
if current_dst_type.is_none() || current_dst_type != Some(src_type) {
|
||||
copy_updates.push((*dst, src_type.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if copy_updates.is_empty() {
|
||||
break; // No more updates, done
|
||||
}
|
||||
|
||||
// Apply copy updates
|
||||
for (dst, ty) in copy_updates {
|
||||
if std::env::var("NYASH_BINOP_REPROP_DEBUG").is_ok() {
|
||||
eprintln!(
|
||||
"[copy-reprop] {} updated {:?} -> {:?}",
|
||||
function.signature.name, dst, ty
|
||||
);
|
||||
}
|
||||
self.type_ctx.value_types.insert(dst, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Phase 279 P0: repropagate_binop_types() method removed
|
||||
// Moved to TypePropagationPipeline (SSOT)
|
||||
}
|
||||
|
||||
// Phase 131-11-E: OperandTypeClass for BinOp type inference
|
||||
// Phase 275 P0: Added Float for number promotion
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum OperandTypeClass {
|
||||
String,
|
||||
Integer,
|
||||
Float, // Phase 275 P0
|
||||
Unknown,
|
||||
}
|
||||
// Phase 279 P0: OperandTypeClass enum removed
|
||||
// Moved to TypePropagationPipeline (SSOT)
|
||||
|
||||
Reference in New Issue
Block a user