feat(joinir): Phase 27-shortterm S-4 完了 - JoinIR → Rust VM ブリッジ参考実装
## 実装内容
### S-4.1: VM インターフェース調査
- VMValue 定義調査(Integer/Float/Bool/String/Future/Void/BoxRef)
- execute_module() API 調査
### S-4.2: JoinValue ↔ VMValue 変換関数実装
- src/mir/join_ir_ops.rs に変換関数追加(+121行)
- to_vm_value()/from_vm_value() 実装
- Float/Future/BoxRef は非対応エラー
### S-4.3: join_ir_vm_bridge.rs 基本構造実装
- src/mir/join_ir_vm_bridge.rs 新規作成(362行)
- run_joinir_via_vm() API 実装
- convert_join_function_to_mir() 実装
- 7つのコンパイルエラー修正完了
### S-4.4-A: Call/Jump 命令実装(skip_ws パターンのみ)
- Call: 末尾呼び出しのみサポート
- Jump: Multi-block CFG 生成(Branch + Return)
- 非対応パターンは unimplemented!() で落とす
### S-4.4-B: A/B テスト作成
- src/tests/joinir_vm_bridge_skip_ws.rs 作成(104行)
- Route A: 直接 VM 実行
- Route C: JoinIR → VM bridge 経由
- skip_ws(" abc") → 3 の A/B 比較実装
## 戦略転換
ChatGPT 先生 + Task 先生の分析により、**Approach 2 (JoinIR Runner 本線化 + ガードレール)** への移行が決定。
本ブリッジ実装は参考実装として保持し、これ以上は太らせない方針。次フェーズで JoinIR Runner を method_router 経由で Rust VM と統合する。
This commit is contained in:
@ -151,8 +151,9 @@ fn convert_joinir_to_mir(
|
||||
fn convert_join_function_to_mir(
|
||||
join_func: &crate::mir::join_ir::JoinFunction,
|
||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
||||
// Phase 27-shortterm S-4.3 最小実装:
|
||||
// 1つの basic block に全命令を配置
|
||||
// Phase 27-shortterm S-4.4: skip_ws パターン対応版
|
||||
// - Call (tail call): MIR Call に変換
|
||||
// - Jump (conditional exit): Branch + Return に変換
|
||||
let entry_block = BasicBlockId(0);
|
||||
|
||||
// Create minimal FunctionSignature for JoinIR function
|
||||
@ -170,44 +171,141 @@ fn convert_join_function_to_mir(
|
||||
|
||||
let mut mir_func = MirFunction::new(signature, entry_block);
|
||||
|
||||
let mut instructions = Vec::new();
|
||||
// Phase 27-shortterm S-4.4: Multi-block conversion for Jump instructions
|
||||
// Strategy:
|
||||
// - Accumulate Compute instructions in current block
|
||||
// - On Jump: emit Branch + create exit block with Return
|
||||
// - On Call: emit Call in current block
|
||||
let mut current_block_id = entry_block;
|
||||
let mut current_instructions = Vec::new();
|
||||
let mut next_block_id = 1u32; // for creating new blocks
|
||||
|
||||
// JoinInst → MirInstruction 変換
|
||||
for join_inst in &join_func.body {
|
||||
match join_inst {
|
||||
JoinInst::Compute(mir_like) => {
|
||||
let mir_inst = convert_mir_like_inst(mir_like)?;
|
||||
instructions.push(mir_inst);
|
||||
current_instructions.push(mir_inst);
|
||||
}
|
||||
JoinInst::Call { func, args, dst, .. } => {
|
||||
// TODO: S-4.4 で関数呼び出し実装
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] WARNING: Call instruction not yet implemented (func={:?}, args={:?}, dst={:?})",
|
||||
func, args, dst
|
||||
);
|
||||
JoinInst::Call { func, args, 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() {
|
||||
return Err(JoinIrVmBridgeError::new(format!(
|
||||
"Non-tail Call not supported (dst={:?}, k_next={:?}). Only skip_ws tail call pattern is supported.",
|
||||
dst, 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)
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Add Const instruction for function name
|
||||
current_instructions.push(MirInstruction::Const {
|
||||
dst: func_name_id,
|
||||
value: MirConstValue::String(func_name.clone()),
|
||||
});
|
||||
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
JoinInst::Jump { cont, args, cond } => {
|
||||
// TODO: S-4.4 で継続実装
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] WARNING: Jump instruction not yet implemented (cont={:?}, args={:?}, cond={:?})",
|
||||
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!("[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}", cont, args, cond);
|
||||
|
||||
match cond {
|
||||
Some(cond_var) => {
|
||||
// Conditional jump: Branch to exit block
|
||||
let exit_block_id = BasicBlockId(next_block_id);
|
||||
next_block_id += 1;
|
||||
let continue_block_id = BasicBlockId(next_block_id);
|
||||
next_block_id += 1;
|
||||
|
||||
// Emit Branch in current block
|
||||
current_instructions.push(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;
|
||||
}
|
||||
|
||||
// Create exit block with Return
|
||||
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,
|
||||
});
|
||||
mir_func.blocks.insert(exit_block_id, exit_block);
|
||||
|
||||
// Create continue block (will be populated by subsequent instructions)
|
||||
let continue_block = crate::mir::BasicBlock::new(continue_block_id);
|
||||
mir_func.blocks.insert(continue_block_id, continue_block);
|
||||
|
||||
// Continue in the next block
|
||||
current_block_id = continue_block_id;
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
None => {
|
||||
// Unconditional jump: direct Return
|
||||
let exit_value = args.first().copied();
|
||||
current_instructions.push(MirInstruction::Return {
|
||||
value: exit_value,
|
||||
});
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
block.instructions = current_instructions;
|
||||
}
|
||||
|
||||
// No continuation after unconditional return
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
JoinInst::Ret { value } => {
|
||||
if let Some(val_id) = value {
|
||||
instructions.push(MirInstruction::Return {
|
||||
value: Some(*val_id),
|
||||
});
|
||||
} else {
|
||||
instructions.push(MirInstruction::Return { value: None });
|
||||
current_instructions.push(MirInstruction::Return {
|
||||
value: *value,
|
||||
});
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
block.instructions = current_instructions;
|
||||
}
|
||||
|
||||
current_instructions = Vec::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entry block に命令を登録
|
||||
if let Some(block) = mir_func.blocks.get_mut(&entry_block) {
|
||||
block.instructions = instructions;
|
||||
// Finalize any remaining instructions in the last block
|
||||
if !current_instructions.is_empty() {
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
block.instructions = current_instructions;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mir_func)
|
||||
|
||||
105
src/tests/joinir_vm_bridge_skip_ws.rs
Normal file
105
src/tests/joinir_vm_bridge_skip_ws.rs
Normal file
@ -0,0 +1,105 @@
|
||||
// Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test
|
||||
//
|
||||
// 目的:
|
||||
// - JoinIR を VM ブリッジ経由で実行し、直接 VM 実行の結果と一致することを確認する
|
||||
// - Route A (AST→MIR→VM) と Route C (AST→MIR→JoinIR→MIR'→VM) の比較
|
||||
//
|
||||
// Test Pattern:
|
||||
// - skip_ws(" abc") → 3 (leading spaces count)
|
||||
//
|
||||
// Implementation Status:
|
||||
// - S-4.3: Basic bridge structure ✅
|
||||
// - S-4.4-A: Call/Jump instructions for skip_ws pattern ✅
|
||||
// - S-4.4-B: A/B test (this file) ✅
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::backend::VM;
|
||||
use crate::mir::join_ir::{lower_skip_ws_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
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn joinir_vm_bridge_skip_ws_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 src = std::fs::read_to_string("apps/tests/minimal_ssa_skip_ws.hako")
|
||||
.expect("failed to read minimal_ssa_skip_ws.hako");
|
||||
let runner = r#"
|
||||
static box Runner {
|
||||
main(args) {
|
||||
return Main.skip(" abc")
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let full_src = format!("{src}\n{runner}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed");
|
||||
|
||||
// Route A: AST → MIR → VM (direct)
|
||||
eprintln!("[joinir_vm_bridge_test] 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("skip_ws: VM execution failed");
|
||||
let vm_result = vm_out.to_string_box().value;
|
||||
std::env::remove_var("NYASH_ENTRY");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test] Route A result: {}", vm_result);
|
||||
|
||||
// Route C: AST → MIR → JoinIR → MIR' → VM (via bridge)
|
||||
eprintln!("[joinir_vm_bridge_test] Route C: JoinIR → VM bridge execution");
|
||||
let join_module =
|
||||
lower_skip_ws_to_joinir(&compiled.module).expect("lower_skip_ws_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 skip_ws");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test] Route C result: {:?}", bridge_result);
|
||||
|
||||
// Assertions: Both routes should produce the same result
|
||||
assert_eq!(vm_result, "3", "Route A (VM) expected to skip 3 leading spaces");
|
||||
match bridge_result {
|
||||
JoinValue::Int(v) => {
|
||||
assert_eq!(v, 3, "Route C (JoinIR→VM bridge) skip_ws result mismatch");
|
||||
eprintln!("[joinir_vm_bridge_test] ✅ A/B test passed: both routes returned 3");
|
||||
}
|
||||
other => panic!("JoinIR VM bridge returned non-int 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");
|
||||
}
|
||||
@ -18,6 +18,7 @@ pub mod mir_joinir_stage1_using_resolver_min; // Phase 27.12: Stage1UsingResolve
|
||||
pub mod mir_joinir_funcscanner_append_defs; // Phase 27.14: FuncScannerBox._append_defs JoinIR変換
|
||||
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 mir_locals_ssa;
|
||||
pub mod mir_loopform_conditional_reassign;
|
||||
pub mod mir_loopform_exit_phi;
|
||||
|
||||
Reference in New Issue
Block a user