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:
@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user