feat(joinir): FuncScannerBox.trim/1 JoinIR VM Bridge A/B test
Phase 30.x: JoinIR VM Bridge でPHI問題を設計で解決できることを実証 変更内容: - src/runner/modes/vm.rs: FuncScannerBox.trim/1 検出時にJoinIR経由実行 - src/mir/join_ir_vm_bridge.rs: Non-tail call サポート追加 - dst=Some(id) の場合、結果を格納して次の命令へ継続 - src/tests/joinir_vm_bridge_trim.rs: A/Bテスト新規作成 - メインテスト: Route A (VM) → "void" (PHI bug), Route C (JoinIR) → "abc" ✅ - エッジケース: 5パターン全てPASS - src/config/env.rs: joinir_vm_bridge_debug() 追加 - docs: Phase 30 TASKS.md に L-0.4 追加 テスト結果: Route A (MIR→VM直接): "void" ← PHI バグ Route C (MIR→JoinIR→VM): "abc" ← 正解 ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -189,6 +189,26 @@ pub fn fail_fast() -> bool {
|
||||
env_bool_default("NYASH_FAIL_FAST", true)
|
||||
}
|
||||
|
||||
// ---- Phase 29/30 JoinIR toggles ----
|
||||
/// JoinIR experiment mode. Required for JoinIR-related experimental paths.
|
||||
/// Set NYASH_JOINIR_EXPERIMENT=1 to enable.
|
||||
pub fn joinir_experiment_enabled() -> bool {
|
||||
env_bool("NYASH_JOINIR_EXPERIMENT")
|
||||
}
|
||||
|
||||
/// JoinIR VM bridge mode. When enabled with NYASH_JOINIR_EXPERIMENT=1,
|
||||
/// specific functions can be executed via JoinIR → VM bridge instead of direct MIR → VM.
|
||||
/// Set NYASH_JOINIR_VM_BRIDGE=1 to enable.
|
||||
pub fn joinir_vm_bridge_enabled() -> bool {
|
||||
env_bool("NYASH_JOINIR_VM_BRIDGE")
|
||||
}
|
||||
|
||||
/// JoinIR VM bridge debug output. Enables verbose logging of JoinIR→MIR conversion.
|
||||
/// Set NYASH_JOINIR_VM_BRIDGE_DEBUG=1 to enable.
|
||||
pub fn joinir_vm_bridge_debug() -> bool {
|
||||
env_bool("NYASH_JOINIR_VM_BRIDGE_DEBUG")
|
||||
}
|
||||
|
||||
// VM legacy by-name call fallback was removed (Phase 2 complete).
|
||||
|
||||
// ---- Phase 11.8 MIR cleanup toggles ----
|
||||
|
||||
@ -23,15 +23,26 @@
|
||||
//! 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::{
|
||||
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 {
|
||||
@ -52,6 +63,40 @@ impl From<VMError> for JoinIrVmBridgeError {
|
||||
}
|
||||
}
|
||||
|
||||
/// ブロックを確定する(instructions + spans + terminator を設定)
|
||||
fn finalize_block(
|
||||
mir_func: &mut MirFunction,
|
||||
block_id: BasicBlockId,
|
||||
instructions: Vec<MirInstruction>,
|
||||
terminator: MirInstruction,
|
||||
) {
|
||||
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||
let inst_count = instructions.len();
|
||||
block.instructions = instructions;
|
||||
block.instruction_spans = vec![Span::unknown(); inst_count];
|
||||
block.terminator = Some(terminator);
|
||||
}
|
||||
}
|
||||
|
||||
/// JoinFuncId から MIR 用の関数名を生成
|
||||
fn join_func_name(id: JoinFuncId) -> String {
|
||||
format!("join_func_{}", id.0)
|
||||
}
|
||||
|
||||
/// JoinValue → MirConstValue 変換
|
||||
fn join_value_to_mir_const(value: &JoinValue) -> Result<MirConstValue, JoinIrVmBridgeError> {
|
||||
match value {
|
||||
JoinValue::Int(v) => Ok(MirConstValue::Integer(*v)),
|
||||
JoinValue::Bool(b) => Ok(MirConstValue::Bool(*b)),
|
||||
JoinValue::Str(s) => Ok(MirConstValue::String(s.clone())),
|
||||
JoinValue::Unit => Ok(MirConstValue::Null),
|
||||
_ => Err(JoinIrVmBridgeError::new(format!(
|
||||
"Unsupported JoinValue type: {:?}",
|
||||
value
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
||||
///
|
||||
/// ## Arguments
|
||||
@ -78,23 +123,21 @@ pub fn run_joinir_via_vm(
|
||||
entry_func: JoinFuncId,
|
||||
args: &[JoinValue],
|
||||
) -> Result<JoinValue, JoinIrVmBridgeError> {
|
||||
eprintln!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
||||
eprintln!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
||||
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, entry_func)?;
|
||||
// Step 1: JoinIR → MIR 変換 (with argument wrapper)
|
||||
let mir_module = convert_joinir_to_mir_with_args(join_module, entry_func, args)?;
|
||||
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR",
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR (+ entry wrapper)",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
// Step 2: VM 実行
|
||||
let mut vm = MirInterpreter::new();
|
||||
|
||||
// 初期引数を VM に設定(暫定実装: 環境変数経由)
|
||||
// TODO: S-4.4 で引数渡し機構を追加
|
||||
eprintln!(
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Executing via VM with {} arguments",
|
||||
args.len()
|
||||
);
|
||||
@ -106,44 +149,106 @@ pub fn run_joinir_via_vm(
|
||||
let join_result = JoinValue::from_vm_value(&vm_value)
|
||||
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
||||
|
||||
eprintln!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
||||
debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
||||
|
||||
Ok(join_result)
|
||||
}
|
||||
|
||||
/// Phase 27-shortterm S-4.3: JoinIR → MIR 変換器
|
||||
/// Phase 30.x: JoinIR → MIR 変換器 (with entry arguments)
|
||||
///
|
||||
/// JoinIR の関数群を MIR モジュールに変換する。
|
||||
/// JoinIR の正規化構造(Pinned/Carrier、Exit φ)は関数引数として表現されているため、
|
||||
/// MIR に変換しても構造的意味は保持される。
|
||||
fn convert_joinir_to_mir(
|
||||
/// Creates a wrapper "main" function that:
|
||||
/// 1. Creates constant values for the entry arguments
|
||||
/// 2. Calls the actual entry function with those arguments
|
||||
/// 3. Returns the result
|
||||
fn convert_joinir_to_mir_with_args(
|
||||
join_module: &JoinModule,
|
||||
entry_func: JoinFuncId,
|
||||
args: &[JoinValue],
|
||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||
let mut mir_module = MirModule::new("joinir_bridge".to_string());
|
||||
|
||||
// すべての JoinIR 関数を MIR 関数に変換
|
||||
// Convert all JoinIR functions to MIR (entry function becomes "skip" or similar)
|
||||
for (func_id, join_func) in &join_module.functions {
|
||||
eprintln!(
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting JoinFunction {} ({})",
|
||||
func_id.0, join_func.name
|
||||
);
|
||||
|
||||
let mir_func = convert_join_function_to_mir(join_func)?;
|
||||
|
||||
// Entry function を "main" として登録
|
||||
let func_name = if *func_id == entry_func {
|
||||
"main".to_string()
|
||||
} else {
|
||||
format!("join_func_{}", func_id.0)
|
||||
};
|
||||
|
||||
mir_module.functions.insert(func_name, mir_func);
|
||||
// Use actual function name (not "main") since we'll create a wrapper
|
||||
mir_module.functions.insert(join_func_name(*func_id), mir_func);
|
||||
}
|
||||
|
||||
// Create a wrapper "main" function that calls the entry function with args
|
||||
let wrapper = create_entry_wrapper(entry_func, args)?;
|
||||
mir_module.functions.insert("main".to_string(), wrapper);
|
||||
|
||||
Ok(mir_module)
|
||||
}
|
||||
|
||||
/// Create a wrapper "main" function that calls the entry function with constant arguments
|
||||
fn create_entry_wrapper(
|
||||
entry_func: JoinFuncId,
|
||||
args: &[JoinValue],
|
||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||
let entry_block = BasicBlockId(0);
|
||||
|
||||
let signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![], // No parameters - args are hardcoded
|
||||
return_type: MirType::Unknown,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let mut mir_func = MirFunction::new(signature, entry_block);
|
||||
|
||||
// Generate instructions to create constant arguments and call entry function
|
||||
let mut instructions = Vec::new();
|
||||
|
||||
// Create constant values for each argument
|
||||
let mut arg_value_ids = Vec::new();
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
let dst = ValueId(50000 + i as u32); // Use high ValueIds to avoid conflicts
|
||||
arg_value_ids.push(dst);
|
||||
|
||||
let const_value = join_value_to_mir_const(arg)?;
|
||||
instructions.push(MirInstruction::Const { dst, value: const_value });
|
||||
}
|
||||
|
||||
// Create function name constant
|
||||
let func_name_id = ValueId(59990);
|
||||
instructions.push(MirInstruction::Const {
|
||||
dst: func_name_id,
|
||||
value: MirConstValue::String(join_func_name(entry_func)),
|
||||
});
|
||||
|
||||
// Create Call instruction
|
||||
let result_id = ValueId(59991);
|
||||
instructions.push(MirInstruction::Call {
|
||||
dst: Some(result_id),
|
||||
func: func_name_id,
|
||||
callee: None,
|
||||
args: arg_value_ids,
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
|
||||
// Set up entry block
|
||||
finalize_block(
|
||||
&mut mir_func,
|
||||
entry_block,
|
||||
instructions,
|
||||
MirInstruction::Return { value: Some(result_id) },
|
||||
);
|
||||
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Created entry wrapper with {} args",
|
||||
args.len()
|
||||
);
|
||||
|
||||
Ok(mir_func)
|
||||
}
|
||||
|
||||
/// JoinFunction → MirFunction 変換
|
||||
fn convert_join_function_to_mir(
|
||||
join_func: &crate::mir::join_ir::JoinFunction,
|
||||
@ -170,6 +275,10 @@ fn convert_join_function_to_mir(
|
||||
|
||||
let mut mir_func = MirFunction::new(signature, entry_block);
|
||||
|
||||
// Phase 30.x: Set parameter ValueIds from JoinIR function
|
||||
// JoinIR's VarId is an alias for ValueId, so direct copy works
|
||||
mir_func.params = join_func.params.clone();
|
||||
|
||||
// Phase 27-shortterm S-4.4: Multi-block conversion for Jump instructions
|
||||
// Strategy:
|
||||
// - Accumulate Compute instructions in current block
|
||||
@ -191,55 +300,66 @@ fn convert_join_function_to_mir(
|
||||
dst,
|
||||
k_next,
|
||||
} => {
|
||||
// Phase 27-shortterm S-4.4-A: skip_ws pattern only (tail calls)
|
||||
// Validate: dst=None, k_next=None (tail call)
|
||||
if dst.is_some() || k_next.is_some() {
|
||||
// Phase 30.x: Support both tail calls and non-tail calls
|
||||
// - dst=None, k_next=None: Tail call → call + return
|
||||
// - dst=Some(id), k_next=None: Non-tail call → call + store + continue
|
||||
// - k_next=Some: Not yet supported
|
||||
|
||||
if k_next.is_some() {
|
||||
return Err(JoinIrVmBridgeError::new(format!(
|
||||
"Non-tail Call not supported (dst={:?}, k_next={:?}). Only skip_ws tail call pattern is supported.",
|
||||
dst, k_next
|
||||
"Call with k_next is not yet supported (k_next={:?})",
|
||||
k_next
|
||||
)));
|
||||
}
|
||||
|
||||
// Convert JoinFuncId to function name
|
||||
// For skip_ws: func=0 → "main", func=1 → "join_func_1"
|
||||
let func_name = if func.0 == 0 {
|
||||
"main".to_string()
|
||||
} else {
|
||||
format!("join_func_{}", func.0)
|
||||
};
|
||||
let func_name = join_func_name(*func);
|
||||
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] Converting Call to function '{}' with {} args",
|
||||
func_name,
|
||||
args.len()
|
||||
);
|
||||
|
||||
// Create a temporary ValueId to hold the function name (string constant)
|
||||
// This is a workaround for MIR's string-based Call resolution
|
||||
// In Phase 27-shortterm, we use a high ValueId to avoid conflicts
|
||||
let func_name_id = ValueId(9999);
|
||||
// Create temporary ValueId for function name
|
||||
let func_name_id = ValueId(99990 + next_block_id);
|
||||
next_block_id += 1;
|
||||
|
||||
// Add Const instruction for function name
|
||||
current_instructions.push(MirInstruction::Const {
|
||||
dst: func_name_id,
|
||||
value: MirConstValue::String(func_name.clone()),
|
||||
value: MirConstValue::String(func_name),
|
||||
});
|
||||
|
||||
// Create Call instruction (legacy string-based)
|
||||
// Phase 27-shortterm: No Callee yet, use func field with string ValueId
|
||||
current_instructions.push(MirInstruction::Call {
|
||||
dst: None, // tail call, no return value captured
|
||||
func: func_name_id,
|
||||
callee: None, // Phase 27-shortterm: no Callee resolution
|
||||
args: args.clone(),
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
match dst {
|
||||
Some(result_dst) => {
|
||||
// Non-tail call: store result in dst and continue
|
||||
current_instructions.push(MirInstruction::Call {
|
||||
dst: Some(*result_dst),
|
||||
func: func_name_id,
|
||||
callee: None,
|
||||
args: args.clone(),
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
// Continue to next instruction (no block termination)
|
||||
}
|
||||
None => {
|
||||
// Tail call: call + return result
|
||||
let call_result_id = ValueId(99991);
|
||||
current_instructions.push(MirInstruction::Call {
|
||||
dst: Some(call_result_id),
|
||||
func: func_name_id,
|
||||
callee: None,
|
||||
args: args.clone(),
|
||||
effects: EffectMask::PURE,
|
||||
});
|
||||
|
||||
// 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);
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
eprintln!(
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}",
|
||||
cont, args, cond
|
||||
);
|
||||
@ -252,24 +372,20 @@ fn convert_join_function_to_mir(
|
||||
let continue_block_id = BasicBlockId(next_block_id);
|
||||
next_block_id += 1;
|
||||
|
||||
// Emit Branch in current block
|
||||
current_instructions.push(MirInstruction::Branch {
|
||||
// Phase 30.x: Branch terminator (separate from instructions)
|
||||
let branch_terminator = MirInstruction::Branch {
|
||||
condition: *cond_var,
|
||||
then_bb: exit_block_id,
|
||||
else_bb: continue_block_id,
|
||||
});
|
||||
};
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
block.instructions = current_instructions;
|
||||
}
|
||||
// Finalize current block with Branch terminator
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, branch_terminator);
|
||||
|
||||
// Create exit block with Return
|
||||
// Create exit block with Return terminator
|
||||
let exit_value = args.first().copied();
|
||||
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
|
||||
exit_block
|
||||
.instructions
|
||||
.push(MirInstruction::Return { value: exit_value });
|
||||
exit_block.terminator = Some(MirInstruction::Return { value: exit_value });
|
||||
mir_func.blocks.insert(exit_block_id, exit_block);
|
||||
|
||||
// Create continue block (will be populated by subsequent instructions)
|
||||
@ -281,14 +397,12 @@ fn convert_join_function_to_mir(
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
None => {
|
||||
// Unconditional jump: direct Return
|
||||
// Unconditional jump: direct Return terminator
|
||||
let exit_value = args.first().copied();
|
||||
current_instructions.push(MirInstruction::Return { value: exit_value });
|
||||
let return_terminator = MirInstruction::Return { value: exit_value };
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
block.instructions = current_instructions;
|
||||
}
|
||||
// Finalize current block with Return terminator
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator);
|
||||
|
||||
// No continuation after unconditional return
|
||||
current_instructions = Vec::new();
|
||||
@ -296,12 +410,11 @@ fn convert_join_function_to_mir(
|
||||
}
|
||||
}
|
||||
JoinInst::Ret { value } => {
|
||||
current_instructions.push(MirInstruction::Return { value: *value });
|
||||
// Phase 30.x: Return terminator (separate from instructions)
|
||||
let return_terminator = MirInstruction::Return { value: *value };
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
block.instructions = current_instructions;
|
||||
}
|
||||
// Finalize current block with Return terminator
|
||||
finalize_block(&mut mir_func, current_block_id, current_instructions, return_terminator);
|
||||
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
@ -310,11 +423,34 @@ fn convert_join_function_to_mir(
|
||||
|
||||
// Finalize any remaining instructions in the last block
|
||||
if !current_instructions.is_empty() {
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Final block {:?} has {} remaining instructions",
|
||||
current_block_id,
|
||||
current_instructions.len()
|
||||
);
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
// Phase 30.x: VM requires instruction_spans to match instructions length
|
||||
let inst_count = current_instructions.len();
|
||||
block.instructions = current_instructions;
|
||||
block.instruction_spans = vec![Span::unknown(); inst_count];
|
||||
}
|
||||
}
|
||||
|
||||
// Debug: print all blocks and their instruction counts + terminators
|
||||
debug_log!(
|
||||
"[joinir_vm_bridge] Function '{}' has {} blocks:",
|
||||
mir_func.signature.name,
|
||||
mir_func.blocks.len()
|
||||
);
|
||||
for (block_id, block) in &mir_func.blocks {
|
||||
debug_log!(
|
||||
" Block {:?}: {} instructions, terminator={:?}",
|
||||
block_id,
|
||||
block.instructions.len(),
|
||||
block.terminator
|
||||
);
|
||||
}
|
||||
|
||||
Ok(mir_func)
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,13 @@ use super::super::NyashRunner;
|
||||
use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
|
||||
use std::{fs, process};
|
||||
|
||||
// Phase 30.x: JoinIR VM Bridge integration (experimental)
|
||||
// Used only when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||
use crate::config::env::{joinir_experiment_enabled, joinir_vm_bridge_enabled};
|
||||
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;
|
||||
|
||||
impl NyashRunner {
|
||||
/// Execute VM mode with full plugin initialization and AST prelude merge
|
||||
pub(crate) fn execute_vm_mode(&self, filename: &str) {
|
||||
@ -495,6 +502,106 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 30.x: JoinIR VM Bridge experimental path
|
||||
// Activated when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||
// Currently only supports minimal_ssa_skip_ws.hako (Main.skip function)
|
||||
let joinir_path_attempted = if joinir_experiment_enabled() && joinir_vm_bridge_enabled() {
|
||||
// Check if this module contains Main.skip/1 (minimal_ssa_skip_ws target)
|
||||
// Note: function names include arity suffix like "Main.skip/1"
|
||||
let has_main_skip = module_vm.functions.contains_key("Main.skip/1");
|
||||
let has_trim = module_vm.functions.contains_key("FuncScannerBox.trim/1");
|
||||
|
||||
if has_main_skip {
|
||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for Main.skip");
|
||||
match lower_skip_ws_to_joinir(&module_vm) {
|
||||
Some(join_module) => {
|
||||
// Get input argument from NYASH_JOINIR_INPUT or use default
|
||||
let input = std::env::var("NYASH_JOINIR_INPUT")
|
||||
.unwrap_or_else(|_| " abc".to_string());
|
||||
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
||||
|
||||
match run_joinir_via_vm(
|
||||
&join_module,
|
||||
JoinFuncId::new(0),
|
||||
&[JoinValue::Str(input)],
|
||||
) {
|
||||
Ok(result) => {
|
||||
let exit_code = match &result {
|
||||
JoinValue::Int(v) => *v as i32,
|
||||
JoinValue::Bool(b) => if *b { 1 } else { 0 },
|
||||
_ => 0,
|
||||
};
|
||||
eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result);
|
||||
if !quiet_pipe {
|
||||
println!("RC: {}", exit_code);
|
||||
}
|
||||
process::exit(exit_code);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[joinir/vm_bridge] ❌ JoinIR execution failed: {:?}", e);
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false // Continue to normal VM execution
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("[joinir/vm_bridge] lower_skip_ws_to_joinir returned None");
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
} else if has_trim {
|
||||
// Phase 30.x: FuncScannerBox.trim/1 JoinIR path
|
||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for FuncScannerBox.trim");
|
||||
match lower_funcscanner_trim_to_joinir(&module_vm) {
|
||||
Some(join_module) => {
|
||||
// Get input argument from NYASH_JOINIR_INPUT or use default
|
||||
let input = std::env::var("NYASH_JOINIR_INPUT")
|
||||
.unwrap_or_else(|_| " abc ".to_string());
|
||||
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
||||
|
||||
match run_joinir_via_vm(
|
||||
&join_module,
|
||||
JoinFuncId::new(0),
|
||||
&[JoinValue::Str(input)],
|
||||
) {
|
||||
Ok(result) => {
|
||||
// trim returns a string, print it and exit with 0
|
||||
eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result);
|
||||
if !quiet_pipe {
|
||||
match &result {
|
||||
JoinValue::Str(s) => println!("{}", s),
|
||||
_ => println!("{:?}", result),
|
||||
}
|
||||
}
|
||||
process::exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e);
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("[joinir/vm_bridge] lower_funcscanner_trim_to_joinir returned None");
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false // No supported JoinIR target function
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Normal VM execution path (fallback or default)
|
||||
if joinir_path_attempted {
|
||||
// This branch is never reached because successful JoinIR path calls process::exit()
|
||||
unreachable!("JoinIR path should have exited");
|
||||
}
|
||||
|
||||
match vm.execute_module(&module_vm) {
|
||||
Ok(ret) => {
|
||||
use crate::box_trait::{BoolBox, IntegerBox};
|
||||
|
||||
222
src/tests/joinir_vm_bridge_trim.rs
Normal file
222
src/tests/joinir_vm_bridge_trim.rs
Normal file
@ -0,0 +1,222 @@
|
||||
// Phase 30.x: JoinIR → Rust VM Bridge A/B Test for FuncScannerBox.trim/1
|
||||
//
|
||||
// 目的:
|
||||
// - JoinIR を VM ブリッジ経由で実行し、直接 VM 実行の結果と一致することを確認する
|
||||
// - Route A (AST→MIR→VM) と Route C (AST→MIR→JoinIR→MIR'→VM) の比較
|
||||
//
|
||||
// Test Pattern:
|
||||
// - trim(" abc ") → "abc" (leading + trailing whitespace removed)
|
||||
//
|
||||
// Implementation Status:
|
||||
// - Phase 30.x: Non-tail call support in VM bridge ✅
|
||||
// - A/B test (this file) ✅
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::backend::VM;
|
||||
use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, JoinFuncId};
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
use crate::mir::MirCompiler;
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
fn require_experiment_toggle() -> bool {
|
||||
if std::env::var("NYASH_JOINIR_VM_BRIDGE").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Minimal FuncScannerBox.trim/1 implementation for testing
|
||||
const TRIM_SOURCE: &str = r#"
|
||||
static box FuncScannerBox {
|
||||
trim(s) {
|
||||
local str = "" + s
|
||||
local n = str.length()
|
||||
local b = me._skip_leading(str, 0, n)
|
||||
local e = n + 0
|
||||
return me._trim_trailing(str, b, e)
|
||||
}
|
||||
|
||||
_skip_leading(str, i, n) {
|
||||
loop(i < n) {
|
||||
local ch = str.substring(i, i + 1)
|
||||
local is_ws = (ch == " ") or (ch == "\t") or (ch == "\n") or (ch == "\r")
|
||||
if (not is_ws) {
|
||||
return i
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
_trim_trailing(str, b, e) {
|
||||
loop(e > b) {
|
||||
local ch = str.substring(e - 1, e)
|
||||
local is_ws = (ch == " ") or (ch == "\t") or (ch == "\n") or (ch == "\r")
|
||||
if (not is_ws) {
|
||||
return str.substring(b, e)
|
||||
}
|
||||
e = e - 1
|
||||
}
|
||||
return str.substring(b, e)
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn joinir_vm_bridge_trim_matches_direct_vm() {
|
||||
if !require_experiment_toggle() {
|
||||
return;
|
||||
}
|
||||
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||
std::env::set_var("NYASH_VM_MAX_STEPS", "100000");
|
||||
|
||||
let runner = r#"
|
||||
static box Runner {
|
||||
main(args) {
|
||||
return FuncScannerBox.trim(" abc ")
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let full_src = format!("{TRIM_SOURCE}\n{runner}");
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("trim: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("trim: MIR compile failed");
|
||||
|
||||
// Route A: AST → MIR → VM (direct)
|
||||
eprintln!("[joinir_vm_bridge_test/trim] Route A: Direct VM execution");
|
||||
std::env::set_var("NYASH_ENTRY", "Runner.main");
|
||||
let mut vm = VM::new();
|
||||
let vm_out = vm
|
||||
.execute_module(&compiled.module)
|
||||
.expect("trim: VM execution failed");
|
||||
let vm_result = vm_out.to_string_box().value;
|
||||
std::env::remove_var("NYASH_ENTRY");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test/trim] Route A result: {}", vm_result);
|
||||
|
||||
// Route C: AST → MIR → JoinIR → MIR' → VM (via bridge)
|
||||
eprintln!("[joinir_vm_bridge_test/trim] Route C: JoinIR → VM bridge execution");
|
||||
let join_module = lower_funcscanner_trim_to_joinir(&compiled.module)
|
||||
.expect("lower_funcscanner_trim_to_joinir failed");
|
||||
|
||||
let bridge_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
JoinFuncId::new(0),
|
||||
&[JoinValue::Str(" abc ".to_string())],
|
||||
)
|
||||
.expect("JoinIR VM bridge failed for trim");
|
||||
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/trim] Route C result: {:?}",
|
||||
bridge_result
|
||||
);
|
||||
|
||||
// Assertions:
|
||||
// Note: Route A (direct VM) may return incorrect result due to PHI bugs.
|
||||
// This is expected and is exactly what JoinIR is designed to fix.
|
||||
// We verify that JoinIR returns the correct result.
|
||||
match bridge_result {
|
||||
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'");
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
other => panic!("JoinIR VM bridge returned non-string value: {:?}", other),
|
||||
}
|
||||
|
||||
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||
std::env::remove_var("HAKO_PARSER_STAGE3");
|
||||
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||
std::env::remove_var("NYASH_VM_MAX_STEPS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn joinir_vm_bridge_trim_edge_cases() {
|
||||
if !require_experiment_toggle() {
|
||||
return;
|
||||
}
|
||||
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||
|
||||
// Test cases: (input, expected_output)
|
||||
// Note: Escape characters like \t\n\r are not tested here because
|
||||
// Nyash string literal parsing differs from Rust escape handling.
|
||||
let test_cases = [
|
||||
(" abc ", "abc"),
|
||||
("hello", "hello"),
|
||||
(" ", ""),
|
||||
(" x ", "x"),
|
||||
(" hello world ", "hello world"),
|
||||
];
|
||||
|
||||
for (input, expected) in test_cases {
|
||||
// Re-set environment variables at each iteration to avoid race conditions
|
||||
// with parallel test execution
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
std::env::set_var("NYASH_DISABLE_PLUGINS", "1");
|
||||
|
||||
let runner = format!(
|
||||
r#"
|
||||
static box Runner {{
|
||||
main(args) {{
|
||||
return FuncScannerBox.trim("{input}")
|
||||
}}
|
||||
}}
|
||||
"#
|
||||
);
|
||||
let full_src = format!("{TRIM_SOURCE}\n{runner}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("trim edge case: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("trim edge case: MIR compile failed");
|
||||
|
||||
// JoinIR path only
|
||||
let join_module = lower_funcscanner_trim_to_joinir(&compiled.module)
|
||||
.expect("lower_funcscanner_trim_to_joinir failed");
|
||||
|
||||
let bridge_result = run_joinir_via_vm(
|
||||
&join_module,
|
||||
JoinFuncId::new(0),
|
||||
&[JoinValue::Str(input.to_string())],
|
||||
)
|
||||
.expect("JoinIR VM bridge failed for trim edge case");
|
||||
|
||||
match bridge_result {
|
||||
JoinValue::Str(s) => {
|
||||
assert_eq!(
|
||||
s, expected,
|
||||
"trim({:?}) expected {:?} but got {:?}",
|
||||
input, expected, s
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test/trim] ✅ trim({:?}) = {:?}",
|
||||
input, s
|
||||
);
|
||||
}
|
||||
other => panic!(
|
||||
"trim({:?}) returned non-string value: {:?}",
|
||||
input, other
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
std::env::remove_var("NYASH_PARSER_STAGE3");
|
||||
std::env::remove_var("HAKO_PARSER_STAGE3");
|
||||
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||
}
|
||||
@ -10,6 +10,7 @@ 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 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;
|
||||
|
||||
Reference in New Issue
Block a user