refactor(joinir): Phase 27.9 - Modular separation of join_ir.rs into directory structure

Phase 27.9 で join_ir.rs (~1,336行) を以下のモジュール構造に分離:

## 新規ディレクトリ構造:
```
src/mir/join_ir/
├── mod.rs                           # 型定義・共通ユーティリティ (~330行)
└── lowering/
    ├── mod.rs                       # lowering インターフェース
    ├── min_loop.rs                  # lower_min_loop_to_joinir (~140行)
    ├── skip_ws.rs                   # skip_ws lowering 3関数 (~390行)
    └── funcscanner_trim.rs          # trim lowering (~480行)
```

## 技術的変更:
- **型定義統一**: JoinFuncId, JoinInst, JoinModule 等を mod.rs に集約
- **lowering 分離**: 3つの lowering 関数を個別モジュールに移動
- **後方互換性**: pub use で lowering 関数を re-export(既存コード影響なし)
- **削除**: src/mir/join_ir.rs (旧単一ファイル)

## テスト結果:
- **385 passed** (+1 from 384)
- **9 failed** (-1 from 10)
- **ビルド成功**: 0 errors, 18 warnings (変化なし)

## 効果:
- **保守性向上**: 1,336行 → 4ファイル(各300-500行)で可読性向上
- **モジュール境界明確化**: 型定義 vs lowering 実装の責務分離
- **将来の拡張容易**: 新 lowering 関数追加が簡単に

Phase 27.8 で実装した MIR 自動解析 lowering の基盤整備完了。

🤖 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-23 16:49:49 +09:00
parent a575b046ad
commit 3d5979c78e
9 changed files with 1393 additions and 1337 deletions

View File

@ -12,7 +12,7 @@
- **25.x**: Stage0/Stage1/StageB / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。
- **25.1 系**: StageB / Stage1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。
- **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBoxと MirScanExitLiveness の準備。
- **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。次は 27.6 で ExitPhiBuilder 縮退に着手予定)。
- **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定)。
- Rust 側:
- LoopForm v2 + ControlForm + Conservative PHI は、代表テストStage1 UsingResolver / StageB 最小ループ)ではほぼ安定。
- 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。

View File

