docs(joinir): Phase 187 LoopBuilder Physical Removal - Completion Documentation

## Changes
- Added Phase 187 section to Phase 180 README documenting complete LoopBuilder deletion
- Documented all 4 tasks completion (187-1 through 187-4)
- Added architectural impact analysis before/after Phase 187
- Documented code deletion summary and archival strategy
- Explained NYASH_LEGACY_LOOPBUILDER variable status after deletion

## Key Achievements
- LoopBuilder module (8 files, ~1000+ lines) completely deleted
- IfInLoopPhiEmitter preserved in minimal new module
- JoinIR Frontend is now sole authoritative loop lowering system
- Explicit failures replace implicit fallbacks

## Architectural Impact
- Single authoritative path (JoinIR Frontend)
- No implicit fallbacks
- Future JoinIR expansion is only way forward
- Fail-Fast principle enforced at architecture level

## Related Phases
- Phase 185: Strict mode semantics unified
- Phase 186: Hard freeze with access control guard
- Phase 187: Physical module deletion (this change)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-04 23:51:49 +09:00
parent 1e350a6bc5
commit fa8a96a51b
13 changed files with 45 additions and 1747 deletions

View File

@ -0,0 +1,416 @@
//! 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)));
}
}