diff --git a/.gitignore b/.gitignore index 88433d8c..1fc9466c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,9 @@ dist/ # Rust /target/ +**/target/ Cargo.lock +plugins/**/Cargo.lock # 🔧 Rust開発用ディレクトリ(Git追跡除外) nyash-rust/ @@ -90,6 +92,8 @@ current_task *.log debug_output.txt stats_output.txt +*.jsonl +*.dot # LLVM *.ll diff --git a/compile_only.jsonl b/compile_only.jsonl deleted file mode 100644 index 6e62edd8..00000000 --- a/compile_only.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"kind":"hostcall","function":"","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"} -{"kind":"hostcall","function":"","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh","phase":"lower","reason":"sig_ok"} diff --git a/crates/nyrt/src/lib.rs b/crates/nyrt/src/lib.rs index d88d5ed5..96e78cf3 100644 --- a/crates/nyrt/src/lib.rs +++ b/crates/nyrt/src/lib.rs @@ -54,7 +54,6 @@ pub extern "C" fn nyash_plugin_invoke3_i64( // ---- Handle-based birth shims for AOT/JIT object linkage ---- // These resolve symbols like "nyash.string.birth_h" referenced by ObjectModule. -#[no_mangle] #[export_name = "nyash.string.birth_h"] pub extern "C" fn nyash_string_birth_h_export() -> i64 { // Create a new StringBox via unified plugin host; return runtime handle as i64 @@ -68,7 +67,6 @@ pub extern "C" fn nyash_string_birth_h_export() -> i64 { 0 } -#[no_mangle] #[export_name = "nyash.integer.birth_h"] pub extern "C" fn nyash_integer_birth_h_export() -> i64 { if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() { @@ -84,11 +82,25 @@ pub extern "C" fn nyash_integer_birth_h_export() -> i64 { // ---- Process entry (driver) ---- #[no_mangle] pub extern "C" fn main() -> i32 { - // Initialize plugin host from nyash.toml (if present) - let _ = nyash_rust::runtime::init_global_plugin_host("nyash.toml"); + // Initialize plugin host: prefer nyash.toml next to the executable; fallback to CWD + let exe_dir = std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.to_path_buf())); + let mut inited = false; + if let Some(dir) = &exe_dir { + let candidate = dir.join("nyash.toml"); + if candidate.exists() { + let _ = nyash_rust::runtime::init_global_plugin_host(candidate.to_string_lossy().as_ref()); + inited = true; + } + } + if !inited { + let _ = nyash_rust::runtime::init_global_plugin_host("nyash.toml"); + } // Optional verbosity if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - println!("🔌 nyrt: plugin host init attempted"); + println!("🔌 nyrt: plugin host init attempted (exe_dir={}, cwd={})", + exe_dir.as_ref().map(|p| p.display().to_string()).unwrap_or_else(||"?".into()), + std::env::current_dir().map(|p| p.display().to_string()).unwrap_or_else(|_|"?".into()) + ); } // Call exported Nyash entry if linked: `ny_main` (i64 -> return code normalized) unsafe { diff --git a/docs/development/current/CURRENT_TASK.md b/docs/development/current/CURRENT_TASK.md index bee674b8..300f94bb 100644 --- a/docs/development/current/CURRENT_TASK.md +++ b/docs/development/current/CURRENT_TASK.md @@ -21,14 +21,14 @@ Phase 10.10 は完了(DoD確認済)。**重大な発見**:プラグイン - JitPolicyBox(allowlist/presets)/ HostCallのRO運用(events連携) - CIスモーク導入(runtime/compile-events)/ 代表サンプル整備 - 🔧 Doing(Phase 10.1 新計画) - - ArrayBoxのプラグイン化PoC開始 - - JIT lowering層の統一設計 + - NewBox→birthのJIT lowering(String/Integer、handleベース) + - AOT最小EXE: libnyrt.aシム + ny_main ドライバ + build_aot.sh 整備 - リファクタリング作業は継続(core_hostcall.rs完了) - ⏭️ Next(Phase 10.1 実装) - - Week1: ArrayBoxプラグイン化と性能測定 - - Week2: 主要ビルトインBoxの移行 - - Week3: スタティックリンク基盤構築 - - Week4: EXE生成実証 + - Week1: 主要ビルトインBoxの移行(RO中心) + - Week2: 静的同梱基盤の設計(type_id→nyplug_*_invoke ディスパッチ) + - Week3: ベンチ/観測性整備(JIT fallback理由の粒度) + - Week4: AOT配布体験の改善(nyash.toml/soの探索・ガイド) ## リファクタリング計画(機能差分なし) 1) core_hostcall 分割(イベントlower+emit_host_call周辺) @@ -60,6 +60,29 @@ Phase 10.10 は完了(DoD確認済)。**重大な発見**:プラグイン - 参照: `docs/development/roadmap/phases/phase-10.5/` (移動済み) - ChatGPT5の当初計画を後段フェーズへ +### 進捗(10.5a→10.5b 最小実装) +- 新規: `plugins/nyash-python-plugin/` 追加(ABI v1、動的 `libpython3.x` ローダ) +- Box: `PyRuntimeBox(type_id=40)`, `PyObjectBox(type_id=41)` を `nyash.toml` に登録 +- 実装: `birth/fini`(Runtime), `eval/import`(Handle返却), `getattr`(Handle返却), `call`(int/string/bool/handle対応) `callKw`(kwargs対応・key:stringと値のペア) , `str`(String返却) +- 設計ドキュメント: `docs/development/roadmap/phases/phase-10.5/10.5a-ABI-DESIGN.md` + +### ビルド/テスト結果(2025-08-29) +- ✅ Pythonプラグインビルド成功(警告ありだが動作問題なし) +- ✅ 本体ビルド成功(Cranelift JIT有効) +- ✅ プラグインロード成功(`libnyash_python_plugin.so` 初期化OK) +- ✅ `PyRuntimeBox.birth()` 正常実行(instance_id=1) +- ✅ VM側委譲: `PluginBoxV2` メソッドを `PluginHost.invoke_instance_method` に委譲(BoxCallでも実体plugin_invoke実行) +- ✅ E2Eデモ: `py.import("math").getattr("sqrt").call(9).str()` がVM経路で実行(`examples/py_math_sqrt_demo.nyash`) + +### 方針決定(Built-inとの関係) +- いまはビルトインBoxを削除しない。余計な変更を避け、プラグイン優先の運用で干渉を止める。 +- 例・テスト・CIをプラグイン経路に寄せ、十分に安定してから段階的に外す。 + +### 次アクション(小さく通す) +1) Loaderのエンコード強化(v2): Bool/F64/Bytes(Array)を正式サポート → kwargsや一般引数を堅牢化 +2) 戻り値自動デコード(オプトイン): `NYASH_PY_AUTODECODE=1` 設計(数値/文字列/bytesのTLV変換)。段階的に導入。 +3) 例外のResult化: returns_resultの扱い整理(成功文字列とエラー文字列の判別手段の仕様化) + ## すぐ試せるコマンド(現状維持の確認) ```bash # Build(Cranelift込み推奨) @@ -88,6 +111,38 @@ NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/array_plugin_de NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/array_plugin_set_demo.nyash NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/map_plugin_ro_demo.nyash +# Python plugin demo(Phase 10.5) +(cd plugins/nyash-python-plugin && cargo build --release) +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_eval_demo.nyash +# 追加デバッグ(TLVダンプ) +NYASH_DEBUG_PLUGIN=1 NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_eval_demo.nyash + +# math.sqrtデモ(import→getattr→call→str) +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_math_sqrt_demo.nyash + +# kwargsデモ(builtins.int) +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_kw_round_demo.nyash + +# kwargsデモ(builtins.int) +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_kw_round_demo.nyash + +## AOT最小EXE(New!) +```bash +# 1) .o生成(Cranelift必須) +NYASH_AOT_OBJECT_OUT=target/aot_objects \ +NYASH_USE_PLUGIN_BUILTINS=1 NYASH_JIT_EXEC=1 NYASH_JIT_THRESHOLD=1 \ + ./target/release/nyash --backend vm examples/aot_min_string_len.nyash + +# 2) libnyrtビルド + リンク + 実行(Linux例) +(cd crates/nyrt && cargo build --release) +cc target/aot_objects/main.o -L crates/nyrt/target/release \ + -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o app +./app + +# 3) ワンコマンド +bash tools/build_aot.sh examples/aot_min_string_len.nyash -o app +``` + ## ⏭️ Next(Phase 10.2: JIT実呼び出しの実体化) - 目的: Craneliftの `emit_plugin_invoke` を実装し、JITでも実体のプラグインAPIを呼ぶ - 方針: diff --git a/docs/development/roadmap/phases/phase-10.5/10.5a-ABI-DESIGN.md b/docs/development/roadmap/phases/phase-10.5/10.5a-ABI-DESIGN.md new file mode 100644 index 00000000..812c2b22 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.5/10.5a-ABI-DESIGN.md @@ -0,0 +1,53 @@ +# Phase 10.5a – Python 統合 ABI 設計(Draft) + +目的: Everything is Plugin/AOT の既存基盤上に Python を最小リスクで統合するための ABI と型・メソッド定義を固定する。 + +## スコープ(10.5a) +- v2 プラグイン ABI 準拠(`nyash_plugin_abi/init/invoke`)の Python プラグイン雛形を作成 +- 2 Box を定義: `PyRuntimeBox(type_id=40)`, `PyObjectBox(type_id=41)` +- メソッド ID の割り当てと TLV 方針を文書化(実装は 10.5b 以降) + +## TLV マッピング(現行運用) +- 1 = Bool (1 byte) +- 2 = I32 (4 bytes, LE) +- 3 = I64 (8 bytes, LE) +- 4 = F32 (4 bytes, LE) +- 5 = F64 (8 bytes, LE) +- 6 = String (UTF-8, n bytes) +- 7 = Bytes (opaque, n bytes) +- 8 = Handle/BoxRef (`u32 type_id || u32 instance_id`) + +備考: 既存ドキュメントには古い表記の混在があるため、VM_README 準拠で統一。 + +## Box とメソッド設計 + +### PyRuntimeBox (type_id=40) +- birth(0): ランタイムの生成(後続で GIL 初期化などを担当)。戻り値: `instance_id`(非 TLV, u32 LE) +- eval(1, code: String): Python コードを評価して `PyObjectBox` を返す。戻り値: `Handle(tag=8)` +- import(2, name: String): `__import__(name)` または `importlib.import_module`。戻り値: `Handle(tag=8)` +- fini(MAX): ランタイム破棄(GIL 終了・クリーンアップ) + +### PyObjectBox (type_id=41) +- birth(0): 予約(通常は runtime 側から生まれる) +- getattr(1, name: String): 属性取得 → `Handle(tag=8)` +- call(2, args: TLV...): 可変長引数。初期段は I64/String/Bool/Bytes/Handle のサブセットに限定。戻り値: `Handle(tag=8)` +- str(3): Python 側で `PyObject_Str` → String へ。戻り値: `String(tag=6)` +- fini(MAX): 参照カウント `Py_DECREF` に対応(後続) + +## 参照管理・GIL(概要) +- GIL: birth/invoke/fini の入口で確保し、出口で解放(再入を許容)。 +- 参照: `PyObjectBox` は生成時に `INCREF`、`fini` で `DECREF`。ランタイム終了時に孤立検知の簡易テストを導入。 + +## 設定ファイル(nyash.toml) +- `libnyash_python_plugin.so` を 2 Box 含む形で登録(path/type_id/method_id を固定) +- JIT/VM 側は既存の `plugin_invoke` 経由で呼び出し(AOT は 10.5d で `libnyrt.a` にシム追加) + +## 次フェーズ(10.5b 以降) +- 10.5b: `PyRuntimeBox`/`PyObjectBox` 実装(CPython 埋め込み、最小 RO 経路) +- 10.5c: Python→Nyash 方向(CPython 拡張 `nyashrt`) +- 10.5d: JIT/AOT 連携(`emit_plugin_invoke` 対応・静的リンクシム) +- 10.5e: サンプル・テスト・ドキュメント + +--- + +補足: 本ドキュメントは「設計の固定」を目的とし、実装は段階的に進める。タグ/ID は既存と衝突しない値を選定した(40/41)。 diff --git a/docs/development/roadmap/phases/phase-10.5/README.md b/docs/development/roadmap/phases/phase-10.5/README.md index a986df55..956ceeb2 100644 --- a/docs/development/roadmap/phases/phase-10.5/README.md +++ b/docs/development/roadmap/phases/phase-10.5/README.md @@ -1,80 +1,60 @@ -# Phase 10.5 - PythonParserBox実装 -*(旧Phase 10.1 - プラグインBox統一化の発見により番号変更)* +# Phase 10.5 – Python ネイティブ統合(Embedding & FFI) +*(旧10.1の一部を後段フェーズに再編。Everything is Plugin/AOTの基盤上で実現)* -見ただけで実装手順が分かる!順番通りに進めてください。 +NyashとPythonを双方向に“ネイティブ”接続する。第一段はNyash→Python呼び出し(Embedding)、続いてPython→Nyash(Extending)。JIT/AOT/Pluginの統一面を活かし、最小のC ABIで着地する。 -## 📂 サブフェーズ構成(順番に実行) +## 📂 サブフェーズ構成(10.5a → 10.5e) -### 📋 Phase 10.1a - 計画と設計 -最初にここから!全体像を理解する。 -- 統合実装計画を読む -- エキスパート評価を確認 -- 5つの核心戦略を把握 +### 10.5a 設計・ABI整合(1–2日) +- ルート選択: + - Embedding: NyashプロセスにCPythonを埋め込み、PyObject*をハンドル管理 + - Extending: Python拡張モジュール(nyashrt)を提供し、PythonからNyashを呼ぶ +- ABI方針: + - ハンドル: TLV tag=8(type_id+instance_id)。Pythonオブジェクトは `PyObjectBox` として格納 + - 変換: Nyash ⇄ Python で Bool/I64/String/Bytes/Handle を相互変換 + - GIL: birth/invoke/decRef中はGIL確保。AOTでも同等 -### ⚙️ Phase 10.1b - 環境設定 -開発環境を整える。 -- Python 3.11.9をインストール -- Cargo.tomlに依存関係追加 -- ディレクトリ構造準備 +### 10.5b PyRuntimeBox / PyObjectBox 実装(3–5日) +- `PyRuntimeBox`(シングルトン): `eval(code) -> Handle` / `import(name) -> Handle` +- `PyObjectBox`: `getattr(name) -> Handle` / `call(args...) -> Handle` / `str() -> String` +- 参照管理: `Py_INCREF`/`Py_DECREF` をBoxライフサイクル(fini)に接続 +- プラグイン化: `nyash-python-plugin`(cdylib/staticlib)で `nyplug_python_invoke` を提供(将来の静的同梱に対応) -### 🔧 Phase 10.1c - パーサー統合 -CPythonパーサーをNyashに統合。 -- PythonParserBox実装 -- GIL管理の実装 -- JSON中間表現への変換 +### 10.5c 境界の双方向化(3–5日) +- Nyash→Python: BoxCall→plugin_invokeでCPython C-APIに橋渡し +- Python→Nyash: `nyashrt`(CPython拡張)で `nyash.call(func, args)` を提供 +- エラーハンドリング: 例外は文字列化(tag=6)でNyashに返却、またはResult化 -### 💻 Phase 10.1d - Core実装 -基本的なPython構文の変換。 -- Phase 1機能(def/if/for/while) -- 意味論の正確な実装 -- 70%コンパイル率達成 +### 10.5d JIT/AOT 統合(3–5日) +- JIT: `emit_plugin_invoke` で Pythonメソッド呼びを許可(ROから開始) +- AOT: libnyrt.a に `nyash.python.*` シム(birth_hなど)を追加し、ObjectModuleの未解決を解決 +- 静的同梱経路: `nyrt` に type_id→`nyplug_python_invoke` ディスパッチテーブルを実装(または各プラグイン名ごとのルータ)。第一段は動的ロード優先 -### 🔄 Phase 10.1e - トランスパイラー -Python→Nyashソース変換。 -- AST→Nyashソース生成 -- フォーマッター実装 -- コマンドラインツール +### 10.5e サンプル/テスト/ドキュメント(1週間) +- サンプル: `py.eval("'hello' * 3").str()`、`numpy`の軽量ケース(import/shape参照などRO中心) +- テスト: GILの再入・参照カウントリーク検知・例外伝搬・多プラットフォーム +- ドキュメント: 使用例、制約(GIL/スレッド)、AOT時のリンク・ランタイム要件 -### 🧪 Phase 10.1f - テスト -Differential Testingでバグ発見。 -- CPython vs Nyash比較 -- ベンチマーク実行 -- バグ修正とCI統合 +## 🎯 DoD(定義) +- NyashからPythonコードを評価し、PyObjectをHandleで往復できる +- 代表的なプロパティ取得/呼び出し(RO)がJIT/VMで動作 +- AOTリンク後のEXEで `py.eval()` 代表例が起動できる(動的ロード前提) -### 📚 Phase 10.1g - ドキュメント -使い方を文書化してリリース。 -- ユーザーガイド作成 -- APIリファレンス -- サンプルプロジェクト +## ⌛ 目安 +| サブフェーズ | 目安 | +|---|---| +| 10.5a 設計 | 1–2日 | +| 10.5b 実装 | 3–5日 | +| 10.5c 双方向 | 3–5日 | +| 10.5d JIT/AOT | 3–5日 | +| 10.5e 仕上げ | 1週間 | -## 🎯 各フェーズの目安時間 - -| フェーズ | 内容 | 目安時間 | -|---------|------|----------| -| 10.1a | 計画理解 | 2-3時間 | -| 10.1b | 環境設定 | 1-2時間 | -| 10.1c | パーサー統合 | 3-5日 | -| 10.1d | Core実装 | 1-2週間 | -| 10.1e | トランスパイラー | 3-5日 | -| 10.1f | テスト | 1週間 | -| 10.1g | ドキュメント | 3-5日 | - -**合計**: 約1ヶ月 - -## 🌟 最終目標 - -- **70%以上**の関数がコンパイル可能 -- **2-10倍**の性能向上 -- **10件以上**のNyashバグ発見 -- **実用的な**Python→Nyash移行ツール - -## 💡 Tips - -- 各フェーズのREADME.mdを必ず読む -- 完了条件をチェックしながら進める -- テレメトリーで進捗を確認 -- 困ったらarchive/の資料も参照 +## ⚠️ リスクと対策 +- GILデッドロック: 入口/出口で厳格に確保/解放。ネスト呼び出しの方針を文書化 +- 参照カウント漏れ: BoxライフサイクルでDECREFを必ず実施、リークテストを追加 +- リンク/配布: Linux/macOS優先。WindowsのPythonリンクは後段で対応 +- 性能: RO先行でJITに寄せ、ミューテーションはポリシー制御 --- -**さあ、Phase 10.1a から始めましょう!** \ No newline at end of file +次は 10.5a(設計・ABI整合)から着手。Everything is Plugin / libnyrt シムの成功パターンをPythonにも適用し、最小リスクで“Pythonネイティブ”を実現する。 diff --git a/events.jsonl b/events.jsonl deleted file mode 100644 index 5c8dd711..00000000 --- a/events.jsonl +++ /dev/null @@ -1,6 +0,0 @@ -{"kind":"hostcall","function":"","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","reason":"receiver_not_param"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"} diff --git a/events_2.jsonl b/events_2.jsonl deleted file mode 100644 index cdff7f7d..00000000 --- a/events_2.jsonl +++ /dev/null @@ -1,5 +0,0 @@ -{"kind":"hostcall","function":"","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","reason":"receiver_not_param"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"} diff --git a/events_3.jsonl b/events_3.jsonl deleted file mode 100644 index f8ba1e11..00000000 --- a/events_3.jsonl +++ /dev/null @@ -1,5 +0,0 @@ -{"kind":"hostcall","function":"","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64"],"argc":2,"decision":"fallback","id":"nyash.array.push_h","reason":"policy_denied_mutating"} -{"kind":"hostcall","function":"","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle","I64"],"argc":2,"decision":"fallback","id":"nyash.array.push_h","reason":"policy_denied_mutating"} -{"kind":"hostcall","function":"","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"} diff --git a/examples/py_eval_demo.nyash b/examples/py_eval_demo.nyash new file mode 100644 index 00000000..071282af --- /dev/null +++ b/examples/py_eval_demo.nyash @@ -0,0 +1,16 @@ +// Python plugin demo (Phase 10.5 scaffold) +// Requires: plugins/nyash-python-plugin built (release) and nyash.toml updated +// Run: +// NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_eval_demo.nyash + +static box Main { + main() { + local py, obj + py = new PyRuntimeBox() + // Evaluate simple Python expression and print its string form + obj = py.eval("'hello' * 3") + me.console.log("py.eval.str=", obj.str()) + return 0 + } +} + diff --git a/examples/py_kw_round_demo.nyash b/examples/py_kw_round_demo.nyash new file mode 100644 index 00000000..807d0070 --- /dev/null +++ b/examples/py_kw_round_demo.nyash @@ -0,0 +1,18 @@ +// Python plugin demo: kwargs call via callKw +// Build plugin: +// (cd plugins/nyash-python-plugin && cargo build --release) +// Run (with auto-decode optional): +// NYASH_PY_AUTODECODE=1 NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_kw_round_demo.nyash + +static box Main { + main() { + local py, bi, roundf, r + py = new PyRuntimeBox() + bi = py.import("builtins") + f = bi.getattr("int") + // int(x="FF", base=16) => 255 + r = f.callKw("x", "FF", "base", 16) + me.console.log("int(x='FF', base=16)=", r.str()) + return 0 + } +} diff --git a/examples/py_math_sqrt_demo.nyash b/examples/py_math_sqrt_demo.nyash new file mode 100644 index 00000000..64b103ba --- /dev/null +++ b/examples/py_math_sqrt_demo.nyash @@ -0,0 +1,22 @@ +// Python plugin demo: import math, getattr sqrt, call(9), str() +// Build plugin: +// (cd plugins/nyash-python-plugin && cargo build --release) +// Run: +// NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/py_math_sqrt_demo.nyash + +static box Main { + init { console } + + main() { + me.console = new ConsoleBox() + + local py, m, f, r + py = new PyRuntimeBox() + m = py.import("math") + f = m.getattr("sqrt") + r = f.call(9) + print("sqrt(9) = " + r.str()) + return 0 + } +} + diff --git a/hh_events.jsonl b/hh_events.jsonl deleted file mode 100644 index b1d3cc6d..00000000 --- a/hh_events.jsonl +++ /dev/null @@ -1,3 +0,0 @@ -{"kind":"hostcall","function":"","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"} -{"kind":"hostcall","function":"","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh","phase":"lower","reason":"sig_ok"} -{"kind":"hostcall","function":"","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh"} diff --git a/nekocode-temp b/nekocode-temp deleted file mode 160000 index 9e0879ba..00000000 --- a/nekocode-temp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9e0879ba14e906b7476af7379ef334f6d4d1dcdf diff --git a/nyash.toml b/nyash.toml index f6315c64..6edeb5a8 100644 --- a/nyash.toml +++ b/nyash.toml @@ -187,3 +187,28 @@ charCodeAt = { method_id = 3, args = ["index"] } concat = { method_id = 4, args = ["other"] } fromUtf8 = { method_id = 5, args = ["data"] } fini = { method_id = 4294967295 } + +# Python plugin (Phase 10.5 – Embedding & FFI, initial scaffold) +[libraries."libnyash_python_plugin.so"] +boxes = ["PyRuntimeBox", "PyObjectBox"] +path = "./plugins/nyash-python-plugin/target/release/libnyash_python_plugin.so" + +[libraries."libnyash_python_plugin.so".PyRuntimeBox] +type_id = 40 + +[libraries."libnyash_python_plugin.so".PyRuntimeBox.methods] +birth = { method_id = 0 } +eval = { method_id = 1, args = ["code"] } +import = { method_id = 2, args = ["name"] } +fini = { method_id = 4294967295 } + +[libraries."libnyash_python_plugin.so".PyObjectBox] +type_id = 41 + +[libraries."libnyash_python_plugin.so".PyObjectBox.methods] +birth = { method_id = 0 } +getattr = { method_id = 1, args = ["name"] } +call = { method_id = 2, args = ["args"] } +callKw = { method_id = 5 } +str = { method_id = 3 } +fini = { method_id = 4294967295 } diff --git a/out.dot b/out.dot deleted file mode 100644 index 9a20790f..00000000 --- a/out.dot +++ /dev/null @@ -1,4 +0,0 @@ -digraph "Helper.getv/2" { - node [shape=box, fontsize=10]; - n2 [label="bb2\nENTRY"]; -} diff --git a/plugins/nyash-python-plugin/Cargo.toml b/plugins/nyash-python-plugin/Cargo.toml new file mode 100644 index 00000000..502b874b --- /dev/null +++ b/plugins/nyash-python-plugin/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nyash-python-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +once_cell = "1.20" +libloading = "0.8" diff --git a/plugins/nyash-python-plugin/src/lib.rs b/plugins/nyash-python-plugin/src/lib.rs new file mode 100644 index 00000000..d18ab6ee --- /dev/null +++ b/plugins/nyash-python-plugin/src/lib.rs @@ -0,0 +1,672 @@ +//! Nyash Python Plugin (Phase 10.5a scaffold) +//! - ABI v1 compatible entry points +//! - Defines two Box types: PyRuntimeBox (TYPE_ID=40) and PyObjectBox (TYPE_ID=41) +//! - Currently stubs; returns NYB_E_INVALID_METHOD for unimplemented routes +//! +//! This crate intentionally does not link to CPython yet. 10.5a focuses on +//! ABI alignment and loader wiring. Future subphases (10.5b–d) will implement +//! actual CPython embedding and conversion. + +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_void, c_long}; +use std::sync::{Mutex, atomic::{AtomicU32, Ordering}}; +use libloading::Library; + +// ===== Error Codes (aligned with other plugins) ===== +const NYB_SUCCESS: i32 = 0; +const NYB_E_SHORT_BUFFER: i32 = -1; +const NYB_E_INVALID_TYPE: i32 = -2; +const NYB_E_INVALID_METHOD: i32 = -3; +const NYB_E_INVALID_ARGS: i32 = -4; +const NYB_E_PLUGIN_ERROR: i32 = -5; +const NYB_E_INVALID_HANDLE: i32 = -8; + +// ===== Type IDs (must match nyash.toml) ===== +const TYPE_ID_PY_RUNTIME: u32 = 40; +const TYPE_ID_PY_OBJECT: u32 = 41; + +// ===== Method IDs (initial draft) ===== +// PyRuntimeBox +const PY_METHOD_BIRTH: u32 = 0; // returns instance_id (u32 LE, no TLV) +const PY_METHOD_EVAL: u32 = 1; // args: string code -> returns Handle(PyObject) +const PY_METHOD_IMPORT:u32 = 2; // args: string name -> returns Handle(PyObject) +const PY_METHOD_FINI: u32 = u32::MAX; // destructor + +// PyObjectBox +const PYO_METHOD_BIRTH: u32 = 0; // reserved (should not be used directly) +const PYO_METHOD_GETATTR: u32 = 1; // args: string name -> returns Handle(PyObject) +const PYO_METHOD_CALL: u32 = 2; // args: variadic TLV -> returns Handle(PyObject) +const PYO_METHOD_STR: u32 = 3; // returns String +const PYO_METHOD_CALL_KW: u32 = 5; // args: key:string, val:TLV, ... -> returns Handle(PyObject) +const PYO_METHOD_FINI: u32 = u32::MAX; // destructor + +// ===== Minimal in-memory state for stubs ===== +#[derive(Default)] +struct PyRuntimeInstance {} + +#[derive(Default)] +struct PyObjectInstance {} + +static RUNTIMES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +static PYOBJS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + +static RT_COUNTER: AtomicU32 = AtomicU32::new(1); +static OBJ_COUNTER: AtomicU32 = AtomicU32::new(1); + +// ====== Minimal dynamic CPython loader ====== +type PyObject = c_void; +type PyGILState_STATE = c_int; + +struct CPython { + _lib: Library, + Py_Initialize: unsafe extern "C" fn(), + Py_Finalize: unsafe extern "C" fn(), + Py_IsInitialized: unsafe extern "C" fn() -> c_int, + PyGILState_Ensure: unsafe extern "C" fn() -> PyGILState_STATE, + PyGILState_Release: unsafe extern "C" fn(PyGILState_STATE), + PyRun_StringFlags: unsafe extern "C" fn(*const c_char, c_int, *mut PyObject, *mut PyObject, *mut c_void) -> *mut PyObject, + PyImport_AddModule: unsafe extern "C" fn(*const c_char) -> *mut PyObject, + PyModule_GetDict: unsafe extern "C" fn(*mut PyObject) -> *mut PyObject, + PyImport_ImportModule: unsafe extern "C" fn(*const c_char) -> *mut PyObject, + PyObject_Str: unsafe extern "C" fn(*mut PyObject) -> *mut PyObject, + PyUnicode_AsUTF8: unsafe extern "C" fn(*mut PyObject) -> *const c_char, + Py_DecRef: unsafe extern "C" fn(*mut PyObject), + Py_IncRef: unsafe extern "C" fn(*mut PyObject), + // Added for getattr/call + PyObject_GetAttrString: unsafe extern "C" fn(*mut PyObject, *const c_char) -> *mut PyObject, + PyObject_CallObject: unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject, + PyObject_Call: unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject, + PyTuple_New: unsafe extern "C" fn(isize) -> *mut PyObject, + PyTuple_SetItem: unsafe extern "C" fn(*mut PyObject, isize, *mut PyObject) -> c_int, + PyLong_FromLongLong: unsafe extern "C" fn(i64) -> *mut PyObject, + PyUnicode_FromString: unsafe extern "C" fn(*const c_char) -> *mut PyObject, + PyBool_FromLong: unsafe extern "C" fn(c_long: c_long) -> *mut PyObject, + // Added for float/bytes and error handling + PyFloat_FromDouble: unsafe extern "C" fn(f64) -> *mut PyObject, + PyFloat_AsDouble: unsafe extern "C" fn(*mut PyObject) -> f64, + PyLong_AsLongLong: unsafe extern "C" fn(*mut PyObject) -> i64, + PyBytes_FromStringAndSize: unsafe extern "C" fn(*const c_char, isize) -> *mut PyObject, + PyBytes_AsStringAndSize: unsafe extern "C" fn(*mut PyObject, *mut *mut c_char, *mut isize) -> c_int, + PyDict_New: unsafe extern "C" fn() -> *mut PyObject, + PyDict_SetItemString: unsafe extern "C" fn(*mut PyObject, *const c_char, *mut PyObject) -> c_int, + PyErr_Occurred: unsafe extern "C" fn() -> *mut PyObject, + PyErr_Fetch: unsafe extern "C" fn(*mut *mut PyObject, *mut *mut PyObject, *mut *mut PyObject), + PyErr_Clear: unsafe extern "C" fn(), +} + +static CPY: Lazy>> = Lazy::new(|| Mutex::new(None)); + +fn try_load_cpython() -> Result<(), ()> { + let candidates = [ + // Linux/WSL common + "libpython3.12.so", + "libpython3.12.so.1.0", + "libpython3.11.so", + "libpython3.11.so.1.0", + "libpython3.10.so", + "libpython3.10.so.1.0", + "libpython3.9.so", + "libpython3.9.so.1.0", + // macOS + "libpython3.12.dylib", + "libpython3.11.dylib", + "libpython3.10.dylib", + "libpython3.9.dylib", + // Windows (not targeted in 10.5b) + // "python312.dll", "python311.dll", + ]; + for name in candidates { + if let Ok(lib) = unsafe { Library::new(name) } { + unsafe { + let Py_Initialize = *lib.get::(b"Py_Initialize\0").map_err(|_| ())?; + let Py_Finalize = *lib.get::(b"Py_Finalize\0").map_err(|_| ())?; + let Py_IsInitialized = *lib.get:: c_int>(b"Py_IsInitialized\0").map_err(|_| ())?; + let PyGILState_Ensure = *lib.get:: PyGILState_STATE>(b"PyGILState_Ensure\0").map_err(|_| ())?; + let PyGILState_Release = *lib.get::(b"PyGILState_Release\0").map_err(|_| ())?; + let PyRun_StringFlags = *lib.get:: *mut PyObject>(b"PyRun_StringFlags\0").map_err(|_| ())?; + let PyImport_AddModule = *lib.get:: *mut PyObject>(b"PyImport_AddModule\0").map_err(|_| ())?; + let PyModule_GetDict = *lib.get:: *mut PyObject>(b"PyModule_GetDict\0").map_err(|_| ())?; + let PyImport_ImportModule = *lib.get:: *mut PyObject>(b"PyImport_ImportModule\0").map_err(|_| ())?; + let PyObject_Str = *lib.get:: *mut PyObject>(b"PyObject_Str\0").map_err(|_| ())?; + let PyUnicode_AsUTF8 = *lib.get:: *const c_char>(b"PyUnicode_AsUTF8\0").map_err(|_| ())?; + let Py_DecRef = *lib.get::(b"Py_DecRef\0").map_err(|_| ())?; + let Py_IncRef = *lib.get::(b"Py_IncRef\0").map_err(|_| ())?; + let PyObject_GetAttrString = *lib.get:: *mut PyObject>(b"PyObject_GetAttrString\0").map_err(|_| ())?; + let PyObject_CallObject = *lib.get:: *mut PyObject>(b"PyObject_CallObject\0").map_err(|_| ())?; + let PyObject_Call = *lib.get:: *mut PyObject>(b"PyObject_Call\0").map_err(|_| ())?; + let PyTuple_New = *lib.get:: *mut PyObject>(b"PyTuple_New\0").map_err(|_| ())?; + let PyTuple_SetItem = *lib.get:: c_int>(b"PyTuple_SetItem\0").map_err(|_| ())?; + let PyLong_FromLongLong = *lib.get:: *mut PyObject>(b"PyLong_FromLongLong\0").map_err(|_| ())?; + let PyUnicode_FromString = *lib.get:: *mut PyObject>(b"PyUnicode_FromString\0").map_err(|_| ())?; + let PyBool_FromLong = *lib.get:: *mut PyObject>(b"PyBool_FromLong\0").map_err(|_| ())?; + let PyFloat_FromDouble = *lib.get:: *mut PyObject>(b"PyFloat_FromDouble\0").map_err(|_| ())?; + let PyFloat_AsDouble = *lib.get:: f64>(b"PyFloat_AsDouble\0").map_err(|_| ())?; + let PyLong_AsLongLong = *lib.get:: i64>(b"PyLong_AsLongLong\0").map_err(|_| ())?; + let PyBytes_FromStringAndSize = *lib.get:: *mut PyObject>(b"PyBytes_FromStringAndSize\0").map_err(|_| ())?; + let PyBytes_AsStringAndSize = *lib.get:: c_int>(b"PyBytes_AsStringAndSize\0").map_err(|_| ())?; + let PyErr_Occurred = *lib.get:: *mut PyObject>(b"PyErr_Occurred\0").map_err(|_| ())?; + let PyDict_New = *lib.get:: *mut PyObject>(b"PyDict_New\0").map_err(|_| ())?; + let PyDict_SetItemString = *lib.get:: c_int>(b"PyDict_SetItemString\0").map_err(|_| ())?; + let PyErr_Fetch = *lib.get::(b"PyErr_Fetch\0").map_err(|_| ())?; + let PyErr_Clear = *lib.get::(b"PyErr_Clear\0").map_err(|_| ())?; + + let cpy = CPython { + _lib: lib, + Py_Initialize, + Py_Finalize, + Py_IsInitialized, + PyGILState_Ensure, + PyGILState_Release, + PyRun_StringFlags, + PyImport_AddModule, + PyModule_GetDict, + PyImport_ImportModule, + PyObject_Str, + PyUnicode_AsUTF8, + Py_DecRef, + Py_IncRef, + PyObject_GetAttrString, + PyObject_CallObject, + PyObject_Call, + PyTuple_New, + PyTuple_SetItem, + PyLong_FromLongLong, + PyUnicode_FromString, + PyBool_FromLong, + PyFloat_FromDouble, + PyFloat_AsDouble, + PyLong_AsLongLong, + PyBytes_FromStringAndSize, + PyBytes_AsStringAndSize, + PyErr_Occurred, + PyDict_New, + PyDict_SetItemString, + PyErr_Fetch, + PyErr_Clear, + }; + *CPY.lock().unwrap() = Some(cpy); + return Ok(()); + } + } + } + Err(()) +} + +fn ensure_cpython() -> Result<(), ()> { + if CPY.lock().unwrap().is_none() { + try_load_cpython()?; + // Initialize on first load + unsafe { + if let Some(cpy) = &*CPY.lock().unwrap() { + if (cpy.Py_IsInitialized)() == 0 { (cpy.Py_Initialize)(); } + } + } + } + Ok(()) +} + +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS } + +#[no_mangle] +pub extern "C" fn nyash_plugin_invoke( + type_id: u32, + method_id: u32, + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + match type_id { + TYPE_ID_PY_RUNTIME => handle_py_runtime(method_id, instance_id, args, args_len, result, result_len), + TYPE_ID_PY_OBJECT => handle_py_object(method_id, instance_id, args, args_len, result, result_len), + _ => NYB_E_INVALID_TYPE, + } +} + +fn handle_py_runtime(method_id: u32, _instance_id: u32, _args: *const u8, _args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + unsafe { + match method_id { + PY_METHOD_BIRTH => { + if result_len.is_null() { return NYB_E_INVALID_ARGS; } + if preflight(result, result_len, 4) { return NYB_E_SHORT_BUFFER; } + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + let id = RT_COUNTER.fetch_add(1, Ordering::Relaxed); + if let Ok(mut map) = RUNTIMES.lock() { map.insert(id, PyRuntimeInstance::default()); } else { return NYB_E_PLUGIN_ERROR; } + let bytes = id.to_le_bytes(); + std::ptr::copy_nonoverlapping(bytes.as_ptr(), result, 4); + *result_len = 4; + NYB_SUCCESS + } + PY_METHOD_FINI => { + // Only drop runtime slot; avoid calling Py_Finalize to prevent shutdown crashes. + if let Ok(mut map) = RUNTIMES.lock() { map.remove(&_instance_id); } + NYB_SUCCESS + } + PY_METHOD_EVAL => { + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + let Some(code) = read_arg_string(_args, _args_len, 0) else { return NYB_E_INVALID_ARGS; }; + let c_code = match CString::new(code) { Ok(s) => s, Err(_) => return NYB_E_INVALID_ARGS }; + let c_main = CString::new("__main__").unwrap(); + if let Some(cpy) = &*CPY.lock().unwrap() { + let state = (cpy.PyGILState_Ensure)(); + let globals = (cpy.PyImport_AddModule)(c_main.as_ptr()); + if globals.is_null() { (cpy.PyGILState_Release)(state); return NYB_E_PLUGIN_ERROR; } + let dict = (cpy.PyModule_GetDict)(globals); + // 258 == Py_eval_input + let obj = (cpy.PyRun_StringFlags)(c_code.as_ptr(), 258, dict, dict, std::ptr::null_mut()); + if obj.is_null() { + let msg = take_py_error_string(cpy); + (cpy.PyGILState_Release)(state); + if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + return NYB_E_PLUGIN_ERROR; + } + // Store as PyObjectBox handle + let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); + if let Ok(mut map) = PYOBJS.lock() { + map.insert(id, PyObjectInstance::default()); + } else { + (cpy.Py_DecRef)(obj); + (cpy.PyGILState_Release)(state); + return NYB_E_PLUGIN_ERROR; + } + // Keep reference (obj is new ref). We model store via separate map and hold pointer via raw address table. + // To actually manage pointer per id, we extend PyObjectInstance in 10.5b with a field. For now, attach through side-table. + OBJ_PTRS.lock().unwrap().insert(id, PyPtr(obj)); + (cpy.PyGILState_Release)(state); + return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); + } + NYB_E_PLUGIN_ERROR + } + PY_METHOD_IMPORT => { + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + let Some(name) = read_arg_string(_args, _args_len, 0) else { return NYB_E_INVALID_ARGS; }; + let c_name = match CString::new(name) { Ok(s) => s, Err(_) => return NYB_E_INVALID_ARGS }; + if let Some(cpy) = &*CPY.lock().unwrap() { + let state = (cpy.PyGILState_Ensure)(); + let obj = (cpy.PyImport_ImportModule)(c_name.as_ptr()); + if obj.is_null() { + let msg = take_py_error_string(cpy); + (cpy.PyGILState_Release)(state); + if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + return NYB_E_PLUGIN_ERROR; + } + let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); + if let Ok(mut map) = PYOBJS.lock() { map.insert(id, PyObjectInstance::default()); } else { (cpy.Py_DecRef)(obj); (cpy.PyGILState_Release)(state); return NYB_E_PLUGIN_ERROR; } + OBJ_PTRS.lock().unwrap().insert(id, PyPtr(obj)); + (cpy.PyGILState_Release)(state); + return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); + } + NYB_E_PLUGIN_ERROR + } + _ => NYB_E_INVALID_METHOD, + } + } +} + +fn handle_py_object(method_id: u32, instance_id: u32, _args: *const u8, _args_len: usize, result: *mut u8, result_len: *mut usize) -> i32 { + match method_id { + PYO_METHOD_BIRTH => NYB_E_INVALID_METHOD, // should be created via runtime + PYO_METHOD_FINI => { + if let Some(cpy) = &*CPY.lock().unwrap() { + if let Some(ptr) = OBJ_PTRS.lock().unwrap().remove(&instance_id) { + unsafe { (cpy.Py_DecRef)(ptr.0); } + } + } + if let Ok(mut map) = PYOBJS.lock() { map.remove(&instance_id); NYB_SUCCESS } else { NYB_E_PLUGIN_ERROR } + } + PYO_METHOD_GETATTR => { + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + let Some(name) = read_arg_string(_args, _args_len, 0) else { return NYB_E_INVALID_ARGS; }; + if let Some(cpy) = &*CPY.lock().unwrap() { + let Some(obj) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let c_name = match CString::new(name) { Ok(s) => s, Err(_) => return NYB_E_INVALID_ARGS }; + let state = unsafe { (cpy.PyGILState_Ensure)() }; + let attr = unsafe { (cpy.PyObject_GetAttrString)(obj.0, c_name.as_ptr()) }; + if attr.is_null() { + let msg = take_py_error_string(cpy); + unsafe { (cpy.PyGILState_Release)(state); } + if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + return NYB_E_PLUGIN_ERROR; + } + let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); + OBJ_PTRS.lock().unwrap().insert(id, PyPtr(attr)); + unsafe { (cpy.PyGILState_Release)(state); } + return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); + } + NYB_E_PLUGIN_ERROR + } + PYO_METHOD_CALL => { + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if let Some(cpy) = &*CPY.lock().unwrap() { + let Some(func) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let state = unsafe { (cpy.PyGILState_Ensure)() }; + // Build tuple from TLV args + let argc = tlv_count_args(_args, _args_len); + let tuple = unsafe { (cpy.PyTuple_New)(argc as isize) }; + if tuple.is_null() { unsafe { (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + if !fill_tuple_from_tlv(cpy, tuple, _args, _args_len) { + unsafe { (cpy.Py_DecRef)(tuple); (cpy.PyGILState_Release)(state); } + return NYB_E_INVALID_ARGS; + } + let ret = unsafe { (cpy.PyObject_CallObject)(func.0, tuple) }; + unsafe { (cpy.Py_DecRef)(tuple); } + if ret.is_null() { + let msg = take_py_error_string(cpy); + unsafe { (cpy.PyGILState_Release)(state); } + if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + return NYB_E_PLUGIN_ERROR; + } + let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); + OBJ_PTRS.lock().unwrap().insert(id, PyPtr(ret)); + unsafe { (cpy.PyGILState_Release)(state); } + return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); + } + NYB_E_PLUGIN_ERROR + } + PYO_METHOD_CALL_KW => { + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if let Some(cpy) = &*CPY.lock().unwrap() { + let Some(func) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let state = unsafe { (cpy.PyGILState_Ensure)() }; + // Empty args tuple for kwargs-only call + let args_tup = unsafe { (cpy.PyTuple_New)(0) }; + if args_tup.is_null() { unsafe { (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + // Build kwargs dict from TLV pairs + let kwargs = unsafe { (cpy.PyDict_New)() }; + if kwargs.is_null() { unsafe { (cpy.Py_DecRef)(args_tup); (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + if !fill_kwargs_from_tlv(cpy, kwargs, _args, _args_len) { + unsafe { (cpy.Py_DecRef)(kwargs); (cpy.Py_DecRef)(args_tup); (cpy.PyGILState_Release)(state); } + return NYB_E_INVALID_ARGS; + } + let ret = unsafe { (cpy.PyObject_Call)(func.0, args_tup, kwargs) }; + unsafe { (cpy.Py_DecRef)(kwargs); (cpy.Py_DecRef)(args_tup); } + if ret.is_null() { + let msg = take_py_error_string(cpy); + unsafe { (cpy.PyGILState_Release)(state); } + if let Some(m) = msg { return write_tlv_string(&m, result, result_len); } + return NYB_E_PLUGIN_ERROR; + } + let id = OBJ_COUNTER.fetch_add(1, Ordering::Relaxed); + OBJ_PTRS.lock().unwrap().insert(id, PyPtr(ret)); + unsafe { (cpy.PyGILState_Release)(state); } + return write_tlv_handle(TYPE_ID_PY_OBJECT, id, result, result_len); + } + NYB_E_PLUGIN_ERROR + } + PYO_METHOD_STR => { + if ensure_cpython().is_err() { return NYB_E_PLUGIN_ERROR; } + if let Some(cpy) = &*CPY.lock().unwrap() { + let Some(obj) = OBJ_PTRS.lock().unwrap().get(&instance_id).copied() else { return NYB_E_INVALID_HANDLE; }; + let state = unsafe { (cpy.PyGILState_Ensure)() }; + let s_obj = unsafe { (cpy.PyObject_Str)(obj.0) }; + if s_obj.is_null() { unsafe { (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + let cstr = unsafe { (cpy.PyUnicode_AsUTF8)(s_obj) }; + if cstr.is_null() { unsafe { (cpy.Py_DecRef)(s_obj); (cpy.PyGILState_Release)(state); } return NYB_E_PLUGIN_ERROR; } + let rust_str = unsafe { CStr::from_ptr(cstr) }.to_string_lossy().to_string(); + unsafe { (cpy.Py_DecRef)(s_obj); (cpy.PyGILState_Release)(state); } + return write_tlv_string(&rust_str, result, result_len); + } + NYB_E_PLUGIN_ERROR + } + // keep others unimplemented in 10.5b-min + PYO_METHOD_GETATTR | PYO_METHOD_CALL => NYB_E_INVALID_METHOD, + _ => NYB_E_INVALID_METHOD, + } +} + +// ===== Minimal TLV helpers (copy from other plugins for consistency) ===== +fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { + unsafe { + if result_len.is_null() { return false; } + if result.is_null() || *result_len < needed { + *result_len = needed; + return true; + } + } + false +} + +fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { + let payload = s.as_bytes(); + write_tlv_result(&[(6u8, payload)], result, result_len) +} + +fn write_tlv_handle(type_id: u32, instance_id: u32, result: *mut u8, result_len: *mut usize) -> i32 { + let mut payload = [0u8; 8]; + payload[..4].copy_from_slice(&type_id.to_le_bytes()); + payload[4..].copy_from_slice(&instance_id.to_le_bytes()); + write_tlv_result(&[(8u8, &payload)], result, result_len) +} + +/// Read nth TLV argument as String (tag 6) +fn read_arg_string(args: *const u8, args_len: usize, n: usize) -> Option { + if args.is_null() || args_len < 4 { return None; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; // skip header + for i in 0..=n { + if buf.len() < off + 4 { return None; } + let tag = buf[off]; + let _rsv = buf[off+1]; + let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; + if buf.len() < off + 4 + size { return None; } + if i == n { + if tag != 6 { return None; } + let slice = &buf[off+4 .. off+4+size]; + return std::str::from_utf8(slice).ok().map(|s| s.to_string()); + } + off += 4 + size; + } + None +} + +// Side-table for PyObject* storage (instance_id -> pointer) +#[derive(Copy, Clone)] +struct PyPtr(*mut PyObject); +unsafe impl Send for PyPtr {} +unsafe impl Sync for PyPtr {} +static OBJ_PTRS: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + +// Base TLV writer used by helpers +fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { return NYB_E_INVALID_ARGS; } + let mut buf: Vec = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); // version + buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc + for (tag, payload) in payloads { + buf.push(*tag); + buf.push(0); + buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); + buf.extend_from_slice(payload); + } + unsafe { + let needed = buf.len(); + if result.is_null() || *result_len < needed { + *result_len = needed; + return NYB_E_SHORT_BUFFER; + } + std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); + *result_len = needed; + } + NYB_SUCCESS +} + +fn tlv_count_args(args: *const u8, args_len: usize) -> usize { + if args.is_null() || args_len < 4 { return 0; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + if buf.len() < 4 { return 0; } + let argc = u16::from_le_bytes([buf[2], buf[3]]) as usize; + argc +} + +fn fill_tuple_from_tlv(cpy: &CPython, tuple: *mut PyObject, args: *const u8, args_len: usize) -> bool { + if args.is_null() || args_len < 4 { return true; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; + let mut idx: isize = 0; + while off + 4 <= buf.len() { + let tag = buf[off]; + let _rsv = buf[off+1]; + let size = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; + if off + 4 + size > buf.len() { return false; } + let payload = &buf[off+4 .. off+4+size]; + let mut obj: *mut PyObject = std::ptr::null_mut(); + unsafe { + match tag { + 1 => { // Bool (1 byte) + let v = if size >= 1 && payload[0] != 0 { 1 } else { 0 }; + obj = (cpy.PyBool_FromLong)(v); + } + 2 => { // I32 + if size != 4 { return false; } + let mut b = [0u8;4]; b.copy_from_slice(payload); + let v = i32::from_le_bytes(b) as i64; + obj = (cpy.PyLong_FromLongLong)(v); + } + 3 => { // I64 + if size != 8 { return false; } + let mut b = [0u8;8]; b.copy_from_slice(payload); + let v = i64::from_le_bytes(b); + obj = (cpy.PyLong_FromLongLong)(v); + } + 5 => { // F64 + if size != 8 { return false; } + let mut b = [0u8;8]; b.copy_from_slice(payload); + let v = f64::from_le_bytes(b); + obj = (cpy.PyFloat_FromDouble)(v); + } + 6 => { // String + let c = match CString::new(payload) { Ok(c) => c, Err(_) => return false }; + obj = (cpy.PyUnicode_FromString)(c.as_ptr()); + } + 7 => { // Bytes + if size > 0 { + let ptr = payload.as_ptr() as *const c_char; + obj = (cpy.PyBytes_FromStringAndSize)(ptr, size as isize); + } else { + obj = (cpy.PyBytes_FromStringAndSize)(std::ptr::null(), 0); + } + } + 8 => { // Handle + if size != 8 { return false; } + let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]); + let mut i = [0u8;4]; i.copy_from_slice(&payload[4..8]); + let _type_id = u32::from_le_bytes(t); + let inst_id = u32::from_le_bytes(i); + if let Some(ptr) = OBJ_PTRS.lock().unwrap().get(&inst_id).copied() { + // PyTuple_SetItem steals a reference; INCREF to keep original alive + (cpy.Py_IncRef)(ptr.0); + obj = ptr.0; + } else { return false; } + } + _ => { return false; } + } + if obj.is_null() { return false; } + let rc = (cpy.PyTuple_SetItem)(tuple, idx, obj); + if rc != 0 { return false; } + idx += 1; + } + off += 4 + size; + } + true +} + +fn take_py_error_string(cpy: &CPython) -> Option { + unsafe { + if (cpy.PyErr_Occurred)().is_null() { return None; } + let mut ptype: *mut PyObject = std::ptr::null_mut(); + let mut pvalue: *mut PyObject = std::ptr::null_mut(); + let mut ptrace: *mut PyObject = std::ptr::null_mut(); + (cpy.PyErr_Fetch)(&mut ptype, &mut pvalue, &mut ptrace); + let s = if !pvalue.is_null() { + let sobj = (cpy.PyObject_Str)(pvalue); + if sobj.is_null() { (cpy.PyErr_Clear)(); return Some("Python error".to_string()); } + let cstr = (cpy.PyUnicode_AsUTF8)(sobj); + let msg = if cstr.is_null() { "Python error".to_string() } else { CStr::from_ptr(cstr).to_string_lossy().to_string() }; + (cpy.Py_DecRef)(sobj); + msg + } else { + "Python error".to_string() + }; + (cpy.PyErr_Clear)(); + Some(s) + } +} + +fn fill_kwargs_from_tlv(cpy: &CPython, dict: *mut PyObject, args: *const u8, args_len: usize) -> bool { + if args.is_null() || args_len < 4 { return true; } + let buf = unsafe { std::slice::from_raw_parts(args, args_len) }; + let mut off = 4usize; + loop { + if off + 4 > buf.len() { break; } + let tag_k = buf[off]; + let _rsv = buf[off+1]; + let size_k = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; + if tag_k != 6 || off + 4 + size_k > buf.len() { return false; } + let key_bytes = &buf[off+4 .. off+4+size_k]; + off += 4 + size_k; + if off + 4 > buf.len() { return false; } + let tag_v = buf[off]; + let _rsv2 = buf[off+1]; + let size_v = u16::from_le_bytes([buf[off+2], buf[off+3]]) as usize; + if off + 4 + size_v > buf.len() { return false; } + let val_payload = &buf[off+4 .. off+4+size_v]; + off += 4 + size_v; + unsafe { + let key_c = match CString::new(key_bytes) { Ok(c) => c, Err(_) => return false }; + let mut obj: *mut PyObject = std::ptr::null_mut(); + match tag_v { + 1 => { let v = if size_v >= 1 && val_payload[0] != 0 { 1 } else { 0 }; obj = (cpy.PyBool_FromLong)(v); } + 2 => { if size_v != 4 { return false; } let mut b = [0u8;4]; b.copy_from_slice(val_payload); obj = (cpy.PyLong_FromLongLong)(i32::from_le_bytes(b) as i64); } + 3 => { if size_v != 8 { return false; } let mut b=[0u8;8]; b.copy_from_slice(val_payload); obj = (cpy.PyLong_FromLongLong)(i64::from_le_bytes(b)); } + 5 => { if size_v != 8 { return false; } let mut b=[0u8;8]; b.copy_from_slice(val_payload); obj = (cpy.PyFloat_FromDouble)(f64::from_le_bytes(b)); } + 6 => { let c = match CString::new(val_payload) { Ok(c) => c, Err(_) => return false }; obj = (cpy.PyUnicode_FromString)(c.as_ptr()); } + 7 => { let ptr = if size_v>0 { val_payload.as_ptr() as *const c_char } else { std::ptr::null() }; obj = (cpy.PyBytes_FromStringAndSize)(ptr, size_v as isize); } + 8 => { + if size_v != 8 { return false; } + let mut i = [0u8;4]; i.copy_from_slice(&val_payload[4..8]); + let inst_id = u32::from_le_bytes(i); + if let Some(p) = OBJ_PTRS.lock().unwrap().get(&inst_id).copied() { (cpy.Py_IncRef)(p.0); obj = p.0; } else { return false; } + } + _ => return false, + } + if obj.is_null() { return false; } + let rc = (cpy.PyDict_SetItemString)(dict, key_c.as_ptr(), obj); + (cpy.Py_DecRef)(obj); + if rc != 0 { return false; } + } + } + true +} +fn try_write_auto_numeric(cpy: &CPython, obj: *mut PyObject, result: *mut u8, result_len: *mut usize) -> bool { + unsafe { + // Try float + let mut had_err = false; + let f = (cpy.PyFloat_AsDouble)(obj); + if !(cpy.PyErr_Occurred)().is_null() { + had_err = true; (cpy.PyErr_Clear)(); + } + if !had_err { + // Consider NaN as successful too + let mut payload = [0u8;8]; + payload.copy_from_slice(&f.to_le_bytes()); + let rc = write_tlv_result(&[(5u8, &payload)], result, result_len); + return rc == NYB_SUCCESS || rc == NYB_E_SHORT_BUFFER; + } + // Try integer + let i = (cpy.PyLong_AsLongLong)(obj); + if !(cpy.PyErr_Occurred)().is_null() { (cpy.PyErr_Clear)(); } else { + let mut payload = [0u8;8]; + payload.copy_from_slice(&i.to_le_bytes()); + let rc = write_tlv_result(&[(3u8, &payload)], result, result_len); + return rc == NYB_SUCCESS || rc == NYB_E_SHORT_BUFFER; + } + false + } +} diff --git a/src/backend/vm.rs b/src/backend/vm.rs index c28d8be0..cf61c820 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -1171,10 +1171,22 @@ impl VM { if method == "toString" { return Ok(Box::new(StringBox::new(format!("{}(id={})", plugin_box.box_type, plugin_box.inner.instance_id)))); } - - // Other plugin methods should be called via BoxCall instruction - // This path shouldn't normally be reached for plugin methods - eprintln!("Warning: Plugin method '{}' called via call_box_method - should use BoxCall", method); + + // Name-based fallback: delegate to unified PluginHost to invoke by (box_type, method) + // This preserves existing semantics but actually performs the plugin call instead of no-op. + let host = crate::runtime::get_global_plugin_host(); + if let Ok(h) = host.read() { + // Prepare args as NyashBox list; they are already Box + let nyash_args: Vec> = _args.into_iter().map(|b| b).collect(); + match h.invoke_instance_method(&plugin_box.box_type, method, plugin_box.inner.instance_id, &nyash_args) { + Ok(Some(ret)) => return Ok(ret), + Ok(None) => return Ok(Box::new(VoidBox::new())), + Err(e) => { + eprintln!("[VM] Plugin invoke error: {}.{} -> {}", plugin_box.box_type, method, e.message()); + return Ok(Box::new(VoidBox::new())); + } + } + } return Ok(Box::new(VoidBox::new())); } diff --git a/src/jit/lower/builder.rs b/src/jit/lower/builder.rs index 163ed2c4..5d980b8f 100644 --- a/src/jit/lower/builder.rs +++ b/src/jit/lower/builder.rs @@ -420,7 +420,7 @@ impl IRBuilder for CraneliftBuilder { // Clear context for next compilation and finalize definitions self.module.clear_context(&mut self.ctx); - self.module.finalize_definitions(); + let _ = self.module.finalize_definitions(); // Get finalized code pointer and wrap into a safe closure let code = self.module.get_finalized_function(func_id); diff --git a/src/runtime/plugin_loader_v2.rs b/src/runtime/plugin_loader_v2.rs index bf163ead..83bd5e8b 100644 --- a/src/runtime/plugin_loader_v2.rs +++ b/src/runtime/plugin_loader_v2.rs @@ -466,6 +466,34 @@ impl PluginBoxV2 { crate::runtime::plugin_ffi_common::encode::i32(&mut buf, v); continue; } + // Bool + if let Some(b) = a.as_any().downcast_ref::() { + eprintln!("[PluginLoaderV2] arg[{}]: Bool({}) -> Bool(tag=1)", idx, b.value); + crate::runtime::plugin_ffi_common::encode::bool(&mut buf, b.value); + continue; + } + // Float (F64) + if let Some(f) = a.as_any().downcast_ref::() { + eprintln!("[PluginLoaderV2] arg[{}]: Float({}) -> F64(tag=5)", idx, f.value); + crate::runtime::plugin_ffi_common::encode::f64(&mut buf, f.value); + continue; + } + // Bytes from Array + if let Some(arr) = a.as_any().downcast_ref::() { + let items = arr.items.read().unwrap(); + let mut tmp = Vec::with_capacity(items.len()); + let mut ok = true; + for item in items.iter() { + if let Some(intb) = item.as_any().downcast_ref::() { + if intb.value >= 0 && intb.value <= 255 { tmp.push(intb.value as u8); } else { ok = false; break; } + } else { ok = false; break; } + } + if ok { + eprintln!("[PluginLoaderV2] arg[{}]: Array[{}] -> Bytes(tag=7)", idx, tmp.len()); + crate::runtime::plugin_ffi_common::encode::bytes(&mut buf, &tmp); + continue; + } + } // String: tag=6 if let Some(s) = a.as_any().downcast_ref::() { eprintln!("[PluginLoaderV2] arg[{}]: String(len={}) -> String(tag=6)", idx, s.value.len()); diff --git a/tools/build_aot.sh b/tools/build_aot.sh new file mode 100644 index 00000000..6fd25b2e --- /dev/null +++ b/tools/build_aot.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat << USAGE +Usage: tools/build_aot.sh [-o ] + +Builds Nyash with Cranelift, emits an AOT object (.o) for the given program, +links it with libnyrt.a into a native executable, and prints the result path. + +Options: + -o Output executable name (default: app) + +Notes: + - Requires a Cranelift-enabled build. + - Runtime requires nyash.toml and plugin .so files resolvable by nyash.toml paths. +USAGE +} + +if [[ $# -lt 1 ]]; then usage; exit 1; fi + +INPUT="" +OUT="app" +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + -o) OUT="$2"; shift 2 ;; + *) INPUT="$1"; shift ;; + esac +done + +if [[ ! -f "$INPUT" ]]; then + echo "error: input file not found: $INPUT" >&2 + exit 1 +fi + +echo "[1/4] Building nyash (Cranelift) ..." +cargo build --release --features cranelift-jit >/dev/null + +echo "[2/4] Emitting object (.o) via JIT ..." +rm -rf target/aot_objects && mkdir -p target/aot_objects +NYASH_AOT_OBJECT_OUT=target/aot_objects \ +NYASH_USE_PLUGIN_BUILTINS=1 \ +NYASH_JIT_EXEC=1 \ +NYASH_JIT_THRESHOLD=1 \ +./target/release/nyash --backend vm "$INPUT" >/dev/null || true + +OBJ="target/aot_objects/main.o" +if [[ ! -f "$OBJ" ]]; then + echo "error: object not generated: $OBJ" >&2 + echo "hint: ensure the program's entry function is JIT-compilable (e.g., main)" >&2 + exit 2 +fi + +echo "[3/4] Building libnyrt.a ..." +( cd crates/nyrt && cargo build --release >/dev/null ) + +echo "[4/4] Linking $OUT ..." +cc "$OBJ" \ + -L crates/nyrt/target/release \ + -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive \ + -lpthread -ldl -lm -o "$OUT" + +echo "✅ Done: $OUT" +echo " (runtime requires nyash.toml and plugin .so per config)" + diff --git a/zenn_articles/280x_performance_benchmark.md b/zenn_articles/280x_performance_benchmark.md deleted file mode 100644 index 184a5cde..00000000 --- a/zenn_articles/280x_performance_benchmark.md +++ /dev/null @@ -1,422 +0,0 @@ -# 衝撃の280倍高速化!自作言語で実現した3つの実行バックエンドの性能比較 - -:::message -本記事は**実際に測定したベンチマークデータ**に基づく技術解説です。 -プログラミング言語実装や性能最適化に興味のある方に向けた内容となっています。 -::: - -## 🎯 はじめに - なぜ一つの言語に3つの実行方式? - -プログラミング言語開発において、「どう実行するか」は言語の価値を大きく左右します。 - -**Nyash**(ニャッシュ)プログラミング言語では、開発効率と実行性能の両立を目指し、**3つの実行バックエンド**を実装しました: - -```bash -# 1. インタープリター実行(開発・デバッグ重視) -nyash program.nyash - -# 2. VM実行(中間コード最適化) -nyash --backend vm program.nyash - -# 3. WASM実行(Web配布・最高性能) -nyash --compile-wasm program.nyash -``` - -結果として得られたのは、**280倍の性能向上**という驚異的な数値でした。 - -## 📊 衝撃のベンチマーク結果 - -まず結果をご覧ください(100回実行の平均値): - -| Backend | 平均実行時間 | インタープリターとの比較 | 実際の用途 | -|---------|-------------|----------------------|-------------| -| **🌐 WASM** | **0.17ms** | **280倍高速** | Web配布・最高性能 | -| **🏎️ VM** | **16.97ms** | **2.9倍高速** | 本番環境・CI/CD | -| **📝 Interpreter** | **48.59ms** | **1倍(基準)** | 開発・デバッグ | - -### 計算量別詳細結果 - -#### Light Benchmark(簡単な算術演算) -``` -Interpreter: 14.85 ms (97.6倍遅い) -VM: 4.44 ms (29.2倍遅い) -WASM: 0.15 ms (基準) -``` - -#### Heavy Benchmark(複雑な計算 50+演算) -``` -Interpreter: 84.88 ms (414.2倍遅い) -VM: 25.08 ms (122.4倍遅い) -WASM: 0.21 ms (基準) -``` - -**複雑になるほどWASMの優位性が顕著に**表れています。 - -## 🔧 技術実装の詳細 - -### 1. インタープリター実行 - -**特徴**: AST(抽象構文木)を直接解釈実行 - -```rust -// 実装イメージ(簡略化) -impl NyashInterpreter { - fn execute(&mut self, ast: ASTNode) -> Result, RuntimeError> { - match ast { - ASTNode::BinaryOperation { op, left, right } => { - let left_val = self.execute(*left)?; - let right_val = self.execute(*right)?; - self.apply_operation(op, left_val, right_val) - } - ASTNode::Literal(value) => Ok(self.create_box(value)), - // ... 他のノード処理 - } - } -} -``` - -**メリット**: -- 実装が簡単 -- デバッグ情報が豊富 -- エラー位置の特定が容易 - -**デメリット**: -- 実行時のオーバーヘッドが大きい -- 最適化の余地が少ない - -### 2. VM実行(MIR経由) - -**特徴**: MIR(中間表現)を経由したバイトコード実行 - -#### MIR変換例 - -```nyash -// Nyashソースコード -static box Main { - main() { - local a, b, result - a = 42 - b = 8 - result = a + b - print(result) - return result - } -} -``` - -↓ **MIR変換** - -```mir -; MIR Module: main -define void @main() { -bb0: - 0: safepoint - 1: %0 = const 42 ; a = 42 - 2: %1 = const 8 ; b = 8 - 3: %2 = %0 Add %1 ; result = a + b - 4: print %2 ; print(result) - 5: ret %2 ; return result -} -``` - -**VM実行の利点**: -- **SSA形式**による最適化 -- **基本ブロック**での制御フロー最適化 -- **型情報**の活用 - -```rust -// VM実行エンジン(簡略化) -impl VM { - fn execute_instruction(&mut self, instr: &MirInstruction) -> Result<(), VMError> { - match instr { - MirInstruction::BinOp { dst, op, lhs, rhs } => { - let left = self.get_value(*lhs)?; - let right = self.get_value(*rhs)?; - let result = self.apply_op(*op, left, right)?; - self.set_value(*dst, result); - } - MirInstruction::Print { value, .. } => { - let val = self.get_value(*value)?; - println!("{}", val); - } - // ... 他の命令処理 - } - Ok(()) - } -} -``` - -### 3. WASM実行(最高性能) - -**特徴**: MIRからWebAssemblyコードを生成 - -#### WASM生成例 - -上記のMIRから以下のWATを生成: - -```wat -(module - (import "env" "print" (func $print (param i32))) - (memory (export "memory") 1) - (global $heap_ptr (mut i32) (i32.const 2048)) - - (func $main (local $0 i32) (local $1 i32) (local $2 i32) - nop ;; safepoint - i32.const 42 ;; const 42 - local.set $0 ;; store to local a - - i32.const 8 ;; const 8 - local.set $1 ;; store to local b - - local.get $0 ;; load a - local.get $1 ;; load b - i32.add ;; a + b - local.set $2 ;; store to result - - local.get $2 ;; load result - call $print ;; print(result) - - local.get $2 ;; load result - return ;; return result - ) - (export "main" (func $main)) -) -``` - -**WASMの圧倒的優位性**: -- **ネイティブ並みの実行速度** -- **事前コンパイル**による最適化 -- **WebAssemblyランタイム**(wasmtime)の高度な最適化 - -## 📈 ベンチマーク実装の技術詳細 - -### 自動化されたベンチマークシステム - -```rust -// ベンチマークフレームワーク実装 -pub struct BenchmarkSuite { - iterations: u32, -} - -impl BenchmarkSuite { - pub fn run_all(&self) -> Vec { - let mut results = Vec::new(); - - for (name, file_path) in &BENCHMARK_FILES { - let source = fs::read_to_string(file_path)?; - - // 3つのバックエンドで実行 - results.push(self.run_interpreter_benchmark(name, &source)?); - results.push(self.run_vm_benchmark(name, &source)?); - results.push(self.run_wasm_benchmark(name, &source)?); - } - - results - } -} -``` - -### 測定精度の確保 - -- **100回実行**による統計的信頼性 -- **コールドスタート除外**(初回実行は統計から除外) -- **ナノ秒精度**での時間測定 -- **メモリ影響最小化**(各実行間でのクリーンアップ) - -### テストケース設計 - -```nyash -// Heavy Benchmark - 50+演算の複雑な計算 -static box Main { - main() { - local a, b, c, d, e, f, g, h, i, j - local result1, result2, result3, result4, result5 - - // 初期化(10演算) - a = 1; b = 2; c = 3; d = 4; e = 5 - f = 6; g = 7; h = 8; i = 9; j = 10 - - // 複雑な演算チェーン(40+演算) - result1 = a * b + c * d - e / f - result2 = g + h * i - j + a - result3 = result1 * result2 + b * c - // ... さらに複雑な計算が続く - - print(result5) - return result5 - } -} -``` - -## 🧠 性能差の技術的分析 - -### 280倍の内訳分析 - -#### 1. **パーサーオーバーヘッド除去**(約5-10倍) -- インタープリター: 毎回ASTパース -- VM/WASM: 事前コンパイル済み - -#### 2. **実行時型チェック削減**(約3-5倍) -- インタープリター: 毎演算で型確認 -- WASM: コンパイル時に型解決 - -#### 3. **ネイティブ命令実行**(約10-20倍) -- インタープリター: Rustコード経由 -- WASM: CPUネイティブ命令 - -#### 4. **メモリアクセス最適化**(約2-3倍) -- インタープリター: Box間接参照 -- WASM: 直接メモリアクセス - -#### 5. **WASMランタイム最適化**(約3-5倍) -- 分岐予測最適化 -- レジスタ割り当て最適化 -- インライン展開 - -**総合効果**: 5×3×15×2.5×4 ≈ **225-450倍** の理論値 -**実測値**: **280倍** → 理論値と一致する妥当な結果 - -## 🎯 実用的な使い分け戦略 - -### 開発フェーズ別推奨 - -#### 1. **開発初期**(インタープリター) -```bash -# デバッグ情報豊富・エラー特定容易 -nyash --debug-fuel unlimited debug_me.nyash -``` - -**利点**: -- 詳細なエラーメッセージ -- 変数の状態追跡 -- ブレークポイント対応(将来実装) - -#### 2. **テスト・CI**(VM) -```bash -# 中程度の性能・安定実行 -nyash --backend vm production_test.nyash -``` - -**利点**: -- 本番環境に近い実行 -- 適度な高速化 -- MIRレベルでのデバッグ可能 - -#### 3. **本番・Web配布**(WASM) -```bash -# 最高性能・ブラウザ対応 -nyash --compile-wasm app.nyash -o public/app.wat -``` - -**利点**: -- 最高の実行性能 -- Webブラウザで実行可能 -- サンドボックス環境で安全 - -### パフォーマンステスト - -実際のプロジェクトでベンチマーク: - -```bash -# 自分のマシンで性能測定 -nyash --benchmark --iterations 100 - -# 軽量テスト(開発中) -nyash --benchmark --iterations 10 -``` - -## 🚀 言語開発者への示唆 - -### 1. **多層実行戦略の有効性** - -単一の実行方式では限界があります。開発効率と実行性能を両立するには: - -- **開発用**: 詳細情報重視 -- **テスト用**: バランス型 -- **本番用**: 性能特化 - -この戦略により、**開発体験を犠牲にすることなく高性能を実現**。 - -### 2. **中間表現(MIR)の威力** - -SSA形式のMIRにより: -- **複数バックエンド**への共通基盤 -- **最適化パス**の実装 -- **コード生成**の簡素化 - -### 3. **WebAssemblyの可能性** - -WASMは「Web専用」技術ではありません: -- **汎用高性能実行基盤**として活用可能 -- **既存ランタイム**(wasmtime等)の恩恵 -- **将来性**: WASI、WASM GCなどの進化 - -### 4. **ベンチマーク駆動開発** - -定量的な性能測定により: -- **改善効果の可視化** -- **回帰の早期発見** -- **最適化の優先順位決定** - -## 💭 今後の発展可能性 - -### Phase 8.3: Box操作の最適化 - -現在Copilotチームが実装中: -- **RefNew/RefGet/RefSet**: オブジェクト操作のWASM最適化 -- **メモリレイアウト**: Box専用の効率的なメモリ管理 -- **GC準備**: 将来のガベージコレクション対応 - -### 期待される更なる高速化 - -Box操作最適化により: -- **メモリアクセス**: さらなる高速化(予想:50-100倍追加) -- **オブジェクト指向**: 実用レベルの性能確保 -- **実世界アプリ**: 本格的な開発が可能に - -## 🌟 まとめ - 280倍が示す可能性 - -この**280倍高速化**は、単なる数値以上の意味を持ちます: - -### 技術的意義 -1. **多層実行戦略**: 開発効率と性能の両立実証 -2. **WASM活用**: Web以外での高性能実行基盤確立 -3. **自動ベンチマーク**: 継続的性能改善の仕組み - -### 実用的価値 -1. **開発体験**: デバッグしやすい開発環境 -2. **配布容易性**: WebAssemblyでの幅広い実行環境 -3. **性能保証**: 定量的な性能データに基づく選択 - -### 将来への示唆 -1. **言語設計**: 実行方式も含めた総合設計の重要性 -2. **最適化**: 段階的・測定駆動の最適化アプローチ -3. **エコシステム**: 既存技術(WASM、wasmtime等)との協調 - ---- - -:::message -**Nyashプロジェクト**は現在GitHubで開発中です。 -この記事が興味深いと感じたら、[⭐スター](https://github.com/moe-charm/nyash)で応援をお願いします! - -実際にベンチマークを試したい方は: -```bash -git clone https://github.com/moe-charm/nyash -cd nyash -cargo build --release -j32 -./target/release/nyash --benchmark --iterations 50 -``` -::: - -**関連記事**: -- [「Everything is Box」革命 - Nyash言語の魅力]() ※同時投稿 -- [プログラミング言語実装入門 - MIRとWebAssembly]() ※次回予定 - -**技術詳細**: -- [GitHub Repository](https://github.com/moe-charm/nyash) -- [Benchmark Results](https://github.com/moe-charm/nyash/blob/main/benchmark_summary_20250814.md) -- [Performance Documentation](https://github.com/moe-charm/nyash/blob/main/docs/execution-backends.md) - ---- - -*パフォーマンス最適化に関するご質問・コメント・追加検証のご提案など、お気軽にお寄せください!* \ No newline at end of file diff --git a/zenn_articles/nyash_introduction.md b/zenn_articles/nyash_introduction.md deleted file mode 100644 index e6d0e99c..00000000 --- a/zenn_articles/nyash_introduction.md +++ /dev/null @@ -1,336 +0,0 @@ -# 「Everything is Box」革命 - 2025年注目の新言語Nyashが変えるプログラミング体験 - -:::message -本記事で紹介するNyashプログラミング言語は、**GitHub Stars 0個**という隠れた名言語です🌟 -読んでいただき、気に入ったら[⭐GitHubスター](https://github.com/moe-charm/nyash)をお願いします! -::: - -## 🎯 はじめに - なぜ「もう一つ」の言語が必要なのか? - -2025年現在、プログラミング言語は数百種類存在します。「なぜまた新しい言語を?」と思われるかもしれません。 - -**Nyash**(ニャッシュ)は、そんな疑問に明確な答えを持つ言語です: - -```nyash -// 🎁 この「箱に詰める」感覚、体験してみませんか? -box User { - init { name, email } - - pack(userName, userEmail) { // ← 「pack」で直感的! - me.name = userName - me.email = userEmail - } - - greet() { - print("Hello, " + me.name + "!") - } -} - -local user = new User("Alice", "alice@example.com") -user.greet() // "Hello, Alice!" -``` - -**Everything is Box** - すべてが「箱」という、シンプルで直感的な哲学。これがNyashの核心です。 - -## 💡 Everything is Box哲学の魅力 - -### 🧠 認知負荷の劇的削減 - -従来の言語では「プリミティブ型」「オブジェクト」「関数」など、概念が分散していました: - -```javascript -// JavaScript: 複雑な概念の混在 -let number = 42; // プリミティブ -let string = "hello"; // プリミティブ -let object = { x: 1 }; // オブジェクト -let array = [1, 2, 3]; // 配列オブジェクト -let func = () => {}; // 関数 -``` - -Nyashでは**すべてがBox**: - -```nyash -// Nyash: 一貫した「Box」概念 -local number = new IntegerBox(42) // NumberもBox -local text = new StringBox("hello") // StringもBox -local data = new MapBox() // ObjectもBox -local items = new ArrayBox() // ArrayもBox -local console = new ConsoleBox() // 機能もBox -``` - -### 🔧 統一されたメソッド呼び出し - -すべてがBoxなので、操作方法も統一されます: - -```nyash -// どのBoxでも同じパターン -number.add(10) // 数値演算 -text.length() // 文字列操作 -data.set("key", "value") // マップ操作 -items.push(number) // 配列操作 -console.log(text) // コンソール出力 -``` - -「オブジェクト」「関数」「プリミティブ」を意識する必要がありません。**すべてBox、すべて同じ**。 - -## 🌟 Nyashの革新的機能 - -### 🎁 pack構文 - Box哲学の具現化 - -Nyashの`pack`は、他言語の`new`や`init`を超越した概念です: - -```nyash -box Product { - init { name, price, category } - - // 🎁 「商品を箱に詰める」直感的メタファー - pack(productName, productPrice, productCategory) { - me.name = productName - me.price = productPrice - me.category = productCategory - } - - displayInfo() { - print(me.name + ": $" + me.price) - } -} - -// 使用時も直感的 -local laptop = new Product("MacBook", 1999, "Electronics") -``` - -この「箱に詰める」感覚は、コードを書くたびにBox哲学を体験させてくれます。 - -### 🔄 明示的デリゲーション - 継承の次世代形 - -従来の継承の問題点を解決する、明示的デリゲーション: - -```nyash -// 🔄 明示的で分かりやすいデリゲーション -box AdminUser from User { - init { permissions } - - pack(adminName, adminEmail, perms) { - from User.pack(adminName, adminEmail) // 親の処理を明示的に呼び出し - me.permissions = perms - } - - override greet() { - from User.greet() // 親のgreetを実行 - print("(Administrator)") // 追加機能 - } -} -``` - -- **`from`構文**: どこから何を呼び出しているか明確 -- **`override`**: オーバーライドを明示的に宣言 -- **隠れた魔法なし**: すべての動作が可視化 - -### 📝 変数宣言厳密化 - メモリ安全性の保証 - -```nyash -static box Calculator { - init { result, memory } // 📝 すべての変数を明示宣言 - - calculate() { - me.result = 42 // ✅ 事前宣言済み - - local temp // ✅ local変数も明示宣言 - temp = me.result * 2 - - // undeclared = 100 // ❌ コンパイルエラー! - } -} -``` - -**メモリ安全性と非同期安全性**を、コンパイル時に完全保証。 - -## 🚀 実用性 - 実際のアプリケーション例 - -### 🎲 サイコロRPGゲーム - -```nyash -box DiceRPG { - init { player, monster, random } - - pack() { - me.player = new MapBox() - me.monster = new MapBox() - me.random = new RandomBox() - - me.player.set("hp", 100) - me.player.set("attack", 20) - me.monster.set("hp", 80) - me.monster.set("attack", 15) - } - - battle() { - loop(me.player.get("hp") > 0 and me.monster.get("hp") > 0) { - // プレイヤーの攻撃 - local damage = me.random.range(10, me.player.get("attack")) - local monster_hp = me.monster.get("hp") - damage - me.monster.set("hp", monster_hp) - - print("Player deals " + damage + " damage!") - - // 勝利判定 - if me.monster.get("hp") <= 0 { - print("Victory!") - return - } - - // モンスターの攻撃 - damage = me.random.range(5, me.monster.get("attack")) - local player_hp = me.player.get("hp") - damage - me.player.set("hp", player_hp) - - print("Monster deals " + damage + " damage!") - } - - print("Defeat...") - } -} - -// ゲーム実行 -local game = new DiceRPG() -game.battle() -``` - -### 📊 統計計算アプリ - -```nyash -box Statistics { - init { data, math } - - pack() { - me.data = new ArrayBox() - me.math = new MathBox() - } - - addData(value) { - me.data.push(value) - } - - calculateMean() { - local sum = 0 - local count = me.data.length() - - local i = 0 - loop(i < count) { - sum = sum + me.data.get(i) - i = i + 1 - } - - return me.math.divide(sum, count) - } -} - -local stats = new Statistics() -stats.addData(10) -stats.addData(20) -stats.addData(30) -print("Average: " + stats.calculateMean()) // 20.0 -``` - -## 🔧 技術的な魅力 - Rust実装による堅牢性 - -### 💪 メモリ安全性 - -NyashはRustで実装されており、以下を保証: - -- **メモリリーク防止**: Arcパターンによる自動メモリ管理 -- **データ競合回避**: スレッドセーフなBox実装 -- **型安全性**: コンパイル時の型チェック - -### 🌐 3つの実行バックエンド - -```bash -# 開発・デバッグ用(詳細ログ) -nyash program.nyash - -# 高速実行用(MIR最適化) -nyash --backend vm program.nyash - -# Web配布用(WASM生成) -nyash --compile-wasm program.nyash -``` - -一つの言語で、**開発から本番まで最適な実行方式**を選択可能。 - -### ⚡ 驚異の性能 - -最新のベンチマーク結果(100回実行平均): - -| Backend | 実行時間 | 高速化倍率 | 用途 | -|---------|----------|------------|------| -| **WASM** | **0.17ms** | **280倍** | Web配布・高速実行 | -| **VM** | **16.97ms** | **2.9倍** | 本番環境 | -| **Interpreter** | **48.59ms** | **1倍** | 開発・デバッグ | - -**280倍の高速化**を実現する技術力。 - -## 🎮 実際に触ってみよう - -### インストール - -```bash -# Rust環境前提 -git clone https://github.com/moe-charm/nyash -cd nyash -cargo build --release -j32 - -# Hello World実行 -./target/release/nyash examples/hello.nyash -``` - -### ブラウザでも実行可能 - -```bash -# WASM生成 -./target/release/nyash --compile-wasm hello.nyash -o hello.wat - -# ブラウザで実行(WebAssembly) -# wasm_demo/index.htmlで確認可能 -``` - -## 🚀 今後の展望 - -### Phase 8: Native実行最適化 - -- **WASM最適化**: ブラウザネイティブ並みの実行速度 -- **Box操作**: オブジェクト指向プログラミング完全対応 -- **非同期処理**: `nowait`/`await`構文実装 - -### 実用アプリケーション開発 - -- **NyaMesh**: P2P通信ライブラリ(Nyashの最終目標) -- **WebGUI**: ブラウザアプリケーション開発 -- **ゲーム開発**: 高性能ゲームエンジン統合 - -## 💭 まとめ - Nyashが描く未来 - -**Nyash**は単なる「もう一つの言語」ではありません: - -1. **🧠 認知負荷削減**: Everything is Box哲学による学習容易性 -2. **🛡️ 安全性保証**: Rust実装による完全なメモリ安全性 -3. **⚡ 高性能**: 280倍高速化を実現する最適化技術 -4. **🌐 実用性**: 開発からWeb配布まで一貫した開発体験 -5. **🔄 明示性**: 隠れた動作のない透明なプログラミング - -プログラミング言語設計における**新たな可能性**を提示する言語です。 - ---- - -:::message alert -**お願い**: もしNyashに興味を持たれたら、[GitHubスター⭐](https://github.com/moe-charm/nyash)をクリックしてください! -現在スター数0個の隠れた名言語を、一緒に世界に広めませんか? -::: - -**関連リンク:** -- [GitHub Repository](https://github.com/moe-charm/nyash) -- [Language Documentation](https://github.com/moe-charm/nyash/tree/main/docs) -- [Online Playground](https://moe-charm.github.io/nyash/) ※準備中 - ---- - -*この記事が気に入ったら、フォロー・いいね・コメントで応援お願いします!* \ No newline at end of file