feat(joinir): Phase 61-4 loop-outside If JoinIR infrastructure

Phase 61-4 ループ外 If の JoinIR 基盤実装:

1. env フラグ追加:
   - HAKO_JOINIR_IF_TOPLEVEL: 本番経路有効化
   - HAKO_JOINIR_IF_TOPLEVEL_DRYRUN: dry-run モード

2. IfPhiContext 拡張:
   - pure_if() コンストラクタ追加(ループ外 if 用)

3. if_form.rs 統合:
   - JoinIR 試行コード追加(dry-run対応)
   - フォールバック経路維持(既存PHI生成)

現状:
- dry-run モード動作確認済み
- 関数名ガードにより main 関数はスキップ
- 本番経路は未実装(関数名ガード拡張が必要)

🤖 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:08:23 +09:00
parent 6b38d8ee97
commit b73413566d
3 changed files with 153 additions and 41 deletions

View File

@ -294,6 +294,33 @@ pub fn joinir_if_in_loop_enable() -> bool {
env_bool("HAKO_JOINIR_IF_IN_LOOP_ENABLE")
}
/// Phase 61-4: ループ外If JoinIR経路有効化
///
/// `HAKO_JOINIR_IF_TOPLEVEL=1` でループ外IfのJoinIR経路を有効化
///
/// 動作:
/// - ON: try_lower_if_to_joinir経路if_form.rsで使用
/// - OFF: PhiBuilderBox経路既存
///
/// 前提条件:
/// - HAKO_JOINIR_IF_SELECT=1Phase 33基盤
///
/// デフォルト: OFF安全第一
pub fn joinir_if_toplevel_enabled() -> bool {
env_bool("HAKO_JOINIR_IF_TOPLEVEL")
}
/// Phase 61-4: ループ外If JoinIR dry-run有効化
///
/// `HAKO_JOINIR_IF_TOPLEVEL_DRYRUN=1` でdry-runモードを有効化
///
/// dry-runモード:
/// - JoinIR経路を試行しログ出力
/// - 実際のPHI生成は既存経路を使用安全
pub fn joinir_if_toplevel_dryrun_enabled() -> bool {
env_bool("HAKO_JOINIR_IF_TOPLEVEL_DRYRUN")
}
// VM legacy by-name call fallback was removed (Phase 2 complete).
// Phase 40-4.1: use_joinir_for_array_filter() removed (Route B now default).

View File

@ -1,6 +1,7 @@
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).
@ -134,28 +135,92 @@ impl MirBuilder {
let assigned_else_pre: Option<String> = None;
let pre_then_var_value: Option<ValueId> = None;
let result_val = 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,
)?;
// 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;
if joinir_enabled && (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();
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: {:?}",
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()
);
}
// 本番経路が有効な場合のみ success = true
if joinir_toplevel {
joinir_success = true;
// TODO: Phase 61-4 本番経路のPHI生成
// 現時点では dry-run 的動作(フォールバック経路を使用)
if joinir_dryrun {
eprintln!("[Phase 61-4] ⚠️ Production path not yet implemented, using fallback");
}
joinir_success = false; // フォールバック使用
}
}
None => {
if joinir_dryrun {
eprintln!("[Phase 61-4] ⏭️ JoinIR pattern not matched, using fallback");
}
}
}
}
}
// 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
@ -177,25 +242,27 @@ impl MirBuilder {
}
// Merge other modified variables (skip the primary assignment if any)
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,
)?;
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

View File

@ -70,6 +70,24 @@ impl IfPhiContext {
}
}
/// ループ外 if 用コンテキスト作成Phase 61-4
///
/// ループ外の純粋な if に対しては、carrier_names は空になる。
/// これにより、JoinIR は純粋な Select/IfMerge パターンとして処理する。
///
/// # Example
///
/// ```ignore
/// let context = IfPhiContext::pure_if();
/// try_lower_if_to_joinir(func, block_id, debug, Some(&context));
/// ```
pub fn pure_if() -> Self {
Self {
in_loop_body: false,
carrier_names: BTreeSet::new(),
}
}
/// 指定された変数がループキャリアかどうか判定
///
/// # Arguments