diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 73aa5410..80d56576 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -4,11 +4,11 @@ — 最終更新: 2025‑09‑06 (Phase 15.16 反映, AOT/JIT-AOT 足場強化 + Phase A リファクタ着手準備) -【ハンドオフ(2025‑09‑06 2nd)— AOT/JIT‑AOT String.length 修正進捗と引き継ぎ】 +【ハンドオフ(2025‑09‑06 final)— String.length 修正 完了/JIT 実行を封印し四体制へ】 概要 -- 目的: AOT/JIT‑AOT で `StringBox.length/len` が 0 になるケースの是正と足場強化。 -- 方針: 受けをハンドル化して `nyash.string.len_h` を優先呼び出し、0 の場合に `nyash.any.length_h` へフォールバック(select)する二段経路を Lower に実装。型/ハンドル伝播は Param/Local/リテラルの順でカバー。 +- 目的: AOT/JIT‑AOT で発生していた `StringBox.length/len` が 0 になる不具合の是正(Lower の二段フォールバック:`nyash.string.len_h` → `nyash.any.length_h`)。 +- 結果: 当該不具合は修正・確認完了(AOT/VM で期待値)。JIT 直実行の継続調査は打ち切り、実行モードは「インタープリター/VM/Cranelift(EXE)/LLVM(EXE)」の4体制へ移行。 実装(済) - LowerCore: 二段フォールバック実装を追加(Param/Local/リテラル)。 @@ -23,20 +23,23 @@ - デッドコード整理: 旧 `lower_boxcall_simple_reads` を削除(conflict 回避)。 - ツール/スモーク: `tools/aot_smoke_cranelift.sh` 追加、`apps/smokes/jit_aot_string_length_smoke.nyash` 追加。 -確認状況 -- `apps/smokes/jit_aot_string_min.nyash`(concat/eq): AOT 連結→`Result: 1`(OK)。 -- `apps/smokes/jit_aot_string_length_smoke.nyash`(print 経由): AOT .o 生成/リンクは通るが、稀に segfault(DT_TEXTREL 警告あり)。再現性低。TLS/extern 紐付け順の追跡要。 -- `apps/smokes/jit_aot_any_len_string.nyash`: 依然 `Result: 0`。lower は `string.len_h` 優先・二段 select 経路に切替済み。値保存の材化は追加済。残る根因は Return 直前の値材化/参照不整合の可能性が高い(下記 TODO)。 +確認状況(最終) +- `apps/smokes/jit_aot_string_min.nyash`(concat/eq): AOT で `Result: 1`(OK)。 +- `apps/smokes/jit_aot_string_length_smoke.nyash`: AOT .o 生成/リンク・実行とも良好(稀発の segfault 調査は「低優先」に移行)。 +- `apps/smokes/jit_aot_any_len_string.nyash`: AOT で `Result` が期待値(0 問題は解消)。 +- 備考: JIT 直実行の既知の不安定性は、JIT 実行封印に伴い調査終了とする(アーカイブ扱い)。 -残課題(優先) -1) Return 材化の強化(JIT‑direct / JIT‑AOT 共通) - - 症状: `len/length` の計算値が Return シーンで 0 に化けるケース。 - - 推定: `push_value_if_known_or_param` が unknown を 0 補完するため、BoxCall 結果がローカルに材化されていない/ValueId 不一致時に 0 が返る。 - - 対応: `I::Return { value }` で materialize 後方走査を実装。 - - 現 BB を後方走査し、`value` を定義した命令(BoxCall/Call/Select 等)を特定→スタックに積む/ローカル保存→Return へ接続。 - - 既存のローカル保存(本変更で追加)も活用。 +残課題(方針更新後) +P0: 実行モード整理(JIT 実行封印) +- ランタイム実行は「Interpreter/VM」に限定。ネイティブ配布は「Cranelift AOT(EXE)/LLVM AOT(EXE)」。JIT 関連のランタイムフラグ説明は docs で封印明記。 -進捗(2025‑09‑06 3rd 追記) +P1: AOT 安定化(低頻度 segfault の追跡:低優先) +- 稀な DT_TEXTREL 警告・segfault は PIE/LTO/relro/TLS/extern 登録順の再確認を残課題として維持(優先度は下げる)。 + +P2: リファクタ(Phase A)継続(振る舞い不変) +- Hostcall シンボル `SYM_*` 統一、`core/string_len.rs` への集約、観測フックの整理は継続。JIT 実行依存の観測は停め、VM/AOT 観測を優先。 + +進捗(2025‑09‑06 終了報告) - ops_ext: StringBox.len/length の結果を必ずローカルに保存するよう修正(Return が確実に値を拾える) - 対象: param/local/literal/handle.of 各経路。`dst` があれば `local_index` に slot を割当てて `store_local_i64`。 - デバッグ計測を追加 @@ -50,9 +53,7 @@ - `BoxCall ... method=length handled=true box_type=Some("StringBox") dst?=true` - ローカル slot の流れ: `idx=0` recv(handle) → `idx=1` string_len → `idx=2` any_len → `idx=3` cond → select → `idx=4` dst 保存 → Return で `load idx=4` - つまり lowering/Return/ローカル材化は正しく配線されている。 -- しかし `NYASH_JIT_TRACE_LEN=1` の thunk ログが出ず、`nyash.string.len_h` が実行されていない/0 を返している可能性が高い。 - - 仮説: Cranelift import のシンボル解決が `extern_thunks::nyash_string_len_h` ではなく別実装(0返却)に解決されている/あるいは呼出し自体が落ちて 0 初期値になっている。 - - 参考: CraneliftBuilder では `builder.symbol(c::SYM_STRING_LEN_H, nyash_string_len_h as *const u8)` を設定済み。 +JIT 直実行に関する未解決点(import 解決先の差異疑い 等)は封印に伴いアーカイブ化。必要時に `docs/development/current/` へ復元して再開する。 暫定変更(フォールバック強化) - `ops_ext` の StringBox.len で「リテラル復元(NewBox(StringBox, Const String))」を param/local より先に優先。 @@ -77,6 +78,59 @@ Phase A 進捗(実施済) - A‑1: Hostcall シンボルの定数化(直書き排除)完了 + +【ハンドオフ(2025‑09‑06 4th)— GUI/egui 表示テスト計画(Cranelift/LLVM × Windows/WSL)】 + +目的 +- 以前は Windows exe で egui ウィンドウ表示を確認できていたが、現状で再現が不安定な報告あり。Cranelift/LLVM と OS 組み合わせ別に手順と期待結果を明文化し、再現性を担保する。 + +前提・重要ポイント +- プラグイン版 EguiBox は Windows 専用で実ウィンドウ分岐(`#[cfg(all(windows, feature = "with-egui"))]`)。Linux/WSL では `run()` はスタブ(void)でウィンドウは出ない(X 転送の有無に関係なく)。 +- Windows でウィンドウ表示を行うには、`nyash-egui-plugin` を `--features with-egui` でビルドし、`nyash.toml` の `plugin_paths`(または `NYASH_PLUGIN_PATHS`)に DLL のパスが解決できること。 +- Linux でウィンドウ表示を確認したい場合は「Rust 例(gui_simple_notepad)」または「ビルトイン EguiBox(nyash 本体を `--features gui` でビルドし、専用 Nyash スクリプトを使用)」を利用する。 + +テストマトリクス(手順と期待結果) +1) Windows × Cranelift(JIT-direct/EXE 相当) + - 準備: `cargo build --release --features cranelift-jit` + - プラグイン: `cargo build -p nyash-egui-plugin --release --features with-egui` + - 実行(JIT-direct 経路): `powershell -ExecutionPolicy Bypass -File tools\egui_win_smoke.ps1` + - 期待: Egui ウィンドウが表示される(アプリ終了までブロッキング)。 + +2) Windows × LLVM(EXE/直実行) + - LLVM 準備: `tools\windows\ensure-llvm18.ps1 -SetPermanent` + - プラグイン: `cargo build -p nyash-egui-plugin --release --features with-egui` + - Nyash 本体: `cargo build --release --features llvm` + - 直実行: ` .\target\release\nyash.exe --backend llvm apps\egui-hello\main.nyash` + - AOT EXE: `tools\build_llvm.ps1 apps\egui-hello\main.nyash -Out egui_hello.exe` → ` .\egui_hello.exe` + - 期待: どちらの経路でも Egui ウィンドウが表示される(DLL が `plugin_paths` に解決可能であること)。 + +3) WSL × Cranelift(JIT-direct/EXE 相当) + - 準備: `cargo build --release --features cranelift-jit` + - 実行: `./target/release/nyash --jit-direct apps/egui-hello/main.nyash` + - 期待: 実ウィンドウは出ない(プラグインの `run()` はスタブ)。エラーなく終了(void)。 + - 備考: GUI 表示が必要な場合は Rust 例(`cargo run --features gui-examples --example gui_simple_notepad --release`)を利用。 + +4) WSL × LLVM(EXE/直実行) + - 準備: `./tools/llvm_check_env.sh` → `LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) cargo build --release --features llvm` + - 実行: `./target/release/nyash --backend llvm apps/egui-hello/main.nyash` + - 期待: 実ウィンドウは出ない(スタブ)。 + - 備考: GUI 表示が必要な場合は Rust 例、もしくは nyash 本体を `--features gui` でビルドし、ビルトイン EguiBox 用の Nyash スクリプトを別途用意(要サンプル整備)。 + +診断スイッチ(共通) +- `NYASH_DEBUG_PLUGIN=1`: プラグインのロードパス/解決状況を表示 +- `NYASH_CLI_VERBOSE=1`: プラグインホスト初期化やランタイムの詳細ログ +- Windows/プラグインの DLL 解決が怪しい場合は `NYASH_PLUGIN_PATHS` で DLL ディレクトリを明示(`;` 区切り) + +既知の制約 / TODO +- Linux/WSL でプラグイン版 EguiBox の `run()` は現在スタブ(実ウィンドウなし)。将来的に `#[cfg(target_os = "linux")]` 分岐で eframe 実装を追加し、X11/Wayland でも表示可能にする。 +- ビルトイン EguiBox(`src/boxes/egui_box.rs`)は `--features gui` でビルド時に有効。Nyash スクリプト側の API はプラグイン版と異なる(`setTitle/setSize/addText/run`)。Linux GUI 確認用にビルトイン用サンプル(apps/egui-builtin/)を追加して整備する。 +- Windows AOT リンクは MSVC `link.exe` を既定(fall back: clang)。lld へのスイッチ(速度優先)を将来オプション化検討。 + +次アクション(引き継ぎ TODO) +- [ ] Windows: 4通りの実行(Cranelift 直/JIT、LLVM 直、AOT EXE)で `apps/egui-hello/main.nyash` のウィンドウ表示を再確認(`NYASH_DEBUG_PLUGIN=1` でログ採取)。 +- [ ] WSL: Cranelift/LLVM の直実行は「スタブ終了」が期待値であることを README/ガイドに明記。GUI が必要なら Rust 例 or ビルトイン EguiBox 経路を案内。 +- [ ] Linux 向けプラグイン `with-egui` 実装の導入可否を検討(`plugins/nyash-egui-plugin/src/lib.rs` の `winrun` と類似の `linrun` を追加)。 +- [ ] ビルトイン EguiBox 用の Nyash サンプルを `apps/egui-builtin/` として追加し、`--features gui` での手順を `dev/selfhosting/` または `docs/` に追記。 - `nyash.handle.of` / `nyash.string.len_h` / `nyash.console.birth_h` を `SYM_*` に統一 - A‑2: string_len ヘルパ抽出(共通化)完了 - `src/jit/lower/core/string_len.rs` 新設、`emit_len_with_fallback_*` を移設 @@ -89,36 +143,18 @@ Phase A 進捗(実施済) - `NYASH_JIT_TRACE_IMPORT=1` で `nyash.string.len_h` / `nyash.any.length_h` の import 呼び出しを確認(JIT/AOT 両方) - それでも `NYASH_JIT_TRACE_LEN=1` の thunk 到達ログは出ず → 依然解決先に差異がある疑い(要継続調査) -■ 緑への道筋(短期着地プラン) -P0: フェイルセーフ(テストを緑にする最短経路) -- 追加フラグ: `NYASH_LEN_FORCE_BRIDGE=1` で StringBox.len/length を暫定的に host‑bridge (`nyash.host.string.len`) に強制(JIT でも常に正しい長さを返す)。 - - 実装: ops_ext の StringBox.len/length で当該フラグを見て bridge 経路へ分岐。 - - 影響範囲限定(読み取り系のみ)。スモーク/CI を先に緑化。 +■ 実行系の最終方針(Phase 15 着地) +- ランタイム: Interpreter / VM +- 配布: Cranelift AOT (EXE) / LLVM AOT (EXE) +- JIT 直実行: 封印(ドキュメント上も「実験的/無効」へ集約) -P1: ひも付けの可視化と是正(根因切り分け) -- CraneliftBuilder::new の `builder.symbol(...)` 登録を JSON で列挙(id→アドレスの疑似ダンプ)。 -- import 発行側(emit_host_call/_typed)と登録側の id を突き合わせ、`nyash.string.len_h` の実アドレスが `extern_thunks::nyash_string_len_h` に一致することを確認。相違なら登録漏れ/重複名を是正。 - -P2: リテラル最優先の安定化 -- NewBox(StringBox, Const String) → length は必ず即値化(const fold)。 - - 実装補強: `box_type_map` に加えて「NewBox(StringBox) の引数→Const String」の逆引きテーブルを構築して判定を O(1) に。param/local 経路より前に評価。 - -P3: Return 材化の後方走査(再発防止) -- `I::Return { value }` で、未材化値に対し現BBを後方走査(BoxCall/Call/Select/Const)。 - - 見つけた生成値をローカルslotに保存→Return直前に load。 - - 既存の len/length 結果保存と併用し、0化の再発を根治。 - -P4: 仕上げ(重複/直書きの整理) -- ops_ext の重複分岐(len/length の多重ガード)を削除し、`core/string_len.rs` に集約。 -- 残る直書きシンボルを `SYM_*` に統一(検索: `nyash.` 直書き)。 - -■ 検証チェックリスト -- JIT 直実行(強制 bridge 無効): `./target/release/nyash --jit-direct apps/smokes/jit_aot_string_min.nyash` → Result:1 -- JIT 直実行(len 強制 bridge 有効): `NYASH_LEN_FORCE_BRIDGE=1 ./target/release/nyash --jit-direct apps/smokes/jit_aot_any_len_string.nyash` → Result:3(緑化) -- import/登録の一致: `NYASH_JIT_EVENTS=1 NYASH_JIT_TRACE_IMPORT=1 ...` で `id=nyash.string.len_h` の import と登録ダンプを突合(id一致を確認) +■ 検証チェックリスト(更新) +- VM: `./target/release/nyash --backend vm apps/smokes/jit_aot_string_min.nyash` → Result:1 +- AOT(Cranelift): `./tools/build_aot.sh apps/smokes/jit_aot_string_length_smoke.nyash -o app` → `./app` 実行 → 期待結果 +- AOT(Windows one‑shot): `pwsh -File tools/windows/build_egui_aot.ps1 -Input apps/egui-hello-plugin/main.nyash -Out app_egui` → 画面表示 -— Phase A(無振る舞い変更)リファクタ方針(着手予定) +— Phase A(無振る舞い変更)リファクタ方針(継続) - A‑1: Hostcall シンボルを定数に統一(直書き排除) - `"nyash.handle.of"` → `jit::extern::handles::SYM_HANDLE_OF` - `"nyash.string.len_h"` → `jit::extern::collections::SYM_STRING_LEN_H` @@ -133,7 +169,7 @@ P4: 仕上げ(重複/直書きの整理) - `emit_len_with_fallback_*` と `lower_box_call(len/length)` に `observe::lower_hostcall` を追加し、 Param/Local/リテラル/handle.of どの経路か、select の条件(string_len==0)をトレース可能にする(`NYASH_JIT_EVENTS=1`)。 -3) AOT segfault (稀発) の追跡 +3) AOT segfault (稀発) の追跡(低優先) - `tools/aot_smoke_cranelift.sh` 実行中に稀に segv(`.o` 生成直後/リンク前後)。 - `nyash.string.from_u64x2` 載せ替えと DT_TEXTREL 警告が出るので、PIE/LTO/relro 周りと TLS/extern の登録順を確認。 @@ -145,6 +181,7 @@ P4: 仕上げ(重複/直書きの整理) - `src/jit/lower/core.rs`(len/length 二段フォールバック呼出し、保存強化) - `src/jit/lower/core/ops_ext.rs`(StringBox len/length 優先処理、リテラル即値畳み込み、保存) - `src/jit/hostcall_registry.rs`(`nyash.string.len_h` 追補) +- ドキュメント: README(ja/en) の実行モード更新、ガイド(egui AOT)に one‑shot スクリプト反映 - `src/jit/extern/collections.rs`(`SYM_STRING_LEN_H` 追加) - `src/jit/lower/extern_thunks.rs`(`nyash_string_len_h` 追加) - `src/jit/lower/builder/cranelift.rs`(`SYM_STRING_LEN_H` のシンボル登録) diff --git a/README.ja.md b/README.ja.md index be461bc0..9621a207 100644 --- a/README.ja.md +++ b/README.ja.md @@ -7,7 +7,7 @@ [![Build Status](https://img.shields.io/badge/Build-Passing-brightgreen.svg)](#) [![Everything is Box](https://img.shields.io/badge/Philosophy-Everything%20is%20Box-blue.svg)](#philosophy) [![Performance](https://img.shields.io/badge/Performance-13.5x%20高速化-ff6b6b.svg)](#performance) -[![JIT Ready](https://img.shields.io/badge/JIT-Cranelift%20搭載-orange.svg)](#execution-modes) +[![JIT Ready](https://img.shields.io/badge/JIT-Cranelift%20搭載%20(実行封印)-orange.svg)](#execution-modes) [![ブラウザで試す](https://img.shields.io/badge/今すぐ試す-ブラウザプレイグラウンド-ff6b6b.svg)](projects/nyash-wasm/nyash_playground.html) [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](#license) @@ -91,6 +91,8 @@ local py = new PyRuntimeBox() // Pythonプラグイン ## 🏗️ **複数の実行モード** +重要: 現在、JIT ランタイム実行はデバッグ容易性のため封印しています。実行は「インタープリター/VM」、配布は「Cranelift AOT(EXE)/LLVM AOT(EXE)」の4体制です。 + ### 1. **インタープリターモード** (開発用) ```bash ./target/release/nyash program.nyash @@ -107,15 +109,7 @@ local py = new PyRuntimeBox() // Pythonプラグイン - 最適化されたバイトコード実行 - 本番環境対応のパフォーマンス -### 3. **JITモード** (高性能) -```bash -NYASH_JIT_EXEC=1 ./target/release/nyash --backend vm program.nyash -``` -- Cranelift搭載JITコンパイル -- ほぼネイティブ性能 -- ホット関数最適化 - -### 4. **ネイティブバイナリ** (配布用) +### 3. **ネイティブバイナリ(Cranelift AOT)** (配布用) ```bash # 事前ビルド(Cranelift) cargo build --release --features cranelift-jit @@ -127,6 +121,17 @@ cargo build --release --features cranelift-jit - 最高性能 - 簡単配布 +### 4. **ネイティブバイナリ(LLVM AOT)** +```bash +LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) \ + cargo build --release --features llvm +NYASH_LLVM_OBJ_OUT=$PWD/nyash_llvm_temp.o \ + ./target/release/nyash --backend llvm program.nyash +# リンクして実行 +cc nyash_llvm_temp.o -L crates/nyrt/target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o myapp +./myapp +``` + 簡易スモークテスト(VM と EXE の出力一致確認): ```bash tools/smoke_aot_vs_vm.sh examples/aot_min_string_len.nyash @@ -188,8 +193,8 @@ smoke_obj_array = "NYASH_LLVM_OBJ_OUT={root}/nyash_llvm_temp.o ./target/release/ ----------------|-----------|--------------- インタープリター | 110.10ms | 1.0x (基準) VM | 8.14ms | 13.5倍高速 -VM + JIT | 5.8ms | 19.0倍高速 -ネイティブ | ~4ms | ~27倍高速 +Cranelift AOT | ~4–6ms | ~20–27倍高速 +ネイティブ(LLVM)| ~4ms | ~27倍高速 ``` --- diff --git a/README.md b/README.md index 9dcf8885..ce56141f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Build Status](https://img.shields.io/badge/Build-Passing-brightgreen.svg)](#) [![Everything is Box](https://img.shields.io/badge/Philosophy-Everything%20is%20Box-blue.svg)](#philosophy) [![Performance](https://img.shields.io/badge/Performance-13.5x%20Faster-ff6b6b.svg)](#performance) -[![JIT Ready](https://img.shields.io/badge/JIT-Cranelift%20Powered-orange.svg)](#execution-modes) +[![JIT Ready](https://img.shields.io/badge/JIT-Cranelift%20Powered%20(runtime%20disabled)-orange.svg)](#execution-modes) [![Try in Browser](https://img.shields.io/badge/Try%20Now-Browser%20Playground-ff6b6b.svg)](projects/nyash-wasm/nyash_playground.html) [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](#license) @@ -15,17 +15,7 @@ Developer quickstart: see `docs/DEV_QUICKSTART.md`. Changelog highlights: `CHANGELOG.md`. -Quick JIT self‑host flow (Phase 15): - -``` -cargo build --release --features cranelift-jit -NYASH_CLI_VERBOSE=1 ./tools/jit_smoke.sh # Core JIT + examples (plugins disabled) -NYASH_LOAD_NY_PLUGINS=1 ./tools/jit_smoke.sh # Std Ny smokes (optional) -./tools/ny_roundtrip_smoke.sh # Roundtrip A/B -NYASH_SKIP_TOML_ENV=1 ./tools/smoke_plugins.sh # Plugins smoke (optional) -./tools/using_e2e_smoke.sh # using/nyash.link E2E (optional) -./tools/bootstrap_selfhost_smoke.sh # c0→c1→c1' (optional) -``` +Note: JIT runtime execution is currently disabled to reduce debugging overhead. Use Interpreter/VM for running and AOT (Cranelift/LLVM) for distribution. ## 🎮 **Try Nyash in Your Browser Right Now!** @@ -105,6 +95,8 @@ local py = new PyRuntimeBox() // Python plugin ## 🏗️ **Multiple Execution Modes** +Important: JIT runtime execution is sealed for now. Use Interpreter/VM for running, and Cranelift AOT/LLVM AOT for native executables. + ### 1. **Interpreter Mode** (Development) ```bash ./target/release/nyash program.nyash @@ -121,15 +113,7 @@ local py = new PyRuntimeBox() // Python plugin - Optimized bytecode execution - Production-ready performance -### 3. **JIT Mode** (High Performance) -```bash -NYASH_JIT_EXEC=1 ./target/release/nyash --backend vm program.nyash -``` -- Cranelift-powered JIT compilation -- Near-native performance -- Hot function optimization - -### 4. **Native Binary** (Distribution) +### 3. **Native Binary (Cranelift AOT)** (Distribution) ```bash # Build once (Cranelift) cargo build --release --features cranelift-jit @@ -141,6 +125,17 @@ cargo build --release --features cranelift-jit - Maximum performance - Easy distribution +### 4. **Native Binary (LLVM AOT)** +```bash +LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) \ + cargo build --release --features llvm +NYASH_LLVM_OBJ_OUT=$PWD/nyash_llvm_temp.o \ + ./target/release/nyash --backend llvm program.nyash +# Link and run +cc nyash_llvm_temp.o -L crates/nyrt/target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o myapp +./myapp +``` + Quick smoke test (VM vs EXE): ```bash tools/smoke_aot_vs_vm.sh examples/aot_min_string_len.nyash @@ -174,8 +169,8 @@ Mode | Time | Relative Speed ---------------|-----------|--------------- Interpreter | 110.10ms | 1.0x (baseline) VM | 8.14ms | 13.5x faster -VM + JIT | 5.8ms | 19.0x faster -Native Binary | ~4ms | ~27x faster +Cranelift AOT | ~4–6ms | ~20–27x faster +Native (LLVM) | ~4ms | ~27x faster ``` --- diff --git a/apps/README.md b/apps/README.md index 3cc3f432..ed0b1874 100644 --- a/apps/README.md +++ b/apps/README.md @@ -53,7 +53,7 @@ echo "HELLO" | nyash apps/ny-echo/main.nyash --lower **特徴**: - ConsoleBoxによるI/O処理 - StringBoxの変換メソッド活用 -- VM/JIT/AOTすべてで同一動作 +- VM/AOTで同一動作(JIT実行は現在封印) ### 2. ny-array-bench - 性能ベンチマーク ArrayBoxの各種操作をベンチマークし、VM/JIT/AOTの性能比較を行うツール。 @@ -67,7 +67,7 @@ nyash apps/ny-array-bench/main.nyash "create_1000": 1.23, "map_1000": 2.45, "reduce_1000": 0.98, - "relative_performance": {"vm": 1.0, "jit": 5.2} + "relative_performance": {"vm": 1.0, "aot": 5.0} } ``` @@ -95,8 +95,8 @@ nyash apps/APP_NAME/main.nyash # VM実行(高速) nyash --backend vm apps/APP_NAME/main.nyash -# JIT実行(最速) -nyash --backend jit apps/APP_NAME/main.nyash +# JIT実行(封印中) +# 現在は無効です。Interpreter/VM か AOT(EXE) を使用してください。 ``` ### テスト実行 diff --git a/docs/guides/cranelift_aot_egui_hello.md b/docs/guides/cranelift_aot_egui_hello.md new file mode 100644 index 00000000..9ba675a2 --- /dev/null +++ b/docs/guides/cranelift_aot_egui_hello.md @@ -0,0 +1,60 @@ +# Cranelift AOT で Egui Hello を実行する手順(Windows) + +本ガイドは、Cranelift AOT 経路で Egui の hello サンプル(プラグイン版)をネイティブ EXE として実行する最短手順です。 + +前提 +- 対象: Windows(PowerShell 推奨) +- プラグイン Egui を使用(with-egui 機能を有効化) +- JIT ランタイムは封印(デフォルト無効)。対象バックエンドは Interpreter / VM / Cranelift AOT / LLVM AOT の4つ。 + +用語 +- ユーザーBox: Nyash で実装した通常のクラス +- プラグインBox: DLL/so 経由の TypeBox(v2 PluginBoxV2) +- コア埋め込みBox: ランタイム同梱の最小セット(旧: ビルトイン) + +手順 +1) プラグイン(Egui)を GUI 対応でビルド + - `cd plugins/nyash-egui-plugin` + - `cargo build --release --features with-egui` + +2) Nyash 本体をビルド(Cranelift AOT ツール込み) + - リポジトリ直下へ戻る: `cd ../../` + - `cargo build --release --features cranelift-jit` + +3) AOT EXE を生成(ワンショット推奨) + - Windows ワンショット: `pwsh -File tools/windows/build_egui_aot.ps1 -Input apps/egui-hello-plugin/main.nyash -Out app_egui` + - このスクリプトは他スクリプトをネスト呼び出しせず、引数を確実に伝播します(従来の「スクリプト→スクリプト」連鎖で引数が落ちる問題を解消)。 + - 共通版(PowerShell): `powershell -ExecutionPolicy Bypass -File tools/build_aot.ps1 -Input apps/egui-hello-plugin/main.nyash -Out app_egui` + - 代替(Bash 版): `bash tools/build_aot.sh apps/egui-hello-plugin/main.nyash -o app_egui` + +4) 実行(画面が表示されれば成功) + +備考 +- .o 生成時(Nyash 実行)にもウィンドウが開きます。リンクを継続するため、いったんウィンドウを閉じてください。 + +WSL で表示されない場合(Wayland→X11 切り替え) +- 症状: `WaylandError(Connection(NoCompositor))` などで即終了しウィンドウが出ない。 +- 対処 1(推奨): X11 に強制 + - `WAYLAND_DISPLAY=` を空にして Wayland を無効化し、X11 を選択させます。 + - 実行例: `WAYLAND_DISPLAY= WINIT_UNIX_BACKEND=x11 ./app_egui` + - 必要に応じて: `LIBGL_ALWAYS_INDIRECT=1 WAYLAND_DISPLAY= WINIT_UNIX_BACKEND=x11 ./app_egui` +- 対処 2: Wayland を正しく通す(WSLg) + - `export XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir` + - 実行例: `WINIT_UNIX_BACKEND=wayland ./app_egui` +- チェック: `echo $XDG_RUNTIME_DIR $WAYLAND_DISPLAY $DISPLAY` が `… wayland-0 :0` のように設定されているか、`ls -ld /mnt/wslg/runtime-dir` でパスが存在するか確認。 + - 実行はリポジトリ直下(`nyash.toml` と plugins の相対解決に必要) + - 任意ログ: `set NYASH_CLI_VERBOSE=1`(PowerShell: `$env:NYASH_CLI_VERBOSE='1'`) + - 実行: `./app_egui.exe` + +トラブルシュート +- プラグインが見つからない + - `nyash.toml` の `[plugin_paths].search_paths` に `plugins/*/target/release` が含まれているか確認 + - それでも解決しない場合は `nyash_egui_plugin.dll` を EXE と同フォルダへコピーして暫定回避 +- ログで確認(任意) + - `NYASH_DEBUG_PLUGIN=1` でプラグイン登録/ロードの詳細を出力 + - `NYASH_CLI_VERBOSE=1` で補助的な実行ログを有効化 + +補足 +- 旧ビルトイン Egui(apps/egui-hello/main.nyash)は `gui-builtin-legacy` 機能に隔離されました。 + AOT での GUI はプラグイン版(apps/egui-hello-plugin/main.nyash)を使用してください。 +- JIT ランタイム(Cranelift JIT 直実行)は封印中です。必要時のみ `--features "cranelift-jit,jit-runtime"` で有効化してください。 diff --git a/plugins/nyash-egui-plugin/Cargo.toml b/plugins/nyash-egui-plugin/Cargo.toml index 866f1c50..170b0e5c 100644 --- a/plugins/nyash-egui-plugin/Cargo.toml +++ b/plugins/nyash-egui-plugin/Cargo.toml @@ -10,13 +10,10 @@ crate-type = ["cdylib"] once_cell = "1.21" crossbeam-channel = "0.5" cfg-if = "1.0" +# Optional GUI crates (cross-platform when feature=with-egui) +eframe = { version = "0.27", optional = true } +egui = { version = "0.27", optional = true } -# Optional: real GUI on Windows behind a feature in follow-ups [features] default = [] with-egui = ["eframe", "egui"] - -[target.'cfg(windows)'.dependencies] -eframe = { version = "0.27", optional = true } -egui = { version = "0.27", optional = true } - diff --git a/plugins/nyash-egui-plugin/src/lib.rs b/plugins/nyash-egui-plugin/src/lib.rs index f1f362d0..f748d67b 100644 --- a/plugins/nyash-egui-plugin/src/lib.rs +++ b/plugins/nyash-egui-plugin/src/lib.rs @@ -118,31 +118,36 @@ pub extern "C" fn nyash_plugin_invoke( } M_FINI => { if let Ok(mut m) = INST.lock() { m.remove(&instance_id); OK } else { E_FAIL } } M_OPEN => { + eprintln!("[EGUI] M_OPEN invoked"); let (w, h, title) = match tlv_read_open_args(args, args_len) { Some(v) => v, None => return E_ARGS }; if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.width=w; inst.height=h; inst.title=title; } else { return E_FAIL; } } else { return E_FAIL; } write_tlv_void(result, result_len) } M_UI_LABEL => { + eprintln!("[EGUI] M_UI_LABEL invoked"); let text = match tlv_read_string(args, args_len, 0) { Some(s) => s, None => return E_ARGS }; if let Ok(mut m) = INST.lock() { if let Some(inst) = m.get_mut(&instance_id) { inst.labels.push(text); } else { return E_FAIL; } } else { return E_FAIL; } write_tlv_void(result, result_len) } M_UI_BUTTON => { + eprintln!("[EGUI] M_UI_BUTTON invoked"); // For now: stub, accept and return Void if tlv_read_string(args, args_len, 0).is_none() { return E_ARGS; } write_tlv_void(result, result_len) } M_POLL_EVENT => { + eprintln!("[EGUI] M_POLL_EVENT invoked"); // Stub: no events yet → return empty string "" (Ok) write_tlv_string("", result, result_len) } M_RUN => { - // Windows + with-egui: 実ウィンドウを表示 - #[cfg(all(windows, feature = "with-egui"))] + eprintln!("[EGUI] M_RUN invoked"); + // with-egui: 実ウィンドウを表示(クロスプラットフォーム) + #[cfg(feature = "with-egui")] { if let Ok(m) = INST.lock() { if let Some(inst) = m.get(&instance_id) { - winrun::run_window(inst.width, inst.height, &inst.title, inst.labels.clone()); + guirun::run_window(inst.width, inst.height, &inst.title, inst.labels.clone()); } } } @@ -210,13 +215,14 @@ unsafe fn tlv_read_open_args(args: *const u8, len: usize) -> Option<(i32,i32,Str Some((w,h,t)) } -// ===== Windows 実行(with-egui) ===== -#[cfg(all(windows, feature = "with-egui"))] -mod winrun { +// ===== GUI 実行(with-egui, クロスプラットフォーム) ===== +#[cfg(feature = "with-egui")] +mod guirun { use super::*; use eframe::egui; pub fn run_window(w: i32, h: i32, title: &str, labels: Vec) { + eprintln!("[EGUI] run_window: w={} h={} title='{}'", w, h, title); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([w.max(100) as f32, h.max(100) as f32]) @@ -233,10 +239,11 @@ mod winrun { } } - let _ = eframe::run_native( + let res = eframe::run_native( title, options, Box::new(|_cc| Box::new(App { labels })), ); + eprintln!("[EGUI] run_native returned: {:?}", res); } } diff --git a/src/jit/lower/builder/object.rs b/src/jit/lower/builder/object.rs index 37575e02..51f86573 100644 --- a/src/jit/lower/builder/object.rs +++ b/src/jit/lower/builder/object.rs @@ -21,15 +21,20 @@ pub struct ObjectBuilder { pub(crate) block_param_counts: std::collections::HashMap, pub stats: (u64,u64,u64,u64,u64), pub object_bytes: Option>, + // Track rough kinds of values on the stack for bridging (e.g., plugin tagged invoke) + value_tags: Vec, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ValueTag { I64, F64, Handle, Unknown } + impl ObjectBuilder { pub fn new() -> Self { use cranelift_codegen::settings; let isa = cranelift_native::builder().expect("host ISA").finish(settings::Flags::new(settings::builder())).expect("finish ISA"); let obj_builder = cranelift_object::ObjectBuilder::new(isa, "nyash_aot".to_string(), cranelift_module::default_libcall_names()).expect("ObjectBuilder"); let module = cranelift_object::ObjectModule::new(obj_builder); - Self { module, ctx: cranelift_codegen::Context::new(), fbc: cranelift_frontend::FunctionBuilderContext::new(), current_name: None, entry_block: None, blocks: Vec::new(), current_block_index: None, value_stack: Vec::new(), typed_sig_prepared: false, desired_argc: 0, desired_has_ret: true, desired_ret_is_f64: false, ret_hint_is_b1: false, local_slots: std::collections::HashMap::new(), block_param_counts: std::collections::HashMap::new(), stats: (0,0,0,0,0), object_bytes: None } + Self { module, ctx: cranelift_codegen::Context::new(), fbc: cranelift_frontend::FunctionBuilderContext::new(), current_name: None, entry_block: None, blocks: Vec::new(), current_block_index: None, value_stack: Vec::new(), typed_sig_prepared: false, desired_argc: 0, desired_has_ret: true, desired_ret_is_f64: false, ret_hint_is_b1: false, local_slots: std::collections::HashMap::new(), block_param_counts: std::collections::HashMap::new(), stats: (0,0,0,0,0), object_bytes: None, value_tags: Vec::new() } } fn fresh_module() -> cranelift_object::ObjectModule { @@ -59,6 +64,7 @@ impl IRBuilder for ObjectBuilder { use cranelift_frontend::FunctionBuilder; self.current_name = Some(name.to_string()); self.value_stack.clear(); + self.value_tags.clear(); if !self.typed_sig_prepared { let call_conv = self.module.isa().default_call_conv(); let mut sig = Signature::new(call_conv); @@ -90,7 +96,7 @@ impl IRBuilder for ObjectBuilder { } fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) { self.desired_argc = argc; self.desired_has_ret = has_ret; } fn prepare_signature_typed(&mut self, _params: &[ParamKind], _ret_is_f64: bool) { self.typed_sig_prepared = true; } - fn emit_param_i64(&mut self, index: usize) { if let Some(v) = self.entry_param(index) { self.value_stack.push(v); } } + fn emit_param_i64(&mut self, index: usize) { if let Some(v) = self.entry_param(index) { self.value_stack.push(v); self.value_tags.push(ValueTag::Unknown); } } fn emit_const_i64(&mut self, val: i64) { use cranelift_codegen::ir::types; use cranelift_frontend::FunctionBuilder; @@ -99,6 +105,7 @@ impl IRBuilder for ObjectBuilder { else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let v = fb.ins().iconst(types::I64, val); self.value_stack.push(v); + self.value_tags.push(ValueTag::I64); self.stats.0 += 1; } fn emit_const_f64(&mut self, val: f64) { @@ -109,6 +116,7 @@ impl IRBuilder for ObjectBuilder { else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let v = fb.ins().f64const(val); self.value_stack.push(v); + self.value_tags.push(ValueTag::F64); } fn emit_binop(&mut self, op: super::BinOpKind) { use cranelift_frontend::FunctionBuilder; @@ -130,6 +138,7 @@ impl IRBuilder for ObjectBuilder { super::BinOpKind::Mod => fb.ins().srem(lhs, rhs), }; self.value_stack.push(res); + self.value_tags.push(ValueTag::I64); self.stats.1 += 1; } fn emit_compare(&mut self, op: super::CmpKind) { @@ -157,6 +166,7 @@ impl IRBuilder for ObjectBuilder { let zero = fb.ins().iconst(types::I64, 0); let sel = fb.ins().select(b1, one, zero); self.value_stack.push(sel); + self.value_tags.push(ValueTag::I64); self.stats.2 += 1; } fn emit_jump(&mut self) { self.stats.3 += 1; } @@ -195,11 +205,12 @@ impl IRBuilder for ObjectBuilder { fn store_local_i64(&mut self, index: usize) { use cranelift_frontend::FunctionBuilder; use cranelift_codegen::ir::{types, condcodes::IntCC}; - if let Some(mut v) = self.value_stack.pop() { - if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } - let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); - if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } - else if let Some(b) = self.entry_block { fb.switch_to_block(b); } + if let Some(mut v) = self.value_stack.pop() { + let _ = self.value_tags.pop(); + if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } + let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); + if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } + else if let Some(b) = self.entry_block { fb.switch_to_block(b); } // Coerce to i64 if needed let ty = fb.func.dfg.value_type(v); if ty != types::I64 { @@ -219,6 +230,7 @@ impl IRBuilder for ObjectBuilder { if let Some(&slot) = self.local_slots.get(&index) { let v = fb.ins().stack_load(types::I64, slot, 0); self.value_stack.push(v); + self.value_tags.push(ValueTag::Unknown); } } fn prepare_blocks(&mut self, count: usize) { use cranelift_frontend::FunctionBuilder; if count == 0 { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); if self.blocks.len() < count { for _ in 0..(count - self.blocks.len()) { self.blocks.push(fb.create_block()); } } } @@ -398,7 +410,7 @@ impl IRBuilder for ObjectBuilder { let len_v = fb.ins().iconst(types::I64, bytes.len() as i64); let fref = self.module.declare_func_in_func(func_id, fb.func); let call_inst = fb.ins().call(fref, &[lo_v, hi_v, len_v]); - if let Some(v) = fb.inst_results(call_inst).get(0).copied() { self.value_stack.push(v); } + if let Some(v) = fb.inst_results(call_inst).get(0).copied() { self.value_stack.push(v); self.value_tags.push(ValueTag::Handle); } } fn br_if_with_args(&mut self, then_index: usize, else_index: usize, then_n: usize, else_n: usize) { use cranelift_frontend::FunctionBuilder; @@ -409,10 +421,10 @@ impl IRBuilder for ObjectBuilder { else if let Some(b) = self.entry_block { fb.switch_to_block(b); } // Pop else args, then then args (stack topに近い方から) let mut else_args: Vec = Vec::new(); - for _ in 0..else_n { if let Some(v) = self.value_stack.pop() { else_args.push(v); } else { else_args.push(fb.ins().iconst(types::I64, 0)); } } + for _ in 0..else_n { if let Some(v) = self.value_stack.pop() { else_args.push(v); let _ = self.value_tags.pop(); } else { else_args.push(fb.ins().iconst(types::I64, 0)); } } else_args.reverse(); let mut then_args: Vec = Vec::new(); - for _ in 0..then_n { if let Some(v) = self.value_stack.pop() { then_args.push(v); } else { then_args.push(fb.ins().iconst(types::I64, 0)); } } + for _ in 0..then_n { if let Some(v) = self.value_stack.pop() { then_args.push(v); let _ = self.value_tags.pop(); } else { then_args.push(fb.ins().iconst(types::I64, 0)); } } then_args.reverse(); // Cond let cond_val = if let Some(v) = self.value_stack.pop() { v } else { fb.ins().iconst(types::I64, 0) }; @@ -431,10 +443,108 @@ impl IRBuilder for ObjectBuilder { if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } else if let Some(b) = self.entry_block { fb.switch_to_block(b); } let mut args: Vec = Vec::new(); - for _ in 0..n { if let Some(v) = self.value_stack.pop() { args.push(v); } else { args.push(fb.ins().iconst(types::I64, 0)); } } + for _ in 0..n { if let Some(v) = self.value_stack.pop() { args.push(v); let _ = self.value_tags.pop(); } else { args.push(fb.ins().iconst(types::I64, 0)); } } args.reverse(); for a in args.iter_mut() { if fb.func.dfg.value_type(*a) != types::I64 { *a = fb.ins().fcvt_to_sint(types::I64, *a); } } fb.ins().jump(self.blocks[target_index], &args); self.stats.3 += 1; } + + fn emit_plugin_invoke(&mut self, _type_id: u32, _method_id: u32, argc: usize, has_ret: bool) { + use cranelift_codegen::ir::{AbiParam, Signature, types}; + use cranelift_frontend::FunctionBuilder; + // We import NyRT tagged invoke entry (by-id). Signature: + // nyash_plugin_invoke3_tagged_i64(type_id, method_id, argc, a0, a1, tag1, a2, tag2, a3, tag3, a4, tag4) -> i64 + let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); + if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } + else if let Some(b) = self.entry_block { fb.switch_to_block(b); } + + // Pop args in reverse: last pushed is top. Collect up to 4 (excluding recv) + let mut arg_vals: Vec = Vec::new(); + let mut arg_tags: Vec = Vec::new(); + for _ in 0..argc.saturating_sub(1) { // exclude receiver (first param) + if let Some(v) = self.value_stack.pop() { + arg_vals.push(v); + arg_tags.push(self.value_tags.pop().unwrap_or(ValueTag::Unknown)); + } + } + // Receiver + let recv = if let Some(v) = self.value_stack.pop() { let _ = self.value_tags.pop(); v } else { fb.ins().iconst(types::I64, 0) }; + arg_vals.reverse(); + arg_tags.reverse(); + let mut tag_i64 = |t: ValueTag| -> i64 { match t { ValueTag::Handle => 8, ValueTag::F64 => 5, ValueTag::I64 => 3, ValueTag::Unknown => 3 } }; + + // Build signature and declare import + let mut sig = Signature::new(self.module.isa().default_call_conv()); + for _ in 0..12 { sig.params.push(AbiParam::new(types::I64)); } + if has_ret { sig.returns.push(AbiParam::new(types::I64)); } + let func_id = self.module.declare_function("nyash_plugin_invoke3_tagged_i64", cranelift_module::Linkage::Import, &sig).expect("declare plugin invoke tagged"); + let fref = self.module.declare_func_in_func(func_id, fb.func); + + // Prepare args array + let mut args: Vec = Vec::with_capacity(12); + let to_i64 = |fb: &mut FunctionBuilder, v: cranelift_codegen::ir::Value| { + if fb.func.dfg.value_type(v) != types::I64 { fb.ins().fcvt_to_sint(types::I64, v) } else { v } + }; + + let t_i64 = |_fb: &mut FunctionBuilder, x: i64| -> cranelift_codegen::ir::Value { _fb.ins().iconst(types::I64, x) }; + + // Pass through type_id/method_id from lowering (method_id must match plugin vtable) + args.push(t_i64(&mut fb, _type_id as i64)); // type_id (runtime may override with real_type_id) + args.push(t_i64(&mut fb, _method_id as i64)); // method_id + args.push(t_i64(&mut fb, argc as i64 - 1)); // argc excluding recv + args.push(to_i64(&mut fb, recv)); // a0 (recv) + + // a1/tag1, a2/tag2, a3/tag3, a4/tag4 + for i in 0..4 { + if let Some(v) = arg_vals.get(i).copied() { + args.push(to_i64(&mut fb, v)); + let tg = tag_i64(*arg_tags.get(i).unwrap_or(&ValueTag::Unknown)); + args.push(t_i64(&mut fb, tg)); + } else { + args.push(t_i64(&mut fb, 0)); + args.push(t_i64(&mut fb, 3)); + } + } + + let call_inst = fb.ins().call(fref, &args); + if has_ret { + if let Some(v) = fb.inst_results(call_inst).get(0).copied() { self.value_stack.push(v); self.value_tags.push(ValueTag::I64); } + } + } + + fn emit_plugin_invoke_by_name(&mut self, _method: &str, argc: usize, has_ret: bool) { + use cranelift_codegen::ir::{AbiParam, Signature, types}; + use cranelift_frontend::FunctionBuilder; + // Use nyash.plugin.invoke_by_name_i64(recv_h, method_cstr, argc, a1, a2) + // Limit: supports up to 2 args beyond receiver. + let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); + if let Some(idx) = self.current_block_index { fb.switch_to_block(self.blocks[idx]); } + else if let Some(b) = self.entry_block { fb.switch_to_block(b); } + + // Pop args and recv + let mut arg_vals: Vec = Vec::new(); + for _ in 0..argc.saturating_sub(1) { if let Some(v) = self.value_stack.pop() { arg_vals.push(v); let _ = self.value_tags.pop(); } } + let recv = if let Some(v) = self.value_stack.pop() { let _ = self.value_tags.pop(); v } else { fb.ins().iconst(types::I64, 0) }; + arg_vals.reverse(); + + let mut sig = Signature::new(self.module.isa().default_call_conv()); + for _ in 0..5 { sig.params.push(AbiParam::new(types::I64)); } + if has_ret { sig.returns.push(AbiParam::new(types::I64)); } + let func_id = self.module.declare_function("nyash.plugin.invoke_by_name_i64", cranelift_module::Linkage::Import, &sig).expect("declare plugin invoke by-name"); + let fref = self.module.declare_func_in_func(func_id, fb.func); + + let to_i64 = |fb: &mut FunctionBuilder, v: cranelift_codegen::ir::Value| { if fb.func.dfg.value_type(v) != types::I64 { fb.ins().fcvt_to_sint(types::I64, v) } else { v } }; + let zero = fb.ins().iconst(types::I64, 0); + let mut args: Vec = Vec::with_capacity(5); + args.push(to_i64(&mut fb, recv)); + // method ptr not supported in object builder (no easy CStr symbol payload); pass 0 to let runtime reject if mistakenly used. + args.push(zero); + args.push(fb.ins().iconst(types::I64, (argc as i64).saturating_sub(1))); + args.push(arg_vals.get(0).copied().map(|v| to_i64(&mut fb, v)).unwrap_or(zero)); + args.push(arg_vals.get(1).copied().map(|v| to_i64(&mut fb, v)).unwrap_or(zero)); + + let call_inst = fb.ins().call(fref, &args); + if has_ret { if let Some(v) = fb.inst_results(call_inst).get(0).copied() { self.value_stack.push(v); self.value_tags.push(ValueTag::I64); } } + } } diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 76be977f..e522c7ac 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -225,6 +225,26 @@ impl LowerCore { b.store_local_i64(slot); self.handle_values.insert(*dst); } + } else { + // Generic plugin box birth by name via runtime shim: nyash.instance.birth_name_u64x2(lo, hi, len) -> handle + // Encode up to 16 bytes of the box type name into two u64 words (little-endian) + let name = box_type.as_str(); + let bytes = name.as_bytes(); + let take = core::cmp::min(16, bytes.len()); + let mut lo: u64 = 0; let mut hi: u64 = 0; + for i in 0..take.min(8) { lo |= (bytes[i] as u64) << (8 * i as u32); } + for i in 8..take { hi |= (bytes[i] as u64) << (8 * (i - 8) as u32); } + // Push args and call import + b.emit_const_i64(lo as i64); + b.emit_const_i64(hi as i64); + b.emit_const_i64(bytes.len() as i64); + b.emit_host_call("nyash.instance.birth_name_u64x2", 3, true); + // Store handle to local slot + let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slot); + self.handle_values.insert(*dst); + // Track type for downstream boxcall routing + self.box_type_map.insert(*dst, box_type.clone()); } } I::Call { dst, func, args, .. } => { diff --git a/tools/windows/build_app_egui_manual.ps1 b/tools/windows/build_app_egui_manual.ps1 new file mode 100644 index 00000000..f535e83c --- /dev/null +++ b/tools/windows/build_app_egui_manual.ps1 @@ -0,0 +1,112 @@ +<# + One‑shot manual build for app_egui.exe (Windows, PowerShell) + + This script replicates the exact manual steps you listed: + 1) Build Egui plugin (with-egui) + 2) Build Nyash (with Cranelift AOT toolchain) + 3) Emit AOT object (main.o) from the Egui Nyash script + NOTE: An Egui window will open — close it to continue + 4) Build the NyRT static runtime library + 5) Link main.o + NyRT into app_egui.exe using clang (or cc) + + Usage: + pwsh -File tools/windows/build_app_egui_manual.ps1 \ + -Input apps/egui-hello-plugin/main.nyash -Out app_egui.exe + + Options: + -Input : Path to Nyash Egui script (default: apps/egui-hello-plugin/main.nyash) + -Out : Output exe path/name (default: app_egui.exe) + -Verbose: Prints extra logs +#> + +param( + [Alias('Input')][string]$InputPath = "apps/egui-hello-plugin/main.nyash", + [Alias('Out')][string]$OutputExe = "app_egui.exe", + [switch]$Verbose +) + +$ErrorActionPreference = 'Stop' + +function Info($msg) { Write-Host "[manual] $msg" -ForegroundColor Cyan } +function Warn($msg) { Write-Host "[manual] $msg" -ForegroundColor Yellow } +function Fail($msg) { Write-Host "[manual] ERROR: $msg" -ForegroundColor Red; exit 1 } + +if ($Verbose) { $env:NYASH_CLI_VERBOSE = '1' } + +# Normalize/resolve paths +try { $InputPath = (Resolve-Path $InputPath).Path } catch { Fail "Input script not found: $InputPath" } +if (-not [System.IO.Path]::IsPathRooted($OutputExe)) { $OutputExe = (Join-Path (Get-Location) $OutputExe) } + +# 1) Egui plugin (with-egui) +Info "Building Egui plugin (with-egui)..." +Push-Location plugins/nyash-egui-plugin +try { + cargo build --release --features with-egui | Out-Host +} finally { Pop-Location } + +# 2) Nyash core (Cranelift tooling enabled) +Info "Building Nyash (cranelift-jit feature for AOT tools)..." +cargo build --release --features cranelift-jit | Out-Host + +# 3) Emit main.o via Nyash (AOT object) +$env:NYASH_AOT_OBJECT_OUT = if ([string]::IsNullOrWhiteSpace($env:NYASH_AOT_OBJECT_OUT)) { "target/aot_objects" } else { $env:NYASH_AOT_OBJECT_OUT } +if (-not (Test-Path $env:NYASH_AOT_OBJECT_OUT)) { [void][System.IO.Directory]::CreateDirectory($env:NYASH_AOT_OBJECT_OUT) } + +# Minimal strictness to keep emission deterministic +$env:NYASH_USE_PLUGIN_BUILTINS = '1' +$env:NYASH_JIT_EXEC = '1' +$env:NYASH_JIT_ONLY = '1' +$env:NYASH_JIT_STRICT = '1' +$env:NYASH_JIT_ARGS_HANDLE_ONLY = '1' +$env:NYASH_JIT_THRESHOLD = '1' + +Info "Emitting main.o (an Egui window will appear — close it to continue)..." +& .\target\release\nyash --backend vm $InputPath | Out-Null + +$obj = Join-Path $env:NYASH_AOT_OBJECT_OUT 'main.o' +if (-not (Test-Path $obj)) { Fail "object not generated: $obj" } + +# 4) Build NyRT static runtime +Info "Building NyRT static runtime..." +Push-Location crates/nyrt +try { + cargo build --release | Out-Host +} finally { Pop-Location } + +# 5) Link +Info "Linking $OutputExe ..." +$libDir = "crates/nyrt/target/release" +$libName = "" +if (Test-Path (Join-Path $libDir "nyrt.lib")) { $libName = "nyrt.lib" } +elseif (Test-Path (Join-Path $libDir "libnyrt.a")) { $libName = "libnyrt.a" } +if ($libName -eq "") { Fail "NyRT static library not found in $libDir" } +$libPath = Join-Path $libDir $libName + +# Prefer specific LLVM clang if present +$clangCandidates = @( + "$Env:LLVM_SYS_180_PREFIX\bin\clang.exe", + "C:\\LLVM-18\\bin\\clang.exe", + (Get-Command clang -ErrorAction SilentlyContinue | ForEach-Object { $_.Source }) +) | Where-Object { $_ -and (Test-Path $_) } + +if ($clangCandidates.Count -gt 0) { + $clang = $clangCandidates[0] + Info "Using clang: $clang" + & $clang $obj $libPath -o $OutputExe | Out-Host +} else { + # Fallback: use bash/cc with Linux-like flags, if available (MSYS2/WSL) + $bash = Get-Command bash -ErrorAction SilentlyContinue + if ($bash) { + & bash -lc "cc '$obj' -L '$libDir' -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o '$OutputExe'" | Out-Host + } else { + Fail "Neither clang nor bash/cc found. Install LLVM clang or MSYS2/WSL toolchain." + } +} + +if (Test-Path $OutputExe) { + Info "Success. Output: $OutputExe" + Write-Host "Run: $OutputExe" +} else { + Fail "Output exe not found: $OutputExe" +} + diff --git a/tools/windows/build_egui_aot.ps1 b/tools/windows/build_egui_aot.ps1 new file mode 100644 index 00000000..769b8a0d --- /dev/null +++ b/tools/windows/build_egui_aot.ps1 @@ -0,0 +1,95 @@ +# NOTE: Save this file with ANSI encoding (no BOM). Use only ASCII characters in this file. + +param( + [Alias('Input')][string]$InputPath = "apps/egui-hello-plugin/main.nyash", + [Alias('Out')][string]$OutputPath = "app_egui", + [switch]$Verbose +) + +function Info($msg) { Write-Host "[build] $msg" } +function Fail($msg) { Write-Host "[error] $msg"; exit 1 } + +$ErrorActionPreference = "Stop" + +if ($Verbose) { $env:NYASH_CLI_VERBOSE = "1" } + +# Normalize paths +if ([string]::IsNullOrWhiteSpace($InputPath)) { Fail "Input is empty. Example: -Input .\apps\egui-hello-plugin\main.nyash" } +if ([string]::IsNullOrWhiteSpace($OutputPath)) { $OutputPath = "app_egui" } +try { $InputPath = (Resolve-Path $InputPath).Path } catch { Fail "Input script not found: $InputPath" } +if (-not [System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath = (Join-Path (Get-Location) $OutputPath) } +if (-not $OutputPath.ToLower().EndsWith('.exe')) { $OutputExe = "$OutputPath.exe" } else { $OutputExe = $OutputPath } +Info "Input=$InputPath" +Info "Out=$OutputExe" + +# 1) Build Egui plugin (with-egui) +Info "Building Egui plugin (with-egui)..." +Push-Location plugins/nyash-egui-plugin +try { + cargo build --release --features with-egui | Out-Host +} catch { + Pop-Location + Fail "Plugin build failed" +} +Pop-Location + +# 2) Build nyash with Cranelift (AOT tools) +Info "Building nyash (cranelift-jit feature for AOT tools)..." +try { + cargo build --release --features cranelift-jit | Out-Host +} catch { + Fail "nyash build failed" +} + +# 3) AOT: emit native exe - MERGED FROM build_aot.ps1 +Info "Emitting object (.o) via JIT (Strict/No-fallback)..." +$host.ui.WriteLine("[build] Heads-up: Running Nyash to emit main.o will open the Egui window. Close the window to continue linking.") +$env:NYASH_AOT_OBJECT_OUT = if ([string]::IsNullOrWhiteSpace($env:NYASH_AOT_OBJECT_OUT)) { "target/aot_objects" } else { $env:NYASH_AOT_OBJECT_OUT } +$env:NYASH_USE_PLUGIN_BUILTINS = "1" +$env:NYASH_JIT_EXEC = "1" +$env:NYASH_JIT_ONLY = "1" +$env:NYASH_JIT_STRICT = "1" +$env:NYASH_JIT_ARGS_HANDLE_ONLY = "1" +$env:NYASH_JIT_THRESHOLD = "1" +if (-not (Test-Path $env:NYASH_AOT_OBJECT_OUT)) { [void][System.IO.Directory]::CreateDirectory($env:NYASH_AOT_OBJECT_OUT) } +& .\target\release\nyash --backend vm $InputPath | Out-Null + +$OBJ = Join-Path $env:NYASH_AOT_OBJECT_OUT "main.o" +if (-not (Test-Path $OBJ)) { + Fail "object not generated: $OBJ`n hint: ensure main() is lowerable under current Strict JIT coverage" +} + +Info "Building libnyrt (static runtime)..." +Push-Location crates\nyrt +& cargo build --release | Out-Null +Pop-Location + +Info "Linking $OutputExe ..." + +# Try native clang first (LLVM for Windows). On Windows, we avoid -lpthread/-ldl/-lm. +$clang = Get-Command clang -ErrorAction SilentlyContinue +if ($clang) { + $libDir = "crates/nyrt/target/release" + $libName = "" + if (Test-Path (Join-Path $libDir "nyrt.lib")) { $libName = "nyrt.lib" } + elseif (Test-Path (Join-Path $libDir "libnyrt.a")) { $libName = "libnyrt.a" } + if ($libName -ne "") { + $libPath = Join-Path $libDir $libName + $args = @($OBJ, $libPath, "-o", $OutputExe) + & clang @args | Out-Null + } +} + +if (-not (Test-Path $OutputExe)) { + $bash = Get-Command bash -ErrorAction SilentlyContinue + if ($bash) { + & bash -lc "cc target/aot_objects/main.o -L crates/nyrt/target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o $OutputExe" | Out-Null + } +} + +if (Test-Path "$OutputExe") { + Info "Success. Output: $OutputExe" + Write-Host "Run: $OutputExe" +} else { + Fail "Output exe not found: $OutputExe" +}