docs: Add field visibility analysis and MIR BoxCall documentation
## Field Visibility Analysis Results
- Confirmed init{} fields are **public** in current Nyash implementation
- No access modifier (private/public) system currently implemented
- All fields accessible via me.fieldname syntax
- Documented findings for future reference
## MIR Documentation Enhancements
- Created comprehensive mir-dumper-guide.md for reading MIR dumps
- Enhanced mir-26-specification.md with BoxCall vs regular Call examples
- Added clear identification patterns:
* BoxCall: `call %value.method(args)` (plugins/builtins)
* Regular Call: `call %func(%me, args)` (user-defined boxes)
## VM Backend BoxRef Handling Improvements
- Fixed BoxRef method dispatch using share_box() instead of clone_box()
- Prevents unintended constructor calls during method resolution
- Maintains proper instance identity throughout VM execution
## MIR Builder User-Defined Box Tracking
- Added user_defined_boxes HashSet to track declared user boxes
- Improved method lowering decisions for user-defined vs builtin boxes
- Enhanced AST→MIR conversion accuracy for method calls
## Plugin Tester Lifecycle Enhancements
- Added comprehensive FileBox lifecycle testing (open/write/close)
- Enhanced cloneSelf() and copyFrom() testing with proper Handle parsing
- Added TLV encoding helpers for strings and bytes
- Improved error reporting and step-by-step validation
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -88,6 +88,21 @@ This document specifies the official 26-instruction set for Nyash MIR (Machine I
|
|||||||
```
|
```
|
||||||
Effect: context-dependent
|
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
|
13. **ExternCall** - Call external function
|
||||||
```mir
|
```mir
|
||||||
%dst = extern_call "interface.method"(%arg1, %arg2, ...)
|
%dst = extern_call "interface.method"(%arg1, %arg2, ...)
|
||||||
|
|||||||
59
docs/reference/mir-dumper-guide.md
Normal file
59
docs/reference/mir-dumper-guide.md
Normal file
@ -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ダンプを見る際は、この形式の違いに注目することで、どのタイプのメソッド呼び出しかを判断できます。
|
||||||
@ -90,8 +90,8 @@ impl VMValue {
|
|||||||
VMValue::String(s) => Box::new(StringBox::new(s)),
|
VMValue::String(s) => Box::new(StringBox::new(s)),
|
||||||
VMValue::Future(f) => Box::new(f.clone()),
|
VMValue::Future(f) => Box::new(f.clone()),
|
||||||
VMValue::Void => Box::new(VoidBox::new()),
|
VMValue::Void => Box::new(VoidBox::new()),
|
||||||
// Phase 9.78a: BoxRef returns cloned Box
|
// BoxRef returns a shared handle (do NOT birth a new instance)
|
||||||
VMValue::BoxRef(arc_box) => arc_box.clone_box(),
|
VMValue::BoxRef(arc_box) => arc_box.share_box(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,7 +504,8 @@ impl VM {
|
|||||||
|
|
||||||
// Handle BoxRef for proper method dispatch
|
// Handle BoxRef for proper method dispatch
|
||||||
let box_nyash = match &box_vm_value {
|
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(),
|
_ => box_vm_value.to_nyash_box(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -607,8 +608,8 @@ impl VM {
|
|||||||
};
|
};
|
||||||
match created {
|
match created {
|
||||||
Ok(b) => {
|
Ok(b) => {
|
||||||
// Register for scope-based finalization (clone for registration)
|
// Register for scope-based finalization (share; keep same instance)
|
||||||
let reg_arc = std::sync::Arc::from(b.clone_box());
|
let reg_arc = std::sync::Arc::from(b.share_box());
|
||||||
self.scope_tracker.register_box(reg_arc);
|
self.scope_tracker.register_box(reg_arc);
|
||||||
// Store value in VM
|
// Store value in VM
|
||||||
self.set_value(*dst, VMValue::from_nyash_box(b));
|
self.set_value(*dst, VMValue::from_nyash_box(b));
|
||||||
|
|||||||
@ -11,6 +11,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::ast::{ASTNode, LiteralValue, BinaryOperator};
|
use crate::ast::{ASTNode, LiteralValue, BinaryOperator};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
/// MIR builder for converting AST to SSA form
|
/// MIR builder for converting AST to SSA form
|
||||||
pub struct MirBuilder {
|
pub struct MirBuilder {
|
||||||
@ -39,6 +40,9 @@ pub struct MirBuilder {
|
|||||||
/// Origin tracking for simple optimizations (e.g., object.method after new)
|
/// 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
|
/// Maps a ValueId to the class name if it was produced by NewBox of that class
|
||||||
pub(super) value_origin_newbox: HashMap<ValueId, String>,
|
pub(super) value_origin_newbox: HashMap<ValueId, String>,
|
||||||
|
|
||||||
|
/// Names of user-defined boxes declared in the current module
|
||||||
|
pub(super) user_defined_boxes: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MirBuilder {
|
impl MirBuilder {
|
||||||
@ -53,6 +57,7 @@ impl MirBuilder {
|
|||||||
variable_map: HashMap::new(),
|
variable_map: HashMap::new(),
|
||||||
pending_phis: Vec::new(),
|
pending_phis: Vec::new(),
|
||||||
value_origin_newbox: HashMap::new(),
|
value_origin_newbox: HashMap::new(),
|
||||||
|
user_defined_boxes: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,6 +304,8 @@ impl MirBuilder {
|
|||||||
self.build_static_main_box(methods.clone())
|
self.build_static_main_box(methods.clone())
|
||||||
} else {
|
} else {
|
||||||
// Support user-defined boxes - handle as statement, return void
|
// 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())?;
|
self.build_box_declaration(name.clone(), methods.clone(), fields.clone())?;
|
||||||
|
|
||||||
// Phase 2: Lower constructors (birth/N) into MIR functions
|
// Phase 2: Lower constructors (birth/N) into MIR functions
|
||||||
@ -993,7 +1000,8 @@ impl MirBuilder {
|
|||||||
if let ASTNode::New { class, .. } = object {
|
if let ASTNode::New { class, .. } = object {
|
||||||
// Build function name and only lower to Call if the function exists (user-defined)
|
// 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 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 {
|
if can_lower {
|
||||||
let func_val = self.value_gen.next();
|
let func_val = self.value_gen.next();
|
||||||
self.emit_instruction(MirInstruction::Const { dst: func_val, value: ConstValue::String(func_name) })?;
|
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 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() {
|
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 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 {
|
if can_lower {
|
||||||
let func_val = self.value_gen.next();
|
let func_val = self.value_gen.next();
|
||||||
self.emit_instruction(MirInstruction::Const { dst: func_val, value: ConstValue::String(func_name) })?;
|
self.emit_instruction(MirInstruction::Const { dst: func_val, value: ConstValue::String(func_name) })?;
|
||||||
|
|||||||
@ -100,6 +100,33 @@ fn tlv_encode_one_handle(type_id: u32, instance_id: u32) -> Vec<u8> {
|
|||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tlv_encode_two_strings(a: &str, b: &str) -> Vec<u8> {
|
||||||
|
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<u8> {
|
||||||
|
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<u32, String> {
|
fn tlv_decode_u32(data: &[u8]) -> Result<u32, String> {
|
||||||
if data.len() >= 4 {
|
if data.len() >= 4 {
|
||||||
Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]]))
|
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);
|
println!("{}: Birth successful, instance_id = {}", "✓".green(), instance_id);
|
||||||
|
|
||||||
// Optional: If method 'copyFrom' exists, create another instance and pass it as Box arg
|
// 1b. Open the first instance (if open exists)
|
||||||
if box_config.methods.contains_key("copyFrom") {
|
if let Some(open_def) = box_config.methods.get("open") {
|
||||||
println!("\n{}", "1b. Testing method with Box arg: copyFrom(other) ...".cyan());
|
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
|
// 1c. Write some bytes (if write exists)
|
||||||
let args2 = tlv_encode_empty();
|
if let Some(write_def) = box_config.methods.get("write") {
|
||||||
let mut out2 = vec![0u8; 1024];
|
println!("\n{}", "1c. Writing to src FileBox ...".cyan());
|
||||||
let mut out2_len = out2.len();
|
let args_write = tlv_encode_bytes(b"hello nyash");
|
||||||
let rc2 = invoke_fn(
|
let mut out = vec![0u8; 1024];
|
||||||
box_config.type_id,
|
let mut out_len = out.len();
|
||||||
0,
|
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);
|
||||||
0,
|
if rc == 0 { println!("{}: write ok", "✓".green()); } else { eprintln!("{}: write rc={}", "WARN".yellow(), rc); }
|
||||||
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());
|
// 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); }
|
||||||
}
|
}
|
||||||
} else {
|
if dst_id.is_none() {
|
||||||
eprintln!("{}: Failed to create other instance for copyFrom (rc={})", "WARN".yellow(), rc2);
|
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
|
// Optional: If method 'cloneSelf' exists, call it and verify Handle return
|
||||||
|
|||||||
Reference in New Issue
Block a user