Files
hakorune/src/mir/if_in_loop_phi/mod.rs
nyash-codex 440f8646b1 feat(joinir): Phase 183 LoopBodyLocal role separation + test fixes
Phase 183 Implementation:
- Added is_var_used_in_condition() helper for AST variable detection
- Implemented LoopBodyLocal filtering in TrimLoopLowerer
- Created 4 test files for P1/P2 patterns
- Added 5 unit tests for variable detection

Test Fixes:
- Fixed test_is_outer_scope_variable_pinned (BasicBlockId import)
- Fixed test_pattern2_accepts_loop_param_only (literal node usage)

Refactoring:
- Unified pattern detection documentation
- Consolidated CarrierInfo initialization
- Documented LoopScopeShape construction paths

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 23:43:26 +09:00

417 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Phase 61-3: If-in-loop PHI Emitter
//!
//! ループ内 if の PHI 生成を担当する「箱」。
//! JoinIR + PhiSpec から直接 PHI 命令を発行する。
//!
//! ## 箱理論における位置づけ
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────┐
//! │ if_lowering.rsオーケストレーター
//! │ ↓ │
//! │ ┌─────────────────────┐ ┌─────────────────────────────┐ │
//! │ │ JoinIR Lowering │ │ IfInLoopPhiEmitter │ │
//! │ │ (PhiSpec生成) │→ │ (PHI命令発行) │ │
//! │ └─────────────────────┘ └─────────────────────────────┘ │
//! └─────────────────────────────────────────────────────────────┘
//! ```
//!
//! ## 責務
//!
//! - Header PHI 生成(ループイテレーション継続用)
//! - VarLookup 方式による incoming 値解決
//! - PhiBuilderOps trait 経由での PHI 発行
//!
//! ## 設計原則
//!
//! - **Thin Box**: ロジックは最小限、組み立てに専念
//! - **CFG非依存**: incoming値は snapshot から直接取得
//! - **決定性保証**: BTreeSet/BTreeMap のみ使用
use crate::mir::control_form::IfShape;
use crate::mir::join_ir::lowering::if_phi_spec::PhiSpec;
use crate::mir::phi_core::phi_builder_box::PhiBuilderOps;
use crate::mir::ValueId;
use std::collections::{BTreeMap, BTreeSet};
/// If-in-loop PHI Emitter
///
/// JoinIR経路で生成された PhiSpec を元に、
/// 実際の PHI 命令を発行する。
pub struct IfInLoopPhiEmitter;
impl IfInLoopPhiEmitter {
/// Header PHI 生成
///
/// ループキャリア変数に対して、then/else 腕の値を統合する PHI を生成する。
///
/// # Arguments
///
/// * `phi_spec` - JoinIR から計算された PHI 仕様
/// * `pre_if_var_map` - if 直前の変数マップVarLookup
/// * `then_snapshot` - then 腕終了時の変数スナップショット
/// * `else_snapshot_opt` - else 腕終了時の変数スナップショット(ない場合は None
/// * `carrier_names` - ループキャリア変数名(片腕 PHI 対象)
/// * `ops` - PHI 生成操作インターフェース
/// * `if_shape` - If 構造情報
///
/// # Returns
///
/// 生成した PHI の数
pub fn emit_header_phis<O: PhiBuilderOps>(
phi_spec: &PhiSpec,
pre_if_var_map: &BTreeMap<String, ValueId>,
then_snapshot: &BTreeMap<String, ValueId>,
else_snapshot_opt: Option<&BTreeMap<String, ValueId>>,
carrier_names: &BTreeSet<String>,
ops: &mut O,
if_shape: &IfShape,
) -> Result<usize, String> {
let mut phi_count = 0;
let trace_on = crate::config::env::joinir_dev_enabled()
&& std::env::var("HAKO_JOINIR_IF_IN_LOOP_TRACE")
.ok()
.as_deref()
== Some("1");
if trace_on {
eprintln!("[Phase 61-3] IfInLoopPhiEmitter::emit_header_phis start");
eprintln!("[Phase 61-3] header_phis: {:?}", phi_spec.header_phis);
eprintln!("[Phase 61-3] carrier_names: {:?}", carrier_names);
}
// Header PHI 生成: carrier_names に含まれる変数のみ
for var_name in &phi_spec.header_phis {
if !carrier_names.contains(var_name) {
continue;
}
// VarLookup: pre_if値を取得
let pre_val = match pre_if_var_map.get(var_name) {
Some(&v) => v,
None => {
if trace_on {
eprintln!(
"[Phase 61-3] ⚠️ var={} not found in pre_if_var_map, skipping",
var_name
);
}
continue;
}
};
// Then値: snapshot から取得、なければ pre_val
let then_val = then_snapshot.get(var_name).copied().unwrap_or(pre_val);
// Else値: snapshot から取得、なければ pre_val片腕 PHI パターン)
let else_val = else_snapshot_opt
.and_then(|m| m.get(var_name).copied())
.unwrap_or(pre_val);
// 値が同一なら PHI 不要
if then_val == else_val {
if trace_on {
eprintln!(
"[Phase 61-3] var={}: then_val == else_val ({:?}), no PHI needed",
var_name, then_val
);
}
ops.update_var(var_name.clone(), then_val);
continue;
}
// PHI 生成
let phi_dst = ops.new_value();
let mut inputs = Vec::new();
// Then 入力
inputs.push((if_shape.then_block, then_val));
// Else 入力
if let Some(else_bb) = if_shape.else_block {
inputs.push((else_bb, else_val));
}
if trace_on {
eprintln!(
"[Phase 61-3] var={}: emit PHI dst={:?} inputs={:?}",
var_name, phi_dst, inputs
);
}
ops.emit_phi(if_shape.merge_block, phi_dst, inputs)?;
ops.update_var(var_name.clone(), phi_dst);
phi_count += 1;
}
if trace_on {
eprintln!(
"[Phase 61-3] IfInLoopPhiEmitter::emit_header_phis done: {} PHIs",
phi_count
);
}
Ok(phi_count)
}
/// Phase 61-4: ループ外 If PHI 生成Toplevel If
///
/// ループ外の純粋な if に対して PHI を生成する。
/// carrier_names が空のため、PhiSpec の全変数を対象とする。
///
/// # Arguments
///
/// * `phi_spec` - JoinIR から計算された PHI 仕様
/// * `pre_if_var_map` - if 直前の変数マップ
/// * `then_snapshot` - then 腕終了時の変数スナップショット
/// * `else_snapshot_opt` - else 腕終了時の変数スナップショット
/// * `ops` - PHI 生成操作インターフェース
/// * `if_shape` - If 構造情報
///
/// # Returns
///
/// 生成した PHI の数
pub fn emit_toplevel_phis<O: PhiBuilderOps>(
phi_spec: &PhiSpec,
pre_if_var_map: &BTreeMap<String, ValueId>,
then_snapshot: &BTreeMap<String, ValueId>,
else_snapshot_opt: Option<&BTreeMap<String, ValueId>>,
ops: &mut O,
if_shape: &IfShape,
) -> Result<usize, String> {
let mut phi_count = 0;
let trace_on = crate::config::env::joinir_dev_enabled()
&& std::env::var("HAKO_JOINIR_IF_TOPLEVEL_TRACE")
.ok()
.as_deref()
== Some("1");
if trace_on {
eprintln!("[Phase 61-4] IfInLoopPhiEmitter::emit_toplevel_phis start");
eprintln!("[Phase 61-4] header_phis: {:?}", phi_spec.header_phis);
}
// Toplevel PHI 生成: PhiSpec の全変数を対象
for var_name in &phi_spec.header_phis {
// pre_if値を取得
let pre_val = match pre_if_var_map.get(var_name) {
Some(&v) => v,
None => {
if trace_on {
eprintln!(
"[Phase 61-4] ⚠️ var={} not found in pre_if_var_map, skipping",
var_name
);
}
continue;
}
};
// Then値: snapshot から取得、なければ pre_val
let then_val = then_snapshot.get(var_name).copied().unwrap_or(pre_val);
// Else値: snapshot から取得、なければ pre_val
let else_val = else_snapshot_opt
.and_then(|m| m.get(var_name).copied())
.unwrap_or(pre_val);
// 値が同一なら PHI 不要
if then_val == else_val {
if trace_on {
eprintln!(
"[Phase 61-4] var={}: then_val == else_val ({:?}), no PHI needed",
var_name, then_val
);
}
ops.update_var(var_name.clone(), then_val);
continue;
}
// PHI 生成
let phi_dst = ops.new_value();
let mut inputs = Vec::new();
// Then 入力
inputs.push((if_shape.then_block, then_val));
// Else 入力
if let Some(else_bb) = if_shape.else_block {
inputs.push((else_bb, else_val));
}
if trace_on {
eprintln!(
"[Phase 61-4] var={}: emit PHI dst={:?} inputs={:?}",
var_name, phi_dst, inputs
);
}
ops.emit_phi(if_shape.merge_block, phi_dst, inputs)?;
ops.update_var(var_name.clone(), phi_dst);
phi_count += 1;
}
if trace_on {
eprintln!(
"[Phase 61-4] IfInLoopPhiEmitter::emit_toplevel_phis done: {} PHIs",
phi_count
);
}
Ok(phi_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::BasicBlockId;
/// モック PhiBuilderOps 実装
struct MockOps {
next_id: u32,
emitted_phis: Vec<(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)>,
var_updates: Vec<(String, ValueId)>,
}
impl MockOps {
fn new() -> Self {
Self {
next_id: 1000,
emitted_phis: Vec::new(),
var_updates: Vec::new(),
}
}
}
impl PhiBuilderOps for MockOps {
fn new_value(&mut self) -> ValueId {
let id = self.next_id;
self.next_id += 1;
ValueId(id)
}
fn emit_phi(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
self.emitted_phis.push((block, dst, inputs));
Ok(())
}
fn update_var(&mut self, name: String, value: ValueId) {
self.var_updates.push((name, value));
}
fn get_block_predecessors(&self, _block: BasicBlockId) -> Vec<BasicBlockId> {
Vec::new()
}
fn emit_void(&mut self) -> ValueId {
self.new_value()
}
fn set_current_block(&mut self, _block: BasicBlockId) -> Result<(), String> {
Ok(())
}
fn block_exists(&self, _block: BasicBlockId) -> bool {
true
}
}
#[test]
fn test_emit_header_phis_basic() {
// Setup
let mut phi_spec = PhiSpec::new();
phi_spec.header_phis.insert("x".to_string());
let mut pre_if = BTreeMap::new();
pre_if.insert("x".to_string(), ValueId(1));
let mut then_snap = BTreeMap::new();
then_snap.insert("x".to_string(), ValueId(2)); // then で変更
let else_snap = BTreeMap::new(); // else では変更なし
let mut carriers = BTreeSet::new();
carriers.insert("x".to_string());
let if_shape = IfShape {
cond_block: BasicBlockId(10),
then_block: BasicBlockId(11),
else_block: Some(BasicBlockId(12)),
merge_block: BasicBlockId(13),
};
let mut ops = MockOps::new();
// Execute
let count = IfInLoopPhiEmitter::emit_header_phis(
&phi_spec,
&pre_if,
&then_snap,
Some(&else_snap),
&carriers,
&mut ops,
&if_shape,
)
.unwrap();
// Verify
assert_eq!(count, 1);
assert_eq!(ops.emitted_phis.len(), 1);
let (block, _dst, inputs) = &ops.emitted_phis[0];
assert_eq!(*block, BasicBlockId(13)); // merge_block
assert_eq!(inputs.len(), 2);
assert_eq!(inputs[0], (BasicBlockId(11), ValueId(2))); // then: 変更値
assert_eq!(inputs[1], (BasicBlockId(12), ValueId(1))); // else: pre_val
}
#[test]
fn test_emit_header_phis_same_value() {
// 両腕で同じ値の場合、PHI不要
let mut phi_spec = PhiSpec::new();
phi_spec.header_phis.insert("x".to_string());
let mut pre_if = BTreeMap::new();
pre_if.insert("x".to_string(), ValueId(1));
let mut then_snap = BTreeMap::new();
then_snap.insert("x".to_string(), ValueId(1)); // 変更なし
let else_snap = BTreeMap::new();
let mut carriers = BTreeSet::new();
carriers.insert("x".to_string());
let if_shape = IfShape {
cond_block: BasicBlockId(10),
then_block: BasicBlockId(11),
else_block: Some(BasicBlockId(12)),
merge_block: BasicBlockId(13),
};
let mut ops = MockOps::new();
let count = IfInLoopPhiEmitter::emit_header_phis(
&phi_spec,
&pre_if,
&then_snap,
Some(&else_snap),
&carriers,
&mut ops,
&if_shape,
)
.unwrap();
assert_eq!(count, 0); // PHI不要
assert_eq!(ops.emitted_phis.len(), 0);
assert_eq!(ops.var_updates.len(), 1);
assert_eq!(ops.var_updates[0], ("x".to_string(), ValueId(1)));
}
}