refactor(joinir): Split ast_lowerer and join_ir_vm_bridge into modules

ast_lowerer.rs → ast_lowerer/ (10 files):
- mod.rs: public surface + entry dispatch
- context.rs: ExtractCtx helpers
- expr.rs: expression-to-JoinIR extraction
- if_return.rs: simple if→Select lowering
- loop_patterns.rs: loop variants (simple/break/continue)
- read_quoted.rs: read_quoted_from lowering (Phase 45-46)
- nested_if.rs: NestedIfMerge lowering
- analysis.rs: loop if-var analysis + metadata helpers
- tests.rs: frontend lowering tests
- README.md: module documentation

join_ir_vm_bridge.rs → join_ir_vm_bridge/ (5 files):
- mod.rs: public surface + shared helpers
- convert.rs: JoinIR→MIR lowering
- runner.rs: VM execution entry (run_joinir_via_vm)
- meta.rs: experimental metadata-aware hooks
- tests.rs: bridge-specific unit tests
- README.md: module documentation

Benefits:
- Clear separation of concerns per pattern
- Easier navigation and maintenance
- Each file has single responsibility
- README documents module boundaries

Co-authored-by: ChatGPT <noreply@openai.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-28 17:42:19 +09:00
parent 31472cb171
commit 447bbec998
74 changed files with 4135 additions and 3746 deletions

View File

@ -155,7 +155,11 @@ pub(super) fn try_handle_instance_box(
);
}
let mut argv: Vec<VMValue> = Vec::with_capacity(if include_me { 1 + args.len() } else { args.len() });
let mut argv: Vec<VMValue> = 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<VMValue> = Vec::with_capacity(if include_me { 1 + args.len() } else { args.len() });
let mut argv: Vec<VMValue> = 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(

View File

@ -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))

View File

@ -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<String> {
/// 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).

View File

@ -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",
];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
"add",
"sub",
"mul",
"div",
"and",
"or",
"eq",
"ne",
"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"];

View File

@ -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<Item = (usize, SpannedInstRef<'_>)> {
self.iter_spanned()
.enumerate()
.map(|(idx, sp)| (idx, sp))
pub fn iter_spanned_enumerated(&self) -> impl Iterator<Item = (usize, SpannedInstRef<'_>)> {
self.iter_spanned().enumerate().map(|(idx, sp)| (idx, sp))
}
/// Iterate all instructions (including terminator) with spans.

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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`: ループ本体ASTJSON v0形式
/// - `loop_vars`: ループで使用される変数Header PHIで定義
///
/// ## Output
/// - `HashSet<String>`: 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<String>,
/// ) -> HashSet<String> {
/// // 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::<HashSet<_>>();
///
/// // 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<String>,
) -> HashSet<String> {
// 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<String> {
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<String>`: 代入された変数名全て
///
/// # 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<String> {
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<String>,
) {
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<String> {
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
}
}

View File

@ -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<String, VarId>,
}
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<VarId> {
self.var_map.get(name).copied()
}
}

View File

