Files
hakorune/docs/reference/plugin-system/vm-plugin-integration.md
Moe Charm cc2a820af7 feat(plugin): Fix plugin BoxRef return and Box argument support
- Fixed deadlock in FileBox plugin copyFrom implementation (single lock)
- Added TLV Handle (tag=8) parsing in calls.rs for returned BoxRefs
- Improved plugin loader with config path consistency and detailed logging
- Fixed loader routing for proper Handle type_id/fini_method_id resolution
- Added detailed logging for TLV encoding/decoding in plugin_loader_v2

Test docs/examples/plugin_boxref_return.nyash now works correctly:
- cloneSelf() returns FileBox Handle properly
- copyFrom(Box) accepts plugin Box arguments
- Both FileBox instances close and fini correctly

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 00:41:26 +09:00

504 lines
16 KiB
Markdown
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.

# VM Plugin Integration仕様書
## 🎯 概要
NyashのVMバックエンドとプラグインシステムBID-FFI v1の統合に関する技術仕様。Everything is Box哲学に基づき、**すべてのBox型ビルトイン、ユーザー定義、プラグイン**をVMで統一的に扱えるようにする。
## ⚠️ **現在のVM実装の重大な問題**
1. **ユーザー定義Box未対応** - NewBoxで文字列を返すだけ
2. **birth/finiライフサイクル欠落** - コンストラクタ・デストラクタが呼ばれない
3. **メソッド呼び出しハードコード** - 新メソッド追加が困難
これらを解決し、インタープリターと同等の統一処理を実現する。
## 🏗️ アーキテクチャ
### 統一Box管理モデル
```
┌─────────────────────────────────────────────────┐
│ Nyash VM │
├─────────────────────────────────────────────────┤
│ VMValue │
│ ├─ Integer(i64) ← 基本型は直接保持 │
│ ├─ String(String) │
│ ├─ Bool(bool) │
│ └─ BoxRef(Arc<dyn NyashBox>) ← 複雑型全般 │
├─────────────────────────────────────────────────┤
│ 統一Box管理層 │
│ ├─ BoxFactory : 統一Box作成 │
│ ├─ ScopeTracker : ライフサイクル管理 │
│ └─ MethodDispatcher : 統一メソッド呼び出し │
├─────────────────────────────────────────────────┤
│ 変換レイヤー │
│ ├─ to_nyash_box() : VMValue → Box │
│ └─ from_nyash_box() : Box → VMValue │
├─────────────────────────────────────────────────┤
│ プラグインローダー (PluginLoaderV2) │
│ └─ BID-FFI v1プロトコルで通信 │
└─────────────────────────────────────────────────┘
```
### VM構造体の完全形
```rust
pub struct VM {
// 既存フィールド
registers: HashMap<RegisterId, VMValue>,
memory: HashMap<MemoryLocation, VMValue>,
// 統一Box管理新規
box_factory: Arc<BoxFactory>, // 統一Box作成
plugin_loader: Option<Arc<PluginLoaderV2>>, // プラグイン
scope_tracker: ScopeTracker, // finiライフサイクル
box_declarations: Arc<RwLock<HashMap<String, BoxDeclaration>>>, // ユーザー定義Box
}
```
## 📊 VMValue拡張仕様
### 型定義
```rust
pub enum VMValue {
// 基本型(既存)
Integer(i64),
Float(f64),
Bool(bool),
String(String),
Future(FutureBox),
Void,
// 拡張型(新規)
BoxRef(Arc<dyn NyashBox>),
}
```
### 変換規則
#### NyashBox → VMValue
1. **基本型の最適化**
- IntegerBox → VMValue::Integer値を直接保持
- StringBox → VMValue::String値を直接保持
- BoolBox → VMValue::Bool値を直接保持
2. **複雑型の参照保持**
- PluginBoxV2 → VMValue::BoxRef
- ユーザー定義Box → VMValue::BoxRef
- その他のBox → VMValue::BoxRef
#### VMValue → NyashBox
1. **基本型の再Box化**
- VMValue::Integer → IntegerBox::new()
- VMValue::String → StringBox::new()
- VMValue::Bool → BoolBox::new()
2. **参照型のクローン**
- VMValue::BoxRef → Arc::clone_box()
## 🔄 MIR命令の処理
### NewBox命令の統一実装
```rust
MirInstruction::NewBox { dst, box_type, args } => {
// 🌟 統一Box作成プロセス
// Step 1: 引数を評価してNyashBoxに変換
let nyash_args: Vec<Box<dyn NyashBox>> = args.iter()
.map(|id| self.get_value(*id)?.to_nyash_box())
.collect::<Result<Vec<_>, _>>()?;
// Step 2: BoxFactory経由で統一作成
let new_box = self.box_factory.create_box(box_type, &nyash_args)?;
// Step 3: birth実行ユーザー定義Boxの場合
if let Some(instance) = new_box.as_any().downcast_ref::<InstanceBox>() {
// birthコンストラクタを検索
let birth_key = format!("birth/{}", args.len());
if let Some(box_decl) = self.box_declarations.read().unwrap().get(&instance.class_name) {
if let Some(constructor) = box_decl.constructors.get(&birth_key) {
// birthメソッドを実行
self.push_scope(); // 新しいスコープ
self.set_variable("me", new_box.clone()); // me をバインド
// コンストラクタ本体を実行
let result = self.execute_constructor(constructor, nyash_args)?;
self.pop_scope(); // スコープ終了
}
}
}
// Step 4: プラグインBoxのbirth実行
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
if new_box.as_any().downcast_ref::<PluginBoxV2>().is_some() {
// プラグインのbirthは既にcreate_box内で実行済み
}
// Step 5: スコープ追跡に登録fini用
self.scope_tracker.register_box(new_box.clone());
// Step 6: VMValueに変換して格納
let vm_value = VMValue::from_nyash_box(new_box);
self.set_value(*dst, vm_value);
}
```
### BoxCall命令の統一処理
```rust
MirInstruction::BoxCall { dst, box_val, method, args, effects } => {
let box_vm_value = self.get_value(*box_val)?;
// 統一的なメソッド呼び出し
let result = match &box_vm_value {
// 基本型の最適化パス
VMValue::String(s) => {
self.call_string_method_optimized(s, method, args)?
},
VMValue::Integer(i) => {
self.call_integer_method_optimized(i, method, args)?
},
// BoxRef経由の汎用パス
VMValue::BoxRef(arc_box) => {
let nyash_args = convert_args_to_nyash(args);
self.call_box_method_generic(arc_box.as_ref(), method, nyash_args)?
},
_ => return Err(VMError::TypeError("Not a box type"))
};
if let Some(dst_id) = dst {
self.set_value(*dst_id, result);
}
}
```
### ExternCall命令の実装
```rust
MirInstruction::ExternCall { dst, iface_name, method_name, args, effects } => {
match (iface_name.as_str(), method_name.as_str()) {
// プラグインBox作成
("plugin", "new") => {
let box_type = self.get_value(args[0])?.to_string();
let ctor_args = self.convert_args_to_nyash(&args[1..])?;
if let Some(loader) = &self.plugin_loader {
let plugin_box = loader.create_box(&box_type, ctor_args)?;
let vm_value = VMValue::from_nyash_box(plugin_box);
if let Some(dst_id) = dst {
self.set_value(*dst_id, vm_value);
}
}
},
// 既存のconsole.log等
("env.console", "log") => {
// 既存の処理
},
_ => {
println!("ExternCall stub: {}.{}", iface_name, method_name);
}
}
}
```
## 🔧 メモリ管理
### 参照カウント管理
1. **BoxRefの作成時**
- Arc::fromでBox<dyn NyashBox>をArc<dyn NyashBox>に変換
- 参照カウント = 1
2. **BoxRefのクローン時**
- Arc::cloneで参照カウント増加
- 軽量なポインタコピー
3. **BoxRefの破棄時**
- 参照カウント減少
- 0になったら自動解放
### スコープとライフタイム
```rust
// VMのスコープ管理
impl VM {
fn exit_scope(&mut self) {
// BoxRefを含むレジスタがクリアされると
// 参照カウントが自動的に減少
self.registers.clear();
}
}
```
## 📈 パフォーマンス最適化
### 基本型の直接処理
```rust
// 最適化されたStringメソッド呼び出し
fn call_string_method_optimized(&self, s: &str, method: &str, args: &[ValueId])
-> Result<VMValue, VMError> {
match method {
"length" => Ok(VMValue::Integer(s.len() as i64)),
"substring" => {
// 引数を直接整数として取得Box化を回避
let start = self.get_value(args[0])?.to_i64()?;
let end = self.get_value(args[1])?.to_i64()?;
Ok(VMValue::String(s[start..end].to_string()))
},
_ => {
// 未知のメソッドは汎用パスへ
let string_box = Box::new(StringBox::new(s));
self.call_box_method_generic(&*string_box, method, args)
}
}
}
```
### プラグイン呼び出しの最適化
1. **メソッドIDキャッシュ**
- 頻繁に呼ばれるメソッドのIDをキャッシュ
- 文字列比較を回避
2. **TLV変換の遅延評価**
- 必要になるまでTLV変換を遅延
- 基本型は直接渡す
## 🧪 テスト戦略
### 単体テスト
```rust
#[test]
fn test_vm_plugin_box_creation() {
let plugin_loader = create_test_plugin_loader();
let mut vm = VM::new_with_plugins(plugin_loader);
// FileBoxの作成
let result = vm.execute_extern_call(
"plugin", "new",
vec!["FileBox", "test.txt"]
);
assert!(matches!(result, Ok(VMValue::BoxRef(_))));
}
```
### 統合テスト
```nyash
// VMで実行されるNyashコード
local file = new FileBox("output.txt")
file.write("VM Plugin Test")
local content = file.read()
assert(content == "VM Plugin Test")
```
### パフォーマンステスト
```rust
#[bench]
fn bench_plugin_method_call(b: &mut Bencher) {
let vm = setup_vm_with_plugins();
let file_box = create_file_box(&vm);
b.iter(|| {
vm.call_box_method(&file_box, "write", &["test"])
});
}
```
## 🚨 エラーハンドリング
### プラグイン関連エラー
```rust
pub enum VMError {
// 既存のエラー
TypeError(String),
RuntimeError(String),
// プラグイン関連(新規)
PluginNotFound(String),
PluginMethodError {
plugin: String,
method: String,
error: String
},
PluginInitError(String),
}
```
### エラー伝播
```rust
// プラグインエラーをVMエラーに変換
impl From<PluginError> for VMError {
fn from(err: PluginError) -> Self {
match err {
PluginError::MethodNotFound(m) => {
VMError::PluginMethodError {
plugin: "unknown".to_string(),
method: m,
error: "Method not found".to_string()
}
},
// ... 他のエラー変換
}
}
}
```
## 📊 メトリクスとモニタリング
### パフォーマンスメトリクス
- プラグイン呼び出し回数
- 平均呼び出し時間
- TLV変換オーバーヘッド
- メモリ使用量
### デバッグ情報
```rust
// デバッグモードでの詳細ログ
if cfg!(debug_assertions) {
eprintln!("VM: Calling plugin method {}.{}", box_type, method);
eprintln!("VM: Args: {:?}", args);
eprintln!("VM: Result: {:?}", result);
}
```
## 🔄 ライフサイクル管理
### スコープ管理とfini呼び出し
```rust
pub struct ScopeTracker {
scopes: Vec<Scope>,
}
pub struct Scope {
boxes: Vec<(u64, Arc<dyn NyashBox>)>, // (id, box)
variables: HashMap<String, VMValue>, // ローカル変数
}
impl VM {
/// スコープ開始
fn push_scope(&mut self) {
self.scope_tracker.scopes.push(Scope::new());
}
/// スコープ終了時の自動fini呼び出し
fn pop_scope(&mut self) -> Result<(), VMError> {
if let Some(scope) = self.scope_tracker.scopes.pop() {
// 逆順でfiniを呼ぶ作成順と逆
for (_, box_ref) in scope.boxes.iter().rev() {
self.call_fini_if_needed(box_ref)?;
}
}
Ok(())
}
/// 統一fini呼び出し
fn call_fini_if_needed(&mut self, box_ref: &Arc<dyn NyashBox>) -> Result<(), VMError> {
match box_ref.type_name() {
// ユーザー定義Box
name if self.box_declarations.read().unwrap().contains_key(name) => {
if let Some(instance) = box_ref.as_any().downcast_ref::<InstanceBox>() {
// finiメソッドが定義されているか確認
if let Some(box_decl) = self.box_declarations.read().unwrap().get(name) {
if let Some(fini_method) = box_decl.methods.get("fini") {
// finiを実行
self.set_variable("me", box_ref.clone_box());
self.execute_method(fini_method.clone())?;
}
}
}
},
// プラグインBox
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
_ if box_ref.as_any().downcast_ref::<PluginBoxV2>().is_some() => {
if let Some(plugin) = box_ref.as_any().downcast_ref::<PluginBoxV2>() {
plugin.call_fini();
}
},
// ビルトインBox将来finiサポート予定
_ => {
// 現在ビルトインBoxはfiniなし
// 将来的にはStringBox等もfini対応
}
}
Ok(())
}
}
```
### ライフサイクルの完全性
```nyash
// 🌟 すべてのBoxが同じライフサイクル
{ // スコープ開始
local str = new StringBox("hello") // birth引数1つ
local user = new UserBox("Alice", 25) // birth引数2つ
local file = new FileBox("test.txt") // birth引数1つ
// 使用
str.length()
user.greet()
file.write("data")
} // スコープ終了 → 自動的にfini呼び出し
// file.fini() → user.fini() → str.fini() の順
```
## 🎯 統一の利点
### 1. **シンプルな実装**
- すべてのBox型が同じコードパスを通る
- 特殊ケースの削減
- バグの温床排除
### 2. **拡張性**
- 新しいBox型追加が容易
- プラグインも同じ扱い
- 将来の機能追加も簡単
### 3. **パフォーマンス**
- 基本型は最適化パス維持
- 必要時のみBoxRef使用
- メソッドディスパッチの効率化
---
**最終更新**: 2025-08-21
**関連文書**:
- [BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md)
- [Phase 9.78a VM Plugin Integration](../../予定/native-plan/issues/phase_9_78a_vm_plugin_integration.md)
- [Phase 9.78a 深層分析](../../予定/native-plan/issues/phase_9_78a_vm_plugin_integration_deep_analysis.md)
- [nyash.toml v2.1: BoxRef仕様](../plugin-system/nyash-toml-v2_1-spec.md)
### 付録: 引数エンコードv2.1 追加)
- TLVタグ: 1=Bool, 2=I32, 3=I64, 4=F32, 5=F64, 6=String, 7=Bytes, 8=Handle(BoxRef)
- BoxRef payload(tag=8): `type_id:u32` + `instance_id:u32`LE, 8バイト
- `nyash.toml``args``{ kind="box", category="plugin" }` を指定したとき、Loaderは `tag=8` を使用
### 返り値v2.2
- プラグインが `tag=8` を返した場合、Loaderは `type_id` からBox型名を逆引きし `PluginBoxV2` を構築
- 同一ライブラリでなくてもOK構成ファイル全体から探索