feat: Phase 10.5 Python統合プラグインとAOTビルド対応

- Pythonプラグイン(PyRuntimeBox/PyObjectBox)を追加
  - eval, import, getattr, call, callKw, strメソッド実装
  - math.sqrtデモ等のサンプルコード追加
- AOTビルドサポート追加
  - libnyrtランタイムライブラリ
  - build_aot.shビルドスクリプト
- .gitignore改善
  - JSONLとDOTファイル除外
  - プラグインのビルド成果物除外
- 不要ファイル削除
  - nekocode-temp, zenn_articles
  - 一時的なログファイル類

Phase 10.1の新計画に基づいて、プラグインBox統一化を推進
This commit is contained in:
Moe Charm
2025-08-29 10:22:44 +09:00
parent 12adde9477
commit d24149d0a1
24 changed files with 1057 additions and 867 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -1,2 +0,0 @@
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh","phase":"lower","reason":"sig_ok"}

View File

@ -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 {

View File

@ -21,14 +21,14 @@ Phase 10.10 は完了DoD確認済。**重大な発見**:プラグイン
- JitPolicyBoxallowlist/presets/ HostCallのRO運用events連携
- CIスモーク導入runtime/compile-events/ 代表サンプル整備
- 🔧 DoingPhase 10.1 新計画)
- ArrayBoxのプラグイン化PoC開始
- JIT lowering層の統一設計
- NewBox→birthのJIT loweringString/Integer、handleベース
- AOT最小EXE: libnyrt.aシム + ny_main ドライバ + build_aot.sh 整備
- リファクタリング作業は継続core_hostcall.rs完了
- ⏭️ NextPhase 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 分割イベントloweremit_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/BytesArray<u8>)を正式サポート → kwargsや一般引数を堅牢化
2) 戻り値自動デコード(オプトイン): `NYASH_PY_AUTODECODE=1` 設計(数値/文字列/bytesのTLV変換。段階的に導入。
3) 例外のResult化: returns_resultの扱い整理成功文字列とエラー文字列の判別手段の仕様化
## すぐ試せるコマンド(現状維持の確認)
```bash
# BuildCranelift込み推奨
@ -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 demoPhase 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最小EXENew!
```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
```
## ⏭️ NextPhase 10.2: JIT実呼び出しの実体化
- 目的: Craneliftの `emit_plugin_invoke` を実装し、JITでも実体のプラグインAPIを呼ぶ
- 方針:

View File

@ -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

View File

@ -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→NyashExtending。JIT/AOT/Pluginの統一面を活かし、最小のC ABIで着地する
## 📂 サブフェーズ構成(順番に実行
## 📂 サブフェーズ構成(10.5a → 10.5e
### 📋 Phase 10.1a - 計画と設計
最初にここから!全体像を理解する。
- 統合実装計画を読む
- エキスパート評価を確認
- 5つの核心戦略を把握
### 10.5a 設計・ABI整合12日
- ルート選択:
- Embedding: NyashプロセスにCPythonを埋め込み、PyObject*をハンドル管理
- Extending: Python拡張モジュールnyashrtを提供し、PythonからNyashを呼ぶ
- ABI方針:
- ハンドル: TLV tag=8type_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 実装35日
- `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 境界の双方向化35日
- 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 統合35日
- 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 設計 | 12日 |
| 10.5b 実装 | 35日 |
| 10.5c 双方向 | 35日 |
| 10.5d JIT/AOT | 35日 |
| 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 から始めましょう!**
次は 10.5a設計・ABI整合から着手。Everything is Plugin / libnyrt シムの成功パターンをPythonにも適用し、最小リスクで“Pythonネイティブ”を実現する。

View File

@ -1,6 +0,0 @@
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","reason":"receiver_not_param"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}

View File

@ -1,5 +0,0 @@
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","reason":"receiver_not_param"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"allow","id":"nyash.array.push_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}

View File

