Remove Trio boxes and tidy loop scope warnings

This commit is contained in:
nyash-codex
2025-11-30 11:46:14 +09:00
parent ea120dc9b1
commit 2ea0f2a202
18 changed files with 294 additions and 1765 deletions

View File

@ -1,361 +0,0 @@
/// LocalScopeInspectorBox - Variable definition tracker
///
/// # Phase 30: LoopScopeShape 移行中
///
/// このBoxは将来 LoopScopeShape に吸収される予定。
/// 定義位置情報は LoopScopeShape::from_existing_boxes() 内部で集約され、
/// 変数分類に使用される。
///
/// **新規コード**: LoopScopeShape が利用可能な場合は、
/// `classify()` メソッドを直接使うことを推奨。
///
/// # Purpose
///
/// This box tracks which variables are defined in which basic blocks.
/// Used by LoopVarClassBox to determine if a body-local variable is
/// defined in all exit predecessors (making it eligible for exit PHI).
///
/// # Responsibility
///
/// - Record variable definitions per block
/// - Query if a variable is defined in specific blocks
/// - Query if a variable is defined in ALL of a set of blocks
///
/// # Design Philosophy (Box Theory)
///
/// This box has ONE job: track definition locations.
/// It doesn't know about loops, PHI nodes, or exit blocks.
/// It's a pure data structure that other boxes can query.
use crate::mir::{BasicBlockId, ValueId};
use std::collections::{BTreeMap, BTreeSet};
/// Tracks which variables are defined in which blocks
#[derive(Debug, Clone, Default)]
pub struct LocalScopeInspectorBox {
/// Variable name → Set of blocks where it's defined
var_definitions: BTreeMap<String, BTreeSet<BasicBlockId>>,
}
impl LocalScopeInspectorBox {
/// Create a new empty inspector
pub fn new() -> Self {
Self {
var_definitions: BTreeMap::new(),
}
}
/// Record that a variable is defined in a specific block
///
/// # Example
/// ```
/// let mut inspector = LocalScopeInspectorBox::new();
/// inspector.record_definition("ch", BasicBlockId::new(5));
/// inspector.record_definition("i", BasicBlockId::new(2));
/// inspector.record_definition("i", BasicBlockId::new(5)); // i defined in multiple blocks
/// ```
pub fn record_definition(&mut self, var_name: &str, block: BasicBlockId) {
self.var_definitions
.entry(var_name.to_string())
.or_insert_with(BTreeSet::new)
.insert(block);
}
/// Record definitions from a snapshot (block_id, vars)
///
/// # Example
/// ```
/// let snapshot = BTreeMap::from([
/// ("i".to_string(), ValueId(1)),
/// ("n".to_string(), ValueId(2)),
/// ]);
/// inspector.record_snapshot(BasicBlockId::new(5), &snapshot);
/// ```
pub fn record_snapshot(&mut self, block: BasicBlockId, vars: &BTreeMap<String, ValueId>) {
for var_name in vars.keys() {
self.record_definition(var_name, block);
}
}
/// Check if a variable is defined in a specific block
///
/// # Returns
/// - `true` if the variable is defined in the block
/// - `false` if the variable is not defined in the block, or doesn't exist
pub fn is_defined_in(&self, var_name: &str, block: BasicBlockId) -> bool {
self.var_definitions
.get(var_name)
.map(|blocks| blocks.contains(&block))
.unwrap_or(false)
}
/// Check if a variable is AVAILABLE in ALL of the specified blocks
///
/// # Semantics (Important!)
///
/// This checks if the variable is "in scope" (available) at each block,
/// NOT whether it was "newly defined" in that block.
///
/// - A variable is "available" if it's in the snapshot at that point
/// - This includes both newly-defined variables AND inherited variables
///
/// For Option C PHI generation:
/// - "available in all exit preds" → can generate exit PHI
/// - "NOT available in all exit preds" → BodyLocalInternal, skip exit PHI
///
/// # Returns
/// - `true` if the variable is available in every block in `required_blocks`
/// - `false` if the variable is missing from any block, or doesn't exist
///
/// # Example
/// ```
/// // Variable "ch" is only available in block 5
/// inspector.record_definition("ch", BasicBlockId::new(5));
///
/// let exit_preds = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
/// assert!(!inspector.is_available_in_all("ch", &exit_preds)); // false: missing block 2
///
/// // Variable "i" is available in both blocks
/// inspector.record_definition("i", BasicBlockId::new(2));
/// inspector.record_definition("i", BasicBlockId::new(5));
/// assert!(inspector.is_available_in_all("i", &exit_preds)); // true: in all blocks
/// ```
pub fn is_available_in_all(&self, var_name: &str, required_blocks: &[BasicBlockId]) -> bool {
if let Some(defining_blocks) = self.var_definitions.get(var_name) {
required_blocks
.iter()
.all(|block| defining_blocks.contains(block))
} else {
// Variable doesn't exist at all
false
}
}
/// Get all blocks where a variable is defined
///
/// # Returns
/// - Vec of BasicBlockIds where the variable is defined
/// - Empty vec if the variable doesn't exist
pub fn get_defining_blocks(&self, var_name: &str) -> Vec<BasicBlockId> {
self.var_definitions
.get(var_name)
.map(|blocks| blocks.iter().copied().collect())
.unwrap_or_default()
}
/// Get all tracked variables
pub fn all_variables(&self) -> Vec<String> {
self.var_definitions.keys().cloned().collect()
}
/// Get the number of blocks where a variable is defined
pub fn definition_count(&self, var_name: &str) -> usize {
self.var_definitions
.get(var_name)
.map(|blocks| blocks.len())
.unwrap_or(0)
}
// ========================================================================
// Phase 30 F-1.3: LoopScopeShape 委譲メソッド
// ========================================================================
/// Phase 30: LoopScopeShape を使って変数が全 exit pred で利用可能か判定
///
/// 新規コードでは LoopScopeShape::classify() を直接使うことを推奨。
/// このメソッドは LoopScopeShape が既にある場合の便利メソッド。
///
/// # Note
///
/// LoopScopeShape::classify() は既に exit_live 情報を含んでいるため、
/// 直接 classify() → needs_exit_phi() を使う方が効率的。
#[allow(dead_code)] // Phase 30 F-1.3: will be used when LocalScopeInspectorBox is absorbed
pub(crate) fn is_available_in_all_with_scope(
&self,
var_name: &str,
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
) -> bool {
// LoopScopeShape の分類を使用
// Pinned/Carrier/BodyLocalExit → available in all (exit PHI needed)
// BodyLocalInternal → NOT available in all (no exit PHI)
scope.needs_exit_phi(var_name)
}
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_and_query_single_definition() {
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("ch", BasicBlockId::new(5));
assert!(inspector.is_defined_in("ch", BasicBlockId::new(5)));
assert!(!inspector.is_defined_in("ch", BasicBlockId::new(2)));
assert!(!inspector.is_defined_in("unknown", BasicBlockId::new(5)));
}
#[test]
fn test_record_multiple_blocks() {
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("i", BasicBlockId::new(2));
inspector.record_definition("i", BasicBlockId::new(5));
inspector.record_definition("i", BasicBlockId::new(7));
assert!(inspector.is_defined_in("i", BasicBlockId::new(2)));
assert!(inspector.is_defined_in("i", BasicBlockId::new(5)));
assert!(inspector.is_defined_in("i", BasicBlockId::new(7)));
assert!(!inspector.is_defined_in("i", BasicBlockId::new(10)));
}
#[test]
fn test_is_available_in_all_success() {
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("i", BasicBlockId::new(2));
inspector.record_definition("i", BasicBlockId::new(5));
inspector.record_definition("i", BasicBlockId::new(7));
let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
assert!(inspector.is_available_in_all("i", &required));
}
#[test]
fn test_is_available_in_all_failure() {
let mut inspector = LocalScopeInspectorBox::new();
// "ch" only defined in block 5
inspector.record_definition("ch", BasicBlockId::new(5));
let required = vec![BasicBlockId::new(2), BasicBlockId::new(5)];
// Should fail because block 2 is missing
assert!(!inspector.is_available_in_all("ch", &required));
}
#[test]
fn test_is_available_in_all_unknown_variable() {
let inspector = LocalScopeInspectorBox::new();
let required = vec![BasicBlockId::new(2)];
assert!(!inspector.is_available_in_all("unknown", &required));
}
#[test]
fn test_get_defining_blocks() {
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("x", BasicBlockId::new(3));
inspector.record_definition("x", BasicBlockId::new(7));
inspector.record_definition("x", BasicBlockId::new(1));
let mut blocks = inspector.get_defining_blocks("x");
blocks.sort_by_key(|b| b.0);
assert_eq!(
blocks,
vec![
BasicBlockId::new(1),
BasicBlockId::new(3),
BasicBlockId::new(7),
]
);
}
#[test]
fn test_get_defining_blocks_unknown() {
let inspector = LocalScopeInspectorBox::new();
assert_eq!(
inspector.get_defining_blocks("unknown"),
Vec::<BasicBlockId>::new()
);
}
#[test]
fn test_record_snapshot() {
let mut inspector = LocalScopeInspectorBox::new();
let mut snapshot = BTreeMap::new();
snapshot.insert("i".to_string(), ValueId(10));
snapshot.insert("n".to_string(), ValueId(20));
snapshot.insert("ch".to_string(), ValueId(712));
inspector.record_snapshot(BasicBlockId::new(5), &snapshot);
assert!(inspector.is_defined_in("i", BasicBlockId::new(5)));
assert!(inspector.is_defined_in("n", BasicBlockId::new(5)));
assert!(inspector.is_defined_in("ch", BasicBlockId::new(5)));
assert!(!inspector.is_defined_in("i", BasicBlockId::new(2)));
}
#[test]
fn test_all_variables() {
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("a", BasicBlockId::new(1));
inspector.record_definition("b", BasicBlockId::new(2));
inspector.record_definition("c", BasicBlockId::new(3));
let mut vars = inspector.all_variables();
vars.sort();
assert_eq!(vars, vec!["a", "b", "c"]);
}
#[test]
fn test_definition_count() {
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("x", BasicBlockId::new(1));
inspector.record_definition("x", BasicBlockId::new(2));
inspector.record_definition("x", BasicBlockId::new(3));
assert_eq!(inspector.definition_count("x"), 3);
assert_eq!(inspector.definition_count("unknown"), 0);
}
/// Test the skip_whitespace PHI bug scenario
#[test]
fn test_skip_whitespace_scenario() {
let mut inspector = LocalScopeInspectorBox::new();
// Simulate skip_whitespace:
// - Variables i, n, s are defined in all blocks (header + breaks)
// - Variable ch is only defined in block 5 (break path 2)
let block_2 = BasicBlockId::new(2); // header / break path 1
let block_5 = BasicBlockId::new(5); // break path 2
let block_7 = BasicBlockId::new(7); // latch (hypothetical)
// i, n, s are in all blocks
for var in &["i", "n", "s"] {
inspector.record_definition(var, block_2);
inspector.record_definition(var, block_5);
inspector.record_definition(var, block_7);
}
// ch is only in block 5
inspector.record_definition("ch", block_5);
let exit_preds = vec![block_2, block_5, block_7];
// i, n, s should be in all exit preds
assert!(inspector.is_available_in_all("i", &exit_preds));
assert!(inspector.is_available_in_all("n", &exit_preds));
assert!(inspector.is_available_in_all("s", &exit_preds));
// ch should NOT be in all exit preds (missing block 2 and 7)
assert!(!inspector.is_available_in_all("ch", &exit_preds));
// This means ch should NOT get an exit PHI!
}
}