@ -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<JoinInst>`: 値を計算するための 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<JoinInst>) {
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),
}
}
}

View File

@ -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),
}
}
}

View File

@ -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 の loweringCase-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 ノード処理(同名再宣言 = 再代入)
/// ループ本体loweringPhase 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_exitPhase 31 と同じパターン)
//
// Phase 34-7: Jump = 早期 return、Call = 末尾再帰
// - entry: 初期化 → Call(loop_step)
// - loop_step:
// Jump(k_exit, cond=!(i<n)) // 条件が 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)) → 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);
// 条件式を評価i < n
let (cond_var, cond_insts) = self.extract_value(loop_cond_expr, &mut step_ctx);
// !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 が truei >= 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_exit3関数構造
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_exit3関数構造
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),
}
}
}

View File

@ -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 テスト専用)
/// - ループ・複数変数・副作用付き ifPhase 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()
}
}

View File

@ -0,0 +1,297 @@
use super::BTreeMap;
use super::{AstToJoinIrLowerer, ExtractCtx, JoinFunction, JoinInst, JoinModule};
impl AstToJoinIrLowerer {
/// Phase 41-4.2: ネスト if パターンの lowering
///
/// # Purpose
///
/// 深いネスト if3-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 {
// パターンがマッチしない場合は panicdev フラグ 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<NestedIfPattern> {
// 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<serde_json::Value>,
merges: &mut Vec<(String, serde_json::Value, Option<serde_json::Value>)>,
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<serde_json::Value>,
/// 変数代入リスト: (変数名, then値, else値)
pub(crate) merges: Vec<(String, serde_json::Value, Option<serde_json::Value>)>,
}

View File

@ -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_exit3関数構造
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
// 逆条件で Jumpguard_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 + 1then 側の 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 + 1substring の 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),
}
}
}

View File

@ -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");
}

View File

@ -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)]

View File

@ -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<W: Write>(module: &JoinModule, out: &mut W) -> std::io::Result<()> {
pub fn write_join_module_as_json<W: Write>(
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<W: Write>(module: &JoinModule, out: &mut W) ->
fn write_function<W: Write>(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<W: Write>(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<W: Write>(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<W: Write>(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<W: Write>(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)),
});

View File

@ -629,12 +629,12 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
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!(

View File

@ -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<JoinModule> {
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<JoinModule> {
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<JoinModule> {
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<JoinModule> {
pub(crate) fn lower_case_a_stage1_usingresolver_with_scope(
scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
) -> Option<JoinModule> {
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<JoinModule> {
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)?;

View File

@ -42,10 +42,7 @@ impl IfLoweringDryRunner {
/// - 各 Branch ブロックで try_lower_if_to_joinir() 試行
/// - パフォーマンス計測(マイクロ秒レベル)
/// - 統計情報収集Select/IfMerge分類
pub fn scan_module(
&self,
functions: &HashMap<String, MirFunction>,
) -> DryRunStats {
pub fn scan_module(&self, functions: &HashMap<String, MirFunction>) -> 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();

View File

@ -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<ValueId> {
fn find_written_value(&self, instructions: &[MirInstruction], dst: ValueId) -> Option<ValueId> {
// 逆順で探索して最後の書き込みを見つける
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),
..

View File

@ -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<IfPattern> {
fn find_if_pattern(&self, func: &MirFunction, block_id: BasicBlockId) -> Option<IfPattern> {
// 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,

View File

@ -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<String>,
@ -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<Self> {
// 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<String>,
@ -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(),

View File

@ -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!(
@ -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<JoinModule> {
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<JoinModule> {
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<JoinModule> {
self.lower(func, loop_form, Some("StageBFuncScannerBox.scan_all_boxes/1"))
self.lower(
func,
loop_form,
Some("StageBFuncScannerBox.scan_all_boxes/1"),
)
}
}

View File

@ -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 を優先的に試行(複数変数パターン)

View File

@ -352,7 +352,9 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// 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<JoinModule> {
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"
);

View File

@ -170,7 +170,9 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// 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);
};

View File

@ -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,

View File

@ -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<bool, JoinRuntimeError> {
#[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);

View File

@ -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)

View File

@ -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<String>) -> Self {
Self {
message: msg.into(),
}
}
}
impl From<VMError> 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<JoinValue, JoinIrVmBridgeError> {
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<VMValue> = 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<MirModule, JoinIrVmBridgeError> {
pub(crate) fn convert_joinir_to_mir(
join_module: &JoinModule,
) -> Result<MirModule, JoinIrVmBridgeError> {
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<MirModul
///
/// - AST lowering: `ast_lowerer.rs::extract_if_in_loop_modified_vars()`
/// - Design: `docs/.../phase-39-if-phi-level2/joinir_extension_design.md`
fn convert_join_function_to_mir(
join_func: &crate::mir::join_ir::JoinFunction,
pub(crate) fn convert_join_function_to_mir(
join_func: &JoinFunction,
) -> Result<MirFunction, JoinIrVmBridgeError> {
// 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<MirInstruction, JoinIrVmBridgeError> {
pub(crate) fn convert_mir_like_inst(
mir_like: &MirLikeInst,
) -> Result<MirInstruction, JoinIrVmBridgeError> {
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<MirInstruction, JoinI
}
}
}
// ========================================
// Phase 40-1 Experimental API
// ========================================
use crate::mir::join_ir::frontend::{JoinFuncMeta, JoinFuncMetaMap};
use crate::mir::builder::MirBuilder;
use std::collections::BTreeMap;
/// 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<MirModule, JoinIrVmBridgeError> {
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<String>,
) -> 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");
}
}

View File

@ -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<MirModule, JoinIrVmBridgeError> {
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<String>,
) -> 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(())
}

View File

@ -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<String>) -> Self {
Self {
message: msg.into(),
}
}
}
impl From<VMError> 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)
}

View File

@ -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<JoinValue, JoinIrVmBridgeError> {
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<VMValue> = 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)
}

View File

@ -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");
}

View File

@ -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) => {

View File

@ -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 φ を生成しない。

View File

@ -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,
};

View File

@ -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

View File

@ -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、同様にデッドコード

View File

@ -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"
);
}
}

View File

@ -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コンテキストを設定

View File

@ -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(

View File

@ -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);
}

View File

@ -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));
}

View File

@ -74,4 +74,3 @@ pub(super) fn resolve_bridge_global(
Ok(None)
}
}

View File

@ -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 実装。

View File

@ -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<S: VarScope>(
env: &BridgeEnv,

View File

@ -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;

View File

@ -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};

View File

@ -42,4 +42,3 @@ pub(super) fn lower_throw(
(dst, cur_bb)
}
}

View File

@ -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(

View File

@ -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;

View File

@ -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.

View File

@ -105,7 +105,8 @@ 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(
let compile_result =
match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
&mut mir_compiler,
ast,
Some(filename),

View File

@ -32,7 +32,8 @@ 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(
let compile_result =
match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
&mut mir_compiler,
ast,
Some(filename),

View File

@ -147,7 +147,8 @@ 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(
let mut compile_result =
match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
&mut mir_compiler,
ast,
Some(filename),

View File

@ -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);
}

View File

@ -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<String> = Vec::new();
@ -277,7 +280,10 @@ impl NyashRunner {
) {
// 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
);
}
}
}

View File

@ -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 <backend> <source.hako>
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);

View File

@ -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

View File

@ -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");

View File

@ -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>, JoinValue)],
) -> Result<(), String> {
pub fn run_cases(&self, cases: &[(Vec<JoinValue>, JoinValue)]) -> Result<(), String> {
for (inputs, expected) in cases {
self.run_case(inputs, expected.clone())?;
}

View File

@ -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"
);
}

View File

@ -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 テスト
///

View File

@ -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");

View File

@ -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 {

View File

@ -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(
@ -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)

View File

@ -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),
}
}

View File

@ -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 {
@ -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);
}

View File

@ -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)

View File

@ -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 {
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");
}

View File

@ -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);