feat(joinir): Phase 61-4-D/E function guard and toplevel emitter

Phase 61-4 追加実装:

1. Phase 61-4-D: 関数名ガード整理
   - is_joinir_if_toplevel_target() ヘルパー追加
   - IfSelectTest.*, IfToplevelTest.*, IfMergeTest.* 対応
   - if_form.rs で関数名チェック統合

2. Phase 61-4-E: emit_toplevel_phis() 追加
   - IfInLoopPhiEmitter に toplevel 用メソッド追加
   - carrier_names 不要(PhiSpec の全変数を対象)
   - HAKO_JOINIR_IF_TOPLEVEL_TRACE でトレース可能

現状:
- dry-run モードでパターンマッチング確認可能
- 本番経路のPHI生成統合は次フェーズ(MirBuilder PHI emit 方式検討必要)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-29 15:15:22 +09:00
parent b73413566d
commit 3439de0f65
3 changed files with 147 additions and 8 deletions

View File

@ -1,7 +1,6 @@
use super::{MirBuilder, ValueId};
use crate::ast::ASTNode;
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
use std::collections::BTreeSet;
impl MirBuilder {
/// Lower an if/else using a structured IfForm (header→then/else→merge).
@ -141,10 +140,16 @@ impl MirBuilder {
let joinir_dryrun = crate::config::env::joinir_if_toplevel_dryrun_enabled();
let mut joinir_success = false;
if joinir_enabled && (joinir_toplevel || joinir_dryrun) {
// 関数名ガードチェック
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 {
// carrier_names: ループ外なので空集合pure_if()使用)
let _carrier_names: BTreeSet<String> = BTreeSet::new(); // Phase 61-4: 将来の本番経路用
let context =
crate::mir::join_ir::lowering::if_phi_context::IfPhiContext::pure_if();
@ -157,8 +162,8 @@ impl MirBuilder {
Some(join_inst) => {
if joinir_dryrun || joinir_toplevel {
eprintln!(
"[Phase 61-4] ✅ Toplevel If lowered via JoinIR: {:?}",
join_inst
"[Phase 61-4] ✅ Toplevel If lowered via JoinIR ({}): {:?}",
func_name, join_inst
);
}
@ -176,7 +181,7 @@ impl MirBuilder {
// 本番経路が有効な場合のみ success = true
if joinir_toplevel {
joinir_success = true;
// TODO: Phase 61-4 本番経路のPHI生成
// TODO: Phase 61-4-E 本番経路のPHI生成
// 現時点では dry-run 的動作(フォールバック経路を使用)
if joinir_dryrun {
eprintln!("[Phase 61-4] ⚠️ Production path not yet implemented, using fallback");
@ -186,7 +191,7 @@ impl MirBuilder {
}
None => {
if joinir_dryrun {
eprintln!("[Phase 61-4] ⏭️ JoinIR pattern not matched, using fallback");
eprintln!("[Phase 61-4] ⏭️ JoinIR pattern not matched for {}, using fallback", func_name);
}
}
}

View File

@ -79,6 +79,36 @@ pub(crate) fn is_loop_lowered_function(name: &str) -> bool {
LOOP_LOWERED_FUNCTIONS.contains(&name)
}
/// Phase 61-4: ループ外 If の JoinIR 対象関数判定
///
/// HAKO_JOINIR_IF_TOPLEVEL=1 有効時に、ループ外 if の JoinIR 経路を試行する関数。
/// 段階的に対象を拡大するため、最小限のホワイトリストから開始。
///
/// ## 対象関数
/// - IfSelectTest.*: テスト専用関数群
/// - IfToplevelTest.*: ループ外 if テスト専用Phase 61-4
/// - JsonShapeToMap._read_value_from_pair/1: Phase 33-4 で検証済み
///
/// ## 使用方法
/// if_form.rs から呼び出され、関数名がホワイトリストに含まれる場合のみ
/// JoinIR 経路を試行する。
pub fn is_joinir_if_toplevel_target(name: &str) -> bool {
// Test prefixes (always enabled)
if name.starts_with("IfSelectTest.")
|| name.starts_with("IfToplevelTest.")
|| name.starts_with("IfMergeTest.")
{
return true;
}
// Explicit approvals (Phase 33-4 verified)
matches!(
name,
"JsonShapeToMap._read_value_from_pair/1"
| "Stage1JsonScannerBox.value_start_after_key_pos/2"
)
}
/// Phase 33-7: Try to lower if/else to JoinIR Select/IfMerge instruction
///
/// Scope:

View File

@ -154,6 +154,110 @@ impl IfInLoopPhiEmitter {
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 =
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)]