Files
hakorune/src/mir/join_ir/lowering/mod.rs
nyash-codex 68615e72fb feat(joinir): Phase 61-2 If-in-loop JoinIR dry-run検証インフラ実装
## 実装内容

### 61-2.1: dry-runフラグ追加
- `src/config/env.rs`: joinir_if_in_loop_dryrun_enabled() 追加 (+11行)
- `HAKO_JOINIR_IF_IN_LOOP_DRYRUN=1` でdry-runモード有効化

### 61-2.2: loop_builder.rs dry-run統合
- `src/mir/loop_builder.rs`: JoinIR PhiSpec計算とA/B比較実装 (+47行)
- JoinInst取得時にPhiSpec保存、PhiBuilderBox実行後に比較

### 61-2.3: PhiSpec計算ロジック実装
- `src/mir/join_ir/lowering/if_phi_spec.rs`: 新規作成 (+203行)
  - PhiSpec構造体(header_phis/exit_phis)
  - compute_phi_spec_from_joinir(): JoinInstからPHI仕様計算
  - extract_phi_spec_from_builder(): PhiBuilderBox結果抽出
  - compare_and_log_phi_specs(): A/B比較とログ出力
- BTreeMap/BTreeSet使用(決定的イテレーション保証)

### 61-2.4: A/B比較テスト実装
- `src/tests/phase61_if_in_loop_dryrun.rs`: 新規作成 (+49行)
  - phase61_2_dry_run_flag_available: フラグ動作確認
  - phase61_2_phi_spec_creation: PhiSpec構造体テスト
- テスト結果:  2/2 PASS

## テスト結果

- Phase 61-2新規テスト:  2/2 PASS
- 既存loopformテスト:  14/14 PASS(退行なし)
- ビルド:  成功(エラー0件)

## コード変更量

+312行(env.rs: +11, if_phi_spec.rs: +203, loop_builder.rs: +47, tests: +49, その他: +2)

## 技術的成果

1. PhiSpec構造体完成(JoinIR/PhiBuilderBox統一表現)
2. dry-run検証インフラ(本番動作に影響なし)
3. BTreeMap統一(Option C知見活用)

## 次のステップ(Phase 61-3)

- dry-run → 本番経路への昇格
- PhiBuilderBox If側メソッド削除(-226行)
- JoinIR経路のみでif-in-loop PHI生成

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 12:26:02 +09:00

246 lines
9.3 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.