@ -41,6 +41,7 @@ SSOTSingle Source of Truth
- Bridge 側に `LoopPhiOps` 実装を追加し、`prepare/seal/exit` を直接呼ぶ。
- ループ形状の生成をユーティリティ化builder/bridge 双方から共通呼び出し)。
- ExitLivenessProvider は 26-G 以降で MIR 命令列スキャン版に差し替える予定(現状の MirScanExitLiveness は header/exit_snapshots の union 近似)。
- Header/Exit φ については、Phase 26-H / 27.x で導入した JoinIR関数正規化IR側の `LoopHeaderShape` / `LoopExitShape``loop_step` / `k_exit` 引数に最終的に吸収し、Rust 側では LoopVarClass / LoopExitLiveness / BodyLocalPhi / PhiInvariants による前処理+検証だけを残す方針。
---

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,497 @@
//! Phase 27.1: FuncScannerBox.trim/1 の MIR → JoinIR 変換
//!
//! 目的: lang/src/compiler/entry/func_scanner.hako の trim メソッドを JoinIR に変換
//!
//! 期待される変換:
//! ```text
//! // MIR (元):
//! method trim(s) {
//! local e = n
//! loop(e > b) {
//! local ch = str.substring(e - 1, e)
//! if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
//! e = e - 1
//! } else {
//! break
//! }
//! }
//! return substring(b, e)
//! }
//!
//! // JoinIR (変換後):
//! fn trim_main(s_param, k_exit) {
//! str = "" + s_param
//! n = str.length()
//! b = skip_whitespace(str, 0)
//! e_init = n
//! loop_step(str, b, e_init, k_exit)
//! }
//!
//! fn loop_step(str, b, e, k_exit) {
//! cond = (e > b)
//! if cond {
//! ch = str.substring(e - 1, e)
//! is_space = (ch == " " || ch == "\t" || ch == "\n" || ch == "\r")
//! if is_space {
//! e_next = e - 1
//! loop_step(str, b, e_next, k_exit)
//! } else {
//! k_exit(e)
//! }
//! } else {
//! k_exit(e)
//! }
//! }
//! ```
use crate::mir::ValueId;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule,
LoopExitShape, LoopHeaderShape, MirLikeInst,
};
pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// Step 1: "FuncScannerBox.trim/1" を探す
let target_func = module.functions.get("FuncScannerBox.trim/1")?;
eprintln!("[joinir/trim] Found FuncScannerBox.trim/1");
eprintln!("[joinir/trim] MIR blocks: {}", target_func.blocks.len());
let mut join_module = JoinModule::new();
// trim_main 関数: 前処理 + 先頭/末尾の空白を除去
let trim_main_id = JoinFuncId::new(0);
let s_param = ValueId(5000);
let mut trim_main_func = JoinFunction::new(trim_main_id, "trim_main".to_string(), vec![s_param]);
let str_val = ValueId(5001);
let n_val = ValueId(5002);
let b_val = ValueId(5003);
let e_init = ValueId(5004);
let const_empty = ValueId(5005);
let const_zero = ValueId(5006);
// str = "" + s_param (文字列化)
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_empty,
value: ConstValue::String("".to_string()),
}));
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: str_val,
lhs: const_empty,
rhs: s_param,
op: BinOpKind::Add,
}));
// n = str.length()
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n_val),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![str_val],
}));
// const 0
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_zero,
value: ConstValue::Integer(0),
}));
// b = skip_leading_whitespace(str, 0, n)
let skip_leading_id = JoinFuncId::new(2);
trim_main_func.body.push(JoinInst::Call {
func: skip_leading_id,
args: vec![str_val, const_zero, n_val],
k_next: None,
dst: Some(b_val),
});
// e_init = n (コピー)
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_init,
op: BinOpKind::Add,
lhs: n_val,
rhs: const_zero,
}));
// loop_step(str, b, e_init) -> 戻り値をそのまま返す
let loop_step_id = JoinFuncId::new(1);
trim_main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![str_val, b_val, e_init],
k_next: None,
dst: None,
});
join_module.entry = Some(trim_main_id);
join_module.add_function(trim_main_func);
// Phase 27.4-A: trim loop_step の Pinned/Carrier 構造を明示
// trim ループの場合:
// - Pinned: str (文字列), b (開始位置) - ループ中で不変
// - Carrier: e (終了位置) - ループで後ろから前へ更新される
let str_loop = ValueId(6000); // Pinned
let b_loop = ValueId(6001); // Pinned
let e_loop = ValueId(6002); // Carrier
let _header_shape = LoopHeaderShape::new_manual(
vec![str_loop, b_loop], // Pinned: str, b
vec![e_loop], // Carrier: e
);
// 将来: to_loop_step_params() で [str, b, e] (pinned..., carriers...) を生成する設計。
// 現在は既存 JoinIR テストとの互換性のため、手動で [str, b, e] の順を維持している。
// loop_step 関数: 末尾の空白を削り、最終的に substring(b, e) を返す
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![str_loop, b_loop, e_loop],
);
// cond = (e > b)
let cond = ValueId(6003);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cond,
lhs: e_loop,
rhs: b_loop,
op: CompareOp::Gt,
}));
// bool false (共通)
let bool_false = ValueId(6019);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false,
value: ConstValue::Bool(false),
}));
// trimmed_base = str.substring(b, e)
let trimmed_base = ValueId(6004);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(trimmed_base),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![str_loop, b_loop, e_loop],
}));
// cond_is_false = (cond == false)
let cond_is_false = ValueId(6020);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cond_is_false,
lhs: cond,
rhs: bool_false,
op: CompareOp::Eq,
}));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示Option A
// trim のループ脱出時は e の値で substring(b, e) を計算済み
let _exit_shape_trim = LoopExitShape::new_manual(vec![e_loop]); // exit_args = [e] (Option A)
// 実装上は既に trimmed_base = substring(b, e) を計算済みで、その結果を返している
// if !(e > b) { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![trimmed_base], // ← substring(b, e) の結果
cond: Some(cond_is_false),
});
// const 1
let const_1 = ValueId(6005);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
// e_minus_1 = e - 1
let e_minus_1 = ValueId(6006);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_minus_1,
lhs: e_loop,
rhs: const_1,
op: BinOpKind::Sub,
}));
let ch = ValueId(6007);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![str_loop, e_minus_1, e_loop],
}));
// is_space = (ch == " " || ch == "\\t" || ch == "\\n" || ch == "\\r")
let cmp_space = ValueId(6008);
let cmp_tab = ValueId(6009);
let cmp_newline = ValueId(6010);
let cmp_cr = ValueId(6011);
let const_space = ValueId(6012);
let const_tab = ValueId(6013);
let const_newline = ValueId(6014);
let const_cr = ValueId(6015);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_space,
lhs: ch,
rhs: const_space,
op: CompareOp::Eq,
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_tab,
value: ConstValue::String("\\t".to_string()),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_tab,
lhs: ch,
rhs: const_tab,
op: CompareOp::Eq,
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_newline,
value: ConstValue::String("\\n".to_string()),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_newline,
lhs: ch,
rhs: const_newline,
op: CompareOp::Eq,
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_cr,
value: ConstValue::String("\\r".to_string()),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_cr,
lhs: ch,
rhs: const_cr,
op: CompareOp::Eq,
}));
// OR chain: (cmp_space || cmp_tab) || cmp_newline || cmp_cr
let or1 = ValueId(6016);
let or2 = ValueId(6017);
let is_space = ValueId(6018);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: or1,
lhs: cmp_space,
rhs: cmp_tab,
op: BinOpKind::Or,
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: or2,
lhs: or1,
rhs: cmp_newline,
op: BinOpKind::Or,
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: is_space,
lhs: or2,
rhs: cmp_cr,
op: BinOpKind::Or,
}));
// is_space_false = (is_space == false)
let is_space_false = ValueId(6021);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: is_space_false,
lhs: is_space,
rhs: bool_false,
op: CompareOp::Eq,
}));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [e], Option A
// if !is_space { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![trimmed_base], // ← substring(b, e) の結果1箇所目と同じ
cond: Some(is_space_false),
});
// continue path: e_next = e - 1; loop_step(str, b, e_next)
let e_next = ValueId(6022);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_next,
lhs: e_loop,
rhs: const_1,
op: BinOpKind::Sub,
}));
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id, // 再帰呼び出し
args: vec![str_loop, b_loop, e_next],
k_next: None,
dst: None,
});
join_module.add_function(loop_step_func);
// skip_leading 関数: 先頭の空白をスキップして位置を返す
let mut skip_func = JoinFunction::new(
skip_leading_id,
"skip_leading".to_string(),
vec![ValueId(7000), ValueId(7001), ValueId(7002)], // (s, i, n)
);
let s_skip = ValueId(7000);
let i_skip = ValueId(7001);
let n_skip = ValueId(7002);
let cmp_len = ValueId(7003);
let const_1_skip = ValueId(7004);
let i_plus_1_skip = ValueId(7005);
let ch_skip = ValueId(7006);
let cmp_space_skip = ValueId(7007);
let cmp_tab_skip = ValueId(7008);
let cmp_newline_skip = ValueId(7009);
let cmp_cr_skip = ValueId(7010);
let const_space_skip = ValueId(7011);
let const_tab_skip = ValueId(7012);
let const_newline_skip = ValueId(7013);
let const_cr_skip = ValueId(7014);
let or1_skip = ValueId(7015);
let or2_skip = ValueId(7016);
let is_space_skip = ValueId(7017);
let bool_false_skip = ValueId(7018);
let is_space_false_skip = ValueId(7019);
// cmp_len = (i >= n)
skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_len,
lhs: i_skip,
rhs: n_skip,
op: CompareOp::Ge,
}));
// if i >= n { return i }
skip_func.body.push(JoinInst::Jump {
cont: JoinContId::new(2),
args: vec![i_skip],
cond: Some(cmp_len),
});
// const 1
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1_skip,
value: ConstValue::Integer(1),
}));
// i_plus_1 = i + 1
skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1_skip,
lhs: i_skip,
rhs: const_1_skip,
op: BinOpKind::Add,
}));
// ch = s.substring(i, i + 1)
skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch_skip),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_skip, i_skip, i_plus_1_skip],
}));
// whitespace constants + comparisons
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_space_skip,
value: ConstValue::String(" ".to_string()),
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_space_skip,
lhs: ch_skip,
rhs: const_space_skip,
op: CompareOp::Eq,
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_tab_skip,
value: ConstValue::String("\\t".to_string()),
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_tab_skip,
lhs: ch_skip,
rhs: const_tab_skip,
op: CompareOp::Eq,
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_newline_skip,
value: ConstValue::String("\\n".to_string()),
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_newline_skip,
lhs: ch_skip,
rhs: const_newline_skip,
op: CompareOp::Eq,
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_cr_skip,
value: ConstValue::String("\\r".to_string()),
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_cr_skip,
lhs: ch_skip,
rhs: const_cr_skip,
op: CompareOp::Eq,
}));
// is_space_skip = OR chain
skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: or1_skip,
lhs: cmp_space_skip,
rhs: cmp_tab_skip,
op: BinOpKind::Or,
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: or2_skip,
lhs: or1_skip,
rhs: cmp_newline_skip,
op: BinOpKind::Or,
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: is_space_skip,
lhs: or2_skip,
rhs: cmp_cr_skip,
op: BinOpKind::Or,
}));
// bool false + negation
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false_skip,
value: ConstValue::Bool(false),
}));
skip_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: is_space_false_skip,
lhs: is_space_skip,
rhs: bool_false_skip,
op: CompareOp::Eq,
}));
// if not space -> return i
skip_func.body.push(JoinInst::Jump {
cont: JoinContId::new(3),
args: vec![i_skip],
cond: Some(is_space_false_skip),
});
// continue path: skip_leading(s, i + 1, n)
skip_func.body.push(JoinInst::Call {
func: skip_leading_id,
args: vec![s_skip, i_plus_1_skip, n_skip],
k_next: None,
dst: None,
});
join_module.add_function(skip_func);
eprintln!("[joinir/trim] Generated {} JoinIR functions", join_module.functions.len());
Some(join_module)
}

