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:
Submodule docs/private updated: 3b8f4aa464...b9a9a17d19
@ -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,
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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: {}",
|
|
||||||
box_name
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
match method.as_str() {
|
|
||||||
"length" => {
|
|
||||||
let arg = expect_str(&read_var(locals, args[0])?)?;
|
|
||||||
// S-5.2: VM の StringBox.length() 実装を使用(hardcoded 削除)
|
|
||||||
let string_box = crate::boxes::basic::StringBox::new(arg);
|
|
||||||
let result_box = string_box.length();
|
|
||||||
let result_value = box_to_join_value(result_box)?;
|
|
||||||
let dst_var = dst.ok_or_else(|| {
|
|
||||||
JoinRuntimeError::new("length call requires destination")
|
|
||||||
})?;
|
|
||||||
locals.insert(dst_var, result_value);
|
|
||||||
}
|
|
||||||
"substring" => {
|
|
||||||
if args.len() != 3 {
|
|
||||||
return Err(JoinRuntimeError::new(
|
return Err(JoinRuntimeError::new(
|
||||||
"substring expects 3 arguments (s, start, end)",
|
"BoxCall requires at least a receiver argument",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let s = expect_str(&read_var(locals, args[0])?)?;
|
|
||||||
let start = expect_int(&read_var(locals, args[1])?)? as usize;
|
// Convert receiver to VMValue
|
||||||
let end = expect_int(&read_var(locals, args[2])?)? as usize;
|
let receiver_jv = read_var(locals, args[0])?;
|
||||||
// S-5.2: VM の StringBox.substring() 実装を使用(hardcoded 削除)
|
let receiver_vm = receiver_jv.to_vm_value();
|
||||||
let string_box = crate::boxes::basic::StringBox::new(s);
|
|
||||||
let result_box = string_box.substring(start, end);
|
// 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("substring 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
|
||||||
_ => {
|
let result_vm = vm.execute_box_call(receiver_vm, method, method_args_vm)
|
||||||
return Err(JoinRuntimeError::new(format!(
|
.map_err(|e| JoinRuntimeError::new(format!("BoxCall failed: {}", e)))?;
|
||||||
"Unsupported StringBox method: {}",
|
|
||||||
method
|
// Convert result back to JoinValue
|
||||||
)))
|
let result_jv = crate::mir::join_ir_ops::JoinValue::from_vm_value(&result_vm)?;
|
||||||
}
|
|
||||||
|
// Store result if destination is specified
|
||||||
|
if let Some(dst_var) = dst {
|
||||||
|
locals.insert(*dst_var, result_jv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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)))
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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())],
|
||||||
|
|||||||
@ -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())],
|
||||||
|
|||||||
Reference in New Issue
Block a user