//! JoinIR Lowering Functions
//!
//! Phase 27.9: Modular separation of MIR → JoinIR lowering implementations.
//!
//! このモジュールは各種 MIR 関数を JoinIR に変換する lowering 関数を提供します。
//!
//! ## 構成:
//! - `common.rs`: CFG sanity checks と lowering 共通ユーティリティPhase 27.10
//! - `value_id_ranges.rs`: ValueId 範囲管理Phase 27.13+
//! - `min_loop.rs`: JoinIrMin.main/0 専用の最小ループ lowering
//! - `skip_ws.rs`: Main.skip/1 の空白スキップ lowering手書き版MIR自動解析版
//! - `funcscanner_trim.rs`: FuncScannerBox.trim/1 の trim lowering
//! - `stage1_using_resolver.rs`: Stage1UsingResolverBox.resolve_for_source entries loop loweringPhase 27.12
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 loweringPhase 27.14
//! - `if_select.rs`: Phase 33 If/Else → Select lowering
//! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版)
pub mod common;
pub mod exit_args_resolver;
pub mod funcscanner_append_defs;
pub mod funcscanner_trim;
pub mod generic_case_a;
pub mod if_dry_runner; // Phase 33-10.0
pub mod if_merge; // Phase 33-7
pub mod if_phi_context; // Phase 61-1
pub mod if_phi_spec; // Phase 61-2
pub mod if_select; // Phase 33
pub mod loop_form_intake;
pub mod loop_scope_shape;
pub mod loop_to_join;
pub mod min_loop;
pub mod skip_ws;
pub mod stage1_using_resolver;
pub mod stageb_body;
pub mod stageb_funcscanner;
pub mod value_id_ranges;
// Re-export public lowering functions
pub use funcscanner_append_defs::lower_funcscanner_append_defs_to_joinir;
pub use funcscanner_trim::lower_funcscanner_trim_to_joinir;
// Phase 31: LoopToJoinLowerer 統一箱
pub use loop_to_join::LoopToJoinLowerer;
// Phase 30 F-3: 旧 lower_case_a_loop_to_joinir_for_minimal_skip_ws は _with_scope に置き換え済みのため削除
pub use min_loop::lower_min_loop_to_joinir;
pub use skip_ws::lower_skip_ws_to_joinir;
pub use stage1_using_resolver::lower_stage1_usingresolver_to_joinir;
pub use stageb_body::lower_stageb_body_to_joinir;
pub use stageb_funcscanner::lower_stageb_funcscanner_to_joinir;
// Phase 33: If/Else → Select lowering entry point
use crate::mir::join_ir::JoinInst;
use crate::mir::{BasicBlockId, MirFunction};
/// Phase 33-9.1: Loop lowering対象関数の判定
///
/// これらの関数は Phase 32/33 で LoopToJoinLowerer によって処理されます。
/// If lowering (Select/IfMerge) の対象から除外することで、Loop/If の責務を明確に分離します。
///
/// ## 対象関数6本
/// - Main.skip/1: 空白スキップループ
/// - FuncScannerBox.trim/1: 前後空白削除ループ
/// - FuncScannerBox.append_defs/2: 配列結合ループ
/// - Stage1UsingResolverBox.resolve_for_source/5: using解析ループ
/// - StageBBodyExtractorBox.build_body_src/2: Stage-B本体抽出ループ
/// - StageBFuncScannerBox.scan_all_boxes/1: Stage-B Box走査ループ
///
/// ## 将来の拡張
/// NYASH_JOINIR_LOWER_GENERIC=1 で汎用 Case-A ループにも拡張可能
pub(crate) fn is_loop_lowered_function(name: &str) -> bool {
const LOOP_LOWERED_FUNCTIONS: &[&str] = &[
"Main.skip/1",
"FuncScannerBox.trim/1",
"FuncScannerBox.append_defs/2",
"Stage1UsingResolverBox.resolve_for_source/5",
"StageBBodyExtractorBox.build_body_src/2",
"StageBFuncScannerBox.scan_all_boxes/1",
];
LOOP_LOWERED_FUNCTIONS.contains(&name)
}
/// Phase 33-7: Try to lower if/else to JoinIR Select/IfMerge instruction
///
/// Scope:
/// - Only applies to whitelisted functions:
/// - IfSelectTest.* (Phase 33-2/33-3)
/// - IfMergeTest.* (Phase 33-7)
/// - JsonShapeToMap._read_value_from_pair/1 (Phase 33-4 Stage-1)
/// - Stage1JsonScannerBox.value_start_after_key_pos/2 (Phase 33-4 Stage-B)
/// - Requires NYASH_JOINIR_IF_SELECT=1 environment variable
/// - Falls back to traditional if_phi on pattern mismatch
///
/// Pattern selection:
/// - 1 variable → Select
/// - 2+ variables → IfMerge
///
/// Phase 61-1: If-in-loop support
/// - `context` parameter: If-in-loop context (carrier_names for loop variables)
/// - None = Pure If, Some(_) = If-in-loop
///
/// Returns Some(JoinInst::Select) or Some(JoinInst::IfMerge) if pattern matched, None otherwise.
pub fn try_lower_if_to_joinir(
func: &MirFunction,
block_id: BasicBlockId,
debug: bool,
context: Option<&if_phi_context::IfPhiContext>, // Phase 61-1: If-in-loop context
) -> Option<JoinInst> {
// 1. dev トグルチェック
if !crate::config::env::joinir_if_select_enabled() {
return None;
}
// Phase 33-9.1: Loop専任関数の除外Loop/If責務分離
// Loop lowering対象関数はIf loweringの対象外にすることで、
// 1関数につき1 loweringの原則を保証します
if is_loop_lowered_function(&func.signature.name) {
return None;
}
// Phase 33-8: デバッグログレベル取得0-3
let debug_level = crate::config::env::joinir_debug_level();
let _debug = debug || debug_level >= 1;
// 2. Phase 33-8: 関数名ガード拡張(テスト + Stage-1 rollout + 明示承認)
let is_allowed =
// Test functions (always enabled)
func.signature.name.starts_with("IfSelectTest.") ||
func.signature.name.starts_with("IfSelectLocalTest.") || // Phase 33-10 test
func.signature.name.starts_with("IfMergeTest.") ||
func.signature.name.starts_with("Stage1JsonScannerTestBox.") || // Phase 33-5 test
// Stage-1 rollout (env-controlled)
(crate::config::env::joinir_stage1_enabled() &&
func.signature.name.starts_with("Stage1")) ||
// Explicit approvals (Phase 33-4で検証済み, always on)
matches!(func.signature.name.as_str(),
"JsonShapeToMap._read_value_from_pair/1" |
"Stage1JsonScannerBox.value_start_after_key_pos/2"
);
if !is_allowed {
if debug_level >= 2 {
eprintln!(
"[try_lower_if_to_joinir] skipping non-allowed function: {}",
func.signature.name
);
}
return None;
}
if debug_level >= 1 {
eprintln!(
"[try_lower_if_to_joinir] trying to lower {}",
func.signature.name
);
}
// 3. Phase 33-7: IfMerge を優先的に試行(複数変数パターン)
// IfMerge が成功すればそれを返す、失敗したら Select を試行
// Phase 61-1: context がある場合は with_context() を使用
let if_merge_lowerer = if let Some(ctx) = context {
if_merge::IfMergeLowerer::with_context(debug_level, ctx.clone())
} else {
if_merge::IfMergeLowerer::new(debug_level)
};
if if_merge_lowerer.can_lower_to_if_merge(func, block_id) {
if let Some(result) = if_merge_lowerer.lower_if_to_if_merge(func, block_id) {
if debug_level >= 1 {
eprintln!(
"[try_lower_if_to_joinir] ✅ IfMerge lowering used for {}",
func.signature.name
);
}
return Some(result);
}
}
// 4. IfMerge が失敗したら Select を試行(単一変数パターン)
// Phase 61-1: context がある場合は with_context() を使用
let if_select_lowerer = if let Some(ctx) = context {
if_select::IfSelectLowerer::with_context(debug_level, ctx.clone())
} else {
if_select::IfSelectLowerer::new(debug_level)
};
if !if_select_lowerer.can_lower_to_select(func, block_id) {
if debug_level >= 1 {
eprintln!(
"[try_lower_if_to_joinir] pattern not matched for {}",
func.signature.name
);
}
return None;
}
let result = if_select_lowerer.lower_if_to_select(func, block_id);
if result.is_some() && debug_level >= 1 {
eprintln!(
"[try_lower_if_to_joinir] ✅ Select lowering used for {}",
func.signature.name
);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
/// Phase 33-9.1: is_loop_lowered_function() の動作確認
#[test]
fn test_is_loop_lowered_function() {
// Loop 専任関数6本は true を返す
assert!(is_loop_lowered_function("Main.skip/1"));
assert!(is_loop_lowered_function("FuncScannerBox.trim/1"));
assert!(is_loop_lowered_function("FuncScannerBox.append_defs/2"));
assert!(is_loop_lowered_function(
"Stage1UsingResolverBox.resolve_for_source/5"
));
assert!(is_loop_lowered_function(
"StageBBodyExtractorBox.build_body_src/2"
));
assert!(is_loop_lowered_function(
"StageBFuncScannerBox.scan_all_boxes/1"
));
// If lowering 対象関数は false を返す
assert!(!is_loop_lowered_function("IfSelectTest.simple_return/0"));
assert!(!is_loop_lowered_function("IfMergeTest.multiple_true/0"));
assert!(!is_loop_lowered_function(
"JsonShapeToMap._read_value_from_pair/1"
));
assert!(!is_loop_lowered_function(
"Stage1JsonScannerBox.value_start_after_key_pos/2"
));
// 一般的な関数も false を返す
assert!(!is_loop_lowered_function("SomeBox.some_method/3"));
assert!(!is_loop_lowered_function("Main.main/0"));
}
}