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:
2025-12-22 15:34:03 +09:00
parent 264940ef51
commit f07c2e7874
7 changed files with 502 additions and 344 deletions

View File

@ -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)

View File

@ -102,8 +102,9 @@ impl JoinIrFunctionConverter {
let mut block_converter = JoinIrBlockConverter::new();
block_converter.convert_function_body(&mut mir_func, &join_func.body)?;
// Phase 275 P0: Type propagation for JoinIR → MIR conversion
Self::propagate_types(&mut mir_func);
// Phase 279 P0: Type propagation for JoinIR → MIR conversion (SSOT)
Self::propagate_types(&mut mir_func)
.map_err(|e| JoinIrVmBridgeError::new(format!("Type propagation failed: {}", e)))?;
// Debug: print all blocks
debug_log!(
@ -153,8 +154,9 @@ impl JoinIrFunctionConverter {
let mut block_converter = JoinIrBlockConverter::new_with_func_names(func_name_map);
block_converter.convert_function_body(&mut mir_func, &join_func.body)?;
// Phase 275 P0: Type propagation for JoinIR → MIR conversion
Self::propagate_types(&mut mir_func);
// Phase 279 P0: Type propagation for JoinIR → MIR conversion (SSOT)
Self::propagate_types(&mut mir_func)
.map_err(|e| JoinIrVmBridgeError::new(format!("Type propagation failed: {}", e)))?;
debug_log!(
"[joinir_vm_bridge] Function '{}' has {} blocks:",
@ -173,149 +175,27 @@ impl JoinIrFunctionConverter {
Ok(mir_func)
}
/// Phase 275 P0: Type propagation for JoinIR-converted MIR
/// Phase 279 P0: Type propagation for JoinIR-converted MIR
///
/// Runs Copy type propagation, BinOp type re-propagation, and PHI type inference after conversion.
fn propagate_types(mir_func: &mut MirFunction) {
if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() {
eprintln!("[phi_resolver] propagate_types START for function: {}", mir_func.signature.name);
}
/// SSOT 型伝播パイプラインを呼び出す。
/// lifecycle.rs と同じ入口を使用することで、順序ドリフトを防止。
fn propagate_types(mir_func: &mut MirFunction) -> Result<(), String> {
use crate::mir::type_propagation::TypePropagationPipeline;
// Extract value_types to avoid borrow conflicts
let mut value_types = std::mem::take(&mut mir_func.metadata.value_types);
// Step 1: Copy type propagation
CopyTypePropagator::propagate(mir_func, &mut value_types);
// Step 2: BinOp type re-propagation (after Copy types are resolved)
// This fixes Int+Float → Float promotion
if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() {
eprintln!("[phi_resolver] Before repropagate_binop_types:");
for vid in [9, 10, 11, 12].iter() {
let v = crate::mir::ValueId(*vid);
if let Some(ty) = value_types.get(&v) {
eprintln!("[phi_resolver] {:?} = {:?}", v, ty);
}
}
}
Self::repropagate_binop_types(mir_func, &mut value_types);
if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() {
eprintln!("[phi_resolver] After repropagate_binop_types:");
for vid in [9, 10, 11, 12].iter() {
let v = crate::mir::ValueId(*vid);
if let Some(ty) = value_types.get(&v) {
eprintln!("[phi_resolver] {:?} = {:?}", v, ty);
}
}
}
// Step 3: Another round of Copy propagation (for chains like BinOp→Copy→PHI)
CopyTypePropagator::propagate(mir_func, &mut value_types);
// Step 4: PHI type inference from incoming values
// Collect PHI dsts first
let mut phi_dsts: Vec<crate::mir::ValueId> = Vec::new();
for (_bid, bb) in mir_func.blocks.iter() {
for inst in &bb.instructions {
if let MirInstruction::Phi { dst, .. } = inst {
phi_dsts.push(*dst);
}
}
}
// Infer PHI types using PhiTypeResolver
if !phi_dsts.is_empty() {
// Debug: Check value_types before PHI resolution
if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() {
eprintln!("[phi_resolver] value_types before PHI resolution:");
for vid in [9, 10, 11, 12].iter() {
let v = crate::mir::ValueId(*vid);
if let Some(ty) = value_types.get(&v) {
eprintln!("[phi_resolver] {:?} = {:?}", v, ty);
}
}
}
let phi_resolver = PhiTypeResolver::new(mir_func, &value_types);
let mut inferred_types: Vec<(crate::mir::ValueId, MirType)> = Vec::new();
for dst in phi_dsts {
if let Some(mt) = phi_resolver.resolve(dst) {
let existing = value_types.get(&dst);
if existing.is_none() || existing != Some(&mt) {
inferred_types.push((dst, mt));
}
}
}
// Apply inferred types
for (dst, mt) in inferred_types {
value_types.insert(dst, mt);
}
}
// Phase 279 P0: Use SSOT type propagation pipeline
// 順序固定: Copy → BinOp → Copy → PHI
TypePropagationPipeline::run(mir_func, &mut value_types)?;
// Put value_types back
mir_func.metadata.value_types = value_types;
Ok(())
}
/// Phase 275 P0: BinOp type re-propagation
///
/// Re-infers BinOp result types based on operand types.
/// Handles Int+Float → Float promotion.
fn repropagate_binop_types(
mir_func: &MirFunction,
value_types: &mut BTreeMap<crate::mir::ValueId, MirType>,
) {
use crate::mir::BinaryOp;
let mut updates: Vec<(crate::mir::ValueId, MirType)> = Vec::new();
for (_bid, bb) in mir_func.blocks.iter() {
for inst in bb.instructions.iter() {
if let MirInstruction::BinOp { dst, op, lhs, rhs } = inst {
// Get operand types
let lhs_type = value_types.get(lhs);
let rhs_type = value_types.get(rhs);
let new_type = if matches!(op, BinaryOp::Add) {
// Add can be numeric or string concat
match (lhs_type, rhs_type) {
(Some(MirType::Float), _) | (_, Some(MirType::Float)) => {
Some(MirType::Float)
}
(Some(MirType::Integer), Some(MirType::Integer)) => {
Some(MirType::Integer)
}
(Some(MirType::String), Some(MirType::String)) => {
Some(MirType::Box("StringBox".to_string()))
}
_ => None,
}
} else {
// Other arithmetic ops: check for Float
match (lhs_type, rhs_type) {
(Some(MirType::Float), _) | (_, Some(MirType::Float)) => {
Some(MirType::Float)
}
_ => None,
}
};
if let Some(new_ty) = new_type {
let current = value_types.get(dst);
if current.is_none() || current != Some(&new_ty) {
updates.push((*dst, new_ty));
}
}
}
}
}
// Apply updates
for (dst, ty) in updates {
value_types.insert(dst, ty);
}
}
// Phase 279 P0: repropagate_binop_types() static method removed
// Moved to TypePropagationPipeline (SSOT)
}
#[cfg(test)]

View File

@ -42,6 +42,7 @@ pub mod optimizer_stats; // extracted stats struct
pub mod passes;
pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only)
pub mod printer;
pub mod type_propagation; // Phase 279 P0: SSOT type propagation pipeline
mod printer_helpers; // internal helpers extracted from printer.rs
pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery)
pub mod region; // Phase 25.1l: Region/GC観測レイヤLoopForm v2 × RefKind

View File

@ -0,0 +1,20 @@
//! Phase 279 P0: Type propagation SSOT module
//!
//! # 責務
//!
//! 型伝播パイプラインの SSOT 入口を提供する。
//! lifecycle.rs と joinir_function_converter.rs の両方がこのモジュールを使用する。
//!
//! # 公開 API
//!
//! - `TypePropagationPipeline::run()` - SSOT 型伝播パイプライン入口
//!
//! # 設計原則
//!
//! - **入口一本化**: 全てのルートが TypePropagationPipeline::run() を呼ぶ
//! - **Private step**: PHI 推論を private にして、直接呼び出しを防止
//! - **順序固定**: Copy → BinOp → Copy → PHI順序ドリフト防止
mod pipeline;
pub use pipeline::TypePropagationPipeline;

View File

@ -0,0 +1,327 @@
//! Phase 279 P0: SSOT Type Propagation Pipeline
//!
//! # 責務
//!
//! 全ての型伝播処理を1つの入口SSOTに統一する。
//! lifecycle.rs と joinir_function_converter.rs の両方のルートがこのパイプラインを呼ぶ。
//!
//! # 設計原則(箱理論)
//!
//! - **単一責務**: 型伝播パイプライン全体の統括SSOT
//! - **固定順序**: Copy → BinOp → Copy → PHI順序ドリフト防止
//! - **Private step**: PHI 推論を private にして、直接呼び出しを防止(構造的保証)
//!
//! # アルゴリズム
//!
//! 1. Copy type propagation初回
//! 2. BinOp re-propagation数値型昇格: Int+Float→Float
//! 3. Copy type propagation昇格後の型を伝播
//! 4. PHI type inferencePHI ノードの型推論)
//!
//! # Fail-Fast ガード
//!
//! - PHI 推論は private step → lifecycle/joinir が直接 PhiTypeResolver を呼べない
//! - 入口一本化により、順序ドリフトが構造的に不可能
use crate::mir::phi_core::copy_type_propagator::CopyTypePropagator;
use crate::mir::phi_core::phi_type_resolver::PhiTypeResolver;
use crate::mir::{BinaryOp, MirFunction, MirInstruction, MirType, ValueId};
use std::collections::BTreeMap;
/// Phase 279 P0: SSOT 型伝播パイプライン
///
/// lifecycle.rs と joinir_function_converter.rs の両方から呼ばれる唯一の入口。
pub struct TypePropagationPipeline;
impl TypePropagationPipeline {
/// SSOT 入口: 完全な型伝播パイプラインを実行
///
/// # 引数
///
/// - `function`: MIR 関数
/// - `value_types`: 型マップ(更新される)
///
/// # 順序(固定)
///
/// 1. Copy propagation初回
/// 2. BinOp re-propagation数値型昇格
/// 3. Copy propagation昇格後の型を伝播
/// 4. PHI type inferenceprivate step
///
/// # Fail-Fast ガード
///
/// PHI 推論は private step なので、lifecycle/joinir が直接 PhiTypeResolver を呼ぶことは不可能。
/// 入口一本化により、順序ドリフトが構造的に防止される。
pub fn run(
function: &mut MirFunction,
value_types: &mut BTreeMap<ValueId, MirType>,
) -> Result<(), String> {
// Step 1: Copy propagation (initial)
Self::step1_copy_propagation(function, value_types)?;
// Step 2: BinOp re-propagation (numeric promotion)
Self::step2_binop_repropagation(function, value_types)?;
// Step 3: Copy propagation (propagate promoted types)
Self::step3_copy_propagation(function, value_types)?;
// Step 4: PHI type inference (private - cannot be called directly)
Self::step4_phi_type_inference(function, value_types)?;
Ok(())
}
// ========================================================================
// Private steps - 外部から直接呼び出し不可(構造的保証)
// ========================================================================
/// Step 1: Copy type propagation初回
///
/// Loop exit や If merge の edge copy で発生する型欠如を解消する。
fn step1_copy_propagation(
function: &MirFunction,
value_types: &mut BTreeMap<ValueId, MirType>,
) -> Result<(), String> {
CopyTypePropagator::propagate(function, value_types);
Ok(())
}
/// Step 2: BinOp re-propagation数値型昇格
///
/// Phase 275 P0: BinOp 結果型の再推論(オペランド型に基づく)
/// - String+String → StringBox
/// - Int+Int → Integer
/// - Int+Float → Float数値型昇格
///
/// # アルゴリズム
///
/// Pass 1: BinOp 命令の型更新を収集
/// - Add: String+String→StringBox, Int+Int→Integer, Int+Float→Float
/// - Other ops: Always Integer型が欠けている場合
///
/// Pass 2: Copy チェーン経由で型を伝播固定点ループ、最大10回
///
/// # 元の実装
///
/// lifecycle.rs の repropagate_binop_types() (lines 687-797) から抽出。
/// より包括的な実装を採用joinir_function_converter.rs 版は破棄)。
fn step2_binop_repropagation(
function: &MirFunction,
value_types: &mut BTreeMap<ValueId, MirType>,
) -> Result<(), String> {
// Pass 1: Process BinOp instructions first
let mut binop_updates: Vec<(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, BinaryOp::Add) {
// Get current lhs/rhs types after initial Copy propagation
let lhs_type = value_types.get(lhs);
let rhs_type = value_types.get(rhs);
// Classify types (Phase 275 P0: Added Float)
let lhs_class = classify_operand_type(lhs_type);
let rhs_class = classify_operand_type(rhs_type);
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 = 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 !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
);
}
value_types.insert(dst, ty);
}
// 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<(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) = value_types.get(src) {
let current_dst_type = 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
);
}
value_types.insert(dst, ty);
}
}
Ok(())
}
/// Step 3: Copy type propagation昇格後の型を伝播
///
/// BinOp で昇格された Float 型を Copy チェーン経由で伝播する。
/// 例: v11Float→ v12Copy
fn step3_copy_propagation(
function: &MirFunction,
value_types: &mut BTreeMap<ValueId, MirType>,
) -> Result<(), String> {
CopyTypePropagator::propagate(function, value_types);
Ok(())
}
/// Step 4: PHI type inferencePHI ノードの型推論)
///
/// **Private step**: lifecycle.rs / joinir_function_converter.rs が直接 PhiTypeResolver を呼ぶことは不可能。
/// 入口一本化により、PHI 推論が BinOp re-propagation の前に実行されることが構造的に防止される。
///
/// # アルゴリズム
///
/// 1. 全ブロックから PHI dst を収集
/// 2. PhiTypeResolver で各 PHI の型を推論
/// 3. 型が変更された、または欠けている場合のみ value_types を更新
///
/// # 元の実装
///
/// lifecycle.rs の PHI type inference (lines 333-399) から抽出。
fn step4_phi_type_inference(
function: &MirFunction,
value_types: &mut BTreeMap<ValueId, MirType>,
) -> Result<(), String> {
// 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 = 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()
);
}
// 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, 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 = 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 = value_types.get(&dst).cloned();
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
);
}
}
}
}
Ok(())
}
}
// ============================================================================
// Helper types and functions
// ============================================================================
/// 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,
}
/// Classify operand type for BinOp re-propagation
fn classify_operand_type(ty: Option<&MirType>) -> OperandTypeClass {
match ty {
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,
}
}