Files
hakorune/src/mir/builder/if_form.rs
nyash-codex 3194cc1e6c feat(joinir): Phase 61-4-F ToplevelOps and production path integration
Phase 61-4-F: Loop-outside If JoinIR production path

Changes:
- F.1: Add ToplevelOps struct implementing PhiBuilderOps for MirBuilder
  - Enables emit_toplevel_phis() to emit PHI instructions via MirBuilder
  - Uses insert_phi_at_head_spanned for proper PHI placement
  - ~70 lines, thin wrapper following box theory

- F.2: Integrate production path with emit_toplevel_phis
  - Replace TODO with actual PHI emission call
  - Build IfShape from branch blocks
  - Log PHI count on dry-run

- Add IfToplevelTest.* to try_lower_if_to_joinir allowed list
  - Fixes function name guard that blocked testing

Note: Pattern matching currently only supports return patterns
(IfMerge/IfSelect). Local variable assignment patterns fall back
to existing PHI generation, which correctly produces valid MIR.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 15:32:40 +09:00

373 lines
15 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.

use super::{MirBuilder, ValueId};
use crate::ast::ASTNode;
use crate::mir::control_form::IfShape;
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
use crate::mir::phi_core::phi_builder_box::PhiBuilderOps;
use crate::mir::{BasicBlockId, ConstValue, MirInstruction};
/// Phase 61-4-F: MirBuilder 用 PhiBuilderOps 実装
///
/// ループ外 if の JoinIR 経路で emit_toplevel_phis() を呼ぶためのラッパー。
struct ToplevelOps<'a>(&'a mut MirBuilder);
impl<'a> PhiBuilderOps for ToplevelOps<'a> {
fn new_value(&mut self) -> ValueId {
self.0.next_value_id()
}
fn emit_phi(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
// merge ブロックの先頭に PHI を挿入
if let (Some(func), Some(_cur_bb)) = (self.0.current_function.as_mut(), self.0.current_block)
{
crate::mir::ssot::cf_common::insert_phi_at_head_spanned(
func,
block,
dst,
inputs,
self.0.current_span,
);
} else {
self.0.emit_instruction(MirInstruction::Phi { dst, inputs })?;
}
Ok(())
}
fn update_var(&mut self, name: String, value: ValueId) {
self.0.variable_map.insert(name, value);
}
fn get_block_predecessors(&self, block: BasicBlockId) -> Vec<BasicBlockId> {
if let Some(ref func) = self.0.current_function {
func.blocks
.get(&block)
.map(|bb| bb.predecessors.iter().copied().collect())
.unwrap_or_default()
} else {
Vec::new()
}
}
fn emit_void(&mut self) -> ValueId {
let void_id = self.0.next_value_id();
let _ = self.0.emit_instruction(MirInstruction::Const {
dst: void_id,
value: ConstValue::Void,
});
void_id
}
fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> {
self.0.current_block = Some(block);
Ok(())
}
fn block_exists(&self, block: BasicBlockId) -> bool {
if let Some(ref func) = self.0.current_function {
func.blocks.contains_key(&block)
} else {
false
}
}
}
impl MirBuilder {
/// Lower an if/else using a structured IfForm (header→then/else→merge).
/// PHI-off: edge-copy only on predecessors; PHI-on: Phi at merge.
pub(super) fn lower_if_form(
&mut self,
condition: ASTNode,
then_branch: ASTNode,
else_branch: Option<ASTNode>,
) -> Result<ValueId, String> {
// Reserve a deterministic join id for debug region labeling
let join_id = self.debug_next_join_id();
// Pre-pin heuristic was deprecated; keep operands as-is for predictability.
let condition_val = self.build_expression(condition)?;
let condition_val = self.local_cond(condition_val);
// Create blocks
let then_block = self.block_gen.next();
let else_block = self.block_gen.next();
let merge_block = self.block_gen.next();
// Branch
let pre_branch_bb = self.current_block()?;
let mut condition_val = condition_val;
crate::mir::builder::ssa::local::finalize_branch_cond(self, &mut condition_val);
crate::mir::builder::emission::branch::emit_conditional(
self,
condition_val,
then_block,
else_block,
)?;
// Snapshot variables before entering branches
let pre_if_var_map = self.variable_map.clone();
let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1");
// then
self.start_new_block(then_block)?;
// Debug region: join then-branch
self.debug_push_region(format!("join#{}", join_id) + "/then");
// Scope enter for then-branch
self.hint_scope_enter(0);
let then_ast_for_analysis = then_branch.clone();
self.variable_map = pre_if_var_map.clone();
// Materialize all variables at block entry via single-pred Phi (correctness-first)
for (name, &pre_v) in pre_if_var_map.iter() {
let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?;
self.variable_map.insert(name.clone(), phi_val);
if trace_if {
eprintln!(
"[if-trace] then-entry phi var={} pre={:?} -> dst={:?}",
name, pre_v, phi_val
);
}
}
let then_value_raw = self.build_expression(then_branch)?;
let then_exit_block = self.current_block()?;
let then_reaches_merge = !self.is_current_block_terminated();
let then_var_map_end = self.variable_map.clone();
if then_reaches_merge {
// Scope leave for then-branch
self.hint_scope_leave(0);
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
}
// Pop then-branch debug region
self.debug_pop_region();
// else
self.start_new_block(else_block)?;
// Debug region: join else-branch
self.debug_push_region(format!("join#{}", join_id) + "/else");
// Scope enter for else-branch
self.hint_scope_enter(0);
let (else_value_raw, else_ast_for_analysis, else_var_map_end_opt) =
if let Some(else_ast) = else_branch {
// Reset variable_map BEFORE materializing PHI nodes (same pattern as then-branch)
self.variable_map = pre_if_var_map.clone();
// Materialize all variables at block entry via single-pred Phi (correctness-first)
for (name, &pre_v) in pre_if_var_map.iter() {
let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?;
self.variable_map.insert(name.clone(), phi_val);
if trace_if {
eprintln!(
"[if-trace] else-entry phi var={} pre={:?} -> dst={:?}",
name, pre_v, phi_val
);
}
}
let val = self.build_expression(else_ast.clone())?;
(val, Some(else_ast), Some(self.variable_map.clone()))
} else {
// No else branch: materialize PHI nodes for the empty else block
self.variable_map = pre_if_var_map.clone();
for (name, &pre_v) in pre_if_var_map.iter() {
let phi_val = self.insert_phi_single(pre_branch_bb, pre_v)?;
self.variable_map.insert(name.clone(), phi_val);
if trace_if {
eprintln!(
"[if-trace] else-entry phi var={} pre={:?} -> dst={:?}",
name, pre_v, phi_val
);
}
}
let void_val = crate::mir::builder::emission::constant::emit_void(self);
// Phase 25.1c/k: Pass PHI-renamed variable_map for empty else branch
// This ensures merge_modified_vars uses correct ValueIds after PHI renaming
(void_val, None, Some(self.variable_map.clone()))
};
let else_exit_block = self.current_block()?;
let else_reaches_merge = !self.is_current_block_terminated();
if else_reaches_merge {
// Scope leave for else-branch
self.hint_scope_leave(0);
crate::mir::builder::emission::branch::emit_jump(self, merge_block)?;
}
// Pop else-branch debug region
self.debug_pop_region();
// merge: primary result via helper, then delta-based variable merges
// Ensure PHIs are first in the block by suppressing entry pin copies here
self.suppress_next_entry_pin_copy();
self.start_new_block(merge_block)?;
// Debug region: join merge
self.debug_push_region(format!("join#{}", join_id) + "/join");
self.push_if_merge(merge_block);
// Phase 38: Pre-analysis hints removed (JoinIR AST lowering handles assignment detection)
let assigned_then_pre: Option<String> = None;
let assigned_else_pre: Option<String> = None;
let pre_then_var_value: Option<ValueId> = None;
// Phase 61-4: JoinIR 経路試行(ループ外 If
let joinir_enabled = crate::config::env::joinir_if_select_enabled();
let joinir_toplevel = crate::config::env::joinir_if_toplevel_enabled();
let joinir_dryrun = crate::config::env::joinir_if_toplevel_dryrun_enabled();
let mut joinir_success = false;
// 関数名ガードチェック
let func_name = self
.current_function
.as_ref()
.map(|f| f.signature.name.as_str())
.unwrap_or("");
let is_target = crate::mir::join_ir::lowering::is_joinir_if_toplevel_target(func_name);
if joinir_enabled && is_target && (joinir_toplevel || joinir_dryrun) {
if let Some(ref func) = self.current_function {
let context =
crate::mir::join_ir::lowering::if_phi_context::IfPhiContext::pure_if();
match crate::mir::join_ir::lowering::try_lower_if_to_joinir(
func,
pre_branch_bb,
false,
Some(&context),
) {
Some(join_inst) => {
if joinir_dryrun || joinir_toplevel {
eprintln!(
"[Phase 61-4] ✅ Toplevel If lowered via JoinIR ({}): {:?}",
func_name, join_inst
);
}
// PhiSpec 計算
let phi_spec = crate::mir::join_ir::lowering::if_phi_spec::compute_phi_spec_from_joinir(&context, &join_inst);
if joinir_dryrun {
eprintln!(
"[Phase 61-4] 🔍 dry-run: JoinIR PhiSpec header={}, exit={}",
phi_spec.header_count(),
phi_spec.exit_count()
);
}
// Phase 61-4-F: 本番経路 - emit_toplevel_phis でPHI生成
if joinir_toplevel {
// IfShape 構築
let if_shape = IfShape {
cond_block: pre_branch_bb,
then_block,
else_block: Some(else_block),
merge_block,
};
// PHI 生成
let phi_count = {
let mut ops = ToplevelOps(self);
crate::mir::loop_builder::IfInLoopPhiEmitter::emit_toplevel_phis(
&phi_spec,
&pre_if_var_map,
&then_var_map_end,
else_var_map_end_opt.as_ref(),
&mut ops,
&if_shape,
)?
};
if joinir_dryrun {
eprintln!(
"[Phase 61-4] ✅ Production path: {} PHIs generated via JoinIR",
phi_count
);
}
joinir_success = true;
}
}
None => {
if joinir_dryrun {
eprintln!("[Phase 61-4] ⏭️ JoinIR pattern not matched for {}, using fallback", func_name);
}
}
}
}
}
// Phase 61-4: JoinIR成功時はスキップ、失敗時は既存経路
let result_val = if joinir_success {
// JoinIR 経路(未実装 - フォールバック)
crate::mir::builder::emission::constant::emit_void(self)
} else {
self.normalize_if_else_phi(
then_block,
else_block,
if then_reaches_merge {
Some(then_exit_block)
} else {
None
},
if else_reaches_merge {
Some(else_exit_block)
} else {
None
},
then_value_raw,
else_value_raw,
&pre_if_var_map,
&then_ast_for_analysis,
&else_ast_for_analysis,
&then_var_map_end,
&else_var_map_end_opt,
pre_then_var_value,
)?
};
// Hint: join result variable(s)
// 1) Primary: if both branches assign to the same variable name, emit a hint for that name
if let (Some(tn), Some(en)) = (assigned_then_pre.as_deref(), assigned_else_pre.as_deref()) {
if tn == en {
self.hint_join_result(tn);
}
}
// 2) Secondary: if both branches assign multiple variables, hint全件制限なし
if let Some(ref else_map_end) = else_var_map_end_opt {
for name in then_var_map_end.keys() {
if Some(name.as_str()) == assigned_then_pre.as_deref() {
continue;
}
if else_map_end.contains_key(name) {
self.hint_join_result(name.as_str());
}
}
}
// Merge other modified variables (skip the primary assignment if any)
if !joinir_success {
let skip_name = assigned_then_pre.as_deref();
self.merge_modified_vars(
then_block,
else_block,
if then_reaches_merge {
Some(then_exit_block)
} else {
None
},
if else_reaches_merge {
Some(else_exit_block)
} else {
None
},
&pre_if_var_map,
&then_var_map_end,
&else_var_map_end_opt,
skip_name,
)?;
}
self.pop_if_merge();
// Pop merge debug region
self.debug_pop_region();
Ok(result_val)
}
}