From 3d5979c78e866d25bc0e21e31fd7824cf9efa2d5 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sun, 23 Nov 2025 16:49:49 +0900 Subject: [PATCH] refactor(joinir): Phase 27.9 - Modular separation of join_ir.rs into directory structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CURRENT_TASK.md | 2 +- .../architecture/loops/loopform_ssot.md | 1 + src/mir/join_ir.rs | 1335 ----------------- src/mir/join_ir/lowering/funcscanner_trim.rs | 497 ++++++ src/mir/join_ir/lowering/min_loop.rs | 138 ++ src/mir/join_ir/lowering/mod.rs | 19 + src/mir/join_ir/lowering/skip_ws.rs | 414 +++++ src/mir/join_ir/mod.rs | 315 ++++ src/runner/json_v0_bridge/lowering/loop_.rs | 9 +- 9 files changed, 1393 insertions(+), 1337 deletions(-) delete mode 100644 src/mir/join_ir.rs create mode 100644 src/mir/join_ir/lowering/funcscanner_trim.rs create mode 100644 src/mir/join_ir/lowering/min_loop.rs create mode 100644 src/mir/join_ir/lowering/mod.rs create mode 100644 src/mir/join_ir/lowering/skip_ws.rs create mode 100644 src/mir/join_ir/mod.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 18cf0f89..46691e72 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -12,7 +12,7 @@ - **25.x**: Stage0/Stage1/Stage‑B / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。 - **25.1 系**: Stage‑B / Stage‑1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。 - **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成(LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBox)と MirScanExitLiveness の準備。 - - **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。次は 27.6 で ExitPhiBuilder 縮退に着手予定)。 + - **26-H / 27.x(New)**: 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 は、代表テスト(Stage‑1 UsingResolver / Stage‑B 最小ループ)ではほぼ安定。 - 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。 diff --git a/docs/development/architecture/loops/loopform_ssot.md b/docs/development/architecture/loops/loopform_ssot.md index 5d8ea98a..bb84dd33 100644 --- a/docs/development/architecture/loops/loopform_ssot.md +++ b/docs/development/architecture/loops/loopform_ssot.md @@ -41,6 +41,7 @@ SSOT(Single 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 による前処理+検証だけを残す方針。 --- diff --git a/src/mir/join_ir.rs b/src/mir/join_ir.rs deleted file mode 100644 index 09e648cf..00000000 --- a/src/mir/join_ir.rs +++ /dev/null @@ -1,1335 +0,0 @@ -//! JoinIR — 関数正規化 IR(Phase 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 出力で妥当性確認 - -use std::collections::BTreeMap; - -use crate::mir::{BasicBlockId, ValueId}; - -/// JoinIR 関数ID(MIR 関数とは別 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) - } -} - -/// 変数ID(Phase 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 で実際に使用予定(現在は設計の雛形) -struct LoopHeaderShape { - /// Pinned: ループ中で不変の変数リスト(初期値がそのまま使われる) - pinned: Vec, - /// Carrier: ループで更新される変数リスト(φ ノードで合流が必要) - carriers: Vec, -} - -#[allow(dead_code)] // Phase 27.4-C で実際に使用予定 -impl LoopHeaderShape { - /// Phase 27.4-A: 手動で Pinned/Carrier を指定して構築 - fn new_manual(pinned: Vec, carriers: Vec) -> Self { - LoopHeaderShape { pinned, carriers } - } - - /// loop_step 関数の引数リストを生成(pinned → carrier の順) - fn to_loop_step_params(&self) -> Vec { - 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 φ 統合の実装フェーズで使用予定(現在は設計の雛形) -struct LoopExitShape { - /// Exit 時に k_exit に渡したい値(JoinIR 引数) - exit_args: Vec, -} - -#[allow(dead_code)] // Phase 27.6 で実際に使用予定 -impl LoopExitShape { - /// Phase 27.5: 手動で exit_args を指定して構築 - fn new_manual(exit_args: Vec) -> Self { - LoopExitShape { exit_args } - } -} - -/// JoinIR 関数 -#[derive(Debug, Clone)] -pub struct JoinFunction { - /// 関数ID - pub id: JoinFuncId, - - /// 関数名(デバッグ用) - pub name: String, - - /// 引数(φ に相当) - pub params: Vec, - - /// 命令列(現在は直列、将来的にはブロック構造も可) - pub body: Vec, - - /// 呼び出し元に返す継続(ルートは None) - pub exit_cont: Option, -} - -impl JoinFunction { - pub fn new(id: JoinFuncId, name: String, params: Vec) -> 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, - k_next: Option, - /// 呼び出し結果を書き込む変数(None の場合は末尾呼び出しとして扱う) - dst: Option, - }, - - /// 継続呼び出し(join / exit 継続など) - Jump { - cont: JoinContId, - args: Vec, - /// None のときは無条件ジャンプ、Some(var) のときは var が truthy のときだけ実行 - cond: Option, - }, - - /// ルート関数 or 上位への戻り - Ret { - value: Option, - }, - - /// それ以外の演算は、現行 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, - box_name: String, - method: String, - args: Vec, - }, -} - -/// 定数値(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, - - /// エントリーポイント関数ID - pub entry: Option, -} - -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() - } -} - -/// 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 -/// } -/// } -/// ``` -pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option { - // 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) -} - -/// 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 -/// } -/// } -/// } -/// ``` -/// 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 { - // 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 lowering(MIR 自動解析版) -/// -/// 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 { - // 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 { - 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) - } -} - -/// 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) -/// } -/// } -/// ``` -pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Option { - // 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) -} - -#[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]); - } -} diff --git a/src/mir/join_ir/lowering/funcscanner_trim.rs b/src/mir/join_ir/lowering/funcscanner_trim.rs new file mode 100644 index 00000000..45dd55a1 --- /dev/null +++ b/src/mir/join_ir/lowering/funcscanner_trim.rs @@ -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 { + // 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) +} diff --git a/src/mir/join_ir/lowering/min_loop.rs b/src/mir/join_ir/lowering/min_loop.rs new file mode 100644 index 00000000..6026db2e --- /dev/null +++ b/src/mir/join_ir/lowering/min_loop.rs @@ -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 { + // 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) +} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs new file mode 100644 index 00000000..a91fb5c5 --- /dev/null +++ b/src/mir/join_ir/lowering/mod.rs @@ -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; diff --git a/src/mir/join_ir/lowering/skip_ws.rs b/src/mir/join_ir/lowering/skip_ws.rs new file mode 100644 index 00000000..408167a3 --- /dev/null +++ b/src/mir/join_ir/lowering/skip_ws.rs @@ -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 { + // 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 lowering(MIR 自動解析版) +/// +/// 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 { + // 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 { + 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) + } +} diff --git a/src/mir/join_ir/mod.rs b/src/mir/join_ir/mod.rs new file mode 100644 index 00000000..c8d5bb6b --- /dev/null +++ b/src/mir/join_ir/mod.rs @@ -0,0 +1,315 @@ +//! JoinIR — 関数正規化 IR(Phase 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 関数ID(MIR 関数とは別 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) + } +} + +/// 変数ID(Phase 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, + /// Carrier: ループで更新される変数リスト(φ ノードで合流が必要) + carriers: Vec, +} + +#[allow(dead_code)] // Phase 27.4-C で実際に使用予定 +impl LoopHeaderShape { + /// Phase 27.4-A: 手動で Pinned/Carrier を指定して構築 + pub(crate) fn new_manual(pinned: Vec, carriers: Vec) -> Self { + LoopHeaderShape { pinned, carriers } + } + + /// loop_step 関数の引数リストを生成(pinned → carrier の順) + pub(crate) fn to_loop_step_params(&self) -> Vec { + 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, +} + +#[allow(dead_code)] // Phase 27.6 で実際に使用予定 +impl LoopExitShape { + /// Phase 27.5: 手動で exit_args を指定して構築 + pub(crate) fn new_manual(exit_args: Vec) -> Self { + LoopExitShape { exit_args } + } +} + +/// JoinIR 関数 +#[derive(Debug, Clone)] +pub struct JoinFunction { + /// 関数ID + pub id: JoinFuncId, + + /// 関数名(デバッグ用) + pub name: String, + + /// 引数(φ に相当) + pub params: Vec, + + /// 命令列(現在は直列、将来的にはブロック構造も可) + pub body: Vec, + + /// 呼び出し元に返す継続(ルートは None) + pub exit_cont: Option, +} + +impl JoinFunction { + pub fn new(id: JoinFuncId, name: String, params: Vec) -> 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, + k_next: Option, + /// 呼び出し結果を書き込む変数(None の場合は末尾呼び出しとして扱う) + dst: Option, + }, + + /// 継続呼び出し(join / exit 継続など) + Jump { + cont: JoinContId, + args: Vec, + /// None のときは無条件ジャンプ、Some(var) のときは var が truthy のときだけ実行 + cond: Option, + }, + + /// ルート関数 or 上位への戻り + Ret { + value: Option, + }, + + /// それ以外の演算は、現行 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, + box_name: String, + method: String, + args: Vec, + }, +} + +/// 定数値(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, + + /// エントリーポイント関数ID + pub entry: Option, +} + +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]); + } +} diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index d8bc67c1..353f9437 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -385,7 +385,14 @@ pub(super) fn lower_loop_stmt( // 7) header PHI seal(latch + 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 PHI(header fallthrough + break スナップショット) // Option C: Create inspector (build_exit_phis will populate it)