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>
This commit is contained in:
87
docs/reference/plugin-system/README.md
Normal file
87
docs/reference/plugin-system/README.md
Normal file
@ -0,0 +1,87 @@
|
||||
# Nyash Plugin System Documentation
|
||||
|
||||
## 🎯 Quick Start
|
||||
|
||||
**For new developers**: Start with [BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md)
|
||||
|
||||
## 📚 Documentation Index
|
||||
|
||||
### 🟢 **Current & Accurate**
|
||||
- **[bid-ffi-v1-actual-specification.md](./bid-ffi-v1-actual-specification.md)** - **主要仕様書**
|
||||
- 実際に動作している実装をベースとした正確な仕様
|
||||
- FileBoxプラグインで実証済み
|
||||
- プラグイン開発者はここから始める
|
||||
|
||||
- **[vm-plugin-integration.md](./vm-plugin-integration.md)** - **VM統合仕様書** 🆕
|
||||
- VMバックエンドとプラグインシステムの統合
|
||||
- BoxRef型による統一アーキテクチャ
|
||||
- パフォーマンス最適化とエラーハンドリング
|
||||
|
||||
- **[plugin-tester.md](./plugin-tester.md)** - プラグイン診断ツール
|
||||
- プラグインの動作確認とデバッグに使用
|
||||
- `tools/plugin-tester`ツールの使用方法
|
||||
|
||||
- **[filebox-bid-mapping.md](./filebox-bid-mapping.md)** - 参考資料
|
||||
- FileBox APIとプラグイン実装の対応表
|
||||
- API設計の参考として有用
|
||||
|
||||
### 🔄 **Migration & Reference**
|
||||
- **[migration-guide.md](./migration-guide.md)** - 移行ガイド
|
||||
- 古いドキュメントから現在の実装への移行方法
|
||||
- ドキュメント状況の整理
|
||||
|
||||
### ⚠️ **Deprecated - 非推奨**
|
||||
- **[ffi-abi-specification.md](./ffi-abi-specification.md)** - ❌ 理想案、未実装
|
||||
- **[plugin-system.md](./plugin-system.md)** - ❌ 将来構想
|
||||
- **[nyash-toml-v2-spec.md](./nyash-toml-v2-spec.md)** - ⚠️ 部分的に古い
|
||||
|
||||
## 🚀 For Plugin Developers
|
||||
|
||||
### 1. **Read the Specification**
|
||||
```bash
|
||||
# 主要仕様書を読む
|
||||
cat docs/説明書/reference/plugin-system/bid-ffi-v1-actual-specification.md
|
||||
```
|
||||
|
||||
### 2. **Study Working Example**
|
||||
```bash
|
||||
# FileBoxプラグインを参考にする
|
||||
cd plugins/nyash-filebox-plugin
|
||||
cat src/lib.rs
|
||||
```
|
||||
|
||||
### 3. **Configure Your Plugin**
|
||||
```bash
|
||||
# nyash.tomlで設定
|
||||
cat nyash.toml # 実際の設定形式を確認
|
||||
```
|
||||
|
||||
### 4. **Test Your Plugin**
|
||||
```bash
|
||||
# プラグインテスターで確認
|
||||
cd tools/plugin-tester
|
||||
cargo build --release
|
||||
./target/release/plugin-tester check path/to/your/plugin.so
|
||||
```
|
||||
|
||||
## 🔧 For Nyash Core Developers
|
||||
|
||||
### Implementation Files
|
||||
- **[plugin_loader_v2.rs](../../../../src/runtime/plugin_loader_v2.rs)** - プラグインローダー実装
|
||||
- **[nyash_toml_v2.rs](../../../../src/config/nyash_toml_v2.rs)** - 設定パーサー
|
||||
- **[tlv.rs](../../../../src/bid/tlv.rs)** - TLVエンコーダー/デコーダー
|
||||
|
||||
### Next Steps
|
||||
- **Phase 3**: MIR ExternCall → plugin system 接続実装
|
||||
- **Future**: HTTP系ボックスのプラグイン化
|
||||
|
||||
## 📞 Support & Issues
|
||||
|
||||
- **Working Examples**: `plugins/nyash-filebox-plugin/`
|
||||
- **Issues**: Report at [GitHub Issues](https://github.com/moe-charm/nyash/issues)
|
||||
- **Configuration**: `nyash.toml` in project root
|
||||
|
||||
---
|
||||
|
||||
**Status**: Phase 2 Documentation Reorganization - Completed
|
||||
**Last Updated**: 2025-08-20
|
||||
194
docs/reference/plugin-system/bid-ffi-v1-actual-specification.md
Normal file
194
docs/reference/plugin-system/bid-ffi-v1-actual-specification.md
Normal file
@ -0,0 +1,194 @@
|
||||
# BID-FFI v1 実装仕様書 (実装ベース)
|
||||
|
||||
## 🎯 概要
|
||||
|
||||
**これは現在動作している実装をベースとした正確な仕様書です。**
|
||||
- FileBoxプラグインで実証済み
|
||||
- plugin_loader_v2.rsの実装に基づく
|
||||
- 理想案ではなく、実際に動く仕様
|
||||
|
||||
## 📋 プラグインAPI仕様
|
||||
|
||||
### 必須エクスポート関数
|
||||
|
||||
#### 1. ABI Version (オプション)
|
||||
```c
|
||||
extern "C" u32 nyash_plugin_abi(void) {
|
||||
return 1; // BID-FFI v1
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 初期化 (オプション)
|
||||
```c
|
||||
extern "C" i32 nyash_plugin_init(void) {
|
||||
// グローバルリソース初期化
|
||||
// 0=成功, 負数=エラー(プラグイン無効化)
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. メソッド呼び出し (必須)
|
||||
```c
|
||||
extern "C" i32 nyash_plugin_invoke(
|
||||
u32 type_id, // Box型ID (6=FileBox)
|
||||
u32 method_id, // メソッドID (0=birth, 4294967295=fini)
|
||||
u32 instance_id, // インスタンスID (0=static call)
|
||||
const u8* args, // TLV引数
|
||||
usize args_len, // 引数サイズ
|
||||
u8* result, // TLV結果バッファ
|
||||
usize* result_len // [IN/OUT]バッファサイズ
|
||||
) -> i32; // 0=成功, 負数=エラー
|
||||
```
|
||||
|
||||
#### 4. 終了処理 (オプション)
|
||||
```c
|
||||
extern "C" void nyash_plugin_shutdown(void) {
|
||||
// グローバルリソース解放
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 エラーコード
|
||||
|
||||
```c
|
||||
#define NYB_SUCCESS 0 // 成功
|
||||
#define NYB_E_SHORT_BUFFER -1 // バッファ不足
|
||||
#define NYB_E_INVALID_TYPE -2 // 無効な型ID
|
||||
#define NYB_E_INVALID_METHOD -3 // 無効なメソッドID
|
||||
#define NYB_E_INVALID_ARGS -4 // 無効な引数
|
||||
#define NYB_E_PLUGIN_ERROR -5 // プラグイン内部エラー
|
||||
#define NYB_E_INVALID_HANDLE -8 // 無効なハンドル
|
||||
```
|
||||
|
||||
## 🏗️ TLV (Type-Length-Value) 形式
|
||||
|
||||
### ヘッダー構造
|
||||
```c
|
||||
struct TlvHeader {
|
||||
u16 version; // 1 (BID-FFI v1)
|
||||
u16 argc; // 引数数
|
||||
};
|
||||
```
|
||||
|
||||
### エントリー構造
|
||||
```c
|
||||
struct TlvEntry {
|
||||
u8 tag; // 型タグ
|
||||
u8 reserved; // 0(将来拡張用)
|
||||
u16 size; // ペイロードサイズ
|
||||
// followed by payload data
|
||||
};
|
||||
```
|
||||
|
||||
### 型タグ定義
|
||||
```c
|
||||
#define BID_TAG_BOOL 1 // bool: 1 byte (0/1)
|
||||
#define BID_TAG_I32 2 // i32: 4 bytes (little-endian)
|
||||
#define BID_TAG_I64 3 // i64: 8 bytes (little-endian)
|
||||
#define BID_TAG_F32 4 // f32: 4 bytes (IEEE 754)
|
||||
#define BID_TAG_F64 5 // f64: 8 bytes (IEEE 754)
|
||||
#define BID_TAG_STRING 6 // string: UTF-8 bytes
|
||||
#define BID_TAG_BYTES 7 // bytes: binary data
|
||||
#define BID_TAG_HANDLE 8 // handle: 8 bytes (type_id + instance_id)
|
||||
#define BID_TAG_VOID 9 // void: 0 bytes
|
||||
```
|
||||
|
||||
## 🔧 nyash.toml設定仕様
|
||||
|
||||
### 基本構造
|
||||
```toml
|
||||
[libraries."<library_name>"]
|
||||
boxes = ["BoxType1", "BoxType2"] # 提供するBox型
|
||||
path = "./path/to/library.so" # ライブラリパス
|
||||
|
||||
[libraries."<library_name>".<BoxType>]
|
||||
type_id = <number> # Box型ID (必須)
|
||||
|
||||
[libraries."<library_name>".<BoxType>.methods]
|
||||
<method_name> = { method_id = <number> }
|
||||
```
|
||||
|
||||
### 実例 (FileBox)
|
||||
```toml
|
||||
[libraries."libnyash_filebox_plugin.so"]
|
||||
boxes = ["FileBox"]
|
||||
path = "./plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.so"
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox]
|
||||
type_id = 6
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox.methods]
|
||||
birth = { method_id = 0 } # コンストラクタ
|
||||
open = { method_id = 1 }
|
||||
read = { method_id = 2 }
|
||||
write = { method_id = 3 }
|
||||
close = { method_id = 4 }
|
||||
fini = { method_id = 4294967295 } # デストラクタ (u32::MAX)
|
||||
```
|
||||
|
||||
## 🔄 必須メソッド規約
|
||||
|
||||
### birth() - コンストラクタ
|
||||
- **method_id**: 必ず 0
|
||||
- **引数**: TLV形式(型依存)
|
||||
- **戻り値**: instance_id (u32, little-endian, 4bytes)
|
||||
- **呼び出し**: instance_id=0 (static call)
|
||||
|
||||
### fini() - デストラクタ
|
||||
- **method_id**: 必ず 4294967295 (u32::MAX)
|
||||
- **引数**: 空のTLV (version=1, argc=0)
|
||||
- **戻り値**: Void
|
||||
- **呼び出し**: 対象のinstance_id
|
||||
|
||||
## 📝 PluginBoxV2構造体
|
||||
|
||||
```rust
|
||||
pub struct PluginBoxV2 {
|
||||
pub box_type: String, // "FileBox"
|
||||
pub type_id: u32, // 6
|
||||
pub invoke_fn: InvokeFn, // 関数ポインタ
|
||||
pub instance_id: u32, // プラグイン生成ID
|
||||
pub fini_method_id: Option<u32>, // finiメソッドID
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 重要な制約
|
||||
|
||||
### メモリ管理
|
||||
- **プラグイン責任**: プラグインが確保したメモリはプラグインが解放
|
||||
- **2段階呼び出し**:
|
||||
1. result=NULL でサイズ取得
|
||||
2. ホストがバッファ確保後、実際のデータ取得
|
||||
|
||||
### 文字列エンコーディング
|
||||
- **UTF-8必須**: すべての文字列はUTF-8
|
||||
- **NUL終端不要**: lengthが正確性を保証
|
||||
|
||||
### インスタンス管理
|
||||
- **instance_id**: プラグイン内で一意
|
||||
- **birth順序**: birth() → 実際のメソッド → fini()
|
||||
- **共有・複製**: clone_box()は新birth()、share_box()は同一instance_id
|
||||
|
||||
## 🔗 実装ファイル
|
||||
|
||||
### Nyash側
|
||||
- `src/runtime/plugin_loader_v2.rs` - プラグインローダー
|
||||
- `src/config/nyash_toml_v2.rs` - 設定パーサー
|
||||
- `src/bid/tlv.rs` - TLVエンコーダー/デコーダー
|
||||
|
||||
### プラグイン例
|
||||
- `plugins/nyash-filebox-plugin/src/lib.rs` - FileBox実装
|
||||
- `plugins/nyash-test-multibox/src/lib.rs` - マルチBox実装
|
||||
|
||||
## ✅ 動作確認済み
|
||||
|
||||
- ✅ FileBoxプラグイン完全動作
|
||||
- ✅ birth/finiライフサイクル
|
||||
- ✅ TLVエンコーディング/デコーディング
|
||||
- ✅ clone_box/share_box メソッド
|
||||
- ✅ マルチインスタンス管理
|
||||
|
||||
---
|
||||
|
||||
**最終更新**: 2025年8月20日 - Phase 1現実調査完了
|
||||
**ベース**: plugin_loader_v2.rs実装 + FileBox実証
|
||||
**状態**: Production Ready (実際に動作中)
|
||||
626
docs/reference/plugin-system/ffi-abi-specification.md
Normal file
626
docs/reference/plugin-system/ffi-abi-specification.md
Normal file
@ -0,0 +1,626 @@
|
||||
# Box FFI/ABI v0 (BID-1 Enhanced Edition)
|
||||
|
||||
> ⚠️ **DEPRECATED - 理想案、未実装**
|
||||
>
|
||||
> この文書は将来の理想的なプラグインシステム設計案です。
|
||||
> **現在の実装とは異なります。**
|
||||
>
|
||||
> **実際に動作している仕様については、以下を参照してください:**
|
||||
> - [BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md) - 現在動作中の仕様
|
||||
> - [nyash.toml設定例](../../../../nyash.toml) - 実際の設定形式
|
||||
> - [plugin_loader_v2.rs](../../../../src/runtime/plugin_loader_v2.rs) - 実装詳細
|
||||
|
||||
Purpose
|
||||
- Define a language-agnostic ABI to call external libraries as Boxes.
|
||||
- Serve as a single source of truth for MIR ExternCall, WASM RuntimeImports, VM stubs, and future language codegens (TS/Python/Rust/LLVM IR).
|
||||
- Support Everything is Box philosophy with efficient Handle design.
|
||||
|
||||
Design Goals
|
||||
- Simple first: UTF-8 strings as (ptr,len), i32 for small integers, 32-bit linear memory alignment friendly.
|
||||
- Deterministic and portable across WASM/VM/native backends.
|
||||
- Align with MIR effect system (pure/mut/io/control) to preserve optimization safety.
|
||||
- Efficient Handle design for Box instances (type_id + instance_id).
|
||||
|
||||
Core Types
|
||||
- i32, i64, f32, f64, bool (0|1)
|
||||
- string: UTF-8 in linear memory as (ptr: usize, len: usize)
|
||||
- bytes: Binary data as (ptr: usize, len: usize)
|
||||
- handle: Efficient Box handle as {type_id: u32, instance_id: u32} or packed u64
|
||||
- array(T): (ptr: usize, len: usize)
|
||||
- void (no return value)
|
||||
|
||||
Memory & Alignment
|
||||
- All pointers are platform-dependent (usize): 32-bit in WASM MVP, 64-bit on native x86-64.
|
||||
- Alignment: 8-byte boundary for all structures.
|
||||
- Strings/arrays must be contiguous in linear memory; no NUL terminator required (len is authoritative).
|
||||
- Box layout examples for built-ins (for backends that materialize Boxes in memory):
|
||||
- Header: [type_id:i32][ref_count:i32][field_count:i32]
|
||||
- StringBox: header + [data_ptr:usize][length:usize]
|
||||
|
||||
Handle Design (BID-1 Enhancement)
|
||||
- Handle represents a Box instance with two components:
|
||||
- type_id: u32 (1=StringBox, 6=FileBox, 7=FutureBox, 8=P2PBox, etc.)
|
||||
- instance_id: u32 (unique instance identifier)
|
||||
- Can be passed as single u64 (type_id << 32 | instance_id) or struct
|
||||
|
||||
Naming & Resolution
|
||||
- Interface namespace: `env.console`, `env.canvas`, `nyash.file`, etc.
|
||||
- Method: `log`, `fillRect`, `fillText`, `open`, `read`, `write`, `close`.
|
||||
- Fully-qualified name: `env.console.log`, `env.canvas.fillRect`, `nyash.file.open`.
|
||||
|
||||
Calling Convention (BID-1)
|
||||
- Single entry point: `nyash_plugin_invoke`
|
||||
- Arguments passed as BID-1 TLV format
|
||||
- Return values in BID-1 TLV format
|
||||
- Two-call pattern for dynamic results:
|
||||
1. First call with null result buffer to get size
|
||||
2. Second call with allocated buffer to get data
|
||||
|
||||
Error Model (BID-1)
|
||||
- Standardized error codes:
|
||||
- NYB_SUCCESS (0): Operation successful
|
||||
- NYB_E_SHORT_BUFFER (-1): Buffer too small
|
||||
- NYB_E_INVALID_TYPE (-2): Invalid type ID
|
||||
- NYB_E_INVALID_METHOD (-3): Invalid method ID
|
||||
- NYB_E_INVALID_ARGS (-4): Invalid arguments
|
||||
- NYB_E_PLUGIN_ERROR (-5): Plugin internal error
|
||||
|
||||
Effects
|
||||
- Each BID method declares one of: pure | mut | io | control.
|
||||
- Optimizer/verifier rules:
|
||||
- pure: reordering permitted, memoization possible
|
||||
- mut: preserve order w.r.t same resource
|
||||
- io: preserve program order strictly
|
||||
- control: affects CFG; handled as terminators or dedicated ops
|
||||
|
||||
BID-1 TLV Format
|
||||
```c
|
||||
// BID-1 TLV specification - unified format for arguments and results
|
||||
struct BidTLV {
|
||||
u16 version; // 1 (BID-1)
|
||||
u16 argc; // argument count
|
||||
// followed by TLVEntry array
|
||||
};
|
||||
|
||||
struct TLVEntry {
|
||||
u8 tag; // type tag
|
||||
u8 reserved; // future use (0)
|
||||
u16 size; // payload size
|
||||
// followed by payload data
|
||||
};
|
||||
|
||||
// Tag definitions (Phase 1)
|
||||
#define BID_TAG_BOOL 1 // payload: 1 byte (0/1)
|
||||
#define BID_TAG_I32 2 // payload: 4 bytes (little-endian)
|
||||
#define BID_TAG_I64 3 // payload: 8 bytes (little-endian)
|
||||
#define BID_TAG_F32 4 // payload: 4 bytes (IEEE 754)
|
||||
#define BID_TAG_F64 5 // payload: 8 bytes (IEEE 754)
|
||||
#define BID_TAG_STRING 6 // payload: UTF-8 bytes
|
||||
#define BID_TAG_BYTES 7 // payload: binary data
|
||||
#define BID_TAG_HANDLE 8 // payload: 8 bytes (type_id + instance_id)
|
||||
|
||||
// Phase 2 reserved
|
||||
#define BID_TAG_RESULT 20 // Result<T,E>
|
||||
#define BID_TAG_OPTION 21 // Option<T>
|
||||
#define BID_TAG_ARRAY 22 // Array<T>
|
||||
```
|
||||
|
||||
Plugin API (BID-1)
|
||||
```c
|
||||
// src/bid/plugin_api.h - Plugin API complete version
|
||||
|
||||
// Host function table
|
||||
typedef struct {
|
||||
void* (*alloc)(size_t size); // Memory allocation
|
||||
void (*free)(void* ptr); // Memory deallocation
|
||||
void (*wake)(u32 future_id); // FutureBox wake
|
||||
void (*log)(const char* msg); // Log output
|
||||
} NyashHostVtable;
|
||||
|
||||
// Plugin information
|
||||
typedef struct {
|
||||
u32 type_id; // Box type ID
|
||||
const char* type_name; // "FileBox" etc.
|
||||
u32 method_count; // Method count
|
||||
const NyashMethodInfo* methods; // Method table
|
||||
} NyashPluginInfo;
|
||||
|
||||
typedef struct {
|
||||
u32 method_id; // Method ID
|
||||
const char* method_name; // "open", "read" etc.
|
||||
u32 signature_hash; // Type signature hash
|
||||
} NyashMethodInfo;
|
||||
|
||||
// Plugin API (required implementation)
|
||||
extern "C" {
|
||||
// Get ABI version
|
||||
u32 nyash_plugin_abi(void);
|
||||
|
||||
// Initialize (host integration & metadata registration)
|
||||
i32 nyash_plugin_init(const NyashHostVtable* host, NyashPluginInfo* info);
|
||||
|
||||
// Unified method invocation
|
||||
i32 nyash_plugin_invoke(
|
||||
u32 type_id, // Box type ID
|
||||
u32 method_id, // Method ID
|
||||
u32 instance_id, // Instance ID
|
||||
const u8* args, // BID-1 TLV arguments
|
||||
size_t args_len, // Arguments size
|
||||
u8* result, // BID-1 TLV result
|
||||
size_t* result_len // Result size (in/out)
|
||||
);
|
||||
|
||||
// Shutdown
|
||||
void nyash_plugin_shutdown(void);
|
||||
}
|
||||
```
|
||||
|
||||
BID (Box Interface Definition) — YAML
|
||||
```yaml
|
||||
version: 1 # BID-1 format
|
||||
interfaces:
|
||||
- name: env.console
|
||||
box: Console
|
||||
methods:
|
||||
- name: log
|
||||
params: [ {string: msg} ]
|
||||
returns: void
|
||||
effect: io
|
||||
|
||||
- name: env.canvas
|
||||
box: Canvas
|
||||
methods:
|
||||
- name: fillRect
|
||||
params:
|
||||
- {string: canvas_id}
|
||||
- {i32: x}
|
||||
- {i32: y}
|
||||
- {i32: w}
|
||||
- {i32: h}
|
||||
- {string: color}
|
||||
returns: void
|
||||
effect: io
|
||||
|
||||
- name: fillText
|
||||
params:
|
||||
- {string: canvas_id}
|
||||
- {string: text}
|
||||
- {i32: x}
|
||||
- {i32: y}
|
||||
- {string: font}
|
||||
- {string: color}
|
||||
returns: void
|
||||
effect: io
|
||||
|
||||
- name: nyash.file
|
||||
box: FileBox
|
||||
type_id: 6
|
||||
methods:
|
||||
- name: open
|
||||
method_id: 1
|
||||
params:
|
||||
- {string: path}
|
||||
- {string: mode}
|
||||
returns: {handle: FileBox}
|
||||
effect: io
|
||||
|
||||
- name: read
|
||||
method_id: 2
|
||||
params:
|
||||
- {handle: handle}
|
||||
- {i32: size}
|
||||
returns: {bytes: data}
|
||||
effect: io
|
||||
|
||||
- name: write
|
||||
method_id: 3
|
||||
params:
|
||||
- {handle: handle}
|
||||
- {bytes: data}
|
||||
returns: {i32: written}
|
||||
effect: io
|
||||
|
||||
- name: close
|
||||
method_id: 4
|
||||
params:
|
||||
- {handle: handle}
|
||||
returns: void
|
||||
effect: io
|
||||
```
|
||||
|
||||
FileBox Plugin Example (BID-1)
|
||||
```c
|
||||
// plugins/nyash-file/src/lib.c
|
||||
|
||||
#include "nyash_plugin_api.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const NyashHostVtable* g_host = NULL;
|
||||
|
||||
// ABI version
|
||||
u32 nyash_plugin_abi(void) {
|
||||
return 1; // BID-1 support
|
||||
}
|
||||
|
||||
// Method table
|
||||
static const NyashMethodInfo FILE_METHODS[] = {
|
||||
{0, "birth", 0xBEEFCAFE}, // birth(path: string, mode: string) - Constructor
|
||||
{1, "open", 0x12345678}, // open(path: string, mode: string) -> Handle
|
||||
{2, "read", 0x87654321}, // read(handle: Handle, size: i32) -> Bytes
|
||||
{3, "write", 0x11223344}, // write(handle: Handle, data: Bytes) -> i32
|
||||
{4, "close", 0xABCDEF00}, // close(handle: Handle) -> Void
|
||||
{5, "fini", 0xDEADBEEF}, // fini() - Destructor
|
||||
};
|
||||
|
||||
// Initialize
|
||||
i32 nyash_plugin_init(const NyashHostVtable* host, NyashPluginInfo* info) {
|
||||
info->type_id = 6; // FileBox
|
||||
info->type_name = "FileBox";
|
||||
info->method_count = 4;
|
||||
info->methods = FILE_METHODS;
|
||||
|
||||
// Save host functions
|
||||
g_host = host;
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
// Method execution
|
||||
i32 nyash_plugin_invoke(u32 type_id, u32 method_id, u32 instance_id,
|
||||
const u8* args, size_t args_len,
|
||||
u8* result, size_t* result_len) {
|
||||
if (type_id != 6) return NYB_E_INVALID_TYPE;
|
||||
|
||||
switch (method_id) {
|
||||
case 1: return file_open(args, args_len, result, result_len);
|
||||
case 2: return file_read(instance_id, args, args_len, result, result_len);
|
||||
case 3: return file_write(instance_id, args, args_len, result, result_len);
|
||||
case 4: return file_close(instance_id, args, args_len, result, result_len);
|
||||
default: return NYB_E_INVALID_METHOD;
|
||||
}
|
||||
}
|
||||
|
||||
// File open implementation
|
||||
static i32 file_open(const u8* args, size_t args_len,
|
||||
u8* result, size_t* result_len) {
|
||||
// BID-1 TLV parsing
|
||||
BidTLV* tlv = (BidTLV*)args;
|
||||
if (tlv->version != 1 || tlv->argc != 2) {
|
||||
return NYB_E_INVALID_ARGS;
|
||||
}
|
||||
|
||||
// Extract arguments: path, mode
|
||||
const char* path = extract_string_arg(tlv, 0);
|
||||
const char* mode = extract_string_arg(tlv, 1);
|
||||
|
||||
// Open file
|
||||
FILE* fp = fopen(path, mode);
|
||||
if (!fp) return NYB_E_PLUGIN_ERROR;
|
||||
|
||||
// Generate handle
|
||||
u32 handle_id = register_file_handle(fp);
|
||||
|
||||
// BID-1 result creation
|
||||
if (!result) {
|
||||
*result_len = sizeof(BidTLV) + sizeof(TLVEntry) + 8; // Handle
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
// Return Handle{type_id: 6, instance_id: handle_id} in TLV
|
||||
encode_handle_result(result, 6, handle_id);
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
// birth implementation - Box constructor
|
||||
static i32 file_birth(u32 instance_id, const u8* args, size_t args_len,
|
||||
u8* result, size_t* result_len) {
|
||||
// Parse constructor arguments
|
||||
BidTLV* tlv = (BidTLV*)args;
|
||||
const char* path = extract_string_arg(tlv, 0);
|
||||
const char* mode = extract_string_arg(tlv, 1);
|
||||
|
||||
// Create instance
|
||||
FileInstance* instance = malloc(sizeof(FileInstance));
|
||||
instance->fp = fopen(path, mode);
|
||||
instance->buffer = NULL;
|
||||
|
||||
// Register instance
|
||||
register_instance(instance_id, instance);
|
||||
|
||||
// No return value for birth
|
||||
*result_len = 0;
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
// fini implementation - Box destructor
|
||||
static i32 file_fini(u32 instance_id) {
|
||||
FileInstance* instance = get_instance(instance_id);
|
||||
if (!instance) return NYB_E_INVALID_HANDLE;
|
||||
|
||||
// Free plugin-allocated memory
|
||||
if (instance->buffer) {
|
||||
free(instance->buffer);
|
||||
}
|
||||
|
||||
// Close file handle
|
||||
if (instance->fp) {
|
||||
fclose(instance->fp);
|
||||
}
|
||||
|
||||
// Free instance
|
||||
free(instance);
|
||||
unregister_instance(instance_id);
|
||||
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
void nyash_plugin_shutdown(void) {
|
||||
// Cleanup all remaining instances
|
||||
cleanup_all_instances();
|
||||
}
|
||||
```
|
||||
|
||||
WASM Mapping (RuntimeImports)
|
||||
- Import examples:
|
||||
- `(import "env" "console_log" (func $console_log (param i32 i32)))` // (ptr,len)
|
||||
- `(import "env" "canvas_fillRect" (func $canvas_fillRect (param i32 i32 i32 i32 i32 i32)))`
|
||||
- `(import "env" "canvas_fillText" (func $canvas_fillText (param i32 i32 i32 i32 i32 i32 i32 i32)))` // two strings as (ptr,len) each
|
||||
- Host responsibilities:
|
||||
- Resolve strings from memory via `(ptr,len)` using TextDecoder('utf-8')
|
||||
- Map to DOM/Canvas/Console as appropriate
|
||||
- For plugins: use dlopen/dlsym to load and invoke nyash_plugin_* functions
|
||||
|
||||
WASM Mapping Rules (v0)
|
||||
- String marshalling: UTF-8 `(ptr:i32, len:i32)`; memory exported as `memory`.
|
||||
- Alignment: `ptr` 4-byte aligned is推奨(必須ではないが実装簡素化のため)。
|
||||
- Import naming: `env.<iface>_<method>` or nested `env` modules(実装都合でどちらでも可)。
|
||||
- 推奨: `env.console_log`, `env.canvas_fillRect`, `env.canvas_fillText`。
|
||||
- Argument order: 文字列は `(ptr,len)` を1引数扱いで連続配置。複数文字列はその都度 `(ptr,len)`。
|
||||
- Return: v0では`void`または整数のみ(複合戻りはout-paramに委譲)。
|
||||
- Memory growth: ホストは`memory.buffer`の再割当を考慮(必要に応じて毎回ビューを取り直す)。
|
||||
|
||||
RuntimeImportsとBIDの関係
|
||||
- `RuntimeImports` は ABI/BID をWASM向けに具体化した実装レイヤー(WASM専用の橋渡し)。
|
||||
- 生成方針: 将来的にBID(YAML/JSON)から`importObject`と`(import ...)`宣言を自動生成する。
|
||||
- 例(BID→WASM):
|
||||
- `env.console.log(string msg)` → `console_log(ptr:i32, len:i32)`
|
||||
- `env.canvas.fillRect(string canvasId, i32 x, i32 y, i32 w, i32 h, string color)`
|
||||
→ `canvas_fillRect(id_ptr, id_len, x, y, w, h, color_ptr, color_len)`
|
||||
|
||||
============================================================
|
||||
ABIの確定事項(BID-1, 日本語)
|
||||
============================================================
|
||||
|
||||
基本方針(BID-1)
|
||||
- 文字列は UTF-8 の `(ptr:usize, len:usize)` で受け渡す(NUL終端不要、内部NUL禁止)。
|
||||
- 配列/バイト列は `(ptr:usize, len:usize)` とする。
|
||||
- 数値は WASM/LLVM と親和性の高い素のプリミティブ(i32/i64/f32/f64)。
|
||||
- 真偽値は `i32` で 0=false, 1=true。
|
||||
- ポインタは `usize`(WASM MVPは32bit、ネイティブx86-64は64bit)。
|
||||
- エンディアンはリトルエンディアン(WASM/一般的なネイティブと一致)。
|
||||
- 呼出規約は単一エントリーポイント `nyash_plugin_invoke` + BID-1 TLV形式。
|
||||
- 戻り値はBID-1 TLV形式(2回呼び出しパターンでサイズ取得)。
|
||||
- メモリは `memory` をエクスポート(WASM)。ホスト側で管理。
|
||||
- 効果(effect)は BID に必須。pure は再順序化可、mut/io は順序保持。
|
||||
- 同期のみ(非同期は将来拡張)。
|
||||
- スレッド前提:シングルスレッド(Phase 1)。
|
||||
|
||||
メモリ管理戦略
|
||||
- 2回呼び出しパターン:
|
||||
1. result=NULLでサイズ取得
|
||||
2. ホストがallocateして結果取得
|
||||
- 文字列エンコーディング:UTF-8必須、内部NUL禁止
|
||||
- ハンドル再利用対策:generation追加で ABA問題回避(将来)
|
||||
|
||||
Boxライフサイクル管理
|
||||
- **birth/fini原則**:
|
||||
- method_id=0 は必ず`birth()`(コンストラクタ)
|
||||
- method_id=最大値 は必ず`fini()`(デストラクタ)
|
||||
- birthで割り当てたリソースはfiniで解放
|
||||
- **メモリ所有権**:
|
||||
- プラグインがmalloc()したメモリ → プラグインがfree()
|
||||
- ホストが提供したバッファ → ホストが管理
|
||||
- 引数として渡されたメモリ → read-onlyとして扱う
|
||||
- **インスタンス管理**:
|
||||
- instance_idはホストが発行・管理
|
||||
- プラグインは内部マップでinstance_id → 実装構造体を管理
|
||||
- nyash_plugin_shutdown()で全インスタンスをクリーンアップ
|
||||
|
||||
型と表現(BID-1)
|
||||
- `i32`: 32bit 符号付き整数
|
||||
- `i64`: 64bit 符号付き整数(WASMではJSブリッジ注意。Host側はBigInt等)
|
||||
- `f32/f64`: IEEE 754
|
||||
- `bool`: i32(0/1)
|
||||
- `string`: UTF-8 `(ptr:usize, len:usize)`
|
||||
- `bytes`: バイナリデータ `(ptr:usize, len:usize)`
|
||||
- `array<T>`: `(ptr:usize, len:usize)`
|
||||
- `handle`: Box参照 `{type_id:u32, instance_id:u32}` または packed u64
|
||||
- `void`: 戻り値なし
|
||||
|
||||
BidType Rust実装
|
||||
```rust
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BidType {
|
||||
// プリミティブ(FFI境界で値渡し)
|
||||
Bool, I32, I64, F32, F64,
|
||||
String, Bytes,
|
||||
|
||||
// Handle設計
|
||||
Handle { type_id: u32, instance_id: u32 },
|
||||
|
||||
// メタ型
|
||||
Void,
|
||||
|
||||
// Phase 2予約
|
||||
Option(Box<BidType>),
|
||||
Result(Box<BidType>, Box<BidType>),
|
||||
Array(Box<BidType>),
|
||||
}
|
||||
```
|
||||
|
||||
アラインメント/境界
|
||||
- `ptr` は 8byte アライン必須(構造体の効率的アクセス)。
|
||||
- 範囲外アクセスは未定義ではなく「Hostが防ぐ/検証する」方針(将来、Verifier/境界チェック生成)。
|
||||
- プラットフォーム依存:
|
||||
- Linux x86-64: 8バイト境界
|
||||
- WASM MVP: 4バイト境界(互換性のため)
|
||||
|
||||
命名規約
|
||||
- `env.console.log`, `env.canvas.fillRect` のように `<namespace>.<iface>.<method>`。
|
||||
- WASM import 名は `env.console_log` 等の平坦化でも可(生成側で一貫)。
|
||||
- プラグイン関数名: `nyash_plugin_*` プレフィックス必須。
|
||||
|
||||
エラー/例外
|
||||
- BID-1標準エラーコード使用(NYB_*)。
|
||||
- 失敗は整数ステータスで返却。
|
||||
- 例外/シグナルは範囲外。
|
||||
|
||||
セキュリティ/権限(将来)
|
||||
- BID に必要権限(console/canvas/storage/net…)を記述。HostはAllowlistで制御(Phase 9.9)。
|
||||
|
||||
実装上の注意点
|
||||
- ハンドル再利用/ABA: generation追加で回避
|
||||
- スレッド前提: シングルスレッド前提を明記
|
||||
- メソッドID衝突: ビルド時固定で回避
|
||||
- エラー伝播: トランスポート/ドメインエラー分離
|
||||
- 文字列エンコード: UTF-8必須、内部NUL禁止
|
||||
|
||||
============================================================
|
||||
BIDサンプル(YAML, 日本語)
|
||||
============================================================
|
||||
|
||||
```yaml
|
||||
version: 0
|
||||
interfaces:
|
||||
- name: env.console
|
||||
box: Console
|
||||
methods:
|
||||
- name: log
|
||||
params: [ { string: msg } ]
|
||||
returns: void
|
||||
effect: io
|
||||
|
||||
- name: env.canvas
|
||||
box: Canvas
|
||||
methods:
|
||||
- name: fillRect
|
||||
params:
|
||||
- { string: canvas_id }
|
||||
- { i32: x }
|
||||
- { i32: y }
|
||||
- { i32: w }
|
||||
- { i32: h }
|
||||
- { string: color }
|
||||
returns: void
|
||||
effect: io
|
||||
|
||||
- name: fillText
|
||||
params:
|
||||
- { string: canvas_id }
|
||||
- { string: text }
|
||||
- { i32: x }
|
||||
- { i32: y }
|
||||
- { string: font }
|
||||
- { string: color }
|
||||
returns: void
|
||||
effect: io
|
||||
```
|
||||
|
||||
ファイルとしてのサンプル(同等内容)
|
||||
- `docs/nyir/bid_samples/console.yaml`
|
||||
- `docs/nyir/bid_samples/canvas.yaml`
|
||||
|
||||
============================================================
|
||||
Host側 importObject サンプル(ブラウザ, 日本語)
|
||||
============================================================
|
||||
|
||||
```js
|
||||
// 文字列(ptr,len)の復元ヘルパ
|
||||
function utf8FromMemory(memory, ptr, len) {
|
||||
const u8 = new Uint8Array(memory.buffer, ptr, len);
|
||||
return new TextDecoder('utf-8').decode(u8);
|
||||
}
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
print: (v) => console.log(v),
|
||||
print_str: (ptr, len) => {
|
||||
console.log(utf8FromMemory(wasmInstance.exports.memory, ptr, len));
|
||||
},
|
||||
console_log: (ptr, len) => {
|
||||
console.log(utf8FromMemory(wasmInstance.exports.memory, ptr, len));
|
||||
},
|
||||
canvas_fillRect: (idPtr, idLen, x, y, w, h, colorPtr, colorLen) => {
|
||||
const mem = wasmInstance.exports.memory;
|
||||
const id = utf8FromMemory(mem, idPtr, idLen);
|
||||
const color = utf8FromMemory(mem, colorPtr, colorLen);
|
||||
const cv = document.getElementById(id);
|
||||
if (!cv) return;
|
||||
const ctx = cv.getContext('2d');
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
},
|
||||
canvas_fillText: (idPtr, idLen, textPtr, textLen, x, y, fontPtr, fontLen, colorPtr, colorLen) => {
|
||||
const mem = wasmInstance.exports.memory;
|
||||
const id = utf8FromMemory(mem, idPtr, idLen);
|
||||
const text = utf8FromMemory(mem, textPtr, textLen);
|
||||
const font = utf8FromMemory(mem, fontPtr, fontLen);
|
||||
const color = utf8FromMemory(mem, colorPtr, colorLen);
|
||||
const cv = document.getElementById(id);
|
||||
if (!cv) return;
|
||||
const ctx = cv.getContext('2d');
|
||||
ctx.font = font;
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillText(text, x, y);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
============================================================
|
||||
ExternCall → WASM 呼び出しの例(日本語)
|
||||
============================================================
|
||||
|
||||
Nyash コード(概念):
|
||||
```
|
||||
console = new WebConsoleBox("output")
|
||||
console.log("Hello Nyash!")
|
||||
|
||||
canvas = new WebCanvasBox("game-canvas", 400, 300)
|
||||
canvas.fillRect(50, 50, 80, 60, "red")
|
||||
```
|
||||
|
||||
MIR(ExternCall化のイメージ):
|
||||
```
|
||||
ExternCall { iface: "env.console", method: "log", args: [ string("Hello Nyash!") ] }
|
||||
ExternCall { iface: "env.canvas", method: "fillRect", args: [ string("game-canvas"), 50, 50, 80, 60, string("red") ] }
|
||||
```
|
||||
|
||||
WASM import 呼び出し(概念):
|
||||
```
|
||||
call $console_log(msg_ptr, msg_len)
|
||||
call $canvas_fillRect(id_ptr, id_len, 50, 50, 80, 60, color_ptr, color_len)
|
||||
```
|
||||
|
||||
備考
|
||||
- 文字列定数は data segment に配置し、実行時に (ptr,len) を与える。
|
||||
- 動的文字列はランタイムでバッファ確保→(ptr,len) を渡す。
|
||||
|
||||
|
||||
VM Mapping (Stub v0)
|
||||
- Maintain a registry of externs by FQN (e.g., env.console.log) → function pointer.
|
||||
- Console: print to stdout; Canvas: log params or no-op.
|
||||
|
||||
LLVM IR Mapping (Preview)
|
||||
- Declare external functions with matching signatures (i32/i64/f32/f64/bool, i8* + i32 for strings).
|
||||
- Example: `declare void @env_console_log(i8* nocapture, i32)`
|
||||
- Strings allocated in data segment or heap; pass pointer + length.
|
||||
|
||||
Versioning
|
||||
- `version: 0` for the first public draft.
|
||||
- Backward-compatible extensions should add new methods/imports; breaking changes bump major.
|
||||
|
||||
Open Points (to validate post v0)
|
||||
- Boxref passing across FFI boundaries (opaque handles vs pointers).
|
||||
- Async externs and scheduling.
|
||||
- Error model harmonization (status vs result-box).
|
||||
68
docs/reference/plugin-system/filebox-bid-mapping.md
Normal file
68
docs/reference/plugin-system/filebox-bid-mapping.md
Normal file
@ -0,0 +1,68 @@
|
||||
FileBox × BID-FFI 対応表(Nyash API ↔ Plugin ABI)
|
||||
|
||||
概要
|
||||
- 目的: Nyash言語における `FileBox` のAPIを、BID-FFIプラグイン実装(C ABI)と正確に対応付ける。
|
||||
- 設置: C:\git\nyash-project\nyash\docs\説明書\reference\box-design\filebox-bid-mapping.md(Windowsパス例)
|
||||
|
||||
前提
|
||||
- BID-FFI v1(2段階応答/ShortBuffer=-1)
|
||||
- TLVヘッダ: `u16 version(=1)`, `u16 argc`
|
||||
- TLVエントリ: `u8 tag`, `u8 reserved(0)`, `u16 size`, payload
|
||||
- 主要タグ: 1=Bool, 2=I32, 3=I64, 4=F32, 5=F64, 6=String, 7=Bytes, 8=Handle(u64), 9=Void
|
||||
|
||||
メソッドID(プラグイン側)
|
||||
- 0: birth(instance生成) → 戻り値: u32 instance_id(暫定)
|
||||
- 1: open(String path, String mode) → Void
|
||||
- 2: read(I32 size) → Bytes
|
||||
- 3: write(Bytes data) → I32(書込バイト数)
|
||||
- 4: close() → Void
|
||||
- 0xFFFF_FFFF: fini(破棄)
|
||||
|
||||
Nyash API ↔ Plugin ABI 対応
|
||||
- 構築: `new FileBox(path: string)`
|
||||
- 既定動作: プラグイン設定が有効な場合、birth→open(path, "rw") を内部実行
|
||||
- フォールバック: プラグインが無効/未設定ならビルトインFileBoxを使用
|
||||
|
||||
- 書込: `FileBox.write(data: string)`
|
||||
- 変換: String → Bytes(UTF-8)
|
||||
- 呼出: method_id=3(write)
|
||||
- 戻り: I32 を受け取り、Nyash側は "ok" を返却(将来は書込サイズも返せる拡張余地)
|
||||
|
||||
- 読取: `FileBox.read([size: integer])`
|
||||
- 変換: 省略時デフォルト 1MB(1_048_576)を指定
|
||||
- 呼出: method_id=2(read)
|
||||
- 戻り: Bytes → String(UTF-8として解釈、失敗時はlossy)
|
||||
|
||||
- 閉じ: `FileBox.close()`
|
||||
- 呼出: method_id=4(close)
|
||||
- 戻り: Void → Nyash側は "ok"
|
||||
|
||||
エラーモデル(戻り値)
|
||||
- 0: 成功
|
||||
- -1: ShortBuffer(2段階応答。副作用なしで必要サイズを *result_len に返却)
|
||||
- -2: InvalidType
|
||||
- -3: InvalidMethod
|
||||
- -4: InvalidArgs
|
||||
- -5: PluginError
|
||||
- -8: InvalidHandle
|
||||
|
||||
例(Nyashコード)
|
||||
```
|
||||
// プラグイン優先で FileBox を生成
|
||||
local f
|
||||
f = new FileBox("/tmp/nyash_example.txt")
|
||||
f.write("Hello from Nyash via plugin!")
|
||||
print("READ=" + f.read())
|
||||
f.close()
|
||||
```
|
||||
|
||||
実装メモ(現在の挙動)
|
||||
- コンストラクタ: プラグイン有効時は birth→open("rw")。指定モードでの open は将来のAPI拡張候補(例: `FileBox.open(mode)`)。
|
||||
- read(size): Nyashからサイズを指定するAPIは次段で追加予定。現状は既定1MBで読み取り。
|
||||
- write: 書込サイズはプラグインからI32で返るが、Nyash側APIは簡便化のため "ok" を返却(将来拡張余地)。
|
||||
|
||||
関連ドキュメント
|
||||
- plugin-ABI: docs/説明書/reference/box-design/ffi-abi-specification.md
|
||||
- plugin system: docs/説明書/reference/box-design/plugin-system.md
|
||||
- plugin-tester: docs/説明書/reference/plugin-tester.md
|
||||
|
||||
104
docs/reference/plugin-system/migration-guide.md
Normal file
104
docs/reference/plugin-system/migration-guide.md
Normal file
@ -0,0 +1,104 @@
|
||||
# Plugin Documentation Migration Guide
|
||||
|
||||
## 🎯 概要
|
||||
|
||||
このガイドは、Nyashプラグインシステムの古いドキュメントから実際の実装に移行するためのものです。
|
||||
|
||||
## 📚 Documentation Status
|
||||
|
||||
### ✅ **Current Working Specification**
|
||||
- **[BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md)** - **RECOMMENDED**
|
||||
- 実際に動作している実装をベースとした正確な仕様
|
||||
- FileBoxプラグインで実証済み
|
||||
- `plugin_loader_v2.rs`の実装に基づく
|
||||
|
||||
### ⚠️ **Deprecated Documentation**
|
||||
- **[ffi-abi-specification.md](./ffi-abi-specification.md)** - ❌ DEPRECATED
|
||||
- 理想的な設計案だが未実装
|
||||
- MIR ExternCall設計が含まれているが、実際には使われていない
|
||||
|
||||
- **[plugin-system.md](./plugin-system.md)** - ❌ DEPRECATED
|
||||
- YAML DSLを使った将来構想
|
||||
- 現在の実装とは大きく異なる
|
||||
|
||||
- **[nyash-toml-v2-spec.md](./nyash-toml-v2-spec.md)** - ⚠️ PARTIALLY OUTDATED
|
||||
- 基本構造は正しいが、実際の形式と部分的に異なる
|
||||
|
||||
### ✅ **Still Accurate Documentation**
|
||||
- **[plugin-tester.md](./plugin-tester.md)** - ✅ CURRENT
|
||||
- プラグイン診断ツールの使用方法
|
||||
- 実際のツールと一致
|
||||
|
||||
- **[filebox-bid-mapping.md](./filebox-bid-mapping.md)** - ✅ USEFUL REFERENCE
|
||||
- FileBox APIとプラグイン実装の対応表
|
||||
- 開発時の参考資料として有効
|
||||
|
||||
## 🔄 Migration Steps
|
||||
|
||||
### For Plugin Developers
|
||||
|
||||
1. **Start with**: [BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md)
|
||||
2. **Refer to**: [実際のnyash.toml](../../../../nyash.toml) for configuration format
|
||||
3. **Use**: [plugin-tester](../../../../tools/plugin-tester/) for testing
|
||||
4. **Study**: [FileBox plugin](../../../../plugins/nyash-filebox-plugin/) as reference implementation
|
||||
|
||||
### For Nyash Core Developers
|
||||
|
||||
1. **Phase 1**: ✅ COMPLETED - Documentation cleanup with deprecation notices
|
||||
2. **Phase 2**: ✅ COMPLETED - Accurate specification creation
|
||||
3. **Phase 3**: 🚧 TODO - MIR ExternCall implementation to connect with plugin system
|
||||
|
||||
## 🎯 Key Differences
|
||||
|
||||
### Old Documentation vs Reality
|
||||
|
||||
| Aspect | Old Docs | Reality |
|
||||
|--------|----------|---------|
|
||||
| Configuration | YAML DSL | TOML format |
|
||||
| API Design | Complex handle system | Simple TLV + method_id |
|
||||
| MIR Integration | Fully designed | Stub only |
|
||||
| ABI Version | Multiple versions | BID-FFI v1 only |
|
||||
|
||||
### Working Configuration Format
|
||||
|
||||
**Old (in deprecated docs)**:
|
||||
```yaml
|
||||
# filebox.plugin.yaml
|
||||
schema: 1
|
||||
apis:
|
||||
- sig: "FileBox::open(path: string) -> FileBox"
|
||||
```
|
||||
|
||||
**Current (actual)**:
|
||||
```toml
|
||||
[libraries."libnyash_filebox_plugin.so"]
|
||||
boxes = ["FileBox"]
|
||||
path = "./plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.so"
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox.methods]
|
||||
birth = { method_id = 0 }
|
||||
open = { method_id = 1 }
|
||||
```
|
||||
|
||||
## 📞 FFI Interface
|
||||
|
||||
**Old (complex)**:
|
||||
- Multiple entry points
|
||||
- Complex handle management
|
||||
- Dynamic type discovery
|
||||
|
||||
**Current (simple)**:
|
||||
- Single entry point: `nyash_plugin_invoke`
|
||||
- Fixed TLV protocol
|
||||
- Static configuration in nyash.toml
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. ✅ **Documentation Cleanup**: Completed
|
||||
2. 🚧 **MIR Integration**: Implement ExternCall → plugin system connection
|
||||
3. 🔮 **Future**: Consider implementing some ideas from deprecated docs
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-08-20
|
||||
**Status**: Documentation reorganization Phase 2 completed
|
||||
163
docs/reference/plugin-system/nyash-toml-v2-spec.md
Normal file
163
docs/reference/plugin-system/nyash-toml-v2-spec.md
Normal file
@ -0,0 +1,163 @@
|
||||
# nyash.toml v2 仕様 - 究極のシンプル設計
|
||||
|
||||
> ⚠️ **PARTIALLY OUTDATED - 部分的に古い**
|
||||
>
|
||||
> この文書の基本構造は正しいですが、実際の形式と一部異なります。
|
||||
>
|
||||
> **最新の実際の設定形式については、以下を参照してください:**
|
||||
> - [BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md) - 現在動作中の仕様
|
||||
> - [nyash.toml設定例](../../../../nyash.toml) - 実際の設定形式
|
||||
|
||||
## 🎯 概要
|
||||
**革命的シンプル設計**: nyash.toml中心アーキテクチャ + 最小限FFI
|
||||
|
||||
## 📝 nyash.toml v2形式
|
||||
|
||||
### マルチBox型プラグイン対応
|
||||
```toml
|
||||
[libraries]
|
||||
# ライブラリ定義(1つのプラグインで複数のBox型を提供可能)
|
||||
"libnyash_filebox_plugin.so" = {
|
||||
boxes = ["FileBox"],
|
||||
path = "./target/release/libnyash_filebox_plugin.so"
|
||||
}
|
||||
|
||||
# 将来の拡張例: 1つのプラグインで複数Box型
|
||||
"libnyash_network_plugin.so" = {
|
||||
boxes = ["SocketBox", "HTTPServerBox", "HTTPClientBox"],
|
||||
path = "./target/release/libnyash_network_plugin.so"
|
||||
}
|
||||
|
||||
# FileBoxの型情報定義
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox]
|
||||
type_id = 6
|
||||
abi_version = 1 # ABIバージョンもここに!
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox.methods]
|
||||
# method_id だけで十分(引数情報は実行時チェック)
|
||||
birth = { method_id = 0 }
|
||||
open = { method_id = 1 }
|
||||
read = { method_id = 2 }
|
||||
write = { method_id = 3 }
|
||||
close = { method_id = 4 }
|
||||
fini = { method_id = 4294967295 } # 0xFFFFFFFF
|
||||
```
|
||||
|
||||
## 🚀 究極のシンプルFFI
|
||||
|
||||
### プラグインが実装する関数
|
||||
|
||||
#### 必須: メソッド実行エントリーポイント
|
||||
```c
|
||||
// 唯一の必須関数 - すべてのメソッド呼び出しはここから
|
||||
extern "C" fn nyash_plugin_invoke(
|
||||
type_id: u32, // Box型ID(例: FileBox = 6)
|
||||
method_id: u32, // メソッドID(0=birth, 0xFFFFFFFF=fini)
|
||||
instance_id: u32, // インスタンスID(0=static/birth)
|
||||
args: *const u8, // TLVエンコード引数
|
||||
args_len: usize,
|
||||
result: *mut u8, // TLVエンコード結果バッファ
|
||||
result_len: *mut usize // [IN/OUT]バッファサイズ
|
||||
) -> i32 // 0=成功, 負=エラー
|
||||
```
|
||||
|
||||
#### オプション: グローバル初期化
|
||||
```c
|
||||
// プラグインロード時に1回だけ呼ばれる(実装は任意)
|
||||
extern "C" fn nyash_plugin_init() -> i32 {
|
||||
// グローバルリソースの初期化
|
||||
// 設定ファイルの読み込み
|
||||
// ログファイルのオープン
|
||||
// 0=成功, 負=エラー(プラグインは無効化される)
|
||||
}
|
||||
```
|
||||
|
||||
### 廃止されたAPI
|
||||
```c
|
||||
// ❌ これらは全部不要!
|
||||
nyash_plugin_abi_version() // → nyash.tomlのabi_version
|
||||
nyash_plugin_get_box_count() // → nyash.tomlのboxes配列
|
||||
nyash_plugin_get_box_info() // → nyash.tomlから取得
|
||||
NyashHostVtable // → 完全廃止!
|
||||
```
|
||||
|
||||
## 📊 設計原則
|
||||
|
||||
### 1. **Single Source of Truth**
|
||||
- すべてのメタ情報はnyash.tomlに集約
|
||||
- プラグインは純粋な実装のみ
|
||||
|
||||
### 2. **Zero Dependencies**
|
||||
- Host VTable廃止 = 依存関係ゼロ
|
||||
- プラグインは完全に独立
|
||||
|
||||
### 3. **シンプルなライフサイクル**
|
||||
- `init` (オプション): プラグインロード時の初期化
|
||||
- `birth` (method_id=0): インスタンス作成
|
||||
- 各種メソッド: インスタンス操作
|
||||
- `fini` (method_id=0xFFFFFFFF): 論理的終了
|
||||
|
||||
### 4. **ログ出力**
|
||||
```rust
|
||||
// プラグインは自己完結でログ出力
|
||||
eprintln!("[FileBox] Opened: {}", path); // 標準エラー
|
||||
|
||||
// または専用ログファイル
|
||||
let mut log = File::create("plugin_debug.log")?;
|
||||
writeln!(log, "{}: FileBox birth", chrono::Local::now())?;
|
||||
```
|
||||
|
||||
### 5. **init関数の活用例**
|
||||
```rust
|
||||
static mut LOG_FILE: Option<File> = None;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nyash_plugin_init() -> i32 {
|
||||
// ログファイルを事前に開く
|
||||
match File::create("filebox.log") {
|
||||
Ok(f) => {
|
||||
unsafe { LOG_FILE = Some(f); }
|
||||
0 // 成功
|
||||
}
|
||||
Err(_) => -1 // エラー → プラグイン無効化
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 実装の流れ
|
||||
|
||||
### Phase 1: nyash.toml v2パーサー
|
||||
1. 新形式の読み込み
|
||||
2. Box型情報の抽出
|
||||
3. メソッドID管理
|
||||
|
||||
### Phase 2: プラグインローダー簡素化
|
||||
1. `nyash_plugin_init`(オプション)と`nyash_plugin_invoke`(必須)をロード
|
||||
2. nyash.tomlベースの型登録
|
||||
3. Host VTable関連コードを削除
|
||||
4. init関数が存在し失敗した場合はプラグインを無効化
|
||||
|
||||
### Phase 3: プラグイン側の対応
|
||||
1. abi/get_box_count/get_box_info関数を削除
|
||||
2. init関数は必要に応じて実装(グローバル初期化)
|
||||
3. invoke関数でメソッド処理
|
||||
4. ログ出力を自己完結に
|
||||
|
||||
## 🎉 メリット
|
||||
|
||||
1. **究極のシンプルさ** - 基本的にFFI関数1つ(initはオプション)
|
||||
2. **保守性向上** - 複雑な相互依存なし
|
||||
3. **テスト容易性** - モック不要
|
||||
4. **移植性** - どの言語でも実装可能
|
||||
5. **拡張性** - nyash.toml編集で機能追加
|
||||
6. **初期化保証** - init関数で早期エラー検出可能
|
||||
|
||||
## 🚨 注意事項
|
||||
|
||||
- プラグインのログは標準エラー出力かファイル出力で
|
||||
- メモリ管理はプラグイン内で完結
|
||||
- 非同期処理はNyash側でFutureBoxラップ
|
||||
|
||||
---
|
||||
|
||||
**革命完了**: これ以上シンプルにできない究極の設計!
|
||||
55
docs/reference/plugin-system/nyash-toml-v2_1-spec.md
Normal file
55
docs/reference/plugin-system/nyash-toml-v2_1-spec.md
Normal file
@ -0,0 +1,55 @@
|
||||
# nyash.toml v2.1 拡張仕様(最小)
|
||||
|
||||
目的: プラグインBoxのメソッド引数として、他のBoxを不透明参照(BoxRef)で安全に受け渡す。
|
||||
|
||||
## 変更点(v2 → v2.1)
|
||||
- メソッド引数型に `kind = "box"` を追加(当面は `category = "plugin"` のみ)
|
||||
- TLVに BoxRef(Handle)を追加(tag = 8)
|
||||
- payload: `type_id: u32 (LE)`, `instance_id: u32 (LE)`(合計8バイト)
|
||||
- 既存タグは不変:1=Int64, 2=String(UTF-8), 3=Bool
|
||||
|
||||
## 例: libraries セクション
|
||||
```toml
|
||||
[libraries]
|
||||
[libraries."libnyash_filebox_plugin.so"]
|
||||
boxes = ["FileBox"]
|
||||
path = "./target/release/libnyash_filebox_plugin.so"
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox]
|
||||
type_id = 6
|
||||
abi_version = 1
|
||||
|
||||
[libraries."libnyash_filebox_plugin.so".FileBox.methods]
|
||||
# 既存
|
||||
birth = { method_id = 0 }
|
||||
open = { method_id = 1 }
|
||||
close = { method_id = 4 }
|
||||
|
||||
# 追加例: Box引数を1つ受け取る
|
||||
copyFrom = { method_id = 7, args = [ { kind = "box", category = "plugin" } ] }
|
||||
```
|
||||
|
||||
備考:
|
||||
- `args` を省略した場合は引数なし(ゼロ引数)とみなす(v2互換)
|
||||
- 複数引数は配列で列挙(例: 2引数なら2要素)
|
||||
- ユーザー定義Boxや複雑なビルトインBoxは当面対象外(将来のvtable/retain-release設計で拡張)
|
||||
|
||||
## 呼び出し時のTLVエンコード
|
||||
- 先頭ヘッダ `[ver:1, argc:1, rsv:2]` の後、各引数を `tag + payload` で列挙
|
||||
- `tag=8 (Handle/BoxRef)`: payload = `type_id(4) + instance_id(4)` (LE)
|
||||
- 未対応Box種別に対してはエラーを返す(開発時のみ toString フォールバックを許容可能)
|
||||
|
||||
## 戻り値(v2.1→v2.2)
|
||||
- v2.1: Int/String/Bool(1/6/3)とVoid(9)
|
||||
- v2.2: BoxRef(Handle, tag=8) の「返り値」対応を追加(同一/別Box型どちらも可)
|
||||
- payload: `type_id:u32` + `instance_id:u32`
|
||||
- Loaderは `type_id` から `lib_name/box_name` を逆引きし、`PluginBoxV2` を生成して返す
|
||||
|
||||
## 互換性
|
||||
- `args` 宣言がない既存v2設定はそのまま利用可
|
||||
- BoxRefを使わないメソッドは従来通り Int/String/Bool のみで動作
|
||||
|
||||
## 実装メモ(参考)
|
||||
- Loader: invoke時の引数エンコードに `tag=4` を追加(`category=plugin` のみ)
|
||||
- プラグイン側: 受領した `type_id` と期待型を照合し、不一致ならエラー
|
||||
- 所有権: 呼び出し中の一時借用(保持は将来の retain/release で対応)
|
||||
437
docs/reference/plugin-system/plugin-system.md
Normal file
437
docs/reference/plugin-system/plugin-system.md
Normal file
@ -0,0 +1,437 @@
|
||||
# Nyash Box プラグインシステム設計
|
||||
|
||||
> ⚠️ **DEPRECATED - 将来構想**
|
||||
>
|
||||
> この文書はYAML DSLを使った将来的なプラグインシステム構想です。
|
||||
> **現在の実装とは異なります。**
|
||||
>
|
||||
> **実際に動作している仕様については、以下を参照してください:**
|
||||
> - [BID-FFI v1 実装仕様書](./bid-ffi-v1-actual-specification.md) - 現在動作中の仕様
|
||||
> - [nyash.toml設定例](../../../../nyash.toml) - 実際の設定形式
|
||||
> - [plugin_loader_v2.rs](../../../../src/runtime/plugin_loader_v2.rs) - 実装詳細
|
||||
|
||||
## 概要
|
||||
|
||||
Nyashの「Everything is Box」哲学を維持しながら、Boxの実装をプラグイン化できるシステム。ビルトインBoxとプラグインBoxを透過的に切り替え可能。
|
||||
|
||||
## 🎯 設計原則
|
||||
|
||||
1. **シンプル** - 設定ファイル1つで切り替え
|
||||
2. **透過的** - Nyashコードの変更不要
|
||||
3. **統一的** - ビルトインもプラグインも同じBox
|
||||
|
||||
## 📋 プラグイン定義(YAML署名DSL)
|
||||
|
||||
```yaml
|
||||
# filebox.plugin.yaml
|
||||
schema: 1
|
||||
plugin:
|
||||
name: filebox
|
||||
version: 1
|
||||
|
||||
apis:
|
||||
# 静的メソッド(::)
|
||||
- sig: "FileBox::open(path: string, mode?: string) -> FileBox"
|
||||
doc: "Open a file with optional mode"
|
||||
|
||||
- sig: "FileBox::exists(path: string) -> bool"
|
||||
doc: "Check if file exists"
|
||||
|
||||
# インスタンスメソッド(#)
|
||||
- sig: "FileBox#read(size?: int) -> string"
|
||||
doc: "Read file content"
|
||||
|
||||
- sig: "FileBox#write(content: string) -> int"
|
||||
doc: "Write to file"
|
||||
|
||||
- sig: "FileBox#close() -> void"
|
||||
doc: "Close file handle"
|
||||
```
|
||||
|
||||
### 署名DSL仕様
|
||||
|
||||
- **静的メソッド**: `Type::method()` - C++風の`::`記法
|
||||
- **インスタンスメソッド**: `Type#method()` - Ruby風の`#`記法
|
||||
- **オプショナル引数**: `arg?: type` - `?`サフィックス
|
||||
- **戻り値**: `-> type` - 矢印記法
|
||||
|
||||
### 🔄 Boxライフサイクル管理
|
||||
|
||||
```yaml
|
||||
lifecycle:
|
||||
# コンストラクタ(生命を与える)
|
||||
- sig: "FileBox#birth(path: string, mode?: string)"
|
||||
doc: "Box creation - called after memory allocation"
|
||||
|
||||
# デストラクタ(生命を終える)
|
||||
- sig: "FileBox#fini()"
|
||||
doc: "Box destruction - called before memory deallocation"
|
||||
```
|
||||
|
||||
**重要な原則**:
|
||||
- `birth()` - Boxインスタンス作成時に呼ばれる(メモリ割り当て後)
|
||||
- `fini()` - Boxインスタンス破棄時に呼ばれる(メモリ解放前)
|
||||
- プラグインが割り当てたメモリはプラグインが解放する責任を持つ
|
||||
|
||||
## 🔧 設定ファイル(nyash.toml)
|
||||
|
||||
### 基本形式(v1) - 単一Box型プラグイン
|
||||
|
||||
```toml
|
||||
# プロジェクトルートのnyash.toml
|
||||
[plugins]
|
||||
FileBox = "nyash-filebox-plugin" # FileBoxはプラグイン版を使用
|
||||
# StringBox = "mystring" # コメントアウト = ビルトイン使用
|
||||
|
||||
# FileBoxの型情報定義
|
||||
[plugins.FileBox.methods]
|
||||
read = { args = [] }
|
||||
write = { args = [{ from = "string", to = "bytes" }] }
|
||||
open = { args = [
|
||||
{ name = "path", from = "string", to = "string" },
|
||||
{ name = "mode", from = "string", to = "string" }
|
||||
] }
|
||||
close = { args = [] }
|
||||
exists = { args = [], returns = "bool" }
|
||||
```
|
||||
|
||||
### 拡張形式(v2) - マルチBox型プラグイン
|
||||
|
||||
```toml
|
||||
# 1つのプラグインで複数のBox型を提供
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox", "HttpClientBox"]
|
||||
}
|
||||
|
||||
"nyash-stdlib" = {
|
||||
plugin_path = "libnyash_stdlib.so",
|
||||
provides = ["MathBox", "TimeBox", "RandomBox"]
|
||||
}
|
||||
|
||||
# 各Box型の詳細定義
|
||||
[plugins.types.SocketBox]
|
||||
library = "nyash-network"
|
||||
type_id = 100
|
||||
methods = {
|
||||
bind = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
connect = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
read = { args = [], returns = "string" },
|
||||
write = { args = [{ from = "string", to = "bytes" }] },
|
||||
close = { args = [] }
|
||||
}
|
||||
|
||||
[plugins.types.HTTPServerBox]
|
||||
library = "nyash-network"
|
||||
type_id = 101
|
||||
methods = {
|
||||
bind = { args = [
|
||||
{ name = "address", from = "string", to = "string" },
|
||||
{ name = "port", from = "integer", to = "u16" }
|
||||
]},
|
||||
route = { args = [
|
||||
{ name = "path", from = "string", to = "string" },
|
||||
{ name = "method", from = "string", to = "string" }
|
||||
]},
|
||||
start = { args = [] }
|
||||
}
|
||||
|
||||
[plugins.types.HttpClientBox]
|
||||
library = "nyash-network"
|
||||
type_id = 102
|
||||
methods = {
|
||||
get = { args = [{ name = "url", from = "string", to = "string" }], returns = "string" },
|
||||
post = { args = [
|
||||
{ name = "url", from = "string", to = "string" },
|
||||
{ name = "body", from = "string", to = "string" }
|
||||
], returns = "string" }
|
||||
}
|
||||
```
|
||||
|
||||
### 型マッピング仕様
|
||||
|
||||
#### 基本型
|
||||
| Nyash型 | FFI型 | TLVタグ | 説明 |
|
||||
|---------|-------|---------|------|
|
||||
| `string` | `string` | 0x01 | UTF-8文字列 |
|
||||
| `integer` | `i64` | 0x02 | 64ビット整数 |
|
||||
| `float` | `f64` | 0x03 | 64ビット浮動小数点 |
|
||||
| `bool` | `bool` | 0x04 | 真偽値 |
|
||||
| `bytes` | `Vec<u8>` | 0x05 | バイト配列 |
|
||||
|
||||
|
||||
### プラグイン検索パス
|
||||
|
||||
```toml
|
||||
[plugin_paths]
|
||||
search_paths = [
|
||||
"./plugins/*/target/release", # 開発時リリースビルド
|
||||
"./plugins/*/target/debug", # 開発時デバッグビルド
|
||||
"/usr/local/lib/nyash/plugins", # システムインストール
|
||||
"~/.nyash/plugins" # ユーザーローカル
|
||||
]
|
||||
```
|
||||
|
||||
## 🏗️ アーキテクチャ
|
||||
|
||||
### 1. Boxレジストリ(v2対応版)
|
||||
|
||||
```rust
|
||||
// 起動時の動作
|
||||
let mut registry = HashMap::new();
|
||||
let mut loaded_plugins = HashMap::new();
|
||||
|
||||
// 1. ビルトインBoxを登録
|
||||
registry.insert("FileBox", BoxProvider::Builtin(native_filebox));
|
||||
registry.insert("StringBox", BoxProvider::Builtin(native_stringbox));
|
||||
|
||||
// 2. nyash.toml読み込み
|
||||
let config = parse_nyash_toml_v2()?;
|
||||
|
||||
// 3a. v1形式:単一Box型プラグイン
|
||||
for (box_name, plugin_name) in config.plugins {
|
||||
registry.insert(box_name, BoxProvider::Plugin(plugin_name));
|
||||
}
|
||||
|
||||
// 3b. v2形式:マルチBox型プラグイン
|
||||
if let Some(libraries) = config.libraries {
|
||||
for (lib_name, lib_def) in libraries.libraries {
|
||||
// プラグインを一度だけロード
|
||||
let plugin = load_plugin(&lib_def.plugin_path)?;
|
||||
loaded_plugins.insert(lib_name.clone(), plugin);
|
||||
|
||||
// 提供する全Box型を登録
|
||||
for box_type in &lib_def.provides {
|
||||
registry.insert(box_type, BoxProvider::MultiPlugin(lib_name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### マルチBox型プラグインFFI
|
||||
|
||||
```c
|
||||
// v2プラグインの追加エクスポート関数
|
||||
// 提供するBox型の数を返す
|
||||
extern "C" u32 nyash_plugin_get_box_count();
|
||||
|
||||
// 各Box型の情報を取得
|
||||
extern "C" NyashPluginInfo* nyash_plugin_get_box_info(u32 index);
|
||||
|
||||
// Box型名からtype_idを解決
|
||||
extern "C" u32 nyash_plugin_get_type_id(const char* box_name);
|
||||
```
|
||||
|
||||
### 2. 透過的なディスパッチ
|
||||
|
||||
```nyash
|
||||
# Nyashコード(変更不要!)
|
||||
local file = new FileBox("test.txt")
|
||||
file.write("Hello, plugin!")
|
||||
local content = file.read()
|
||||
```
|
||||
|
||||
内部動作:
|
||||
1. `new FileBox` → レジストリ検索
|
||||
2. `BoxProvider::Plugin("filebox")` → プラグインロード
|
||||
3. BID-FFI経由で実行
|
||||
|
||||
### 3. PluginBoxプロキシ
|
||||
|
||||
```rust
|
||||
// すべてのプラグインBoxの統一インターフェース
|
||||
pub struct PluginBox {
|
||||
plugin_name: String,
|
||||
handle: BidHandle, // プラグイン内のインスタンス
|
||||
}
|
||||
|
||||
impl NyashBox for PluginBox {
|
||||
// NyashBoxトレイトの全メソッドを
|
||||
// FFI経由でプラグインに転送
|
||||
}
|
||||
```
|
||||
|
||||
## 📦 プラグイン実装例
|
||||
|
||||
```c
|
||||
// plugins/filebox/src/filebox.c
|
||||
#include "nyash_plugin_api.h"
|
||||
|
||||
// インスタンス管理
|
||||
typedef struct {
|
||||
FILE* fp;
|
||||
char* buffer; // プラグインが管理するバッファ
|
||||
} FileBoxInstance;
|
||||
|
||||
// birth - Boxに生命を与える
|
||||
i32 filebox_birth(u32 instance_id, const u8* args, size_t args_len) {
|
||||
// 引数からpath, modeを取得
|
||||
const char* path = extract_string_arg(args, 0);
|
||||
const char* mode = extract_string_arg(args, 1);
|
||||
|
||||
// インスタンス作成
|
||||
FileBoxInstance* instance = malloc(sizeof(FileBoxInstance));
|
||||
instance->fp = fopen(path, mode);
|
||||
instance->buffer = NULL;
|
||||
|
||||
// インスタンスを登録
|
||||
register_instance(instance_id, instance);
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
// fini - Boxの生命を終える
|
||||
i32 filebox_fini(u32 instance_id) {
|
||||
FileBoxInstance* instance = get_instance(instance_id);
|
||||
if (!instance) return NYB_E_INVALID_HANDLE;
|
||||
|
||||
// プラグインが割り当てたメモリを解放
|
||||
if (instance->buffer) {
|
||||
free(instance->buffer);
|
||||
}
|
||||
|
||||
// ファイルハンドルをクローズ
|
||||
if (instance->fp) {
|
||||
fclose(instance->fp);
|
||||
}
|
||||
|
||||
// インスタンス自体を解放
|
||||
free(instance);
|
||||
unregister_instance(instance_id);
|
||||
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
|
||||
// read - バッファはプラグインが管理
|
||||
i32 filebox_read(u32 instance_id, i32 size, u8** result, size_t* result_len) {
|
||||
FileBoxInstance* instance = get_instance(instance_id);
|
||||
|
||||
// 既存バッファを解放して新規割り当て
|
||||
if (instance->buffer) free(instance->buffer);
|
||||
instance->buffer = malloc(size + 1);
|
||||
|
||||
// ファイル読み込み
|
||||
size_t read = fread(instance->buffer, 1, size, instance->fp);
|
||||
instance->buffer[read] = '\0';
|
||||
|
||||
// プラグインが所有するメモリを返す
|
||||
*result = instance->buffer;
|
||||
*result_len = read;
|
||||
|
||||
return NYB_SUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 メモリ管理の原則
|
||||
|
||||
### 所有権ルール
|
||||
1. **プラグインが割り当てたメモリ**
|
||||
- プラグインが`malloc()`したメモリはプラグインが`free()`する
|
||||
- `fini()`メソッドで確実に解放する
|
||||
- Nyash側は読み取りのみ(書き込み禁止)
|
||||
|
||||
2. **Nyashが割り当てたメモリ**
|
||||
- Nyashが提供したバッファはNyashが管理
|
||||
- プラグインは読み書き可能だが解放禁止
|
||||
- 引数として渡されたメモリはread-only
|
||||
|
||||
3. **ライフサイクル保証**
|
||||
- `birth()` → 各メソッド呼び出し → `fini()` の順序を保証
|
||||
- `fini()`は必ず呼ばれる(GC時またはプログラム終了時)
|
||||
- 循環参照による`fini()`遅延に注意
|
||||
|
||||
### Nyash側の実装
|
||||
```rust
|
||||
impl Drop for PluginBox {
|
||||
fn drop(&mut self) {
|
||||
// Boxが破棄される時、必ずfiniを呼ぶ
|
||||
let result = self.plugin.invoke(
|
||||
self.handle.type_id,
|
||||
FINI_METHOD_ID, // 最大値のmethod_id
|
||||
self.handle.instance_id,
|
||||
&[], // no arguments
|
||||
&mut []
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
eprintln!("Warning: fini failed for instance {}", self.handle.instance_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 段階的導入計画
|
||||
|
||||
### Phase 1: 基本実装(完了)
|
||||
- [x] BID-FFI基盤
|
||||
- [x] FileBoxプラグイン実装
|
||||
- [x] nyash.toml v1パーサー
|
||||
- [x] PluginBoxプロキシ
|
||||
- [x] プラグインロード機能
|
||||
|
||||
### Phase 2: マルチBox型対応(進行中)
|
||||
- [ ] nyash.toml v2パーサー実装
|
||||
- [ ] マルチBox型プラグインFFI拡張
|
||||
- [ ] plugin-testerの複数Box型対応
|
||||
- [ ] ネットワーク系プラグイン統合
|
||||
- HttpClientBox(新規実装)
|
||||
- SocketBox(既存移行)
|
||||
- HTTPServerBox(既存移行)
|
||||
- HTTPRequestBox(既存移行)
|
||||
- HTTPResponseBox(既存移行)
|
||||
|
||||
### Phase 3: 開発体験向上
|
||||
- [ ] YAMLからFFIコード自動生成
|
||||
- [ ] エラーメッセージ改善
|
||||
- [ ] プラグインテンプレート
|
||||
- [ ] ホットリロード対応
|
||||
|
||||
### Phase 4: エコシステム
|
||||
- [ ] プラグインレジストリ
|
||||
- [ ] バージョン管理
|
||||
- [ ] 依存関係解決
|
||||
- [ ] プラグイン間通信
|
||||
|
||||
## 🎉 利点
|
||||
|
||||
### v1形式の利点
|
||||
1. **ビルド時間短縮** - 使わないBoxはコンパイル不要
|
||||
2. **動的拡張** - 再コンパイルなしで新Box追加
|
||||
3. **Everything is Box維持** - 哲学は変わらない
|
||||
4. **段階的移行** - 1つずつBoxをプラグイン化
|
||||
|
||||
### v2形式の追加利点
|
||||
5. **依存関係の解決** - 関連Box群を1つのプラグインに
|
||||
6. **効率的な配布** - 複数Box型を1ライブラリで提供
|
||||
7. **メモリ効率** - 共有ライブラリは1度だけロード
|
||||
8. **内部連携** - 同一プラグイン内で直接通信可能
|
||||
|
||||
### 実例:HTTPServerBoxの依存問題解決
|
||||
|
||||
```toml
|
||||
# v1では困難だった構成
|
||||
# HTTPServerBoxはSocketBoxに依存するが...
|
||||
[plugins]
|
||||
SocketBox = "socket-plugin" # 別プラグイン
|
||||
HTTPServerBox = "http-plugin" # SocketBoxが使えない!
|
||||
|
||||
# v2なら簡単に解決
|
||||
[plugins.libraries]
|
||||
"nyash-network" = {
|
||||
plugin_path = "libnyash_network.so",
|
||||
provides = ["SocketBox", "HTTPServerBox", "HTTPRequestBox", "HTTPResponseBox"]
|
||||
}
|
||||
# HTTPServerBoxは同じプラグイン内でSocketBoxを直接使用可能
|
||||
```
|
||||
|
||||
## 📚 関連ドキュメント
|
||||
|
||||
- [BID-FFI仕様](./ffi-abi-specification.md)
|
||||
- [Everything is Box哲学](./everything-is-box.md)
|
||||
- [実装タスク](../../../予定/native-plan/issues/phase_9_75g_0_chatgpt_enhanced_final.md)
|
||||
66
docs/reference/plugin-system/plugin-tester.md
Normal file
66
docs/reference/plugin-system/plugin-tester.md
Normal file
@ -0,0 +1,66 @@
|
||||
Nyash Plugin Tester - 開発者向けツールガイド
|
||||
|
||||
概要
|
||||
- 目的: Nyash用プラグイン(BID-FFI準拠)の基本健全性を素早く診断するツール。
|
||||
- 実装場所: `tools/plugin-tester`
|
||||
- 想定対象: C ABIで `nyash_plugin_*` をエクスポートする動的ライブラリ(.so/.dll/.dylib)
|
||||
|
||||
ビルド
|
||||
- コマンド: `cd tools/plugin-tester && cargo build --release`
|
||||
- 実行ファイル: `tools/plugin-tester/target/release/plugin-tester`
|
||||
|
||||
サブコマンド
|
||||
- `check <plugin>`: プラグインのロード、ABI確認、init呼び出し、型名・メソッド一覧の表示
|
||||
- `lifecycle <plugin>`: birth→fini の往復テスト(インスタンスIDを返すことを確認)
|
||||
- `io <plugin>`: FileBox向けE2E(open→write→close→open→read)テスト
|
||||
|
||||
使用例
|
||||
- チェック:
|
||||
- `tools/plugin-tester/target/release/plugin-tester check plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.so`
|
||||
- 期待出力例:
|
||||
- `ABI version: 1`
|
||||
- `Plugin initialized`
|
||||
- `Box Type: FileBox (ID: 6)` と 6メソッド(birth/open/read/write/close/fini)の列挙
|
||||
- ライフサイクル:
|
||||
- `tools/plugin-tester/target/release/plugin-tester lifecycle <path-to-plugin>`
|
||||
- 期待出力例: `birth → instance_id=1`, `fini → instance 1 cleaned`
|
||||
- ファイルI/O:
|
||||
- `tools/plugin-tester/target/release/plugin-tester io <path-to-plugin>`
|
||||
- 期待出力例: `open(w)`, `write 25 bytes`, `open(r)`, `read 25 bytes → 'Hello from plugin-tester!'`
|
||||
|
||||
BID-FFI 前提(v1)
|
||||
- 必須シンボル: `nyash_plugin_abi`, `nyash_plugin_init`, `nyash_plugin_invoke`, `nyash_plugin_shutdown`
|
||||
- 返却コード: 0=成功, -1=ShortBuffer(2段階応答), -2=InvalidType, -3=InvalidMethod, -4=InvalidArgs, -5=PluginError, -8=InvalidHandle
|
||||
- 2段階応答: `result`がNULLまたは小さい場合は `*result_len` に必要サイズを設定し -1 を返す(副作用なし)
|
||||
|
||||
TLV(Type-Length-Value)概要(簡易)
|
||||
- ヘッダ: `u16 version (=1)`, `u16 argc`
|
||||
- エントリ: `u8 tag`, `u8 reserved(0)`, `u16 size`, `payload...`
|
||||
- 主なタグ: 1=Bool, 2=I32, 3=I64, 4=F32, 5=F64, 6=String, 7=Bytes, 8=Handle(u64), 9=Void
|
||||
- plugin-testerの `io` は最小限のTLVエンコード/デコードを内蔵
|
||||
|
||||
プラグイン例(FileBox)
|
||||
- 実装場所: `plugins/nyash-filebox-plugin`
|
||||
- メソッドID: 0=birth, 1=open, 2=read, 3=write, 4=close, 0xFFFF_FFFF=fini
|
||||
- `open(path, mode)`: 引数は TLV(String, String)、返り値は TLV(Void)
|
||||
- `read(size)`: 引数 TLV(I32)、返 TLV(Bytes)
|
||||
- `write(bytes)`: 引数 TLV(Bytes)、返 TLV(I32: 書き込みバイト数)
|
||||
- `close()`: 返 TLV(Void)
|
||||
|
||||
パスの指定(例)
|
||||
- Linux: `plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.so`
|
||||
- Windows: `plugins\nyash-filebox-plugin\target\release\nyash_filebox_plugin.dll`
|
||||
- macOS: `plugins/nyash-filebox-plugin/target/release/libnyash_filebox_plugin.dylib`
|
||||
|
||||
トラブルシュート
|
||||
- `nyash_plugin_abi not found`: ビルド設定(cdylib)やシンボル名を再確認
|
||||
- `ShortBuffer`が返るのにデータが取れない: 2回目の呼び出しで `result` と `*result_len` を適切に設定しているか確認
|
||||
- 読み出しサイズが0: 書き込み後に `close`→`open(r)` してから `read` を実行しているか確認
|
||||
|
||||
関連ドキュメント
|
||||
- `docs/CURRENT_TASK.md`(現在の進捗)
|
||||
- `docs/予定/native-plan/issues/phase_9_75g_bid_integration_architecture.md`(設計計画)
|
||||
|
||||
備考
|
||||
- 本説明書は `C:\git\nyash-project\nyash\docs\説明書\reference\plugin-tester.md` に配置されます(Windowsパス例)。
|
||||
|
||||
503
docs/reference/plugin-system/vm-plugin-integration.md
Normal file
503
docs/reference/plugin-system/vm-plugin-integration.md
Normal file
@ -0,0 +1,503 @@
|
||||
# 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(構成ファイル全体から探索)
|
||||
Reference in New Issue
Block a user