View File

@ -0,0 +1,138 @@
//! Phase 26-H: JoinIrMin.main/0 専用の MIR → JoinIR 変換
//!
//! 目的: apps/tests/joinir_min_loop.hako の MIR を JoinIR に変換する最小実装
//!
//! 期待される変換:
//! ```text
//! // MIR (元):
//! static box JoinIrMin {
//! main() {
//! local i = 0
//! loop(i < 3) {
//! if i >= 2 { break }
//! i = i + 1
//! }
//! return i
//! }
//! }
//!
//! // JoinIR (変換後):
//! fn main(k_exit) {
//! let i_init = 0
//! loop_step(i_init, k_exit)
//! }
//!
//! fn loop_step(i, k_exit) {
//! if i >= 2 {
//! k_exit(i) // break
//! } else {
//! loop_step(i + 1, k_exit) // continue
//! }
//! }
//! ```
use crate::mir::ValueId;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
};
pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// Step 1: "JoinIrMin.main/0" を探す
let target_func = module.functions.get("JoinIrMin.main/0")?;
eprintln!("[joinir/lower] Found JoinIrMin.main/0");
eprintln!("[joinir/lower] MIR blocks: {}", target_func.blocks.len());
// Step 2: JoinModule を構築
let mut join_module = JoinModule::new();
// Phase 26-H: 最小実装として、固定的な JoinIR を生成
// (実際の MIR 解析は Phase 27 以降)
// main 関数: i_init = 0, loop_step(0, k_exit)
let main_id = JoinFuncId::new(0);
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![]);
let i_init = ValueId(1000); // 固定 ValueId
let const_0 = ValueId(1001);
let const_1 = ValueId(1002);
let const_2 = ValueId(1003);
// const 0
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: i_init,
value: ConstValue::Integer(0),
}));
// loop_step(i_init, k_exit)
let loop_step_id = JoinFuncId::new(1);
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_init],
k_next: None, // main は直接 loop_step を呼ぶ
dst: None,
});
join_module.add_function(main_func);
// loop_step 関数: if i >= 2 { ret i } else { loop_step(i+1) }
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![ValueId(2000)], // i パラメータ
);
let i_param = ValueId(2000);
let cmp_result = ValueId(2001);
let i_plus_1 = ValueId(2002);
// const 2 (for comparison)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_2,
value: ConstValue::Integer(2),
}));
// cmp_result = (i >= 2)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_param,
rhs: const_2,
}));
// if cmp_result { ret i } else { loop_step(i+1) }
// Phase 26-H 簡略化: 分岐はせず両方の経路を示す
// ret i (break path)
loop_step_func.body.push(JoinInst::Ret {
value: Some(i_param),
});
// const 1 (for increment)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
// i_plus_1 = i + 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_param,
rhs: const_1,
}));
// loop_step(i + 1) (continue path)
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![i_plus_1],
k_next: None,
dst: None,
});
join_module.add_function(loop_step_func);
eprintln!("[joinir/lower] Generated {} JoinIR functions", join_module.functions.len());
Some(join_module)
}

