feat(phase-9.75f): ビルトインBox動的ライブラリ分離アーキテクチャ設計
- Gemini先生のアドバイスに基づく実装計画策定 - C ABI + libloading による安定した動的ライブラリ実装 - 段階的移行戦略:インタープリターExternCall → プラグイン化 - FFI-ABI file実装(stdlib方式)完了 - MIRビルダーにfile.read/write/exists追加 - ビルド時間2分→15秒、バイナリ15MB→2MBを目標 - docs/予定/native-plan/issues/phase_9_75f_dynamic_library_architecture.md作成 - CURRENT_TASK.md更新 次のステップ: - インタープリターでExternCall直接実行実装 - 元のFileBox実装を探して置き換え - プラグインアーキテクチャ基盤構築
This commit is contained in:
@ -361,8 +361,29 @@ if let TokenType::IDENTIFIER(id) = &self.current_token().token_type {
|
||||
- パフォーマンステスト基盤
|
||||
|
||||
---
|
||||
**現在状況**: 🔬 **WASM Canvas機能研究中**
|
||||
**最終更新**: 2025-08-16 22:30
|
||||
**現在状況**: 🚀 **Phase 9.75f ビルトインBox動的ライブラリ分離開始!**
|
||||
**最終更新**: 2025-08-17 03:30
|
||||
|
||||
## 🔥 **Phase 9.75f: 緊急ビルド時間改善**
|
||||
|
||||
### 🎯 **動的ライブラリ化によるビルド革命**
|
||||
- **現状**: 16個のビルトインBox静的リンク → 2分以上のビルド
|
||||
- **目標**: コア2MB + 動的ライブラリ → 15秒ビルド
|
||||
|
||||
### 📋 **Gemini先生の推奨実装**
|
||||
1. **C ABI + libloading**: 最も安定した方法
|
||||
2. **段階的移行**: インタープリターExternCall → プラグイン化
|
||||
3. **メモリ管理**: ハンドルパターンでArc<RwLock>問題解決
|
||||
|
||||
### ✅ **完了タスク**
|
||||
- FFI-ABI file実装(stdlib方式)
|
||||
- MIRビルダーfile.read/write追加
|
||||
- Gemini先生への相談・回答取得
|
||||
|
||||
### 🚀 **次のステップ**
|
||||
1. インタープリターでExternCall直接実行
|
||||
2. FileBoxの元実装を探して置き換え
|
||||
3. プラグインアーキテクチャ基盤構築
|
||||
|
||||
## 🌐 **WASM研究メモ**
|
||||
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
# Phase 9.75f: ビルトインBox動的ライブラリ分離アーキテクチャ
|
||||
|
||||
## 🎯 目的
|
||||
- ビルド時間を2分→15秒に短縮
|
||||
- バイナリサイズを15MB→2MBに削減
|
||||
- Box単位での独立開発を可能に
|
||||
|
||||
## 📋 Gemini先生からのアドバイス
|
||||
|
||||
### 1. **C ABI + libloading が最も安定**
|
||||
```rust
|
||||
#[no_mangle]
|
||||
extern "C" fn nyash_file_read(path: *const c_char) -> *mut c_char {
|
||||
// 実装
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **段階的移行戦略**
|
||||
- Phase 1: インタープリターでExternCall直接実行
|
||||
- Phase 2: FileBox/ConsoleBoxをプラグイン化
|
||||
- Phase 3: 残りのBox順次移行
|
||||
|
||||
### 3. **メモリ管理の重要性**
|
||||
- 所有権ルールを明確に
|
||||
- データ生成側が解放関数も提供
|
||||
- Arc<RwLock>は直接共有不可→ハンドルパターン使用
|
||||
|
||||
## 🚀 実装計画
|
||||
|
||||
### Step 1: インタープリターExternCall(即実装可能)
|
||||
```rust
|
||||
// interpreter/expressions.rs
|
||||
impl NyashInterpreter {
|
||||
fn execute_extern_call(&mut self,
|
||||
iface: &str,
|
||||
method: &str,
|
||||
args: Vec<Box<dyn NyashBox>>)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
match (iface, method) {
|
||||
("env.file", "read") => {
|
||||
// 直接実行
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: プラグインAPI定義
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct PluginAPI {
|
||||
pub version: u32,
|
||||
pub name: *const c_char,
|
||||
pub methods: *const MethodTable,
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: ワークスペース構成
|
||||
```toml
|
||||
[workspace]
|
||||
members = [
|
||||
"nyash-core", # 2MB
|
||||
"nyash-plugin-api", # 共通API
|
||||
"plugins/io", # FileBox, ConsoleBox
|
||||
"plugins/web", # CanvasBox
|
||||
]
|
||||
```
|
||||
|
||||
## ⚠️ 注意点
|
||||
- プラグイン間の直接依存は避ける
|
||||
- セキュリティ考慮(信頼できるソースのみ)
|
||||
- クロスプラットフォーム対応(.so/.dll/.dylib)
|
||||
85
gemini_consultation.txt
Normal file
85
gemini_consultation.txt
Normal file
@ -0,0 +1,85 @@
|
||||
Nyashプログラミング言語のビルド時間改善について深い技術相談です。
|
||||
|
||||
【背景と問題】
|
||||
Nyashは「Everything is Box」哲学のRust製言語で、現在16個のビルトインBox(StringBox, IntegerBox, ConsoleBox, P2PBox等)が静的リンクされています。
|
||||
問題:
|
||||
- cargo buildに2分以上(wasmtime等の巨大依存を含む)
|
||||
- 新Box追加のたびに全体再コンパイル
|
||||
- バイナリサイズ15MB
|
||||
- 開発効率が著しく低下
|
||||
|
||||
【革新的提案:動的ライブラリ分離アーキテクチャ】
|
||||
すでに実装済みのFFI-ABI/ExternCall機構を活用し、ビルトインBoxを動的ライブラリ化する。
|
||||
|
||||
1. **nyash-core(2MB)**
|
||||
- インタープリター本体
|
||||
- 基本型のみ(String/Integer/Bool)
|
||||
- ExternCallディスパッチャー
|
||||
- FFI-ABIランタイム
|
||||
|
||||
2. **プラグイン構成**
|
||||
- libnyash_io.so: FileBox, ConsoleBox
|
||||
- libnyash_web.so: CanvasBox, WebDisplayBox
|
||||
- libnyash_p2p.so: P2PBox, IntentBox
|
||||
- libnyash_math.so: MathBox, RandomBox
|
||||
|
||||
3. **統一インターフェース**
|
||||
```rust
|
||||
// すでにあるExternCall命令を活用
|
||||
ExternCall { iface: "env.file", method: "read", args: [...] }
|
||||
```
|
||||
- WASM: RuntimeImports経由
|
||||
- VM: スタブ実装
|
||||
- ネイティブ: 動的ライブラリ呼び出し
|
||||
- インタープリター: 直接実行(NEW!)
|
||||
|
||||
【技術的検討事項】
|
||||
|
||||
1. **Rust動的ライブラリ設計**
|
||||
- ABI安定性: `#[repr(C)]`使用?それともstable ABI crate?
|
||||
- libloading vs dlopen2 vs abi_stable?
|
||||
- プラグイン発見機構(ディレクトリスキャン vs 明示的登録)
|
||||
|
||||
2. **インターフェース設計**
|
||||
```rust
|
||||
// Option A: C ABI
|
||||
#[no_mangle]
|
||||
extern "C" fn nyash_file_read(path: *const c_char) -> *mut c_char
|
||||
|
||||
// Option B: Rust trait object
|
||||
trait NyashPlugin {
|
||||
fn get_methods(&self) -> &'static [(&'static str, MethodPtr)];
|
||||
}
|
||||
```
|
||||
|
||||
3. **メモリ管理**
|
||||
- 文字列の所有権: 誰がfreeする?
|
||||
- Box<dyn NyashBox>の境界越え
|
||||
- Arc<RwLock>の共有は可能?
|
||||
|
||||
4. **段階的移行戦略**
|
||||
Phase 1: インタープリターでExternCall直接実行(MIR不要)
|
||||
Phase 2: FileBox/ConsoleBoxを最初のプラグインに
|
||||
Phase 3: 残りのBoxも順次移行
|
||||
Phase 4: wasmtime依存をオプショナルに
|
||||
|
||||
5. **クロスプラットフォーム**
|
||||
- Windows: .dll + __declspec(dllexport)
|
||||
- macOS: .dylib + 特殊リンカーフラグ
|
||||
- Linux: .so + RTLD_LAZY
|
||||
- パス解決とセキュリティ
|
||||
|
||||
【期待される成果】
|
||||
- ビルド時間: 120秒 → 15秒(8倍高速化)
|
||||
- バイナリ: 15MB → 2MB(コア部分のみ)
|
||||
- 開発効率: Box単位で独立ビルド・テスト
|
||||
- 配布: 必要なBoxだけ選択的ロード
|
||||
|
||||
【質問】
|
||||
1. Rustで最も安定した動的ライブラリ実装方法は?
|
||||
2. abi_stable crateは本番環境で信頼できるか?
|
||||
3. プラグイン間の依存関係管理のベストプラクティスは?
|
||||
4. セキュリティ(信頼できないプラグイン)の考慮は必要か?
|
||||
5. 実装の落とし穴や注意点は?
|
||||
|
||||
Everything is Box哲学を維持しつつ、モジュラーで高速なビルドを実現したいです。実装経験に基づいた具体的なアドバイスをお願いします。
|
||||
46
local_tests/test_file_ffi.nyash
Normal file
46
local_tests/test_file_ffi.nyash
Normal file
@ -0,0 +1,46 @@
|
||||
// FFI-ABI File I/O test
|
||||
// 純粋FFI-ABI方式でのファイル操作デモ
|
||||
|
||||
using nyashstd
|
||||
|
||||
// テスト用のファイル名
|
||||
local test_file = "test_output.txt"
|
||||
|
||||
// ファイルに書き込み
|
||||
file.write(test_file, "Hello from Nyash FFI-ABI!\nThis is a test of file I/O.")
|
||||
console.log("✅ File written: " + test_file)
|
||||
|
||||
// ファイルの存在確認
|
||||
if file.exists(test_file) {
|
||||
console.log("✅ File exists!")
|
||||
|
||||
// ファイルを読み込み
|
||||
local content = file.read(test_file)
|
||||
if content != null {
|
||||
console.log("✅ File content:")
|
||||
console.log(content)
|
||||
|
||||
// 内容を追記
|
||||
file.write(test_file, content + "\nAdded line at: " + new StringBox("timestamp"))
|
||||
console.log("✅ Content appended")
|
||||
|
||||
// 再度読み込み
|
||||
local updated = file.read(test_file)
|
||||
console.log("✅ Updated content:")
|
||||
console.log(updated)
|
||||
} else {
|
||||
console.log("❌ Failed to read file")
|
||||
}
|
||||
} else {
|
||||
console.log("❌ File does not exist")
|
||||
}
|
||||
|
||||
// 存在しないファイルを読もうとする
|
||||
local missing = file.read("non_existent_file.txt")
|
||||
if missing == null {
|
||||
console.log("✅ Non-existent file correctly returns null")
|
||||
} else {
|
||||
console.log("❌ Unexpected content from non-existent file")
|
||||
}
|
||||
|
||||
console.log("\n🎉 FFI-ABI File I/O test complete!")
|
||||
11
local_tests/test_stdlib_file.nyash
Normal file
11
local_tests/test_stdlib_file.nyash
Normal file
@ -0,0 +1,11 @@
|
||||
// Test stdlib file registration
|
||||
using nyashstd
|
||||
|
||||
// Check available static boxes
|
||||
console.log("Testing stdlib file registration...")
|
||||
|
||||
// Try to access console (should work)
|
||||
console.log("✅ console.log works!")
|
||||
|
||||
// Check if file is available
|
||||
// For now, just try to verify what's available
|
||||
@ -926,6 +926,48 @@ impl MirBuilder {
|
||||
})?;
|
||||
return Ok(void_id);
|
||||
},
|
||||
("file", "read") => {
|
||||
// Generate ExternCall for file.read
|
||||
let result_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: Some(result_id), // file.read returns string
|
||||
iface_name: "env.file".to_string(),
|
||||
method_name: "read".to_string(),
|
||||
args: arg_values,
|
||||
effects: EffectMask::IO, // File operations are I/O
|
||||
})?;
|
||||
return Ok(result_id);
|
||||
},
|
||||
("file", "write") => {
|
||||
// Generate ExternCall for file.write
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: None, // file.write is void
|
||||
iface_name: "env.file".to_string(),
|
||||
method_name: "write".to_string(),
|
||||
args: arg_values,
|
||||
effects: EffectMask::IO, // File operations are I/O
|
||||
})?;
|
||||
|
||||
// Return void value
|
||||
let void_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: void_id,
|
||||
value: ConstValue::Void,
|
||||
})?;
|
||||
return Ok(void_id);
|
||||
},
|
||||
("file", "exists") => {
|
||||
// Generate ExternCall for file.exists
|
||||
let result_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::ExternCall {
|
||||
dst: Some(result_id), // file.exists returns bool
|
||||
iface_name: "env.file".to_string(),
|
||||
method_name: "exists".to_string(),
|
||||
args: arg_values,
|
||||
effects: EffectMask::IO, // File operations are I/O
|
||||
})?;
|
||||
return Ok(result_id);
|
||||
},
|
||||
_ => {
|
||||
// Regular method call - continue with BoxCall
|
||||
}
|
||||
|
||||
@ -65,6 +65,9 @@ impl BuiltinStdlib {
|
||||
// console static box
|
||||
nyashstd.static_boxes.insert("console".to_string(), Self::create_console_box());
|
||||
|
||||
// file static box (FFI-ABI demonstration)
|
||||
nyashstd.static_boxes.insert("file".to_string(), Self::create_file_box());
|
||||
|
||||
self.namespaces.insert("nyashstd".to_string(), nyashstd);
|
||||
}
|
||||
|
||||
@ -222,4 +225,89 @@ impl BuiltinStdlib {
|
||||
|
||||
console_box
|
||||
}
|
||||
|
||||
/// file static boxを作成 (FFI-ABI demonstration)
|
||||
fn create_file_box() -> BuiltinStaticBox {
|
||||
let mut file_box = BuiltinStaticBox {
|
||||
name: "file".to_string(),
|
||||
methods: HashMap::new(),
|
||||
};
|
||||
|
||||
// file.read(path) -> string (or null on error)
|
||||
file_box.methods.insert("read".to_string(), |args| {
|
||||
if args.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: "file.read() takes exactly 1 argument".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
// StringBoxにダウンキャスト
|
||||
if let Some(path_arg) = args[0].as_any().downcast_ref::<StringBox>() {
|
||||
// Rust標準ライブラリでファイル読み込み
|
||||
match std::fs::read_to_string(&path_arg.value) {
|
||||
Ok(content) => Ok(Box::new(StringBox::new(content))),
|
||||
Err(_) => {
|
||||
// エラー時はnullを返す
|
||||
use crate::boxes::NullBox;
|
||||
Ok(Box::new(NullBox::new()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: format!("file.read() expects string argument, got {:?}", args[0].type_name())
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// file.write(path, content) -> void
|
||||
file_box.methods.insert("write".to_string(), |args| {
|
||||
if args.len() != 2 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: "file.write() takes exactly 2 arguments".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
// 両方の引数をStringBoxにダウンキャスト
|
||||
if let (Some(path_arg), Some(content_arg)) =
|
||||
(args[0].as_any().downcast_ref::<StringBox>(),
|
||||
args[1].as_any().downcast_ref::<StringBox>()) {
|
||||
// Rust標準ライブラリでファイル書き込み
|
||||
if let Err(e) = std::fs::write(&path_arg.value, &content_arg.value) {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Failed to write file: {}", e)
|
||||
});
|
||||
}
|
||||
|
||||
// VoidBoxを返す
|
||||
use crate::box_trait::VoidBox;
|
||||
Ok(Box::new(VoidBox::new()))
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "file.write() expects two string arguments".to_string()
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// file.exists(path) -> bool
|
||||
file_box.methods.insert("exists".to_string(), |args| {
|
||||
if args.len() != 1 {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: "file.exists() takes exactly 1 argument".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
// StringBoxにダウンキャスト
|
||||
if let Some(path_arg) = args[0].as_any().downcast_ref::<StringBox>() {
|
||||
// Rust標準ライブラリでファイル存在確認
|
||||
let exists = std::path::Path::new(&path_arg.value).exists();
|
||||
Ok(Box::new(BoolBox::new(exists)))
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: format!("file.exists() expects string argument, got {:?}", args[0].type_name())
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
file_box
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user