View File

@ -1,414 +0,0 @@
//! Loop Exit Liveness Box - Exit後で使われる変数の決定専門Box
//!
//! Phase 26-F-4: Exit Liveness Analysis
//! - Exit後で本当に使われる変数を決定
//! - Phase 1: 空集合MIRスキャン実装待ち
//! - Phase 2+: MIRスキャン実装予定LoopFormOps拡張後
//!
//! # 環境変数制御
//! - `NYASH_EXIT_LIVE_ENABLE=1`: Phase 2+ MIRスキャン実装を有効化実験用
//! - デフォルト(未設定): Phase 26-F-3 相当の挙動(安定版)
//!
//! # Box-First理論: 責務の明確な分離
//!
//! - **LoopVarClassBox**: スコープ分類Pinned/Carrier/BodyLocal
//! - **LoopExitLivenessBox**: Exit後の実際の使用この箱
//! - **BodyLocalPhiBuilder**: 両者を統合して exit PHI 判定
//!
//! # Phase 26-F-4: ChatGPT設計による箱離婚
//!
//! **問題**: LoopVarClassBox が「スコープ分類」と「exit後使用」を混在
//!
//! **解決**: LoopExitLivenessBox 新設で完全分離
//! - スコープ分類: LoopVarClassBox変更なし
//! - 実際の使用: LoopExitLivenessBox新箱
//! - 統合判定: BodyLocalPhiBuilderOR論理
use crate::mir::{BasicBlockId, ValueId};
use std::collections::{BTreeMap, BTreeSet};
/// ExitLivenessProvider
///
/// ExitPhiBuilder が依存するインターフェース。環境や実装を差し替えやすくするために
/// trait として定義し、Legacy / MIR スキャン版のどちらでも差し込めるようにしている。
pub trait ExitLivenessProvider: Send + Sync {
fn compute_live_at_exit(
&self,
mir_query: &dyn crate::mir::MirQuery,
exit_block: BasicBlockId,
header_vals: &BTreeMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
) -> BTreeSet<String>;
}
/// Loop Exit Liveness BoxLegacy/Phase 1
///
/// # Phase 30: LoopScopeShape 移行中
///
/// このBoxは将来 LoopScopeShape に吸収される予定。
/// LoopScopeShape.exit_live が唯一の live_at_exit 情報源になる。
///
/// **新規コード**: `LoopScopeShape::exit_live` を直接参照すること。
///
/// # Purpose
/// Exit後で本当に使われる変数を決定する専門箱
///
/// # Responsibility
/// - live_at_exit 変数集合の計算
/// - 将来: MIRスキャンによる精密liveness解析
/// - 現在: 保守的近似全変数をliveとみなす
///
/// # Phase 1 Implementation (Conservative)
/// header_vals + exit_snapshots に現れる全変数を live_at_exit に含める
///
/// **理由**: LoopFormOps に MIR アクセスがないため、保守的近似で先行実装
///
/// # Future Phase 2+ (Precise)
/// exit ブロックの MIR をスキャンして、実際に read される変数のみを live とする
///
/// **将来拡張**: LoopFormOps 拡張 or MirFunction 参照追加時に精密化
///
/// # Usage
/// ```ignore
/// let liveness_box = LoopExitLivenessBox::new();
/// let live_at_exit = liveness_box.compute_live_at_exit(
/// &header_vals,
/// &exit_snapshots,
/// );
/// // → ch, pname, pend 等が含まれる(保守的)
/// ```
#[derive(Debug, Clone, Default)]
pub struct LoopExitLivenessBox;
impl LoopExitLivenessBox {
/// Create new LoopExitLivenessBox
pub fn new() -> Self {
Self
}
/// Compute live_at_exit variables (Phase 1: Conservative)
///
/// # Arguments
/// * `header_vals` - Header variable values
/// * `exit_snapshots` - Exit predecessor snapshots
///
/// # Returns
/// BTreeSet of variable names that are potentially live at exit
///
/// # Phase 1 Logic (Conservative)
/// すべての header_vals と exit_snapshots に現れる変数を live とみなす
///
/// **保守的な理由**:
/// - BodyLocalInternal でも exit 後で使われる変数があるch, pname, pend
/// - MIR スキャンなしでは正確な判定不可
/// - Phase 35-5: phi_invariants.rs deleted (validation moved to JoinIR Verifier)
///
/// # Future Phase 2+ (Precise)
/// exit ブロックの MIR をスキャンして、実際に使われる変数のみを返す
///
/// **精密化計画**:
/// 1. LoopFormOps に `get_block_instructions()` 追加
/// 2. exit ブロックの Copy/BinOp/Call 等で read される ValueId を収集
/// 3. ValueId → 変数名の逆引きvariable_map 逆マップ)
/// 4. read される変数のみを live_at_exit に含める
///
/// # Example
/// ```ignore
/// // Phase 1: 保守的近似
/// // skip_whitespace: ch は一部 pred でしか定義されないが、exit 後で使われる
/// let live_at_exit = liveness_box.compute_live_at_exit(
/// query,
/// exit_block,
/// &header_vals, // { i: %10, n: %20 }
/// &exit_snapshots, // [{ i: %30, ch: %40 }, { i: %50 }]
/// );
/// // → live_at_exit = { "i", "n", "ch" }(保守的に ch も含める)
/// ```
pub fn compute_live_at_exit(
&self,
_mir_query: &dyn crate::mir::MirQuery,
_exit_block: BasicBlockId,
_header_vals: &BTreeMap<String, ValueId>,
_exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
) -> BTreeSet<String> {
// Phase 26-F-4: 環境変数制御による段階的実装
//
// **環境変数ガード**:
// - NYASH_EXIT_LIVE_ENABLE=1: Phase 2+ MIRスキャン実装を有効化実験用
// - デフォルト(未設定): Phase 26-F-3 相当の挙動(安定版)
let enable_phase2 = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1");
let live_vars = if enable_phase2 {
// Phase 2+ MIRスキャン実装未実装
// TODO: LoopFormOps拡張後に実装
// - exit ブロックの MIR をスキャン
// - Copy/BinOp/Call 等で read される ValueId を収集
// - ValueId → 変数名の逆引き
// - read される変数のみを live_at_exit に含める
BTreeSet::new()
} else {
// Phase 1: 空の live_at_exit を返すPhase 26-F-3 相当)
//
// **理由**: 保守的近似(全変数を liveでは以前の PhiInvariantsBox でエラーだった
// Phase 35-5 で phi_invariants.rs 削除済み、検証は JoinIR Verifier へ移譲
//
// **問題**: header_vals + exit_snapshots の全変数を live とすると、
// BodyLocalInternal 変数も live_at_exit に含まれる
// → exit PHI 候補に昇格
// → 一部の exit pred でのみ定義 → 構造的に不正
//
// **Phase 1 方針**: live_at_exit を空にして、既存の LoopVarClassBox 分類のみに依存
// → BodyLocalInternal は exit PHI 候補から除外Phase 26-F-3 と同じ)
BTreeSet::new()
};
// Debug trace
if std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[LoopExitLivenessBox] Phase {}: live_at_exit={} vars{}",
if enable_phase2 { "2+" } else { "1" },
live_vars.len(),
if enable_phase2 {
" (MIR scan - experimental)"
} else {
" (empty - Phase 26-F-3 equivalent)"
}
);
}
live_vars
}
// ========================================================================
// Phase 30 F-1.2: LoopScopeShape 委譲メソッド
// ========================================================================
/// Phase 30: LoopScopeShape の exit_live を直接取得
///
/// 新規コードではこのメソッドを使うことを推奨。
/// 将来的には旧 compute_live_at_exit() メソッドを削除し、
/// LoopScopeShape::exit_live を直接参照するのが標準になる。
///
/// # Example
///
/// ```ignore
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
/// let live = liveness_box.get_exit_live_from_scope(&scope);
/// ```
#[allow(dead_code)] // Phase 30 F-1.2: will be used when LoopExitLivenessBox is absorbed
pub(crate) fn get_exit_live_from_scope(
&self,
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
) -> BTreeSet<String> {
scope.exit_live.clone()
}
}
impl ExitLivenessProvider for LoopExitLivenessBox {
fn compute_live_at_exit(
&self,
_mir_query: &dyn crate::mir::MirQuery,
_exit_block: BasicBlockId,
header_vals: &BTreeMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
) -> BTreeSet<String> {
self.compute_live_at_exit(_mir_query, _exit_block, header_vals, exit_snapshots)
}
}
/// MirScanExitLiveness - Phase 2+ 用のExitLivenessProviderプレースホルダ
///
/// Phase 26-F 時点では LoopExitLivenessBox と同じ実装を呼び出すだけの薄い箱だよ。
/// 将来、LoopFormOps や MirFunction へのアクセスが整備されたら、
/// ここに本物の MIR スキャン実装use/def ベースの live_at_exit 計算)を差し込む予定。
#[derive(Debug, Clone, Default)]
pub struct MirScanExitLiveness;
impl ExitLivenessProvider for MirScanExitLiveness {
fn compute_live_at_exit(
&self,
mir_query: &dyn crate::mir::MirQuery,
exit_block: BasicBlockId,
header_vals: &BTreeMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
) -> BTreeSet<String> {
let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1");
// 対象ブロック集合exit と break preds
let mut targets: BTreeSet<BasicBlockId> = BTreeSet::new();
targets.insert(exit_block);
for (bb, _) in exit_snapshots {
targets.insert(*bb);
}
// live map per block
let mut live_map: BTreeMap<BasicBlockId, BTreeSet<ValueId>> = BTreeMap::new();
for bb in &targets {
live_map.insert(*bb, BTreeSet::new());
}
let mut changed = true;
while changed {
changed = false;
// 逆順で走査(安定順序: BTreeSet
for bb in targets.iter().rev() {
let mut live: BTreeSet<ValueId> = BTreeSet::new();
// succ の live を流入(対象集合内のみ)
for succ in mir_query.succs(*bb) {
if targets.contains(&succ) {
if let Some(succ_live) = live_map.get(&succ) {
live.extend(succ_live);
}
}
}
// 命令を逆順スキャン
for inst in mir_query.insts_in_block(*bb).iter().rev() {
// kill writes
for w in mir_query.writes_of(inst) {
live.remove(&w);
}
// add reads
for r in mir_query.reads_of(inst) {
live.insert(r);
}
}
// 更新チェック
let entry = live_map.entry(*bb).or_default();
if *entry != live {
*entry = live;
changed = true;
}
}
}
// ValueId→名前の逆引きテーブルheader + exit snapshots
let mut name_pool: BTreeMap<ValueId, String> = BTreeMap::new();
for (name, vid) in header_vals {
name_pool.insert(*vid, name.clone());
}
for (_bb, snap) in exit_snapshots {
for (name, vid) in snap {
name_pool.insert(*vid, name.clone());
}
}
let mut live_names: BTreeSet<String> = BTreeSet::new();
for live in live_map.values() {
for v in live {
if let Some(name) = name_pool.get(v) {
live_names.insert(name.clone());
}
}
}
if trace {
eprintln!(
"[LoopExitLiveness/MirScan] live_at_exit={} vars (use/def scan)",
live_names.len()
);
}
live_names
}
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
struct EmptyQuery;
impl crate::mir::MirQuery for EmptyQuery {
fn insts_in_block(&self, _bb: BasicBlockId) -> &[crate::mir::MirInstruction] {
&[]
}
fn succs(&self, _bb: BasicBlockId) -> Vec<BasicBlockId> {
Vec::new()
}
fn reads_of(&self, _inst: &crate::mir::MirInstruction) -> Vec<ValueId> {
Vec::new()
}
fn writes_of(&self, _inst: &crate::mir::MirInstruction) -> Vec<ValueId> {
Vec::new()
}
}
#[test]
fn test_compute_live_at_exit_conservative() {
let liveness_box = LoopExitLivenessBox::new();
let query = EmptyQuery;
let mut header_vals = BTreeMap::new();
header_vals.insert("i".to_string(), ValueId(10));
header_vals.insert("n".to_string(), ValueId(20));
let mut snap1 = BTreeMap::new();
snap1.insert("i".to_string(), ValueId(30));
snap1.insert("ch".to_string(), ValueId(40));
let mut snap2 = BTreeMap::new();
snap2.insert("i".to_string(), ValueId(50));
snap2.insert("pname".to_string(), ValueId(60));
let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)];
let live_at_exit = liveness_box.compute_live_at_exit(
&query,
BasicBlockId(0),
&header_vals,
&exit_snapshots,
);
// Phase 1: 空の live_at_exitMIRスキャン実装待ち
assert_eq!(live_at_exit.len(), 0);
}
#[test]
fn test_compute_live_at_exit_empty() {
let liveness_box = LoopExitLivenessBox::new();
let query = EmptyQuery;
let header_vals = BTreeMap::new();
let exit_snapshots = vec![];
let live_at_exit = liveness_box.compute_live_at_exit(
&query,
BasicBlockId(0),
&header_vals,
&exit_snapshots,
);
assert_eq!(live_at_exit.len(), 0);
}
#[test]
fn test_compute_live_at_exit_deduplication() {
let liveness_box = LoopExitLivenessBox::new();
let query = EmptyQuery;
let mut header_vals = BTreeMap::new();
header_vals.insert("i".to_string(), ValueId(10));
let mut snap1 = BTreeMap::new();
snap1.insert("i".to_string(), ValueId(20)); // 重複
let mut snap2 = BTreeMap::new();
snap2.insert("i".to_string(), ValueId(30)); // 重複
let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)];
let live_at_exit = liveness_box.compute_live_at_exit(
&query,
BasicBlockId(0),
&header_vals,
&exit_snapshots,
);
// Phase 1: 空の live_at_exitMIRスキャン実装待ち
assert_eq!(live_at_exit.len(), 0);
}
}