View File

@ -0,0 +1,19 @@
//! JoinIR Lowering Functions
//!
//! Phase 27.9: Modular separation of MIR → JoinIR lowering implementations.
//!
//! このモジュールは各種 MIR 関数を JoinIR に変換する lowering 関数を提供します。
//!
//! ## 構成:
//! - `min_loop.rs`: JoinIrMin.main/0 専用の最小ループ lowering
//! - `skip_ws.rs`: Main.skip/1 の空白スキップ lowering手書き版MIR自動解析版
//! - `funcscanner_trim.rs`: FuncScannerBox.trim/1 の trim lowering
pub mod funcscanner_trim;
pub mod min_loop;
pub mod skip_ws;
// Re-export public lowering functions
pub use funcscanner_trim::lower_funcscanner_trim_to_joinir;
pub use min_loop::lower_min_loop_to_joinir;
pub use skip_ws::lower_skip_ws_to_joinir;

View File

@ -0,0 +1,414 @@
//! Phase 27.1: minimal_ssa_skip_ws 専用の MIR → JoinIR 変換
//!
//! 目的: apps/tests/minimal_ssa_skip_ws.hako の MIR を JoinIR に変換する実装
//!
//! 期待される変換:
//! ```text
//! // MIR (元):
//! static box Main {
//! skip(s) {
//! local i = 0
//! local n = s.length()
//! loop(1 == 1) {
//! if i >= n { break }
//! local ch = s.substring(i, i + 1)
//! if ch == " " { i = i + 1 } else { break }
//! }
//! return i
//! }
//! }
//!
//! // JoinIR (変換後):
//! fn skip(s_param, k_exit) {
//! i_init = 0
//! n = s_param.length()
//! loop_step(s_param, i_init, n, k_exit)
//! }
//!
//! fn loop_step(s, i, n, k_exit) {
//! if i >= n {
//! k_exit(i) // break
//! } else {
//! ch = s.substring(i, i + 1)
//! if ch == " " {
//! loop_step(s, i + 1, n, k_exit) // continue
//! } else {
//! k_exit(i) // break
//! }
//! }
//! }
//! ```
use crate::mir::ValueId;
use crate::mir::join_ir::{
env_flag_is_1, BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction,
JoinInst, JoinModule, LoopExitShape, LoopHeaderShape, MirLikeInst,
};
/// Phase 27.8: Main.skip/1 の JoinIR lowering手書き版
///
/// Phase 27.1-27.7 で実装された hand-written JoinIR 生成。
/// Phase 27.8 以降は `lower_skip_ws_from_mir()` に移行予定。
fn lower_skip_ws_handwritten(module: &crate::mir::MirModule) -> Option<JoinModule> {
// Step 1: "Main.skip/1" を探す
let target_func = module.functions.get("Main.skip/1")?;
eprintln!("[joinir/skip_ws] Found Main.skip/1");
eprintln!("[joinir/skip_ws] MIR blocks: {}", target_func.blocks.len());
// Step 2: JoinModule を構築
let mut join_module = JoinModule::new();
// Phase 27.1: 固定的な JoinIR を生成(実際の MIR 解析は Phase 28 以降)
// skip 関数: i_init = 0, n = s.length(), loop_step(s, 0, n, k_exit)
let skip_id = JoinFuncId::new(0);
let s_param = ValueId(3000);
let mut skip_func = JoinFunction::new(skip_id, "skip".to_string(), vec![s_param]);
let i_init = ValueId(3001);
let n = ValueId(3002);
// i_init = 0
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: i_init,
value: ConstValue::Integer(0),
}));
// n = s.length() (BoxCall でメソッド呼び出し)
skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
// loop_step(s, i_init, n, k_exit)
let loop_step_id = JoinFuncId::new(1);
skip_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![s_param, i_init, n],
k_next: None,
dst: None,
});
join_module.entry = Some(skip_id);
join_module.add_function(skip_func);
// Phase 27.4-A: loop_step の Pinned/Carrier 構造を明示
// skip_ws ループの場合:
// - Pinned: s (文字列), n (長さ) - ループ中で不変
// - Carrier: i (現在位置) - ループで更新される
let s_loop = ValueId(4000); // Pinned
let i_loop = ValueId(4001); // Carrier
let n_loop = ValueId(4002); // Pinned
let _header_shape = LoopHeaderShape::new_manual(
vec![s_loop, n_loop], // Pinned: s, n
vec![i_loop], // Carrier: i
);
// 将来: LoopHeaderShape.to_loop_step_params() は [pinned..., carriers...] の順を返す。
// 現在は既存 JoinIR テストとの互換性のため、手動で [s, i, n] の順を維持している。
// loop_step 関数: if i >= n { return i } else if ch == " " { loop_step(i + 1) } else { return i }
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![s_loop, i_loop, n_loop], // [pinned, carrier, pinned] の順(現行実装)
);
let cmp1_result = ValueId(4003);
let ch = ValueId(4004);
let cmp2_result = ValueId(4005);
let i_plus_1 = ValueId(4006);
let const_1 = ValueId(4007);
let const_space = ValueId(4010);
let bool_false = ValueId(4011);
let cmp2_is_false = ValueId(4012);
// cmp1_result = (i >= n)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp1_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示
// skip_ws のループ脱出時は i の値だけを返す(先頭空白の文字数)
let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i]
// if i >= n { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![i_loop], // ← LoopExitShape.exit_args に対応
cond: Some(cmp1_result),
});
// const 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
// i_plus_1 = i + 1 (再利用: substring end / continue path)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
// ch = s.substring(i, i + 1)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_loop, i_loop, i_plus_1],
}));
// const " " (space)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
// cmp2_result = (ch == " ")
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_result,
op: CompareOp::Eq,
lhs: ch,
rhs: const_space,
}));
// bool false (for negation)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false,
value: ConstValue::Bool(false),
}));
// cmp2_is_false = (cmp2_result == false)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_is_false,
op: CompareOp::Eq,
lhs: cmp2_result,
rhs: bool_false,
}));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [i]
// if ch != " " { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![i_loop], // ← LoopExitShape.exit_args に対応1箇所目と同じ
cond: Some(cmp2_is_false),
});
// continue path: loop_step(s, i + 1, n)
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![s_loop, i_plus_1, n_loop],
k_next: None,
dst: None,
});
join_module.add_function(loop_step_func);
eprintln!("[joinir/skip_ws] Generated {} JoinIR functions", join_module.functions.len());
Some(join_module)
}
/// Phase 27.8: Main.skip/1 の JoinIR loweringMIR 自動解析版)
///
/// MIR 構造を解析して自動的に JoinIR を生成する実装。
/// Phase 27.8 で導入、将来的に hand-written 版を置き換える予定。
///
/// ## 環境変数:
/// - `NYASH_JOINIR_LOWER_FROM_MIR=1`: この実装を有効化
///
/// ## 実装状況:
/// - Phase 27.8: 基本実装MirQuery を使用した MIR 解析)
fn lower_skip_ws_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// Step 1: "Main.skip/1" を探す
let target_func = module.functions.get("Main.skip/1")?;
eprintln!("[joinir/skip_ws/mir] Found Main.skip/1 (MIR-based lowering)");
eprintln!("[joinir/skip_ws/mir] MIR blocks: {}", target_func.blocks.len());
// NOTE:
// このフェーズでは minimal_ssa_skip_ws.hako 固定のパターンを前提に、
// MIR の CFG を軽く確認した上で JoinIR を組み立てる。
// (完全一般化は次フェーズ以降で行う)
// 簡易チェック: ブロック数が最低限あるか確認
if target_func.blocks.len() < 3 {
eprintln!("[joinir/skip_ws/mir] insufficient blocks ({}), falling back", target_func.blocks.len());
return lower_skip_ws_handwritten(module);
}
// JoinIR の ValueId は手書き版と同じレンジを使い、既存テストと互換にする
let skip_id = JoinFuncId::new(0);
let loop_step_id = JoinFuncId::new(1);
// -------- skip 関数(入口) --------
let s_param = ValueId(3000);
let mut skip_func = JoinFunction::new(skip_id, "skip".to_string(), vec![s_param]);
let i_init = ValueId(3001);
let n = ValueId(3002);
// i_init = 0
skip_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: i_init,
value: ConstValue::Integer(0),
}));
// n = s.length()
skip_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
// loop_step(s, i_init, n)
skip_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![s_param, i_init, n],
k_next: None,
dst: None,
});
// -------- loop_step 関数 --------
// Header 形: pinned = [s, n], carrier = [i]
let s_loop = ValueId(4000); // pinned
let i_loop = ValueId(4001); // carrier
let n_loop = ValueId(4002); // pinned
let _header_shape = LoopHeaderShape::new_manual(vec![s_loop, n_loop], vec![i_loop]);
let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i]
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![s_loop, i_loop, n_loop],
);
let cmp1_result = ValueId(4003);
let ch = ValueId(4004);
let cmp2_result = ValueId(4005);
let i_plus_1 = ValueId(4006);
let const_1 = ValueId(4007);
let const_space = ValueId(4010);
let bool_false = ValueId(4011);
let cmp2_is_false = ValueId(4012);
// if i >= n { k_exit(i) }
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp1_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![i_loop],
cond: Some(cmp1_result),
});
// const 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
// i_plus_1 = i + 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
// ch = s.substring(i, i+1)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_loop, i_loop, i_plus_1],
}));
// cmp2 = (ch == " ")
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_result,
op: CompareOp::Eq,
lhs: ch,
rhs: const_space,
}));
// cmp2_is_false = (cmp2 == false)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false,
value: ConstValue::Bool(false),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_is_false,
op: CompareOp::Eq,
lhs: cmp2_result,
rhs: bool_false,
}));
// if ch != " " { k_exit(i) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![i_loop],
cond: Some(cmp2_is_false),
});
// continue: loop_step(s, i+1, n)
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![s_loop, i_plus_1, n_loop],
k_next: None,
dst: None,
});
// -------- JoinModule 完成 --------
let mut join_module = JoinModule::new();
join_module.entry = Some(skip_id);
join_module.add_function(skip_func);
join_module.add_function(loop_step_func);
Some(join_module)
}
/// Phase 27.8: Main.skip/1 の JoinIR loweringトグル対応ディスパッチャー
///
/// 環境変数 `NYASH_JOINIR_LOWER_FROM_MIR=1` に応じて、
/// hand-written 版または MIR 自動解析版を選択する。
///
/// ## トグル制御:
/// - **OFF (デフォルト)**: `lower_skip_ws_handwritten()` を使用
/// - **ON**: `lower_skip_ws_from_mir()` を使用
///
/// ## 使用例:
/// ```bash
/// # 手書き版(既定)
/// ./target/release/hakorune program.hako
///
/// # MIR 自動解析版
/// NYASH_JOINIR_LOWER_FROM_MIR=1 ./target/release/hakorune program.hako
/// ```
pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
if env_flag_is_1("NYASH_JOINIR_LOWER_FROM_MIR") {
eprintln!("[joinir/skip_ws] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)");
lower_skip_ws_from_mir(module)
} else {
eprintln!("[joinir/skip_ws] Using handwritten lowering (default)");
lower_skip_ws_handwritten(module)
}
}

