diff --git a/docs/reference/execution-backend/mir-26-specification.md b/docs/reference/execution-backend/mir-26-specification.md index 4e9545fb..5c06d477 100644 --- a/docs/reference/execution-backend/mir-26-specification.md +++ b/docs/reference/execution-backend/mir-26-specification.md @@ -87,6 +87,21 @@ This document specifies the official 26-instruction set for Nyash MIR (Machine I %dst = call %box.method(%arg1, %arg2, ...) ``` Effect: context-dependent + + **重要な識別方法:** + - BoxCall形式: `call %値.メソッド名(引数)` - 値(%7など)に対してメソッドを直接呼ぶ + - 通常のCall形式: `call %関数値(%me, 引数)` - 関数値を呼び、第1引数にmeを渡す + + 例: + ```mir + ; BoxCall(プラグイン/ビルトインBoxのメソッド) + %17 = call %7.open(%14, %16) + %22 = call %7.write(%21) + + ; 通常のCall(ユーザー定義Boxのメソッド) + %func = const "UserBox.method/2" + %result = call %func(%me, %arg1) + ``` 13. **ExternCall** - Call external function ```mir diff --git a/docs/reference/mir-dumper-guide.md b/docs/reference/mir-dumper-guide.md new file mode 100644 index 00000000..e8e3ee0c --- /dev/null +++ b/docs/reference/mir-dumper-guide.md @@ -0,0 +1,59 @@ +# MIR Dumper Output Guide + +MIRダンプ出力を正しく読み解くためのガイドです。 + +## BoxCall vs 通常のCall の見分け方 + +### BoxCall形式(プラグイン/ビルトインBoxのメソッド) +```mir +%8 = call %7.cloneSelf() +%17 = call %7.open(%14, %16) +%22 = call %7.write(%21) +%23 = call %8.copyFrom(%7) +``` + +**特徴:** +- `call %値.メソッド名(引数)` の形式 +- 値(%7, %8など)に対して直接メソッドを呼ぶ +- プラグインBoxやビルトインBoxで使用される + +### 通常のCall形式(ユーザー定義Boxのメソッド) +```mir +%func = const "UserBox.calculate/2" +%result = call %func(%me, %arg1) +``` + +**特徴:** +- 事前に `const "クラス名.メソッド名/引数数"` で関数値を取得 +- `call %関数値(%me, 引数...)` の形式で呼び出し +- 第1引数は常に `%me`(self相当) + +## 実例での比較 + +### plugin_boxref_return.nyashのMIRダンプ +```mir +11: %7 = new FileBox() +12: call %7.birth() +13: %8 = call %7.cloneSelf() ← BoxCall(プラグインメソッド) +26: %17 = call %7.open(%14, %16) ← BoxCall(プラグインメソッド) +33: %22 = call %7.write(%21) ← BoxCall(プラグインメソッド) +34: %23 = call %8.copyFrom(%7) ← BoxCall(プラグインメソッド) +``` + +これらはすべてBoxCall形式で、プラグインのFileBoxメソッドを直接呼び出しています。 + +### ユーザー定義Boxの場合(仮想例) +```mir +; ユーザー定義Box "Calculator" のメソッド呼び出し +%calc_func = const "Calculator.add/2" +%result = call %calc_func(%me, %10, %20) +``` + +この場合は、MIR関数として事前にlower済みのメソッドを呼び出しています。 + +## まとめ + +- **`call %値.メソッド()`** → BoxCall(プラグイン/ビルトイン) +- **`call %関数値(%me, ...)`** → 通常のCall(ユーザー定義Box) + +MIRダンプを見る際は、この形式の違いに注目することで、どのタイプのメソッド呼び出しかを判断できます。 \ No newline at end of file diff --git a/src/backend/vm.rs b/src/backend/vm.rs index f93be386..6dbb02c5 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -90,8 +90,8 @@ impl VMValue { VMValue::String(s) => Box::new(StringBox::new(s)), VMValue::Future(f) => Box::new(f.clone()), VMValue::Void => Box::new(VoidBox::new()), - // Phase 9.78a: BoxRef returns cloned Box - VMValue::BoxRef(arc_box) => arc_box.clone_box(), + // BoxRef returns a shared handle (do NOT birth a new instance) + VMValue::BoxRef(arc_box) => arc_box.share_box(), } } @@ -504,7 +504,8 @@ impl VM { // Handle BoxRef for proper method dispatch let box_nyash = match &box_vm_value { - VMValue::BoxRef(arc_box) => arc_box.clone_box(), + // Use shared handle to avoid unintended constructor calls + VMValue::BoxRef(arc_box) => arc_box.share_box(), _ => box_vm_value.to_nyash_box(), }; @@ -607,8 +608,8 @@ impl VM { }; match created { Ok(b) => { - // Register for scope-based finalization (clone for registration) - let reg_arc = std::sync::Arc::from(b.clone_box()); + // Register for scope-based finalization (share; keep same instance) + let reg_arc = std::sync::Arc::from(b.share_box()); self.scope_tracker.register_box(reg_arc); // Store value in VM self.set_value(*dst, VMValue::from_nyash_box(b)); diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 8eeee88f..d5210b94 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -11,6 +11,7 @@ use super::{ }; use crate::ast::{ASTNode, LiteralValue, BinaryOperator}; use std::collections::HashMap; +use std::collections::HashSet; /// MIR builder for converting AST to SSA form pub struct MirBuilder { @@ -39,6 +40,9 @@ pub struct MirBuilder { /// Origin tracking for simple optimizations (e.g., object.method after new) /// Maps a ValueId to the class name if it was produced by NewBox of that class pub(super) value_origin_newbox: HashMap, + + /// Names of user-defined boxes declared in the current module + pub(super) user_defined_boxes: HashSet, } impl MirBuilder { @@ -53,6 +57,7 @@ impl MirBuilder { variable_map: HashMap::new(), pending_phis: Vec::new(), value_origin_newbox: HashMap::new(), + user_defined_boxes: HashSet::new(), } } @@ -299,6 +304,8 @@ impl MirBuilder { self.build_static_main_box(methods.clone()) } else { // Support user-defined boxes - handle as statement, return void + // Track as user-defined (eligible for method lowering) + self.user_defined_boxes.insert(name.clone()); self.build_box_declaration(name.clone(), methods.clone(), fields.clone())?; // Phase 2: Lower constructors (birth/N) into MIR functions @@ -993,7 +1000,8 @@ impl MirBuilder { if let ASTNode::New { class, .. } = object { // Build function name and only lower to Call if the function exists (user-defined) let func_name = format!("{}.{}{}", class, method, format!("/{}", arg_values.len())); - let can_lower = if let Some(ref module) = self.current_module { module.functions.contains_key(&func_name) } else { false }; + let can_lower = self.user_defined_boxes.contains(&class) + && if let Some(ref module) = self.current_module { module.functions.contains_key(&func_name) } else { false }; if can_lower { let func_val = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: func_val, value: ConstValue::String(func_name) })?; @@ -1013,7 +1021,8 @@ impl MirBuilder { // If the object originates from a NewBox in this function, we can lower to Call as well if let Some(class_name) = self.value_origin_newbox.get(&object_value).cloned() { let func_name = format!("{}.{}{}", class_name, method, format!("/{}", arg_values.len())); - let can_lower = if let Some(ref module) = self.current_module { module.functions.contains_key(&func_name) } else { false }; + let can_lower = self.user_defined_boxes.contains(&class_name) + && if let Some(ref module) = self.current_module { module.functions.contains_key(&func_name) } else { false }; if can_lower { let func_val = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: func_val, value: ConstValue::String(func_name) })?; diff --git a/tools/plugin-tester/src/main.rs b/tools/plugin-tester/src/main.rs index 53ed332b..4c81ba62 100644 --- a/tools/plugin-tester/src/main.rs +++ b/tools/plugin-tester/src/main.rs @@ -100,6 +100,33 @@ fn tlv_encode_one_handle(type_id: u32, instance_id: u32) -> Vec { buf } +fn tlv_encode_two_strings(a: &str, b: &str) -> Vec { + let ab = a.as_bytes(); + let bb = b.as_bytes(); + let mut buf = Vec::with_capacity(4 + 2 * (4 + ab.len().min(u16::MAX as usize))); + buf.extend_from_slice(&1u16.to_le_bytes()); // ver + buf.extend_from_slice(&2u16.to_le_bytes()); // argc=2 + // first string + buf.push(6u8); buf.push(0u8); + buf.extend_from_slice(&((ab.len().min(u16::MAX as usize) as u16).to_le_bytes())); + buf.extend_from_slice(ab); + // second string + buf.push(6u8); buf.push(0u8); + buf.extend_from_slice(&((bb.len().min(u16::MAX as usize) as u16).to_le_bytes())); + buf.extend_from_slice(bb); + buf +} + +fn tlv_encode_bytes(data: &[u8]) -> Vec { + let mut buf = Vec::with_capacity(4 + 4 + data.len()); + buf.extend_from_slice(&1u16.to_le_bytes()); // ver + buf.extend_from_slice(&1u16.to_le_bytes()); // argc=1 + buf.push(7u8); buf.push(0u8); + buf.extend_from_slice(&((data.len().min(u16::MAX as usize) as u16).to_le_bytes())); + buf.extend_from_slice(data); + buf +} + fn tlv_decode_u32(data: &[u8]) -> Result { if data.len() >= 4 { Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]])) @@ -330,50 +357,73 @@ fn test_lifecycle_v2(config_path: &PathBuf, box_type: &str) { println!("{}: Birth successful, instance_id = {}", "✓".green(), instance_id); - // Optional: If method 'copyFrom' exists, create another instance and pass it as Box arg - if box_config.methods.contains_key("copyFrom") { - println!("\n{}", "1b. Testing method with Box arg: copyFrom(other) ...".cyan()); + // 1b. Open the first instance (if open exists) + if let Some(open_def) = box_config.methods.get("open") { + println!("\n{}", "1b. Opening src FileBox (id=instance_id) ...".cyan()); + let args_open = tlv_encode_two_strings("test_lifecycle.txt", "w"); + let mut out = vec![0u8; 1024]; + let mut out_len = out.len(); + let rc = invoke_fn(box_config.type_id, open_def.method_id, instance_id, args_open.as_ptr(), args_open.len(), out.as_mut_ptr(), &mut out_len); + if rc == 0 { println!("{}: open ok", "✓".green()); } else { eprintln!("{}: open rc={}", "WARN".yellow(), rc); } + } - // Birth another instance to serve as argument handle - let args2 = tlv_encode_empty(); - let mut out2 = vec![0u8; 1024]; - let mut out2_len = out2.len(); - let rc2 = invoke_fn( - box_config.type_id, - 0, - 0, - args2.as_ptr(), - args2.len(), - out2.as_mut_ptr(), - &mut out2_len, - ); - if rc2 == 0 { - if let Ok(other_id) = tlv_decode_u32(&out2[..out2_len]) { - // Encode one Box handle as argument - let arg_buf = tlv_encode_one_handle(box_config.type_id, other_id); - let mut ret = vec![0u8; 1024]; - let mut ret_len = ret.len(); - let method_id = box_config.methods.get("copyFrom").unwrap().method_id; - let rc_call = invoke_fn( - box_config.type_id, - method_id, - instance_id, - arg_buf.as_ptr(), - arg_buf.len(), - ret.as_mut_ptr(), - &mut ret_len, - ); - if rc_call == 0 { - println!("{}: copyFrom call succeeded (arg=BoxRef)", "✓".green()); - } else { - eprintln!("{}: copyFrom call failed (rc={})", "WARN".yellow(), rc_call); - } - } else { - eprintln!("{}: Failed to decode other instance_id", "WARN".yellow()); - } - } else { - eprintln!("{}: Failed to create other instance for copyFrom (rc={})", "WARN".yellow(), rc2); + // 1c. Write some bytes (if write exists) + if let Some(write_def) = box_config.methods.get("write") { + println!("\n{}", "1c. Writing to src FileBox ...".cyan()); + let args_write = tlv_encode_bytes(b"hello nyash"); + let mut out = vec![0u8; 1024]; + let mut out_len = out.len(); + let rc = invoke_fn(box_config.type_id, write_def.method_id, instance_id, args_write.as_ptr(), args_write.len(), out.as_mut_ptr(), &mut out_len); + if rc == 0 { println!("{}: write ok", "✓".green()); } else { eprintln!("{}: write rc={}", "WARN".yellow(), rc); } + } + + // 1d. Create destination instance via cloneSelf() if available; else birth + let mut dst_id = None; + if let Some(clone_def) = box_config.methods.get("cloneSelf") { + println!("\n{}", "1d. Cloning via cloneSelf() ...".cyan()); + let args0 = tlv_encode_empty(); + let mut out = vec![0u8; 1024]; + let mut out_len = out.len(); + let rc = invoke_fn(box_config.type_id, clone_def.method_id, instance_id, args0.as_ptr(), args0.len(), out.as_mut_ptr(), &mut out_len); + if rc == 0 && out_len >= 16 && out[4] == 8 { // Handle + // parse handle payload at bytes 8..16 + let t = u32::from_le_bytes([out[8],out[9],out[10],out[11]]); + let i = u32::from_le_bytes([out[12],out[13],out[14],out[15]]); + if t == box_config.type_id { dst_id = Some(i); println!("{}: cloneSelf returned id={}", "✓".green(), i); } + } else { eprintln!("{}: cloneSelf rc={}", "WARN".yellow(), rc); } + } + if dst_id.is_none() { + println!("\n{}", "1d. Cloning fallback via birth() ...".cyan()); + let args0 = tlv_encode_empty(); + let mut out = vec![0u8; 1024]; + let mut out_len = out.len(); + let rc = invoke_fn(box_config.type_id, 0, 0, args0.as_ptr(), args0.len(), out.as_mut_ptr(), &mut out_len); + if rc == 0 { dst_id = tlv_decode_u32(&out[..out_len]).ok(); } + if let Some(i) = dst_id { println!("{}: birth dst id={}", "✓".green(), i); } else { eprintln!("{}: birth dst failed rc={}", "WARN".yellow(), rc); } + } + + // 1e. copyFrom(dst <- src) + if let (Some(copy_def), Some(dst)) = (box_config.methods.get("copyFrom"), dst_id) { + println!("\n{}", "1e. Testing copyFrom(dst <- src) ...".cyan()); + let arg_buf = tlv_encode_one_handle(box_config.type_id, instance_id); + let mut out = vec![0u8; 1024]; + let mut out_len = out.len(); + let rc = invoke_fn(box_config.type_id, copy_def.method_id, dst, arg_buf.as_ptr(), arg_buf.len(), out.as_mut_ptr(), &mut out_len); + if rc == 0 { println!("{}: copyFrom ok", "✓".green()); } else { eprintln!("{}: copyFrom rc={}", "WARN".yellow(), rc); } + } + + // 1f. close both + if let Some(close_def) = box_config.methods.get("close") { + println!("\n{}", "1f. Closing both instances ...".cyan()); + let args0 = tlv_encode_empty(); + let mut out = vec![0u8; 64]; + let mut out_len = out.len(); + let _ = invoke_fn(box_config.type_id, close_def.method_id, instance_id, args0.as_ptr(), args0.len(), out.as_mut_ptr(), &mut out_len); + if let Some(dst) = dst_id { + out_len = out.len(); + let _ = invoke_fn(box_config.type_id, close_def.method_id, dst, args0.as_ptr(), args0.len(), out.as_mut_ptr(), &mut out_len); } + println!("{}: close done", "✓".green()); } // Optional: If method 'cloneSelf' exists, call it and verify Handle return