feat(joinir): S-5.2-improved - VM完全意味論統合完了

Phase 27-shortterm S-5.2-improved 実装完了:

## 実装内容
1. **execute_box_call() ラッパー追加**:
   - src/backend/mir_interpreter/mod.rs に公開 API 実装
   - 1_000_000 番台レジスタで一時値管理
   - 適切なクリーンアップ処理実装

2. **handle_box_call 可視性変更**:
   - src/backend/mir_interpreter/handlers/boxes.rs を pub に変更
   - JoinIR Runner からアクセス可能に

3. **JoinIR Runner BoxCall 統合**:
   - src/mir/join_ir_runner.rs の BoxCall 処理を書き換え
   - StringBox 直接呼び出し削除
   - VM の execute_box_call 経由に変更
   - JoinValue ↔ VMValue 変換で統合

4. **不要な関数削除**:
   - expect_str(), expect_int(), box_to_join_value() 削除
   - VMValue 変換に一本化

5. **テスト更新**:
   - joinir_runner_standalone.rs: VM インスタンス追加
   - joinir_runner_min.rs: VM 再利用

## 期待される効果
 Void guards 完全対応 (Void.length() → 0)
 PluginBox/InstanceBox 将来対応可能
 VM の完全な BoxCall 意味論統一
 VM 2号機回避(ガードレール設計成功)

## テスト結果
 joinir_runner_standalone_skip_ws ... ok
 joinir_runner_standalone_trim ... ok
 ビルド成功(0エラー)

## 完了タスク
- [x] MirInterpreter::execute_box_call() 実装
- [x] 1_000_000 レジスタ帯域割り当て
- [x] regs クリーンアップ実装
- [x] JoinIR Runner BoxCall 書き換え
- [x] テスト更新&PASS確認

🎉 VM 完全意味論統合完了!
This commit is contained in:
nyash-codex
2025-11-24 08:37:59 +09:00
parent a8555e67d5
commit 98dadf446f
6 changed files with 129 additions and 91 deletions

View File

