aot: enable plugin invokes in ObjectBuilder (tagged by-id), add generic NewBox birth via nyash.instance.birth_name_u64x2; egui(plugin): cross-platform run + debug logs; docs: WSL Wayland→X11 troubleshooting and one‑shot scripts; CURRENT_TASK + READMEs updated (JIT runtime sealed).

This commit is contained in:
Tomoaki
2025-09-06 16:18:46 +09:00
parent e4c7a36e35
commit 07f270b966
11 changed files with 547 additions and 109 deletions

View File

@ -4,11 +4,11 @@
— 最終更新: 20250906 (Phase 15.16 反映, AOT/JIT-AOT 足場強化 + Phase A リファクタ着手準備)
【ハンドオフ20250906 2nd— AOT/JITAOT String.length 修正進捗と引き継ぎ
【ハンドオフ20250906 final— String.length 修正 完了JIT 実行を封印し四体制へ
概要
- 目的: AOT/JITAOT で `StringBox.length/len` が 0 になるケースの是正と足場強化
- 方針: 受けをハンドル化して `nyash.string.len_h` を優先呼び出し、0 の場合に `nyash.any.length_h` へフォールバックselectする二段経路を Lower に実装。型/ハンドル伝播は Param/Local/リテラルの順でカバー
- 目的: AOT/JITAOT で発生していた `StringBox.length/len` が 0 になる不具合の是正Lower の二段フォールバック:`nyash.string.len_h``nyash.any.length_h`
- 結果: 当該不具合は修正・確認完了AOT/VM で期待値。JIT 直実行の継続調査は打ち切り、実行モードは「インタープリターVMCranelift(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 生成/リンクは通るが、稀に segfaultDT_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 材化の強化JITdirect / JITAOT 共通
- 症状: `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 実行封印
- ランタイム実行は「InterpreterVM」に限定。ネイティブ配布は「Cranelift AOT(EXE)LLVM AOT(EXE)」。JIT 関連のランタイムフラグ説明は docs で封印明記
進捗20250906 3rd 追記
P1: AOT 安定化(低頻度 segfault の追跡:低優先
- 稀な DT_TEXTREL 警告・segfault は PIE/LTO/relro/TLS/extern 登録順の再確認を残課題として維持(優先度は下げる)。
P2: リファクタPhase A継続振る舞い不変
- Hostcall シンボル `SYM_*` 統一、`core/string_len.rs` への集約、観測フックの整理は継続。JIT 実行依存の観測は停め、VM/AOT 観測を優先。
進捗20250906 終了報告)
- 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 進捗(実施済)
- A1: Hostcall シンボルの定数化(直書き排除)完了
【ハンドオフ20250906 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」または「ビルトイン EguiBoxnyash 本体を `--features gui` でビルドし、専用 Nyash スクリプトを使用)」を利用する。
テストマトリクス(手順と期待結果)
1) Windows × CraneliftJIT-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 × LLVMEXE/直実行)
- 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 × CraneliftJIT-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 × LLVMEXE/直実行)
- 準備: `./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_*` に統一
- A2: 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 を暫定的に hostbridge (`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 oneshot): `pwsh -File tools/windows/build_egui_aot.ps1 -Input apps/egui-hello-plugin/main.nyash -Out app_egui` → 画面表示
— Phase A無振る舞い変更リファクタ方針着手予定
— Phase A無振る舞い変更リファクタ方針継続
- A1: 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に oneshot スクリプト反映
- `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` のシンボル登録)

View File

@ -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 | ~46ms | ~2027倍高速
ネイティブ(LLVM)| ~4ms | ~27倍高速
```
---

View File

@ -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 selfhost 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 | ~46ms | ~2027x faster
Native (LLVM) | ~4ms | ~27x faster
```
---

View File

@ -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) を使用してください。
```
### テスト実行

View File

@ -0,0 +1,60 @@
# Cranelift AOT で Egui Hello を実行する手順Windows
本ガイドは、Cranelift AOT 経路で Egui の hello サンプル(プラグイン版)をネイティブ EXE として実行する最短手順です。
前提
- 対象: WindowsPowerShell 推奨)
- プラグイン Egui を使用with-egui 機能を有効化)
- JIT ランタイムは封印(デフォルト無効)。対象バックエンドは Interpreter / VM / Cranelift AOT / LLVM AOT の4つ。
用語
- ユーザーBox: Nyash で実装した通常のクラス
- プラグインBox: DLL/so 経由の TypeBoxv2 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` で補助的な実行ログを有効化
補足
- 旧ビルトイン Eguiapps/egui-hello/main.nyash`gui-builtin-legacy` 機能に隔離されました。
AOT での GUI はプラグイン版apps/egui-hello-plugin/main.nyashを使用してください。
- JIT ランタイムCranelift JIT 直実行)は封印中です。必要時のみ `--features "cranelift-jit,jit-runtime"` で有効化してください。

View File

@ -10,13 +10,10 @@ crate-type = ["cdylib"]
once_cell = "1.21"
crossbeam-channel = "0.5"
cfg-if = "1.0"
# Optional: real GUI on Windows behind a feature in follow-ups
[features]
default = []
with-egui = ["eframe", "egui"]
[target.'cfg(windows)'.dependencies]
# Optional GUI crates (cross-platform when feature=with-egui)
eframe = { version = "0.27", optional = true }
egui = { version = "0.27", optional = true }
[features]
default = []
with-egui = ["eframe", "egui"]

View File

@ -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<String>) {
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);
}
}

View File

@ -21,15 +21,20 @@ pub struct ObjectBuilder {
pub(crate) block_param_counts: std::collections::HashMap<usize, usize>,
pub stats: (u64,u64,u64,u64,u64),
pub object_bytes: Option<Vec<u8>>,
// Track rough kinds of values on the stack for bridging (e.g., plugin tagged invoke)
value_tags: Vec<ValueTag>,
}
#[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; }
@ -196,6 +206,7 @@ impl IRBuilder for ObjectBuilder {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
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]); }
@ -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<cranelift_codegen::ir::Value> = 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<cranelift_codegen::ir::Value> = 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<cranelift_codegen::ir::Value> = 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<cranelift_codegen::ir::Value> = Vec::new();
let mut arg_tags: Vec<ValueTag> = 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<cranelift_codegen::ir::Value> = 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<cranelift_codegen::ir::Value> = 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<cranelift_codegen::ir::Value> = 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); } }
}
}

View File

@ -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, .. } => {

View File

@ -0,0 +1,112 @@
<#
Oneshot 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"
}

View File

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