View File

@ -1,40 +1,15 @@
/*!
* Phase 36: LoopSnapshotMergeBox - Exit PHI Merge Utility (Pure Static)
* LoopSnapshotMergeBox - Exit PHI merge utility (pinned/carrier + availability)
*
* **Responsibility**: Exit PHI input merging with variable classification (Option C)
*
* ## What This Box Does (Phase 36 Scope)
* - Exit PHI input merging with LoopVarClassBox classification
* - PHI pred mismatch prevention via LocalScopeInspectorBox
* - Header fallthrough + break snapshot merging
*
* ## What This Box Does NOT Do (Migrated to Other Systems)
* - Header PHI generation: → LoopFormBuilder::seal_phis() + PhiInputCollector
* - PHI optimization: → PhiInputCollector::optimize_same_value()
* - PHI sanitization: → PhiInputCollector::sanitize()
*
* ## Future Migration (Phase 37+)
* - Variable classification: Consider moving to LoopScopeShape
* - Exit PHI generation: Consider JoinIR Exit lowering coverage expansion
*
* ## Design: Pure Static Utility (No State)
* This struct has no fields and provides only static methods.
* It acts as a namespace for exit PHI merging logic.
*
* ## History
* - Phase 25.2: Created for snapshot merge unification (~210 line reduction)
* - Phase 36: Removed dead code (merge_continue_for_header), superseded helpers
* - Reduced from 470 → 309 lines (34% reduction)
* - Converted to pure static utility (no state)
* - Exit PHI inputs are merged using pinned/carrier hints and availability
* reconstructed from header/exit snapshots (Option C guard)。
* - Header/exit PHI の仕様や最適化は LoopFormBuilder / LoopScopeShape 側が SSOT。
* - 将来の縮退・移行計画は docs 側Phase 37+ メモ)を参照してね。
*/
use crate::mir::{BasicBlockId, ValueId};
use std::collections::{BTreeMap, BTreeSet};
// Option C PHI bug fix: Use box-based classification
use super::local_scope_inspector::LocalScopeInspectorBox;
use super::loop_var_classifier::LoopVarClassBox;
/// Phase 36: Pure static utility for exit PHI merging (no state)
///
/// # Responsibility
@ -54,16 +29,10 @@ impl LoopSnapshotMergeBox {
/// # Phase 36 Essential Logic
///
/// This method is the SSOT for exit PHI input merging with:
/// 1. LoopVarClassBox classification (Pinned/Carrier/BodyLocalInOut/BodyLocalInternal)
/// 2. LocalScopeInspectorBox availability checking
/// 1. 変数分類(Pinned/Carrier/BodyLocalExit/BodyLocalInternal
/// 2. スナップショットに基づく availability チェック
/// 3. PHI pred mismatch prevention (Option C)
///
/// # Why This Can't Be in LoopScopeShape (Yet)
///
/// - Variable classification needs LoopVarClassBox + LocalScopeInspectorBox
/// - Option C logic is complex and battle-tested
/// - JoinIR Exit lowering doesn't cover all patterns yet (Phase 34 partial)
///
/// # Future Migration Path (Phase 37+)
///
/// - Expand JoinIR Exit lowering to cover complex patterns
@ -90,8 +59,8 @@ impl LoopSnapshotMergeBox {
///
/// ## Option C ロジック
///
/// 1. LocalScopeInspectorBox で変数定義位置を追跡
/// 2. LoopVarClassBox で変数を分類
/// 1. スナップショットから変数定義位置を再構築
/// 2. pinned/carrier hint を優先し、残りは availability で BodyLocalExit/BodyLocalInternal を判定
/// 3. BodyLocalInternal全exit predsで定義されていない変数は skip
///
/// ## 例skip_whitespace バグ修正)
@ -116,18 +85,7 @@ impl LoopSnapshotMergeBox {
let debug = std::env::var("NYASH_OPTION_C_DEBUG").is_ok();
// Phase 69-2: inspector を内部で構築(外部引数から削除)
// exit_snapshots から変数定義位置を再構築
let mut inspector = LocalScopeInspectorBox::new();
for (block_id, snapshot) in exit_snapshots {
for var_name in snapshot.keys() {
inspector.record_definition(var_name, *block_id);
}
}
// header_vals も記録
for var_name in header_vals.keys() {
inspector.record_definition(var_name, header_id);
}
let definitions = build_definitions(header_id, header_vals, exit_snapshots);
if debug {
eprintln!("[Option C] merge_exit_with_classification called");
eprintln!("[Option C] exit_preds: {:?}", exit_preds);
@ -144,9 +102,6 @@ impl LoopSnapshotMergeBox {
// header 値を exit PHI 入力に含めると「非支配ブロックからの値参照」で壊れる。
let header_in_exit_preds = exit_preds.contains(&header_id);
// LoopVarClassBox でフィルタリング
let classifier = LoopVarClassBox::new();
// すべての変数名を収集決定的順序のためBTreeSet使用
let mut all_vars: BTreeSet<String> = BTreeSet::new();
all_vars.extend(header_vals.keys().cloned());
@ -156,34 +111,35 @@ impl LoopSnapshotMergeBox {
// 各変数を分類して、exit PHI が必要なもののみ処理(アルファベット順で決定的)
for var_name in all_vars {
// Option C: 変数分類実際のCFG predecessorsを使用
let class = classifier.classify(
let class = classify_exit_var(
&var_name,
pinned_vars,
carrier_vars,
&inspector,
exit_preds, // ← 実際のCFG predecessorsを使用
exit_preds,
&definitions,
);
let needs_exit_phi = class_needs_exit_phi(class);
if debug {
eprintln!(
"[Option C] var '{}': {:?} needs_exit_phi={}",
var_name,
class,
class.needs_exit_phi()
needs_exit_phi
);
let defining_blocks = inspector.get_defining_blocks(&var_name);
eprintln!("[Option C] defining_blocks: {:?}", defining_blocks);
if let Some(defining_blocks) = definitions.get(&var_name) {
eprintln!("[Option C] defining_blocks: {:?}", defining_blocks);
}
}
// Option C: Additional check - even Carrier/Pinned need definition check!
// Carrier/Pinned だからといって、全 exit preds で定義されているとは限らない
let is_in_all_preds = inspector.is_available_in_all(&var_name, exit_preds);
let is_in_all_preds = is_available_in_all(&var_name, exit_preds, &definitions);
// exit PHI が不要な場合は skip
if !class.needs_exit_phi() || !is_in_all_preds {
if !needs_exit_phi || !is_in_all_preds {
if debug {
if !class.needs_exit_phi() {
if !needs_exit_phi {
eprintln!(
"[Option C] → SKIP exit PHI for '{}' (class={:?})",
var_name, class
@ -240,6 +196,88 @@ impl LoopSnapshotMergeBox {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ExitVarClass {
Pinned,
Carrier,
BodyLocalExit,
BodyLocalInternal,
}
fn classify_exit_var(
var_name: &str,
pinned_vars: &[String],
carrier_vars: &[String],
exit_preds: &[BasicBlockId],
definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
) -> ExitVarClass {
if is_pin_temp(var_name) {
return ExitVarClass::BodyLocalInternal;
}
if pinned_vars.iter().any(|p| p == var_name) {
return ExitVarClass::Pinned;
}
if carrier_vars.iter().any(|c| c == var_name) {
return ExitVarClass::Carrier;
}
if is_available_in_all(var_name, exit_preds, definitions) {
ExitVarClass::BodyLocalExit
} else {
ExitVarClass::BodyLocalInternal
}
}
fn class_needs_exit_phi(class: ExitVarClass) -> bool {
matches!(
class,
ExitVarClass::Pinned | ExitVarClass::Carrier | ExitVarClass::BodyLocalExit
)
}
fn build_definitions(
header_id: BasicBlockId,
header_vals: &BTreeMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
) -> BTreeMap<String, BTreeSet<BasicBlockId>> {
let mut definitions: BTreeMap<String, BTreeSet<BasicBlockId>> = BTreeMap::new();
for name in header_vals.keys() {
definitions
.entry(name.clone())
.or_default()
.insert(header_id);
}
for (bb, snap) in exit_snapshots {
for name in snap.keys() {
definitions.entry(name.clone()).or_default().insert(*bb);
}
}
definitions
}
fn is_available_in_all(
var_name: &str,
required_blocks: &[BasicBlockId],
definitions: &BTreeMap<String, BTreeSet<BasicBlockId>>,
) -> bool {
if let Some(defining_blocks) = definitions.get(var_name) {
required_blocks
.iter()
.all(|block| defining_blocks.contains(block))
} else {
false
}
}
fn is_pin_temp(var_name: &str) -> bool {
var_name.starts_with("__pin$") && var_name.contains("$@")
}
#[cfg(test)]
mod tests {
use super::*;
@ -293,7 +331,7 @@ mod tests {
let header_vals = BTreeMap::new(); // ch not in header
let mut break1_snap = BTreeMap::new();
let break1_snap = BTreeMap::new();
// break1: ch not defined yet (early exit)
let mut break2_snap = BTreeMap::new();
break2_snap.insert("ch".to_string(), ValueId::new(20)); // ch defined in break2

View File

@ -1,578 +0,0 @@
/// LoopVarClassBox - Loop variable classifier
///
/// # Purpose
///
/// This box classifies loop variables into 4 categories:
/// - Pinned: Loop-crossing parameters (always need PHI)
/// - Carrier: Loop-modified variables (always need PHI)
/// - BodyLocalExit: Body-local but defined in ALL exit predecessors (need exit PHI)
/// - BodyLocalInternal: Body-local, NOT in all exit predecessors (NO exit PHI)
///
/// # Responsibility
///
/// - Classify variables based on their definition scope
/// - Use LocalScopeInspectorBox to check definition locations
/// - Provide classification for PHI generation decision
///
/// # Design Philosophy (Box Theory)
///
/// This box has ONE job: classify variables.
/// It doesn't generate PHI nodes or modify IR.
/// It's a pure decision box that other boxes can query.
use super::local_scope_inspector::LocalScopeInspectorBox;
use crate::mir::BasicBlockId;
/// Variable classification for loop PHI generation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoopVarClass {
/// Loop-crossing parameter (always needs header/exit PHI)
///
/// Example:
/// ```
/// method process(limit) { // limit is Pinned
/// local i = 0
/// loop(i < limit) { i = i + 1 } // limit doesn't change
/// }
/// ```
Pinned,
/// Loop-modified variable (always needs header/exit PHI)
///
/// Example:
/// ```
/// local i = 0
/// loop(i < 10) {
/// i = i + 1 // i is Carrier (modified in loop)
/// }
/// ```
Carrier,
/// Body-local variable defined in ALL exit predecessors (needs exit PHI)
///
/// Example:
/// ```
/// loop(condition) {
/// local x = compute() // defined at loop entry
/// if x > 10 { break } // exit path 1: x exists
/// if x < 0 { break } // exit path 2: x exists
/// }
/// // x is BodyLocalExit → needs exit PHI
/// ```
BodyLocalExit,
/// Body-local variable NOT in all exit predecessors (NO exit PHI)
///
/// Example (skip_whitespace bug):
/// ```
/// loop(1 == 1) {
/// if i >= n { break } // exit path 1: ch doesn't exist
/// local ch = s.substring() // ch defined here
/// if ch == " " { ... } else { break } // exit path 2: ch exists
/// }
/// // ch is BodyLocalInternal → NO exit PHI (would cause pred mismatch!)
/// ```
BodyLocalInternal,
}
impl LoopVarClass {
/// Check if this classification requires exit PHI
pub fn needs_exit_phi(self) -> bool {
match self {
LoopVarClass::Pinned => true,
LoopVarClass::Carrier => true,
LoopVarClass::BodyLocalExit => true,
LoopVarClass::BodyLocalInternal => false, // ← Option C: Skip exit PHI!
}
}
/// Check if this classification requires header PHI
pub fn needs_header_phi(self) -> bool {
match self {
LoopVarClass::Pinned => true,
LoopVarClass::Carrier => true,
LoopVarClass::BodyLocalExit => false, // Body-local: no header PHI
LoopVarClass::BodyLocalInternal => false,
}
}
/// Get human-readable description
pub fn description(&self) -> &'static str {
match self {
LoopVarClass::Pinned => "Loop-crossing parameter",
LoopVarClass::Carrier => "Loop-modified variable",
LoopVarClass::BodyLocalExit => "Body-local (all exits)",
LoopVarClass::BodyLocalInternal => "Body-local (partial exits)",
}
}
}
/// Loop variable classifier box
///
/// # Phase 30: LoopScopeShape 移行中
///
/// このBoxは将来 LoopScopeShape に吸収される予定。
/// 新規コードは `LoopScopeShape::classify()` を直接使うことを推奨。
///
/// # Usage (Legacy)
///
/// ```
/// let inspector = LocalScopeInspectorBox::new();
/// // ... record definitions ...
///
/// let classifier = LoopVarClassBox::new();
/// let class = classifier.classify(
/// "ch",
/// &["limit", "n"], // pinned_vars
/// &["i"], // carrier_vars
/// &inspector,
/// &exit_preds,
/// );
///
/// if class.needs_exit_phi() {
/// // Generate exit PHI
/// }
/// ```
///
/// # Usage (Phase 30 Recommended)
///
/// ```
/// // LoopScopeShape が利用可能な場合は直接使う
/// let class = scope.classify("ch");
/// ```
#[derive(Debug, Clone, Default)]
pub struct LoopVarClassBox;
impl LoopVarClassBox {
/// Create a new classifier
pub fn new() -> Self {
Self
}
/// Classify a variable for PHI generation decision
///
/// # Phase 30 TODO
///
/// このメソッドは将来 `LoopScopeShape::classify()` に置き換える予定。
/// 呼び出し側が LoopScopeShape を持っている場合は、そちらを直接使うこと。
///
/// # Parameters
///
/// - `var_name`: Variable to classify
/// - `pinned_vars`: Known loop-crossing parameters
/// - `carrier_vars`: Known loop-modified variables
/// - `inspector`: LocalScopeInspectorBox to check definition locations
/// - `exit_preds`: All exit predecessor blocks
///
/// # Returns
///
/// LoopVarClass indicating PHI generation requirements
///
/// # Example
///
/// ```
/// // skip_whitespace scenario:
/// let class = classifier.classify(
/// "ch",
/// &[], // not pinned
/// &[], // not carrier
/// &inspector, // ch only in block 5
/// &[BasicBlockId(2), BasicBlockId(5)], // exit preds
/// );
///
/// assert_eq!(class, LoopVarClass::BodyLocalInternal);
/// assert!(!class.needs_exit_phi()); // ← Skip exit PHI!
/// ```
pub fn classify(
&self,
var_name: &str,
pinned_vars: &[String],
carrier_vars: &[String],
inspector: &LocalScopeInspectorBox,
exit_preds: &[BasicBlockId],
) -> LoopVarClass {
// Priority 0: __pin$ temporary variables are ALWAYS BodyLocalInternal
// Reason: These are compiler-generated temporaries for pinning expression values.
// They are defined inside loop bodies and should NEVER get exit PHIs.
// Task先生の発見: ValueId(289)等の未定義値エラーの根本原因!
if var_name.starts_with("__pin$") && var_name.contains("$@") {
return LoopVarClass::BodyLocalInternal;
}
// Priority 1: Check if it's a pinned variable
if pinned_vars.iter().any(|p| p == var_name) {
return LoopVarClass::Pinned;
}
// Priority 2: Check if it's a carrier variable
if carrier_vars.iter().any(|c| c == var_name) {
return LoopVarClass::Carrier;
}
// Priority 3: Check if it's a body-local variable
// Option C logic: Check if defined in ALL exit predecessors
if inspector.is_available_in_all(var_name, exit_preds) {
LoopVarClass::BodyLocalExit
} else {
// ← Option C: Skip exit PHI for partial body-local variables!
LoopVarClass::BodyLocalInternal
}
}
/// Batch classify multiple variables
///
/// # Returns
///
/// Vec of (var_name, classification) tuples
pub fn classify_all(
&self,
var_names: &[String],
pinned_vars: &[String],
carrier_vars: &[String],
inspector: &LocalScopeInspectorBox,
exit_preds: &[BasicBlockId],
) -> Vec<(String, LoopVarClass)> {
var_names
.iter()
.map(|name| {
let class = self.classify(name, pinned_vars, carrier_vars, inspector, exit_preds);
(name.clone(), class)
})
.collect()
}
/// Filter variables that need exit PHI
///
/// # Returns
///
/// Vec of variable names that should get exit PHI
pub fn filter_exit_phi_candidates(
&self,
var_names: &[String],
pinned_vars: &[String],
carrier_vars: &[String],
inspector: &LocalScopeInspectorBox,
exit_preds: &[BasicBlockId],
) -> Vec<String> {
var_names
.iter()
.filter(|name| {
let class = self.classify(name, pinned_vars, carrier_vars, inspector, exit_preds);
class.needs_exit_phi()
})
.cloned()
.collect()
}
// ========================================================================
// Phase 30 F-1.1: LoopScopeShape 委譲メソッド
// ========================================================================
/// Phase 30: LoopScopeShape を使って変数を分類
///
/// 新規コードではこのメソッドを使うことを推奨。
/// 将来的には旧 classify() メソッドを削除し、このメソッドが標準になる。
///
/// # Example
///
/// ```ignore
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
/// let class = classifier.classify_with_scope("ch", &scope);
/// ```
#[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed
pub(crate) fn classify_with_scope(
&self,
var_name: &str,
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
) -> LoopVarClass {
scope.classify(var_name)
}
/// Phase 30: LoopScopeShape を使って複数変数を一括分類
#[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed
pub(crate) fn classify_all_with_scope(
&self,
var_names: &[String],
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
) -> Vec<(String, LoopVarClass)> {
scope.classify_all(var_names)
}
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loop_var_class_needs_exit_phi() {
assert!(LoopVarClass::Pinned.needs_exit_phi());
assert!(LoopVarClass::Carrier.needs_exit_phi());
assert!(LoopVarClass::BodyLocalExit.needs_exit_phi());
assert!(!LoopVarClass::BodyLocalInternal.needs_exit_phi()); // ← Option C!
}
#[test]
fn test_loop_var_class_needs_header_phi() {
assert!(LoopVarClass::Pinned.needs_header_phi());
assert!(LoopVarClass::Carrier.needs_header_phi());
assert!(!LoopVarClass::BodyLocalExit.needs_header_phi());
assert!(!LoopVarClass::BodyLocalInternal.needs_header_phi());
}
#[test]
fn test_classify_pinned_variable() {
let inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
let class = classifier.classify(
"limit",
&["limit".to_string()], // pinned
&[],
&inspector,
&[],
);
assert_eq!(class, LoopVarClass::Pinned);
assert!(class.needs_exit_phi());
assert!(class.needs_header_phi());
}
#[test]
fn test_classify_carrier_variable() {
let inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
let class = classifier.classify(
"i",
&[],
&["i".to_string()], // carrier
&inspector,
&[],
);
assert_eq!(class, LoopVarClass::Carrier);
assert!(class.needs_exit_phi());
assert!(class.needs_header_phi());
}
#[test]
fn test_classify_body_local_exit() {
let mut inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
// Variable "x" is defined in all exit predecessors
let block_2 = BasicBlockId::new(2);
let block_5 = BasicBlockId::new(5);
inspector.record_definition("x", block_2);
inspector.record_definition("x", block_5);
let exit_preds = vec![block_2, block_5];
let class = classifier.classify("x", &[], &[], &inspector, &exit_preds);
assert_eq!(class, LoopVarClass::BodyLocalExit);
assert!(class.needs_exit_phi()); // Should get exit PHI
assert!(!class.needs_header_phi());
}
#[test]
fn test_classify_body_local_internal() {
let mut inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
// Variable "ch" is only defined in block 5, not in block 2
let block_2 = BasicBlockId::new(2);
let block_5 = BasicBlockId::new(5);
inspector.record_definition("ch", block_5); // Only block 5!
let exit_preds = vec![block_2, block_5];
let class = classifier.classify("ch", &[], &[], &inspector, &exit_preds);
assert_eq!(class, LoopVarClass::BodyLocalInternal);
assert!(!class.needs_exit_phi()); // ← Option C: Skip exit PHI!
assert!(!class.needs_header_phi());
}
/// Test the skip_whitespace PHI bug scenario
#[test]
fn test_skip_whitespace_scenario() {
let mut inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
// Simulate skip_whitespace loop:
// - Variables i, n, s are pinned/carrier (defined everywhere)
// - Variable ch is only defined in block 5 (break path 2)
let block_2 = BasicBlockId::new(2); // header / break path 1
let block_5 = BasicBlockId::new(5); // break path 2
let block_7 = BasicBlockId::new(7); // latch (hypothetical)
// i, n, s are in all blocks
for var in &["i", "n", "s"] {
inspector.record_definition(var, block_2);
inspector.record_definition(var, block_5);
inspector.record_definition(var, block_7);
}
// ch is only in block 5
inspector.record_definition("ch", block_5);
let exit_preds = vec![block_2, block_5, block_7];
// Classify all variables
let pinned = vec!["n".to_string(), "s".to_string()];
let carrier = vec!["i".to_string()];
let i_class = classifier.classify("i", &pinned, &carrier, &inspector, &exit_preds);
let n_class = classifier.classify("n", &pinned, &carrier, &inspector, &exit_preds);
let s_class = classifier.classify("s", &pinned, &carrier, &inspector, &exit_preds);
let ch_class = classifier.classify("ch", &pinned, &carrier, &inspector, &exit_preds);
// i, n, s should need exit PHI
assert_eq!(i_class, LoopVarClass::Carrier);
assert_eq!(n_class, LoopVarClass::Pinned);
assert_eq!(s_class, LoopVarClass::Pinned);
assert!(i_class.needs_exit_phi());
assert!(n_class.needs_exit_phi());
assert!(s_class.needs_exit_phi());
// ch should NOT need exit PHI (Option C: prevents PHI pred mismatch!)
assert_eq!(ch_class, LoopVarClass::BodyLocalInternal);
assert!(!ch_class.needs_exit_phi()); // ← This fixes the bug!
}
#[test]
fn test_classify_all() {
let mut inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
let block_2 = BasicBlockId::new(2);
let block_5 = BasicBlockId::new(5);
// Setup: i, n in all blocks; ch only in block 5
for var in &["i", "n"] {
inspector.record_definition(var, block_2);
inspector.record_definition(var, block_5);
}
inspector.record_definition("ch", block_5);
let vars = vec!["i".to_string(), "n".to_string(), "ch".to_string()];
let pinned = vec!["n".to_string()];
let carrier = vec!["i".to_string()];
let exit_preds = vec![block_2, block_5];
let results = classifier.classify_all(&vars, &pinned, &carrier, &inspector, &exit_preds);
assert_eq!(results.len(), 3);
assert_eq!(results[0], ("i".to_string(), LoopVarClass::Carrier));
assert_eq!(results[1], ("n".to_string(), LoopVarClass::Pinned));
assert_eq!(
results[2],
("ch".to_string(), LoopVarClass::BodyLocalInternal)
);
}
#[test]
fn test_filter_exit_phi_candidates() {
let mut inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
let block_2 = BasicBlockId::new(2);
let block_5 = BasicBlockId::new(5);
// Setup: i, n in all blocks; ch only in block 5
for var in &["i", "n"] {
inspector.record_definition(var, block_2);
inspector.record_definition(var, block_5);
}
inspector.record_definition("ch", block_5);
let vars = vec!["i".to_string(), "n".to_string(), "ch".to_string()];
let pinned = vec!["n".to_string()];
let carrier = vec!["i".to_string()];
let exit_preds = vec![block_2, block_5];
let candidates = classifier.filter_exit_phi_candidates(
&vars,
&pinned,
&carrier,
&inspector,
&exit_preds,
);
assert_eq!(candidates.len(), 2);
assert!(candidates.contains(&"i".to_string()));
assert!(candidates.contains(&"n".to_string()));
assert!(!candidates.contains(&"ch".to_string())); // ← Option C: ch is filtered out!
}
/// Test Task先生の発見: __pin$ temporary variables should be BodyLocalInternal
#[test]
fn test_classify_pin_temporary_variables() {
let inspector = LocalScopeInspectorBox::new();
let classifier = LoopVarClassBox::new();
// __pin$ temporary variables should ALWAYS be BodyLocalInternal
// regardless of their definition locations
let pin_vars = vec![
"__pin$285$@binop_lhs",
"__pin$286$@binop_rhs",
"__pin$437$@binop_lhs",
"__pin$297$@assign",
];
for pin_var in pin_vars {
let class = classifier.classify(
pin_var,
&[], // Not pinned
&[], // Not carrier
&inspector,
&[], // No exit preds
);
assert_eq!(
class,
LoopVarClass::BodyLocalInternal,
"Variable '{}' should be BodyLocalInternal",
pin_var
);
assert!(
!class.needs_exit_phi(),
"Variable '{}' should NOT need exit PHI",
pin_var
);
assert!(
!class.needs_header_phi(),
"Variable '{}' should NOT need header PHI",
pin_var
);
}
}
#[test]
fn test_description() {
assert_eq!(
LoopVarClass::Pinned.description(),
"Loop-crossing parameter"
);
assert_eq!(
LoopVarClass::Carrier.description(),
"Loop-modified variable"
);
assert_eq!(
LoopVarClass::BodyLocalExit.description(),
"Body-local (all exits)"
);
assert_eq!(
LoopVarClass::BodyLocalInternal.description(),
"Body-local (partial exits)"
);
}
}

View File

@ -13,35 +13,7 @@ pub mod if_phi;
// Phase 30 F-2.1: loop_phi 削除LoopFormBuilder が SSOT
pub mod loop_snapshot_merge;
pub mod loopform_builder;
// Phase 69-4.2: Trio 公開面削減方針
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ⚠️ Trio Legacy Boxes (Phase 70 削除予定):
// - LocalScopeInspectorBox (361行) - 変数定義位置追跡LoopScopeShapeで代替済み
// - LoopVarClassBox (578行) - 変数分類LoopScopeShapeで代替済み
// - LoopExitLivenessBox (414行) - Exit後生存変数分析LoopScopeShapeで代替済み
//
// 現在の外部依存Phase 69-4.1棚卸し済み):
// 1. src/mir/join_ir/lowering/loop_form_intake.rs (~30行) - LoopScopeShape移行待ち
// 2. src/mir/phi_core/loop_snapshot_merge.rs (~60行) - Exit PHI生成で使用中
//
// Phase 69-4.2 方針:
// - ✅ pub 公開継続外部依存2箇所が残存
// - 🎯 目標: phi_core 内部+テストのみが知る状態(現在達成できず)
// - 📋 Phase 70 実装時: json_v0_bridge 移行後に完全削除
//
// TODO(Phase 70): json_v0_bridge の LoopScopeShape 移行完了後、以下を削除:
// - pub mod local_scope_inspector; (361行)
// - pub mod loop_var_classifier; (578行)
// - pub mod loop_exit_liveness; (414行)
// - loop_snapshot_merge.rs 内の Trio 使用箇所 (~60行)
// - loop_form_intake.rs 内の Trio 使用箇所 (~30行)
// 合計削減見込み: ~1,443行
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Option C PHI bug fix: Box-based design (Phase 70 削除予定)
pub mod local_scope_inspector;
pub mod loop_var_classifier;
// Trio legacy boxes removed in Phase 70: LoopScopeShape now owns classification/liveness.
// Phase 26-B: Box-First Refactoring
// Phase 30 F-2.1: body_local_phi_builder 削除LoopScopeShape で代替)
@ -60,12 +32,6 @@ pub mod phi_builder_box;
// Phase 35-5: if_body_local_merge 削除PhiBuilderBoxに吸収済み
// Phase 35-5: phi_invariants 削除JoinIR Verifierに移譲済み
// Phase 26-F-4: Loop Exit Liveness Box - exit後で使われる変数決定箱
// ⚠️ Phase 69-4.2: Trio Legacy Box (Phase 70 削除予定)
// - 現在の外部依存: loop_form_intake.rs が使用中
// - TODO(Phase 70): LoopScopeShape 移行後に削除
pub mod loop_exit_liveness;
// Phase 61-7.0: Dead code 削除
// 削除された facade 関数:
// - build_if_phis(): 呼び出し元ゼロ、PhiBuilderBox::generate_phis() で代替

View File

@ -83,7 +83,7 @@ pub struct PhiBuilderBox {
///
/// ## 責務分離の原則
/// - **If側この箱**: ループコンテキストを「名前セット」だけで受け取る
/// - **Loop側LoopBuilder**: LoopVarClassBoxから「キャリア変数名」を抽出
/// - **Loop側LoopBuilder**: LoopScopeShape から「キャリア変数名」を抽出
/// - **橋渡し**: このIfPhiContext経由で最小限の情報伝達
///
/// ## なぜこの設計?
@ -102,7 +102,7 @@ pub struct IfPhiContext {
/// - 例: `if i >= n { break }` でthen腕に`i`なし → でもcarrierなのでPHI必要
///
/// # 決定箇所
/// - LoopBuilder側でLoopVarClassBoxから抽出
/// - LoopBuilder側でLoopScopeShapeから抽出
/// - Pinnedループ越しパラメータ + Carrierループ修正変数
pub loop_carrier_names: std::collections::BTreeSet<String>,
}
@ -498,7 +498,7 @@ impl PhiBuilderBox {
/// │ └─ Carrier変数のPHI
/// ├─ Exit PHI: ExitPhiBuilder使用
/// │ ├─ BodyLocalPhiBuilder要否判定
/// │ ├─ LoopVarClassBox(変数分類)
/// │ ├─ LoopScopeShape(変数分類)
/// │ └─ LocalScopeInspectorBox定義追跡
/// └─ Seal: PhiInputCollector使用
/// ```