Phase 33-2: JoinInst::Select implementation + minimal If JoinIR lowering

Implementation:
- Add JoinInst::Select variant to JoinIR schema
- Implement Select execution in JoinIR Runner (Bool/Int cond support)
- Add Select handling in JoinIR→MIR Bridge (4-block structure)
- Create test cases (joinir_if_select_simple/local.hako)
- Add dev toggle NYASH_JOINIR_IF_SELECT=1
- Create lowering infrastructure (if_select.rs, stub for Phase 33-3)

Tests:
- 3/3 unit tests pass (test_select_true/false/int_cond)
- Integration tests pass (RC: 0)
- A/B execution verified (existing if_phi vs JoinIR Select)

Files changed:
- New: apps/tests/joinir_if_select_{simple,local}.hako
- New: src/mir/join_ir/lowering/if_select.rs
- Modified: src/mir/join_ir/{mod,json,runner,vm_bridge}.rs
- Modified: src/config/env.rs (joinir_if_select_enabled)
- Modified: docs/reference/environment-variables.md

Phase 33-3 ready: MIR pattern recognition + auto-lowering pending

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-27 02:58:38 +09:00
parent 0c252406ef
commit 35cd93a37a
13 changed files with 642 additions and 47 deletions

View File

@ -104,6 +104,96 @@ pub enum ExitKind {
Throw,
}
// ============================================================================
// Phase 32 L-1.4: ExitGroup / ExitAnalysis出口辺のグループ化
// ============================================================================
/// 同じ target ブロックに向かう出口辺のグループ
///
/// 複数の ExitEdge が同じブロックに向かう場合、Case-A 判定では
/// これらを 1 つの「論理的な出口」として扱える。
#[derive(Debug, Clone)]
pub struct ExitGroup {
/// グループの出口先ブロック
pub target: BasicBlockId,
/// このグループに含まれる ExitEdge の ID 群
pub edges: Vec<ExitEdgeId>,
/// Break を含むかConditionFalse のみのグループと区別)
pub has_break: bool,
}
/// ループの出口辺を分析した結果
///
/// - `loop_exit_groups`: ループ外の同一ブロックへ向かう辺のグループ群
/// - `nonlocal_exits`: Return/Throw など、ループ外への非ローカル出口
#[derive(Debug, Clone)]
pub struct ExitAnalysis {
/// ループ外への出口グループtarget ブロック単位)
pub loop_exit_groups: Vec<ExitGroup>,
/// Return/Throw など、関数全体を抜ける出口
pub nonlocal_exits: Vec<ExitEdgeId>,
}
impl ExitAnalysis {
/// Case-A 判定: ループ外出口が 1 グループのみで、非ローカル出口がない
pub fn is_single_exit_group(&self) -> bool {
self.loop_exit_groups.len() == 1 && self.nonlocal_exits.is_empty()
}
/// 唯一のループ外出口先ブロックCase-A の場合のみ有効)
pub fn single_exit_target(&self) -> Option<BasicBlockId> {
if self.is_single_exit_group() {
self.loop_exit_groups.first().map(|g| g.target)
} else {
None
}
}
}
/// 出口辺リストを分析して ExitAnalysis を生成
///
/// # Arguments
/// * `exits` - ExitEdge のリスト
///
/// # Returns
/// * `ExitAnalysis` - グループ化された出口情報
pub fn analyze_exits(exits: &[ExitEdge]) -> ExitAnalysis {
use std::collections::BTreeMap;
// target ブロック → (辺ID群, has_break)
let mut groups: BTreeMap<BasicBlockId, (Vec<ExitEdgeId>, bool)> = BTreeMap::new();
let mut nonlocal: Vec<ExitEdgeId> = Vec::new();
for edge in exits {
match &edge.kind {
ExitKind::ConditionFalse | ExitKind::Break { .. } => {
let entry = groups.entry(edge.to).or_insert_with(|| (Vec::new(), false));
entry.0.push(edge.id);
if matches!(&edge.kind, ExitKind::Break { .. }) {
entry.1 = true;
}
}
ExitKind::Return | ExitKind::Throw => {
nonlocal.push(edge.id);
}
}
}
let loop_exit_groups: Vec<ExitGroup> = groups
.into_iter()
.map(|(target, (edges, has_break))| ExitGroup {
target,
edges,
has_break,
})
.collect();
ExitAnalysis {
loop_exit_groups,
nonlocal_exits: nonlocal,
}
}
/// continue 辺を表す構造体
#[derive(Debug, Clone)]
pub struct ContinueEdge {