From c89f08fc52056a8d32999d4966c04b60885169b4 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Tue, 2 Dec 2025 19:54:38 +0900 Subject: [PATCH] feat(mir): Phase 84-3 PhiTypeResolver for PHI+Copy graph type inference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PhiTypeResolver box (ChatGPT Pro design) with DFS graph traversal - Resolve types through PHI + Copy chains with safety conditions - Case D reduced from 9 to 4 (56% reduction) Implementation: - src/mir/phi_core/phi_type_resolver.rs: New box with graph search - src/mir/phi_core/mod.rs: Add module export - src/mir/builder/lifecycle.rs: Integrate as P4 (before P3-C) Algorithm: - DFS traversal: root → Copy → src / Phi → inputs - Collect base types (Const/Call/BoxCall/etc definitions) - Safety: Return Some only when converges to 1 type Test results: - Baseline: 504 passed, 30 failed (was 494/33) - Case D: 4 remaining (from 9, 56% reduction) - Unit tests: 7/7 passed Box responsibilities (final): - GenericTypeResolver: P3-C (generic T/V inference) - CopyTypePropagator: Copy alias only - PhiTypeResolver: PHI + Copy graph traversal Remaining 4 Case D: Special patterns (await/try-catch) need dedicated handling. Phase 84 progress: - Phase 84-1: Const type annotations (20→15→12) - Phase 84-2: CopyTypePropagator (12→9, 25% reduction) - Phase 84-3: PhiTypeResolver (9→4, 56% reduction) - Total: 67% Case D reduction (20→4) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/mir/builder/lifecycle.rs | 19 ++ src/mir/phi_core/mod.rs | 3 + src/mir/phi_core/phi_type_resolver.rs | 333 ++++++++++++++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 src/mir/phi_core/phi_type_resolver.rs diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index 367fb549..1ac87b31 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -45,6 +45,8 @@ use crate::mir::join_ir::lowering::generic_type_resolver::GenericTypeResolver; use crate::mir::join_ir::lowering::method_return_hint::MethodReturnHintBox; // Phase 84-2: Copy命令型伝播箱(ChatGPT Pro設計) use crate::mir::phi_core::copy_type_propagator::CopyTypePropagator; +// Phase 84-3: PHI + Copy グラフ型推論箱(ChatGPT Pro設計) +use crate::mir::phi_core::phi_type_resolver::PhiTypeResolver; // Phase 82: dev ガード用ヘルパー - Case 分類ロジック統一化 // @@ -351,6 +353,23 @@ impl super::MirBuilder { break; } } + // Phase 84-3: P4 PHI + Copy グラフ型推論(P3-C より先に試行) + // + // PHI + Copy の小グラフを DFS 探索し、1 種類の型に収束する場合のみ返す。 + // これにより Loop edge copy / If merge 後の型推論が解決できる。 + if hint.is_none() { + let phi_resolver = PhiTypeResolver::new(&function, &self.value_types); + if let Some(mt) = phi_resolver.resolve(*v) { + if std::env::var("NYASH_P4_DEBUG").is_ok() { + eprintln!( + "[lifecycle/p4] {} type inferred via PhiTypeResolver: {:?}", + function.signature.name, mt + ); + } + inferred = Some(mt); + break; + } + } // Phase 67: P3-C 対象なら GenericTypeResolver を優先使用 if hint.is_none() && TypeHintPolicy::is_p3c_target(&function.signature.name) { if let Some(mt) = diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index 0b38654f..c3f9caeb 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -32,6 +32,9 @@ pub mod phi_builder_box; // Phase 84-2: Copy命令型伝播箱(ChatGPT Pro設計) pub mod copy_type_propagator; +// Phase 84-3: PHI + Copy グラフ型推論箱(ChatGPT Pro設計) +pub mod phi_type_resolver; + // Phase 35-5: if_body_local_merge 削除(PhiBuilderBoxに吸収済み) // Phase 35-5: phi_invariants 削除(JoinIR Verifierに移譲済み) diff --git a/src/mir/phi_core/phi_type_resolver.rs b/src/mir/phi_core/phi_type_resolver.rs new file mode 100644 index 00000000..ee6ad2d9 --- /dev/null +++ b/src/mir/phi_core/phi_type_resolver.rs @@ -0,0 +1,333 @@ +//! Phase 84-3: PhiTypeResolver — PHI + Copy グラフ型推論箱(ChatGPT Pro設計) +//! +//! # 責務 +//! +//! 「PHI + Copy の小さなグラフ」を辿って、安全に型を決められるときだけ +//! MirType を返す。GenericTypeResolver / CopyTypePropagator の責務を +//! 太らせず、PHI 系だけをこの箱に閉じ込める。 +//! +//! # 設計原則(箱理論) +//! +//! - **単一責務**: PHI + Copy グラフ追跡と安全条件判定のみ +//! - **探索限定**: Copy / Phi / base 定義 の 3 種類だけ +//! - **安全条件**: 1 種類の型に収束する場合のみ Some を返す +//! +//! # アルゴリズム +//! +//! 1. DFS/BFS で root から探索開始 +//! 2. Copy → src へ進む +//! 3. Phi → 各 incoming ValueId へ進む +//! 4. それ以外(Const/Call/BoxCall/NewBox/TypeOp/BinOp...)は base 定義 +//! 5. base_types 集合を収集し、1 種類なら返す +//! +//! # 安全装置 +//! +//! - visited で同じ ValueId を 2 回以上辿らない(ループ防止) +//! - 探索上限で打ち切り + +use crate::mir::{MirFunction, MirInstruction, MirType, ValueId}; +use std::collections::{BTreeMap, BTreeSet}; + +/// Phase 84-3: PHI + Copy グラフ型推論箱 +pub struct PhiTypeResolver<'f> { + func: &'f MirFunction, + value_types: &'f BTreeMap, +} + +impl<'f> PhiTypeResolver<'f> { + /// 新しい PhiTypeResolver を作成 + pub fn new(func: &'f MirFunction, value_types: &'f BTreeMap) -> Self { + Self { func, value_types } + } + + /// root (ret_val など) の型を、PHI + Copy グラフから安全に推論できれば返す + /// + /// # 戻り値 + /// + /// - `Some(MirType)`: 1 種類の型に収束した場合 + /// - `None`: 空 / 2 種類以上 / Unknown/Void のみ + pub fn resolve(&self, root: ValueId) -> Option { + let mut visited: BTreeSet = BTreeSet::new(); + let mut stack: Vec = vec![root]; + let mut base_types: Vec = Vec::new(); + + // 安全装置: 探索上限 + let max_visits = self.value_types.len() + 100; + + while let Some(v) = stack.pop() { + // 既に訪問済みならスキップ + if visited.contains(&v) { + continue; + } + visited.insert(v); + + // 安全装置: 探索上限チェック + if visited.len() > max_visits { + if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() { + eprintln!( + "[phi_resolver] warning: exceeded max visits {}, stopping", + max_visits + ); + } + break; + } + + // v の定義元命令を探す + match self.find_definition(v) { + Some(DefKind::Copy { src }) => { + // Copy → src へ進む + stack.push(src); + } + Some(DefKind::Phi { inputs }) => { + // Phi → 各 incoming ValueId へ進む + for (_, incoming) in inputs { + stack.push(incoming); + } + } + Some(DefKind::Base) | None => { + // base 定義または未知 → value_types から型を取得 + if let Some(ty) = self.value_types.get(&v) { + // Unknown / Void は除外 + if !matches!(ty, MirType::Unknown | MirType::Void) { + // 重複を避けて追加(eq で比較) + if !base_types.iter().any(|t| t == ty) { + base_types.push(ty.clone()); + } + } + } + } + } + } + + // 安全条件: 1 種類に収束したら返す + if base_types.len() == 1 { + let ty = base_types.into_iter().next().unwrap(); + if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() { + eprintln!( + "[phi_resolver] resolved {:?} -> {:?} (visited {})", + root, + ty, + visited.len() + ); + } + return Some(ty); + } + + if std::env::var("NYASH_PHI_RESOLVER_DEBUG").is_ok() { + eprintln!( + "[phi_resolver] failed for {:?}: base_types = {:?}", + root, base_types + ); + } + + None + } + + /// ValueId の定義元命令を探す + fn find_definition(&self, v: ValueId) -> Option { + for (_bid, bb) in self.func.blocks.iter() { + for inst in bb.instructions.iter() { + match inst { + MirInstruction::Copy { dst, src } if *dst == v => { + return Some(DefKind::Copy { src: *src }); + } + MirInstruction::Phi { dst, inputs, .. } if *dst == v => { + return Some(DefKind::Phi { + inputs: inputs.clone(), + }); + } + // 他の dst を持つ命令は base 定義 + _ => { + if Self::instruction_defines(inst, v) { + return Some(DefKind::Base); + } + } + } + } + } + None + } + + /// 命令が ValueId を定義しているか + fn instruction_defines(inst: &MirInstruction, v: ValueId) -> bool { + match inst { + MirInstruction::Const { dst, .. } => *dst == v, + MirInstruction::UnaryOp { dst, .. } => *dst == v, + MirInstruction::BinOp { dst, .. } => *dst == v, + MirInstruction::Compare { dst, .. } => *dst == v, + MirInstruction::TypeOp { dst, .. } => *dst == v, + MirInstruction::Load { dst, .. } => *dst == v, + MirInstruction::NewBox { dst, .. } => *dst == v, + MirInstruction::BoxCall { dst: Some(dst), .. } => *dst == v, + MirInstruction::Call { dst: Some(dst), .. } => *dst == v, + MirInstruction::ExternCall { dst: Some(dst), .. } => *dst == v, + _ => false, + } + } +} + +/// 定義元の種類 +enum DefKind { + /// Copy { dst, src } → src へ進む + Copy { src: ValueId }, + /// Phi { dst, inputs } → 各 incoming へ進む + Phi { + inputs: Vec<(crate::mir::BasicBlockId, ValueId)>, + }, + /// その他の base 定義(Const/Call/BoxCall/NewBox 等) + Base, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::{BasicBlock, BasicBlockId, EffectMask, FunctionSignature, MirFunction}; + + fn make_test_function() -> MirFunction { + let sig = FunctionSignature { + name: "test".to_string(), + params: vec![], + return_type: MirType::Void, + effects: EffectMask::PURE, + }; + MirFunction::new(sig, BasicBlockId::new(0)) + } + + #[test] + fn test_resolve_direct_type() { + let f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 has type Integer directly + value_types.insert(ValueId(1), MirType::Integer); + + let resolver = PhiTypeResolver::new(&f, &value_types); + assert_eq!(resolver.resolve(ValueId(1)), Some(MirType::Integer)); + } + + #[test] + fn test_resolve_through_copy() { + let mut f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 has type Integer, Copy v2 <- v1 + value_types.insert(ValueId(1), MirType::Integer); + + let mut bb = BasicBlock::new(BasicBlockId::new(0)); + bb.instructions.push(MirInstruction::Copy { + dst: ValueId(2), + src: ValueId(1), + }); + f.blocks.insert(BasicBlockId::new(0), bb); + + let resolver = PhiTypeResolver::new(&f, &value_types); + assert_eq!(resolver.resolve(ValueId(2)), Some(MirType::Integer)); + } + + #[test] + fn test_resolve_through_phi_uniform() { + let mut f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 and v2 both have type Integer + value_types.insert(ValueId(1), MirType::Integer); + value_types.insert(ValueId(2), MirType::Integer); + + // Phi v3 <- [(Block0, v1), (Block1, v2)] + let mut bb = BasicBlock::new(BasicBlockId::new(0)); + bb.instructions.push(MirInstruction::Phi { + dst: ValueId(3), + inputs: vec![ + (BasicBlockId::new(0), ValueId(1)), + (BasicBlockId::new(1), ValueId(2)), + ], + type_hint: None, + }); + f.blocks.insert(BasicBlockId::new(0), bb); + + let resolver = PhiTypeResolver::new(&f, &value_types); + assert_eq!(resolver.resolve(ValueId(3)), Some(MirType::Integer)); + } + + #[test] + fn test_resolve_through_phi_mixed_types() { + let mut f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 has Integer, v2 has String (different types) + value_types.insert(ValueId(1), MirType::Integer); + value_types.insert(ValueId(2), MirType::String); + + // Phi v3 <- [(Block0, v1), (Block1, v2)] + let mut bb = BasicBlock::new(BasicBlockId::new(0)); + bb.instructions.push(MirInstruction::Phi { + dst: ValueId(3), + inputs: vec![ + (BasicBlockId::new(0), ValueId(1)), + (BasicBlockId::new(1), ValueId(2)), + ], + type_hint: None, + }); + f.blocks.insert(BasicBlockId::new(0), bb); + + let resolver = PhiTypeResolver::new(&f, &value_types); + // Should return None because types don't match + assert_eq!(resolver.resolve(ValueId(3)), None); + } + + #[test] + fn test_resolve_through_phi_and_copy() { + let mut f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 has type Integer + value_types.insert(ValueId(1), MirType::Integer); + + let mut bb0 = BasicBlock::new(BasicBlockId::new(0)); + // Copy v2 <- v1 + bb0.instructions.push(MirInstruction::Copy { + dst: ValueId(2), + src: ValueId(1), + }); + f.blocks.insert(BasicBlockId::new(0), bb0); + + let mut bb1 = BasicBlock::new(BasicBlockId::new(1)); + // Phi v3 <- [(Block0, v2)] + bb1.instructions.push(MirInstruction::Phi { + dst: ValueId(3), + inputs: vec![(BasicBlockId::new(0), ValueId(2))], + type_hint: None, + }); + f.blocks.insert(BasicBlockId::new(1), bb1); + + let resolver = PhiTypeResolver::new(&f, &value_types); + // Should resolve v3 through Phi -> v2 -> Copy -> v1 -> Integer + assert_eq!(resolver.resolve(ValueId(3)), Some(MirType::Integer)); + } + + #[test] + fn test_resolve_unknown_returns_none() { + let f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 has type Unknown + value_types.insert(ValueId(1), MirType::Unknown); + + let resolver = PhiTypeResolver::new(&f, &value_types); + // Unknown should be filtered out + assert_eq!(resolver.resolve(ValueId(1)), None); + } + + #[test] + fn test_resolve_void_returns_none() { + let f = make_test_function(); + let mut value_types = BTreeMap::new(); + + // v1 has type Void + value_types.insert(ValueId(1), MirType::Void); + + let resolver = PhiTypeResolver::new(&f, &value_types); + // Void should be filtered out + assert_eq!(resolver.resolve(ValueId(1)), None); + } +}