@ -106,7 +106,8 @@ impl MirInterpreter {
} }
} }
pub(super) fn handle_box_call( /// S-5.2-improved: Made public for JoinIR Runner integration via execute_box_call wrapper
pub fn handle_box_call(
&mut self, &mut self,
dst: Option<ValueId>, dst: Option<ValueId>,
box_val: ValueId, box_val: ValueId,

View File

@ -79,6 +79,71 @@ impl MirInterpreter {
self.static_box_decls.insert(name, decl); self.static_box_decls.insert(name, decl);
} }
/// Execute a BoxCall with VM's complete semantics (Phase 27-shortterm S-5.2-improved)
///
/// This wrapper allows external modules (e.g., JoinIR Runner) to invoke BoxCall
/// with the VM's complete semantics including:
/// - Void guards (e.g., Void.length() → 0)
/// - PluginBox support (FileBox, NetBox, etc.)
/// - InstanceBox policy checks
/// - object_fields handling
/// - Method re-routing (toString→str)
///
/// # Implementation Notes
/// - Uses 1_000_000 register range for scratch registers (避ける ID 衝突)
/// - Properly cleans up temporary registers after use
/// - Delegates to `handle_box_call` for complete VM semantics
///
/// # Arguments
/// - `receiver`: The box instance (VMValue::BoxRef or primitive)
/// - `method`: Method name to invoke
/// - `args`: Method arguments as VMValue
///
/// # Returns
/// Result value as VMValue (may be Void, Int, String, BoxRef, etc.)
pub fn execute_box_call(
&mut self,
receiver: VMValue,
method: &str,
args: Vec<VMValue>,
) -> Result<VMValue, VMError> {
// Allocate temporary register IDs in the 1_000_000 range (not 1000!)
// This avoids conflicts with user code and future extensions
let base = ValueId(1_000_000);
let recv_id = base;
// Place receiver in register
self.regs.insert(recv_id, receiver);
// Place arguments in consecutive registers
let arg_ids: Vec<ValueId> = args
.into_iter()
.enumerate()
.map(|(i, v)| {
let id = ValueId(base.0 + 1 + i as u32);
self.regs.insert(id, v);
id
})
.collect();
// Allocate destination register
let dst_id = ValueId(base.0 + 1000);
// Invoke handle_box_call for complete VM semantics
self.handle_box_call(Some(dst_id), recv_id, method, &arg_ids)?;
// Read result (may be Void if method returns nothing)
let result = self.regs.remove(&dst_id).unwrap_or(VMValue::Void);
// Cleanup temporary registers (important to avoid stale values!)
self.regs.remove(&recv_id);
for id in arg_ids {
self.regs.remove(&id);
}
Ok(result)
}
/// Ensure static box singleton instance exists, create if not /// Ensure static box singleton instance exists, create if not
/// Returns mutable reference to the singleton instance /// Returns mutable reference to the singleton instance
fn ensure_static_box_instance( fn ensure_static_box_instance(

View File

@ -22,14 +22,16 @@ pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue};
pub type JoinRuntimeError = JoinIrOpError; pub type JoinRuntimeError = JoinIrOpError;
pub fn run_joinir_function( pub fn run_joinir_function(
vm: &mut crate::backend::mir_interpreter::MirInterpreter,
module: &JoinModule, module: &JoinModule,
entry: JoinFuncId, entry: JoinFuncId,
args: &[JoinValue], args: &[JoinValue],
) -> Result<JoinValue, JoinRuntimeError> { ) -> Result<JoinValue, JoinRuntimeError> {
execute_function(module, entry, args.to_vec()) execute_function(vm, module, entry, args.to_vec())
} }
fn execute_function( fn execute_function(
vm: &mut crate::backend::mir_interpreter::MirInterpreter,
module: &JoinModule, module: &JoinModule,
mut current_func: JoinFuncId, mut current_func: JoinFuncId,
mut current_args: Vec<JoinValue>, mut current_args: Vec<JoinValue>,
@ -58,7 +60,7 @@ fn execute_function(
while ip < func.body.len() { while ip < func.body.len() {
match &func.body[ip] { match &func.body[ip] {
JoinInst::Compute(inst) => { JoinInst::Compute(inst) => {
eval_compute(inst, &mut locals)?; eval_compute(vm, inst, &mut locals)?;
ip += 1; ip += 1;
} }
JoinInst::Call { JoinInst::Call {
@ -74,7 +76,7 @@ fn execute_function(
} }
let resolved_args = materialize_args(args, &locals)?; let resolved_args = materialize_args(args, &locals)?;
if let Some(dst_var) = dst { if let Some(dst_var) = dst {
let value = execute_function(module, *target, resolved_args)?; let value = execute_function(vm, module, *target, resolved_args)?;
locals.insert(*dst_var, value); locals.insert(*dst_var, value);
ip += 1; ip += 1;
} else { } else {
@ -113,7 +115,11 @@ fn execute_function(
} }
} }
fn eval_compute(inst: &MirLikeInst, locals: &mut HashMap<VarId, JoinValue>) -> Result<(), JoinRuntimeError> { fn eval_compute(
vm: &mut crate::backend::mir_interpreter::MirInterpreter,
inst: &MirLikeInst,
locals: &mut HashMap<VarId, JoinValue>,
) -> Result<(), JoinRuntimeError> {
match inst { match inst {
MirLikeInst::Const { dst, value } => { MirLikeInst::Const { dst, value } => {
let v = match value { let v = match value {
@ -138,57 +144,48 @@ fn eval_compute(inst: &MirLikeInst, locals: &mut HashMap<VarId, JoinValue>) -> R
let v = crate::mir::join_ir_ops::eval_compare(*op, &l, &r)?; let v = crate::mir::join_ir_ops::eval_compare(*op, &l, &r)?;
locals.insert(*dst, v); locals.insert(*dst, v);
} }
// S-5.2: BoxCall → VM method_router 経由(ガードレール設計) // S-5.2-improved: BoxCall → VM execute_box_call ラッパー経由
// - 制御フロー: JoinIR Runner が担当 // - 制御フロー: JoinIR Runner が担当
// - Box/Plugin 実装: Rust VM に委譲VM 2号機を避ける // - Box/Plugin 実装: Rust VM に完全委譲VM 2号機を避ける
// - VM の完全な BoxCall 意味論を使用:
// * Void guards (Void.length() → 0)
// * PluginBox サポート (FileBox, NetBox)
// * InstanceBox policy checks
// * object_fields handling
// * Method re-routing (toString→str)
MirLikeInst::BoxCall { MirLikeInst::BoxCall {
dst, dst,
box_name, box_name: _, // box_name は VM が内部で判定するため不要
method, method,
args, args,
} => { } => {
if box_name != "StringBox" { // First argument is the receiver (box instance)
return Err(JoinRuntimeError::new(format!( if args.is_empty() {
"Unsupported box call target: {}", return Err(JoinRuntimeError::new(
box_name "BoxCall requires at least a receiver argument",
))); ));
} }
match method.as_str() {
"length" => { // Convert receiver to VMValue
let arg = expect_str(&read_var(locals, args[0])?)?; let receiver_jv = read_var(locals, args[0])?;
// S-5.2: VM の StringBox.length() 実装を使用hardcoded 削除) let receiver_vm = receiver_jv.to_vm_value();
let string_box = crate::boxes::basic::StringBox::new(arg);
let result_box = string_box.length(); // Convert remaining arguments to VMValue
let result_value = box_to_join_value(result_box)?; let method_args_vm: Vec<crate::backend::VMValue> = args[1..]
let dst_var = dst.ok_or_else(|| { .iter()
JoinRuntimeError::new("length call requires destination") .map(|&var_id| read_var(locals, var_id).map(|jv| jv.to_vm_value()))
})?; .collect::<Result<Vec<_>, _>>()?;
locals.insert(dst_var, result_value);
} // Invoke VM's execute_box_call for complete semantics
"substring" => { let result_vm = vm.execute_box_call(receiver_vm, method, method_args_vm)
if args.len() != 3 { .map_err(|e| JoinRuntimeError::new(format!("BoxCall failed: {}", e)))?;
return Err(JoinRuntimeError::new(
"substring expects 3 arguments (s, start, end)", // Convert result back to JoinValue
)); let result_jv = crate::mir::join_ir_ops::JoinValue::from_vm_value(&result_vm)?;
}
let s = expect_str(&read_var(locals, args[0])?)?; // Store result if destination is specified
let start = expect_int(&read_var(locals, args[1])?)? as usize; if let Some(dst_var) = dst {
let end = expect_int(&read_var(locals, args[2])?)? as usize; locals.insert(*dst_var, result_jv);
// S-5.2: VM の StringBox.substring() 実装を使用hardcoded 削除)
let string_box = crate::boxes::basic::StringBox::new(s);
let result_box = string_box.substring(start, end);
let result_value = box_to_join_value(result_box)?;
let dst_var = dst.ok_or_else(|| {
JoinRuntimeError::new("substring call requires destination")
})?;
locals.insert(dst_var, result_value);
}
_ => {
return Err(JoinRuntimeError::new(format!(
"Unsupported StringBox method: {}",
method
)))
}
} }
} }
} }
@ -221,44 +218,3 @@ fn as_bool(value: &JoinValue) -> Result<bool, JoinRuntimeError> {
} }
} }
fn expect_int(value: &JoinValue) -> Result<i64, JoinRuntimeError> {
match value {
JoinValue::Int(i) => Ok(*i),
other => Err(JoinRuntimeError::new(format!(
"Expected int, got {:?}",
other
))),
}
}
fn expect_str(value: &JoinValue) -> Result<String, JoinRuntimeError> {
match value {
JoinValue::Str(s) => Ok(s.clone()),
other => Err(JoinRuntimeError::new(format!(
"Expected string, got {:?}",
other
))),
}
}
/// S-5.2: Convert Box<dyn NyashBox> from VM to JoinValue
///
/// Tries to downcast to known primitive types first (IntegerBox, BoolBox, StringBox),
/// otherwise wraps as BoxRef for future use.
fn box_to_join_value(nyash_box: Box<dyn crate::box_trait::NyashBox>) -> Result<JoinValue, JoinRuntimeError> {
use std::sync::Arc;
// Try to downcast to known primitive types first
if let Some(int_box) = nyash_box.as_any().downcast_ref::<crate::boxes::basic::IntegerBox>() {
return Ok(JoinValue::Int(int_box.value));
}
if let Some(bool_box) = nyash_box.as_any().downcast_ref::<crate::boxes::basic::BoolBox>() {
return Ok(JoinValue::Bool(bool_box.value));
}
if let Some(str_box) = nyash_box.as_any().downcast_ref::<crate::boxes::basic::StringBox>() {
return Ok(JoinValue::Str(str_box.value.clone()));
}
// Otherwise, wrap as BoxRef (for S-5.3/S-5.4 future use)
Ok(JoinValue::BoxRef(Arc::from(nyash_box)))
}

View File

@ -65,7 +65,9 @@ static box Runner {
let join_module = let join_module =
lower_skip_ws_to_joinir(&compiled.module).expect("lower_skip_ws_to_joinir failed"); lower_skip_ws_to_joinir(&compiled.module).expect("lower_skip_ws_to_joinir failed");
// S-5.2-improved: Reuse VM instance for JoinIR Runner
let join_result = run_joinir_function( let join_result = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), JoinFuncId::new(0),
&[JoinValue::Str(" abc".to_string())], &[JoinValue::Str(" abc".to_string())],
@ -126,7 +128,9 @@ static box Runner {
let join_module = lower_funcscanner_trim_to_joinir(&compiled.module) let join_module = lower_funcscanner_trim_to_joinir(&compiled.module)
.expect("lower_funcscanner_trim_to_joinir failed"); .expect("lower_funcscanner_trim_to_joinir failed");
// S-5.2-improved: Reuse VM instance for JoinIR Runner
let join_result = run_joinir_function( let join_result = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), JoinFuncId::new(0),
&[JoinValue::Str(" abc ".to_string())], &[JoinValue::Str(" abc ".to_string())],

View File

@ -60,8 +60,12 @@ fn joinir_runner_standalone_skip_ws() {
let join_module = build_skip_ws_joinir(); let join_module = build_skip_ws_joinir();
// S-5.2-improved: Create MirInterpreter instance for VM integration
let mut vm = crate::backend::mir_interpreter::MirInterpreter::new();
// Test case 1: " abc" → 3 // Test case 1: " abc" → 3
let result = run_joinir_function( let result = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), // entry function JoinFuncId::new(0), // entry function
&[JoinValue::Str(" abc".to_string())], &[JoinValue::Str(" abc".to_string())],
@ -74,6 +78,7 @@ fn joinir_runner_standalone_skip_ws() {
// Test case 2: "" → 0 // Test case 2: "" → 0
let result_empty = run_joinir_function( let result_empty = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), JoinFuncId::new(0),
&[JoinValue::Str("".to_string())], &[JoinValue::Str("".to_string())],
@ -86,6 +91,7 @@ fn joinir_runner_standalone_skip_ws() {
// Test case 3: "abc" → 0 // Test case 3: "abc" → 0
let result_no_ws = run_joinir_function( let result_no_ws = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), JoinFuncId::new(0),
&[JoinValue::Str("abc".to_string())], &[JoinValue::Str("abc".to_string())],
@ -143,8 +149,12 @@ fn joinir_runner_standalone_trim() {
let join_module = build_trim_joinir(); let join_module = build_trim_joinir();
// S-5.2-improved: Create MirInterpreter instance for VM integration
let mut vm = crate::backend::mir_interpreter::MirInterpreter::new();
// Test case 1: " abc " → "abc " (simplified - only leading whitespace) // Test case 1: " abc " → "abc " (simplified - only leading whitespace)
let result = run_joinir_function( let result = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), // entry function JoinFuncId::new(0), // entry function
&[JoinValue::Str(" abc ".to_string())], &[JoinValue::Str(" abc ".to_string())],
@ -157,6 +167,7 @@ fn joinir_runner_standalone_trim() {
// Test case 2: "" → "" // Test case 2: "" → ""
let result_empty = run_joinir_function( let result_empty = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), JoinFuncId::new(0),
&[JoinValue::Str("".to_string())], &[JoinValue::Str("".to_string())],
@ -169,6 +180,7 @@ fn joinir_runner_standalone_trim() {
// Test case 3: "abc" → "abc" // Test case 3: "abc" → "abc"
let result_no_ws = run_joinir_function( let result_no_ws = run_joinir_function(
&mut vm,
&join_module, &join_module,
JoinFuncId::new(0), JoinFuncId::new(0),
&[JoinValue::Str("abc".to_string())], &[JoinValue::Str("abc".to_string())],