feat(mir): Phase 84-3 PhiTypeResolver for PHI+Copy graph type inference
- 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 <noreply@anthropic.com>
This commit is contained in:
@ -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) =
|
||||
|
||||
@ -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に移譲済み)
|
||||
|
||||
|
||||
333
src/mir/phi_core/phi_type_resolver.rs
Normal file
333
src/mir/phi_core/phi_type_resolver.rs
Normal file
@ -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<ValueId, MirType>,
|
||||
}
|
||||
|
||||
impl<'f> PhiTypeResolver<'f> {
|
||||
/// 新しい PhiTypeResolver を作成
|
||||
pub fn new(func: &'f MirFunction, value_types: &'f BTreeMap<ValueId, MirType>) -> Self {
|
||||
Self { func, value_types }
|
||||
}
|
||||
|
||||
/// root (ret_val など) の型を、PHI + Copy グラフから安全に推論できれば返す
|
||||
///
|
||||
/// # 戻り値
|
||||
///
|
||||
/// - `Some(MirType)`: 1 種類の型に収束した場合
|
||||
/// - `None`: 空 / 2 種類以上 / Unknown/Void のみ
|
||||
pub fn resolve(&self, root: ValueId) -> Option<MirType> {
|
||||
let mut visited: BTreeSet<ValueId> = BTreeSet::new();
|
||||
let mut stack: Vec<ValueId> = vec![root];
|
||||
let mut base_types: Vec<MirType> = 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<DefKind> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user