Files
hakorune/src/mir/join_ir_vm_bridge.rs
nyash-codex 853d80ba74 feat(joinir): Phase 34-7.5 helpers + Phase 34-8 Break/Continue implementation
Phase 34-7.5: Code organization improvements
- Added type conversion helpers (as_cont/as_func) in join_ir/mod.rs
- Enhanced docstrings for JoinCall/JoinJump with usage examples
- Improved error messages in join_ir_vm_bridge.rs

JoinIrFrontendTestRunner box implementation
- Created src/tests/helpers/joinir_frontend.rs (119 lines)
- Reduced test code by 83% (70 lines → 12 lines per test)
- 26% overall reduction in test file (284 → 209 lines)

Phase 34-8: Break/Continue pattern implementation
- Extended ast_lowerer.rs (+630 lines)
  - lower_loop_break_pattern(): Break as Jump (early return)
  - lower_loop_continue_pattern(): Continue as Select + Call
  - Added Bool literal support in extract_value()
- Created 2 fixtures: loop_frontend_{break,continue}.program.json
- Added 2 A/B tests (all 6 Phase 34 tests PASS)

Technical achievements:
- Break = Jump (early return pattern)
- Continue = Select + Call (NOT Jump) - critical discovery
- 3-function structure sufficient (no k_continue needed)
- SSA-style re-assignment with natural var_map updates

Test results:
 joinir_frontend_if_select_simple_ab_test
 joinir_frontend_if_select_local_ab_test
 joinir_frontend_json_shape_read_value_ab_test
 joinir_frontend_loop_simple_ab_test
 joinir_frontend_loop_break_ab_test
 joinir_frontend_loop_continue_ab_test

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 01:02:49 +09:00

