Files
hakorune/src/mir/join_ir.rs
nyash-codex df2248d3c1 feat(phi): Phase 27.4-C - HeaderPhiBuilder bypass for JoinIR experiment
JoinIR 実験経路限定で Header φ 生成をスキップ可能に。

実装内容:
- トグルシステム: joinir_header_bypass_enabled() / is_joinir_header_bypass_target()
- バイパス実装: loop_builder.rs で関数名チェック後に emit_header_phis() をスキップ
- ターゲット関数: Main.skip/1, FuncScannerBox.trim/1 のみ
- テスト更新: JoinIR テストファイルに Phase 27.4-C 対応コメント追加

環境変数:
- NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_HEADER_EXP=1 の両方が必要

本線影響: ゼロ(MIR/LoopForm→VM 経路は完全に影響なし)
2025-11-23 10:08:48 +09:00

1097 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! JoinIR — 関数正規化 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 出力で妥当性確認
use std::collections::BTreeMap;
use crate::mir::{BasicBlockId, ValueId};
/// 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 で実際に使用予定(現在は設計の雛形)
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 を指定して構築
fn new_manual(pinned: Vec<ValueId>, carriers: Vec<ValueId>) -> Self {
LoopHeaderShape { pinned, carriers }
}
/// loop_step 関数の引数リストを生成pinned → carrier の順)
fn to_loop_step_params(&self) -> Vec<ValueId> {
let mut params = self.pinned.clone();
params.extend(self.carriers.clone());
params
}
}
/// 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()
}
}
/// 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<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)
}
/// 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
/// }
/// }
/// }
/// ```
pub fn lower_skip_ws_to_joinir(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,
}));
// if i >= n { return i }
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 (再利用: 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,
}));
// if ch != " " { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![i_loop],
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.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<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,
}));
// if !(e > b) { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![trimmed_base],
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,
}));
// if !is_space { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![trimmed_base],
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]);
}
}