@ -1,5 +0,0 @@
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"fallback","id":"nyash.array.push_h","reason":"policy_denied_mutating"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64"],"argc":2,"decision":"fallback","id":"nyash.array.push_h","reason":"policy_denied_mutating"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle"],"argc":1,"decision":"allow","id":"nyash.any.length_h","reason":"sig_ok"}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,3 +0,0 @@
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","I64","I64"],"argc":3,"decision":"fallback","id":"nyash.map.set_h","phase":"lower","reason":"receiver_not_param"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh","phase":"lower","reason":"sig_ok"}
{"kind":"hostcall","function":"<jit>","arg_types":["Handle","Handle"],"argc":2,"decision":"allow","id":"nyash.map.get_hh"}

Submodule nekocode-temp deleted from 9e0879ba14

View File

@ -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 }

View File

@ -1,4 +0,0 @@
digraph "Helper.getv/2" {
node [shape=box, fontsize=10];
n2 [label="bb2\nENTRY"];
}

View File

@ -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"

View File

@ -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.5bd) 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<Mutex<HashMap<u32, PyRuntimeInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static PYOBJS: Lazy<Mutex<HashMap<u32, PyObjectInstance>>> = 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<Mutex<Option<CPython>>> = 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::<unsafe extern "C" fn()>(b"Py_Initialize\0").map_err(|_| ())?;
let Py_Finalize = *lib.get::<unsafe extern "C" fn()>(b"Py_Finalize\0").map_err(|_| ())?;
let Py_IsInitialized = *lib.get::<unsafe extern "C" fn() -> c_int>(b"Py_IsInitialized\0").map_err(|_| ())?;
let PyGILState_Ensure = *lib.get::<unsafe extern "C" fn() -> PyGILState_STATE>(b"PyGILState_Ensure\0").map_err(|_| ())?;
let PyGILState_Release = *lib.get::<unsafe extern "C" fn(PyGILState_STATE)>(b"PyGILState_Release\0").map_err(|_| ())?;
let PyRun_StringFlags = *lib.get::<unsafe extern "C" fn(*const c_char, c_int, *mut PyObject, *mut PyObject, *mut c_void) -> *mut PyObject>(b"PyRun_StringFlags\0").map_err(|_| ())?;
let PyImport_AddModule = *lib.get::<unsafe extern "C" fn(*const c_char) -> *mut PyObject>(b"PyImport_AddModule\0").map_err(|_| ())?;
let PyModule_GetDict = *lib.get::<unsafe extern "C" fn(*mut PyObject) -> *mut PyObject>(b"PyModule_GetDict\0").map_err(|_| ())?;
let PyImport_ImportModule = *lib.get::<unsafe extern "C" fn(*const c_char) -> *mut PyObject>(b"PyImport_ImportModule\0").map_err(|_| ())?;
let PyObject_Str = *lib.get::<unsafe extern "C" fn(*mut PyObject) -> *mut PyObject>(b"PyObject_Str\0").map_err(|_| ())?;
let PyUnicode_AsUTF8 = *lib.get::<unsafe extern "C" fn(*mut PyObject) -> *const c_char>(b"PyUnicode_AsUTF8\0").map_err(|_| ())?;
let Py_DecRef = *lib.get::<unsafe extern "C" fn(*mut PyObject)>(b"Py_DecRef\0").map_err(|_| ())?;
let Py_IncRef = *lib.get::<unsafe extern "C" fn(*mut PyObject)>(b"Py_IncRef\0").map_err(|_| ())?;
let PyObject_GetAttrString = *lib.get::<unsafe extern "C" fn(*mut PyObject, *const c_char) -> *mut PyObject>(b"PyObject_GetAttrString\0").map_err(|_| ())?;
let PyObject_CallObject = *lib.get::<unsafe extern "C" fn(*mut PyObject, *mut PyObject) -> *mut PyObject>(b"PyObject_CallObject\0").map_err(|_| ())?;
let PyObject_Call = *lib.get::<unsafe extern "C" fn(*mut PyObject, *mut PyObject, *mut PyObject) -> *mut PyObject>(b"PyObject_Call\0").map_err(|_| ())?;
let PyTuple_New = *lib.get::<unsafe extern "C" fn(isize) -> *mut PyObject>(b"PyTuple_New\0").map_err(|_| ())?;
let PyTuple_SetItem = *lib.get::<unsafe extern "C" fn(*mut PyObject, isize, *mut PyObject) -> c_int>(b"PyTuple_SetItem\0").map_err(|_| ())?;
let PyLong_FromLongLong = *lib.get::<unsafe extern "C" fn(i64) -> *mut PyObject>(b"PyLong_FromLongLong\0").map_err(|_| ())?;
let PyUnicode_FromString = *lib.get::<unsafe extern "C" fn(*const c_char) -> *mut PyObject>(b"PyUnicode_FromString\0").map_err(|_| ())?;
let PyBool_FromLong = *lib.get::<unsafe extern "C" fn(c_long: c_long) -> *mut PyObject>(b"PyBool_FromLong\0").map_err(|_| ())?;
let PyFloat_FromDouble = *lib.get::<unsafe extern "C" fn(f64) -> *mut PyObject>(b"PyFloat_FromDouble\0").map_err(|_| ())?;
let PyFloat_AsDouble = *lib.get::<unsafe extern "C" fn(*mut PyObject) -> f64>(b"PyFloat_AsDouble\0").map_err(|_| ())?;
let PyLong_AsLongLong = *lib.get::<unsafe extern "C" fn(*mut PyObject) -> i64>(b"PyLong_AsLongLong\0").map_err(|_| ())?;
let PyBytes_FromStringAndSize = *lib.get::<unsafe extern "C" fn(*const c_char, isize) -> *mut PyObject>(b"PyBytes_FromStringAndSize\0").map_err(|_| ())?;
let PyBytes_AsStringAndSize = *lib.get::<unsafe extern "C" fn(*mut PyObject, *mut *mut c_char, *mut isize) -> c_int>(b"PyBytes_AsStringAndSize\0").map_err(|_| ())?;
let PyErr_Occurred = *lib.get::<unsafe extern "C" fn() -> *mut PyObject>(b"PyErr_Occurred\0").map_err(|_| ())?;
let PyDict_New = *lib.get::<unsafe extern "C" fn() -> *mut PyObject>(b"PyDict_New\0").map_err(|_| ())?;
let PyDict_SetItemString = *lib.get::<unsafe extern "C" fn(*mut PyObject, *const c_char, *mut PyObject) -> c_int>(b"PyDict_SetItemString\0").map_err(|_| ())?;
let PyErr_Fetch = *lib.get::<unsafe extern "C" fn(*mut *mut PyObject, *mut *mut PyObject, *mut *mut PyObject)>(b"PyErr_Fetch\0").map_err(|_| ())?;
let PyErr_Clear = *lib.get::<unsafe extern "C" fn()>(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<String> {
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<Mutex<HashMap<u32, PyPtr>>> = 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<u8> = Vec::with_capacity(4 + payloads.iter().map(|(_,p)| 4 + p.len()).sum::<usize>());
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<String> {
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
}
}

View File

@ -1172,9 +1172,21 @@ impl VM {
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<dyn NyashBox>
let nyash_args: Vec<Box<dyn NyashBox>> = _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()));
}

View File

@ -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);

View File

@ -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::<crate::box_trait::BoolBox>() {
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::<crate::boxes::math_box::FloatBox>() {
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<uint8>
if let Some(arr) = a.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
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::<IntegerBox>() {
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<uint8>[{}] -> 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::<StringBox>() {
eprintln!("[PluginLoaderV2] arg[{}]: String(len={}) -> String(tag=6)", idx, s.value.len());

66
tools/build_aot.sh Normal file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat << USAGE
Usage: tools/build_aot.sh <input.nyash> [-o <output>]
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> 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)"

View File

@ -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<Box<dyn NyashBox>, 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<BenchmarkResult> {
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)
---
*パフォーマンス最適化に関するご質問・コメント・追加検証のご提案など、お気軽にお寄せください!*

View File

@ -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<Mutex>パターンによる自動メモリ管理
- **データ競合回避**: スレッドセーフな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/) ※準備中
---
*この記事が気に入ったら、フォロー・いいね・コメントで応援お願いします!*