diff --git a/src/backend/mir_interpreter/handlers/boxes_instance.rs b/src/backend/mir_interpreter/handlers/boxes_instance.rs index 66d0ff0e..a88379e0 100644 --- a/src/backend/mir_interpreter/handlers/boxes_instance.rs +++ b/src/backend/mir_interpreter/handlers/boxes_instance.rs @@ -155,7 +155,11 @@ pub(super) fn try_handle_instance_box( ); } - let mut argv: Vec = Vec::with_capacity(if include_me { 1 + args.len() } else { args.len() }); + let mut argv: Vec = Vec::with_capacity(if include_me { + 1 + args.len() + } else { + args.len() + }); // Dev assert: forbid birth(me==Void) if method == "birth" && crate::config::env::using_is_dev() { if matches!(recv_vm, VMValue::Void) { @@ -211,7 +215,11 @@ pub(super) fn try_handle_instance_box( true }; - let mut argv: Vec = Vec::with_capacity(if include_me { 1 + args.len() } else { args.len() }); + let mut argv: Vec = Vec::with_capacity(if include_me { + 1 + args.len() + } else { + args.len() + }); if method == "birth" && crate::config::env::using_is_dev() { if matches!(recv_vm, VMValue::Void) { return Err( diff --git a/src/backend/mir_interpreter/mod.rs b/src/backend/mir_interpreter/mod.rs index cfaa2f97..f747c6b0 100644 --- a/src/backend/mir_interpreter/mod.rs +++ b/src/backend/mir_interpreter/mod.rs @@ -329,7 +329,9 @@ impl MirInterpreter { let func = self .functions .get(func_name) - .ok_or_else(|| VMError::InvalidInstruction(format!("function not found: {}", func_name)))? + .ok_or_else(|| { + VMError::InvalidInstruction(format!("function not found: {}", func_name)) + })? .clone(); self.exec_function_inner(&func, Some(args)) diff --git a/src/config/env.rs b/src/config/env.rs index 1578168f..f0f9b9a3 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -663,10 +663,7 @@ pub fn ny_compiler_use_tmp_only() -> bool { /// Use Python MVP harness for Ny compiler (NYASH_NY_COMPILER_USE_PY=1). pub fn ny_compiler_use_py() -> bool { - std::env::var("NYASH_NY_COMPILER_USE_PY") - .ok() - .as_deref() - == Some("1") + std::env::var("NYASH_NY_COMPILER_USE_PY").ok().as_deref() == Some("1") } /// Macro pre-expand mode for selfhost (NYASH_MACRO_SELFHOST_PRE_EXPAND). @@ -677,26 +674,17 @@ pub fn macro_selfhost_pre_expand() -> Option { /// ScopeBox enable flag (NYASH_SCOPEBOX_ENABLE=1). pub fn scopebox_enable() -> bool { - std::env::var("NYASH_SCOPEBOX_ENABLE") - .ok() - .as_deref() - == Some("1") + std::env::var("NYASH_SCOPEBOX_ENABLE").ok().as_deref() == Some("1") } /// LoopForm normalize flag (NYASH_LOOPFORM_NORMALIZE=1). pub fn loopform_normalize() -> bool { - std::env::var("NYASH_LOOPFORM_NORMALIZE") - .ok() - .as_deref() - == Some("1") + std::env::var("NYASH_LOOPFORM_NORMALIZE").ok().as_deref() == Some("1") } /// Dev-only escape hatch: force inline selfhost path (NYASH_SELFHOST_INLINE_FORCE=1). pub fn selfhost_inline_force() -> bool { - std::env::var("NYASH_SELFHOST_INLINE_FORCE") - .ok() - .as_deref() - == Some("1") + std::env::var("NYASH_SELFHOST_INLINE_FORCE").ok().as_deref() == Some("1") } /// Unicode decode toggle for string literals (\uXXXX, optional surrogate pairs). diff --git a/src/grammar/generated.rs b/src/grammar/generated.rs index d94cb9c1..25bb6e51 100644 --- a/src/grammar/generated.rs +++ b/src/grammar/generated.rs @@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[ ]; pub fn lookup_keyword(word: &str) -> Option<&'static str> { for (k, t) in KEYWORDS { - if *k == word { return Some(*t); } + if *k == word { + return Some(*t); + } } None } pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ - "box", - "global", - "function", - "static", - "if", - "loop", - "break", - "return", - "print", - "nowait", - "include", - "local", - "outbox", - "try", - "throw", - "using", - "from", + "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", + "include", "local", "outbox", "try", "throw", "using", "from", ]; -pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ - "add", - "sub", - "mul", - "div", - "and", - "or", - "eq", - "ne", -]; \ No newline at end of file +pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"]; diff --git a/src/mir/basic_block.rs b/src/mir/basic_block.rs index 72c5fb80..10d1e339 100644 --- a/src/mir/basic_block.rs +++ b/src/mir/basic_block.rs @@ -244,10 +244,7 @@ impl BasicBlock { self.instructions .get(idx) .zip(self.instruction_spans.get(idx)) - .map(|(inst, span)| SpannedInstRef { - inst, - span: *span, - }) + .map(|(inst, span)| SpannedInstRef { inst, span: *span }) } /// Get span for terminator instruction @@ -268,19 +265,12 @@ impl BasicBlock { self.instructions .iter() .zip(self.instruction_spans.iter()) - .map(|(inst, span)| SpannedInstRef { - inst, - span: *span, - }) + .map(|(inst, span)| SpannedInstRef { inst, span: *span }) } /// Iterate instructions with index and span. - pub fn iter_spanned_enumerated( - &self, - ) -> impl Iterator)> { - self.iter_spanned() - .enumerate() - .map(|(idx, sp)| (idx, sp)) + pub fn iter_spanned_enumerated(&self) -> impl Iterator)> { + self.iter_spanned().enumerate().map(|(idx, sp)| (idx, sp)) } /// Iterate all instructions (including terminator) with spans. diff --git a/src/mir/join_ir/frontend/ast_lowerer.rs b/src/mir/join_ir/frontend/ast_lowerer.rs deleted file mode 100644 index ebb15818..00000000 --- a/src/mir/join_ir/frontend/ast_lowerer.rs +++ /dev/null @@ -1,2780 +0,0 @@ -//! AST/CFG → JoinIR Lowering -//! -//! このモジュールは AST/CFG ノードを JoinIR 命令に変換する。 -//! -//! ## 責務 -//! -//! - **If 文→Select/IfMerge 変換**: 条件分岐を JoinIR の継続渡しスタイルに変換 -//! - **Loop 文→loop_step/k_exit 変換**: ループを関数呼び出しと継続に正規化 -//! - **Break/Continue/Return→k_* 変換**: 制御フローを継続 ID として表現 -//! -//! ## Phase 34-2 での実装スコープ -//! -//! 最初は `IfSelectTest.*` 相当の tiny ケースのみ対応: -//! - Simple pattern: `if cond { return 1 } else { return 2 }` -//! -//! ## 設計原則 -//! -//! - **JoinIR = PHI 生成器**: 既存 PHI の変換器にはしない(Phase 33-10 原則) -//! - **段階的移行**: 既存 MIR Builder 経路は保持、新経路はデフォルト OFF -//! - **A/B テスト可能**: 既存経路と新経路の両方で実行して比較検証 - -use crate::mir::join_ir::{ConstValue, JoinFunction, JoinFuncId, JoinInst, JoinModule, MergePair, VarId}; -use std::collections::{BTreeMap, HashSet}; - -/// Phase 34-5: 式から値を抽出する際のコンテキスト -/// -/// extract_value ヘルパ関数で使用する内部状態 -struct ExtractCtx { - /// 次に使える ValueId カウンタ - next_var_id: u32, - /// 変数名 → ValueId のマップ(パラメータなど) - var_map: BTreeMap, -} - -impl ExtractCtx { - /// 新しいコンテキストを作成 - fn new(start_var_id: u32) -> Self { - Self { - next_var_id: start_var_id, - var_map: BTreeMap::new(), - } - } - - /// パラメータを登録 - fn register_param(&mut self, name: String, var_id: VarId) { - self.var_map.insert(name, var_id); - } - - /// 新しい ValueId を割り当て - fn alloc_var(&mut self) -> VarId { - let id = crate::mir::ValueId(self.next_var_id); - self.next_var_id += 1; - id - } - - /// 変数名から ValueId を取得 - fn get_var(&self, name: &str) -> Option { - self.var_map.get(name).copied() - } -} - -/// AST/CFG → JoinIR 変換器 -/// -/// Phase 34-2: Program(JSON v0) から tiny IfSelect ケースを JoinIR に変換 -pub struct AstToJoinIrLowerer { - next_func_id: u32, - next_var_id: u32, -} - -impl AstToJoinIrLowerer { - /// 新しい lowerer を作成 - pub fn new() -> Self { - Self { - next_func_id: 0, - next_var_id: 0, - } - } - - /// Program(JSON v0) → JoinModule - /// - /// Phase 34-2/34-3/34-4: simple/local/json_shape pattern に対応 - /// Phase 34-5: extract_value 統一化(Int/Var/Method 構造まで) - /// - /// # Panics - /// - /// - パターンに合わない Program(JSON) が来た場合(Phase 34 は tiny テスト専用) - /// - ループ・複数変数・副作用付き if(Phase 34-6 以降で対応予定) - pub fn lower_program_json(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から defs を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - // 2. 最初の関数定義を取得 - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - // 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5/34-7/34-8/41-4/45) - // test/local/_read_value_from_pair: If Return pattern - // simple: Loop pattern (Phase 34-7/34-8) - // parse_loop: Phase 41-4 NestedIfMerge pattern - // read_quoted_from: Phase 45 Guard if + Loop with break + accumulator - match func_name { - "test" | "local" | "_read_value_from_pair" => self.lower_if_return_pattern(program_json), - "simple" => { - // Phase 34-8: Loop パターンの詳細分析(break/continue 検出) - self.lower_loop_with_break_continue(program_json) - }, - "parse_loop" => { - // Phase 41-4: NestedIfMerge pattern (dev flag gated) - // Guard: HAKO_JOINIR_NESTED_IF=1 を要求 - if std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1") { - self.lower_nested_if_pattern(program_json) - } else { - // Dev flag が OFF の場合は panic(旧ルートにフォールバックするため) - panic!( - "parse_loop NestedIfMerge requires HAKO_JOINIR_NESTED_IF=1. \ - Set env var to enable Phase 41-4 route." - ); - } - }, - "read_quoted_from" => { - // Phase 45: read_quoted_from pattern (dev flag gated) - // Guard if + Loop with break + accumulator - if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1") { - self.lower_read_quoted_pattern(program_json) - } else { - panic!( - "read_quoted_from JoinIR requires HAKO_JOINIR_READ_QUOTED=1. \ - Set env var to enable Phase 45 route." - ); - } - }, - _ => panic!("Unsupported function: {}", func_name), - } - } - - /// If Return pattern の共通 lowering - /// - /// Phase 34-2/34-3/34-4: simple/local/json_shape 対応 - /// Phase 34-5: extract_value ベースに統一(Int/Var/Method 構造まで) - /// - /// - simple: `if cond { return 10 } else { return 20 }` - /// - local: `if cond { x=10 } else { x=20 }; return x` (意味論的) - /// - json_shape: `if at { return v.substring(0, at) } else { return v }` (Var/Method) - /// - /// すべて同じ JoinIR Select に正規化される - fn lower_if_return_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から defs を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - // 2. 最初の関数定義を取得 - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - let params = func_def["params"] - .as_array() - .expect("Function must have 'params' array"); - - // 3. body 内の If statement を検索(Phase 34-2/34-3 共通) - let body = &func_def["body"]["body"]; - let if_stmt = body - .as_array() - .and_then(|stmts| stmts.get(0)) - .expect("Function body must have at least one statement"); - - assert_eq!( - if_stmt["type"].as_str(), - Some("If"), - "First statement must be If" - ); - - // 4. then/else の Return から値を抽出 - let then_stmts = if_stmt["then"] - .as_array() - .expect("If must have 'then' array"); - let else_stmts = if_stmt["else"] - .as_array() - .expect("If must have 'else' array (simple pattern)"); - - let then_ret = then_stmts - .get(0) - .expect("then branch must have Return"); - let else_ret = else_stmts - .get(0) - .expect("else branch must have Return"); - - assert_eq!( - then_ret["type"].as_str(), - Some("Return"), - "then branch must be Return" - ); - assert_eq!( - else_ret["type"].as_str(), - Some("Return"), - "else branch must be Return" - ); - - // Phase 34-5: extract_value ベースの新実装 - // 5. ExtractCtx を作成し、パラメータを登録 - let func_id = self.next_func_id(); - - let mut ctx = ExtractCtx::new(params.len() as u32); - - // パラメータを ExtractCtx に登録(cond, at など) - for (i, param) in params.iter().enumerate() { - let param_name = param - .as_str() - .expect("Parameter must be string") - .to_string(); - ctx.register_param(param_name, crate::mir::ValueId(i as u32)); - } - - // Phase 34-6: cond/then/else の expr を extract_value で処理 - let (cond_var, cond_insts) = self.extract_value(&if_stmt["cond"], &mut ctx); - let (then_var, then_insts) = self.extract_value(&then_ret["expr"], &mut ctx); - let (else_var, else_insts) = self.extract_value(&else_ret["expr"], &mut ctx); - - // 7. Select 結果変数を割り当て - let result_var = ctx.alloc_var(); - - // 8. JoinIR 命令列を組み立て(cond → then → else → Select の順) - let mut insts = Vec::new(); - - // cond の計算命令を先頭に追加 - insts.extend(cond_insts); - - // then/else の計算命令を追加 - insts.extend(then_insts); - insts.extend(else_insts); - - // Select: result = Select(cond, then_var, else_var) - insts.push(JoinInst::Select { - dst: result_var, - cond: cond_var, - then_val: then_var, - else_val: else_var, - }); - - // Ret result - insts.push(JoinInst::Ret { - value: Some(result_var), - }); - - let func = JoinFunction { - id: func_id, - name: func_name.to_string(), - params: (0..params.len()) - .map(|i| crate::mir::ValueId(i as u32)) - .collect(), - body: insts, - exit_cont: None, // Phase 34-2/34-3: ルート関数なので exit_cont は None - }; - - let mut functions = BTreeMap::new(); - functions.insert(func_id, func); - - JoinModule { - functions, - entry: Some(func_id), - } - } - - /// Phase 34-8: Break/Continue 付きループの lowering(パターン検出) - /// - /// Loop body を解析して Break/Continue を検出し、適切な lowering 関数にディスパッチ - fn lower_loop_with_break_continue(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から defs を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - // 2. body を解析: Local 初期化 + Loop + Return - let body = &func_def["body"]["body"]; - let stmts = body - .as_array() - .expect("Function body must be array"); - - // Loop ノードを探す - let loop_node = stmts.iter().find(|stmt| { - stmt["type"].as_str() == Some("Loop") - }).expect("Loop node not found"); - - let loop_body = loop_node["body"] - .as_array() - .expect("Loop must have 'body' array"); - - // Break/Continue を検出 - let has_break = Self::has_break_in_loop_body(loop_body); - let has_continue = Self::has_continue_in_loop_body(loop_body); - - // パターンに応じてディスパッチ - if has_break && !has_continue { - self.lower_loop_break_pattern(program_json) - } else if has_continue && !has_break { - self.lower_loop_continue_pattern(program_json) - } else if has_break && has_continue { - panic!("Mixed Break/Continue pattern not yet supported in Phase 34-8"); - } else { - // Phase 34-7 の simple pattern - self.lower_loop_case_a_simple(program_json) - } - } - - /// Loop body に Break があるかチェック - fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool { - loop_body.iter().any(|stmt| { - if stmt["type"].as_str() == Some("If") { - if let Some(then_body) = stmt["then"].as_array() { - then_body.iter().any(|s| s["type"].as_str() == Some("Break")) - } else { - false - } - } else { - false - } - }) - } - - /// Loop body に Continue があるかチェック - fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool { - loop_body.iter().any(|stmt| { - if stmt["type"].as_str() == Some("If") { - if let Some(then_body) = stmt["then"].as_array() { - then_body.iter().any(|s| s["type"].as_str() == Some("Continue")) - } else { - false - } - } else { - false - } - }) - } - - /// Phase 34-7: Loop pattern の lowering(Case-A: tiny while loop) - /// - /// 対象: `LoopFrontendTest.simple(n)` 相当 - /// - local i = 0; local acc = 0; - /// - loop(i < n) { acc = acc + 1; i = i + 1; } - /// - return acc - /// - /// 目標 JoinIR: Phase 31 の LoopToJoinLowerer と同型 - /// - entry: 初期化 → loop_step 呼び出し - /// - loop_step: 条件チェック → 再帰 or k_exit - /// - k_exit: 結果を return - /// - /// Phase 34-7.4b: Local ノード処理(同名再宣言 = 再代入) - /// ループ本体lowering(Phase 34実装) - /// - /// # Phase 40拡張予定 - /// - /// この関数に以下を追加: - /// 1. `extract_if_in_loop_modified_vars()`呼び出し - /// 2. 検出された変数をloop exit PHI生成に使用 - /// - /// ```rust,ignore - /// // TODO(Phase 40-1): Add if-in-loop variable tracking - /// // let loop_vars = self.get_loop_carried_vars(&loop_ctx); - /// // let modified_in_if = self.extract_if_in_loop_modified_vars(&loop_body, &loop_vars); - /// // Pass modified_in_if to create_exit_function() for PHI generation - /// ``` - fn lower_loop_case_a_simple(&mut self, program_json: &serde_json::Value) -> JoinModule { - // TODO(Phase 40-1): Add if-in-loop variable tracking here - // Integration point for extract_if_in_loop_modified_vars() - - // 1. Program(JSON) から defs を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - let params = func_def["params"] - .as_array() - .expect("Function must have 'params' array"); - - // 2. ExtractCtx 作成とパラメータ登録 - let mut ctx = ExtractCtx::new(params.len() as u32); - for (i, param) in params.iter().enumerate() { - let param_name = param - .as_str() - .expect("Parameter must be string") - .to_string(); - ctx.register_param(param_name, crate::mir::ValueId(i as u32)); - } - - // 3. body を解析: Local 初期化 + Loop + Return - let body = &func_def["body"]["body"]; - let stmts = body - .as_array() - .expect("Function body must be array"); - - // Phase 34-7.4b: Local ノード処理(初期化) - // stmts[0]: Local i = 0 - // stmts[1]: Local acc = 0 - // stmts[2]: Loop { cond, body } - // stmts[3]: Return acc - - let mut init_insts = Vec::new(); - let mut loop_node_idx = None; - - for (idx, stmt) in stmts.iter().enumerate() { - let stmt_type = stmt["type"].as_str().expect("Statement must have type"); - - match stmt_type { - "Local" => { - // Phase 34-7.4b: Local ノード処理 - let var_name = stmt["name"] - .as_str() - .expect("Local must have 'name'") - .to_string(); - let expr = &stmt["expr"]; - - // extract_value で式を評価 - let (var_id, insts) = self.extract_value(expr, &mut ctx); - init_insts.extend(insts); - - // 同名再宣言 = var_map を更新(再代入の意味論) - ctx.register_param(var_name, var_id); - } - "Loop" => { - loop_node_idx = Some(idx); - break; // Loop 以降は別処理 - } - _ => panic!("Unexpected statement type before Loop: {}", stmt_type), - } - } - - let loop_node_idx = loop_node_idx.expect("Loop node not found"); - let loop_node = &stmts[loop_node_idx]; - - // 4. Loop の cond と body を抽出 - let loop_cond_expr = &loop_node["cond"]; - let loop_body_stmts = loop_node["body"] - .as_array() - .expect("Loop must have 'body' array"); - - // 5. Return 文を抽出(Loop の次) - let return_stmt = &stmts[loop_node_idx + 1]; - assert_eq!( - return_stmt["type"].as_str(), - Some("Return"), - "Expected Return after Loop" - ); - - // 6. JoinIR 生成: entry / loop_step / k_exit(Phase 31 と同じパターン) - // - // Phase 34-7: Jump = 早期 return、Call = 末尾再帰 - // - entry: 初期化 → Call(loop_step) - // - loop_step: - // Jump(k_exit, cond=exit_cond) // 条件が true なら抜ける - // body 処理 - // Call(loop_step) 末尾再帰 - // - k_exit: 結果を返す - - let entry_id = self.next_func_id(); - let loop_step_id = self.next_func_id(); - let k_exit_id = self.next_func_id(); - - // entry 関数: 初期化命令 + Call(loop_step) - let i_init = ctx.get_var("i").expect("i must be initialized"); - let acc_init = ctx.get_var("acc").expect("acc must be initialized"); - let n_param = ctx.get_var("n").expect("n must be parameter"); - - let loop_result = ctx.alloc_var(); - - let mut entry_body = init_insts; - entry_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_init, acc_init, n_param], - k_next: None, - dst: Some(loop_result), - }); - entry_body.push(JoinInst::Ret { - value: Some(loop_result), - }); - - let entry_func = JoinFunction { - id: entry_id, - name: func_name.to_string(), - params: (0..params.len()) - .map(|i| crate::mir::ValueId(i as u32)) - .collect(), - body: entry_body, - exit_cont: None, - }; - - // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i= n なら抜ける) - let false_const = step_ctx.alloc_var(); - let exit_cond = step_ctx.alloc_var(); - - let mut loop_step_body = cond_insts; - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: false_const, - value: ConstValue::Bool(false), - })); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: exit_cond, - op: crate::mir::join_ir::CompareOp::Eq, - lhs: cond_var, - rhs: false_const, - })); - - // 早期 return: exit_cond が true(i >= n)なら k_exit へ Jump - loop_step_body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), // Phase 34-7.5: 型変換ヘルパー使用 - args: vec![step_acc], - cond: Some(exit_cond), - }); - - // loop body を処理(Jump で抜けなかった場合のみ実行される) - for body_stmt in loop_body_stmts { - assert_eq!( - body_stmt["type"].as_str(), - Some("Local"), - "Loop body must contain only Local statements" - ); - - let var_name = body_stmt["name"] - .as_str() - .expect("Local must have 'name'") - .to_string(); - let expr = &body_stmt["expr"]; - - let (var_id, insts) = self.extract_value(expr, &mut step_ctx); - loop_step_body.extend(insts); - step_ctx.register_param(var_name, var_id); - } - - // body 処理後の i_next, acc_next を取得 - let i_next = step_ctx.get_var("i").expect("i must be updated in loop body"); - let acc_next = step_ctx.get_var("acc").expect("acc must be updated in loop body"); - - // loop_step を再帰的に Call(末尾再帰) - let recurse_result = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_next, acc_next, step_n], - k_next: None, - dst: Some(recurse_result), - }); - - loop_step_body.push(JoinInst::Ret { - value: Some(recurse_result), - }); - - let loop_step_func = JoinFunction { - id: loop_step_id, - name: format!("{}_loop_step", func_name), - params: vec![step_i, step_acc, step_n], - body: loop_step_body, - exit_cont: None, - }; - - // k_exit 関数: (acc) → Ret acc - let k_exit_acc = crate::mir::ValueId(0); - - let k_exit_func = JoinFunction { - id: k_exit_id, - name: format!("{}_k_exit", func_name), - params: vec![k_exit_acc], - body: vec![JoinInst::Ret { - value: Some(k_exit_acc), - }], - exit_cont: None, - }; - - // JoinModule を構築 - let mut functions = BTreeMap::new(); - functions.insert(entry_id, entry_func); - functions.insert(loop_step_id, loop_step_func); - functions.insert(k_exit_id, k_exit_func); - - JoinModule { - functions, - entry: Some(entry_id), - } - } - - /// Phase 34-8: Break pattern の lowering - /// - /// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }` - /// 目標: Jump(k_exit, cond=i>=n) で早期 return を実現 - fn lower_loop_break_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から基本情報を取得(Phase 34-7 と同じ) - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - let params = func_def["params"] - .as_array() - .expect("Function must have 'params' array"); - - // 2. ExtractCtx 作成とパラメータ登録 - let mut ctx = ExtractCtx::new(params.len() as u32); - for (i, param) in params.iter().enumerate() { - let param_name = param - .as_str() - .expect("Parameter must be string") - .to_string(); - ctx.register_param(param_name, crate::mir::ValueId(i as u32)); - } - - // 3. body を解析: Local 初期化 + Loop + Return - let body = &func_def["body"]["body"]; - let stmts = body - .as_array() - .expect("Function body must be array"); - - // Local ノード処理(初期化) - let mut init_insts = Vec::new(); - let mut loop_node_idx = None; - - for (idx, stmt) in stmts.iter().enumerate() { - let stmt_type = stmt["type"].as_str().expect("Statement must have type"); - - match stmt_type { - "Local" => { - let var_name = stmt["name"] - .as_str() - .expect("Local must have 'name'") - .to_string(); - let expr = &stmt["expr"]; - - let (var_id, insts) = self.extract_value(expr, &mut ctx); - init_insts.extend(insts); - ctx.register_param(var_name, var_id); - } - "Loop" => { - loop_node_idx = Some(idx); - break; - } - _ => panic!("Unexpected statement type before Loop: {}", stmt_type), - } - } - - let loop_node_idx = loop_node_idx.expect("Loop node not found"); - let loop_node = &stmts[loop_node_idx]; - - // 4. Loop body から Break If を探す - let loop_body = loop_node["body"] - .as_array() - .expect("Loop must have 'body' array"); - - let break_if_stmt = loop_body.iter().find(|stmt| { - stmt["type"].as_str() == Some("If") && - stmt["then"].as_array().map_or(false, |then| { - then.iter().any(|s| s["type"].as_str() == Some("Break")) - }) - }).expect("Break pattern must have If + Break"); - - let break_cond_expr = &break_if_stmt["cond"]; - - // 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造) - let entry_id = self.next_func_id(); - let loop_step_id = self.next_func_id(); - let k_exit_id = self.next_func_id(); - - // entry 関数 - let i_init = ctx.get_var("i").expect("i must be initialized"); - let acc_init = ctx.get_var("acc").expect("acc must be initialized"); - let n_param = ctx.get_var("n").expect("n must be parameter"); - - let loop_result = ctx.alloc_var(); - - let mut entry_body = init_insts; - entry_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_init, acc_init, n_param], - k_next: None, - dst: Some(loop_result), - }); - entry_body.push(JoinInst::Ret { - value: Some(loop_result), - }); - - let entry_func = JoinFunction { - id: entry_id, - name: func_name.to_string(), - params: (0..params.len()) - .map(|i| crate::mir::ValueId(i as u32)) - .collect(), - body: entry_body, - exit_cont: None, - }; - - // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=break_cond) → body → Call(loop_step) - let step_i = crate::mir::ValueId(0); - let step_acc = crate::mir::ValueId(1); - let step_n = crate::mir::ValueId(2); - - let mut step_ctx = ExtractCtx::new(3); - step_ctx.register_param("i".to_string(), step_i); - step_ctx.register_param("acc".to_string(), step_acc); - step_ctx.register_param("n".to_string(), step_n); - - // Break 条件を評価 - let (break_cond_var, break_cond_insts) = self.extract_value(break_cond_expr, &mut step_ctx); - - let mut loop_step_body = break_cond_insts; - - // 早期 return: break_cond が true なら k_exit へ Jump - loop_step_body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: vec![step_acc], - cond: Some(break_cond_var), - }); - - // loop body を処理(Break の後の Local 命令群) - for body_stmt in loop_body { - // If + Break はスキップ(Jump で処理済み) - if body_stmt["type"].as_str() == Some("If") { - continue; - } - - assert_eq!( - body_stmt["type"].as_str(), - Some("Local"), - "Loop body must contain only Local statements after If" - ); - - let var_name = body_stmt["name"] - .as_str() - .expect("Local must have 'name'") - .to_string(); - let expr = &body_stmt["expr"]; - - let (var_id, insts) = self.extract_value(expr, &mut step_ctx); - loop_step_body.extend(insts); - step_ctx.register_param(var_name, var_id); - } - - // body 処理後の i_next, acc_next を取得 - let i_next = step_ctx.get_var("i").expect("i must be updated in loop body"); - let acc_next = step_ctx.get_var("acc").expect("acc must be updated in loop body"); - - // loop_step を再帰的に Call(末尾再帰) - let recurse_result = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_next, acc_next, step_n], - k_next: None, - dst: Some(recurse_result), - }); - - loop_step_body.push(JoinInst::Ret { - value: Some(recurse_result), - }); - - let loop_step_func = JoinFunction { - id: loop_step_id, - name: format!("{}_loop_step", func_name), - params: vec![step_i, step_acc, step_n], - body: loop_step_body, - exit_cont: None, - }; - - // k_exit 関数 - let k_exit_acc = crate::mir::ValueId(0); - - let k_exit_func = JoinFunction { - id: k_exit_id, - name: format!("{}_k_exit", func_name), - params: vec![k_exit_acc], - body: vec![JoinInst::Ret { - value: Some(k_exit_acc), - }], - exit_cont: None, - }; - - // JoinModule を構築 - let mut functions = BTreeMap::new(); - functions.insert(entry_id, entry_func); - functions.insert(loop_step_id, loop_step_func); - functions.insert(k_exit_id, k_exit_func); - - JoinModule { - functions, - entry: Some(entry_id), - } - } - - /// Phase 34-8: Continue pattern の lowering - /// - /// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }` - /// 目標: Select(条件付き値更新)+ Call(末尾再帰)で実現 - fn lower_loop_continue_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から基本情報を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - let params = func_def["params"] - .as_array() - .expect("Function must have 'params' array"); - - // 2. ExtractCtx 作成とパラメータ登録 - let mut ctx = ExtractCtx::new(params.len() as u32); - for (i, param) in params.iter().enumerate() { - let param_name = param - .as_str() - .expect("Parameter must be string") - .to_string(); - ctx.register_param(param_name, crate::mir::ValueId(i as u32)); - } - - // 3. body を解析: Local 初期化 + Loop + Return - let body = &func_def["body"]["body"]; - let stmts = body - .as_array() - .expect("Function body must be array"); - - // Local ノード処理(初期化) - let mut init_insts = Vec::new(); - let mut loop_node_idx = None; - - for (idx, stmt) in stmts.iter().enumerate() { - let stmt_type = stmt["type"].as_str().expect("Statement must have type"); - - match stmt_type { - "Local" => { - let var_name = stmt["name"] - .as_str() - .expect("Local must have 'name'") - .to_string(); - let expr = &stmt["expr"]; - - let (var_id, insts) = self.extract_value(expr, &mut ctx); - init_insts.extend(insts); - ctx.register_param(var_name, var_id); - } - "Loop" => { - loop_node_idx = Some(idx); - break; - } - _ => panic!("Unexpected statement type before Loop: {}", stmt_type), - } - } - - let loop_node_idx = loop_node_idx.expect("Loop node not found"); - let loop_node = &stmts[loop_node_idx]; - - // 4. Loop の cond を取得 - let loop_cond_expr = &loop_node["cond"]; - - // 5. Loop body から Continue If を探す - let loop_body = loop_node["body"] - .as_array() - .expect("Loop must have 'body' array"); - - let continue_if_stmt = loop_body.iter().find(|stmt| { - stmt["type"].as_str() == Some("If") && - stmt["then"].as_array().map_or(false, |then| { - then.iter().any(|s| s["type"].as_str() == Some("Continue")) - }) - }).expect("Continue pattern must have If + Continue"); - - let continue_cond_expr = &continue_if_stmt["cond"]; - - // 6. JoinIR 生成: entry / loop_step / k_exit(3関数構造) - let entry_id = self.next_func_id(); - let loop_step_id = self.next_func_id(); - let k_exit_id = self.next_func_id(); - - // entry 関数 - let i_init = ctx.get_var("i").expect("i must be initialized"); - let acc_init = ctx.get_var("acc").expect("acc must be initialized"); - let n_param = ctx.get_var("n").expect("n must be parameter"); - - let loop_result = ctx.alloc_var(); - - let mut entry_body = init_insts; - entry_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_init, acc_init, n_param], - k_next: None, - dst: Some(loop_result), - }); - entry_body.push(JoinInst::Ret { - value: Some(loop_result), - }); - - let entry_func = JoinFunction { - id: entry_id, - name: func_name.to_string(), - params: (0..params.len()) - .map(|i| crate::mir::ValueId(i as u32)) - .collect(), - body: entry_body, - exit_cont: None, - }; - - // loop_step 関数: (i, acc, n) → exit check → i++ → Select → Call(loop_step) - let step_i = crate::mir::ValueId(0); - let step_acc = crate::mir::ValueId(1); - let step_n = crate::mir::ValueId(2); - - let mut step_ctx = ExtractCtx::new(3); - step_ctx.register_param("i".to_string(), step_i); - step_ctx.register_param("acc".to_string(), step_acc); - step_ctx.register_param("n".to_string(), step_n); - - let mut loop_step_body = Vec::new(); - - // 1. exit 条件チェック(!(i < n) = i >= n で抜ける) - let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx); - loop_step_body.extend(cond_insts); - - let false_const = step_ctx.alloc_var(); - let exit_cond = step_ctx.alloc_var(); - - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: false_const, - value: ConstValue::Bool(false), - })); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: exit_cond, - op: crate::mir::join_ir::CompareOp::Eq, - lhs: cond_var, - rhs: false_const, - })); - - loop_step_body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: vec![step_acc], - cond: Some(exit_cond), - }); - - // 2. Continue pattern 特有の処理: i のインクリメントが先 - // Loop body の最初の Local(i) を処理 - let first_local = loop_body.iter().find(|stmt| { - stmt["type"].as_str() == Some("Local") && - stmt["name"].as_str() == Some("i") - }).expect("Continue pattern must have i increment as first Local"); - - let i_expr = &first_local["expr"]; - let (i_next, i_insts) = self.extract_value(i_expr, &mut step_ctx); - loop_step_body.extend(i_insts); - step_ctx.register_param("i".to_string(), i_next); - - // 3. Continue 条件を評価 - let (continue_cond_var, continue_cond_insts) = self.extract_value(continue_cond_expr, &mut step_ctx); - loop_step_body.extend(continue_cond_insts); - - // 4. acc の更新値を計算(If の後の Local(acc) から) - let acc_update_local = loop_body.iter().find(|stmt| { - stmt["type"].as_str() == Some("Local") && - stmt["name"].as_str() == Some("acc") - }).expect("Continue pattern must have acc update Local"); - - let acc_expr = &acc_update_local["expr"]; - let (acc_increment, acc_insts) = self.extract_value(acc_expr, &mut step_ctx); - loop_step_body.extend(acc_insts); - - // 5. Select: Continue なら acc そのまま、そうでなければ acc の更新値 - let acc_next = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Select { - dst: acc_next, - cond: continue_cond_var, - then_val: step_acc, // Continue: 更新しない - else_val: acc_increment, // 通常: 更新 - }); - - // 6. 末尾再帰 - let recurse_result = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_next, acc_next, step_n], - k_next: None, - dst: Some(recurse_result), - }); - - loop_step_body.push(JoinInst::Ret { - value: Some(recurse_result), - }); - - let loop_step_func = JoinFunction { - id: loop_step_id, - name: format!("{}_loop_step", func_name), - params: vec![step_i, step_acc, step_n], - body: loop_step_body, - exit_cont: None, - }; - - // k_exit 関数 - let k_exit_acc = crate::mir::ValueId(0); - - let k_exit_func = JoinFunction { - id: k_exit_id, - name: format!("{}_k_exit", func_name), - params: vec![k_exit_acc], - body: vec![JoinInst::Ret { - value: Some(k_exit_acc), - }], - exit_cont: None, - }; - - // JoinModule を構築 - let mut functions = BTreeMap::new(); - functions.insert(entry_id, entry_func); - functions.insert(loop_step_id, loop_step_func); - functions.insert(k_exit_id, k_exit_func); - - JoinModule { - functions, - entry: Some(entry_id), - } - } - - /// 次の関数 ID を生成 - fn next_func_id(&mut self) -> JoinFuncId { - let id = JoinFuncId::new(self.next_func_id); - self.next_func_id += 1; - id - } - - // ======================================== - // Phase 45: read_quoted_from Pattern Lowering - // ======================================== - - /// Phase 45: read_quoted_from パターンの lowering - /// - /// # Pattern - /// - /// ```nyash,ignore - /// read_quoted_from(s, pos) { - /// local i = pos - /// if s.substring(i, i+1) != "\"" { return "" } // Guard if - /// i = i + 1 - /// local out = "" - /// local n = s.length() - /// loop (i < n) { - /// local ch = s.substring(i, i+1) - /// if ch == "\"" { break } // Found closing quote - /// // NOTE: Escape handling (if ch == "\\") has known PHI issue - /// // Variable reassignment inside if-block doesn't generate PHI - /// // This will be addressed by JoinIR IfMerge improvements - /// out = out + ch - /// i = i + 1 - /// } - /// return out - /// } - /// ``` - /// - /// # JoinIR Output - /// - /// - entry: Guard if check → Select(guard_passed ? Call(loop_step) : Return("")) - /// - loop_step: (i, out, n, s) → exit check → break check → body → Call(loop_step) - /// - k_exit: (out) → Return(out) - /// - /// # Dev Flag - /// - /// 環境変数 `HAKO_JOINIR_READ_QUOTED=1` が必須。 - pub(crate) fn lower_read_quoted_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から基本情報を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - let params = func_def["params"] - .as_array() - .expect("Function must have 'params' array"); - - // 2. ExtractCtx 作成とパラメータ登録 (s, pos) - let mut ctx = ExtractCtx::new(params.len() as u32); - for (i, param) in params.iter().enumerate() { - let param_name = param - .as_str() - .expect("Parameter must be string") - .to_string(); - ctx.register_param(param_name, crate::mir::ValueId(i as u32)); - } - - // 3. body を解析 - let body = &func_def["body"]["body"]; - let stmts = body - .as_array() - .expect("Function body must be array"); - - // 4. AST 構造を解析: - // - Local i = pos - // - If guard { return "" } - // - i = i + 1 - // - Local out = "" - // - Local n = s.length() - // - Loop { ... } - // - Return out - - // 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造) - let entry_id = self.next_func_id(); - let loop_step_id = self.next_func_id(); - let k_exit_id = self.next_func_id(); - - // ======================================== - // Entry 関数の構築 - // ======================================== - let mut entry_body = Vec::new(); - - // パラメータ取得 (s=0, pos=1) - let s_param = ctx.get_var("s").expect("s must be parameter"); - let pos_param = ctx.get_var("pos").expect("pos must be parameter"); - - // local i = pos - ctx.register_param("i".to_string(), pos_param); - let i_var = pos_param; - - // Guard: s.substring(i, i+1) を計算 - // i+1 を計算 - let one_const = ctx.alloc_var(); - entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: one_const, - value: ConstValue::Integer(1), - })); - - let i_plus_1 = ctx.alloc_var(); - entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst: i_plus_1, - op: crate::mir::join_ir::BinOpKind::Add, - lhs: i_var, - rhs: one_const, - })); - - // s.substring(i, i+1) を呼び出し - let first_char = ctx.alloc_var(); - entry_body.push(JoinInst::MethodCall { - dst: first_char, - receiver: s_param, - method: "substring".to_string(), - args: vec![i_var, i_plus_1], - }); - - // Guard 条件: first_char != '"' - let quote_const = ctx.alloc_var(); - entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: quote_const, - value: ConstValue::String("\"".to_string()), - })); - - let guard_cond = ctx.alloc_var(); - entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: guard_cond, - op: crate::mir::join_ir::CompareOp::Ne, - lhs: first_char, - rhs: quote_const, - })); - - // Guard 失敗時の戻り値: "" - let empty_string = ctx.alloc_var(); - entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: empty_string, - value: ConstValue::String("".to_string()), - })); - - // Guard 成功時: i = i + 1 - let i_after_guard = i_plus_1; // 既に計算済み - ctx.register_param("i".to_string(), i_after_guard); - - // local out = "" - let out_init = ctx.alloc_var(); - entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: out_init, - value: ConstValue::String("".to_string()), - })); - ctx.register_param("out".to_string(), out_init); - - // local n = s.length() - let n_var = ctx.alloc_var(); - entry_body.push(JoinInst::MethodCall { - dst: n_var, - receiver: s_param, - method: "length".to_string(), - args: vec![], - }); - ctx.register_param("n".to_string(), n_var); - - // Guard check → Jump to early return if guard fails - // 逆条件で Jump(guard_cond == true なら early return) - let k_guard_fail_id = self.next_func_id(); - - entry_body.push(JoinInst::Jump { - cont: k_guard_fail_id.as_cont(), - args: vec![empty_string], - cond: Some(guard_cond), - }); - - // Guard 成功: loop_step を呼び出し - let loop_result = ctx.alloc_var(); - entry_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_after_guard, out_init, n_var, s_param], - k_next: None, - dst: Some(loop_result), - }); - - entry_body.push(JoinInst::Ret { - value: Some(loop_result), - }); - - let entry_func = JoinFunction { - id: entry_id, - name: func_name.to_string(), - params: (0..params.len()) - .map(|i| crate::mir::ValueId(i as u32)) - .collect(), - body: entry_body, - exit_cont: None, - }; - - // ======================================== - // k_guard_fail 関数(Guard 失敗時 early return) - // ======================================== - let k_guard_fail_result = crate::mir::ValueId(0); - let k_guard_fail_func = JoinFunction { - id: k_guard_fail_id, - name: format!("{}_k_guard_fail", func_name), - params: vec![k_guard_fail_result], - body: vec![JoinInst::Ret { - value: Some(k_guard_fail_result), - }], - exit_cont: None, - }; - - // ======================================== - // loop_step 関数の構築 - // ======================================== - // params: (i, out, n, s) - let step_i = crate::mir::ValueId(0); - let step_out = crate::mir::ValueId(1); - let step_n = crate::mir::ValueId(2); - let step_s = crate::mir::ValueId(3); - - let mut step_ctx = ExtractCtx::new(4); - step_ctx.register_param("i".to_string(), step_i); - step_ctx.register_param("out".to_string(), step_out); - step_ctx.register_param("n".to_string(), step_n); - step_ctx.register_param("s".to_string(), step_s); - - let mut loop_step_body = Vec::new(); - - // 1. Exit 条件チェック: !(i < n) = i >= n で抜ける - let i_lt_n = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: i_lt_n, - op: crate::mir::join_ir::CompareOp::Lt, - lhs: step_i, - rhs: step_n, - })); - - let false_const = step_ctx.alloc_var(); - let exit_cond = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: false_const, - value: ConstValue::Bool(false), - })); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: exit_cond, - op: crate::mir::join_ir::CompareOp::Eq, - lhs: i_lt_n, - rhs: false_const, - })); - - // i >= n なら k_exit へ Jump - loop_step_body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: vec![step_out], - cond: Some(exit_cond), - }); - - // 2. ch = s.substring(i, i+1) - let step_one = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: step_one, - value: ConstValue::Integer(1), - })); - - let step_i_plus_1 = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst: step_i_plus_1, - op: crate::mir::join_ir::BinOpKind::Add, - lhs: step_i, - rhs: step_one, - })); - - let step_ch = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::MethodCall { - dst: step_ch, - receiver: step_s, - method: "substring".to_string(), - args: vec![step_i, step_i_plus_1], - }); - - // 3. Break 条件: ch == '"' - let step_quote = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: step_quote, - value: ConstValue::String("\"".to_string()), - })); - - let break_cond = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: break_cond, - op: crate::mir::join_ir::CompareOp::Eq, - lhs: step_ch, - rhs: step_quote, - })); - - // ch == '"' なら k_exit へ Jump (break) - loop_step_body.push(JoinInst::Jump { - cont: k_exit_id.as_cont(), - args: vec![step_out], - cond: Some(break_cond), - }); - - // ======================================== - // 4. Escape 処理: if ch == "\\" { i = i + 1; ch = s.substring(i, i+1) } - // ======================================== - // Phase 46: IfMerge で if-body 後の値をマージ - let enable_escape = crate::mir::join_ir::env_flag_is_1("HAKO_JOINIR_READ_QUOTED_IFMERGE"); - - // 条件と then 側の値を事前計算(投機的実行) - let step_backslash = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst: step_backslash, - value: ConstValue::String("\\".to_string()), - })); - - let esc_cond = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst: esc_cond, - op: crate::mir::join_ir::CompareOp::Eq, - lhs: step_ch, - rhs: step_backslash, - })); - - // i_esc = i + 1(then 側の i 値) - let i_esc = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst: i_esc, - op: crate::mir::join_ir::BinOpKind::Add, - lhs: step_i, - rhs: step_one, - })); - - // i_esc_plus_1 = i_esc + 1(substring の end 引数用) - let i_esc_plus_1 = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst: i_esc_plus_1, - op: crate::mir::join_ir::BinOpKind::Add, - lhs: i_esc, - rhs: step_one, - })); - - // ch_esc = s.substring(i_esc, i_esc+1)(then 側の ch 値) - let ch_esc = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::MethodCall { - dst: ch_esc, - receiver: step_s, - method: "substring".to_string(), - args: vec![i_esc, i_esc_plus_1], - }); - - // IfMerge: if-body 後の i と ch をマージ - let (i_after_esc, ch_merged) = if enable_escape { - let i_after_esc = step_ctx.alloc_var(); - let ch_merged = step_ctx.alloc_var(); - - loop_step_body.push(JoinInst::IfMerge { - cond: esc_cond, - merges: vec![ - MergePair { - dst: i_after_esc, - then_val: i_esc, - else_val: step_i, - }, - MergePair { - dst: ch_merged, - then_val: ch_esc, - else_val: step_ch, - }, - ], - k_next: None, - }); - - (i_after_esc, ch_merged) - } else { - // 旧パス: escape 未対応(step_i と step_ch をそのまま使う) - (step_i, step_ch) - }; - - // ======================================== - // 5. Accumulator: out = out + ch_merged - // ======================================== - let out_next = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst: out_next, - op: crate::mir::join_ir::BinOpKind::Add, // String concatenation - lhs: step_out, - rhs: ch_merged, // ← ch_merged を使う! - })); - - // ======================================== - // 6. i_next = i_after_esc + 1 - // ======================================== - let i_next = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst: i_next, - op: crate::mir::join_ir::BinOpKind::Add, - lhs: i_after_esc, // ← i_after_esc を使う! - rhs: step_one, - })); - - // 7. 末尾再帰: Call(loop_step) - let recurse_result = step_ctx.alloc_var(); - loop_step_body.push(JoinInst::Call { - func: loop_step_id, - args: vec![i_next, out_next, step_n, step_s], - k_next: None, - dst: Some(recurse_result), - }); - - loop_step_body.push(JoinInst::Ret { - value: Some(recurse_result), - }); - - let loop_step_func = JoinFunction { - id: loop_step_id, - name: format!("{}_loop_step", func_name), - params: vec![step_i, step_out, step_n, step_s], - body: loop_step_body, - exit_cont: None, - }; - - // ======================================== - // k_exit 関数の構築 - // ======================================== - let k_exit_out = crate::mir::ValueId(0); - let k_exit_func = JoinFunction { - id: k_exit_id, - name: format!("{}_k_exit", func_name), - params: vec![k_exit_out], - body: vec![JoinInst::Ret { - value: Some(k_exit_out), - }], - exit_cont: None, - }; - - // ======================================== - // JoinModule の構築 - // ======================================== - let mut functions = BTreeMap::new(); - functions.insert(entry_id, entry_func); - functions.insert(k_guard_fail_id, k_guard_fail_func); - functions.insert(loop_step_id, loop_step_func); - functions.insert(k_exit_id, k_exit_func); - - JoinModule { - functions, - entry: Some(entry_id), - } - } - - /// Phase 34-5: expr から「値を計算する JoinIR」と「結果を入れる ValueId」を返す - /// - /// ## 設計方針 - /// - /// - **Int literal**: 新しい dst を割り当てて Const 命令を生成 - /// - **Var 参照**: ctx の var_map から既存 ValueId を引く(追加命令なし) - /// - **Method 呼び出し**: pattern match のみ実装(JoinIR 出力はダミーでも可) - /// - /// ## 戻り値 - /// - /// - `ValueId`: 結果が入る変数 ID - /// - `Vec`: 値を計算するための JoinIR 命令列 - /// - /// ## Phase 34-5 実装範囲 - /// - /// - **段階 1**: Int / Var 対応(確実に実装) - /// - **段階 2**: Method 呼び出し pattern match(ダミー可) - /// - /// ## Panics - /// - /// - 未対応の expr 形式(Phase 34-5 は tiny テスト専用) - #[allow(dead_code)] // Phase 34-5.4 で lower_if_return_pattern から呼ばれる - fn extract_value( - &self, - expr: &serde_json::Value, - ctx: &mut ExtractCtx, - ) -> (VarId, Vec) { - let expr_type = expr["type"] - .as_str() - .expect("expr must have 'type' field"); - - match expr_type { - // 段階 1: Int literal 対応 - "Int" => { - let value = expr["value"] - .as_i64() - .expect("Int value must be i64"); - - let dst = ctx.alloc_var(); - let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst, - value: ConstValue::Integer(value), - }); - - (dst, vec![inst]) - } - - // Phase 34-8: Bool literal 対応 - "Bool" => { - let value = expr["value"] - .as_bool() - .expect("Bool value must be boolean"); - - let dst = ctx.alloc_var(); - let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { - dst, - value: ConstValue::Bool(value), - }); - - (dst, vec![inst]) - } - - // 段階 1: Var 参照対応 - "Var" => { - let var_name = expr["name"] - .as_str() - .expect("Var must have 'name' field"); - - let var_id = ctx.get_var(var_name) - .unwrap_or_else(|| panic!("Undefined variable: {}", var_name)); - - // Var 参照は追加命令なし(既存の ValueId を返すだけ) - (var_id, vec![]) - } - - // Phase 34-6: Method 呼び出し構造の完全実装 - "Method" => { - // receiver.method(args...) の構造を抽出 - let receiver_expr = &expr["receiver"]; - let method_name = expr["method"] - .as_str() - .expect("Method must have 'method' field"); - let args_array = expr["args"] - .as_array() - .expect("Method must have 'args' array"); - - // receiver を extract_value で処理 - let (receiver_var, mut receiver_insts) = self.extract_value(receiver_expr, ctx); - - // args を extract_value で処理 - let mut arg_vars = Vec::new(); - let mut arg_insts = Vec::new(); - for arg_expr in args_array { - let (arg_var, arg_inst) = self.extract_value(arg_expr, ctx); - arg_vars.push(arg_var); - arg_insts.extend(arg_inst); - } - - // MethodCall 命令を生成 - let dst = ctx.alloc_var(); - let method_call_inst = JoinInst::MethodCall { - dst, - receiver: receiver_var, - method: method_name.to_string(), - args: arg_vars, - }; - - // すべての命令を結合(receiver → args → MethodCall の順) - let mut insts = receiver_insts; - insts.extend(arg_insts); - insts.push(method_call_inst); - - (dst, insts) - } - - // Phase 34-7.4a: Binary 演算対応(i + 1 など) - "Binary" => { - let op_str = expr["op"] - .as_str() - .expect("Binary must have 'op' field"); - let lhs_expr = &expr["lhs"]; - let rhs_expr = &expr["rhs"]; - - // op 文字列を BinOpKind に変換 - let op = match op_str { - "+" => crate::mir::join_ir::BinOpKind::Add, - "-" => crate::mir::join_ir::BinOpKind::Sub, - "*" => crate::mir::join_ir::BinOpKind::Mul, - "/" => crate::mir::join_ir::BinOpKind::Div, - _ => panic!("Unsupported binary op: {}", op_str), - }; - - // lhs と rhs を再帰的に extract_value - let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx); - let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx); - - // 結果変数を割り当て - let dst = ctx.alloc_var(); - - // BinOp 命令を生成 - let binop_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { - dst, - op, - lhs: lhs_var, - rhs: rhs_var, - }); - - // すべての命令を結合(lhs → rhs → BinOp の順) - let mut insts = lhs_insts; - insts.extend(rhs_insts); - insts.push(binop_inst); - - (dst, insts) - } - - // Phase 34-7.4a: Compare 演算対応(i < n など) - "Compare" => { - let op_str = expr["op"] - .as_str() - .expect("Compare must have 'op' field"); - let lhs_expr = &expr["lhs"]; - let rhs_expr = &expr["rhs"]; - - // op 文字列を CompareOp に変換 - let op = match op_str { - "<" => crate::mir::join_ir::CompareOp::Lt, - "<=" => crate::mir::join_ir::CompareOp::Le, - ">" => crate::mir::join_ir::CompareOp::Gt, - ">=" => crate::mir::join_ir::CompareOp::Ge, - "==" => crate::mir::join_ir::CompareOp::Eq, - "!=" => crate::mir::join_ir::CompareOp::Ne, - _ => panic!("Unsupported compare op: {}", op_str), - }; - - // lhs と rhs を再帰的に extract_value - let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx); - let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx); - - // 結果変数を割り当て - let dst = ctx.alloc_var(); - - // Compare 命令を生成 - let compare_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { - dst, - op, - lhs: lhs_var, - rhs: rhs_var, - }); - - // すべての命令を結合(lhs → rhs → Compare の順) - let mut insts = lhs_insts; - insts.extend(rhs_insts); - insts.push(compare_inst); - - (dst, insts) - } - - _ => panic!("Unsupported expr type: {}", expr_type), - } - } - - // ======================================== - // Phase 40拡張予定: If-in-loop AST Lowering - // ======================================== - - /// Phase 40-1で実装予定: ループ内if文の変数追跡 - /// - /// # Purpose - /// - /// array_ext.filter等のif-in-loopパターンで、ループ内で修正される変数を - /// 追跡し、ループ出口PHI生成に使用する。 - /// - /// # Implementation Plan (Phase 40-1) - /// - /// ## Input - /// - `loop_body`: ループ本体AST(JSON v0形式) - /// - `loop_vars`: ループで使用される変数(Header PHIで定義) - /// - /// ## Output - /// - `HashSet`: if分岐内で修正された変数名セット - /// - /// ## Logic - /// 1. Recursive AST walk (helper: `extract_assigned_vars_from_body`) - /// 2. Detect assignments in if branches only - /// 3. Filter for variables in `loop_vars` (loop-carried variables) - /// 4. Return set of modified variable names - /// - /// ## Integration Point - /// - Call from: `lower_loop_case_a_simple()` or similar loop lowering - /// - Use output for: Loop exit PHI generation in `create_exit_function()` - /// - /// # Example - /// - /// ```nyash,ignore - /// local out = new ArrayBox() // loop_vars = {out, i} - /// local i = 0 - /// loop(i < n) { - /// if fn(arr[i]) { // ← この中の代入を検出 - /// out.push(arr[i]) // ← out修正検出 - /// } - /// i = i + 1 - /// } - /// // Result: extract_if_in_loop_modified_vars() = {out} - /// // → Loop exit PHI: phi out_exit = (out_header, out_loop_modified) - /// ``` - /// - /// # Replaces (Phase 40-1削除対象) - /// - /// - `if_phi::collect_assigned_vars()` (32 lines) - /// - Current callsites: loop_builder.rs:1069, 1075 - /// - この関数実装でcallsites削除可能 - /// - /// # See Also - /// - /// - Design: `docs/.../phase-39-if-phi-level2/joinir_extension_design.md` - /// - A/B test: array_ext.filter (Primary representative function) - /// - /// # TODO(Phase 40-1) - /// - /// ```rust,ignore - /// fn extract_if_in_loop_modified_vars( - /// &mut self, - /// loop_body: &serde_json::Value, - /// loop_vars: &HashSet, - /// ) -> HashSet { - /// // Step 1: Recursive AST walk - /// let all_assigned = self.extract_assigned_vars_from_body(loop_body); - /// - /// // Step 2: Filter for if-statement assignments only - /// let if_assigned = all_assigned.iter() - /// .filter(|var| self.is_assigned_in_if_branch(loop_body, var)) - /// .cloned() - /// .collect::>(); - /// - /// // Step 3: Filter for loop-carried variables - /// if_assigned.intersection(loop_vars).cloned().collect() - /// } - /// ``` - #[allow(dead_code)] - pub fn extract_if_in_loop_modified_vars( - &mut self, - loop_body: &serde_json::Value, - loop_vars: &HashSet, - ) -> HashSet { - - // Step 1: Recursive AST walk to collect all assigned variables - let all_assigned = self.extract_assigned_vars_from_body(loop_body); - - // Step 2: Filter for if-statement assignments only - let if_assigned = self.extract_if_assigned_vars(loop_body); - - // Step 3: Filter for loop-carried variables - // Return intersection of (if_assigned ∩ loop_vars) - if_assigned - .intersection(loop_vars) - .filter(|var| all_assigned.contains(*var)) - .cloned() - .collect() - } - - /// Phase 40-1: if文内での代入変数抽出 - /// - /// # Purpose - /// - /// loop body内のif文でのみ代入される変数を抽出する。 - /// これはloop exit PHI生成に必要。 - pub fn extract_if_assigned_vars(&mut self, body: &serde_json::Value) -> std::collections::HashSet { - use std::collections::HashSet; - let mut if_assigned = HashSet::new(); - - // Handle array of statements - if let Some(stmts) = body.as_array() { - for stmt in stmts { - if stmt.get("type").and_then(|t| t.as_str()) == Some("If") { - // Extract assignments from then/else branches - if let Some(then_body) = stmt.get("then") { - if_assigned.extend(self.extract_assigned_vars_from_body(then_body)); - } - if let Some(else_body) = stmt.get("else") { - if_assigned.extend(self.extract_assigned_vars_from_body(else_body)); - } - } - // Recursive: nested loops - else if stmt.get("type").and_then(|t| t.as_str()) == Some("Loop") { - if let Some(loop_body) = stmt.get("body") { - if_assigned.extend(self.extract_if_assigned_vars(loop_body)); - } - } - } - } - // Handle Block node - else if let Some(stmts) = body.get("body").and_then(|b| b.as_array()) { - for stmt in stmts { - if stmt.get("type").and_then(|t| t.as_str()) == Some("If") { - if let Some(then_body) = stmt.get("then") { - if_assigned.extend(self.extract_assigned_vars_from_body(then_body)); - } - if let Some(else_body) = stmt.get("else") { - if_assigned.extend(self.extract_assigned_vars_from_body(else_body)); - } - } - else if stmt.get("type").and_then(|t| t.as_str()) == Some("Loop") { - if let Some(loop_body) = stmt.get("body") { - if_assigned.extend(self.extract_if_assigned_vars(loop_body)); - } - } - } - } - - if_assigned - } - - /// Phase 40-1で実装予定: 再帰的AST走査(代入検出) - /// - /// # Purpose - /// - /// AST bodyを再帰的に走査し、代入文を検出する。 - /// - /// # Implementation Plan - /// - /// ## Recursive Descent - /// - Handle: "Local" assignments (`local x = ...` or `x = ...`) - /// - Handle: Nested blocks (`{ ... }`) - /// - Handle: If/Loop bodies (recursive call) - /// - /// ## Return - /// - `HashSet`: 代入された変数名全て - /// - /// # Example - /// - /// ```json - /// { - /// "type": "Block", - /// "body": [ - /// {"type": "Local", "name": "x", "expr": ...}, // x assigned - /// {"type": "If", "cond": ..., "then": [ - /// {"type": "Local", "name": "y", "expr": ...} // y assigned - /// ]} - /// ] - /// } - /// ``` - /// Result: {x, y} - /// - #[allow(dead_code)] - pub fn extract_assigned_vars_from_body( - &mut self, - body: &serde_json::Value, - ) -> std::collections::HashSet { - use std::collections::HashSet; - let mut assigned = HashSet::new(); - - // Handle array of statements - if let Some(stmts) = body.as_array() { - for stmt in stmts { - self.extract_assigned_vars_from_stmt(stmt, &mut assigned); - } - } - // Handle single statement (Block node) - else if let Some(stmts) = body.get("body").and_then(|b| b.as_array()) { - for stmt in stmts { - self.extract_assigned_vars_from_stmt(stmt, &mut assigned); - } - } - - assigned - } - - /// Phase 40-1: 再帰的AST走査ヘルパー(単一文処理) - /// - /// # Purpose - /// - /// 単一のAST文を処理し、代入された変数を収集する。 - fn extract_assigned_vars_from_stmt( - &mut self, - stmt: &serde_json::Value, - assigned: &mut std::collections::HashSet, - ) { - match stmt.get("type").and_then(|t| t.as_str()) { - Some("Local") => { - // local x = ... または x = ... - if let Some(name) = stmt.get("name").and_then(|n| n.as_str()) { - assigned.insert(name.to_string()); - } - } - Some("If") => { - // if 文の then/else 分岐を再帰処理 - if let Some(then_body) = stmt.get("then") { - assigned.extend(self.extract_assigned_vars_from_body(then_body)); - } - if let Some(else_body) = stmt.get("else") { - assigned.extend(self.extract_assigned_vars_from_body(else_body)); - } - } - Some("Loop") => { - // loop 文の body を再帰処理 - if let Some(loop_body) = stmt.get("body") { - assigned.extend(self.extract_assigned_vars_from_body(loop_body)); - } - } - Some("Block") => { - // { ... } ブロックの body を再帰処理 - if let Some(block_body) = stmt.get("body") { - assigned.extend(self.extract_assigned_vars_from_body(block_body)); - } - } - _ => { - // その他の文は無視(Return, Call, etc.) - } - } - } - - // ======================================== - // Phase 40-1 Experimental API - // ======================================== - - /// Phase 40-1実験用: array_ext.filter パターン専用lowering - /// - /// 通常の lower_loop_case_a_simple() に加えて、 - /// if-in-loop modified varsをJoinFuncMetaMapとして返す。 - /// - /// # Returns - /// - `JoinModule`: 通常のJoinIR module - /// - `JoinFuncMetaMap`: loop_step関数のif_modified_vars情報 - /// - /// # Phase 40-1専用 - /// この関数はPhase 40-1 A/Bテスト専用。 - /// 本番パスでは使わない(従来のlower_loop_case_a_simple()を使う)。 - #[allow(dead_code)] - pub fn lower_loop_with_if_meta( - &mut self, - program_json: &serde_json::Value, - ) -> (JoinModule, super::func_meta::JoinFuncMetaMap) { - use super::func_meta::{JoinFuncMeta, JoinFuncMetaMap}; - - // 1. 通常のJoinModule生成(既存ロジック流用) - let module = self.lower_loop_case_a_simple(program_json); - - // 2. loop body ASTからif-in-loop modified varsを抽出 - let loop_body = self.extract_loop_body_from_program(program_json); - let loop_vars = self.get_loop_carried_vars_from_program(program_json); - let if_modified = self.extract_if_in_loop_modified_vars(&loop_body, &loop_vars); - - // 3. JoinFuncMetaMap組み立て - // loop_step関数のIDを特定(通常は2番目の関数、名前に"loop_step"を含む) - let mut meta_map = JoinFuncMetaMap::new(); - if let Some((loop_step_id, _)) = module - .functions - .iter() - .find(|(_, func)| func.name.contains("loop_step")) - { - meta_map.insert( - *loop_step_id, - JoinFuncMeta { - if_modified_vars: if if_modified.is_empty() { - None - } else { - Some(if_modified) - }, - ..Default::default() - }, - ); - } - - (module, meta_map) - } - - /// loop body ASTを抽出するヘルパー - /// - /// # Purpose - /// Program → defs[0] → body → Loop statement → body のパスでloop bodyを取得 - fn extract_loop_body_from_program(&self, program_json: &serde_json::Value) -> serde_json::Value { - // Program → defs[0] → body → Loop statement → body - program_json["defs"][0]["body"] - .as_array() - .and_then(|stmts| stmts.iter().find(|s| s["type"] == "Loop")) - .and_then(|loop_stmt| loop_stmt.get("body")) - .cloned() - .unwrap_or(serde_json::Value::Null) - } - - /// loop-carried varsを抽出するヘルパー - /// - /// # Purpose - /// loop前に宣言された変数を収集(簡易実装) - /// より正確な実装はPhase 40-2で拡張 - fn get_loop_carried_vars_from_program( - &self, - program_json: &serde_json::Value, - ) -> HashSet { - let mut vars = HashSet::new(); - if let Some(body) = program_json["defs"][0]["body"].as_array() { - for stmt in body { - if stmt["type"] == "Local" { - if let Some(name) = stmt["name"].as_str() { - vars.insert(name.to_string()); - } - } - if stmt["type"] == "Loop" { - break; // loop以降は無視 - } - } - } - vars - } - - // ======================================== - // Phase 41-4: NestedIfMerge Lowering - // ======================================== - - /// Phase 41-4.2: ネスト if パターンの lowering - /// - /// # Purpose - /// - /// 深いネスト if(3-4レベル)を `NestedIfMerge` 命令に変換する。 - /// 対象: `ParserControlBox.parse_loop()` 関数 - /// - /// # Pattern - /// - /// ```nyash,ignore - /// // Level 0 (outer) - /// if cond0 { - /// // Level 1 - /// if cond1 { - /// // Level 2 - /// if cond2 { - /// // Level 3 (deepest) - /// x = new_value - /// } - /// } - /// } - /// // At merge point: x has PHI semantics - /// ``` - /// - /// # Output JoinIR - /// - /// - `NestedIfMerge { conds: [cond0, cond1, cond2], merges: [(x, new_value, old_x)], k_next }` - /// - /// # Dev Flag - /// - /// 環境変数 `HAKO_JOINIR_NESTED_IF=1` が必須。 - pub fn lower_nested_if_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { - // 1. Program(JSON) から基本情報を取得 - let defs = program_json["defs"] - .as_array() - .expect("Program(JSON v0) must have 'defs' array"); - - let func_def = defs - .get(0) - .expect("At least one function definition required"); - - let func_name = func_def["name"] - .as_str() - .expect("Function must have 'name'"); - - let params = func_def["params"] - .as_array() - .expect("Function must have 'params' array"); - - // 2. ExtractCtx 作成とパラメータ登録 - let mut ctx = ExtractCtx::new(params.len() as u32); - for (i, param) in params.iter().enumerate() { - let param_name = param - .as_str() - .expect("Parameter must be string") - .to_string(); - ctx.register_param(param_name, crate::mir::ValueId(i as u32)); - } - - // 3. body を解析 - let body = &func_def["body"]["body"]; - let stmts = body - .as_array() - .expect("Function body must be array"); - - // 4. nested if パターンを検出 - let nested_pattern = self.try_match_nested_if_pattern(stmts, &mut ctx); - - // 5. JoinIR 生成 - let func_id = self.next_func_id(); - let mut insts = Vec::new(); - - // 5.1. Local 初期化を処理(ネスト if の前にある Local 文) - for stmt in stmts { - let stmt_type = stmt["type"].as_str().unwrap_or(""); - if stmt_type == "Local" { - let var_name = stmt["name"] - .as_str() - .expect("Local must have 'name'") - .to_string(); - let expr = &stmt["expr"]; - let (var_id, local_insts) = self.extract_value(expr, &mut ctx); - insts.extend(local_insts); - ctx.register_param(var_name, var_id); - } else if stmt_type == "If" { - // If 文でパターン開始 - break; - } - } - - // 5.2. NestedIfMerge パターンがマッチした場合 - if let Some(pattern) = nested_pattern { - // 条件を評価 - let mut cond_vars = Vec::new(); - for cond_expr in &pattern.conds { - let (cond_var, cond_insts) = self.extract_value(cond_expr, &mut ctx); - insts.extend(cond_insts); - cond_vars.push(cond_var); - } - - // merges を構築 - let mut merges = Vec::new(); - for (var_name, then_expr, else_expr) in &pattern.merges { - let (then_var, then_insts) = self.extract_value(then_expr, &mut ctx); - insts.extend(then_insts); - - let else_var = if let Some(else_e) = else_expr { - let (e_var, e_insts) = self.extract_value(else_e, &mut ctx); - insts.extend(e_insts); - e_var - } else { - // else がない場合は既存の変数値を使用 - ctx.get_var(var_name) - .unwrap_or_else(|| panic!("Undefined variable in merge: {}", var_name)) - }; - - // 新しい dst を割り当て、merge 後の値として ctx に登録 - let dst = ctx.alloc_var(); - ctx.register_param(var_name.clone(), dst); - - merges.push(crate::mir::join_ir::MergePair { - dst, - then_val: then_var, - else_val: else_var, - }); - } - - // NestedIfMerge 命令を追加 - insts.push(JoinInst::NestedIfMerge { - conds: cond_vars, - merges, - k_next: None, // 関数末尾なので継続なし - }); - } else { - // パターンがマッチしない場合は panic(dev フラグ ON の時のみ到達) - panic!( - "lower_nested_if_pattern: No nested if pattern found in parse_loop. \ - Expected 2-4 level nested if structure." - ); - } - - // 5.3. Return 文を処理 - let return_stmt = stmts.iter().find(|s| s["type"].as_str() == Some("Return")); - if let Some(ret) = return_stmt { - let (ret_var, ret_insts) = self.extract_value(&ret["expr"], &mut ctx); - insts.extend(ret_insts); - insts.push(JoinInst::Ret { - value: Some(ret_var), - }); - } else { - // Return がない場合は void return - insts.push(JoinInst::Ret { value: None }); - } - - // 6. JoinFunction 構築 - let func = JoinFunction { - id: func_id, - name: func_name.to_string(), - params: (0..params.len()) - .map(|i| crate::mir::ValueId(i as u32)) - .collect(), - body: insts, - exit_cont: None, - }; - - let mut functions = BTreeMap::new(); - functions.insert(func_id, func); - - JoinModule { - functions, - entry: Some(func_id), - } - } - - /// Phase 41-4.2: ネスト if パターンの検出 - /// - /// # Purpose - /// - /// AST から 2-4 レベルのネスト if を検出し、`NestedIfPattern` として返す。 - /// - /// # Algorithm - /// - /// 1. 最初の If 文を探す - /// 2. then 分岐を再帰的に解析し、ネストレベルをカウント - /// 3. 各レベルの条件式と変数代入を収集 - /// 4. 最大 4 レベルまで対応(それ以上は未サポート) - /// - /// # Returns - /// - /// - `Some(NestedIfPattern)`: パターンがマッチした場合 - /// - `None`: パターンがマッチしない場合 - fn try_match_nested_if_pattern( - &self, - stmts: &[serde_json::Value], - _ctx: &mut ExtractCtx, - ) -> Option { - // 1. 最初の If 文を探す - let first_if = stmts.iter().find(|s| s["type"].as_str() == Some("If"))?; - - // 2. ネスト構造を再帰的に解析 - let mut conds = Vec::new(); - let mut merges = Vec::new(); - - self.collect_nested_if_structure(first_if, &mut conds, &mut merges, 0); - - // 3. 少なくとも 2 レベル以上のネストが必要 - if conds.len() < 2 { - return None; - } - - Some(NestedIfPattern { conds, merges }) - } - - /// Phase 41-4.2: ネスト if 構造の再帰収集 - /// - /// # Arguments - /// - /// - `if_stmt`: 現在の If ノード - /// - `conds`: 条件式リスト(外側から内側へ) - /// - `merges`: 変数代入リスト - /// - `depth`: 現在のネストレベル(0から開始) - fn collect_nested_if_structure( - &self, - if_stmt: &serde_json::Value, - conds: &mut Vec, - merges: &mut Vec<(String, serde_json::Value, Option)>, - depth: usize, - ) { - // 最大 4 レベルまで - if depth >= 4 { - return; - } - - // 条件式を追加 - if let Some(cond) = if_stmt.get("cond") { - conds.push(cond.clone()); - } - - // then 分岐を解析 - if let Some(then_body) = if_stmt.get("then").and_then(|t| t.as_array()) { - for stmt in then_body { - let stmt_type = stmt["type"].as_str().unwrap_or(""); - - match stmt_type { - "If" => { - // ネスト if: 再帰処理 - self.collect_nested_if_structure(stmt, conds, merges, depth + 1); - } - "Local" => { - // 変数代入を記録 - if let Some(var_name) = stmt["name"].as_str() { - let expr = stmt.get("expr").cloned().unwrap_or(serde_json::Value::Null); - // then 値のみ記録(else はパターン解析時に決定) - merges.push((var_name.to_string(), expr, None)); - } - } - "Return" => { - // 早期 return は無視(NestedIfMerge では扱わない) - } - _ => {} - } - } - } - - // else 分岐の代入も収集(存在する場合) - if let Some(else_body) = if_stmt.get("else").and_then(|e| e.as_array()) { - for stmt in else_body { - if stmt["type"].as_str() == Some("Local") { - if let Some(var_name) = stmt["name"].as_str() { - // 既存の merge エントリを探して else 値を更新 - for (name, _, else_val) in merges.iter_mut() { - if name == var_name { - let expr = stmt.get("expr").cloned().unwrap_or(serde_json::Value::Null); - *else_val = Some(expr); - break; - } - } - } - } - } - } - } -} - -/// Phase 41-4.2: ネスト if パターン構造 -/// -/// AST から検出されたネスト if パターンを表現する。 -#[derive(Debug)] -struct NestedIfPattern { - /// 条件式リスト(外側から内側へ) - conds: Vec, - /// 変数代入リスト: (変数名, then値, else値) - merges: Vec<(String, serde_json::Value, Option)>, -} - -impl Default for AstToJoinIrLowerer { - fn default() -> Self { - Self::new() - } -} - -// Phase 34-2: IfSelectTest.* simple pattern 実装 -// Phase 34-3: local pattern 対応(simple と同じ JoinIR 出力) -// Phase 34-4: Stage-1/meta 実用関数対応(JsonShapeToMap._read_value_from_pair/1) -// Phase 34-5: extract_value 統一化(Int/Var/Method 構造まで) -// Phase 34-6: MethodCall 構造の JoinIR への明示と JoinIR→MIR 変換側での Method/Call 意味論実装 -// Phase 34-7: tiny while loop 対応(Case-A simple pattern) -// Phase 34-8: Break/Continue 付きループ対応(Select + Jump で制御フロー表現) -// Phase 41-4: NestedIfMerge 対応(parse_loop 向け深いネスト if) -// Phase 45: read_quoted_from パターン対応(Guard if + Loop with break + accumulator) - -#[cfg(test)] -mod tests { - use super::*; - use crate::mir::join_ir::JoinInst; - - /// Phase 41-4.4: NestedIfMerge パターン検出のテスト - /// - /// 2レベル以上のネスト if が検出されることを確認する。 - #[test] - fn test_nested_if_pattern_detection_two_levels() { - // 2レベルのネスト if: if a { if b { x = 1 } } - let program_json = serde_json::json!({ - "defs": [{ - "name": "parse_loop", - "params": ["src", "i"], - "body": { - "body": [ - { - "type": "Local", - "name": "x", - "expr": {"type": "Int", "value": 0} - }, - { - "type": "If", - "cond": {"type": "Var", "name": "a_cond"}, - "then": [ - { - "type": "If", - "cond": {"type": "Var", "name": "b_cond"}, - "then": [ - { - "type": "Local", - "name": "x", - "expr": {"type": "Int", "value": 42} - } - ], - "else": [] - } - ], - "else": [] - }, - { - "type": "Return", - "expr": {"type": "Var", "name": "x"} - } - ] - } - }] - }); - - // ExtractCtx を用意 - let mut ctx = ExtractCtx::new(2); - ctx.register_param("src".to_string(), crate::mir::ValueId(0)); - ctx.register_param("i".to_string(), crate::mir::ValueId(1)); - ctx.register_param("a_cond".to_string(), crate::mir::ValueId(2)); - ctx.register_param("b_cond".to_string(), crate::mir::ValueId(3)); - - // AST から body を取得 - let stmts = program_json["defs"][0]["body"]["body"] - .as_array() - .expect("body must be array"); - - // パターン検出 - let lowerer = AstToJoinIrLowerer::new(); - let pattern = lowerer.try_match_nested_if_pattern(stmts, &mut ctx); - - assert!(pattern.is_some(), "2-level nested if should be detected"); - let pattern = pattern.unwrap(); - - // 2つの条件が検出される - assert_eq!(pattern.conds.len(), 2, "Should have 2 conditions"); - - // 1つの変数代入が検出される - assert_eq!(pattern.merges.len(), 1, "Should have 1 merge"); - assert_eq!(pattern.merges[0].0, "x", "Merged variable should be 'x'"); - } - - /// Phase 41-4.4: NestedIfMerge lowering のテスト(dev flag 必要) - /// - /// HAKO_JOINIR_NESTED_IF=1 が設定されている場合のみ実行。 - /// 設定されていない場合はスキップ。 - #[test] - fn test_nested_if_merge_lowering() { - // Dev flag がない場合はスキップ - if std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() != Some("1") { - eprintln!( - "[Phase 41-4] Skipping test_nested_if_merge_lowering: \ - Set HAKO_JOINIR_NESTED_IF=1 to enable" - ); - return; - } - - // 2レベルのネスト if - let program_json = serde_json::json!({ - "defs": [{ - "name": "parse_loop", - "params": ["src", "i"], - "body": { - "body": [ - { - "type": "Local", - "name": "x", - "expr": {"type": "Int", "value": 0} - }, - { - "type": "If", - "cond": {"type": "Compare", "op": ">", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 0}}, - "then": [ - { - "type": "If", - "cond": {"type": "Compare", "op": "<", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 100}}, - "then": [ - { - "type": "Local", - "name": "x", - "expr": {"type": "Int", "value": 42} - } - ], - "else": [] - } - ], - "else": [] - }, - { - "type": "Return", - "expr": {"type": "Var", "name": "x"} - } - ] - } - }] - }); - - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_nested_if_pattern(&program_json); - - // JoinModule に 1 つの関数がある - assert_eq!(join_module.functions.len(), 1, "Should have 1 function"); - - // entry が設定されている - assert!(join_module.entry.is_some(), "Entry should be set"); - - // NestedIfMerge 命令が含まれている - let entry_id = join_module.entry.unwrap(); - let entry_func = join_module.functions.get(&entry_id).expect("Entry function"); - - let has_nested_if_merge = entry_func.body.iter().any(|inst| { - matches!(inst, JoinInst::NestedIfMerge { .. }) - }); - - assert!( - has_nested_if_merge, - "JoinFunction should contain NestedIfMerge instruction" - ); - - eprintln!("[Phase 41-4] test_nested_if_merge_lowering PASSED"); - } - - /// Phase 41-4.4: 単一レベル if はマッチしないことを確認 - #[test] - fn test_nested_if_pattern_single_level_does_not_match() { - // 1レベルのif: if a { x = 1 } - let program_json = serde_json::json!({ - "defs": [{ - "name": "test", - "params": [], - "body": { - "body": [ - { - "type": "If", - "cond": {"type": "Var", "name": "a"}, - "then": [ - { - "type": "Local", - "name": "x", - "expr": {"type": "Int", "value": 1} - } - ], - "else": [] - } - ] - } - }] - }); - - let mut ctx = ExtractCtx::new(0); - let stmts = program_json["defs"][0]["body"]["body"] - .as_array() - .expect("body"); - - let lowerer = AstToJoinIrLowerer::new(); - let pattern = lowerer.try_match_nested_if_pattern(stmts, &mut ctx); - - // 1レベルはマッチしない - assert!( - pattern.is_none(), - "Single-level if should NOT match NestedIfMerge pattern" - ); - } - - // ======================================== - // Phase 45: read_quoted_from Pattern Tests - // ======================================== - - /// Phase 45: read_quoted_from lowering のテスト(dev flag 必要) - /// - /// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。 - /// 設定されていない場合はスキップ。 - #[test] - fn test_read_quoted_from_lowering() { - // Dev flag がない場合はスキップ - if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { - eprintln!( - "[Phase 45] Skipping test_read_quoted_from_lowering: \ - Set HAKO_JOINIR_READ_QUOTED=1 to enable" - ); - return; - } - - // read_quoted_from パターンの JSON 表現 - let program_json = serde_json::json!({ - "defs": [{ - "name": "read_quoted_from", - "params": ["s", "pos"], - "body": { - "body": [ - // local i = pos - { - "type": "Local", - "name": "i", - "expr": {"type": "Var", "name": "pos"} - }, - // if s.substring(i, i+1) != '"' { return "" } - { - "type": "If", - "cond": { - "type": "Compare", - "op": "!=", - "lhs": { - "type": "Method", - "receiver": {"type": "Var", "name": "s"}, - "method": "substring", - "args": [ - {"type": "Var", "name": "i"}, - {"type": "Binary", "op": "+", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 1}} - ] - }, - "rhs": {"type": "String", "value": "\""} - }, - "then": [ - {"type": "Return", "expr": {"type": "String", "value": ""}} - ], - "else": [] - }, - // i = i + 1 - { - "type": "Local", - "name": "i", - "expr": {"type": "Binary", "op": "+", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 1}} - }, - // local out = "" - { - "type": "Local", - "name": "out", - "expr": {"type": "String", "value": ""} - }, - // local n = s.length() - { - "type": "Local", - "name": "n", - "expr": { - "type": "Method", - "receiver": {"type": "Var", "name": "s"}, - "method": "length", - "args": [] - } - }, - // Loop (simplified, loop body handled by lower_read_quoted_pattern) - { - "type": "Loop", - "cond": { - "type": "Compare", - "op": "<", - "lhs": {"type": "Var", "name": "i"}, - "rhs": {"type": "Var", "name": "n"} - }, - "body": [] - }, - // return out - { - "type": "Return", - "expr": {"type": "Var", "name": "out"} - } - ] - } - }] - }); - - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_read_quoted_pattern(&program_json); - - // JoinModule に 4 つの関数がある(entry, k_guard_fail, loop_step, k_exit) - assert_eq!(join_module.functions.len(), 4, "Should have 4 functions"); - - // entry が設定されている - assert!(join_module.entry.is_some(), "Entry should be set"); - - // 関数名を確認 - let func_names: Vec<&str> = join_module - .functions - .values() - .map(|f| f.name.as_str()) - .collect(); - - assert!( - func_names.iter().any(|n| *n == "read_quoted_from"), - "Should have entry function" - ); - assert!( - func_names.iter().any(|n| n.contains("loop_step")), - "Should have loop_step function" - ); - assert!( - func_names.iter().any(|n| n.contains("k_exit")), - "Should have k_exit function" - ); - assert!( - func_names.iter().any(|n| n.contains("k_guard_fail")), - "Should have k_guard_fail function" - ); - - eprintln!("[Phase 45] test_read_quoted_from_lowering PASSED"); - eprintln!("[Phase 45] Functions: {:?}", func_names); - } - - /// Phase 45: lowering で生成される JoinInst の種類確認 - #[test] - fn test_read_quoted_from_lowering_instructions() { - // Dev flag がない場合はスキップ - if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { - return; - } - - // 簡易的な program_json(実際の AST 構造は不要、パターンが認識されればOK) - let program_json = serde_json::json!({ - "defs": [{ - "name": "read_quoted_from", - "params": ["s", "pos"], - "body": { "body": [] } - }] - }); - - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_read_quoted_pattern(&program_json); - - // entry 関数の命令を確認 - let entry_id = join_module.entry.unwrap(); - let entry_func = join_module.functions.get(&entry_id).expect("Entry function"); - - // entry には以下が含まれる: - // - Compute (Const, BinOp, Compare) - // - MethodCall (substring, length) - // - Jump (guard check) - // - Call (loop_step) - // - Ret - - let has_method_call = entry_func.body.iter().any(|inst| { - matches!(inst, JoinInst::MethodCall { .. }) - }); - let has_jump = entry_func.body.iter().any(|inst| { - matches!(inst, JoinInst::Jump { .. }) - }); - let has_call = entry_func.body.iter().any(|inst| { - matches!(inst, JoinInst::Call { .. }) - }); - let has_ret = entry_func.body.iter().any(|inst| { - matches!(inst, JoinInst::Ret { .. }) - }); - - assert!(has_method_call, "Entry should have MethodCall for substring/length"); - assert!(has_jump, "Entry should have Jump for guard check"); - assert!(has_call, "Entry should have Call for loop_step"); - assert!(has_ret, "Entry should have Ret"); - - // loop_step 関数の命令を確認 - let loop_step_func = join_module - .functions - .values() - .find(|f| f.name.contains("loop_step")) - .expect("loop_step function"); - - let loop_has_jump = loop_step_func.body.iter().filter(|inst| { - matches!(inst, JoinInst::Jump { .. }) - }).count(); - - // loop_step には 2 つの Jump がある: exit check と break check - assert_eq!(loop_has_jump, 2, "loop_step should have 2 Jumps (exit check, break check)"); - - eprintln!("[Phase 45] test_read_quoted_from_lowering_instructions PASSED"); - } -} diff --git a/src/mir/join_ir/frontend/ast_lowerer/README.md b/src/mir/join_ir/frontend/ast_lowerer/README.md new file mode 100644 index 00000000..bf8d98bf --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/README.md @@ -0,0 +1,22 @@ +AST/CFG → JoinIR frontend lowering layer + +Scope: +- Normalize tiny AST/CFG patterns into JoinIR instructions without touching MIR or runtime concerns. +- Keep pattern-specific lowering isolated (if/return, loop variants, nested-if, read_quoted_from). +- Centralize expression/value extraction and small analysis helpers (if-in-loop var tracking). + +Boundaries: +- No code generation beyond JoinIR; MIR/VM concerns belong to the bridge layer. +- Dev-flagged paths stay opt-in (HAKO_JOINIR_NESTED_IF, HAKO_JOINIR_READ_QUOTED*). +- Avoid hard-coded semantics; prefer structural pattern detection and reusable helpers. + +Layout: +- `mod.rs`: public surface + entry dispatch + shared counters +- `context.rs`: `ExtractCtx` (var ids) helpers +- `expr.rs`: expression-to-JoinIR value extraction +- `if_return.rs`: simple if→Select lowering +- `loop_patterns.rs`: loop variants (simple/break/continue) +- `read_quoted.rs`: read_quoted_from lowering +- `nested_if.rs`: NestedIfMerge lowering/detection +- `analysis.rs`: loop if-var analysis + metadata helpers +- `tests.rs`: frontend lowering tests gated by dev flags diff --git a/src/mir/join_ir/frontend/ast_lowerer/analysis.rs b/src/mir/join_ir/frontend/ast_lowerer/analysis.rs new file mode 100644 index 00000000..6a61678e --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/analysis.rs @@ -0,0 +1,342 @@ +use super::{AstToJoinIrLowerer, HashSet, JoinModule}; +use crate::mir::join_ir::frontend::func_meta::{JoinFuncMeta, JoinFuncMetaMap}; + +impl AstToJoinIrLowerer { + /// Phase 40-1で実装予定: ループ内if文の変数追跡 + /// + /// # Purpose + /// + /// array_ext.filter等のif-in-loopパターンで、ループ内で修正される変数を + /// 追跡し、ループ出口PHI生成に使用する。 + /// + /// # Implementation Plan (Phase 40-1) + /// + /// ## Input + /// - `loop_body`: ループ本体AST(JSON v0形式) + /// - `loop_vars`: ループで使用される変数(Header PHIで定義) + /// + /// ## Output + /// - `HashSet`: if分岐内で修正された変数名セット + /// + /// ## Logic + /// 1. Recursive AST walk (helper: `extract_assigned_vars_from_body`) + /// 2. Detect assignments in if branches only + /// 3. Filter for variables in `loop_vars` (loop-carried variables) + /// 4. Return set of modified variable names + /// + /// ## Integration Point + /// - Call from: `lower_loop_case_a_simple()` or similar loop lowering + /// - Use output for: Loop exit PHI generation in `create_exit_function()` + /// + /// # Example + /// + /// ```nyash,ignore + /// local out = new ArrayBox() // loop_vars = {out, i} + /// local i = 0 + /// loop(i < n) { + /// if fn(arr[i]) { // ← この中の代入を検出 + /// out.push(arr[i]) // ← out修正検出 + /// } + /// i = i + 1 + /// } + /// // Result: extract_if_in_loop_modified_vars() = {out} + /// // → Loop exit PHI: phi out_exit = (out_header, out_loop_modified) + /// ``` + /// + /// # Replaces (Phase 40-1削除対象) + /// + /// - `if_phi::collect_assigned_vars()` (32 lines) + /// - Current callsites: loop_builder.rs:1069, 1075 + /// - この関数実装でcallsites削除可能 + /// + /// # See Also + /// + /// - Design: `docs/.../phase-39-if-phi-level2/joinir_extension_design.md` + /// - A/B test: array_ext.filter (Primary representative function) + /// + /// # TODO(Phase 40-1) + /// + /// ```rust,ignore + /// fn extract_if_in_loop_modified_vars( + /// &mut self, + /// loop_body: &serde_json::Value, + /// loop_vars: &HashSet, + /// ) -> HashSet { + /// // Step 1: Recursive AST walk + /// let all_assigned = self.extract_assigned_vars_from_body(loop_body); + /// + /// // Step 2: Filter for if-statement assignments only + /// let if_assigned = all_assigned.iter() + /// .filter(|var| self.is_assigned_in_if_branch(loop_body, var)) + /// .cloned() + /// .collect::>(); + /// + /// // Step 3: Filter for loop-carried variables + /// if_assigned.intersection(loop_vars).cloned().collect() + /// } + /// ``` + #[allow(dead_code)] + pub fn extract_if_in_loop_modified_vars( + &mut self, + loop_body: &serde_json::Value, + loop_vars: &HashSet, + ) -> HashSet { + // Step 1: Recursive AST walk to collect all assigned variables + let all_assigned = self.extract_assigned_vars_from_body(loop_body); + + // Step 2: Filter for if-statement assignments only + let if_assigned = self.extract_if_assigned_vars(loop_body); + + // Step 3: Filter for loop-carried variables + // Return intersection of (if_assigned ∩ loop_vars) + if_assigned + .intersection(loop_vars) + .filter(|var| all_assigned.contains(*var)) + .cloned() + .collect() + } + + /// Phase 40-1: if文内での代入変数抽出 + /// + /// # Purpose + /// + /// loop body内のif文でのみ代入される変数を抽出する。 + /// これはloop exit PHI生成に必要。 + pub fn extract_if_assigned_vars( + &mut self, + body: &serde_json::Value, + ) -> std::collections::HashSet { + use std::collections::HashSet; + let mut if_assigned = HashSet::new(); + + // Handle array of statements + if let Some(stmts) = body.as_array() { + for stmt in stmts { + if stmt.get("type").and_then(|t| t.as_str()) == Some("If") { + // Extract assignments from then/else branches + if let Some(then_body) = stmt.get("then") { + if_assigned.extend(self.extract_assigned_vars_from_body(then_body)); + } + if let Some(else_body) = stmt.get("else") { + if_assigned.extend(self.extract_assigned_vars_from_body(else_body)); + } + } + // Recursive: nested loops + else if stmt.get("type").and_then(|t| t.as_str()) == Some("Loop") { + if let Some(loop_body) = stmt.get("body") { + if_assigned.extend(self.extract_if_assigned_vars(loop_body)); + } + } + } + } + // Handle Block node + else if let Some(stmts) = body.get("body").and_then(|b| b.as_array()) { + for stmt in stmts { + if stmt.get("type").and_then(|t| t.as_str()) == Some("If") { + if let Some(then_body) = stmt.get("then") { + if_assigned.extend(self.extract_assigned_vars_from_body(then_body)); + } + if let Some(else_body) = stmt.get("else") { + if_assigned.extend(self.extract_assigned_vars_from_body(else_body)); + } + } else if stmt.get("type").and_then(|t| t.as_str()) == Some("Loop") { + if let Some(loop_body) = stmt.get("body") { + if_assigned.extend(self.extract_if_assigned_vars(loop_body)); + } + } + } + } + + if_assigned + } + + /// Phase 40-1で実装予定: 再帰的AST走査(代入検出) + /// + /// # Purpose + /// + /// AST bodyを再帰的に走査し、代入文を検出する。 + /// + /// # Implementation Plan + /// + /// ## Recursive Descent + /// - Handle: "Local" assignments (`local x = ...` or `x = ...`) + /// - Handle: Nested blocks (`{ ... }`) + /// - Handle: If/Loop bodies (recursive call) + /// + /// ## Return + /// - `HashSet`: 代入された変数名全て + /// + /// # Example + /// + /// ```json + /// { + /// "type": "Block", + /// "body": [ + /// {"type": "Local", "name": "x", "expr": ...}, // x assigned + /// {"type": "If", "cond": ..., "then": [ + /// {"type": "Local", "name": "y", "expr": ...} // y assigned + /// ]} + /// ] + /// } + /// ``` + /// Result: {x, y} + /// + #[allow(dead_code)] + pub fn extract_assigned_vars_from_body( + &mut self, + body: &serde_json::Value, + ) -> std::collections::HashSet { + use std::collections::HashSet; + let mut assigned = HashSet::new(); + + // Handle array of statements + if let Some(stmts) = body.as_array() { + for stmt in stmts { + self.extract_assigned_vars_from_stmt(stmt, &mut assigned); + } + } + // Handle single statement (Block node) + else if let Some(stmts) = body.get("body").and_then(|b| b.as_array()) { + for stmt in stmts { + self.extract_assigned_vars_from_stmt(stmt, &mut assigned); + } + } + + assigned + } + + /// Phase 40-1: 再帰的AST走査ヘルパー(単一文処理) + /// + /// # Purpose + /// + /// 単一のAST文を処理し、代入された変数を収集する。 + pub(crate) fn extract_assigned_vars_from_stmt( + &mut self, + stmt: &serde_json::Value, + assigned: &mut std::collections::HashSet, + ) { + match stmt.get("type").and_then(|t| t.as_str()) { + Some("Local") => { + // local x = ... または x = ... + if let Some(name) = stmt.get("name").and_then(|n| n.as_str()) { + assigned.insert(name.to_string()); + } + } + Some("If") => { + // if 文の then/else 分岐を再帰処理 + if let Some(then_body) = stmt.get("then") { + assigned.extend(self.extract_assigned_vars_from_body(then_body)); + } + if let Some(else_body) = stmt.get("else") { + assigned.extend(self.extract_assigned_vars_from_body(else_body)); + } + } + Some("Loop") => { + // loop 文の body を再帰処理 + if let Some(loop_body) = stmt.get("body") { + assigned.extend(self.extract_assigned_vars_from_body(loop_body)); + } + } + Some("Block") => { + // { ... } ブロックの body を再帰処理 + if let Some(block_body) = stmt.get("body") { + assigned.extend(self.extract_assigned_vars_from_body(block_body)); + } + } + _ => { + // その他の文は無視(Return, Call, etc.) + } + } + } + + /// Phase 40-1実験用: array_ext.filter パターン専用lowering + /// + /// 通常の lower_loop_case_a_simple() に加えて、 + /// if-in-loop modified varsをJoinFuncMetaMapとして返す。 + /// + /// # Returns + /// - `JoinModule`: 通常のJoinIR module + /// - `JoinFuncMetaMap`: loop_step関数のif_modified_vars情報 + /// + /// # Phase 40-1専用 + /// この関数はPhase 40-1 A/Bテスト専用。 + /// 本番パスでは使わない(従来のlower_loop_case_a_simple()を使う)。 + #[allow(dead_code)] + pub fn lower_loop_with_if_meta( + &mut self, + program_json: &serde_json::Value, + ) -> (JoinModule, JoinFuncMetaMap) { + // 1. 通常のJoinModule生成(既存ロジック流用) + let module = self.lower_loop_case_a_simple(program_json); + + // 2. loop body ASTからif-in-loop modified varsを抽出 + let loop_body = self.extract_loop_body_from_program(program_json); + let loop_vars = self.get_loop_carried_vars_from_program(program_json); + let if_modified = self.extract_if_in_loop_modified_vars(&loop_body, &loop_vars); + + // 3. JoinFuncMetaMap組み立て + // loop_step関数のIDを特定(通常は2番目の関数、名前に"loop_step"を含む) + let mut meta_map = JoinFuncMetaMap::new(); + if let Some((loop_step_id, _)) = module + .functions + .iter() + .find(|(_, func)| func.name.contains("loop_step")) + { + meta_map.insert( + *loop_step_id, + JoinFuncMeta { + if_modified_vars: if if_modified.is_empty() { + None + } else { + Some(if_modified) + }, + ..Default::default() + }, + ); + } + + (module, meta_map) + } + + /// loop body ASTを抽出するヘルパー + /// + /// # Purpose + /// Program → defs[0] → body → Loop statement → body のパスでloop bodyを取得 + pub fn extract_loop_body_from_program( + &self, + program_json: &serde_json::Value, + ) -> serde_json::Value { + // Program → defs[0] → body → Loop statement → body + program_json["defs"][0]["body"] + .as_array() + .and_then(|stmts| stmts.iter().find(|s| s["type"] == "Loop")) + .and_then(|loop_stmt| loop_stmt.get("body")) + .cloned() + .unwrap_or(serde_json::Value::Null) + } + + /// loop-carried varsを抽出するヘルパー + /// + /// # Purpose + /// loop前に宣言された変数を収集(簡易実装) + /// より正確な実装はPhase 40-2で拡張 + pub fn get_loop_carried_vars_from_program( + &self, + program_json: &serde_json::Value, + ) -> HashSet { + let mut vars = HashSet::new(); + if let Some(body) = program_json["defs"][0]["body"].as_array() { + for stmt in body { + if stmt["type"] == "Local" { + if let Some(name) = stmt["name"].as_str() { + vars.insert(name.to_string()); + } + } + if stmt["type"] == "Loop" { + break; // loop以降は無視 + } + } + } + vars + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/context.rs b/src/mir/join_ir/frontend/ast_lowerer/context.rs new file mode 100644 index 00000000..886e5e44 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/context.rs @@ -0,0 +1,39 @@ +use super::VarId; +use std::collections::BTreeMap; + +/// Phase 34-5: 式から値を抽出する際のコンテキスト +/// +/// extract_value ヘルパ関数で使用する内部状態 +pub(crate) struct ExtractCtx { + /// 次に使える ValueId カウンタ + pub(crate) next_var_id: u32, + /// 変数名 → ValueId のマップ(パラメータなど) + pub(crate) var_map: BTreeMap, +} + +impl ExtractCtx { + /// 新しいコンテキストを作成 + pub(crate) fn new(start_var_id: u32) -> Self { + Self { + next_var_id: start_var_id, + var_map: BTreeMap::new(), + } + } + + /// パラメータを登録 + pub(crate) fn register_param(&mut self, name: String, var_id: VarId) { + self.var_map.insert(name, var_id); + } + + /// 新しい ValueId を割り当て + pub(crate) fn alloc_var(&mut self) -> VarId { + let id = crate::mir::ValueId(self.next_var_id); + self.next_var_id += 1; + id + } + + /// 変数名から ValueId を取得 + pub(crate) fn get_var(&self, name: &str) -> Option { + self.var_map.get(name).copied() + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/expr.rs b/src/mir/join_ir/frontend/ast_lowerer/expr.rs new file mode 100644 index 00000000..7962cd2a --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/expr.rs @@ -0,0 +1,193 @@ +use super::{AstToJoinIrLowerer, BinOpKind, CompareOp, ConstValue, ExtractCtx, JoinInst, VarId}; + +impl AstToJoinIrLowerer { + /// Phase 34-5: expr から「値を計算する JoinIR」と「結果を入れる ValueId」を返す + /// + /// ## 設計方針 + /// + /// - **Int literal**: 新しい dst を割り当てて Const 命令を生成 + /// - **Var 参照**: ctx の var_map から既存 ValueId を引く(追加命令なし) + /// - **Method 呼び出し**: pattern match のみ実装(JoinIR 出力はダミーでも可) + /// + /// ## 戻り値 + /// + /// - `ValueId`: 結果が入る変数 ID + /// - `Vec`: 値を計算するための JoinIR 命令列 + /// + /// ## Phase 34-5 実装範囲 + /// + /// - **段階 1**: Int / Var 対応(確実に実装) + /// - **段階 2**: Method 呼び出し pattern match(ダミー可) + /// + /// ## Panics + /// + /// - 未対応の expr 形式(Phase 34-5 は tiny テスト専用) + #[allow(dead_code)] // Phase 34-5.4 で lower_if_return_pattern から呼ばれる + pub(super) fn extract_value( + &self, + expr: &serde_json::Value, + ctx: &mut ExtractCtx, + ) -> (VarId, Vec) { + let expr_type = expr["type"].as_str().expect("expr must have 'type' field"); + + match expr_type { + // 段階 1: Int literal 対応 + "Int" => { + let value = expr["value"].as_i64().expect("Int value must be i64"); + + let dst = ctx.alloc_var(); + let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst, + value: ConstValue::Integer(value), + }); + + (dst, vec![inst]) + } + + // Phase 34-8: Bool literal 対応 + "Bool" => { + let value = expr["value"].as_bool().expect("Bool value must be boolean"); + + let dst = ctx.alloc_var(); + let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst, + value: ConstValue::Bool(value), + }); + + (dst, vec![inst]) + } + + // 段階 1: Var 参照対応 + "Var" => { + let var_name = expr["name"].as_str().expect("Var must have 'name' field"); + + let var_id = ctx + .get_var(var_name) + .unwrap_or_else(|| panic!("Undefined variable: {}", var_name)); + + // Var 参照は追加命令なし(既存の ValueId を返すだけ) + (var_id, vec![]) + } + + // Phase 34-6: Method 呼び出し構造の完全実装 + "Method" => { + // receiver.method(args...) の構造を抽出 + let receiver_expr = &expr["receiver"]; + let method_name = expr["method"] + .as_str() + .expect("Method must have 'method' field"); + let args_array = expr["args"] + .as_array() + .expect("Method must have 'args' array"); + + // receiver を extract_value で処理 + let (receiver_var, receiver_insts) = self.extract_value(receiver_expr, ctx); + + // args を extract_value で処理 + let mut arg_vars = Vec::new(); + let mut arg_insts = Vec::new(); + for arg_expr in args_array { + let (arg_var, arg_inst) = self.extract_value(arg_expr, ctx); + arg_vars.push(arg_var); + arg_insts.extend(arg_inst); + } + + // MethodCall 命令を生成 + let dst = ctx.alloc_var(); + let method_call_inst = JoinInst::MethodCall { + dst, + receiver: receiver_var, + method: method_name.to_string(), + args: arg_vars, + }; + + // すべての命令を結合(receiver → args → MethodCall の順) + let mut insts = receiver_insts; + insts.extend(arg_insts); + insts.push(method_call_inst); + + (dst, insts) + } + + // Phase 34-7.4a: Binary 演算対応(i + 1 など) + "Binary" => { + let op_str = expr["op"].as_str().expect("Binary must have 'op' field"); + let lhs_expr = &expr["lhs"]; + let rhs_expr = &expr["rhs"]; + + // op 文字列を BinOpKind に変換 + let op = match op_str { + "+" => BinOpKind::Add, + "-" => BinOpKind::Sub, + "*" => BinOpKind::Mul, + "/" => BinOpKind::Div, + _ => panic!("Unsupported binary op: {}", op_str), + }; + + // lhs と rhs を再帰的に extract_value + let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx); + let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx); + + // 結果変数を割り当て + let dst = ctx.alloc_var(); + + // BinOp 命令を生成 + let binop_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst, + op, + lhs: lhs_var, + rhs: rhs_var, + }); + + // すべての命令を結合(lhs → rhs → BinOp の順) + let mut insts = lhs_insts; + insts.extend(rhs_insts); + insts.push(binop_inst); + + (dst, insts) + } + + // Phase 34-7.4a: Compare 演算対応(i < n など) + "Compare" => { + let op_str = expr["op"].as_str().expect("Compare must have 'op' field"); + let lhs_expr = &expr["lhs"]; + let rhs_expr = &expr["rhs"]; + + // op 文字列を CompareOp に変換 + let op = match op_str { + "<" => CompareOp::Lt, + "<=" => CompareOp::Le, + ">" => CompareOp::Gt, + ">=" => CompareOp::Ge, + "==" => CompareOp::Eq, + "!=" => CompareOp::Ne, + _ => panic!("Unsupported compare op: {}", op_str), + }; + + // lhs と rhs を再帰的に extract_value + let (lhs_var, lhs_insts) = self.extract_value(lhs_expr, ctx); + let (rhs_var, rhs_insts) = self.extract_value(rhs_expr, ctx); + + // 結果変数を割り当て + let dst = ctx.alloc_var(); + + // Compare 命令を生成 + let compare_inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { + dst, + op, + lhs: lhs_var, + rhs: rhs_var, + }); + + // すべての命令を結合(lhs → rhs → Compare の順) + let mut insts = lhs_insts; + insts.extend(rhs_insts); + insts.push(compare_inst); + + (dst, insts) + } + + _ => panic!("Unsupported expr type: {}", expr_type), + } + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_return.rs b/src/mir/join_ir/frontend/ast_lowerer/if_return.rs new file mode 100644 index 00000000..6fabdc09 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/if_return.rs @@ -0,0 +1,135 @@ +use super::{AstToJoinIrLowerer, BTreeMap, ExtractCtx, JoinFunction, JoinInst, JoinModule}; + +impl AstToJoinIrLowerer { + /// If Return pattern の共通 lowering + /// + /// Phase 34-2/34-3/34-4: simple/local/json_shape 対応 + /// Phase 34-5: extract_value ベースに統一(Int/Var/Method 構造まで) + /// + /// - simple: `if cond { return 10 } else { return 20 }` + /// - local: `if cond { x=10 } else { x=20 }; return x` (意味論的) + /// - json_shape: `if at { return v.substring(0, at) } else { return v }` (Var/Method) + /// + /// すべて同じ JoinIR Select に正規化される + pub(super) fn lower_if_return_pattern( + &mut self, + program_json: &serde_json::Value, + ) -> JoinModule { + // 1. Program(JSON) から defs を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + // 2. 最初の関数定義を取得 + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 3. body 内の If statement を検索(Phase 34-2/34-3 共通) + let body = &func_def["body"]["body"]; + let if_stmt = body + .as_array() + .and_then(|stmts| stmts.get(0)) + .expect("Function body must have at least one statement"); + + assert_eq!( + if_stmt["type"].as_str(), + Some("If"), + "First statement must be If" + ); + + // 4. then/else の Return から値を抽出 + let then_stmts = if_stmt["then"] + .as_array() + .expect("If must have 'then' array"); + let else_stmts = if_stmt["else"] + .as_array() + .expect("If must have 'else' array (simple pattern)"); + + let then_ret = then_stmts.get(0).expect("then branch must have Return"); + let else_ret = else_stmts.get(0).expect("else branch must have Return"); + + assert_eq!( + then_ret["type"].as_str(), + Some("Return"), + "then branch must be Return" + ); + assert_eq!( + else_ret["type"].as_str(), + Some("Return"), + "else branch must be Return" + ); + + // Phase 34-5: extract_value ベースの新実装 + // 5. ExtractCtx を作成し、パラメータを登録 + let func_id = self.next_func_id(); + + let mut ctx = ExtractCtx::new(params.len() as u32); + + // パラメータを ExtractCtx に登録(cond, at など) + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // Phase 34-6: cond/then/else の expr を extract_value で処理 + let (cond_var, cond_insts) = self.extract_value(&if_stmt["cond"], &mut ctx); + let (then_var, then_insts) = self.extract_value(&then_ret["expr"], &mut ctx); + let (else_var, else_insts) = self.extract_value(&else_ret["expr"], &mut ctx); + + // 7. Select 結果変数を割り当て + let result_var = ctx.alloc_var(); + + // 8. JoinIR 命令列を組み立て(cond → then → else → Select の順) + let mut insts = Vec::new(); + + // cond の計算命令を先頭に追加 + insts.extend(cond_insts); + + // then/else の計算命令を追加 + insts.extend(then_insts); + insts.extend(else_insts); + + // Select: result = Select(cond, then_var, else_var) + insts.push(JoinInst::Select { + dst: result_var, + cond: cond_var, + then_val: then_var, + else_val: else_var, + }); + + // Ret result + insts.push(JoinInst::Ret { + value: Some(result_var), + }); + + let func = JoinFunction { + id: func_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: insts, + exit_cont: None, // Phase 34-2/34-3: ルート関数なので exit_cont は None + }; + + let mut functions = BTreeMap::new(); + functions.insert(func_id, func); + + JoinModule { + functions, + entry: Some(func_id), + } + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs new file mode 100644 index 00000000..70b12516 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns.rs @@ -0,0 +1,821 @@ +use super::BTreeMap; +use super::{AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule}; + +impl AstToJoinIrLowerer { + /// Phase 34-8: Break/Continue 付きループの lowering(パターン検出) + /// + /// Loop body を解析して Break/Continue を検出し、適切な lowering 関数にディスパッチ + pub(super) fn lower_loop_with_break_continue( + &mut self, + program_json: &serde_json::Value, + ) -> JoinModule { + // 1. Program(JSON) から defs を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + // 2. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body.as_array().expect("Function body must be array"); + + // Loop ノードを探す + let loop_node = stmts + .iter() + .find(|stmt| stmt["type"].as_str() == Some("Loop")) + .expect("Loop node not found"); + + let loop_body = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + // Break/Continue を検出 + let has_break = Self::has_break_in_loop_body(loop_body); + let has_continue = Self::has_continue_in_loop_body(loop_body); + + // パターンに応じてディスパッチ + if has_break && !has_continue { + self.lower_loop_break_pattern(program_json) + } else if has_continue && !has_break { + self.lower_loop_continue_pattern(program_json) + } else if has_break && has_continue { + panic!("Mixed Break/Continue pattern not yet supported in Phase 34-8"); + } else { + // Phase 34-7 の simple pattern + self.lower_loop_case_a_simple(program_json) + } + } + + /// Loop body に Break があるかチェック + pub(crate) fn has_break_in_loop_body(loop_body: &[serde_json::Value]) -> bool { + loop_body.iter().any(|stmt| { + if stmt["type"].as_str() == Some("If") { + if let Some(then_body) = stmt["then"].as_array() { + then_body + .iter() + .any(|s| s["type"].as_str() == Some("Break")) + } else { + false + } + } else { + false + } + }) + } + + /// Loop body に Continue があるかチェック + pub(crate) fn has_continue_in_loop_body(loop_body: &[serde_json::Value]) -> bool { + loop_body.iter().any(|stmt| { + if stmt["type"].as_str() == Some("If") { + if let Some(then_body) = stmt["then"].as_array() { + then_body + .iter() + .any(|s| s["type"].as_str() == Some("Continue")) + } else { + false + } + } else { + false + } + }) + } + + /// Phase 34-7: Loop pattern の lowering(Case-A: tiny while loop) + /// + /// 対象: `LoopFrontendTest.simple(n)` 相当 + /// - local i = 0; local acc = 0; + /// - loop(i < n) { acc = acc + 1; i = i + 1; } + /// - return acc + /// + /// 目標 JoinIR: Phase 31 の LoopToJoinLowerer と同型 + /// - entry: 初期化 → loop_step 呼び出し + /// - loop_step: 条件チェック → 再帰 or k_exit + /// - k_exit: 結果を return + /// + /// Phase 34-7.4b: Local ノード処理(同名再宣言 = 再代入) + /// ループ本体lowering(Phase 34実装) + /// + /// # Phase 40拡張予定 + /// + /// この関数に以下を追加: + /// 1. `extract_if_in_loop_modified_vars()`呼び出し + /// 2. 検出された変数をloop exit PHI生成に使用 + /// + /// ```rust,ignore + /// // TODO(Phase 40-1): Add if-in-loop variable tracking + /// // let loop_vars = self.get_loop_carried_vars(&loop_ctx); + /// // let modified_in_if = self.extract_if_in_loop_modified_vars(&loop_body, &loop_vars); + /// // Pass modified_in_if to create_exit_function() for PHI generation + /// ``` + pub(super) fn lower_loop_case_a_simple( + &mut self, + program_json: &serde_json::Value, + ) -> JoinModule { + // TODO(Phase 40-1): Add if-in-loop variable tracking here + // Integration point for extract_if_in_loop_modified_vars() + + // 1. Program(JSON) から defs を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body.as_array().expect("Function body must be array"); + + // Phase 34-7.4b: Local ノード処理(初期化) + // stmts[0]: Local i = 0 + // stmts[1]: Local acc = 0 + // stmts[2]: Loop { cond, body } + // stmts[3]: Return acc + + let mut init_insts = Vec::new(); + let mut loop_node_idx = None; + + for (idx, stmt) in stmts.iter().enumerate() { + let stmt_type = stmt["type"].as_str().expect("Statement must have type"); + + match stmt_type { + "Local" => { + // Phase 34-7.4b: Local ノード処理 + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + // extract_value で式を評価 + let (var_id, insts) = self.extract_value(expr, &mut ctx); + init_insts.extend(insts); + + // 同名再宣言 = var_map を更新(再代入の意味論) + ctx.register_param(var_name, var_id); + } + "Loop" => { + loop_node_idx = Some(idx); + break; // Loop 以降は別処理 + } + _ => panic!("Unexpected statement type before Loop: {}", stmt_type), + } + } + + let loop_node_idx = loop_node_idx.expect("Loop node not found"); + let loop_node = &stmts[loop_node_idx]; + + // 4. Loop の cond と body を抽出 + let loop_cond_expr = &loop_node["cond"]; + let loop_body_stmts = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + // 5. Return 文を抽出(Loop の次) + let return_stmt = &stmts[loop_node_idx + 1]; + assert_eq!( + return_stmt["type"].as_str(), + Some("Return"), + "Expected Return after Loop" + ); + + // 6. JoinIR 生成: entry / loop_step / k_exit(Phase 31 と同じパターン) + // + // Phase 34-7: Jump = 早期 return、Call = 末尾再帰 + // - entry: 初期化 → Call(loop_step) + // - loop_step: + // Jump(k_exit, cond=!(i= n なら抜ける) + let false_const = step_ctx.alloc_var(); + let exit_cond = step_ctx.alloc_var(); + + let mut loop_step_body = cond_insts; + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: false_const, + value: ConstValue::Bool(false), + })); + loop_step_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: exit_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: cond_var, + rhs: false_const, + }, + )); + + // 早期 return: exit_cond が true(i >= n)なら k_exit へ Jump + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), // Phase 34-7.5: 型変換ヘルパー使用 + args: vec![step_acc], + cond: Some(exit_cond), + }); + + // loop body を処理(Jump で抜けなかった場合のみ実行される) + for body_stmt in loop_body_stmts { + assert_eq!( + body_stmt["type"].as_str(), + Some("Local"), + "Loop body must contain only Local statements" + ); + + let var_name = body_stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &body_stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut step_ctx); + loop_step_body.extend(insts); + step_ctx.register_param(var_name, var_id); + } + + // body 処理後の i_next, acc_next を取得 + let i_next = step_ctx + .get_var("i") + .expect("i must be updated in loop body"); + let acc_next = step_ctx + .get_var("acc") + .expect("acc must be updated in loop body"); + + // loop_step を再帰的に Call(末尾再帰) + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body: loop_step_body, + exit_cont: None, + }; + + // k_exit 関数: (acc) → Ret acc + let k_exit_acc = crate::mir::ValueId(0); + + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_acc], + body: vec![JoinInst::Ret { + value: Some(k_exit_acc), + }], + exit_cont: None, + }; + + // JoinModule を構築 + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } + + /// Phase 34-8: Break pattern の lowering + /// + /// パターン: `loop { if i >= n { break }; acc = acc + i; i = i + 1 }` + /// 目標: Jump(k_exit, cond=i>=n) で早期 return を実現 + pub(super) fn lower_loop_break_pattern( + &mut self, + program_json: &serde_json::Value, + ) -> JoinModule { + // 1. Program(JSON) から基本情報を取得(Phase 34-7 と同じ) + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body.as_array().expect("Function body must be array"); + + // Local ノード処理(初期化) + let mut init_insts = Vec::new(); + let mut loop_node_idx = None; + + for (idx, stmt) in stmts.iter().enumerate() { + let stmt_type = stmt["type"].as_str().expect("Statement must have type"); + + match stmt_type { + "Local" => { + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut ctx); + init_insts.extend(insts); + ctx.register_param(var_name, var_id); + } + "Loop" => { + loop_node_idx = Some(idx); + break; + } + _ => panic!("Unexpected statement type before Loop: {}", stmt_type), + } + } + + let loop_node_idx = loop_node_idx.expect("Loop node not found"); + let loop_node = &stmts[loop_node_idx]; + + // 4. Loop body から Break If を探す + let loop_body = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + let break_if_stmt = loop_body + .iter() + .find(|stmt| { + stmt["type"].as_str() == Some("If") + && stmt["then"].as_array().map_or(false, |then| { + then.iter().any(|s| s["type"].as_str() == Some("Break")) + }) + }) + .expect("Break pattern must have If + Break"); + + let break_cond_expr = &break_if_stmt["cond"]; + + // 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造) + let entry_id = self.next_func_id(); + let loop_step_id = self.next_func_id(); + let k_exit_id = self.next_func_id(); + + // entry 関数 + let i_init = ctx.get_var("i").expect("i must be initialized"); + let acc_init = ctx.get_var("acc").expect("acc must be initialized"); + let n_param = ctx.get_var("n").expect("n must be parameter"); + + let loop_result = ctx.alloc_var(); + + let mut entry_body = init_insts; + entry_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_init, acc_init, n_param], + k_next: None, + dst: Some(loop_result), + }); + entry_body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + let entry_func = JoinFunction { + id: entry_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: entry_body, + exit_cont: None, + }; + + // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=break_cond) → body → Call(loop_step) + let step_i = crate::mir::ValueId(0); + let step_acc = crate::mir::ValueId(1); + let step_n = crate::mir::ValueId(2); + + let mut step_ctx = ExtractCtx::new(3); + step_ctx.register_param("i".to_string(), step_i); + step_ctx.register_param("acc".to_string(), step_acc); + step_ctx.register_param("n".to_string(), step_n); + + // Break 条件を評価 + let (break_cond_var, break_cond_insts) = self.extract_value(break_cond_expr, &mut step_ctx); + + let mut loop_step_body = break_cond_insts; + + // 早期 return: break_cond が true なら k_exit へ Jump + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![step_acc], + cond: Some(break_cond_var), + }); + + // loop body を処理(Break の後の Local 命令群) + for body_stmt in loop_body { + // If + Break はスキップ(Jump で処理済み) + if body_stmt["type"].as_str() == Some("If") { + continue; + } + + assert_eq!( + body_stmt["type"].as_str(), + Some("Local"), + "Loop body must contain only Local statements after If" + ); + + let var_name = body_stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &body_stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut step_ctx); + loop_step_body.extend(insts); + step_ctx.register_param(var_name, var_id); + } + + // body 処理後の i_next, acc_next を取得 + let i_next = step_ctx + .get_var("i") + .expect("i must be updated in loop body"); + let acc_next = step_ctx + .get_var("acc") + .expect("acc must be updated in loop body"); + + // loop_step を再帰的に Call(末尾再帰) + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body: loop_step_body, + exit_cont: None, + }; + + // k_exit 関数 + let k_exit_acc = crate::mir::ValueId(0); + + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_acc], + body: vec![JoinInst::Ret { + value: Some(k_exit_acc), + }], + exit_cont: None, + }; + + // JoinModule を構築 + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } + + /// Phase 34-8: Continue pattern の lowering + /// + /// パターン: `loop { i = i + 1; if i == 3 { continue }; acc = acc + i }` + /// 目標: Select(条件付き値更新)+ Call(末尾再帰)で実現 + pub(super) fn lower_loop_continue_pattern( + &mut self, + program_json: &serde_json::Value, + ) -> JoinModule { + // 1. Program(JSON) から基本情報を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析: Local 初期化 + Loop + Return + let body = &func_def["body"]["body"]; + let stmts = body.as_array().expect("Function body must be array"); + + // Local ノード処理(初期化) + let mut init_insts = Vec::new(); + let mut loop_node_idx = None; + + for (idx, stmt) in stmts.iter().enumerate() { + let stmt_type = stmt["type"].as_str().expect("Statement must have type"); + + match stmt_type { + "Local" => { + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + + let (var_id, insts) = self.extract_value(expr, &mut ctx); + init_insts.extend(insts); + ctx.register_param(var_name, var_id); + } + "Loop" => { + loop_node_idx = Some(idx); + break; + } + _ => panic!("Unexpected statement type before Loop: {}", stmt_type), + } + } + + let loop_node_idx = loop_node_idx.expect("Loop node not found"); + let loop_node = &stmts[loop_node_idx]; + + // 4. Loop の cond を取得 + let loop_cond_expr = &loop_node["cond"]; + + // 5. Loop body から Continue If を探す + let loop_body = loop_node["body"] + .as_array() + .expect("Loop must have 'body' array"); + + let continue_if_stmt = loop_body + .iter() + .find(|stmt| { + stmt["type"].as_str() == Some("If") + && stmt["then"].as_array().map_or(false, |then| { + then.iter().any(|s| s["type"].as_str() == Some("Continue")) + }) + }) + .expect("Continue pattern must have If + Continue"); + + let continue_cond_expr = &continue_if_stmt["cond"]; + + // 6. JoinIR 生成: entry / loop_step / k_exit(3関数構造) + let entry_id = self.next_func_id(); + let loop_step_id = self.next_func_id(); + let k_exit_id = self.next_func_id(); + + // entry 関数 + let i_init = ctx.get_var("i").expect("i must be initialized"); + let acc_init = ctx.get_var("acc").expect("acc must be initialized"); + let n_param = ctx.get_var("n").expect("n must be parameter"); + + let loop_result = ctx.alloc_var(); + + let mut entry_body = init_insts; + entry_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_init, acc_init, n_param], + k_next: None, + dst: Some(loop_result), + }); + entry_body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + let entry_func = JoinFunction { + id: entry_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: entry_body, + exit_cont: None, + }; + + // loop_step 関数: (i, acc, n) → exit check → i++ → Select → Call(loop_step) + let step_i = crate::mir::ValueId(0); + let step_acc = crate::mir::ValueId(1); + let step_n = crate::mir::ValueId(2); + + let mut step_ctx = ExtractCtx::new(3); + step_ctx.register_param("i".to_string(), step_i); + step_ctx.register_param("acc".to_string(), step_acc); + step_ctx.register_param("n".to_string(), step_n); + + let mut loop_step_body = Vec::new(); + + // 1. exit 条件チェック(!(i < n) = i >= n で抜ける) + let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx); + loop_step_body.extend(cond_insts); + + let false_const = step_ctx.alloc_var(); + let exit_cond = step_ctx.alloc_var(); + + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: false_const, + value: ConstValue::Bool(false), + })); + loop_step_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: exit_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: cond_var, + rhs: false_const, + }, + )); + + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![step_acc], + cond: Some(exit_cond), + }); + + // 2. Continue pattern 特有の処理: i のインクリメントが先 + // Loop body の最初の Local(i) を処理 + let first_local = loop_body + .iter() + .find(|stmt| { + stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i") + }) + .expect("Continue pattern must have i increment as first Local"); + + let i_expr = &first_local["expr"]; + let (i_next, i_insts) = self.extract_value(i_expr, &mut step_ctx); + loop_step_body.extend(i_insts); + step_ctx.register_param("i".to_string(), i_next); + + // 3. Continue 条件を評価 + let (continue_cond_var, continue_cond_insts) = + self.extract_value(continue_cond_expr, &mut step_ctx); + loop_step_body.extend(continue_cond_insts); + + // 4. acc の更新値を計算(If の後の Local(acc) から) + let acc_update_local = loop_body + .iter() + .find(|stmt| { + stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc") + }) + .expect("Continue pattern must have acc update Local"); + + let acc_expr = &acc_update_local["expr"]; + let (acc_increment, acc_insts) = self.extract_value(acc_expr, &mut step_ctx); + loop_step_body.extend(acc_insts); + + // 5. Select: Continue なら acc そのまま、そうでなければ acc の更新値 + let acc_next = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Select { + dst: acc_next, + cond: continue_cond_var, + then_val: step_acc, // Continue: 更新しない + else_val: acc_increment, // 通常: 更新 + }); + + // 6. 末尾再帰 + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, acc_next, step_n], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_acc, step_n], + body: loop_step_body, + exit_cont: None, + }; + + // k_exit 関数 + let k_exit_acc = crate::mir::ValueId(0); + + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_acc], + body: vec![JoinInst::Ret { + value: Some(k_exit_acc), + }], + exit_cont: None, + }; + + // JoinModule を構築 + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/mod.rs new file mode 100644 index 00000000..f96ecf2e --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/mod.rs @@ -0,0 +1,137 @@ +//! AST/CFG → JoinIR Lowering +//! +//! このモジュールは AST/CFG ノードを JoinIR 命令に変換する。 +//! +//! ## 責務 +//! +//! - **If 文→Select/IfMerge 変換**: 条件分岐を JoinIR の継続渡しスタイルに変換 +//! - **Loop 文→loop_step/k_exit 変換**: ループを関数呼び出しと継続に正規化 +//! - **Break/Continue/Return→k_* 変換**: 制御フローを継続 ID として表現 +//! +//! ## Phase 34-2 での実装スコープ +//! +//! 最初は `IfSelectTest.*` 相当の tiny ケースのみ対応: +//! - Simple pattern: `if cond { return 1 } else { return 2 }` +//! +//! ## 設計原則 +//! +//! - **JoinIR = PHI 生成器**: 既存 PHI の変換器にはしない(Phase 33-10 原則) +//! - **段階的移行**: 既存 MIR Builder 経路は保持、新経路はデフォルト OFF +//! - **A/B テスト可能**: 既存経路と新経路の両方で実行して比較検証 + +pub(crate) use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MergePair, + VarId, +}; +pub(crate) use std::collections::{BTreeMap, HashSet}; + +mod analysis; +mod context; +mod expr; +mod if_return; +mod loop_patterns; +mod nested_if; +mod read_quoted; + +#[cfg(test)] +mod tests; + +pub(crate) use context::ExtractCtx; + +/// AST/CFG → JoinIR 変換器 +/// +/// Phase 34-2: Program(JSON v0) から tiny IfSelect ケースを JoinIR に変換 +pub struct AstToJoinIrLowerer { + pub(crate) next_func_id: u32, + #[allow(dead_code)] + pub(crate) next_var_id: u32, +} + +impl AstToJoinIrLowerer { + /// 新しい lowerer を作成 + pub fn new() -> Self { + Self { + next_func_id: 0, + next_var_id: 0, + } + } + + /// Program(JSON v0) → JoinModule + /// + /// Phase 34-2/34-3/34-4: simple/local/json_shape pattern に対応 + /// Phase 34-5: extract_value 統一化(Int/Var/Method 構造まで) + /// + /// # Panics + /// + /// - パターンに合わない Program(JSON) が来た場合(Phase 34 は tiny テスト専用) + /// - ループ・複数変数・副作用付き if(Phase 34-6 以降で対応予定) + pub fn lower_program_json(&mut self, program_json: &serde_json::Value) -> JoinModule { + // 1. Program(JSON) から defs を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + // 2. 最初の関数定義を取得 + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + // 3. 関数名で分岐(Phase 34-2/34-3/34-4/34-5/34-7/34-8/41-4/45) + // test/local/_read_value_from_pair: If Return pattern + // simple: Loop pattern (Phase 34-7/34-8) + // parse_loop: Phase 41-4 NestedIfMerge pattern + // read_quoted_from: Phase 45 Guard if + Loop with break + accumulator + match func_name { + "test" | "local" | "_read_value_from_pair" => { + self.lower_if_return_pattern(program_json) + } + "simple" => { + // Phase 34-8: Loop パターンの詳細分析(break/continue 検出) + self.lower_loop_with_break_continue(program_json) + } + "parse_loop" => { + // Phase 41-4: NestedIfMerge pattern (dev flag gated) + // Guard: HAKO_JOINIR_NESTED_IF=1 を要求 + if std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() == Some("1") { + self.lower_nested_if_pattern(program_json) + } else { + // Dev flag が OFF の場合は panic(旧ルートにフォールバックするため) + panic!( + "parse_loop NestedIfMerge requires HAKO_JOINIR_NESTED_IF=1. \ + Set env var to enable Phase 41-4 route." + ); + } + } + "read_quoted_from" => { + // Phase 45: read_quoted_from pattern (dev flag gated) + // Guard if + Loop with break + accumulator + if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() == Some("1") { + self.lower_read_quoted_pattern(program_json) + } else { + panic!( + "read_quoted_from JoinIR requires HAKO_JOINIR_READ_QUOTED=1. \ + Set env var to enable Phase 45 route." + ); + } + } + _ => panic!("Unsupported function: {}", func_name), + } + } + + /// 次の関数 ID を生成 + pub(crate) fn next_func_id(&mut self) -> JoinFuncId { + let id = JoinFuncId::new(self.next_func_id); + self.next_func_id += 1; + id + } +} + +impl Default for AstToJoinIrLowerer { + fn default() -> Self { + Self::new() + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs b/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs new file mode 100644 index 00000000..55997a4e --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/nested_if.rs @@ -0,0 +1,297 @@ +use super::BTreeMap; +use super::{AstToJoinIrLowerer, ExtractCtx, JoinFunction, JoinInst, JoinModule}; + +impl AstToJoinIrLowerer { + /// Phase 41-4.2: ネスト if パターンの lowering + /// + /// # Purpose + /// + /// 深いネスト if(3-4レベル)を `NestedIfMerge` 命令に変換する。 + /// 対象: `ParserControlBox.parse_loop()` 関数 + /// + /// # Pattern + /// + /// ```nyash,ignore + /// // Level 0 (outer) + /// if cond0 { + /// // Level 1 + /// if cond1 { + /// // Level 2 + /// if cond2 { + /// // Level 3 (deepest) + /// x = new_value + /// } + /// } + /// } + /// // At merge point: x has PHI semantics + /// ``` + /// + /// # Output JoinIR + /// + /// - `NestedIfMerge { conds: [cond0, cond1, cond2], merges: [(x, new_value, old_x)], k_next }` + /// + /// # Dev Flag + /// + /// 環境変数 `HAKO_JOINIR_NESTED_IF=1` が必須。 + pub fn lower_nested_if_pattern(&mut self, program_json: &serde_json::Value) -> JoinModule { + // 1. Program(JSON) から基本情報を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析 + let body = &func_def["body"]["body"]; + let stmts = body.as_array().expect("Function body must be array"); + + // 4. nested if パターンを検出 + let nested_pattern = self.try_match_nested_if_pattern(stmts, &mut ctx); + + // 5. JoinIR 生成 + let func_id = self.next_func_id(); + let mut insts = Vec::new(); + + // 5.1. Local 初期化を処理(ネスト if の前にある Local 文) + for stmt in stmts { + let stmt_type = stmt["type"].as_str().unwrap_or(""); + if stmt_type == "Local" { + let var_name = stmt["name"] + .as_str() + .expect("Local must have 'name'") + .to_string(); + let expr = &stmt["expr"]; + let (var_id, local_insts) = self.extract_value(expr, &mut ctx); + insts.extend(local_insts); + ctx.register_param(var_name, var_id); + } else if stmt_type == "If" { + // If 文でパターン開始 + break; + } + } + + // 5.2. NestedIfMerge パターンがマッチした場合 + if let Some(pattern) = nested_pattern { + // 条件を評価 + let mut cond_vars = Vec::new(); + for cond_expr in &pattern.conds { + let (cond_var, cond_insts) = self.extract_value(cond_expr, &mut ctx); + insts.extend(cond_insts); + cond_vars.push(cond_var); + } + + // merges を構築 + let mut merges = Vec::new(); + for (var_name, then_expr, else_expr) in &pattern.merges { + let (then_var, then_insts) = self.extract_value(then_expr, &mut ctx); + insts.extend(then_insts); + + let else_var = if let Some(else_e) = else_expr { + let (e_var, e_insts) = self.extract_value(else_e, &mut ctx); + insts.extend(e_insts); + e_var + } else { + // else がない場合は既存の変数値を使用 + ctx.get_var(var_name) + .unwrap_or_else(|| panic!("Undefined variable in merge: {}", var_name)) + }; + + // 新しい dst を割り当て、merge 後の値として ctx に登録 + let dst = ctx.alloc_var(); + ctx.register_param(var_name.clone(), dst); + + merges.push(crate::mir::join_ir::MergePair { + dst, + then_val: then_var, + else_val: else_var, + }); + } + + // NestedIfMerge 命令を追加 + insts.push(JoinInst::NestedIfMerge { + conds: cond_vars, + merges, + k_next: None, // 関数末尾なので継続なし + }); + } else { + // パターンがマッチしない場合は panic(dev フラグ ON の時のみ到達) + panic!( + "lower_nested_if_pattern: No nested if pattern found in parse_loop. \ + Expected 2-4 level nested if structure." + ); + } + + // 5.3. Return 文を処理 + let return_stmt = stmts.iter().find(|s| s["type"].as_str() == Some("Return")); + if let Some(ret) = return_stmt { + let (ret_var, ret_insts) = self.extract_value(&ret["expr"], &mut ctx); + insts.extend(ret_insts); + insts.push(JoinInst::Ret { + value: Some(ret_var), + }); + } else { + // Return がない場合は void return + insts.push(JoinInst::Ret { value: None }); + } + + // 6. JoinFunction 構築 + let func = JoinFunction { + id: func_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: insts, + exit_cont: None, + }; + + let mut functions = BTreeMap::new(); + functions.insert(func_id, func); + + JoinModule { + functions, + entry: Some(func_id), + } + } + + /// Phase 41-4.2: ネスト if パターンの検出 + /// + /// # Purpose + /// + /// AST から 2-4 レベルのネスト if を検出し、`NestedIfPattern` として返す。 + /// + /// # Algorithm + /// + /// 1. 最初の If 文を探す + /// 2. then 分岐を再帰的に解析し、ネストレベルをカウント + /// 3. 各レベルの条件式と変数代入を収集 + /// 4. 最大 4 レベルまで対応(それ以上は未サポート) + /// + /// # Returns + /// + /// - `Some(NestedIfPattern)`: パターンがマッチした場合 + /// - `None`: パターンがマッチしない場合 + pub(crate) fn try_match_nested_if_pattern( + &self, + stmts: &[serde_json::Value], + _ctx: &mut ExtractCtx, + ) -> Option { + // 1. 最初の If 文を探す + let first_if = stmts.iter().find(|s| s["type"].as_str() == Some("If"))?; + + // 2. ネスト構造を再帰的に解析 + let mut conds = Vec::new(); + let mut merges = Vec::new(); + + self.collect_nested_if_structure(first_if, &mut conds, &mut merges, 0); + + // 3. 少なくとも 2 レベル以上のネストが必要 + if conds.len() < 2 { + return None; + } + + Some(NestedIfPattern { conds, merges }) + } + + /// Phase 41-4.2: ネスト if 構造の再帰収集 + /// + /// # Arguments + /// + /// - `if_stmt`: 現在の If ノード + /// - `conds`: 条件式リスト(外側から内側へ) + /// - `merges`: 変数代入リスト + /// - `depth`: 現在のネストレベル(0から開始) + pub(crate) fn collect_nested_if_structure( + &self, + if_stmt: &serde_json::Value, + conds: &mut Vec, + merges: &mut Vec<(String, serde_json::Value, Option)>, + depth: usize, + ) { + // 最大 4 レベルまで + if depth >= 4 { + return; + } + + // 条件式を追加 + if let Some(cond) = if_stmt.get("cond") { + conds.push(cond.clone()); + } + + // then 分岐を解析 + if let Some(then_body) = if_stmt.get("then").and_then(|t| t.as_array()) { + for stmt in then_body { + let stmt_type = stmt["type"].as_str().unwrap_or(""); + + match stmt_type { + "If" => { + // ネスト if: 再帰処理 + self.collect_nested_if_structure(stmt, conds, merges, depth + 1); + } + "Local" => { + // 変数代入を記録 + if let Some(var_name) = stmt["name"].as_str() { + let expr = stmt.get("expr").cloned().unwrap_or(serde_json::Value::Null); + // then 値のみ記録(else はパターン解析時に決定) + merges.push((var_name.to_string(), expr, None)); + } + } + "Return" => { + // 早期 return は無視(NestedIfMerge では扱わない) + } + _ => {} + } + } + } + + // else 分岐の代入も収集(存在する場合) + if let Some(else_body) = if_stmt.get("else").and_then(|e| e.as_array()) { + for stmt in else_body { + if stmt["type"].as_str() == Some("Local") { + if let Some(var_name) = stmt["name"].as_str() { + // 既存の merge エントリを探して else 値を更新 + for (name, _, else_val) in merges.iter_mut() { + if name == var_name { + let expr = + stmt.get("expr").cloned().unwrap_or(serde_json::Value::Null); + *else_val = Some(expr); + break; + } + } + } + } + } + } + } +} + +/// Phase 41-4.2: ネスト if パターン構造 +/// +/// AST から検出されたネスト if パターンを表現する。 +#[derive(Debug)] +pub(crate) struct NestedIfPattern { + /// 条件式リスト(外側から内側へ) + pub(crate) conds: Vec, + /// 変数代入リスト: (変数名, then値, else値) + pub(crate) merges: Vec<(String, serde_json::Value, Option)>, +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs b/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs new file mode 100644 index 00000000..c774e0d5 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/read_quoted.rs @@ -0,0 +1,470 @@ +use super::BTreeMap; +use super::{ + AstToJoinIrLowerer, ConstValue, ExtractCtx, JoinFunction, JoinInst, JoinModule, MergePair, +}; + +impl AstToJoinIrLowerer { + // ======================================== + // Phase 45: read_quoted_from Pattern Lowering + // ======================================== + + /// Phase 45: read_quoted_from パターンの lowering + /// + /// # Pattern + /// + /// ```nyash,ignore + /// read_quoted_from(s, pos) { + /// local i = pos + /// if s.substring(i, i+1) != "\"" { return "" } // Guard if + /// i = i + 1 + /// local out = "" + /// local n = s.length() + /// loop (i < n) { + /// local ch = s.substring(i, i+1) + /// if ch == "\"" { break } // Found closing quote + /// // NOTE: Escape handling (if ch == "\\") has known PHI issue + /// // Variable reassignment inside if-block doesn't generate PHI + /// // This will be addressed by JoinIR IfMerge improvements + /// out = out + ch + /// i = i + 1 + /// } + /// return out + /// } + /// ``` + /// + /// # JoinIR Output + /// + /// - entry: Guard if check → Select(guard_passed ? Call(loop_step) : Return("")) + /// - loop_step: (i, out, n, s) → exit check → break check → body → Call(loop_step) + /// - k_exit: (out) → Return(out) + /// + /// # Dev Flag + /// + /// 環境変数 `HAKO_JOINIR_READ_QUOTED=1` が必須。 + pub(crate) fn lower_read_quoted_pattern( + &mut self, + program_json: &serde_json::Value, + ) -> JoinModule { + // 1. Program(JSON) から基本情報を取得 + let defs = program_json["defs"] + .as_array() + .expect("Program(JSON v0) must have 'defs' array"); + + let func_def = defs + .get(0) + .expect("At least one function definition required"); + + let func_name = func_def["name"] + .as_str() + .expect("Function must have 'name'"); + + let params = func_def["params"] + .as_array() + .expect("Function must have 'params' array"); + + // 2. ExtractCtx 作成とパラメータ登録 (s, pos) + let mut ctx = ExtractCtx::new(params.len() as u32); + for (i, param) in params.iter().enumerate() { + let param_name = param + .as_str() + .expect("Parameter must be string") + .to_string(); + ctx.register_param(param_name, crate::mir::ValueId(i as u32)); + } + + // 3. body を解析 + let body = &func_def["body"]["body"]; + let _stmts = body.as_array().expect("Function body must be array"); + + // 4. AST 構造を解析: + // - Local i = pos + // - If guard { return "" } + // - i = i + 1 + // - local out = "" + // - local n = s.length() + // - Loop { ... } + // - Return out + + // 5. JoinIR 生成: entry / loop_step / k_exit(3関数構造) + let entry_id = self.next_func_id(); + let loop_step_id = self.next_func_id(); + let k_exit_id = self.next_func_id(); + + // ======================================== + // Entry 関数の構築 + // ======================================== + let mut entry_body = Vec::new(); + + // パラメータ取得 (s=0, pos=1) + let s_param = ctx.get_var("s").expect("s must be parameter"); + let pos_param = ctx.get_var("pos").expect("pos must be parameter"); + + // local i = pos + ctx.register_param("i".to_string(), pos_param); + let i_var = pos_param; + + // Guard: s.substring(i, i+1) を計算 + // i+1 を計算 + let one_const = ctx.alloc_var(); + entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: one_const, + value: ConstValue::Integer(1), + })); + + let i_plus_1 = ctx.alloc_var(); + entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst: i_plus_1, + op: crate::mir::join_ir::BinOpKind::Add, + lhs: i_var, + rhs: one_const, + })); + + // s.substring(i, i+1) を呼び出し + let first_char = ctx.alloc_var(); + entry_body.push(JoinInst::MethodCall { + dst: first_char, + receiver: s_param, + method: "substring".to_string(), + args: vec![i_var, i_plus_1], + }); + + // Guard 条件: first_char != '"' + let quote_const = ctx.alloc_var(); + entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: quote_const, + value: ConstValue::String("\"".to_string()), + })); + + let guard_cond = ctx.alloc_var(); + entry_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: guard_cond, + op: crate::mir::join_ir::CompareOp::Ne, + lhs: first_char, + rhs: quote_const, + }, + )); + + // Guard 失敗時の戻り値: "" + let empty_string = ctx.alloc_var(); + entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: empty_string, + value: ConstValue::String("".to_string()), + })); + + // Guard 成功時: i = i + 1 + let i_after_guard = i_plus_1; // 既に計算済み + ctx.register_param("i".to_string(), i_after_guard); + + // local out = "" + let out_init = ctx.alloc_var(); + entry_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: out_init, + value: ConstValue::String("".to_string()), + })); + ctx.register_param("out".to_string(), out_init); + + // local n = s.length() + let n_var = ctx.alloc_var(); + entry_body.push(JoinInst::MethodCall { + dst: n_var, + receiver: s_param, + method: "length".to_string(), + args: vec![], + }); + ctx.register_param("n".to_string(), n_var); + + // Guard check → Jump to early return if guard fails + // 逆条件で Jump(guard_cond == true なら early return) + let k_guard_fail_id = self.next_func_id(); + + entry_body.push(JoinInst::Jump { + cont: k_guard_fail_id.as_cont(), + args: vec![empty_string], + cond: Some(guard_cond), + }); + + // Guard 成功: loop_step を呼び出し + let loop_result = ctx.alloc_var(); + entry_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_after_guard, out_init, n_var, s_param], + k_next: None, + dst: Some(loop_result), + }); + + entry_body.push(JoinInst::Ret { + value: Some(loop_result), + }); + + let entry_func = JoinFunction { + id: entry_id, + name: func_name.to_string(), + params: (0..params.len()) + .map(|i| crate::mir::ValueId(i as u32)) + .collect(), + body: entry_body, + exit_cont: None, + }; + + // ======================================== + // k_guard_fail 関数(Guard 失敗時 early return) + // ======================================== + let k_guard_fail_result = crate::mir::ValueId(0); + let k_guard_fail_func = JoinFunction { + id: k_guard_fail_id, + name: format!("{}_k_guard_fail", func_name), + params: vec![k_guard_fail_result], + body: vec![JoinInst::Ret { + value: Some(k_guard_fail_result), + }], + exit_cont: None, + }; + + // ======================================== + // loop_step 関数の構築 + // ======================================== + // params: (i, out, n, s) + let step_i = crate::mir::ValueId(0); + let step_out = crate::mir::ValueId(1); + let step_n = crate::mir::ValueId(2); + let step_s = crate::mir::ValueId(3); + + let mut step_ctx = ExtractCtx::new(4); + step_ctx.register_param("i".to_string(), step_i); + step_ctx.register_param("out".to_string(), step_out); + step_ctx.register_param("n".to_string(), step_n); + step_ctx.register_param("s".to_string(), step_s); + + let mut loop_step_body = Vec::new(); + + // 1. Exit 条件チェック: !(i < n) = i >= n で抜ける + let i_lt_n = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: i_lt_n, + op: crate::mir::join_ir::CompareOp::Lt, + lhs: step_i, + rhs: step_n, + }, + )); + + let false_const = step_ctx.alloc_var(); + let exit_cond = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: false_const, + value: ConstValue::Bool(false), + })); + loop_step_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: exit_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: i_lt_n, + rhs: false_const, + }, + )); + + // i >= n なら k_exit へ Jump + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![step_out], + cond: Some(exit_cond), + }); + + // 2. ch = s.substring(i, i+1) + let step_one = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: step_one, + value: ConstValue::Integer(1), + })); + + let step_i_plus_1 = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst: step_i_plus_1, + op: crate::mir::join_ir::BinOpKind::Add, + lhs: step_i, + rhs: step_one, + })); + + let step_ch = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::MethodCall { + dst: step_ch, + receiver: step_s, + method: "substring".to_string(), + args: vec![step_i, step_i_plus_1], + }); + + // 3. Break 条件: ch == '"' + let step_quote = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: step_quote, + value: ConstValue::String("\"".to_string()), + })); + + let break_cond = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: break_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: step_ch, + rhs: step_quote, + }, + )); + + // ch == '"' なら k_exit へ Jump (break) + loop_step_body.push(JoinInst::Jump { + cont: k_exit_id.as_cont(), + args: vec![step_out], + cond: Some(break_cond), + }); + + // ======================================== + // 4. Escape 処理: if ch == "\\" { i = i + 1; ch = s.substring(i, i+1) } + // ======================================== + // Phase 46: IfMerge で if-body 後の値をマージ + let enable_escape = crate::mir::join_ir::env_flag_is_1("HAKO_JOINIR_READ_QUOTED_IFMERGE"); + + // 条件と then 側の値を事前計算(投機的実行) + let step_backslash = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const { + dst: step_backslash, + value: ConstValue::String("\\".to_string()), + })); + + let esc_cond = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute( + crate::mir::join_ir::MirLikeInst::Compare { + dst: esc_cond, + op: crate::mir::join_ir::CompareOp::Eq, + lhs: step_ch, + rhs: step_backslash, + }, + )); + + // i_esc = i + 1(then 側の i 値) + let i_esc = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst: i_esc, + op: crate::mir::join_ir::BinOpKind::Add, + lhs: step_i, + rhs: step_one, + })); + + // i_esc_plus_1 = i_esc + 1(substring の end 引数用) + let i_esc_plus_1 = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst: i_esc_plus_1, + op: crate::mir::join_ir::BinOpKind::Add, + lhs: i_esc, + rhs: step_one, + })); + + // ch_esc = s.substring(i_esc, i_esc+1)(then 側の ch 値) + let ch_esc = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::MethodCall { + dst: ch_esc, + receiver: step_s, + method: "substring".to_string(), + args: vec![i_esc, i_esc_plus_1], + }); + + // IfMerge: if-body 後の i と ch をマージ + let (i_after_esc, ch_merged) = if enable_escape { + let i_after_esc = step_ctx.alloc_var(); + let ch_merged = step_ctx.alloc_var(); + + loop_step_body.push(JoinInst::IfMerge { + cond: esc_cond, + merges: vec![ + MergePair { + dst: i_after_esc, + then_val: i_esc, + else_val: step_i, + }, + MergePair { + dst: ch_merged, + then_val: ch_esc, + else_val: step_ch, + }, + ], + k_next: None, + }); + + (i_after_esc, ch_merged) + } else { + // 旧パス: escape 未対応(step_i と step_ch をそのまま使う) + (step_i, step_ch) + }; + + // ======================================== + // 5. Accumulator: out = out + ch_merged + // ======================================== + let out_next = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst: out_next, + op: crate::mir::join_ir::BinOpKind::Add, // String concatenation + lhs: step_out, + rhs: ch_merged, // ← ch_merged を使う! + })); + + // ======================================== + // 6. i_next = i_after_esc + 1 + // ======================================== + let i_next = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Compute(crate::mir::join_ir::MirLikeInst::BinOp { + dst: i_next, + op: crate::mir::join_ir::BinOpKind::Add, + lhs: i_after_esc, // ← i_after_esc を使う! + rhs: step_one, + })); + + // 7. 末尾再帰: Call(loop_step) + let recurse_result = step_ctx.alloc_var(); + loop_step_body.push(JoinInst::Call { + func: loop_step_id, + args: vec![i_next, out_next, step_n, step_s], + k_next: None, + dst: Some(recurse_result), + }); + + loop_step_body.push(JoinInst::Ret { + value: Some(recurse_result), + }); + + let loop_step_func = JoinFunction { + id: loop_step_id, + name: format!("{}_loop_step", func_name), + params: vec![step_i, step_out, step_n, step_s], + body: loop_step_body, + exit_cont: None, + }; + + // ======================================== + // k_exit 関数の構築 + // ======================================== + let k_exit_out = crate::mir::ValueId(0); + let k_exit_func = JoinFunction { + id: k_exit_id, + name: format!("{}_k_exit", func_name), + params: vec![k_exit_out], + body: vec![JoinInst::Ret { + value: Some(k_exit_out), + }], + exit_cont: None, + }; + + // ======================================== + // JoinModule の構築 + // ======================================== + let mut functions = BTreeMap::new(); + functions.insert(entry_id, entry_func); + functions.insert(k_guard_fail_id, k_guard_fail_func); + functions.insert(loop_step_id, loop_step_func); + functions.insert(k_exit_id, k_exit_func); + + JoinModule { + functions, + entry: Some(entry_id), + } + } +} diff --git a/src/mir/join_ir/frontend/ast_lowerer/tests.rs b/src/mir/join_ir/frontend/ast_lowerer/tests.rs new file mode 100644 index 00000000..44219787 --- /dev/null +++ b/src/mir/join_ir/frontend/ast_lowerer/tests.rs @@ -0,0 +1,416 @@ +use super::*; +use crate::mir::join_ir::JoinInst; + +/// Phase 41-4.4: NestedIfMerge パターン検出のテスト +/// +/// 2レベル以上のネスト if が検出されることを確認する。 +#[test] +fn test_nested_if_pattern_detection_two_levels() { + // 2レベルのネスト if: if a { if b { x = 1 } } + let program_json = serde_json::json!({ + "defs": [{ + "name": "parse_loop", + "params": ["src", "i"], + "body": { + "body": [ + { + "type": "Local", + "name": "x", + "expr": {"type": "Int", "value": 0} + }, + { + "type": "If", + "cond": {"type": "Var", "name": "a_cond"}, + "then": [ + { + "type": "If", + "cond": {"type": "Var", "name": "b_cond"}, + "then": [ + { + "type": "Local", + "name": "x", + "expr": {"type": "Int", "value": 42} + } + ], + "else": [] + } + ], + "else": [] + }, + { + "type": "Return", + "expr": {"type": "Var", "name": "x"} + } + ] + } + }] + }); + + // ExtractCtx を用意 + let mut ctx = ExtractCtx::new(2); + ctx.register_param("src".to_string(), crate::mir::ValueId(0)); + ctx.register_param("i".to_string(), crate::mir::ValueId(1)); + ctx.register_param("a_cond".to_string(), crate::mir::ValueId(2)); + ctx.register_param("b_cond".to_string(), crate::mir::ValueId(3)); + + // AST から body を取得 + let stmts = program_json["defs"][0]["body"]["body"] + .as_array() + .expect("body must be array"); + + // パターン検出 + let lowerer = AstToJoinIrLowerer::new(); + let pattern = lowerer.try_match_nested_if_pattern(stmts, &mut ctx); + + assert!(pattern.is_some(), "2-level nested if should be detected"); + let pattern = pattern.unwrap(); + + // 2つの条件が検出される + assert_eq!(pattern.conds.len(), 2, "Should have 2 conditions"); + + // 1つの変数代入が検出される + assert_eq!(pattern.merges.len(), 1, "Should have 1 merge"); + assert_eq!(pattern.merges[0].0, "x", "Merged variable should be 'x'"); +} + +/// Phase 41-4.4: NestedIfMerge lowering のテスト(dev flag 必要) +/// +/// HAKO_JOINIR_NESTED_IF=1 が設定されている場合のみ実行。 +/// 設定されていない場合はスキップ。 +#[test] +fn test_nested_if_merge_lowering() { + // Dev flag がない場合はスキップ + if std::env::var("HAKO_JOINIR_NESTED_IF").ok().as_deref() != Some("1") { + eprintln!( + "[Phase 41-4] Skipping test_nested_if_merge_lowering: \ + Set HAKO_JOINIR_NESTED_IF=1 to enable" + ); + return; + } + + // 2レベルのネスト if + let program_json = serde_json::json!({ + "defs": [{ + "name": "parse_loop", + "params": ["src", "i"], + "body": { + "body": [ + { + "type": "Local", + "name": "x", + "expr": {"type": "Int", "value": 0} + }, + { + "type": "If", + "cond": {"type": "Compare", "op": ">", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 0}}, + "then": [ + { + "type": "If", + "cond": {"type": "Compare", "op": "<", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 100}}, + "then": [ + { + "type": "Local", + "name": "x", + "expr": {"type": "Int", "value": 42} + } + ], + "else": [] + } + ], + "else": [] + }, + { + "type": "Return", + "expr": {"type": "Var", "name": "x"} + } + ] + } + }] + }); + + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_nested_if_pattern(&program_json); + + // JoinModule に 1 つの関数がある + assert_eq!(join_module.functions.len(), 1, "Should have 1 function"); + + // entry が設定されている + assert!(join_module.entry.is_some(), "Entry should be set"); + + // NestedIfMerge 命令が含まれている + let entry_id = join_module.entry.unwrap(); + let entry_func = join_module + .functions + .get(&entry_id) + .expect("Entry function"); + + let has_nested_if_merge = entry_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::NestedIfMerge { .. })); + + assert!( + has_nested_if_merge, + "JoinFunction should contain NestedIfMerge instruction" + ); + + eprintln!("[Phase 41-4] test_nested_if_merge_lowering PASSED"); +} + +/// Phase 41-4.4: 単一レベル if はマッチしないことを確認 +#[test] +fn test_nested_if_pattern_single_level_does_not_match() { + // 1レベルのif: if a { x = 1 } + let program_json = serde_json::json!({ + "defs": [{ + "name": "test", + "params": [], + "body": { + "body": [ + { + "type": "If", + "cond": {"type": "Var", "name": "a"}, + "then": [ + { + "type": "Local", + "name": "x", + "expr": {"type": "Int", "value": 1} + } + ], + "else": [] + } + ] + } + }] + }); + + let mut ctx = ExtractCtx::new(0); + let stmts = program_json["defs"][0]["body"]["body"] + .as_array() + .expect("body"); + + let lowerer = AstToJoinIrLowerer::new(); + let pattern = lowerer.try_match_nested_if_pattern(stmts, &mut ctx); + + // 1レベルはマッチしない + assert!( + pattern.is_none(), + "Single-level if should NOT match NestedIfMerge pattern" + ); +} + +// ======================================== +// Phase 45: read_quoted_from Pattern Tests +// ======================================== + +/// Phase 45: read_quoted_from lowering のテスト(dev flag 必要) +/// +/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。 +/// 設定されていない場合はスキップ。 +#[test] +fn test_read_quoted_from_lowering() { + // Dev flag がない場合はスキップ + if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { + eprintln!( + "[Phase 45] Skipping test_read_quoted_from_lowering: \ + Set HAKO_JOINIR_READ_QUOTED=1 to enable" + ); + return; + } + + // read_quoted_from パターンの JSON 表現 + let program_json = serde_json::json!({ + "defs": [{ + "name": "read_quoted_from", + "params": ["s", "pos"], + "body": { + "body": [ + // local i = pos + { + "type": "Local", + "name": "i", + "expr": {"type": "Var", "name": "pos"} + }, + // if s.substring(i, i+1) != '"' { return "" } + { + "type": "If", + "cond": { + "type": "Compare", + "op": "!=", + "lhs": { + "type": "Method", + "receiver": {"type": "Var", "name": "s"}, + "method": "substring", + "args": [ + {"type": "Var", "name": "i"}, + {"type": "Binary", "op": "+", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 1}} + ] + }, + "rhs": {"type": "String", "value": "\""} + }, + "then": [ + {"type": "Return", "expr": {"type": "String", "value": ""}} + ], + "else": [] + }, + // i = i + 1 + { + "type": "Local", + "name": "i", + "expr": {"type": "Binary", "op": "+", "lhs": {"type": "Var", "name": "i"}, "rhs": {"type": "Int", "value": 1}} + }, + // local out = "" + { + "type": "Local", + "name": "out", + "expr": {"type": "String", "value": ""} + }, + // local n = s.length() + { + "type": "Local", + "name": "n", + "expr": { + "type": "Method", + "receiver": {"type": "Var", "name": "s"}, + "method": "length", + "args": [] + } + }, + // Loop (simplified, loop body handled by lower_read_quoted_pattern) + { + "type": "Loop", + "cond": { + "type": "Compare", + "op": "<", + "lhs": {"type": "Var", "name": "i"}, + "rhs": {"type": "Var", "name": "n"} + }, + "body": [] + }, + // return out + { + "type": "Return", + "expr": {"type": "Var", "name": "out"} + } + ] + } + }] + }); + + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_read_quoted_pattern(&program_json); + + // JoinModule に 4 つの関数がある(entry, k_guard_fail, loop_step, k_exit) + assert_eq!(join_module.functions.len(), 4, "Should have 4 functions"); + + // entry が設定されている + assert!(join_module.entry.is_some(), "Entry should be set"); + + // 関数名を確認 + let func_names: Vec<&str> = join_module + .functions + .values() + .map(|f| f.name.as_str()) + .collect(); + + assert!( + func_names.iter().any(|n| *n == "read_quoted_from"), + "Should have entry function" + ); + assert!( + func_names.iter().any(|n| n.contains("loop_step")), + "Should have loop_step function" + ); + assert!( + func_names.iter().any(|n| n.contains("k_exit")), + "Should have k_exit function" + ); + assert!( + func_names.iter().any(|n| n.contains("k_guard_fail")), + "Should have k_guard_fail function" + ); + + eprintln!("[Phase 45] test_read_quoted_from_lowering PASSED"); + eprintln!("[Phase 45] Functions: {:?}", func_names); +} + +/// Phase 45: lowering で生成される JoinInst の種類確認 +#[test] +fn test_read_quoted_from_lowering_instructions() { + // Dev flag がない場合はスキップ + if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { + return; + } + + // 簡易的な program_json(実際の AST 構造は不要、パターンが認識されればOK) + let program_json = serde_json::json!({ + "defs": [{ + "name": "read_quoted_from", + "params": ["s", "pos"], + "body": { "body": [] } + }] + }); + + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_read_quoted_pattern(&program_json); + + // entry 関数の命令を確認 + let entry_id = join_module.entry.unwrap(); + let entry_func = join_module + .functions + .get(&entry_id) + .expect("Entry function"); + + // entry には以下が含まれる: + // - Compute (Const, BinOp, Compare) + // - MethodCall (substring, length) + // - Jump (guard check) + // - Call (loop_step) + // - Ret + + let has_method_call = entry_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::MethodCall { .. })); + let has_jump = entry_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Jump { .. })); + let has_call = entry_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Call { .. })); + let has_ret = entry_func + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Ret { .. })); + + assert!( + has_method_call, + "Entry should have MethodCall for substring/length" + ); + assert!(has_jump, "Entry should have Jump for guard check"); + assert!(has_call, "Entry should have Call for loop_step"); + assert!(has_ret, "Entry should have Ret"); + + // loop_step 関数の命令を確認 + let loop_step_func = join_module + .functions + .values() + .find(|f| f.name.contains("loop_step")) + .expect("loop_step function"); + + let loop_has_jump = loop_step_func + .body + .iter() + .filter(|inst| matches!(inst, JoinInst::Jump { .. })) + .count(); + + // loop_step には 2 つの Jump がある: exit check と break check + assert_eq!( + loop_has_jump, 2, + "loop_step should have 2 Jumps (exit check, break check)" + ); + + eprintln!("[Phase 45] test_read_quoted_from_lowering_instructions PASSED"); +} diff --git a/src/mir/join_ir/frontend/func_meta.rs b/src/mir/join_ir/frontend/func_meta.rs index 5adda46a..1c1c0f4f 100644 --- a/src/mir/join_ir/frontend/func_meta.rs +++ b/src/mir/join_ir/frontend/func_meta.rs @@ -8,8 +8,8 @@ //! - Phase 40-2: conservative_vars(後で追加) //! - Phase 40-3: reset_vars(後で追加) -use std::collections::{BTreeMap, HashSet}; use super::super::JoinFuncId; +use std::collections::{BTreeMap, HashSet}; /// Phase 40-1実験用メタデータ #[derive(Debug, Default, Clone)] diff --git a/src/mir/join_ir/json.rs b/src/mir/join_ir/json.rs index 3c7995fa..ee855b7a 100644 --- a/src/mir/join_ir/json.rs +++ b/src/mir/join_ir/json.rs @@ -7,9 +7,7 @@ use std::io::Write; -use super::{ - BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst, -}; +use super::{BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst}; /// JoinModule を JSON としてシリアライズする /// @@ -19,7 +17,10 @@ use super::{ /// write_join_module_as_json(&module, &mut output)?; /// let json_str = String::from_utf8(output)?; /// ``` -pub fn write_join_module_as_json(module: &JoinModule, out: &mut W) -> std::io::Result<()> { +pub fn write_join_module_as_json( + module: &JoinModule, + out: &mut W, +) -> std::io::Result<()> { write!(out, "{{")?; write!(out, "\"version\":0")?; @@ -48,7 +49,7 @@ pub fn write_join_module_as_json(module: &JoinModule, out: &mut W) -> fn write_function(func: &JoinFunction, out: &mut W) -> std::io::Result<()> { write!(out, "{{")?; write!(out, "\"id\":{}", func.id.0)?; - write!(out, ",\"name\":\"{}\"" , escape_json_string(&func.name))?; + write!(out, ",\"name\":\"{}\"", escape_json_string(&func.name))?; // params write!(out, ",\"params\":[")?; @@ -134,7 +135,12 @@ fn write_inst(inst: &JoinInst, out: &mut W) -> std::io::Result<()> { write!(out, "}}")?; } // Phase 33: Select instruction JSON serialization - JoinInst::Select { dst, cond, then_val, else_val } => { + JoinInst::Select { + dst, + cond, + then_val, + else_val, + } => { write!(out, "{{\"type\":\"select\"")?; write!(out, ",\"dst\":{}", dst.0)?; write!(out, ",\"cond\":{}", cond.0)?; @@ -143,7 +149,11 @@ fn write_inst(inst: &JoinInst, out: &mut W) -> std::io::Result<()> { write!(out, "}}")?; } // Phase 33-6: IfMerge instruction JSON serialization - JoinInst::IfMerge { cond, merges, k_next } => { + JoinInst::IfMerge { + cond, + merges, + k_next, + } => { write!(out, "{{\"type\":\"if_merge\"")?; write!(out, ",\"cond\":{}", cond.0)?; write!(out, ",\"merges\":[")?; @@ -165,7 +175,12 @@ fn write_inst(inst: &JoinInst, out: &mut W) -> std::io::Result<()> { write!(out, "}}")?; } // Phase 34-6: MethodCall instruction JSON serialization - JoinInst::MethodCall { dst, receiver, method, args } => { + JoinInst::MethodCall { + dst, + receiver, + method, + args, + } => { write!(out, "{{\"type\":\"method_call\"")?; write!(out, ",\"dst\":{}", dst.0)?; write!(out, ",\"receiver\":{}", receiver.0)?; @@ -181,7 +196,11 @@ fn write_inst(inst: &JoinInst, out: &mut W) -> std::io::Result<()> { write!(out, "}}")?; } // Phase 41-4: NestedIfMerge instruction JSON serialization - JoinInst::NestedIfMerge { conds, merges, k_next } => { + JoinInst::NestedIfMerge { + conds, + merges, + k_next, + } => { write!(out, "{{\"type\":\"nested_if_merge\"")?; // conds array write!(out, ",\"conds\":[")?; @@ -355,7 +374,8 @@ mod tests { #[test] fn test_simple_function() { let mut module = JoinModule::new(); - let mut func = JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![ValueId(100)]); + let mut func = + JoinFunction::new(JoinFuncId::new(0), "test".to_string(), vec![ValueId(100)]); func.body.push(JoinInst::Ret { value: Some(ValueId(100)), }); diff --git a/src/mir/join_ir/lowering/funcscanner_trim.rs b/src/mir/join_ir/lowering/funcscanner_trim.rs index 9c6cf40e..e2e64019 100644 --- a/src/mir/join_ir/lowering/funcscanner_trim.rs +++ b/src/mir/join_ir/lowering/funcscanner_trim.rs @@ -629,12 +629,12 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option { return build_funcscanner_trim_joinir(module); }; if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) { - eprintln!( - "[joinir/trim/generic-hook] simple Case A loop detected (LoopToJoinLowerer)" - ); + eprintln!("[joinir/trim/generic-hook] simple Case A loop detected (LoopToJoinLowerer)"); let lowerer = LoopToJoinLowerer::new(); if let Some(jm) = lowerer.lower_case_a_for_trim(target_func, &loop_form) { - eprintln!("[joinir/trim/generic-hook] LoopToJoinLowerer produced JoinIR, returning early"); + eprintln!( + "[joinir/trim/generic-hook] LoopToJoinLowerer produced JoinIR, returning early" + ); return Some(jm); } eprintln!( diff --git a/src/mir/join_ir/lowering/generic_case_a.rs b/src/mir/join_ir/lowering/generic_case_a.rs index 33546632..a1524fde 100644 --- a/src/mir/join_ir/lowering/generic_case_a.rs +++ b/src/mir/join_ir/lowering/generic_case_a.rs @@ -46,7 +46,9 @@ pub(crate) fn lower_case_a_skip_ws_with_scope( /// `_for_minimal_skip_ws` と `_with_scope` の両方から呼ばれる。 fn lower_case_a_skip_ws_core(ctx: &CaseAContext) -> Option { let string_key = ctx.pinned_name_or_first(0)?; - let len_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone()); + let len_key = ctx + .pinned_name_or_first(1) + .unwrap_or_else(|| string_key.clone()); let index_key = ctx.carrier_name_or_first(0)?; let s_loop = ctx.get_loop_id(&string_key)?; @@ -246,7 +248,9 @@ pub(crate) fn lower_case_a_trim_with_scope( /// `_for_trim_minimal` と `_with_scope` の両方から呼ばれる。 fn lower_case_a_trim_core(ctx: &CaseAContext) -> Option { let string_key = ctx.pinned_name_or_first(0)?; - let base_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone()); + let base_key = ctx + .pinned_name_or_first(1) + .unwrap_or_else(|| string_key.clone()); let carrier_key = ctx.carrier_name_or_first(0)?; let s_loop = ctx.get_loop_id(&string_key)?; @@ -722,8 +726,12 @@ pub(crate) fn lower_case_a_append_defs_with_scope( /// `_for_append_defs_minimal` と `_with_scope` の両方から呼ばれる。 fn lower_case_a_append_defs_core(ctx: &CaseAContext) -> Option { let dst_key = ctx.pinned_name_or_first(0)?; - let defs_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| dst_key.clone()); - let n_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| defs_key.clone()); + let defs_key = ctx + .pinned_name_or_first(1) + .unwrap_or_else(|| dst_key.clone()); + let n_key = ctx + .pinned_name_or_first(2) + .unwrap_or_else(|| defs_key.clone()); let i_key = ctx.carrier_name_or_first(0)?; let dst_loop = ctx.get_loop_id(&dst_key)?; @@ -863,8 +871,7 @@ fn lower_case_a_append_defs_core(ctx: &CaseAContext) -> Option { pub(crate) fn lower_case_a_stage1_usingresolver_with_scope( scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, ) -> Option { - let ctx = - CaseAContext::from_scope(scope, "stage1", |offset| stage1_vid::loop_step(offset))?; + let ctx = CaseAContext::from_scope(scope, "stage1", |offset| stage1_vid::loop_step(offset))?; lower_case_a_stage1_usingresolver_core(&ctx) } @@ -874,11 +881,19 @@ pub(crate) fn lower_case_a_stage1_usingresolver_with_scope( /// `_for_stage1_usingresolver_minimal` と `_with_scope` の両方から呼ばれる。 fn lower_case_a_stage1_usingresolver_core(ctx: &CaseAContext) -> Option { let entries_key = ctx.pinned_name_or_first(0)?; - let n_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| entries_key.clone()); - let modules_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| entries_key.clone()); - let seen_key = ctx.pinned_name_or_first(3).unwrap_or_else(|| entries_key.clone()); + let n_key = ctx + .pinned_name_or_first(1) + .unwrap_or_else(|| entries_key.clone()); + let modules_key = ctx + .pinned_name_or_first(2) + .unwrap_or_else(|| entries_key.clone()); + let seen_key = ctx + .pinned_name_or_first(3) + .unwrap_or_else(|| entries_key.clone()); let prefix_key = ctx.carrier_name_or_first(0)?; - let i_key = ctx.carrier_name_or_first(1).unwrap_or_else(|| prefix_key.clone()); + let i_key = ctx + .carrier_name_or_first(1) + .unwrap_or_else(|| prefix_key.clone()); let entries_loop = ctx.get_loop_id(&entries_key)?; let n_loop = ctx.get_loop_id(&n_key)?; diff --git a/src/mir/join_ir/lowering/if_dry_runner.rs b/src/mir/join_ir/lowering/if_dry_runner.rs index d7d0b387..8bf1900d 100644 --- a/src/mir/join_ir/lowering/if_dry_runner.rs +++ b/src/mir/join_ir/lowering/if_dry_runner.rs @@ -42,10 +42,7 @@ impl IfLoweringDryRunner { /// - 各 Branch ブロックで try_lower_if_to_joinir() 試行 /// - パフォーマンス計測(マイクロ秒レベル) /// - 統計情報収集(Select/IfMerge分類) - pub fn scan_module( - &self, - functions: &HashMap, - ) -> DryRunStats { + pub fn scan_module(&self, functions: &HashMap) -> DryRunStats { let mut total_branches = 0; let mut lowered_count = 0; let mut select_count = 0; @@ -60,10 +57,7 @@ impl IfLoweringDryRunner { // 各Branchブロックに対してtry_lower_if_to_joinir()試行 for (block_id, block) in &func.blocks { - if matches!( - block.terminator, - Some(MirInstruction::Branch { .. }) - ) { + if matches!(block.terminator, Some(MirInstruction::Branch { .. })) { total_branches += 1; let start = Instant::now(); diff --git a/src/mir/join_ir/lowering/if_merge.rs b/src/mir/join_ir/lowering/if_merge.rs index f1698441..531cb015 100644 --- a/src/mir/join_ir/lowering/if_merge.rs +++ b/src/mir/join_ir/lowering/if_merge.rs @@ -44,7 +44,9 @@ impl IfMergeLowerer { /// Phase 33-8: debug-level backward compat wrapper pub fn with_debug(debug: bool) -> Self { - Self { debug_level: if debug { 1 } else { 0 } } + Self { + debug_level: if debug { 1 } else { 0 }, + } } /// if/else が IfMerge に lowering できるかチェック @@ -210,29 +212,16 @@ impl IfMergeLowerer { } /// 命令列から dst に書き込まれる値を探す(最後の書き込み) - fn find_written_value( - &self, - instructions: &[MirInstruction], - dst: ValueId, - ) -> Option { + fn find_written_value(&self, instructions: &[MirInstruction], dst: ValueId) -> Option { // 逆順で探索して最後の書き込みを見つける for inst in instructions.iter().rev() { match inst { - MirInstruction::Copy { - dst: inst_dst, - src, - } if *inst_dst == dst => { + MirInstruction::Copy { dst: inst_dst, src } if *inst_dst == dst => { return Some(*src); } - MirInstruction::Const { - dst: inst_dst, .. - } - | MirInstruction::BinOp { - dst: inst_dst, .. - } - | MirInstruction::Compare { - dst: inst_dst, .. - } + MirInstruction::Const { dst: inst_dst, .. } + | MirInstruction::BinOp { dst: inst_dst, .. } + | MirInstruction::Compare { dst: inst_dst, .. } | MirInstruction::Call { dst: Some(inst_dst), .. diff --git a/src/mir/join_ir/lowering/if_select.rs b/src/mir/join_ir/lowering/if_select.rs index 1efde6f0..a693adde 100644 --- a/src/mir/join_ir/lowering/if_select.rs +++ b/src/mir/join_ir/lowering/if_select.rs @@ -51,7 +51,9 @@ impl IfSelectLowerer { /// Phase 33-8: debug-level backward compat wrapper pub fn with_debug(debug: bool) -> Self { - Self { debug_level: if debug { 1 } else { 0 } } + Self { + debug_level: if debug { 1 } else { 0 }, + } } /// if/else が Select に lowering できるかチェック @@ -95,11 +97,7 @@ impl IfSelectLowerer { } /// MIR 関数から if/else パターンを探す - fn find_if_pattern( - &self, - func: &MirFunction, - block_id: BasicBlockId, - ) -> Option { + fn find_if_pattern(&self, func: &MirFunction, block_id: BasicBlockId) -> Option { // 1. Block が Branch 命令で終わっているか確認 let block = func.blocks.get(&block_id)?; let branch = match block.terminator.as_ref()? { @@ -122,11 +120,15 @@ impl IfSelectLowerer { // Phase 33-10: PHI早期チェック(パターンマッチング前) // JoinIRは「PHI生成器」であり「PHI変換器」ではない // then/elseがJumpで終わる場合、merge blockにPHI命令があるか早期確認 - if let Some(merge_block_id) = self.get_merge_block_if_jump_pattern(&branch, then_block, else_block) { + if let Some(merge_block_id) = + self.get_merge_block_if_jump_pattern(&branch, then_block, else_block) + { let merge_block = func.blocks.get(&merge_block_id)?; - if merge_block.instructions.iter().any(|inst| { - matches!(inst, MirInstruction::Phi { .. }) - }) { + if merge_block + .instructions + .iter() + .any(|inst| matches!(inst, MirInstruction::Phi { .. })) + { if self.debug_level >= 2 { eprintln!("[IfSelectLowerer] ⏭️ PHI already exists in merge block, skipping"); } @@ -144,8 +146,7 @@ impl IfSelectLowerer { } // 4. local パターンのチェック - if let Some(pattern) = self.try_match_local_pattern(func, &branch, then_block, else_block) - { + if let Some(pattern) = self.try_match_local_pattern(func, &branch, then_block, else_block) { // Phase 33-8: Level 2 - Pattern matching details if self.debug_level >= 2 { eprintln!("[IfSelectLowerer] ✅ matched local pattern"); @@ -314,9 +315,7 @@ impl IfSelectLowerer { // Phase 33-10: PHIチェックは find_if_pattern() で早期実行済み match merge_block.terminator.as_ref()? { - MirInstruction::Return { - value: Some(v), - } if *v == dst_then => { + MirInstruction::Return { value: Some(v) } if *v == dst_then => { // OK } _ => return None, diff --git a/src/mir/join_ir/lowering/loop_scope_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape.rs index c8b4cf86..43f5ad64 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape.rs @@ -150,7 +150,6 @@ pub(crate) fn is_case_a_minimal_target(func_name: &str) -> bool { #[allow(dead_code)] // Phase 30: block IDs and progress_carrier are for future F-3/F-4 use pub(crate) struct LoopScopeShape { // === Block IDs (Phase 30: from LoopForm) === - /// Loop header block (condition check) pub header: BasicBlockId, @@ -164,7 +163,6 @@ pub(crate) struct LoopScopeShape { pub exit: BasicBlockId, // === Variable Classification === - /// Loop-crossing parameters (always need header/exit PHI) pub pinned: BTreeSet, @@ -241,7 +239,12 @@ impl LoopScopeShape { if let Some(name) = func_name { if is_case_a_minimal_target(name) { return Self::analyze_case_a( - loop_form, intake, var_classes, exit_live_box, query, name, + loop_form, + intake, + var_classes, + exit_live_box, + query, + name, ); } } @@ -316,9 +319,8 @@ impl LoopScopeShape { ) -> Option { // Phase 30 F-3.1: 現在は legacy と同じ実装 // 将来は MIR から独立して pinned/carriers/body_locals/exit_live を計算 - let result = Self::from_existing_boxes_legacy( - loop_form, intake, var_classes, exit_live_box, query, - )?; + let result = + Self::from_existing_boxes_legacy(loop_form, intake, var_classes, exit_live_box, query)?; // Debug: Case-A minimal path が使われていることをログ if std::env::var("NYASH_LOOPSCOPE_DEBUG").is_ok() { @@ -523,7 +525,6 @@ impl LoopScopeShape { #[derive(Debug, Clone)] pub(crate) struct CaseAContext { // Phase 30: scope フィールド削除(ordered_pinned/carriers/exit_args に情報コピー済みで重複) - /// 順序付き pinned 変数名 pub ordered_pinned: Vec, @@ -822,7 +823,10 @@ mod tests { // from_scope は None を返すべき let ctx = CaseAContext::from_scope(scope, "test", |offset| vid::loop_step(offset)); - assert!(ctx.is_none(), "from_scope should return None when header == exit"); + assert!( + ctx.is_none(), + "from_scope should return None when header == exit" + ); } /// block IDs が LoopForm から正しく伝播されるテスト @@ -879,7 +883,10 @@ mod tests { let vec2: Vec<_> = set2.iter().cloned().collect(); assert_eq!(vec1, vec2); - assert_eq!(vec1, vec!["a".to_string(), "m".to_string(), "z".to_string()]); + assert_eq!( + vec1, + vec!["a".to_string(), "m".to_string(), "z".to_string()] + ); } /// needs_header_phi と needs_exit_phi の一貫性テスト @@ -950,7 +957,9 @@ mod tests { exit: BasicBlockId::new(100), pinned: vec!["s".to_string(), "n".to_string()].into_iter().collect(), carriers: vec!["i".to_string()].into_iter().collect(), - body_locals: vec!["x".to_string(), "ch".to_string()].into_iter().collect(), + body_locals: vec!["x".to_string(), "ch".to_string()] + .into_iter() + .collect(), exit_live: vec![ "s".to_string(), "n".to_string(), @@ -989,7 +998,9 @@ mod tests { exit: BasicBlockId::new(100), pinned: vec!["s".to_string()].into_iter().collect(), carriers: vec!["i".to_string()].into_iter().collect(), - body_locals: vec!["x".to_string(), "ch".to_string()].into_iter().collect(), + body_locals: vec!["x".to_string(), "ch".to_string()] + .into_iter() + .collect(), exit_live: vec!["s".to_string(), "i".to_string(), "x".to_string()] .into_iter() .collect(), diff --git a/src/mir/join_ir/lowering/loop_to_join.rs b/src/mir/join_ir/lowering/loop_to_join.rs index 36569428..b5e0395d 100644 --- a/src/mir/join_ir/lowering/loop_to_join.rs +++ b/src/mir/join_ir/lowering/loop_to_join.rs @@ -106,8 +106,14 @@ impl LoopToJoinLowerer { let intake = intake_loop_form(loop_form, &var_classes, &query, func)?; // Step 4: LoopScopeShape を構築 - let scope = - LoopScopeShape::from_existing_boxes(loop_form, &intake, &var_classes, &exit_live, &query, func_name)?; + let scope = LoopScopeShape::from_existing_boxes( + loop_form, + &intake, + &var_classes, + &exit_live, + &query, + func_name, + )?; if self.debug { eprintln!( @@ -195,7 +201,7 @@ impl LoopToJoinLowerer { /// - `false`: Progress carrier なし(unsafe、fallback すべき) fn has_safe_progress( scope: &LoopScopeShape, - _func: &MirFunction, // Phase 2 で使用予定 + _func: &MirFunction, // Phase 2 で使用予定 _region: &LoopRegion, // Phase 2 で使用予定 ) -> bool { // Phase 1: 保守的チェック @@ -274,8 +280,7 @@ impl LoopToJoinLowerer { if self.debug { eprintln!( "[LoopToJoinLowerer] rejected: header {:?} has {} successors (expected 2)", - region.header, - succ_count + region.header, succ_count ); } return false; @@ -412,7 +417,11 @@ impl LoopToJoinLowerer { func: &MirFunction, loop_form: &LoopForm, ) -> Option { - self.lower(func, loop_form, Some("Stage1UsingResolverBox.resolve_for_source/5")) + self.lower( + func, + loop_form, + Some("Stage1UsingResolverBox.resolve_for_source/5"), + ) } /// Case-A 汎用 lowerer の「StageBBodyExtractorBox.build_body_src/2 用」薄いラッパー。 @@ -422,7 +431,11 @@ impl LoopToJoinLowerer { func: &MirFunction, loop_form: &LoopForm, ) -> Option { - self.lower(func, loop_form, Some("StageBBodyExtractorBox.build_body_src/2")) + self.lower( + func, + loop_form, + Some("StageBBodyExtractorBox.build_body_src/2"), + ) } /// Case-A 汎用 lowerer の「StageBFuncScannerBox.scan_all_boxes/1 用」薄いラッパー。 @@ -432,7 +445,11 @@ impl LoopToJoinLowerer { func: &MirFunction, loop_form: &LoopForm, ) -> Option { - self.lower(func, loop_form, Some("StageBFuncScannerBox.scan_all_boxes/1")) + self.lower( + func, + loop_form, + Some("StageBFuncScannerBox.scan_all_boxes/1"), + ) } } diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index e88f4ce3..ac8096db 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -143,7 +143,10 @@ pub fn try_lower_if_to_joinir( } if debug_level >= 1 { - eprintln!("[try_lower_if_to_joinir] trying to lower {}", func.signature.name); + eprintln!( + "[try_lower_if_to_joinir] trying to lower {}", + func.signature.name + ); } // 3. Phase 33-7: IfMerge を優先的に試行(複数変数パターン) diff --git a/src/mir/join_ir/lowering/stage1_using_resolver.rs b/src/mir/join_ir/lowering/stage1_using_resolver.rs index 8de60f59..62a961b5 100644 --- a/src/mir/join_ir/lowering/stage1_using_resolver.rs +++ b/src/mir/join_ir/lowering/stage1_using_resolver.rs @@ -352,7 +352,9 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { // Stage-1: entry_is_preheader=false (entry の succ が preheader) // has_break=false (このループに break はない) let Some(loop_form) = construct_simple_while_loopform(entry, &query, false, false) else { - eprintln!("[joinir/stage1_using_resolver/generic-hook] failed to construct LoopForm from CFG"); + eprintln!( + "[joinir/stage1_using_resolver/generic-hook] failed to construct LoopForm from CFG" + ); return lower_handwritten(module); }; @@ -368,7 +370,8 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { let params_len = target_func.params.len(); if params_len == 5 { let lowerer = LoopToJoinLowerer::new(); - if let Some(jm) = lowerer.lower_case_a_for_stage1_resolver(target_func, &loop_form) { + if let Some(jm) = lowerer.lower_case_a_for_stage1_resolver(target_func, &loop_form) + { eprintln!( "[joinir/stage1_using_resolver/generic-hook] LoopToJoinLowerer produced JoinIR, returning early" ); diff --git a/src/mir/join_ir/lowering/stageb_funcscanner.rs b/src/mir/join_ir/lowering/stageb_funcscanner.rs index 950acb1d..e1bd28bd 100644 --- a/src/mir/join_ir/lowering/stageb_funcscanner.rs +++ b/src/mir/join_ir/lowering/stageb_funcscanner.rs @@ -170,7 +170,9 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option { // stageb_funcscanner: entry_is_preheader=true, has_break=true let Some(loop_form) = construct_simple_while_loopform(entry, &query, true, true) else { - eprintln!("[joinir/stageb_funcscanner/generic-hook] failed to construct LoopForm from CFG"); + eprintln!( + "[joinir/stageb_funcscanner/generic-hook] failed to construct LoopForm from CFG" + ); return build_stageb_funcscanner_joinir(module); }; diff --git a/src/mir/join_ir/verify.rs b/src/mir/join_ir/verify.rs index c9ef1d73..f57dd7d1 100644 --- a/src/mir/join_ir/verify.rs +++ b/src/mir/join_ir/verify.rs @@ -36,9 +36,7 @@ pub enum ProgressError { call_index: usize, }, /// No recursive call found in loop function - NoRecursiveCall { - loop_func_id: JoinFuncId, - }, + NoRecursiveCall { loop_func_id: JoinFuncId }, /// Progress carrier not found ProgressCarrierNotFound { expected_param_index: usize, diff --git a/src/mir/join_ir_runner.rs b/src/mir/join_ir_runner.rs index 66527814..319e2a32 100644 --- a/src/mir/join_ir_runner.rs +++ b/src/mir/join_ir_runner.rs @@ -128,34 +128,54 @@ fn execute_function( return Ok(ret); } // Phase 33: Select instruction execution - JoinInst::Select { dst, cond, then_val, else_val } => { + JoinInst::Select { + dst, + cond, + then_val, + else_val, + } => { // 1. Evaluate cond (Bool or Int) let cond_value = read_var(&locals, *cond)?; - eprintln!("[SELECT DEBUG] cond={:?}, cond_value={:?}", cond, cond_value); + eprintln!( + "[SELECT DEBUG] cond={:?}, cond_value={:?}", + cond, cond_value + ); let cond_bool = match cond_value { JoinValue::Bool(b) => b, JoinValue::Int(i) => i != 0, // Int も許す(0=false, それ以外=true) - _ => return Err(JoinRuntimeError::new(format!( - "Select: cond must be Bool or Int, got {:?}", cond_value - ))), + _ => { + return Err(JoinRuntimeError::new(format!( + "Select: cond must be Bool or Int, got {:?}", + cond_value + ))) + } }; // 2. Select then_val or else_val let then_value = read_var(&locals, *then_val)?; let else_value = read_var(&locals, *else_val)?; - eprintln!("[SELECT DEBUG] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}", - cond_bool, then_val, then_value, else_val, else_value); + eprintln!( + "[SELECT DEBUG] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}", + cond_bool, then_val, then_value, else_val, else_value + ); let selected_id = if cond_bool { *then_val } else { *else_val }; let selected_value = read_var(&locals, selected_id)?; - eprintln!("[SELECT DEBUG] selected_id={:?}, selected_value={:?}", selected_id, selected_value); + eprintln!( + "[SELECT DEBUG] selected_id={:?}, selected_value={:?}", + selected_id, selected_value + ); // 3. Write to dst locals.insert(*dst, selected_value); ip += 1; } // Phase 33-6: IfMerge instruction execution (複数変数 PHI) - JoinInst::IfMerge { cond, merges, k_next } => { + JoinInst::IfMerge { + cond, + merges, + k_next, + } => { // Phase 33-6 最小実装: k_next は None のみサポート if k_next.is_some() { return Err(JoinRuntimeError::new( @@ -168,14 +188,21 @@ fn execute_function( let cond_bool = match cond_value { JoinValue::Bool(b) => b, JoinValue::Int(i) => i != 0, - _ => return Err(JoinRuntimeError::new(format!( - "IfMerge: cond must be Bool or Int, got {:?}", cond_value - ))), + _ => { + return Err(JoinRuntimeError::new(format!( + "IfMerge: cond must be Bool or Int, got {:?}", + cond_value + ))) + } }; // 2. 各 merge ペアについて、cond に応じて値を選択して代入 for merge in merges { - let selected_id = if cond_bool { merge.then_val } else { merge.else_val }; + let selected_id = if cond_bool { + merge.then_val + } else { + merge.else_val + }; let selected_value = read_var(&locals, selected_id)?; locals.insert(merge.dst, selected_value); } @@ -313,9 +340,9 @@ fn as_bool(value: &JoinValue) -> Result { #[cfg(test)] mod tests { use super::*; + use crate::backend::mir_interpreter::MirInterpreter; use crate::mir::join_ir::{ConstValue, JoinFunction, JoinModule}; use crate::mir::ValueId; - use crate::backend::mir_interpreter::MirInterpreter; #[test] fn test_select_true() { @@ -356,7 +383,9 @@ mod tests { }); // return v4 - func.body.push(JoinInst::Ret { value: Some(v_result) }); + func.body.push(JoinInst::Ret { + value: Some(v_result), + }); module.add_function(func); @@ -405,7 +434,9 @@ mod tests { }); // return v4 - func.body.push(JoinInst::Ret { value: Some(v_result) }); + func.body.push(JoinInst::Ret { + value: Some(v_result), + }); module.add_function(func); @@ -453,7 +484,9 @@ mod tests { }); // return v4 - func.body.push(JoinInst::Ret { value: Some(v_result) }); + func.body.push(JoinInst::Ret { + value: Some(v_result), + }); module.add_function(func); @@ -720,7 +753,9 @@ mod tests { rhs: v_result_z, })); - func.body.push(JoinInst::Ret { value: Some(v_sum_xyz) }); + func.body.push(JoinInst::Ret { + value: Some(v_sum_xyz), + }); module.add_function(func); diff --git a/src/mir/join_ir_vm_bridge/README.md b/src/mir/join_ir_vm_bridge/README.md new file mode 100644 index 00000000..701729cd --- /dev/null +++ b/src/mir/join_ir_vm_bridge/README.md @@ -0,0 +1,18 @@ +JoinIR → VM bridge layer + +Responsibilities: +- Convert normalized JoinIR modules into MIR for the Rust VM without changing semantics. +- Provide a thin runner helper that executes a JoinIR entry via the VM. +- Host experimental metadata-aware paths (Phase 40-1) behind clearly marked helpers. + +Boundaries: +- No new control-flow semantics or heuristics here; this layer only maps structures already normalized by JoinIR. +- Keep type information minimal (MirType::Unknown) and avoid adding inference or guessing. +- Debug/diagnostic output must stay behind `NYASH_JOINIR_VM_BRIDGE_DEBUG=1`. + +File layout: +- `mod.rs`: public surface + shared helpers (naming, error, logging) +- `convert.rs`: JoinIR→MIR lowering (functions/blocks/instructions) +- `runner.rs`: VM execution entry (`run_joinir_via_vm`) +- `meta.rs`: experimental metadata-aware conversion hooks +- `tests.rs`: bridge-specific unit tests (kept local to avoid cross-layer leakage) diff --git a/src/mir/join_ir_vm_bridge.rs b/src/mir/join_ir_vm_bridge/convert.rs similarity index 57% rename from src/mir/join_ir_vm_bridge.rs rename to src/mir/join_ir_vm_bridge/convert.rs index 99baee2d..3a2bd0dc 100644 --- a/src/mir/join_ir_vm_bridge.rs +++ b/src/mir/join_ir_vm_bridge/convert.rs @@ -1,67 +1,13 @@ -//! Phase 27-shortterm S-4: JoinIR → Rust VM Bridge -//! -//! 目的: JoinIR(正規化された IR)を Rust VM で実行するブリッジ層 -//! -//! ## Architecture -//! ```text -//! JoinIR (normalized) → MirModule → Rust VM → Result -//! ↑ ↑ ↑ -//! PHI bugs VM input Execution -//! eliminated format (GC, plugins) -//! ``` -//! -//! ## Design Principles -//! - JoinIR の正規化構造を保持したまま VM に渡す -//! - マッピングだけで済ませる(JoinIR でやった正規化は消えない) -//! - VM の機能(GC、プラグイン、エラーハンドリング)を活用 -//! -//! ## Minimal Instruction Set (S-4.3) -//! - **Compute**: Const, BinOp, Compare -//! - **BoxCall**: StringBox メソッド呼び出し -//! - **Call/Jump/Ret**: 制御フロー -//! -//! Phase 27-shortterm scope: skip_ws で green 化できれば成功 - -use crate::backend::{MirInterpreter, VMError, VMValue}; -use crate::config::env::joinir_vm_bridge_debug; -use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, -}; -use crate::mir::join_ir_ops::JoinValue; use crate::ast::Span; +use crate::mir::join_ir::{ + BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst, +}; use crate::mir::{ BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, }; -/// 条件付きデバッグ出力マクロ(NYASH_JOINIR_VM_BRIDGE_DEBUG=1 で有効) -macro_rules! debug_log { - ($($arg:tt)*) => { - if joinir_vm_bridge_debug() { - eprintln!($($arg)*); - } - }; -} - -/// Phase 27-shortterm S-4 エラー型 -#[derive(Debug, Clone)] -pub struct JoinIrVmBridgeError { - pub message: String, -} - -impl JoinIrVmBridgeError { - pub fn new(msg: impl Into) -> Self { - Self { - message: msg.into(), - } - } -} - -impl From for JoinIrVmBridgeError { - fn from(err: VMError) -> Self { - JoinIrVmBridgeError::new(format!("VM error: {:?}", err)) - } -} +use super::{join_func_name, JoinIrVmBridgeError}; /// ブロックを確定する(instructions + spans + terminator を設定) fn finalize_block( @@ -78,88 +24,28 @@ fn finalize_block( } } -/// JoinFuncId から MIR 用の関数名を生成 -fn join_func_name(id: JoinFuncId) -> String { - format!("join_func_{}", id.0) -} - -/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント -/// -/// ## Arguments -/// - `join_module`: JoinIR モジュール(正規化済み) -/// - `entry_func`: エントリーポイント関数ID -/// - `args`: 初期引数(JoinValue 形式) -/// -/// ## Returns -/// - `Ok(JoinValue)`: 実行結果 -/// - `Err(JoinIrVmBridgeError)`: 変換エラーまたは実行エラー -/// -/// ## Example -/// ```ignore -/// let join_module = lower_skip_ws_to_joinir(&mir_module)?; -/// let result = run_joinir_via_vm( -/// &join_module, -/// JoinFuncId::new(0), -/// &[JoinValue::Str(" hello".to_string()), JoinValue::Int(7)] -/// )?; -/// assert_eq!(result, JoinValue::Int(2)); -/// ``` -pub fn run_joinir_via_vm( - join_module: &JoinModule, - entry_func: JoinFuncId, - args: &[JoinValue], -) -> Result { - debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3"); - debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution"); - - // Step 1: JoinIR → MIR 変換 - let mir_module = convert_joinir_to_mir(join_module)?; - - debug_log!( - "[joinir_vm_bridge] Converted {} JoinIR functions to MIR", - join_module.functions.len() - ); - - // Step 2: VM 実行 - let mut vm = MirInterpreter::new(); - - debug_log!( - "[joinir_vm_bridge] Executing via VM with {} arguments", - args.len() - ); - - // Convert JoinValue → VMValue (BoxRef 含む) - let vm_args: Vec = args.iter().cloned().map(|v| v.into_vm_value()).collect(); - - let entry_name = join_func_name(entry_func); - let result = vm.execute_function_with_args(&mir_module, &entry_name, &vm_args)?; - - // Step 3: VMValue → JoinValue 変換 - let join_result = JoinValue::from_vm_value(&result) - .map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?; - - debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result); - - Ok(join_result) -} - /// Phase 30.x: JoinIR → MIR 変換器 /// /// Phase 32 L-2.2 Step-3: テストから呼び出し可能に `pub(crate)` 化 -pub(crate) fn convert_joinir_to_mir(join_module: &JoinModule) -> Result { +pub(crate) fn convert_joinir_to_mir( + join_module: &JoinModule, +) -> Result { let mut mir_module = MirModule::new("joinir_bridge".to_string()); // Convert all JoinIR functions to MIR (entry function becomes "skip" or similar) for (func_id, join_func) in &join_module.functions { debug_log!( "[joinir_vm_bridge] Converting JoinFunction {} ({})", - func_id.0, join_func.name + func_id.0, + join_func.name ); let mir_func = convert_join_function_to_mir(join_func)?; // Use actual function name (not "main") since we'll create a wrapper - mir_module.functions.insert(join_func_name(*func_id), mir_func); + mir_module + .functions + .insert(join_func_name(*func_id), mir_func); } Ok(mir_module) @@ -265,8 +151,8 @@ pub(crate) fn convert_joinir_to_mir(join_module: &JoinModule) -> Result Result { // TODO(Phase 40-1): Generate loop exit PHI for if-in-loop modified variables // See comment above for implementation details @@ -313,7 +199,12 @@ fn convert_join_function_to_mir( let mir_inst = convert_mir_like_inst(mir_like)?; current_instructions.push(mir_inst); } - JoinInst::MethodCall { dst, receiver, method, args } => { + JoinInst::MethodCall { + dst, + receiver, + method, + args, + } => { // Phase 34-6: MethodCall → MIR BoxCall 変換 // receiver.method(args...) を BoxCall(receiver, method, args) に変換 let mir_inst = MirInstruction::BoxCall { @@ -402,19 +293,31 @@ Call {{\n\ }); // Return the result of the tail call - let terminator = MirInstruction::Return { value: Some(call_result_id) }; - finalize_block(&mut mir_func, current_block_id, current_instructions, terminator); + let terminator = MirInstruction::Return { + value: Some(call_result_id), + }; + finalize_block( + &mut mir_func, + current_block_id, + current_instructions, + terminator, + ); current_instructions = Vec::new(); } } } - JoinInst::Jump { cont, args, cond } => { + JoinInst::Jump { + cont: _, + args, + cond, + } => { // Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return // Jump represents an exit continuation (k_exit) in skip_ws pattern debug_log!( - "[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}", - cont, args, cond + "[joinir_vm_bridge] Converting Jump args={:?}, cond={:?}", + args, + cond ); match cond { @@ -433,7 +336,12 @@ Call {{\n\ }; // Finalize current block with Branch terminator - finalize_block(&mut mir_func, current_block_id, current_instructions, branch_terminator); + finalize_block( + &mut mir_func, + current_block_id, + current_instructions, + branch_terminator, + ); // Create exit block with Return terminator let exit_value = args.first().copied(); @@ -455,7 +363,12 @@ Call {{\n\ let return_terminator = MirInstruction::Return { value: exit_value }; // Finalize current block with Return terminator - finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator); + finalize_block( + &mut mir_func, + current_block_id, + current_instructions, + return_terminator, + ); // No continuation after unconditional return current_instructions = Vec::new(); @@ -463,13 +376,21 @@ Call {{\n\ } } // Phase 33: Select instruction conversion to MIR - JoinInst::Select { dst, cond, then_val, else_val } => { + JoinInst::Select { + dst, + cond, + then_val, + else_val, + } => { // Phase 33-2: Select を MIR の if/phi に変換 // 最小実装: cond/then/else/merge の 4 ブロック構造 debug_log!( "[joinir_vm_bridge] Converting Select: dst={:?}, cond={:?}, then={:?}, else={:?}", - dst, cond, then_val, else_val + dst, + cond, + then_val, + else_val ); // 1. cond ブロック(現在のブロック) @@ -493,7 +414,12 @@ Call {{\n\ then_bb: then_block, else_bb: else_block, }; - finalize_block(&mut mir_func, cond_block, current_instructions, branch_terminator); + finalize_block( + &mut mir_func, + cond_block, + current_instructions, + branch_terminator, + ); // 6. then ブロック: dst = then_val; jump merge let mut then_block_obj = crate::mir::BasicBlock::new(then_block); @@ -502,7 +428,9 @@ Call {{\n\ src: *then_val, }); then_block_obj.instruction_spans.push(Span::unknown()); - then_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block }); + then_block_obj.terminator = Some(MirInstruction::Jump { + target: merge_block, + }); mir_func.blocks.insert(then_block, then_block_obj); // 7. else ブロック: dst = else_val; jump merge @@ -512,7 +440,9 @@ Call {{\n\ src: *else_val, }); else_block_obj.instruction_spans.push(Span::unknown()); - else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block }); + else_block_obj.terminator = Some(MirInstruction::Jump { + target: merge_block, + }); mir_func.blocks.insert(else_block, else_block_obj); // 8. merge ブロック作成(空) @@ -524,20 +454,26 @@ Call {{\n\ current_instructions = Vec::new(); } // Phase 33-6: IfMerge instruction conversion to MIR - JoinInst::IfMerge { cond, merges, k_next } => { + JoinInst::IfMerge { + cond, + merges, + k_next, + } => { // Phase 33-6: IfMerge を MIR の if/phi に変換 // Select と同じ 4 ブロック構造だが、複数の Copy を生成 // Phase 33-6 最小実装: k_next は None のみサポート if k_next.is_some() { return Err(JoinIrVmBridgeError::new( - "IfMerge: k_next continuation is not yet supported (Phase 33-6 minimal)".to_string(), + "IfMerge: k_next continuation is not yet supported (Phase 33-6 minimal)" + .to_string(), )); } debug_log!( "[joinir_vm_bridge] Converting IfMerge: cond={:?}, merges.len()={}", - cond, merges.len() + cond, + merges.len() ); // 1. cond ブロック(現在のブロック) @@ -561,7 +497,12 @@ Call {{\n\ then_bb: then_block, else_bb: else_block, }; - finalize_block(&mut mir_func, cond_block, current_instructions, branch_terminator); + finalize_block( + &mut mir_func, + cond_block, + current_instructions, + branch_terminator, + ); // 6. then ブロック: 各 merge について dst = then_val; jump merge let mut then_block_obj = crate::mir::BasicBlock::new(then_block); @@ -572,7 +513,9 @@ Call {{\n\ }); then_block_obj.instruction_spans.push(Span::unknown()); } - then_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block }); + then_block_obj.terminator = Some(MirInstruction::Jump { + target: merge_block, + }); mir_func.blocks.insert(then_block, then_block_obj); // 7. else ブロック: 各 merge について dst = else_val; jump merge @@ -584,7 +527,9 @@ Call {{\n\ }); else_block_obj.instruction_spans.push(Span::unknown()); } - else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block }); + else_block_obj.terminator = Some(MirInstruction::Jump { + target: merge_block, + }); mir_func.blocks.insert(else_block, else_block_obj); // 8. merge ブロック作成(空) @@ -600,12 +545,21 @@ Call {{\n\ let return_terminator = MirInstruction::Return { value: *value }; // Finalize current block with Return terminator - finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator); + finalize_block( + &mut mir_func, + current_block_id, + current_instructions, + return_terminator, + ); current_instructions = Vec::new(); } // Phase 41-4: NestedIfMerge instruction - JoinInst::NestedIfMerge { conds, merges, k_next } => { + JoinInst::NestedIfMerge { + conds, + merges, + k_next, + } => { // Phase 41-4.3: 多段 Branch + PHI を生成 // // 構造: @@ -632,7 +586,8 @@ Call {{\n\ debug_log!( "[joinir_vm_bridge] Converting NestedIfMerge: conds.len()={}, merges.len()={}", - conds.len(), merges.len() + conds.len(), + merges.len() ); // 1. ブロックを事前確保 @@ -663,7 +618,9 @@ Call {{\n\ // 2. level 1+ ブロックを事前作成(finalize_block は既存ブロックのみ更新) for level in 1..num_conds { let level_block = level_blocks[level]; - mir_func.blocks.insert(level_block, crate::mir::BasicBlock::new(level_block)); + mir_func + .blocks + .insert(level_block, crate::mir::BasicBlock::new(level_block)); } // 3. 各レベルで分岐を生成 @@ -686,7 +643,12 @@ Call {{\n\ if level == 0 { // level 0 は current_block、current_instructions を使う - finalize_block(&mut mir_func, this_block, current_instructions.clone(), branch_terminator); + finalize_block( + &mut mir_func, + this_block, + current_instructions.clone(), + branch_terminator, + ); current_instructions.clear(); } else { // level 1+ は事前作成済みブロックを更新 @@ -703,7 +665,9 @@ Call {{\n\ }); then_block_obj.instruction_spans.push(Span::unknown()); } - then_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block }); + then_block_obj.terminator = Some(MirInstruction::Jump { + target: merge_block, + }); mir_func.blocks.insert(then_block, then_block_obj); // 4. final_else_block: いずれかの条件 false の場合 → else_val を Copy @@ -715,7 +679,9 @@ Call {{\n\ }); else_block_obj.instruction_spans.push(Span::unknown()); } - else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block }); + else_block_obj.terminator = Some(MirInstruction::Jump { + target: merge_block, + }); mir_func.blocks.insert(final_else_block, else_block_obj); // 5. merge_block(空) @@ -763,7 +729,9 @@ Call {{\n\ } /// MirLikeInst → MirInstruction 変換 -fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result { +pub(crate) fn convert_mir_like_inst( + mir_like: &MirLikeInst, +) -> Result { match mir_like { MirLikeInst::Const { dst, value } => { let mir_const = match value { @@ -839,422 +807,3 @@ fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result Result { - debug_log!("[Phase 40-1] convert_join_module_to_mir_with_meta"); - - let mut mir_module = MirModule::new("joinir_bridge_with_meta".to_string()); - - // 1. 各関数を変換 - for (func_id, join_func) in &module.functions { - debug_log!( - "[Phase 40-1] Converting JoinFunction {} ({})", - func_id.0, join_func.name - ); - - // 2. 基本のMIR変換(既存ロジック) - let mut mir_func = convert_join_function_to_mir(join_func)?; - - // 3. Phase 40-1: if_modified_varsがあればloop exit PHI生成 - if let Some(m) = meta.get(func_id) { - if let Some(if_vars) = &m.if_modified_vars { - debug_log!( - "[Phase 40-1] Found if_modified_vars for func {:?}: {:?}", - func_id, - if_vars - ); - - // TODO(Phase 40-1.2): emit_loop_exit_phi_for_if_modified()実装後に有効化 - // emit_loop_exit_phi_for_if_modified(&mut mir_func, join_func, if_vars)?; - } - } - - mir_module.functions.insert(join_func_name(*func_id), mir_func); - } - - Ok(mir_module) -} - -/// if-in-loop modified varsに対するloop exit PHI生成 -/// -/// # Purpose -/// JoinIR Frontendで検出されたif-in-loop修正変数に対して、 -/// loop exit blockにPHI命令を追加する。 -/// -/// # Arguments -/// - `mir_func`: 変換済みMIR関数(ミュータブル) -/// - `join_func`: 元のJoinIR関数(メタデータ参照用) -/// - `if_modified_vars`: if-in-loop修正変数名のセット -/// -/// # Implementation Note -/// 現在の実装では、JoinIRのloop_step関数は単一ブロックベースであり、 -/// exit blockの特定が困難。Phase 40-1では**ログ出力のみ**を行い、 -/// 実際のPHI生成はPhase 40-2以降で実装する。 -/// -/// # TODO(Phase 40-2) -/// - exit block特定ロジック実装 -/// - PHI incoming value特定(header vs loop body) -/// - PHI命令生成とブロックへの挿入 -#[allow(dead_code)] -fn emit_loop_exit_phi_for_if_modified( - _mir_func: &mut MirFunction, - join_func: &crate::mir::join_ir::JoinFunction, - if_modified_vars: &std::collections::HashSet, -) -> Result<(), JoinIrVmBridgeError> { - debug_log!( - "[Phase 40-1] emit_loop_exit_phi_for_if_modified: func={}, vars={:?}", - join_func.name, - if_modified_vars - ); - - // Phase 40-1 minimal implementation: ログ出力のみ - // 理由: JoinIRのloop_step関数はtail-recursiveで、exit blockが明示的でない - // TODO(Phase 40-2): JoinIR構造を拡張してexit block情報を保持 - - if !if_modified_vars.is_empty() { - debug_log!( - "[Phase 40-1] Would generate {} loop exit PHIs for: {:?}", - if_modified_vars.len(), - if_modified_vars - ); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_convert_const_inst() { - let join_const = MirLikeInst::Const { - dst: ValueId(10), - value: ConstValue::Integer(42), - }; - - let mir_inst = convert_mir_like_inst(&join_const).unwrap(); - - match mir_inst { - MirInstruction::Const { dst, value } => { - assert_eq!(dst, ValueId(10)); - assert!(matches!(value, MirConstValue::Integer(42))); - } - _ => panic!("Expected Const instruction"), - } - } - - #[test] - fn test_convert_binop_inst() { - let join_binop = MirLikeInst::BinOp { - dst: ValueId(20), - op: BinOpKind::Add, - lhs: ValueId(10), - rhs: ValueId(11), - }; - - let mir_inst = convert_mir_like_inst(&join_binop).unwrap(); - - match mir_inst { - MirInstruction::BinOp { dst, op, lhs, rhs } => { - assert_eq!(dst, ValueId(20)); - assert_eq!(op, BinaryOp::Add); - assert_eq!(lhs, ValueId(10)); - assert_eq!(rhs, ValueId(11)); - } - _ => panic!("Expected BinOp instruction"), - } - } - - #[test] - fn test_convert_compare_inst() { - let join_cmp = MirLikeInst::Compare { - dst: ValueId(30), - op: CompareOp::Ge, - lhs: ValueId(10), - rhs: ValueId(11), - }; - - let mir_inst = convert_mir_like_inst(&join_cmp).unwrap(); - - match mir_inst { - MirInstruction::Compare { dst, op, lhs, rhs } => { - assert_eq!(dst, ValueId(30)); - assert_eq!(op, MirCompareOp::Ge); - assert_eq!(lhs, ValueId(10)); - assert_eq!(rhs, ValueId(11)); - } - _ => panic!("Expected Compare instruction"), - } - } - - // ======================================== - // Phase 45: read_quoted_from Bridge Tests - // ======================================== - - /// Phase 45: read_quoted_from JoinIR → MIR 変換テスト - /// - /// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。 - #[test] - fn test_read_quoted_from_joinir_to_mir_conversion() { - // Dev flag がない場合はスキップ - if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { - eprintln!( - "[Phase 45] Skipping test_read_quoted_from_joinir_to_mir_conversion: \ - Set HAKO_JOINIR_READ_QUOTED=1 to enable" - ); - return; - } - - use crate::mir::join_ir::frontend::AstToJoinIrLowerer; - - // 1. JoinModule を生成(lower_read_quoted_pattern を使用) - let program_json = serde_json::json!({ - "defs": [{ - "name": "read_quoted_from", - "params": ["s", "pos"], - "body": { "body": [] } - }] - }); - - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_read_quoted_pattern(&program_json); - - // 2. JoinIR → MIR 変換 - let mir_module = convert_joinir_to_mir(&join_module); - - assert!( - mir_module.is_ok(), - "JoinIR → MIR conversion should succeed: {:?}", - mir_module.err() - ); - - let mir_module = mir_module.unwrap(); - - // 3. MIR 構造の検証 - // 4 つの関数がある: entry, k_guard_fail, loop_step, k_exit - assert_eq!( - mir_module.functions.len(), - 4, - "MIR should have 4 functions" - ); - - // 関数名を確認 - let func_names: Vec<&str> = mir_module.functions.keys().map(|s| s.as_str()).collect(); - eprintln!("[Phase 45] MIR function names: {:?}", func_names); - - // join_func_0 (entry), join_func_1 (loop_step), join_func_2 (k_exit), join_func_3 (k_guard_fail) - assert!( - func_names.contains(&"join_func_0"), - "Should have entry function join_func_0" - ); - assert!( - func_names.contains(&"join_func_1"), - "Should have loop_step function join_func_1" - ); - assert!( - func_names.contains(&"join_func_2"), - "Should have k_exit function join_func_2" - ); - assert!( - func_names.contains(&"join_func_3"), - "Should have k_guard_fail function join_func_3" - ); - - eprintln!("[Phase 45] test_read_quoted_from_joinir_to_mir_conversion PASSED"); - } - - /// Phase 45: String 定数の MIR 変換テスト - #[test] - fn test_convert_string_const_inst() { - let join_const = MirLikeInst::Const { - dst: ValueId(50), - value: ConstValue::String("\"".to_string()), - }; - - let mir_inst = convert_mir_like_inst(&join_const).unwrap(); - - match mir_inst { - MirInstruction::Const { dst, value } => { - assert_eq!(dst, ValueId(50)); - match value { - MirConstValue::String(s) => assert_eq!(s, "\""), - _ => panic!("Expected String value"), - } - } - _ => panic!("Expected Const instruction"), - } - } - - /// Phase 45: A/B テスト - Route B (JoinIR) E2E 実行テスト - /// - /// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。 - /// - /// # Test Cases (from Phase 45 fixture) - /// - /// - T1: `"abc"` at pos 0 → `abc` - /// - T2: `""` at pos 0 → `` (empty) - /// - T3: `abc` at pos 0 → `` (guard fail, no quote) - /// - T4: `xx"def"` at pos 2 → `def` - /// - /// # Known Limitation - /// - /// T5 (escape handling) is skipped due to known PHI issue - /// with variable reassignment inside if-blocks. - #[test] - fn test_read_quoted_from_route_b_e2e() { - // Dev flag がない場合はスキップ - if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { - eprintln!( - "[Phase 45] Skipping test_read_quoted_from_route_b_e2e: \ - Set HAKO_JOINIR_READ_QUOTED=1 to enable" - ); - return; - } - - use crate::mir::join_ir::frontend::AstToJoinIrLowerer; - use crate::mir::join_ir_ops::JoinValue; - - // 1. JoinModule を生成 - let program_json = serde_json::json!({ - "defs": [{ - "name": "read_quoted_from", - "params": ["s", "pos"], - "body": { "body": [] } - }] - }); - - let mut lowerer = AstToJoinIrLowerer::new(); - let join_module = lowerer.lower_read_quoted_pattern(&program_json); - - let entry_func = join_module.entry.expect("Entry function should exist"); - - // 2. A/B テスト実行 - // Note: Route B (JoinIR) は run_joinir_via_vm で実行 - // Route A (既存) は別途フィクスチャで検証済み - - // T1: "abc" at pos 0 → "abc" - let t1_result = run_joinir_via_vm( - &join_module, - entry_func, - &[JoinValue::Str("\"abc\"".to_string()), JoinValue::Int(0)], - ); - match &t1_result { - Ok(JoinValue::Str(s)) => { - assert_eq!(s, "abc", "T1: Expected 'abc', got '{}'", s); - eprintln!("[Phase 45] T1 PASS: \"abc\" at pos 0 → '{}'", s); - } - Ok(v) => panic!("T1: Expected Str, got {:?}", v), - Err(e) => eprintln!("[Phase 45] T1 SKIP (execution not supported): {:?}", e), - } - - // T2: "" at pos 0 → "" (empty) - let t2_result = run_joinir_via_vm( - &join_module, - entry_func, - &[JoinValue::Str("\"\"".to_string()), JoinValue::Int(0)], - ); - match &t2_result { - Ok(JoinValue::Str(s)) => { - assert_eq!(s, "", "T2: Expected '', got '{}'", s); - eprintln!("[Phase 45] T2 PASS: \"\" at pos 0 → '{}'", s); - } - Ok(v) => panic!("T2: Expected Str, got {:?}", v), - Err(e) => eprintln!("[Phase 45] T2 SKIP (execution not supported): {:?}", e), - } - - // T3: abc at pos 0 → "" (guard fail) - let t3_result = run_joinir_via_vm( - &join_module, - entry_func, - &[JoinValue::Str("abc".to_string()), JoinValue::Int(0)], - ); - match &t3_result { - Ok(JoinValue::Str(s)) => { - assert_eq!(s, "", "T3: Expected '', got '{}'", s); - eprintln!("[Phase 45] T3 PASS: abc at pos 0 → '{}'", s); - } - Ok(v) => panic!("T3: Expected Str, got {:?}", v), - Err(e) => eprintln!("[Phase 45] T3 SKIP (execution not supported): {:?}", e), - } - - // T4: xx"def" at pos 2 → "def" - let t4_result = run_joinir_via_vm( - &join_module, - entry_func, - &[JoinValue::Str("xx\"def\"".to_string()), JoinValue::Int(2)], - ); - match &t4_result { - Ok(JoinValue::Str(s)) => { - assert_eq!(s, "def", "T4: Expected 'def', got '{}'", s); - eprintln!("[Phase 45] T4 PASS: xx\"def\" at pos 2 → '{}'", s); - } - Ok(v) => panic!("T4: Expected Str, got {:?}", v), - Err(e) => eprintln!("[Phase 45] T4 SKIP (execution not supported): {:?}", e), - } - - // T5: Escape handling - "a\"b" at pos 0 → "a"b" (escaped quote) - // Phase 46: IfMerge で if-body 後の i と ch をマージ - let enable_escape_ifmerge = - std::env::var("HAKO_JOINIR_READ_QUOTED_IFMERGE").ok().as_deref() == Some("1"); - - if enable_escape_ifmerge { - // 入力: "a\"b" → 「"」で始まり、a, \", b, 「"」で終わる - // 期待出力: a"b(エスケープされた引用符を含む) - let t5_input = "\"a\\\"b\""; // Rust エスケープ: "a\"b" → JSON "a\"b" - let t5_result = run_joinir_via_vm( - &join_module, - entry_func, - &[JoinValue::Str(t5_input.to_string()), JoinValue::Int(0)], - ); - match &t5_result { - Ok(JoinValue::Str(s)) => { - let expected = "a\"b"; // エスケープ後: a"b - assert_eq!( - s, expected, - "T5: Expected '{}', got '{}'", - expected, s - ); - eprintln!( - "[Phase 46] T5 PASS: \"a\\\"b\" at pos 0 → '{}' (escape handling works!)", - s - ); - } - Ok(v) => panic!("T5: Expected Str, got {:?}", v), - Err(e) => eprintln!("[Phase 46] T5 SKIP (execution not supported): {:?}", e), - } - } else { - eprintln!( - "[Phase 45] T5 SKIP: Set HAKO_JOINIR_READ_QUOTED_IFMERGE=1 to enable \ - escape handling (Phase 46)" - ); - } - - eprintln!("[Phase 45] test_read_quoted_from_route_b_e2e completed"); - } -} diff --git a/src/mir/join_ir_vm_bridge/meta.rs b/src/mir/join_ir_vm_bridge/meta.rs new file mode 100644 index 00000000..0b38a978 --- /dev/null +++ b/src/mir/join_ir_vm_bridge/meta.rs @@ -0,0 +1,106 @@ +use super::{convert_join_function_to_mir, join_func_name, JoinIrVmBridgeError}; +use crate::mir::join_ir::frontend::JoinFuncMetaMap; +use crate::mir::join_ir::JoinModule; +use crate::mir::{MirFunction, MirModule}; + +/// Phase 40-1実験用: JoinFuncMetaを使ったMIR変換 +/// +/// 既存の run_joinir_via_vm() を拡張し、 +/// if_modified_varsがあればloop exit PHIを生成する。 +/// +/// # Phase 40-1専用 +/// この関数はPhase 40-1 A/Bテスト専用。 +/// 本番パスでは使わない(従来のrun_joinir_via_vm()を使う)。 +/// +/// # Architecture +/// JoinModule → MirModule変換において、JoinFuncMetaを参照してPHI生成を拡張 +/// +/// # Returns +/// - `Ok(MirModule)`: 変換済みMIRモジュール(PHI拡張版) +pub fn convert_join_module_to_mir_with_meta( + module: &JoinModule, + meta: &JoinFuncMetaMap, +) -> Result { + debug_log!("[Phase 40-1] convert_join_module_to_mir_with_meta"); + + let mut mir_module = MirModule::new("joinir_bridge_with_meta".to_string()); + + // 1. 各関数を変換 + for (func_id, join_func) in &module.functions { + debug_log!( + "[Phase 40-1] Converting JoinFunction {} ({})", + func_id.0, + join_func.name + ); + + // 2. 基本のMIR変換(既存ロジック) + let mir_func = convert_join_function_to_mir(join_func)?; + + // 3. Phase 40-1: if_modified_varsがあればloop exit PHI生成 + if let Some(m) = meta.get(func_id) { + if let Some(if_vars) = &m.if_modified_vars { + debug_log!( + "[Phase 40-1] Found if_modified_vars for func {:?}: {:?}", + func_id, + if_vars + ); + + // TODO(Phase 40-1.2): emit_loop_exit_phi_for_if_modified()実装後に有効化 + // emit_loop_exit_phi_for_if_modified(&mut mir_func, join_func, if_vars)?; + } + } + + mir_module + .functions + .insert(join_func_name(*func_id), mir_func); + } + + Ok(mir_module) +} + +/// if-in-loop modified varsに対するloop exit PHI生成 +/// +/// # Purpose +/// JoinIR Frontendで検出されたif-in-loop修正変数に対して、 +/// loop exit blockにPHI命令を追加する。 +/// +/// # Arguments +/// - `mir_func`: 変換済みMIR関数(ミュータブル) +/// - `join_func`: 元のJoinIR関数(メタデータ参照用) +/// - `if_modified_vars`: if-in-loop修正変数名のセット +/// +/// # Implementation Note +/// 現在の実装では、JoinIRのloop_step関数は単一ブロックベースであり、 +/// exit blockの特定が困難。Phase 40-1では**ログ出力のみ**を行い、 +/// 実際のPHI生成はPhase 40-2以降で実装する。 +/// +/// # TODO(Phase 40-2) +/// - exit block特定ロジック実装 +/// - PHI incoming value特定(header vs loop body) +/// - PHI命令生成とブロックへの挿入 +#[allow(dead_code)] +pub(crate) fn emit_loop_exit_phi_for_if_modified( + _mir_func: &mut MirFunction, + join_func: &crate::mir::join_ir::JoinFunction, + if_modified_vars: &std::collections::HashSet, +) -> Result<(), JoinIrVmBridgeError> { + debug_log!( + "[Phase 40-1] emit_loop_exit_phi_for_if_modified: func={}, vars={:?}", + join_func.name, + if_modified_vars + ); + + // Phase 40-1 minimal implementation: ログ出力のみ + // 理由: JoinIRのloop_step関数はtail-recursiveで、exit blockが明示的でない + // TODO(Phase 40-2): JoinIR構造を拡張してexit block情報を保持 + + if !if_modified_vars.is_empty() { + debug_log!( + "[Phase 40-1] Would generate {} loop exit PHIs for: {:?}", + if_modified_vars.len(), + if_modified_vars + ); + } + + Ok(()) +} diff --git a/src/mir/join_ir_vm_bridge/mod.rs b/src/mir/join_ir_vm_bridge/mod.rs new file mode 100644 index 00000000..5dee3626 --- /dev/null +++ b/src/mir/join_ir_vm_bridge/mod.rs @@ -0,0 +1,73 @@ +//! Phase 27-shortterm S-4: JoinIR → Rust VM Bridge +//! +//! 目的: JoinIR(正規化された IR)を Rust VM で実行するブリッジ層 +//! +//! ## Architecture +//! ```text +//! JoinIR (normalized) → MirModule → Rust VM → Result +//! ↑ ↑ ↑ +//! PHI bugs VM input Execution +//! eliminated format (GC, plugins) +//! ``` +//! +//! ## Design Principles +//! - JoinIR の正規化構造を保持したまま VM に渡す +//! - マッピングだけで済ませる(JoinIR でやった正規化は消えない) +//! - VM の機能(GC、プラグイン、エラーハンドリング)を活用 +//! +//! ## Minimal Instruction Set (S-4.3) +//! - **Compute**: Const, BinOp, Compare +//! - **BoxCall**: StringBox メソッド呼び出し +//! - **Call/Jump/Ret**: 制御フロー +//! +//! Phase 27-shortterm scope: skip_ws で green 化できれば成功 + +use crate::backend::VMError; +use crate::mir::join_ir::JoinFuncId; + +#[macro_use] +mod logging { + macro_rules! debug_log { + ($($arg:tt)*) => { + if crate::config::env::joinir_vm_bridge_debug() { + eprintln!($($arg)*); + } + }; + } +} + +mod convert; +mod meta; +mod runner; + +#[cfg(test)] +mod tests; + +pub(crate) use convert::{convert_join_function_to_mir, convert_joinir_to_mir}; +pub use meta::convert_join_module_to_mir_with_meta; +pub use runner::run_joinir_via_vm; + +/// Phase 27-shortterm S-4 エラー型 +#[derive(Debug, Clone)] +pub struct JoinIrVmBridgeError { + pub message: String, +} + +impl JoinIrVmBridgeError { + pub fn new(msg: impl Into) -> Self { + Self { + message: msg.into(), + } + } +} + +impl From for JoinIrVmBridgeError { + fn from(err: VMError) -> Self { + JoinIrVmBridgeError::new(format!("VM error: {:?}", err)) + } +} + +/// JoinFuncId から MIR 用の関数名を生成 +pub(crate) fn join_func_name(id: JoinFuncId) -> String { + format!("join_func_{}", id.0) +} diff --git a/src/mir/join_ir_vm_bridge/runner.rs b/src/mir/join_ir_vm_bridge/runner.rs new file mode 100644 index 00000000..68733e33 --- /dev/null +++ b/src/mir/join_ir_vm_bridge/runner.rs @@ -0,0 +1,65 @@ +use super::{convert_joinir_to_mir, join_func_name, JoinIrVmBridgeError}; +use crate::backend::{MirInterpreter, VMValue}; +use crate::mir::join_ir::JoinFuncId; +use crate::mir::join_ir::JoinModule; +use crate::mir::join_ir_ops::JoinValue; + +/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント +/// +/// ## Arguments +/// - `join_module`: JoinIR モジュール(正規化済み) +/// - `entry_func`: エントリーポイント関数ID +/// - `args`: 初期引数(JoinValue 形式) +/// +/// ## Returns +/// - `Ok(JoinValue)`: 実行結果 +/// - `Err(JoinIrVmBridgeError)`: 変換エラーまたは実行エラー +/// +/// ## Example +/// ```ignore +/// let join_module = lower_skip_ws_to_joinir(&mir_module)?; +/// let result = run_joinir_via_vm( +/// &join_module, +/// JoinFuncId::new(0), +/// &[JoinValue::Str(" hello".to_string()), JoinValue::Int(7)] +/// )?; +/// assert_eq!(result, JoinValue::Int(2)); +/// ``` +pub fn run_joinir_via_vm( + join_module: &JoinModule, + entry_func: JoinFuncId, + args: &[JoinValue], +) -> Result { + debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3"); + debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution"); + + // Step 1: JoinIR → MIR 変換 + let mir_module = convert_joinir_to_mir(join_module)?; + + debug_log!( + "[joinir_vm_bridge] Converted {} JoinIR functions to MIR", + join_module.functions.len() + ); + + // Step 2: VM 実行 + let mut vm = MirInterpreter::new(); + + debug_log!( + "[joinir_vm_bridge] Executing via VM with {} arguments", + args.len() + ); + + // Convert JoinValue → VMValue (BoxRef 含む) + let vm_args: Vec = args.iter().cloned().map(|v| v.into_vm_value()).collect(); + + let entry_name = join_func_name(entry_func); + let result = vm.execute_function_with_args(&mir_module, &entry_name, &vm_args)?; + + // Step 3: VMValue → JoinValue 変換 + let join_result = JoinValue::from_vm_value(&result) + .map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?; + + debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result); + + Ok(join_result) +} diff --git a/src/mir/join_ir_vm_bridge/tests.rs b/src/mir/join_ir_vm_bridge/tests.rs new file mode 100644 index 00000000..91c5764f --- /dev/null +++ b/src/mir/join_ir_vm_bridge/tests.rs @@ -0,0 +1,301 @@ +use super::convert::convert_mir_like_inst; +use super::*; +use crate::mir::join_ir::frontend::AstToJoinIrLowerer; +use crate::mir::join_ir_ops::JoinValue; +use crate::mir::{BinaryOp, CompareOp as MirCompareOp, MirInstruction, ValueId}; + +#[test] +fn test_convert_const_inst() { + let join_const = crate::mir::join_ir::MirLikeInst::Const { + dst: ValueId(10), + value: crate::mir::join_ir::ConstValue::Integer(42), + }; + + let mir_inst = convert_mir_like_inst(&join_const).unwrap(); + + match mir_inst { + MirInstruction::Const { dst, value } => { + assert_eq!(dst, ValueId(10)); + assert!(matches!(value, crate::mir::ConstValue::Integer(42))); + } + _ => panic!("Expected Const instruction"), + } +} + +#[test] +fn test_convert_binop_inst() { + let join_binop = crate::mir::join_ir::MirLikeInst::BinOp { + dst: ValueId(20), + op: crate::mir::join_ir::BinOpKind::Add, + lhs: ValueId(10), + rhs: ValueId(11), + }; + + let mir_inst = convert_mir_like_inst(&join_binop).unwrap(); + + match mir_inst { + MirInstruction::BinOp { dst, op, lhs, rhs } => { + assert_eq!(dst, ValueId(20)); + assert_eq!(op, BinaryOp::Add); + assert_eq!(lhs, ValueId(10)); + assert_eq!(rhs, ValueId(11)); + } + _ => panic!("Expected BinOp instruction"), + } +} + +#[test] +fn test_convert_compare_inst() { + let join_cmp = crate::mir::join_ir::MirLikeInst::Compare { + dst: ValueId(30), + op: crate::mir::join_ir::CompareOp::Ge, + lhs: ValueId(10), + rhs: ValueId(11), + }; + + let mir_inst = convert_mir_like_inst(&join_cmp).unwrap(); + + match mir_inst { + MirInstruction::Compare { dst, op, lhs, rhs } => { + assert_eq!(dst, ValueId(30)); + assert_eq!(op, MirCompareOp::Ge); + assert_eq!(lhs, ValueId(10)); + assert_eq!(rhs, ValueId(11)); + } + _ => panic!("Expected Compare instruction"), + } +} + +// ======================================== +// Phase 45: read_quoted_from Bridge Tests +// ======================================== + +/// Phase 45: read_quoted_from JoinIR → MIR 変換テスト +/// +/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。 +#[test] +fn test_read_quoted_from_joinir_to_mir_conversion() { + // Dev flag がない場合はスキップ + if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { + eprintln!( + "[Phase 45] Skipping test_read_quoted_from_joinir_to_mir_conversion: \ + Set HAKO_JOINIR_READ_QUOTED=1 to enable" + ); + return; + } + + // 1. JoinModule を生成(lower_read_quoted_pattern を使用) + let program_json = serde_json::json!({ + "defs": [{ + "name": "read_quoted_from", + "params": ["s", "pos"], + "body": { "body": [] } + }] + }); + + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_read_quoted_pattern(&program_json); + + // 2. JoinIR → MIR 変換 + let mir_module = convert_joinir_to_mir(&join_module); + + assert!( + mir_module.is_ok(), + "JoinIR → MIR conversion should succeed: {:?}", + mir_module.err() + ); + + let mir_module = mir_module.unwrap(); + + // 3. MIR 構造の検証 + // 4 つの関数がある: entry, k_guard_fail, loop_step, k_exit + assert_eq!(mir_module.functions.len(), 4, "MIR should have 4 functions"); + + // 関数名を確認 + let func_names: Vec<&str> = mir_module.functions.keys().map(|s| s.as_str()).collect(); + eprintln!("[Phase 45] MIR function names: {:?}", func_names); + + // join_func_0 (entry), join_func_1 (loop_step), join_func_2 (k_exit), join_func_3 (k_guard_fail) + assert!( + func_names.contains(&"join_func_0"), + "Should have entry function join_func_0" + ); + assert!( + func_names.contains(&"join_func_1"), + "Should have loop_step function join_func_1" + ); + assert!( + func_names.contains(&"join_func_2"), + "Should have k_exit function join_func_2" + ); + assert!( + func_names.contains(&"join_func_3"), + "Should have k_guard_fail function join_func_3" + ); + + eprintln!("[Phase 45] test_read_quoted_from_joinir_to_mir_conversion PASSED"); +} + +/// Phase 45: String 定数の MIR 変換テスト +#[test] +fn test_convert_string_const_inst() { + let join_const = crate::mir::join_ir::MirLikeInst::Const { + dst: ValueId(50), + value: crate::mir::join_ir::ConstValue::String("\"".to_string()), + }; + + let mir_inst = convert_mir_like_inst(&join_const).unwrap(); + + match mir_inst { + MirInstruction::Const { dst, value } => { + assert_eq!(dst, ValueId(50)); + match value { + crate::mir::ConstValue::String(s) => assert_eq!(s, "\""), + _ => panic!("Expected String value"), + } + } + _ => panic!("Expected Const instruction"), + } +} + +/// Phase 45: A/B テスト - Route B (JoinIR) E2E 実行テスト +/// +/// HAKO_JOINIR_READ_QUOTED=1 が設定されている場合のみ実行。 +/// +/// # Test Cases (from Phase 45 fixture) +/// +/// - T1: `"abc"` at pos 0 → `abc` +/// - T2: `""` at pos 0 → `` (empty) +/// - T3: `abc` at pos 0 → `` (guard fail, no quote) +/// - T4: `xx"def"` at pos 2 → `def` +/// +/// # Known Limitation +/// +/// T5 (escape handling) is skipped due to known PHI issue +/// with variable reassignment inside if-blocks. +#[test] +fn test_read_quoted_from_route_b_e2e() { + // Dev flag がない場合はスキップ + if std::env::var("HAKO_JOINIR_READ_QUOTED").ok().as_deref() != Some("1") { + eprintln!( + "[Phase 45] Skipping test_read_quoted_from_route_b_e2e: \ + Set HAKO_JOINIR_READ_QUOTED=1 to enable" + ); + return; + } + + // 1. JoinModule を生成 + let program_json = serde_json::json!({ + "defs": [{ + "name": "read_quoted_from", + "params": ["s", "pos"], + "body": { "body": [] } + }] + }); + + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_read_quoted_pattern(&program_json); + + let entry_func = join_module.entry.expect("Entry function should exist"); + + // 2. A/B テスト実行 + // Note: Route B (JoinIR) は run_joinir_via_vm で実行 + // Route A (既存) は別途フィクスチャで検証済み + + // T1: "abc" at pos 0 → "abc" + let t1_result = run_joinir_via_vm( + &join_module, + entry_func, + &[JoinValue::Str("\"abc\"".to_string()), JoinValue::Int(0)], + ); + match &t1_result { + Ok(JoinValue::Str(s)) => { + assert_eq!(s, "abc", "T1: Expected 'abc', got '{}'", s); + eprintln!("[Phase 45] T1 PASS: \"abc\" at pos 0 → '{}'", s); + } + Ok(v) => panic!("T1: Expected Str, got {:?}", v), + Err(e) => eprintln!("[Phase 45] T1 SKIP (execution not supported): {:?}", e), + } + + // T2: "" at pos 0 → "" (empty) + let t2_result = run_joinir_via_vm( + &join_module, + entry_func, + &[JoinValue::Str("\"\"".to_string()), JoinValue::Int(0)], + ); + match &t2_result { + Ok(JoinValue::Str(s)) => { + assert_eq!(s, "", "T2: Expected '', got '{}'", s); + eprintln!("[Phase 45] T2 PASS: \"\" at pos 0 → '{}'", s); + } + Ok(v) => panic!("T2: Expected Str, got {:?}", v), + Err(e) => eprintln!("[Phase 45] T2 SKIP (execution not supported): {:?}", e), + } + + // T3: abc at pos 0 → "" (guard fail) + let t3_result = run_joinir_via_vm( + &join_module, + entry_func, + &[JoinValue::Str("abc".to_string()), JoinValue::Int(0)], + ); + match &t3_result { + Ok(JoinValue::Str(s)) => { + assert_eq!(s, "", "T3: Expected '', got '{}'", s); + eprintln!("[Phase 45] T3 PASS: abc at pos 0 → '{}'", s); + } + Ok(v) => panic!("T3: Expected Str, got {:?}", v), + Err(e) => eprintln!("[Phase 45] T3 SKIP (execution not supported): {:?}", e), + } + + // T4: xx"def" at pos 2 → "def" + let t4_result = run_joinir_via_vm( + &join_module, + entry_func, + &[JoinValue::Str("xx\"def\"".to_string()), JoinValue::Int(2)], + ); + match &t4_result { + Ok(JoinValue::Str(s)) => { + assert_eq!(s, "def", "T4: Expected 'def', got '{}'", s); + eprintln!("[Phase 45] T4 PASS: xx\"def\" at pos 2 → '{}'", s); + } + Ok(v) => panic!("T4: Expected Str, got {:?}", v), + Err(e) => eprintln!("[Phase 45] T4 SKIP (execution not supported): {:?}", e), + } + + // T5: Escape handling - "a\"b" at pos 0 → "a"b" (escaped quote) + // Phase 46: IfMerge で if-body 後の i と ch をマージ + let enable_escape_ifmerge = std::env::var("HAKO_JOINIR_READ_QUOTED_IFMERGE") + .ok() + .as_deref() + == Some("1"); + + if enable_escape_ifmerge { + // 入力: "a\"b" → 「"」で始まり、a, \", b, 「"」で終わる + // 期待出力: a"b(エスケープされた引用符を含む) + let t5_input = "\"a\\\"b\""; // Rust エスケープ: "a\"b" → JSON "a\"b" + let t5_result = run_joinir_via_vm( + &join_module, + entry_func, + &[JoinValue::Str(t5_input.to_string()), JoinValue::Int(0)], + ); + match &t5_result { + Ok(JoinValue::Str(s)) => { + let expected = "a\"b"; // エスケープ後: a"b + assert_eq!(s, expected, "T5: Expected '{}', got '{}'", expected, s); + eprintln!( + "[Phase 46] T5 PASS: \"a\\\"b\" at pos 0 → '{}' (escape handling works!)", + s + ); + } + Ok(v) => panic!("T5: Expected Str, got {:?}", v), + Err(e) => eprintln!("[Phase 46] T5 SKIP (execution not supported): {:?}", e), + } + } else { + eprintln!( + "[Phase 45] T5 SKIP: Set HAKO_JOINIR_READ_QUOTED_IFMERGE=1 to enable \ + escape handling (Phase 46)" + ); + } + + eprintln!("[Phase 45] test_read_quoted_from_route_b_e2e completed"); +} diff --git a/src/mir/join_ir_vm_bridge_dispatch.rs b/src/mir/join_ir_vm_bridge_dispatch.rs index ae59fbf6..dd297c17 100644 --- a/src/mir/join_ir_vm_bridge_dispatch.rs +++ b/src/mir/join_ir_vm_bridge_dispatch.rs @@ -10,9 +10,9 @@ //! 将来は LoopScopeShape / ExitAnalysis ベースの構造判定に差し替え予定。 use crate::config::env::{joinir_experiment_enabled, joinir_vm_bridge_enabled}; +use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingresolver_to_joinir; use crate::mir::join_ir::lowering::stageb_body::lower_stageb_body_to_joinir; use crate::mir::join_ir::lowering::stageb_funcscanner::lower_stageb_funcscanner_to_joinir; -use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingresolver_to_joinir; use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, lower_skip_ws_to_joinir, JoinFuncId}; use crate::mir::join_ir_ops::JoinValue; use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; @@ -300,9 +300,7 @@ fn try_run_stageb_body(module: &MirModule) -> bool { /// ArrayBox/MapBox 引数がまだ JoinValue でサポートされていないため、 /// JoinIR lowering / Bridge 構造検証のみ行い、実行は VM Route A にフォールバック。 fn try_run_stageb_funcscanner(module: &MirModule) -> bool { - eprintln!( - "[joinir/vm_bridge] Attempting JoinIR path for StageBFuncScannerBox.scan_all_boxes" - ); + eprintln!("[joinir/vm_bridge] Attempting JoinIR path for StageBFuncScannerBox.scan_all_boxes"); match lower_stageb_funcscanner_to_joinir(module) { Some(join_module) => { diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 4bb47f88..e893ceaa 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -300,8 +300,7 @@ impl<'a> LoopBuilder<'a> { .map(|f| f.signature.name.clone()) .unwrap_or_default(); - let bypass_flags = - crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); + let bypass_flags = crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); if bypass_flags.header { // Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。 diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 0158f43a..8f1b4936 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -34,7 +34,6 @@ pub mod join_ir_vm_bridge_dispatch; // Phase 30 F-4.4: JoinIR VM ブリッジ di pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス pub mod optimizer_passes; // optimizer passes (normalize/diagnostics) pub mod optimizer_stats; // extracted stats struct -mod spanned_instruction; pub mod passes; pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only) pub mod printer; @@ -42,6 +41,7 @@ mod printer_helpers; // internal helpers extracted from printer.rs pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery) pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind) pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs) +mod spanned_instruction; pub mod value_id; pub mod value_kind; // Phase 26-A: ValueId型安全化 pub mod verification; @@ -57,9 +57,9 @@ pub use instruction::MirInstruction; pub use join_ir_runner::{run_joinir_function, JoinRuntimeError, JoinValue}; pub use optimizer::MirOptimizer; pub use printer::MirPrinter; -pub use spanned_instruction::{SpannedInstRef, SpannedInstruction}; pub use query::{MirQuery, MirQueryBox}; pub use slot_registry::{BoxTypeId, MethodSlot}; +pub use spanned_instruction::{SpannedInstRef, SpannedInstruction}; pub use types::{ BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp, }; diff --git a/src/mir/optimizer_passes/normalize.rs b/src/mir/optimizer_passes/normalize.rs index ba955f0d..edc29524 100644 --- a/src/mir/optimizer_passes/normalize.rs +++ b/src/mir/optimizer_passes/normalize.rs @@ -1,9 +1,9 @@ +use crate::ast::Span; use crate::mir::optimizer::MirOptimizer; use crate::mir::optimizer_stats::OptimizationStats; use crate::mir::{ BarrierOp, EffectMask, MirModule, SpannedInstruction, TypeOpKind, ValueId, WeakRefOp, }; -use crate::ast::Span; fn idemp_enabled() -> bool { std::env::var("NYASH_MIR_DEV_IDEMP").ok().as_deref() == Some("1") @@ -67,10 +67,8 @@ pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> O }); } - let (insts, spans): (Vec<_>, Vec<_>) = new_spanned - .into_iter() - .map(|sp| (sp.inst, sp.span)) - .unzip(); + let (insts, spans): (Vec<_>, Vec<_>) = + new_spanned.into_iter().map(|sp| (sp.inst, sp.span)).unzip(); block.instructions = insts; block.instruction_spans = spans; block.effects = block @@ -132,10 +130,8 @@ pub fn normalize_python_helper_calls( new_spanned.push(SpannedInstruction { inst, span }); } - let (insts, spans): (Vec<_>, Vec<_>) = new_spanned - .into_iter() - .map(|sp| (sp.inst, sp.span)) - .unzip(); + let (insts, spans): (Vec<_>, Vec<_>) = + new_spanned.into_iter().map(|sp| (sp.inst, sp.span)).unzip(); block.instructions = insts; block.instruction_spans = spans; block.effects = block @@ -407,10 +403,8 @@ pub fn normalize_legacy_instructions( block.terminator_span = Some(span); } - let (insts, spans): (Vec<_>, Vec<_>) = new_spanned - .into_iter() - .map(|sp| (sp.inst, sp.span)) - .unzip(); + let (insts, spans): (Vec<_>, Vec<_>) = + new_spanned.into_iter().map(|sp| (sp.inst, sp.span)).unzip(); block.instructions = insts; block.instruction_spans = spans; block.effects = block diff --git a/src/mir/phi_core/if_phi.rs b/src/mir/phi_core/if_phi.rs index 2588c269..89398fc2 100644 --- a/src/mir/phi_core/if_phi.rs +++ b/src/mir/phi_core/if_phi.rs @@ -80,7 +80,6 @@ pub fn infer_type_from_phi( None } - // ======================================== // Phase 40-4.1: collect_assigned_vars削除完了 // ======================================== @@ -309,4 +308,3 @@ pub trait PhiMergeOps { // ======================================== // - merge_modified_at_merge_with (70行) - PhiBuilderBox::generate_if_phis()に置き換え済み // - merge_with_reset_at_merge_with (29行) - 上記のwrapper、同様にデッドコード - diff --git a/src/mir/phi_core/loop_snapshot_merge.rs b/src/mir/phi_core/loop_snapshot_merge.rs index 6693954d..aee375c2 100644 --- a/src/mir/phi_core/loop_snapshot_merge.rs +++ b/src/mir/phi_core/loop_snapshot_merge.rs @@ -313,7 +313,11 @@ mod tests { .unwrap(); // "ch" should NOT have exit PHI (BodyLocalInternal - not in all exit preds) - assert_eq!(result.len(), 0, "Expected no exit PHI for BodyLocalInternal variable 'ch'"); + assert_eq!( + result.len(), + 0, + "Expected no exit PHI for BodyLocalInternal variable 'ch'" + ); } /// Test merge_exit_with_classification with header not in exit_preds (break-only loop) @@ -357,7 +361,9 @@ mod tests { let i_inputs = result.get("i").unwrap(); assert_eq!(i_inputs.len(), 1); assert!(i_inputs.contains(&(break_bb, ValueId::new(10)))); - assert!(!i_inputs.contains(&(header_id, ValueId::new(1))), - "Header input should be excluded when header not in exit_preds"); + assert!( + !i_inputs.contains(&(header_id, ValueId::new(1))), + "Header input should be excluded when header not in exit_preds" + ); } } diff --git a/src/mir/phi_core/phi_builder_box.rs b/src/mir/phi_core/phi_builder_box.rs index f04baf53..f167e8b1 100644 --- a/src/mir/phi_core/phi_builder_box.rs +++ b/src/mir/phi_core/phi_builder_box.rs @@ -111,9 +111,7 @@ pub struct IfPhiContext { impl PhiBuilderBox { /// 新しいPhiBuilderBoxを作成 pub fn new() -> Self { - Self { - if_context: None, - } + Self { if_context: None } } /// Phase 26-F-3: ループ内if-mergeコンテキストを設定 diff --git a/src/mir/ssot/cf_common.rs b/src/mir/ssot/cf_common.rs index 2bba3ac6..ca5d4316 100644 --- a/src/mir/ssot/cf_common.rs +++ b/src/mir/ssot/cf_common.rs @@ -1,7 +1,7 @@ +use crate::ast::Span; use crate::mir::{ BasicBlockId, CompareOp, MirFunction, MirInstruction, SpannedInstruction, ValueId, }; -use crate::ast::Span; /// Emit a MIR Compare instruction into the current block (function-level SSOT helper) pub fn emit_compare_func( diff --git a/src/mir/verification.rs b/src/mir/verification.rs index 7a23d3b3..986501a3 100644 --- a/src/mir/verification.rs +++ b/src/mir/verification.rs @@ -93,8 +93,9 @@ impl MirVerifier { instruction_index ); if let Some(bb) = function.blocks.get(block) { - let inst_opt = - bb.all_spanned_instructions_enumerated().nth(*instruction_index); + let inst_opt = bb + .all_spanned_instructions_enumerated() + .nth(*instruction_index); if let Some((_idx, sp)) = inst_opt { eprintln!("[mir-ssa-debug-inst] inst={:?}", sp.inst); } diff --git a/src/runner/json_v0_bridge/lowering/expr.rs b/src/runner/json_v0_bridge/lowering/expr.rs index 54403c5e..5f5b6898 100644 --- a/src/runner/json_v0_bridge/lowering/expr.rs +++ b/src/runner/json_v0_bridge/lowering/expr.rs @@ -1,11 +1,11 @@ +use super::globals::resolve_bridge_global; use super::match_expr; use super::merge::new_block; -use super::globals::resolve_bridge_global; use super::ternary; -use super::BridgeEnv; use super::throw_lower::lower_throw; -use crate::mir::{BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId}; +use super::BridgeEnv; use crate::ast::Span; +use crate::mir::{BasicBlockId, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId}; use std::collections::BTreeMap; use super::super::ast::ExprV0; @@ -54,9 +54,7 @@ impl<'a> VarScope for MapVars<'a> { } // Bridge 固有のグローバル解決(imports/hostbridge/env/me dummy)は専用モジュールに委譲 - if let Some(vid) = - resolve_bridge_global(name, env, f, cur_bb, self.vars)? - { + if let Some(vid) = resolve_bridge_global(name, env, f, cur_bb, self.vars)? { return Ok(Some(vid)); } diff --git a/src/runner/json_v0_bridge/lowering/globals.rs b/src/runner/json_v0_bridge/lowering/globals.rs index a325776d..2d1ec182 100644 --- a/src/runner/json_v0_bridge/lowering/globals.rs +++ b/src/runner/json_v0_bridge/lowering/globals.rs @@ -74,4 +74,3 @@ pub(super) fn resolve_bridge_global( Ok(None) } } - diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index 057bbbf0..ce46720c 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -23,10 +23,10 @@ use super::super::ast::ExprV0; use super::super::ast::StmtV0; use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext}; +use crate::ast::Span; use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; use crate::mir::phi_core::phi_input_collector::PhiInputCollector; use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; -use crate::ast::Span; use std::collections::BTreeMap; /// LoopForm v2 用の JSON bridge 実装。 diff --git a/src/runner/json_v0_bridge/lowering/match_expr.rs b/src/runner/json_v0_bridge/lowering/match_expr.rs index a7ff5543..889e5797 100644 --- a/src/runner/json_v0_bridge/lowering/match_expr.rs +++ b/src/runner/json_v0_bridge/lowering/match_expr.rs @@ -4,8 +4,8 @@ use super::super::ast::{ExprV0, MatchArmV0}; use super::expr::{lower_expr_with_scope, VarScope}; use super::merge::new_block; use super::BridgeEnv; -use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId}; use crate::ast::Span; +use crate::mir::{BasicBlockId, CompareOp, ConstValue, MirFunction, MirInstruction, ValueId}; pub(super) fn lower_match_expr_with_scope( env: &BridgeEnv, diff --git a/src/runner/json_v0_bridge/lowering/merge.rs b/src/runner/json_v0_bridge/lowering/merge.rs index 9d16bcac..7450dfca 100644 --- a/src/runner/json_v0_bridge/lowering/merge.rs +++ b/src/runner/json_v0_bridge/lowering/merge.rs @@ -1,5 +1,5 @@ -use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId}; use crate::ast::Span; +use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId}; fn next_block_id(f: &MirFunction) -> BasicBlockId { let mut mx = 0u32; diff --git a/src/runner/json_v0_bridge/lowering/ternary.rs b/src/runner/json_v0_bridge/lowering/ternary.rs index 8181bbe9..5ff4886e 100644 --- a/src/runner/json_v0_bridge/lowering/ternary.rs +++ b/src/runner/json_v0_bridge/lowering/ternary.rs @@ -6,8 +6,8 @@ use super::super::ast::ExprV0; use super::merge::new_block; use super::BridgeEnv; -use crate::mir::{BasicBlockId, MirFunction, ValueId}; use crate::ast::Span; +use crate::mir::{BasicBlockId, MirFunction, ValueId}; use super::expr::{lower_expr_with_scope, VarScope}; diff --git a/src/runner/json_v0_bridge/lowering/throw_lower.rs b/src/runner/json_v0_bridge/lowering/throw_lower.rs index 79321ae8..df3887d1 100644 --- a/src/runner/json_v0_bridge/lowering/throw_lower.rs +++ b/src/runner/json_v0_bridge/lowering/throw_lower.rs @@ -42,4 +42,3 @@ pub(super) fn lower_throw( (dst, cur_bb) } } - diff --git a/src/runner/json_v0_bridge/lowering/try_catch.rs b/src/runner/json_v0_bridge/lowering/try_catch.rs index 4f47e14f..bd26e319 100644 --- a/src/runner/json_v0_bridge/lowering/try_catch.rs +++ b/src/runner/json_v0_bridge/lowering/try_catch.rs @@ -1,7 +1,7 @@ use super::super::ast::{CatchV0, StmtV0}; use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext}; -use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; use crate::ast::Span; +use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; use std::collections::BTreeMap; pub(super) fn lower_try_stmt( diff --git a/src/runner/modes/common_util/mod.rs b/src/runner/modes/common_util/mod.rs index 01eed864..3f0977fe 100644 --- a/src/runner/modes/common_util/mod.rs +++ b/src/runner/modes/common_util/mod.rs @@ -13,7 +13,7 @@ pub mod io; pub mod plugin_guard; pub mod provider_registry; pub mod pyvm; -pub mod source_hint; pub mod resolve; pub mod selfhost; pub mod selfhost_exe; +pub mod source_hint; diff --git a/src/runner/modes/common_util/source_hint.rs b/src/runner/modes/common_util/source_hint.rs index 92a86be1..2b0fc522 100644 --- a/src/runner/modes/common_util/source_hint.rs +++ b/src/runner/modes/common_util/source_hint.rs @@ -1,5 +1,5 @@ -use crate::mir::{MirCompileResult, MirCompiler}; use crate::ast::ASTNode; +use crate::mir::{MirCompileResult, MirCompiler}; /// Compile AST with a source filename hint, reducing call-site duplication. /// Falls back to regular compile when filename is None or empty. diff --git a/src/runner/modes/llvm.rs b/src/runner/modes/llvm.rs index 08262d41..e4b96d60 100644 --- a/src/runner/modes/llvm.rs +++ b/src/runner/modes/llvm.rs @@ -105,17 +105,18 @@ impl NyashRunner { // Compile to MIR let mut mir_compiler = MirCompiler::new(); - let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( - &mut mir_compiler, - ast, - Some(filename), - ) { - Ok(result) => result, - Err(e) => { - eprintln!("❌ MIR compilation error: {}", e); - process::exit(1); - } - }; + let compile_result = + match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { + Ok(result) => result, + Err(e) => { + eprintln!("❌ MIR compilation error: {}", e); + process::exit(1); + } + }; println!("📊 MIR Module compiled successfully!"); println!("📊 Functions: {}", compile_result.module.functions.len()); diff --git a/src/runner/modes/mir.rs b/src/runner/modes/mir.rs index 38325d4c..fa89e129 100644 --- a/src/runner/modes/mir.rs +++ b/src/runner/modes/mir.rs @@ -32,17 +32,18 @@ impl NyashRunner { // Compile to MIR (opt passes configurable) let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize); - let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( - &mut mir_compiler, - ast, - Some(filename), - ) { - Ok(result) => result, - Err(e) => { - eprintln!("❌ MIR compilation error: {}", e); - process::exit(1); - } - }; + let compile_result = + match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { + Ok(result) => result, + Err(e) => { + eprintln!("❌ MIR compilation error: {}", e); + process::exit(1); + } + }; let groups = self.config.as_groups(); // Verify MIR if requested diff --git a/src/runner/modes/pyvm.rs b/src/runner/modes/pyvm.rs index 6cb84d35..519a0525 100644 --- a/src/runner/modes/pyvm.rs +++ b/src/runner/modes/pyvm.rs @@ -147,17 +147,18 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) { // Compile to MIR (respect default optimizer setting) let mut mir_compiler = MirCompiler::with_options(true); - let mut compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint( - &mut mir_compiler, - ast, - Some(filename), - ) { - Ok(result) => result, - Err(e) => { - eprintln!("❌ MIR compilation error: {}", e); - process::exit(1); - } - }; + let mut compile_result = + match crate::runner::modes::common_util::source_hint::compile_with_source_hint( + &mut mir_compiler, + ast, + Some(filename), + ) { + Ok(result) => result, + Err(e) => { + eprintln!("❌ MIR compilation error: {}", e); + process::exit(1); + } + }; // Optional: VM-only escape analysis elision pass retained for parity with VM path if crate::config::env::env_bool("NYASH_VM_ESCAPE_ANALYSIS") { diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index c38d6433..5956fd56 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -503,9 +503,8 @@ impl NyashRunner { // HAKO_JOINIR_IF_SELECT=1 で有効化、IfLoweringDryRunner を使用 if crate::config::env::joinir_if_select_enabled() { let debug_level = crate::config::env::joinir_debug_level(); - let runner = crate::mir::join_ir::lowering::if_dry_runner::IfLoweringDryRunner::new( - debug_level, - ); + let runner = + crate::mir::join_ir::lowering::if_dry_runner::IfLoweringDryRunner::new(debug_level); let stats = runner.scan_module(&module_vm.functions); runner.print_stats(&stats); } diff --git a/src/runner/selfhost.rs b/src/runner/selfhost.rs index 8dd48933..bb54de3f 100644 --- a/src/runner/selfhost.rs +++ b/src/runner/selfhost.rs @@ -219,7 +219,10 @@ impl NyashRunner { if parser_prog.exists() { // Phase 28.2: observation log (NYASH_CLI_VERBOSE>=2) if verbose_level >= 2 { - eprintln!("[selfhost/ny] spawning Ny compiler child process: {}", parser_prog.display()); + eprintln!( + "[selfhost/ny] spawning Ny compiler child process: {}", + parser_prog.display() + ); } // Build extra args forwarded to child program let mut extra_owned: Vec = Vec::new(); @@ -273,11 +276,14 @@ impl NyashRunner { timeout_ms, &extra, &["NYASH_USE_NY_COMPILER", "NYASH_CLI_VERBOSE"], - &[], // rely on apply_selfhost_compiler_env() for env setup + &[], // rely on apply_selfhost_compiler_env() for env setup ) { // Phase 28.2: observation log - JSON received if verbose_level >= 2 { - eprintln!("[selfhost/ny] received Program(JSON v0), size={} bytes", line.len()); + eprintln!( + "[selfhost/ny] received Program(JSON v0), size={} bytes", + line.len() + ); } match json::parse_json_v0_line(&line) { Ok(module) => { @@ -297,11 +303,19 @@ impl NyashRunner { super::json_v0_bridge::maybe_dump_mir(&module); // Phase 28.2: observation log - after maybe_dump_mir if verbose_level >= 2 { - if let Some(ref path) = crate::config::env::dump::rust_mir_dump_path() { + if let Some(ref path) = + crate::config::env::dump::rust_mir_dump_path() + { if std::path::Path::new(path).exists() { - eprintln!("[selfhost/ny] ✅ MIR dump file created: {}", path); + eprintln!( + "[selfhost/ny] ✅ MIR dump file created: {}", + path + ); } else { - eprintln!("[selfhost/ny] ⚠️ MIR dump file NOT created: {}", path); + eprintln!( + "[selfhost/ny] ⚠️ MIR dump file NOT created: {}", + path + ); } } } diff --git a/src/runner/stage1_bridge/args.rs b/src/runner/stage1_bridge/args.rs index 90d38905..ee62aec1 100644 --- a/src/runner/stage1_bridge/args.rs +++ b/src/runner/stage1_bridge/args.rs @@ -4,8 +4,8 @@ * Constructs stage1_args based on execution mode (emit_program / emit_mir / run). */ -use crate::config::env::stage1; use crate::cli::CliGroups; +use crate::config::env::stage1; use serde_json; use std::process; @@ -27,8 +27,7 @@ pub(super) struct Stage1Args { /// - run: run --backend pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args { // Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility. - let source = stage1::input_path() - .or_else(|| groups.input.file.as_ref().cloned()); + let source = stage1::input_path().or_else(|| groups.input.file.as_ref().cloned()); let emit_program = stage1::emit_program_json(); let emit_mir = stage1::emit_mir_json(); @@ -69,8 +68,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args { process::exit(97); }); args.push("run".into()); - let backend = stage1::backend_hint() - .unwrap_or_else(|| groups.backend.backend.clone()); + let backend = stage1::backend_hint().unwrap_or_else(|| groups.backend.backend.clone()); args.push("--backend".into()); args.push(backend); args.push(src); diff --git a/src/runner/stage1_bridge/env.rs b/src/runner/stage1_bridge/env.rs index 926e2ea2..cecabdc5 100644 --- a/src/runner/stage1_bridge/env.rs +++ b/src/runner/stage1_bridge/env.rs @@ -76,16 +76,28 @@ pub(super) fn configure_stage1_env( // Parser toggles if std::env::var("NYASH_ENABLE_USING").is_err() { - cmd.env("NYASH_ENABLE_USING", if env::enable_using() { "1" } else { "0" }); + cmd.env( + "NYASH_ENABLE_USING", + if env::enable_using() { "1" } else { "0" }, + ); } if std::env::var("HAKO_ENABLE_USING").is_err() { - cmd.env("HAKO_ENABLE_USING", if env::enable_using() { "1" } else { "0" }); + cmd.env( + "HAKO_ENABLE_USING", + if env::enable_using() { "1" } else { "0" }, + ); } if std::env::var("NYASH_PARSER_STAGE3").is_err() { - cmd.env("NYASH_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" }); + cmd.env( + "NYASH_PARSER_STAGE3", + if env::parser_stage3() { "1" } else { "0" }, + ); } if std::env::var("HAKO_PARSER_STAGE3").is_err() { - cmd.env("HAKO_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" }); + cmd.env( + "HAKO_PARSER_STAGE3", + if env::parser_stage3() { "1" } else { "0" }, + ); } // Modules list diff --git a/src/runner/stage1_bridge/mod.rs b/src/runner/stage1_bridge/mod.rs index adeda7b2..f8b9715a 100644 --- a/src/runner/stage1_bridge/mod.rs +++ b/src/runner/stage1_bridge/mod.rs @@ -20,17 +20,21 @@ mod env; mod modules; use super::NyashRunner; -use crate::runner::stage1_bridge::args::Stage1Args; +use crate::cli::CliGroups; use crate::config; use crate::config::env::stage1; -use crate::cli::CliGroups; use crate::mir::MirPrinter; +use crate::runner::stage1_bridge::args::Stage1Args; use std::io::Write; use std::path::Path; impl NyashRunner { /// Emit Program(JSON v0) using Stage-1 stub and write to a file. - pub(crate) fn emit_program_json_v0(&self, groups: &CliGroups, out_path: &str) -> Result<(), String> { + pub(crate) fn emit_program_json_v0( + &self, + groups: &CliGroups, + out_path: &str, + ) -> Result<(), String> { // Resolve source path from CLI groups or env let source = stage1::input_path() .or_else(|| groups.input.file.as_ref().cloned()) @@ -49,9 +53,8 @@ impl NyashRunner { let modules_list = modules::collect_modules_list(); // Prepare command - let exe = std::env::current_exe().unwrap_or_else(|_| { - std::path::PathBuf::from("target/release/nyash") - }); + let exe = std::env::current_exe() + .unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash")); let mut cmd = std::process::Command::new(exe); let entry_fn = std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string()); @@ -77,7 +80,9 @@ impl NyashRunner { let output = cmd .output() .map_err(|e| format!("stage1 emit program-json spawn failed: {}", e))?; - if !output.stderr.is_empty() && std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1") { + if !output.stderr.is_empty() + && std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1") + { let _ = std::io::stderr().write_all(&output.stderr); } if !output.status.success() { @@ -94,9 +99,10 @@ impl NyashRunner { } let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout) - .ok_or_else(|| "stage1 emit program-json did not produce Program(JSON v0)".to_string())?; - std::fs::write(out_path, line) - .map_err(|e| format!("write {} failed: {}", out_path, e))?; + .ok_or_else(|| { + "stage1 emit program-json did not produce Program(JSON v0)".to_string() + })?; + std::fs::write(out_path, line).map_err(|e| format!("write {} failed: {}", out_path, e))?; Ok(()) } @@ -199,7 +205,9 @@ impl NyashRunner { return Some(code); } let stdout = String::from_utf8_lossy(&output.stdout).to_string(); - let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout) { + let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line( + &stdout, + ) { Some(l) => l, None => { eprintln!("[stage1-cli] emit-mir: no Program(JSON v0) found in stub output"); diff --git a/src/tests/helpers/joinir_frontend.rs b/src/tests/helpers/joinir_frontend.rs index b9cc9675..98ed7877 100644 --- a/src/tests/helpers/joinir_frontend.rs +++ b/src/tests/helpers/joinir_frontend.rs @@ -3,7 +3,7 @@ //! 目的: フィクスチャベースの AST→JoinIR テストを簡潔に書けるようにする use crate::mir::join_ir::frontend::AstToJoinIrLowerer; -use crate::mir::join_ir::{JoinModule, JoinFuncId}; +use crate::mir::join_ir::{JoinFuncId, JoinModule}; use crate::mir::join_ir_ops::JoinValue; use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; @@ -66,19 +66,13 @@ impl JoinIrFrontendTestRunner { } /// テストケースを実行(単一入力・単一出力) - pub fn run_case( - &self, - inputs: &[JoinValue], - expected: JoinValue, - ) -> Result<(), String> { - let module = self.join_module.as_ref() + pub fn run_case(&self, inputs: &[JoinValue], expected: JoinValue) -> Result<(), String> { + let module = self + .join_module + .as_ref() .ok_or("Module not lowered. Call .lower() first")?; - let result = run_joinir_via_vm( - module, - module.entry.unwrap(), - inputs, - ).map_err(|e| { + let result = run_joinir_via_vm(module, module.entry.unwrap(), inputs).map_err(|e| { format!( "JoinIR execution failed\n\ Inputs: {:?}\n\ @@ -107,10 +101,7 @@ impl JoinIrFrontendTestRunner { } /// 複数テストケースを一括実行 - pub fn run_cases( - &self, - cases: &[(Vec, JoinValue)], - ) -> Result<(), String> { + pub fn run_cases(&self, cases: &[(Vec, JoinValue)]) -> Result<(), String> { for (inputs, expected) in cases { self.run_case(inputs, expected.clone())?; } diff --git a/src/tests/joinir_frontend_if_in_loop_test.rs b/src/tests/joinir_frontend_if_in_loop_test.rs index b2e938a7..1c83313c 100644 --- a/src/tests/joinir_frontend_if_in_loop_test.rs +++ b/src/tests/joinir_frontend_if_in_loop_test.rs @@ -38,8 +38,14 @@ fn test_extract_assigned_vars_from_body() { let result = lowerer.extract_assigned_vars_from_body(&body); assert!(result.contains("x"), "Should detect x assignment"); - assert!(result.contains("y"), "Should detect y assignment in if-branch"); - assert!(result.contains("z"), "Should detect z assignment in loop-body"); + assert!( + result.contains("y"), + "Should detect y assignment in if-branch" + ); + assert!( + result.contains("z"), + "Should detect z assignment in loop-body" + ); assert_eq!(result.len(), 3, "Should detect exactly 3 assignments"); } @@ -62,7 +68,10 @@ fn test_extract_if_assigned_vars() { let mut lowerer = crate::mir::join_ir::frontend::ast_lowerer::AstToJoinIrLowerer::new(); let result = lowerer.extract_if_assigned_vars(&body); - assert!(!result.contains("x"), "Should NOT include top-level assignment"); + assert!( + !result.contains("x"), + "Should NOT include top-level assignment" + ); assert!(result.contains("y"), "Should include if-then assignment"); assert!(result.contains("z"), "Should include if-else assignment"); assert_eq!(result.len(), 2, "Should detect exactly 2 if-assignments"); @@ -90,7 +99,17 @@ fn test_extract_if_in_loop_modified_vars() { let mut lowerer = crate::mir::join_ir::frontend::ast_lowerer::AstToJoinIrLowerer::new(); let result = lowerer.extract_if_in_loop_modified_vars(&loop_body, &loop_vars); - assert!(!result.contains("i"), "Should NOT include non-if assignment"); - assert!(result.contains("out"), "Should include if-in-loop modification"); - assert_eq!(result.len(), 1, "Should detect exactly 1 if-in-loop variable"); + assert!( + !result.contains("i"), + "Should NOT include non-if assignment" + ); + assert!( + result.contains("out"), + "Should include if-in-loop modification" + ); + assert_eq!( + result.len(), + 1, + "Should detect exactly 1 if-in-loop variable" + ); } diff --git a/src/tests/joinir_frontend_if_select.rs b/src/tests/joinir_frontend_if_select.rs index c039e2e0..93abf626 100644 --- a/src/tests/joinir_frontend_if_select.rs +++ b/src/tests/joinir_frontend_if_select.rs @@ -3,8 +3,8 @@ //! Route A: 既存経路(AST→MIR→PHI→VM) //! Route B: 新経路(AST→JoinIR→MIR'→VM) -use crate::tests::helpers::joinir_frontend::JoinIrFrontendTestRunner; use crate::mir::join_ir_ops::JoinValue; +use crate::tests::helpers::joinir_frontend::JoinIrFrontendTestRunner; /// Phase 34-2: IfSelect simple pattern の A/B テスト /// diff --git a/src/tests/joinir_json_min.rs b/src/tests/joinir_json_min.rs index 0041afb8..f6a722b5 100644 --- a/src/tests/joinir_json_min.rs +++ b/src/tests/joinir_json_min.rs @@ -367,7 +367,11 @@ fn run_snapshot_test(case: SnapshotCase) { }; // フィクスチャ生成モード - if std::env::var("NYASH_JOINIR_SNAPSHOT_GENERATE").ok().as_deref() == Some("1") { + if std::env::var("NYASH_JOINIR_SNAPSHOT_GENERATE") + .ok() + .as_deref() + == Some("1") + { std::fs::write(&fixture_path, &json).expect("Failed to write fixture"); eprintln!( "[joinir/snapshot] Generated fixture: {} ({} bytes)", @@ -405,10 +409,7 @@ fn run_snapshot_test(case: SnapshotCase) { panic!("jsonir v0 snapshot mismatch for {}", case.name()); } - eprintln!( - "[joinir/snapshot] {} matches fixture ✓", - case.name() - ); + eprintln!("[joinir/snapshot] {} matches fixture ✓", case.name()); } // ============================================================================ @@ -538,7 +539,10 @@ fn joinir_stageb_body_structure_test() { "[joinir/stageb_body] Function '{}': {} blocks", name, block_count ); - assert!(block_count >= 1, "Each function should have at least 1 block"); + assert!( + block_count >= 1, + "Each function should have at least 1 block" + ); } eprintln!("[joinir/stageb_body] ✅ Structure test passed"); @@ -551,7 +555,8 @@ fn joinir_stageb_funcscanner_structure_test() { std::env::set_var("NYASH_PARSER_STAGE3", "1"); std::env::set_var("HAKO_PARSER_STAGE3", "1"); - let src = match std::fs::read_to_string("apps/tests/stageb_funcscanner_scan_boxes_minimal.hako") { + let src = match std::fs::read_to_string("apps/tests/stageb_funcscanner_scan_boxes_minimal.hako") + { Ok(s) => s, Err(_) => { eprintln!("[joinir/stageb_funcscanner] Source file not found, skipping"); @@ -600,7 +605,10 @@ fn joinir_stageb_funcscanner_structure_test() { let mir_module = match convert_joinir_to_mir(&join_module) { Ok(m) => m, Err(e) => { - eprintln!("[joinir/stageb_funcscanner] JoinIR→MIR conversion failed: {:?}", e); + eprintln!( + "[joinir/stageb_funcscanner] JoinIR→MIR conversion failed: {:?}", + e + ); panic!("JoinIR→MIR conversion should succeed"); } }; @@ -624,7 +632,10 @@ fn joinir_stageb_funcscanner_structure_test() { "[joinir/stageb_funcscanner] Function '{}': {} blocks", name, block_count ); - assert!(block_count >= 1, "Each function should have at least 1 block"); + assert!( + block_count >= 1, + "Each function should have at least 1 block" + ); } eprintln!("[joinir/stageb_funcscanner] ✅ Structure test passed"); diff --git a/src/tests/joinir_runner_standalone.rs b/src/tests/joinir_runner_standalone.rs index e7596c4f..a0302ad5 100644 --- a/src/tests/joinir_runner_standalone.rs +++ b/src/tests/joinir_runner_standalone.rs @@ -10,8 +10,8 @@ // - Runner サポート BoxCall: StringBox.length, StringBox.substring のみ // - ArrayBox/MapBox は未実装 (joinir_coverage.md A-2.5 参照) -use crate::mir::join_ir::*; use crate::mir::join_ir::verify::verify_progress_for_skip_ws; +use crate::mir::join_ir::*; use crate::mir::join_ir_runner::{run_joinir_function, JoinValue}; fn require_experiment_toggle() -> bool { diff --git a/src/tests/joinir_vm_bridge_stage1_usingresolver.rs b/src/tests/joinir_vm_bridge_stage1_usingresolver.rs index 03cfabab..1f58e228 100644 --- a/src/tests/joinir_vm_bridge_stage1_usingresolver.rs +++ b/src/tests/joinir_vm_bridge_stage1_usingresolver.rs @@ -12,7 +12,7 @@ // - Phase 30.x: Stage-1 UsingResolver minimal A/B test use crate::ast::ASTNode; -use crate::backend::{VM, VMValue}; +use crate::backend::{VMValue, VM}; use crate::boxes::array::ArrayBox; use crate::boxes::basic::StringBox; use crate::boxes::map_box::MapBox; @@ -133,7 +133,10 @@ fn joinir_vm_bridge_stage1_usingresolver_empty_entries() { let vm_result = vm_out.to_string_box().value.clone(); std::env::remove_var("NYASH_ENTRY"); - eprintln!("[joinir_vm_bridge_test/stage1] Route A result: {:?}", vm_result); + eprintln!( + "[joinir_vm_bridge_test/stage1] Route A result: {:?}", + vm_result + ); // Route C: AST → MIR → JoinIR → MIR' → VM (via bridge) eprintln!("[joinir_vm_bridge_test/stage1] Route C: JoinIR → VM bridge execution"); @@ -143,8 +146,12 @@ fn joinir_vm_bridge_stage1_usingresolver_empty_entries() { // 空配列、n=0、空Map、空Map、prefix="init" を引数として渡す // JoinValue で表現可能な引数のみ(ArrayBox/MapBox は現状未サポート) // このテストは JoinIR lowering の構造検証が主目的 - eprintln!("[joinir_vm_bridge_test/stage1] Note: JoinIR bridge with ArrayBox requires VM-side support"); - eprintln!("[joinir_vm_bridge_test/stage1] Skipping direct bridge call - structure verification only"); + eprintln!( + "[joinir_vm_bridge_test/stage1] Note: JoinIR bridge with ArrayBox requires VM-side support" + ); + eprintln!( + "[joinir_vm_bridge_test/stage1] Skipping direct bridge call - structure verification only" + ); // 構造検証:JoinModule が正しく生成されているか assert_eq!( @@ -158,12 +165,20 @@ fn joinir_vm_bridge_stage1_usingresolver_empty_entries() { if vm_result == "init" { eprintln!("[joinir_vm_bridge_test/stage1] ✅ VM returned expected 'init'"); } else { - eprintln!("[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (PHI bug - expected 'init')", vm_result); + eprintln!( + "[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (PHI bug - expected 'init')", + vm_result + ); eprintln!("[joinir_vm_bridge_test/stage1] JoinIR would fix this by design"); } - eprintln!("[joinir_vm_bridge_test/stage1] ✅ Empty entries test passed (structure verification)"); - eprintln!("[joinir_vm_bridge_test/stage1] VM result: {:?}, JoinIR structure: 2 functions", vm_result); + eprintln!( + "[joinir_vm_bridge_test/stage1] ✅ Empty entries test passed (structure verification)" + ); + eprintln!( + "[joinir_vm_bridge_test/stage1] VM result: {:?}, JoinIR structure: 2 functions", + vm_result + ); // クリーンアップ std::env::remove_var("NYASH_PARSER_STAGE3"); @@ -204,7 +219,10 @@ fn joinir_vm_bridge_stage1_usingresolver_with_entries() { let vm_result = vm_out.to_string_box().value.clone(); std::env::remove_var("NYASH_ENTRY"); - eprintln!("[joinir_vm_bridge_test/stage1] Route A result: {:?}", vm_result); + eprintln!( + "[joinir_vm_bridge_test/stage1] Route A result: {:?}", + vm_result + ); // Route C: JoinIR 構造検証 eprintln!("[joinir_vm_bridge_test/stage1] Route C: JoinIR structure verification"); @@ -224,7 +242,10 @@ fn joinir_vm_bridge_stage1_usingresolver_with_entries() { if vm_result == "ABC" { eprintln!("[joinir_vm_bridge_test/stage1] ✅ VM returned expected 'ABC'"); } else { - eprintln!("[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (possible PHI bug)", vm_result); + eprintln!( + "[joinir_vm_bridge_test/stage1] ⚠️ VM returned '{}' (possible PHI bug)", + vm_result + ); eprintln!("[joinir_vm_bridge_test/stage1] JoinIR would fix this by design"); } @@ -255,16 +276,17 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_execution() { let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("stage1 route_b: parse failed"); let mut mc = MirCompiler::with_options(false); - let compiled = mc - .compile(ast) - .expect("stage1 route_b: MIR compile failed"); + let compiled = mc.compile(ast).expect("stage1 route_b: MIR compile failed"); eprintln!("[joinir_vm_bridge_test/stage1/route_b] Starting Route B execution test"); let join_module = lower_stage1_usingresolver_to_joinir(&compiled.module) .expect("lower_stage1_usingresolver_to_joinir failed"); - eprintln!("[joinir_vm_bridge_test/stage1/route_b] JoinIR module created: {} functions", join_module.functions.len()); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b] JoinIR module created: {} functions", + join_module.functions.len() + ); eprintln!("[joinir_vm_bridge_test/stage1/route_b] Attempting run_joinir_via_vm with n=0 (Array/Map supported)"); @@ -282,7 +304,10 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_execution() { match result { Ok(value) => { - eprintln!("[joinir_vm_bridge_test/stage1/route_b] ✅ Execution succeeded: {:?}", value); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b] ✅ Execution succeeded: {:?}", + value + ); // n=0 の場合、ループは実行されず prefix_init がそのまま返るはず match &value { JoinValue::Str(s) => { @@ -299,7 +324,10 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_execution() { } } Err(e) => { - eprintln!("[joinir_vm_bridge_test/stage1/route_b] ❌ Execution failed: {:?}", e); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b] ❌ Execution failed: {:?}", + e + ); eprintln!("[joinir_vm_bridge_test/stage1/route_b] This error shows where VM bridge needs extension"); panic!("JoinIR bridge failed: {:?}", e); } @@ -325,8 +353,8 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() { let full_src = format!("{STAGE1_USINGRESOLVER_SOURCE}\n{RUNNER_WITH_ENTRIES_SOURCE}"); - let ast: ASTNode = - NyashParser::parse_from_string(&full_src).expect("stage1 route_b_with_entries: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&full_src) + .expect("stage1 route_b_with_entries: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc .compile(ast) @@ -337,11 +365,16 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() { let join_module = lower_stage1_usingresolver_to_joinir(&compiled.module) .expect("lower_stage1_usingresolver_to_joinir failed"); - eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] JoinIR module created: {} functions", join_module.functions.len()); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b_with_entries] JoinIR module created: {} functions", + join_module.functions.len() + ); // n=3 の場合、ループが実行され、entries.get(i) が呼ばれる // JoinValue::Int(0) を entries に渡すと、get メソッド呼び出しで失敗するはず - eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] Attempting run_joinir_via_vm with n=3"); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b_with_entries] Attempting run_joinir_via_vm with n=3" + ); eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] Note: ArrayBox passed as Int(0) - expecting failure at get() call"); let result = run_joinir_via_vm( @@ -349,7 +382,7 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() { JoinFuncId::new(0), &[ make_entries(&["A", "B", "C"]), - JoinValue::Int(3), // n = 3 (loop will execute 3 times) + JoinValue::Int(3), // n = 3 (loop will execute 3 times) make_empty_map(), make_empty_map(), JoinValue::Str("".to_string()), // prefix_init = "" @@ -358,10 +391,16 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() { match result { Ok(value) => { - eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] ✅ Execution succeeded: {:?}", value); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b_with_entries] ✅ Execution succeeded: {:?}", + value + ); match value { JoinValue::Str(s) => { - eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] Result: {:?}", s); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b_with_entries] Result: {:?}", + s + ); if s == "ABC" { eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] ✅ JoinIR returned correct 'ABC'!"); } else { @@ -374,7 +413,10 @@ fn joinir_vm_bridge_stage1_usingresolver_route_b_with_entries() { } } Err(e) => { - eprintln!("[joinir_vm_bridge_test/stage1/route_b_with_entries] ❌ Execution failed: {:?}", e); + eprintln!( + "[joinir_vm_bridge_test/stage1/route_b_with_entries] ❌ Execution failed: {:?}", + e + ); panic!("JoinIR bridge failed: {:?}", e); } } @@ -394,8 +436,8 @@ fn joinir_vm_bridge_stage1_usingresolver_lowering_sanity() { let full_src = format!("{STAGE1_USINGRESOLVER_SOURCE}\n{RUNNER_SOURCE}"); - let ast: ASTNode = - NyashParser::parse_from_string(&full_src).expect("stage1_usingresolver sanity: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(&full_src) + .expect("stage1_usingresolver sanity: parse failed"); let mut mc = MirCompiler::with_options(false); let compiled = mc .compile(ast) diff --git a/src/tests/joinir_vm_bridge_trim.rs b/src/tests/joinir_vm_bridge_trim.rs index c4fc2d4b..bdf82df6 100644 --- a/src/tests/joinir_vm_bridge_trim.rs +++ b/src/tests/joinir_vm_bridge_trim.rs @@ -126,7 +126,9 @@ static box Runner { JoinValue::Str(s) => { assert_eq!(s, "abc", "Route C (JoinIR→VM bridge) trim result mismatch"); if vm_result == "abc" { - eprintln!("[joinir_vm_bridge_test/trim] ✅ A/B test passed: both routes returned 'abc'"); + eprintln!( + "[joinir_vm_bridge_test/trim] ✅ A/B test passed: both routes returned 'abc'" + ); } else { eprintln!("[joinir_vm_bridge_test/trim] ⚠️ Route A (VM) returned '{}' (PHI bug), Route C (JoinIR) returned 'abc' (correct)", vm_result); eprintln!("[joinir_vm_bridge_test/trim] ✅ JoinIR correctly handles PHI issues that affect direct VM path"); @@ -209,10 +211,7 @@ static box Runner {{ input, s ); } - other => panic!( - "trim({:?}) returned non-string value: {:?}", - input, other - ), + other => panic!("trim({:?}) returned non-string value: {:?}", input, other), } } diff --git a/src/tests/mir_joinir_if_select.rs b/src/tests/mir_joinir_if_select.rs index 3c2d5aa5..080d1e34 100644 --- a/src/tests/mir_joinir_if_select.rs +++ b/src/tests/mir_joinir_if_select.rs @@ -6,9 +6,7 @@ mod tests { use crate::mir::join_ir::lowering::try_lower_if_to_joinir; use crate::mir::join_ir::JoinInst; - use crate::mir::{ - BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId, - }; + use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId}; use std::collections::BTreeMap; /// Helper to create a simple if/else function matching the "simple" pattern @@ -40,8 +38,8 @@ mod tests { }); blocks.insert(BasicBlockId::new(2), else_block); - use crate::mir::{EffectMask, MirType}; use crate::mir::function::FunctionMetadata; + use crate::mir::{EffectMask, MirType}; use std::collections::HashMap; MirFunction { @@ -77,7 +75,7 @@ mod tests { // NOTE: Pattern matcher expects exactly 1 Copy instruction let mut then_block = BasicBlock::new(BasicBlockId::new(1)); then_block.instructions.push(MirInstruction::Copy { - dst: ValueId(3), // x + dst: ValueId(3), // x src: ValueId(10), // Assumes ValueId(10) is const 100 }); then_block.terminator = Some(MirInstruction::Jump { @@ -89,7 +87,7 @@ mod tests { // NOTE: Pattern matcher expects exactly 1 Copy instruction let mut else_block = BasicBlock::new(BasicBlockId::new(2)); else_block.instructions.push(MirInstruction::Copy { - dst: ValueId(3), // x + dst: ValueId(3), // x src: ValueId(20), // Assumes ValueId(20) is const 200 }); else_block.terminator = Some(MirInstruction::Jump { @@ -104,8 +102,8 @@ mod tests { }); blocks.insert(BasicBlockId::new(3), merge_block); - use crate::mir::{EffectMask, MirType}; use crate::mir::function::FunctionMetadata; + use crate::mir::{EffectMask, MirType}; use std::collections::HashMap; MirFunction { @@ -150,7 +148,10 @@ mod tests { }) = result { eprintln!("✅ Simple pattern successfully lowered to Select"); - eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val); + eprintln!( + " dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", + dst, cond, then_val, else_val + ); } else { panic!("Expected JoinInst::Select, got {:?}", result); } @@ -173,7 +174,10 @@ mod tests { }) = result { eprintln!("✅ Local pattern successfully lowered to Select"); - eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val); + eprintln!( + " dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", + dst, cond, then_val, else_val + ); } else { panic!("Expected JoinInst::Select, got {:?}", result); } @@ -257,11 +261,8 @@ mod tests { use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinFunction, JoinInst, MirLikeInst}; let func_id = JoinFuncId::new(0); - let mut join_func = JoinFunction::new( - func_id, - "IfSelectTest.test/1".to_string(), - vec![ValueId(0)], - ); + let mut join_func = + JoinFunction::new(func_id, "IfSelectTest.test/1".to_string(), vec![ValueId(0)]); // First Select join_func.body.push(JoinInst::Select { @@ -331,10 +332,7 @@ mod tests { // Verifier should reject let result = verify_select_minimal(&join_func, true); - assert!( - result.is_err(), - "Verify should reject multiple Selects" - ); + assert!(result.is_err(), "Verify should reject multiple Selects"); match result { Err(e) => { @@ -421,8 +419,8 @@ mod tests { }); blocks.insert(BasicBlockId::new(2), else_block); - use crate::mir::{EffectMask, MirType}; use crate::mir::function::FunctionMetadata; + use crate::mir::{EffectMask, MirType}; use std::collections::HashMap; MirFunction { @@ -492,8 +490,8 @@ mod tests { }); blocks.insert(BasicBlockId::new(2), else_block); - use crate::mir::{EffectMask, MirType}; use crate::mir::function::FunctionMetadata; + use crate::mir::{EffectMask, MirType}; use std::collections::HashMap; MirFunction { @@ -528,11 +526,24 @@ mod tests { "Expected simple 2-variable pattern to be lowered to IfMerge" ); - if let Some(JoinInst::IfMerge { cond, merges, k_next }) = result { + if let Some(JoinInst::IfMerge { + cond, + merges, + k_next, + }) = result + { eprintln!("✅ Simple pattern (2 vars) successfully lowered to IfMerge"); - eprintln!(" cond: {:?}, merges: {} pairs, k_next: {:?}", cond, merges.len(), k_next); + eprintln!( + " cond: {:?}, merges: {} pairs, k_next: {:?}", + cond, + merges.len(), + k_next + ); assert_eq!(merges.len(), 2, "Expected 2 MergePairs for x and y"); - assert!(k_next.is_none(), "Phase 33-7 constraint: k_next should be None"); + assert!( + k_next.is_none(), + "Phase 33-7 constraint: k_next should be None" + ); } else { panic!("Expected JoinInst::IfMerge, got {:?}", result); } @@ -556,11 +567,24 @@ mod tests { "Expected multiple 3-variable pattern to be lowered to IfMerge" ); - if let Some(JoinInst::IfMerge { cond, merges, k_next }) = result { + if let Some(JoinInst::IfMerge { + cond, + merges, + k_next, + }) = result + { eprintln!("✅ Multiple pattern (3 vars) successfully lowered to IfMerge"); - eprintln!(" cond: {:?}, merges: {} pairs, k_next: {:?}", cond, merges.len(), k_next); + eprintln!( + " cond: {:?}, merges: {} pairs, k_next: {:?}", + cond, + merges.len(), + k_next + ); assert_eq!(merges.len(), 3, "Expected 3 MergePairs for x, y, and z"); - assert!(k_next.is_none(), "Phase 33-7 constraint: k_next should be None"); + assert!( + k_next.is_none(), + "Phase 33-7 constraint: k_next should be None" + ); } else { panic!("Expected JoinInst::IfMerge, got {:?}", result); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0ed6617e..72ae372c 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -12,8 +12,8 @@ pub mod joinir_json_min; // Phase 30.x: JoinIR JSON シリアライズテスト pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test -pub mod joinir_vm_bridge_trim; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for trim pub mod joinir_vm_bridge_stage1_usingresolver; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for Stage-1 +pub mod joinir_vm_bridge_trim; // Phase 30.x: JoinIR → Rust VM Bridge A/B Test for trim pub mod json_lint_stringutils_min_vm; // Phase 21.7++: using StringUtils alias resolution fix pub mod mir_breakfinder_ssa; pub mod mir_funcscanner_parse_params_trim_min; @@ -58,7 +58,7 @@ pub mod vtable_strict; pub mod vtable_string; // Phase 34-2: JoinIR Frontend (AST→JoinIR) -pub mod joinir_frontend_if_select; pub mod joinir_frontend_if_in_loop_test; // Phase 40-1: If-in-loop variable tracking A/B test +pub mod joinir_frontend_if_select; pub mod phase40_array_ext_filter_test; // Phase 40-1.1: array_ext.filter A/B test (collect_assigned_vars deletion) pub mod phase41_nested_if_merge_test; // Phase 41-4: NestedIfMerge A/B test (parse_loop) diff --git a/src/tests/phase40_array_ext_filter_test.rs b/src/tests/phase40_array_ext_filter_test.rs index 86d84d38..3ca3f2bd 100644 --- a/src/tests/phase40_array_ext_filter_test.rs +++ b/src/tests/phase40_array_ext_filter_test.rs @@ -44,7 +44,10 @@ fn phase40_joinir_meta_helpers_work() { // Verify: out is detected, i is not assert!(!result.contains("i"), "Loop counter should NOT be included"); - assert!(result.contains("out"), "If-in-loop modified var should be included"); + assert!( + result.contains("out"), + "If-in-loop modified var should be included" + ); assert_eq!(result.len(), 1, "Exactly 1 if-in-loop variable"); } @@ -55,8 +58,8 @@ fn phase40_joinir_meta_helpers_work() { /// 2. No panic or error #[test] fn phase40_mir_conversion_with_empty_meta() { - use crate::mir::join_ir::{JoinFunction, JoinFuncId, JoinModule}; use crate::mir::join_ir::frontend::JoinFuncMetaMap; + use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinModule}; use crate::mir::ValueId; // Create minimal JoinModule @@ -65,7 +68,9 @@ fn phase40_mir_conversion_with_empty_meta() { let mut func = JoinFunction::new(func_id, "test_func".to_string(), vec![]); // Add a simple return instruction - func.body.push(crate::mir::join_ir::JoinInst::Ret { value: Some(ValueId(0)) }); + func.body.push(crate::mir::join_ir::JoinInst::Ret { + value: Some(ValueId(0)), + }); module.functions.insert(func_id, func); @@ -87,8 +92,8 @@ fn phase40_mir_conversion_with_empty_meta() { /// 2. No panic even with metadata present #[test] fn phase40_mir_conversion_with_meta() { - use crate::mir::join_ir::{JoinFunction, JoinFuncId, JoinModule}; use crate::mir::join_ir::frontend::{JoinFuncMeta, JoinFuncMetaMap}; + use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinModule}; use crate::mir::ValueId; // Create minimal JoinModule @@ -96,7 +101,9 @@ fn phase40_mir_conversion_with_meta() { let func_id = JoinFuncId::new(0); let mut func = JoinFunction::new(func_id, "loop_step".to_string(), vec![]); - func.body.push(crate::mir::join_ir::JoinInst::Ret { value: Some(ValueId(0)) }); + func.body.push(crate::mir::join_ir::JoinInst::Ret { + value: Some(ValueId(0)), + }); module.functions.insert(func_id, func); @@ -105,10 +112,13 @@ fn phase40_mir_conversion_with_meta() { let mut if_modified = HashSet::new(); if_modified.insert("out".to_string()); - meta.insert(func_id, JoinFuncMeta { - if_modified_vars: Some(if_modified), - ..Default::default() - }); + meta.insert( + func_id, + JoinFuncMeta { + if_modified_vars: Some(if_modified), + ..Default::default() + }, + ); // Should not panic, metadata is logged but not used for PHI generation yet let result = convert_join_module_to_mir_with_meta(&module, &meta); @@ -152,8 +162,7 @@ fn phase40_joinir_detects_local_declarations() { ]; // JoinIR経由(メイン経路) - let vars = - crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None); + let vars = crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None); // Verify JoinIR detects Local declarations assert!(vars.contains("x"), "JoinIR should detect x declaration"); @@ -201,11 +210,13 @@ fn phase40_joinir_nested_if_local() { ]; // JoinIR経由 - let vars = - crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None); + let vars = crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir(&then_body, None); // Verify: inner (nested in if) and outer (top-level) detected - assert!(vars.contains("inner"), "JoinIR: should detect inner in nested if"); + assert!( + vars.contains("inner"), + "JoinIR: should detect inner in nested if" + ); assert!(vars.contains("outer"), "JoinIR: should detect outer"); assert_eq!(vars.len(), 2, "Should detect exactly 2 declarations"); } diff --git a/src/tests/phase41_nested_if_merge_test.rs b/src/tests/phase41_nested_if_merge_test.rs index 9d09c917..c8ac0210 100644 --- a/src/tests/phase41_nested_if_merge_test.rs +++ b/src/tests/phase41_nested_if_merge_test.rs @@ -70,9 +70,14 @@ fn phase41_nested_if_merge_path_activation() { // NestedIfMerge 命令が含まれていることを確認 let entry_id = join_module.entry.expect("Entry should be set"); - let entry_func = join_module.functions.get(&entry_id).expect("Entry function"); + let entry_func = join_module + .functions + .get(&entry_id) + .expect("Entry function"); - let nested_if_merge_count = entry_func.body.iter() + let nested_if_merge_count = entry_func + .body + .iter() .filter(|inst| matches!(inst, JoinInst::NestedIfMerge { .. })) .count(); @@ -173,11 +178,17 @@ fn phase41_nested_if_merge_route_b_execution() { result, JoinValue::Int(expected), "[Phase 41-4.6] Route B execution failed for ({}, {}): expected {}, got {:?}", - a, b, expected, result + a, + b, + expected, + result ); } - eprintln!("[Phase 41-4.6] Route B execution test PASSED: {} test cases", test_cases.len()); + eprintln!( + "[Phase 41-4.6] Route B execution test PASSED: {} test cases", + test_cases.len() + ); } /// Phase 41-4.6: Route A/B 結果比較テスト @@ -246,8 +257,9 @@ fn phase41_nested_if_merge_route_ab_comparison() { let route_b_result = run_joinir_via_vm( &join_module, entry_id, - &[JoinValue::Int(5), JoinValue::Int(5)] - ).expect("Route B execution failed"); + &[JoinValue::Int(5), JoinValue::Int(5)], + ) + .expect("Route B execution failed"); // 期待結果: a=5, b=5 -> 両方 > 0 -> result = 42 let expected = JoinValue::Int(42);