diff --git a/docs/CURRENT_TASK.md b/docs/CURRENT_TASK.md index a13b23e4..18c10e52 100644 --- a/docs/CURRENT_TASK.md +++ b/docs/CURRENT_TASK.md @@ -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問題解決 + +### ✅ **完了タスク** +- FFI-ABI file実装(stdlib方式) +- MIRビルダーfile.read/write追加 +- Gemini先生への相談・回答取得 + +### 🚀 **次のステップ** +1. インタープリターでExternCall直接実行 +2. FileBoxの元実装を探して置き換え +3. プラグインアーキテクチャ基盤構築 ## 🌐 **WASM研究メモ** diff --git a/docs/予定/native-plan/issues/phase_9_75f_dynamic_library_architecture.md b/docs/予定/native-plan/issues/phase_9_75f_dynamic_library_architecture.md new file mode 100644 index 00000000..9075bde7 --- /dev/null +++ b/docs/予定/native-plan/issues/phase_9_75f_dynamic_library_architecture.md @@ -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は直接共有不可→ハンドルパターン使用 + +## 🚀 実装計画 + +### Step 1: インタープリターExternCall(即実装可能) +```rust +// interpreter/expressions.rs +impl NyashInterpreter { + fn execute_extern_call(&mut self, + iface: &str, + method: &str, + args: Vec>) + -> Result, 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) \ No newline at end of file diff --git a/gemini_consultation.txt b/gemini_consultation.txt new file mode 100644 index 00000000..1fc4b06c --- /dev/null +++ b/gemini_consultation.txt @@ -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の境界越え + - Arcの共有は可能? + +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哲学を維持しつつ、モジュラーで高速なビルドを実現したいです。実装経験に基づいた具体的なアドバイスをお願いします。 \ No newline at end of file diff --git a/local_tests/test_file_ffi.nyash b/local_tests/test_file_ffi.nyash new file mode 100644 index 00000000..98c8f681 --- /dev/null +++ b/local_tests/test_file_ffi.nyash @@ -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!") \ No newline at end of file diff --git a/local_tests/test_stdlib_file.nyash b/local_tests/test_stdlib_file.nyash new file mode 100644 index 00000000..04bab311 --- /dev/null +++ b/local_tests/test_stdlib_file.nyash @@ -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 \ No newline at end of file diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 2cee5c6e..024c4183 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -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 } diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index 83d55f37..f925ee44 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -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::() { + // 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::(), + args[1].as_any().downcast_ref::()) { + // 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::() { + // 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 + } } \ No newline at end of file