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 経路は完全に影響なし)
1097 lines
32 KiB
Rust
1097 lines
32 KiB
Rust
//! 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<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]);
|
||
}
|
||
}
|