Files
hakorune/src/mir/control_form.rs
nyash-codex 35cd93a37a 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>
2025-11-27 02:58:38 +09:00

529 lines
18 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.

/*!
* ControlForm 共通制御構造ビューLoop / If の箱化レイヤ)
*
* 目的:
* - LoopForm v2ループと If 降下を、1段上の「制御構造の形」として統一的に眺めるための薄いレイヤだよ。
* - Conservative PHI Box や将来の可視化/検証ロジックが、Loop 専用 / If 専用に分かれず、
* ControlForm という SSOT から情報を取れるようにするのがねらいだよ。
*
* このモジュール自体は構造定義とデバッグ用のユーティリティのみを提供し、
* 既存の LoopBuilder / If 降下の挙動は変えないよPhase 25.1f では観測レイヤ専用)。
*/
use crate::mir::{BasicBlock, BasicBlockId, MirFunction};
use std::collections::BTreeSet;
// ============================================================================
// Phase 32: 新しい ID 型LoopRegion / LoopControlShape 用)
// ============================================================================
/// ループを一意に識別する ID
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct LoopId(pub u32);
/// 出口辺を一意に識別する ID
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ExitEdgeId(pub u32);
/// continue 辺を一意に識別する ID
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ContinueEdgeId(pub u32);
/// ループラベル(将来の labeled break/continue 用)
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct LoopLabel(pub String);
// ============================================================================
// Phase 32: LoopRegion - ブロック集合 + ネスト構造
// ============================================================================
/// ループ領域を表す箱
///
/// LoopShape の「形だけ」から拡張して、ブロック集合とネスト関係を持つ。
#[derive(Debug, Clone)]
pub struct LoopRegion {
/// このループの ID
pub id: LoopId,
/// ループ直前のブロック
pub preheader: BasicBlockId,
/// ループヘッダ(条件判定)
pub header: BasicBlockId,
/// ラッチブロック群通常は1つだが、複数の場合もある
pub latches: Vec<BasicBlockId>,
/// ループ内の全ブロック集合
pub blocks: BTreeSet<BasicBlockId>,
/// 親ループの IDネストの外側
pub parent: Option<LoopId>,
/// 子ループの ID 群(ネストの内側)
pub children: Vec<LoopId>,
}
// ============================================================================
// Phase 32: LoopControlShape - 制御フロー辺
// ============================================================================
/// ループの制御フロー辺を表す箱
#[derive(Debug, Clone)]
pub struct LoopControlShape {
/// このループの ID
pub loop_id: LoopId,
/// continue 辺の ID 群
pub continues: Vec<ContinueEdgeId>,
/// 出口辺の ID 群
pub exits: Vec<ExitEdgeId>,
}
/// 出口辺を表す構造体
#[derive(Debug, Clone)]
pub struct ExitEdge {
/// この辺の ID
pub id: ExitEdgeId,
/// どのループからの出口か(冗長だけど便利)
pub loop_id: LoopId,
/// 出発ブロック
pub from: BasicBlockId,
/// 到着ブロック
pub to: BasicBlockId,
/// 出口の種類
pub kind: ExitKind,
}
/// 出口辺の種類
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ExitKind {
/// while(cond) の cond が false
ConditionFalse,
/// break 文
Break {
/// labeled break 用(将来拡張)
label: Option<LoopLabel>,
},
/// ループ内 return
Return,
/// throw / panic 相当
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 {
/// この辺の ID
pub id: ContinueEdgeId,
/// どのループの continue か
pub loop_id: LoopId,
/// 出発ブロック
pub from: BasicBlockId,
/// 到着ブロック(通常は latch または header
pub to: BasicBlockId,
/// continue の種類
pub kind: ContinueKind,
}
/// continue 辺の種類
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ContinueKind {
/// 通常の continue
Normal,
// 将来: Labeled { label: LoopLabel },
}
/// ループ構造の形だけを表す箱だよ。
///
/// - `preheader` : ループ直前のブロック(キャリア/ピン変数のコピー元)
/// - `header` : ループヘッダ(条件判定と header PHI が置かれる)
/// - `body` : 代表的なループ本体ブロック(最初の body など)
/// - `latch` : ヘッダへ戻るバックエッジを張るブロック
/// - `exit` : ループを抜けた先のブロック
/// - `continue_targets` : continue 文を含み、`header` へ遷移するブロック群(エッジの「出発点」)
/// - `break_targets` : break 文を含み、`exit` へ遷移するブロック群(エッジの「出発点」)
#[derive(Debug, Clone)]
pub struct LoopShape {
pub preheader: BasicBlockId,
pub header: BasicBlockId,
pub body: BasicBlockId,
pub latch: BasicBlockId,
pub exit: BasicBlockId,
pub continue_targets: Vec<BasicBlockId>,
pub break_targets: Vec<BasicBlockId>,
}
/// if/else 構造の形だけを表す箱だよ。
///
/// - `cond_block` : 条件式を評価するブロック
/// - `then_block` : then ブランチの先頭ブロック
/// - `else_block` : else ブランチの先頭ブロック(無ければ None
/// - `merge_block`: then/else の合流ブロック
#[derive(Debug, Clone)]
pub struct IfShape {
pub cond_block: BasicBlockId,
pub then_block: BasicBlockId,
pub else_block: Option<BasicBlockId>,
pub merge_block: BasicBlockId,
}
/// 制御構造の種別だよ。
#[derive(Debug, Clone)]
pub enum ControlKind {
Loop(LoopShape),
If(IfShape),
}
/// ループ / if / 将来の switch などを、共通のビューとして扱う箱だよ。
///
/// - `entry` : 構造に入る入口ブロック
/// - `exits` : 構造を抜けたあとのブロック群
/// - `kind` : Loop / If などの種別ごとの Shape
#[derive(Debug, Clone)]
pub struct ControlForm {
pub entry: BasicBlockId,
pub exits: Vec<BasicBlockId>,
pub kind: ControlKind,
}
impl ControlForm {
/// ループ用 Shape から ControlForm を生成するよ。
///
/// ループの entry は preheader、exit は exit ブロック 1 つとみなす。
pub fn from_loop(shape: LoopShape) -> Self {
ControlForm {
entry: shape.preheader,
exits: vec![shape.exit],
kind: ControlKind::Loop(shape),
}
}
/// If 用 Shape から ControlForm を生成するよ。
///
/// If の entry は cond_block、exit は merge_block 1 つとみなす。
pub fn from_if(shape: IfShape) -> Self {
ControlForm {
entry: shape.cond_block,
exits: vec![shape.merge_block],
kind: ControlKind::If(shape),
}
}
/// これはループかな?という軽い判定だよ。
pub fn is_loop(&self) -> bool {
matches!(self.kind, ControlKind::Loop(_))
}
/// これは if 構造かな?という軽い判定だよ。
pub fn is_if(&self) -> bool {
matches!(self.kind, ControlKind::If(_))
}
/// デバッグ用に構造をダンプするよ。
///
/// 呼び出し側で `NYASH_CONTROL_FORM_TRACE=1` を見る想定なので、
/// ここでは単純に eprintln! するだけにしておく。
pub fn debug_dump(&self) {
match &self.kind {
ControlKind::Loop(shape) => {
eprintln!(
"[ControlForm::Loop] entry={:?} preheader={:?} header={:?} body={:?} latch={:?} exit={:?} continue={:?} break={:?}",
self.entry,
shape.preheader,
shape.header,
shape.body,
shape.latch,
shape.exit,
shape.continue_targets,
shape.break_targets,
);
}
ControlKind::If(shape) => {
eprintln!(
"[ControlForm::If] entry={:?} cond={:?} then={:?} else={:?} merge={:?} exits={:?}",
self.entry,
shape.cond_block,
shape.then_block,
shape.else_block,
shape.merge_block,
self.exits,
);
}
}
}
}
/// ControlForm の invariant を軽く検査するための CFG 抽象だよ。
///
/// 実装は MirFunction などに持たせて、`debug_validate` から使う想定。
pub trait CfgLike {
fn has_edge(&self, from: BasicBlockId, to: BasicBlockId) -> bool;
fn predecessors_len(&self, block: BasicBlockId) -> usize;
}
impl CfgLike for MirFunction {
fn has_edge(&self, from: BasicBlockId, to: BasicBlockId) -> bool {
self.blocks
.get(&from)
.map(|bb: &BasicBlock| bb.successors.contains(&to))
.unwrap_or(false)
}
fn predecessors_len(&self, block: BasicBlockId) -> usize {
self.blocks
.get(&block)
.map(|bb: &BasicBlock| bb.predecessors.len())
.unwrap_or(0)
}
}
/// ControlForm トレース用の環境フラグを判定するヘルパーだよ。
///
/// - 未設定 → 既定で ON
/// - "0" / "false" → OFF
/// - それ以外 → ON
pub fn is_control_form_trace_on() -> bool {
std::env::var("NYASH_CONTROL_FORM_TRACE")
.map(|v| v != "0" && v.to_lowercase() != "false")
.unwrap_or(true)
}
impl LoopShape {
// ========================================================================
// Phase 32: View メソッド(段階移行用)
// ========================================================================
/// LoopRegion ビューを生成するよ。
///
/// 既存の LoopShape から LoopRegion 形式に変換する。
/// blocks 集合は呼び出し側が後から設定するか、空のままでもOK。
pub fn to_region_view(&self, loop_id: LoopId) -> LoopRegion {
LoopRegion {
id: loop_id,
preheader: self.preheader,
header: self.header,
latches: vec![self.latch], // 既存 LoopShape は latch 1つ
blocks: BTreeSet::new(), // 呼び出し側で設定可能
parent: None, // ネスト情報は後から設定
children: vec![],
}
}
/// LoopControlShape ビューを生成するよ。
///
/// 既存の LoopShape から LoopControlShape 形式に変換する。
/// 辺の ID は呼び出し側で管理するため、ここでは ID リストのみ返す。
pub fn to_control_view(&self, loop_id: LoopId) -> LoopControlShape {
// 辺の数だけ ID を振る0始まり、呼び出し側でオフセット可能
let continues: Vec<ContinueEdgeId> = self
.continue_targets
.iter()
.enumerate()
.map(|(i, _)| ContinueEdgeId(i as u32))
.collect();
let exits: Vec<ExitEdgeId> = self
.break_targets
.iter()
.enumerate()
.map(|(i, _)| ExitEdgeId(i as u32))
.collect();
LoopControlShape {
loop_id,
continues,
exits,
}
}
/// 出口辺を生成するよ。
///
/// break_targets の情報から ExitEdge 群を生成する。
pub fn to_exit_edges(&self, loop_id: LoopId) -> Vec<ExitEdge> {
self.break_targets
.iter()
.enumerate()
.map(|(i, &from)| ExitEdge {
id: ExitEdgeId(i as u32),
loop_id,
from,
to: self.exit,
kind: ExitKind::Break { label: None },
})
.collect()
}
/// continue 辺を生成するよ。
///
/// continue_targets の情報から ContinueEdge 群を生成する。
pub fn to_continue_edges(&self, loop_id: LoopId) -> Vec<ContinueEdge> {
self.continue_targets
.iter()
.enumerate()
.map(|(i, &from)| ContinueEdge {
id: ContinueEdgeId(i as u32),
loop_id,
from,
to: self.header, // continue は header に戻る
kind: ContinueKind::Normal,
})
.collect()
}
/// Debug ビルドでだけ呼ぶ用の簡易 invariant チェックだよ。
///
/// - preheader → header にエッジがあること
/// - latch → header にバックエッジがあること
/// - continue_targets の各ブロックから header へのエッジがあること
/// - break_targets の各ブロックから exit へのエッジがあること
#[cfg(debug_assertions)]
pub fn debug_validate<C: CfgLike>(&self, cfg: &C) {
debug_assert!(
cfg.has_edge(self.preheader, self.header),
"LoopShape invalid: preheader -> header edge missing: {:?} -> {:?}",
self.preheader,
self.header
);
debug_assert!(
cfg.has_edge(self.latch, self.header),
"LoopShape invalid: latch -> header backedge missing: {:?} -> {:?}",
self.latch,
self.header
);
for ct in &self.continue_targets {
debug_assert!(
cfg.has_edge(*ct, self.header),
"LoopShape invalid: continue source block {:?} does not branch to header {:?}",
ct,
self.header
);
}
for bt in &self.break_targets {
debug_assert!(
cfg.has_edge(*bt, self.exit),
"LoopShape invalid: break source block {:?} does not branch to exit {:?}",
bt,
self.exit
);
}
}
}
impl IfShape {
/// Debug ビルドでだけ呼ぶ用の簡易 invariant チェックだよ。
///
/// - cond → then / else にエッジがあること
/// - merge については、predecessor 情報がまだ配線途中のケースもあるので
/// ここでは「0 ならログだけ出すpanic しない)」ことにするよ。
#[cfg(debug_assertions)]
pub fn debug_validate<C: CfgLike>(&self, cfg: &C) {
debug_assert!(
cfg.has_edge(self.cond_block, self.then_block),
"IfShape invalid: cond -> then edge missing: {:?} -> {:?}",
self.cond_block,
self.then_block
);
if let Some(else_blk) = self.else_block {
debug_assert!(
cfg.has_edge(self.cond_block, else_blk),
"IfShape invalid: cond -> else edge missing: {:?} -> {:?}",
self.cond_block,
else_blk
);
}
let preds = cfg.predecessors_len(self.merge_block);
if preds == 0 && std::env::var("NYASH_CONTROL_FORM_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[ControlForm::IfShape] WARN: merge block {:?} has no predecessors yet",
self.merge_block
);
}
}
}