684 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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::{
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))
}
}
/// ブロックを確定する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)
}
/// 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> {
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
);
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);
}
Ok(mir_module)
}
/// JoinFunction → MirFunction 変換
fn convert_join_function_to_mir(
join_func: &crate::mir::join_ir::JoinFunction,
) -> Result<MirFunction, JoinIrVmBridgeError> {
// 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
// Phase 27-shortterm: すべて MirType::Unknown として扱う(型情報は JoinIR に無いため)
let param_types = join_func
.params
.iter()
.map(|_| MirType::Unknown)
.collect::<Vec<_>>();
let signature = FunctionSignature {
name: join_func.name.clone(),
params: param_types,
return_type: MirType::Unknown,
effects: EffectMask::PURE,
};
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
// - 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
for join_inst in &join_func.body {
match join_inst {
JoinInst::Compute(mir_like) => {
let mir_inst = convert_mir_like_inst(mir_like)?;
current_instructions.push(mir_inst);
}
JoinInst::MethodCall { dst, receiver, method, args } => {
// Phase 34-6: MethodCall → MIR BoxCall 変換
// receiver.method(args...) を BoxCall(receiver, method, args) に変換
let mir_inst = MirInstruction::BoxCall {
dst: Some(*dst),
box_val: *receiver,
method: method.clone(),
method_id: None,
args: args.clone(),
effects: EffectMask::PURE,
};
current_instructions.push(mir_inst);
}
JoinInst::Call {
func,
args,
dst,
k_next,
} => {
// 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() {
let call_target_name = join_func_name(*func);
return Err(JoinIrVmBridgeError::new(format!(
"Call with k_next is not yet supported\n\
\n\
Current function: {}\n\
Call target: {} (JoinFuncId: {:?})\n\
Arguments: {} args\n\
k_next: {:?}\n\
\n\
💡 Fix: Change `k_next: Some(...)` to `k_next: None`\n\
💡 Note: Phase 31 and Phase 34 both use k_next=None (bridge limitation)\n\
\n\
Example:\n\
Call {{\n\
func: loop_step_id,\n\
args: vec![i_next, acc_next, n],\n\
k_next: None, // ⚠️ Must be None\n\
dst: Some(result),\n\
}}",
join_func.name,
call_target_name,
func,
args.len(),
k_next,
)));
}
// Convert JoinFuncId to function name
let func_name = join_func_name(*func);
// 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),
});
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
debug_log!(
"[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;
// 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 with 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();
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
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)
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 terminator
let exit_value = args.first().copied();
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);
// No continuation after unconditional return
current_instructions = Vec::new();
}
}
}
// Phase 33: Select instruction conversion to MIR
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
);
// 1. cond ブロック(現在のブロック)
let cond_block = current_block_id;
// 2. then ブロック作成
let then_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 3. else ブロック作成
let else_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 4. merge ブロック作成
let merge_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 5. cond ブロックで分岐
let branch_terminator = MirInstruction::Branch {
condition: *cond,
then_bb: then_block,
else_bb: else_block,
};
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);
then_block_obj.instructions.push(MirInstruction::Copy {
dst: *dst,
src: *then_val,
});
then_block_obj.instruction_spans.push(Span::unknown());
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
let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
else_block_obj.instructions.push(MirInstruction::Copy {
dst: *dst,
src: *else_val,
});
else_block_obj.instruction_spans.push(Span::unknown());
else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
mir_func.blocks.insert(else_block, else_block_obj);
// 8. merge ブロック作成(空)
let merge_block_obj = crate::mir::BasicBlock::new(merge_block);
mir_func.blocks.insert(merge_block, merge_block_obj);
// 9. merge ブロックに移動
current_block_id = merge_block;
current_instructions = Vec::new();
}
// Phase 33-6: IfMerge instruction conversion to MIR
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(),
));
}
debug_log!(
"[joinir_vm_bridge] Converting IfMerge: cond={:?}, merges.len()={}",
cond, merges.len()
);
// 1. cond ブロック(現在のブロック)
let cond_block = current_block_id;
// 2. then ブロック作成
let then_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 3. else ブロック作成
let else_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 4. merge ブロック作成
let merge_block = BasicBlockId(next_block_id);
next_block_id += 1;
// 5. cond ブロックで分岐
let branch_terminator = MirInstruction::Branch {
condition: *cond,
then_bb: then_block,
else_bb: else_block,
};
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);
for merge in merges {
then_block_obj.instructions.push(MirInstruction::Copy {
dst: merge.dst,
src: merge.then_val,
});
then_block_obj.instruction_spans.push(Span::unknown());
}
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
let mut else_block_obj = crate::mir::BasicBlock::new(else_block);
for merge in merges {
else_block_obj.instructions.push(MirInstruction::Copy {
dst: merge.dst,
src: merge.else_val,
});
else_block_obj.instruction_spans.push(Span::unknown());
}
else_block_obj.terminator = Some(MirInstruction::Jump { target: merge_block });
mir_func.blocks.insert(else_block, else_block_obj);
// 8. merge ブロック作成(空)
let merge_block_obj = crate::mir::BasicBlock::new(merge_block);
mir_func.blocks.insert(merge_block, merge_block_obj);
// 9. merge ブロックに移動
current_block_id = merge_block;
current_instructions = Vec::new();
}
JoinInst::Ret { value } => {
// Phase 30.x: Return terminator (separate from instructions)
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);
current_instructions = Vec::new();
}
}
}
// 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(&current_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)
}
/// MirLikeInst → MirInstruction 変換
fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result<MirInstruction, JoinIrVmBridgeError> {
match mir_like {
MirLikeInst::Const { dst, value } => {
let mir_const = match value {
ConstValue::Integer(i) => MirConstValue::Integer(*i),
ConstValue::Bool(b) => MirConstValue::Bool(*b),
ConstValue::String(s) => MirConstValue::String(s.clone()),
ConstValue::Null => MirConstValue::Null,
};
Ok(MirInstruction::Const {
dst: *dst,
value: mir_const,
})
}
MirLikeInst::BinOp { dst, op, lhs, rhs } => {
let mir_op = match op {
BinOpKind::Add => BinaryOp::Add,
BinOpKind::Sub => BinaryOp::Sub,
BinOpKind::Mul => BinaryOp::Mul,
BinOpKind::Div => BinaryOp::Div,
BinOpKind::Or => BinaryOp::Or,
BinOpKind::And => BinaryOp::And,
};
Ok(MirInstruction::BinOp {
dst: *dst,
op: mir_op,
lhs: *lhs,
rhs: *rhs,
})
}
MirLikeInst::Compare { dst, op, lhs, rhs } => {
let mir_cmp = match op {
CompareOp::Lt => MirCompareOp::Lt,
CompareOp::Le => MirCompareOp::Le,
CompareOp::Gt => MirCompareOp::Gt,
CompareOp::Ge => MirCompareOp::Ge,
CompareOp::Eq => MirCompareOp::Eq,
CompareOp::Ne => MirCompareOp::Ne,
};
Ok(MirInstruction::Compare {
dst: *dst,
op: mir_cmp,
lhs: *lhs,
rhs: *rhs,
})
}
MirLikeInst::BoxCall {
dst,
box_name,
method,
args,
} => {
// Phase 27-shortterm S-4.3: BoxCall → MIR BoxCall
// box_name は JoinIR で保持しているが、MIR BoxCall には receiver ValueId のみ
// 暫定: args[0] を receiver として扱う
if args.is_empty() {
return Err(JoinIrVmBridgeError::new(format!(
"BoxCall requires at least one argument (receiver), got: box_name={}, method={}",
box_name, method
)));
}
let receiver = args[0];
let method_args = args[1..].to_vec();
Ok(MirInstruction::BoxCall {
dst: *dst,
box_val: receiver,
method: method.clone(),
method_id: None, // Phase 27-shortterm: no method ID resolution
args: method_args,
effects: EffectMask::PURE, // Phase 27-shortterm: assume pure
})
}
}
}
#[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"),
}
}
}