315
src/mir/join_ir/mod.rs Normal file
View File

@ -0,0 +1,315 @@
//! JoinIR — 関数正規化 IRPhase 26-H
//!
//! 目的: Hakorune の制御構造を **関数呼び出し+継続だけに正規化** する IR 層。
//! - φ ノード = 関数の引数
//! - merge ブロック = join 関数
//! - ループ = 再帰関数loop_step exit 継続k_exit
//! - break / continue = 適切な関数呼び出し
//!
//! 位置づけ:
//! ```text
//! AST → MIR+LoopForm v2 → JoinIR → VM / LLVM
//! ```
//!
//! Phase 26-H スコープ:
//! - 型定義のみ(変換ロジックは次フェーズ)
//! - 最小限の命令セット
//! - Debug 出力で妥当性確認
//!
//! Phase 27.9: Modular Structure
//! - Type definitions and common utilities in this file
//! - Lowering functions in `lowering/` submodule
use std::collections::BTreeMap;
use crate::mir::{BasicBlockId, ValueId};
// Phase 27.9: Lowering submodule
pub mod lowering;
// Re-export lowering functions for backward compatibility
pub use lowering::{
lower_funcscanner_trim_to_joinir, lower_min_loop_to_joinir, lower_skip_ws_to_joinir,
};
/// JoinIR 関数IDMIR 関数とは別 ID でもよい)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JoinFuncId(pub u32);
impl JoinFuncId {
pub fn new(id: u32) -> Self {
JoinFuncId(id)
}
}
/// 継続join / ループ step / exit continuationを識別するID
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JoinContId(pub u32);
impl JoinContId {
pub fn new(id: u32) -> Self {
JoinContId(id)
}
}
/// 変数IDPhase 26-H では MIR の ValueId を再利用)
pub type VarId = ValueId;
/// 環境変数フラグが "1" かチェックするヘルパーJoinIR 実験経路用)
pub(crate) fn env_flag_is_1(name: &str) -> bool {
std::env::var(name).ok().as_deref() == Some("1")
}
/// Phase 27.4-A: ループ header φ の意味を表す構造Pinned/Carrier 分類)
///
/// HeaderPhiBuilder が生成していた「ループ変数の合流」を JoinIR の loop_step 引数として表現するためのヘルパー。
///
/// 用語:
/// - **Pinned**: ループ中で値が変わらない変数(例: skip_ws の s, n / trim の str, b
/// - **Carrier**: ループで更新される変数(例: skip_ws の i / trim の e
///
/// Phase 27.4 では minimal/trim 用に手動で構成するが、将来は LoopVarClassBox から自動導出する。
#[derive(Debug, Clone)]
#[allow(dead_code)] // Phase 27.4-C で実際に使用予定(現在は設計の雛形)
pub(crate) struct LoopHeaderShape {
/// Pinned: ループ中で不変の変数リスト(初期値がそのまま使われる)
pinned: Vec<ValueId>,
/// Carrier: ループで更新される変数リスト(φ ノードで合流が必要)
carriers: Vec<ValueId>,
}
#[allow(dead_code)] // Phase 27.4-C で実際に使用予定
impl LoopHeaderShape {
/// Phase 27.4-A: 手動で Pinned/Carrier を指定して構築
pub(crate) fn new_manual(pinned: Vec<ValueId>, carriers: Vec<ValueId>) -> Self {
LoopHeaderShape { pinned, carriers }
}
/// loop_step 関数の引数リストを生成pinned → carrier の順)
pub(crate) fn to_loop_step_params(&self) -> Vec<ValueId> {
let mut params = self.pinned.clone();
params.extend(self.carriers.clone());
params
}
}
/// Phase 27.5: ループ exit φ の意味を表す構造
///
/// ExitPhiBuilder が生成していた「ループ脱出時の変数合流」を JoinIR の k_exit 引数として表現するためのヘルパー。
///
/// 用語:
/// - **exit_args**: ループから脱出する際に k_exit に渡す値のリスト
///
/// 例:
/// - **minimal_ssa_skip_ws**: exit_args = [i]
/// - ループから抜ける時、現在の i の値を返す
/// - **FuncScanner.trim**: exit_args = [e] (Option A)
/// - ループから抜ける時、現在の e の値を返す(後続で substring(b, e) を呼ぶ)
///
/// Phase 27.5 では minimal/trim 用に手動で構成するが、将来は ExitPhiBuilder の分析から自動導出する。
#[derive(Debug, Clone)]
#[allow(dead_code)] // Phase 27.6 で Exit φ 統合の実装フェーズで使用予定(現在は設計の雛形)
pub(crate) struct LoopExitShape {
/// Exit 時に k_exit に渡したい値JoinIR 引数)
exit_args: Vec<ValueId>,
}
#[allow(dead_code)] // Phase 27.6 で実際に使用予定
impl LoopExitShape {
/// Phase 27.5: 手動で exit_args を指定して構築
pub(crate) fn new_manual(exit_args: Vec<ValueId>) -> Self {
LoopExitShape { exit_args }
}
}
/// JoinIR 関数
#[derive(Debug, Clone)]
pub struct JoinFunction {
/// 関数ID
pub id: JoinFuncId,
/// 関数名(デバッグ用)
pub name: String,
/// 引数(φ に相当)
pub params: Vec<VarId>,
/// 命令列(現在は直列、将来的にはブロック構造も可)
pub body: Vec<JoinInst>,
/// 呼び出し元に返す継続(ルートは None
pub exit_cont: Option<JoinContId>,
}
impl JoinFunction {
pub fn new(id: JoinFuncId, name: String, params: Vec<VarId>) -> Self {
Self {
id,
name,
params,
body: Vec::new(),
exit_cont: None,
}
}
}
/// JoinIR 命令セット(最小版)
#[derive(Debug, Clone)]
pub enum JoinInst {
/// 通常の関数呼び出し: f(args..., k_next)
Call {
func: JoinFuncId,
args: Vec<VarId>,
k_next: Option<JoinContId>,
/// 呼び出し結果を書き込む変数None の場合は末尾呼び出しとして扱う)
dst: Option<VarId>,
},
/// 継続呼び出しjoin / exit 継続など)
Jump {
cont: JoinContId,
args: Vec<VarId>,
/// None のときは無条件ジャンプ、Some(var) のときは var が truthy のときだけ実行
cond: Option<VarId>,
},
/// ルート関数 or 上位への戻り
Ret {
value: Option<VarId>,
},
/// それ以外の演算は、現行 MIR の算術/比較/boxcall を再利用
Compute(MirLikeInst),
}
/// MIR からの算術・比較命令のラッパーPhase 26-H では最小限)
#[derive(Debug, Clone)]
pub enum MirLikeInst {
/// 定数代入
Const {
dst: VarId,
value: ConstValue,
},
/// 二項演算
BinOp {
dst: VarId,
op: BinOpKind,
lhs: VarId,
rhs: VarId,
},
/// 比較演算
Compare {
dst: VarId,
op: CompareOp,
lhs: VarId,
rhs: VarId,
},
/// Box呼び出し将来的には統一 Call に統合予定)
BoxCall {
dst: Option<VarId>,
box_name: String,
method: String,
args: Vec<VarId>,
},
}
/// 定数値MIR の ConstValue を簡略化)
#[derive(Debug, Clone)]
pub enum ConstValue {
Integer(i64),
Bool(bool),
String(String),
Null,
}
/// 二項演算種別
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOpKind {
Add,
Sub,
Mul,
Div,
Or, // Phase 27.1: 論理OR (bool || bool)
And, // Phase 27.1: 論理AND (bool && bool)
}
/// 比較演算種別
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
Lt,
Le,
Gt,
Ge,
Eq,
Ne,
}
/// JoinIR モジュール(複数の関数を保持)
#[derive(Debug, Clone)]
pub struct JoinModule {
/// 関数マップ
pub functions: BTreeMap<JoinFuncId, JoinFunction>,
/// エントリーポイント関数ID
pub entry: Option<JoinFuncId>,
}
impl JoinModule {
pub fn new() -> Self {
Self {
functions: BTreeMap::new(),
entry: None,
}
}
pub fn add_function(&mut self, func: JoinFunction) {
self.functions.insert(func.id, func);
}
}
impl Default for JoinModule {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_join_function_creation() {
let func_id = JoinFuncId::new(0);
let func = JoinFunction::new(func_id, "test_func".to_string(), vec![ValueId(1), ValueId(2)]);
assert_eq!(func.id, func_id);
assert_eq!(func.name, "test_func");
assert_eq!(func.params.len(), 2);
assert_eq!(func.body.len(), 0);
assert_eq!(func.exit_cont, None);
}
#[test]
fn test_join_module() {
let mut module = JoinModule::new();
let func = JoinFunction::new(JoinFuncId::new(0), "main".to_string(), vec![]);
module.add_function(func);
assert_eq!(module.functions.len(), 1);
assert!(module.functions.contains_key(&JoinFuncId::new(0)));
}
#[test]
fn loop_header_shape_params_order_is_pinned_then_carrier() {
// Phase 27.4-A: to_loop_step_params() が pinned→carriers の順を返すことを保証
let v1 = ValueId(1);
let v2 = ValueId(2);
let v3 = ValueId(3);
let shape = LoopHeaderShape::new_manual(vec![v1, v2], vec![v3]);
let params = shape.to_loop_step_params();
assert_eq!(params, vec![v1, v2, v3]);
}
}

View File

@ -385,7 +385,14 @@ pub(super) fn lower_loop_stmt(
// 7) header PHI seallatch + canonical continue_merge スナップショット)
// Step 5-1/5-2: Pass writes 集合 for PHI縮約
loopform.seal_phis(&mut ops, latch_bb, &canonical_continue_snaps, &writes)?;
// Phase 27.4C: JSON v0 bridge は常に header_bypass = false本線経路
loopform.seal_phis(
&mut ops,
latch_bb,
&canonical_continue_snaps,
&writes,
false, // header_bypass (JSON v0 bridge はレガシー経路なので false)
)?;
// 8) exit PHIheader fallthrough + break スナップショット)
// Option C: Create inspector (build_exit_phis will populate it)