diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index c449efd3..0dd31f7e 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,5 +1,139 @@ # CURRENT TASK (Compact) — Phase 15 / Self-Hosting(Ny→MIR→MIR-Interp→VM 先行) +【Quick Update — 2025‑09‑07 PM VM=Plugin‑First 固定 + JIT/AOT スモーク整備】 +- 方針確定: このブランチでは VM はプラグインボックスを一貫利用(plugin‑first)。JIT/AOT は最小 compare/select と name‑invoke を安定化。 +- VM 側の固定(実装) + - src/runner/modes/vm.rs: 起動時にプラグインホスト初期化+BoxFactoryRegistry適用(idempotent)。 + - 既定で `NYASH_USE_PLUGIN_BUILTINS=1`。`NYASH_PLUGIN_OVERRIDE_TYPES` を File/String/Integer/Console/Array/Map/Math/Time で安定設定。 + - 厳格モード: `NYASH_VM_PLUGIN_STRICT=1` でプロバイダー未配置なら即エラー。 +- Interpreter 揺れ緩和(補助) + - src/interpreter/core.rs: 単体起動でも nyash.toml を検出したら一度だけプラグイン初期化+適用。 + - 互換重視: 既定は FileBox/TOMLBox のみプラグイン優先(Array/Mapは維持)。 +- スモーク追加(実行確認) + - VM: `apps/tests/vm-plugin-smoke-counter/main.nyash`(CounterBox inc/get=1) + - `tools/vm_plugin_smoke.sh` + - VM: `apps/tests/vm-plugin-smoke-filebox/main.nyash`(FileBox open/read/close → length=2) + - `tools/vm_filebox_smoke.sh` + - JIT: `apps/tests/mir-compare-multi/main.nyash`(<,==,!= 合成で101) + - `tools/jit_compare_smoke.sh`(branch-ret も併走) + - AOT: 一気通しスクリプト `tools/build_aot.sh`(.o→EXE→実行) + - CounterBox 版: `tools/aot_counter_smoke.sh` +- Cranelift name‑invoke 観測拡充 + - 非コアBoxの `emit_plugin_invoke_by_name` に `emit_lower` を追加(id=plugin_name:., argc)。 + - 既存の PluginInvoke(型ID解決)箇所では type_id/method_id を observe に記録(Console/PyRuntime/Array/Map 等)。 + +次アクション(短期・このブランチ) +1) name‑invoke の引数 3 個超対応(現状 2 まで)。不足時の診断を強化。 +2) AOT FileBox スモーク(read/exists 型)を追加し `.o→EXE` まで通す。 +3) jit‑direct のエラー観測を JSONL で整備(resolve失敗/hostcallフォールバック等)。 +4) docs: README/CURRENT_TASK に VM=plugin‑first の運用を明記、実行スクリプトの一覧を追記。 + +実行コマンド(要点) +- VM(厳格): `NYASH_VM_PLUGIN_STRICT=1 ./target/release/nyash --backend vm ` +- JIT直: `NYASH_JIT_THRESHOLD=1 ./target/release/nyash --jit-direct ` +- AOT: `bash tools/build_aot.sh ` + +【Handoff (Very Compact) 2025‑09‑07】 +- ブランチ/場所: nyash_project/nyash_cranelift(Cranelift 専用ツリー) +- 実行モード: plugins‑only(必ず `NYASH_PLUGIN_ONLY=1`) +- プラグイン: Console/String/Array/Map/File/Integer を `cargo build --release` 済み前提 +- JIT 直: `NYASH_JIT_THRESHOLD=1 ./target/release/nyash --jit-direct apps/tests/mir-branch-ret/main.nyash` → PASS(最小3本もOK) +- AOT .o: `NYASH_AOT_OBJECT_OUT=target/aot_objects ./target/release/nyash --jit-direct apps/tests/mir-branch-ret/main.nyash` → `target/aot_objects/main.o` 生成 + +Next 24h(最優先) +1) FileBox スモークを read/exists 型に調整(plugins‑only で PASS 化) +2) `tools/build_aot.sh` 安定化(`NYASH_PLUGIN_ONLY=1` 明示、ログ抑制解除、`.o` 厳密チェック、`.o→EXE` 実行) +3) AOT スモーク追加(link/実行まで): `cc target/aot_objects/main.o -L crates/nyrt/target/release -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o app_aot` +4) jit-direct 追加最小: select/compare 系(plugins に依存しない) + +参考コマンド(定番) +- ビルド: `cargo build --release --features cranelift-jit` +- プラグイン一括: `tools/smoke_plugins.sh`(無い場合は各 `plugins/*` で `cargo build --release`) +- JIT 直: `NYASH_JIT_THRESHOLD=1 ./target/release/nyash --jit-direct ` +- AOT .o: `NYASH_AOT_OBJECT_OUT=target/aot_objects ./target/release/nyash --jit-direct ` + + +【Quick Update — 2025‑09‑07 plugins‑only 方針 + jit-direct/AOT 最小経路の確定(cranelift ツリー)】 +- ポリシー: 当面 WASM/builtin は使わず「plugins‑only」を既定とする(本ツリーでの実行・検証前提) + - 実行時は `NYASH_PLUGIN_ONLY=1` を付与(既存のツール類は概ね付与済み) +- 必須プラグイン(Console/String/Array/Map/File/Integer)は `cargo build --release` 済みを前提 + - まとめ: `tools/smoke_plugins.sh` で一括ビルド可 +- jit-direct 最小テスト(plugins‑only) + - `NYASH_JIT_THRESHOLD=1 ./target/release/nyash --jit-direct apps/tests/mir-branch-ret/main.nyash` → PASS(unsupported=0) + - ほか `mir-store-load / mir-phi-min / mir-branch-multi` も PASS(単一出口+PHI(min) 合流で安定) +- AOT (ObjectBuilder) + - `NYASH_AOT_OBJECT_OUT=target/aot_objects ./target/release/nyash --jit-direct apps/tests/mir-branch-ret/main.nyash` + - `target/aot_objects/main.o` 生成を確認できる(環境によっては ツールの stdout 抑制で見えづらい → 手動確認推奨) +- 次の着手(本ツリー) + 1) FileBox の最小スモークを read/exists 型に調整し、plugins‑only で PASS に統一(write は後続で仕様照合) + 2) `tools/build_aot.sh` 安定化(ログ抑制解除/診断、`NYASH_PLUGIN_ONLY=1` 明示、`.o` 検知の厳密化) + 3) `.o → EXE` まで一気通しの AOT スモーク追加(nyrt リンク実行) + 4) jit-direct の追加最小(select/compare 等)を plugins 依存なしで拡充 + + +【Quick Update — 2025‑09‑07 Cranelift Lower 追跡と非コアBox共通化(引き継ぎメモ)】 +- 目的: Egui を含む「非コア Box(String/Array/Map/PyRuntime 以外)」を環境変数に依存せず確実に Lower。Lower 追跡ログを一行診断で強化。 +- 対応(main 反映済み): + - 非コア共通経路: `src/jit/lower/core/ops_ext.rs` + - `StringBox/ArrayBox/MapBox/PyRuntimeBox` を除く Box 呼び出しは「名前ベース plugin_invoke」にフォールバックする通常分岐を追加(恒久化)。 + - 受け手は param/local/unknown を `handle.of` でハンドル化。引数は最大2個まで積む。Lower ログ: `[LOWER] . via name-invoke (argc=N)` + - これに伴い、旧 `NYASH_JIT_EGUI_FORCE`(Egui のみの暫定ゲート)は撤去。 + - Copy 伝播: `src/jit/lower/core.rs` の `I::Copy` で `handle_values` と `box_type_map` を伝播(BoxCall の型判断が Copy で消えないように)。 + - 追跡一行診断(未処理時): BoxCall が未ハンドルの場合に、受け手の `param/local/handle`、先頭3引数の分類(i:known_i64 / s:known_str / p:param / l:local / h:handle / -:unknown)、policy 推定(HostCall/PluginInvoke/Fallback 理由)を1行で出力。 + - 例: `[LOWER] fallback(reason=unhandled) box_type='EguiBox' method='birth' recv[param?false local?true handle?true] args=[] policy=plugin_invoke` + - JIT ビルダー安定化: + - `emit_plugin_invoke_by_name`: 冗長な `switch_to_block` を撤去。import シグネチャを「常に I64 返し」に固定(呼び出し側が不要なら破棄)。 + - `switch_to_block`: 同一ブロックの二度切替を回避。未終端ブロックからの切替時には自動ジャンプを注入し CFG を健全化。 + - AOT/Object ビルダー(進行中): + - 名前ベース/tagged invoke の import シグネチャを「常に I64 返し」に固定。 + - begin/end 間で Context を再利用する際のリセットを強化(ctx/fbc/blocks をクリア)。 + +- 現状の確認(JIT 直): + - コマンド(Debug): `NYASH_JIT_TRACE_LOWER=1 ./target/debug/nyash --jit-direct apps/egui-hello/main.nyash` + - 代表ログ: + - `[LOWER] EguiBox.birth via name-invoke (argc=1)` + - `[LOWER] EguiBox.open via name-invoke (argc=3)` + - `[LOWER] EguiBox.uiLabel via name-invoke (argc=2)` + - `[LOWER] EguiBox.run via name-invoke (argc=1)` + - `[LOWER] EguiBox.close via name-invoke (argc=1)` + +- AOT(.o)出力と EXE 連携(進行中): + - 目的: `NYASH_AOT_OBJECT_OUT=target/aot_objects ./target/debug/nyash --jit-direct ` で .o を生成し、`nyrt` とリンク → EXE 実行。 + - 既知課題: Object ビルダーで `cranelift_frontend::FunctionBuilder` を複数箇所で都度生成しており、`func_ctx.is_empty()` アサート(fbc 再利用の順序問題)が発生するケースあり。 + - 改善方針(TODO 下記 1): Object ビルダー側を「1 関数につき 1 つの FunctionBuilder を生成→保持→全 emit/switch を委譲→finalize で破棄」に整理し再発を防止。 + - 参考: name-invoke/ tagged invoke の import はすでに「常に I64 返し」に統一済み。 + +- 再現・検証コマンド(代表): + 1) ビルド(Debug/Release, Cranelift-JIT) + - `cargo build --features cranelift-jit` + - `cargo build --release --features cranelift-jit` + 2) JIT 直(Egui サンプル) + - `NYASH_JIT_TRACE_LOWER=1 ./target/debug/nyash --jit-direct apps/egui-hello/main.nyash` + - 期待: 上記の via name-invoke ログが順に出る(特別な env ゲート不要) + 3) AOT .o 出力(進行中; 現在は Object ビルダーの FB 利用で改善中) + - `rm -rf target/aot_objects; mkdir -p target/aot_objects` + - `NYASH_AOT_OBJECT_OUT=target/aot_objects ./target/debug/nyash --jit-direct apps/egui-hello/main.nyash` + - 期待: `target/aot_objects/.o` が生成(現状: `func_ctx.is_empty()` アサートに遭遇することがある → TODO 1 で是正) + 4) EXE リンク(.o が得られた後)(例: Linux/GNU) + - `cc target/aot_objects/main.o -L target/debug -L crates/nyrt/target/debug -Wl,--whole-archive -lnyrt -Wl,--no-whole-archive -lpthread -ldl -lm -o app_egui` + - 実行: `NYASH_PLUGIN_PATHS=./plugins/nyash-egui-plugin/target/debug ./app_egui` + +- 次アクション(TODO): + 1) Object ビルダーの FunctionBuilder 利用を一元化 + - begin_function で FB を生成・保持し、全ての emit/switch/ensure が同インスタンスを通るように変更。 + - end_function で finalize→ctx/module 定義→state クリア。`func_ctx.is_empty()` アサートの再発を防ぐ。 + 2) スモーク(非コア Box 複数) + - ConsoleBox.log / FileBox.open/close などの最小呼び出しを name-invoke 経由で Lower → 実行ログ確認。 + 3) 診断の統一 + - Extern(env.*)など未処理分岐の一行診断も現行フォーマット(recv/args/policy)に揃える。 + 4) テスト + - `cargo test --features cranelift-jit` のうち、JIT 依存の最小テストを選抜して実行(時間がかかるため段階的に)。 + 5) AOT/EXE 路線の仕上げ + - `.o` の安定化→リンク→実行(`Result:` ログの採取)。Windows/MSVC 向けは別途 power shell スクリプト(既存 docs/tools)参照。 + +— 最終更新: 2025‑09‑07(非コア Box の共通 Lower 化/追跡強化/AOT ビルダーの安定化対応 途中) + + 【Quick Update — 2025‑09‑07 Egui AOT/JIT 状況と次アクション】 - 事象: Windows EXE(AOT, jit-direct emit)で Egui ウィンドウは開くが、`open/uiLabel` が反映されず `M_RUN` のみ実行。ログは `run_window: w=320 h=240 title='Nyash GUI'`(プラグイン側フォールバック)。 - 原因推定: jit-direct 発行の Lower で汎用 PluginInvoke(EguiBox.open/uiLabel)が落ちていない。`box_type_map/handle_values` の伝播(Copy 等)不足、汎用フォールバックの判定漏れが影響。 diff --git a/apps/tests/mir-compare-multi/main.nyash b/apps/tests/mir-compare-multi/main.nyash new file mode 100644 index 00000000..2c34425f --- /dev/null +++ b/apps/tests/mir-compare-multi/main.nyash @@ -0,0 +1,14 @@ +// mir-compare-multi - exercise Compare ops under JIT-direct + +static box Main { + main() { + local c = 0 + if 1 < 2 { c = c + 1 } // true + if 2 < 1 { c = c + 10 } // false (unchanged) + if 5 == 5 { c = c + 100 } // true + if 7 != 7 { c = c + 1000 } // false + // Expect 1 + 100 = 101 + return c + } +} + diff --git a/apps/tests/vm-plugin-smoke-counter/main.nyash b/apps/tests/vm-plugin-smoke-counter/main.nyash new file mode 100644 index 00000000..e6b37271 --- /dev/null +++ b/apps/tests/vm-plugin-smoke-counter/main.nyash @@ -0,0 +1,12 @@ +// vm-plugin-smoke-counter +// Purpose: Verify VM uses plugin boxes deterministically (CounterBox) +// Expected: returns 1 after a single inc() + +// Note: VM runner in this branch initializes plugin host and prefers +// plugin implementations for core boxes. CounterBox is provided by +// libnyash_counter_plugin per nyash.toml. + +local c = new CounterBox() +c.inc() +return c.get() + diff --git a/apps/tests/vm-plugin-smoke-filebox/main.nyash b/apps/tests/vm-plugin-smoke-filebox/main.nyash new file mode 100644 index 00000000..5225422f --- /dev/null +++ b/apps/tests/vm-plugin-smoke-filebox/main.nyash @@ -0,0 +1,12 @@ +// vm-plugin-smoke-filebox +// Purpose: Verify VM uses FileBox plugin to open/read a file. +// Script expects the test file to be created by the runner script. + +local path = "tmp/vm_filebox_smoke.txt" +local f = new FileBox() +f.open(path, "r") +local s = f.read() +f.close() +// Return the length of read content to avoid printing large buffers +return s.length() + diff --git a/src/backend/vm_instructions/plugin_invoke.rs b/src/backend/vm_instructions/plugin_invoke.rs index 9f825539..81281aa3 100644 --- a/src/backend/vm_instructions/plugin_invoke.rs +++ b/src/backend/vm_instructions/plugin_invoke.rs @@ -87,9 +87,16 @@ impl VM { let mut out_len: usize = out.len(); unsafe { (p.inner.invoke_fn)(p.inner.type_id, mh.method_id as u32, p.inner.instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) }; let vm_out_raw: VMValue = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { + eprintln!("[VM←Plugin] tag={} size={} bytes={}", tag, _sz, payload.len()); + } match tag { 1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void), - 2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void), + 2 => { + let v = crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or_default(); + if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { eprintln!("[VM←Plugin] decode i32={}", v); } + VMValue::Integer(v as i64) + }, 5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void), 6 | 7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)), 8 => { diff --git a/src/interpreter/core.rs b/src/interpreter/core.rs index 0f9312b5..e41ccb9e 100644 --- a/src/interpreter/core.rs +++ b/src/interpreter/core.rs @@ -118,6 +118,35 @@ impl NyashInterpreter { // Register MethodBox invoker once (idempotent) self::register_methodbox_invoker(); + // Best-effort: initialize plugin host/config when running interpreter standalone + // This mirrors runner initialization so that `new FileBox()` etc. work without the CLI path. + // Policy: enable plugin-builtins for FileBox/TOMLBox by default; Array/Map remain builtin unless explicitly overridden. + if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") { + if std::path::Path::new("nyash.toml").exists() { + let needs_init = { + let host = crate::runtime::get_global_plugin_host(); + host.read().map(|h| h.config_ref().is_none()).unwrap_or(true) + }; + if needs_init { + let _ = crate::runtime::init_global_plugin_host("nyash.toml"); + // Apply config to BoxFactoryRegistry so UnifiedBoxRegistry can resolve plugin boxes + crate::runner_plugin_init::init_bid_plugins(); + } + // Prefer plugin implementations for specific types when available + if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() { + std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1"); + } + // Merge override list with FileBox/TOMLBox only (safe defaults for interpreter flows) + let mut override_types: Vec = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") { + list.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect() + } else { vec![] }; + for t in ["FileBox", "TOMLBox"] { + if !override_types.iter().any(|x| x == t) { override_types.push(t.to_string()); } + } + std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(",")); + } + } + this } diff --git a/src/jit/lower/builder/cranelift.rs b/src/jit/lower/builder/cranelift.rs index ceddf49c..c7e8d4ad 100644 --- a/src/jit/lower/builder/cranelift.rs +++ b/src/jit/lower/builder/cranelift.rs @@ -512,7 +512,7 @@ impl IRBuilder for CraneliftBuilder { while args.len() < _argc { args.push(fb.ins().iconst(types::I64, 0)); } }); for _ in 0.._argc { sig.params.push(AbiParam::new(types::I64)); } - if has_ret { sig.returns.push(AbiParam::new(types::I64)); } + let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare import failed"); if let Some(v) = tls_call_import_ret(&mut self.module, func_id, &args, has_ret) { self.value_stack.push(v); } } @@ -639,11 +639,7 @@ impl IRBuilder for CraneliftBuilder { } fn emit_plugin_invoke_by_name(&mut self, method: &str, argc: usize, has_ret: bool) { use cranelift_codegen::ir::{AbiParam, Signature, types}; - Self::with_fb(|fb| { - 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); } - }); - // Collect call args + // Collect call args let mut arg_vals: Vec = { let take_n = argc.min(self.value_stack.len()); let mut tmp = Vec::new(); @@ -656,7 +652,8 @@ impl IRBuilder for CraneliftBuilder { sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); - if has_ret { sig.returns.push(AbiParam::new(types::I64)); } + sig.returns.push(AbiParam::new(types::I64)); + let sym = match method { "getattr" => "nyash_plugin_invoke_name_getattr_i64", _ => "nyash_plugin_invoke_name_call_i64" }; let func_id = self.module.declare_function(sym, cranelift_module::Linkage::Import, &sig).expect("declare name shim failed"); let ret_val = Self::with_fb(|fb| { @@ -710,13 +707,20 @@ impl IRBuilder for CraneliftBuilder { } fn switch_to_block(&mut self, index: usize) { if index >= self.blocks.len() { return; } + // Avoid redundant switch_to_block calls that can trip FunctionBuilder state + if self.current_block_index == Some(index) { return; } Self::with_fb(|fb| { // If switching away from a non-terminated block, inject jump to keep CFG sane if let Some(cur) = self.current_block_index { - if self.cur_needs_term && cur != index { fb.ins().jump(self.blocks[index], &[]); self.cur_needs_term = false; } + if self.cur_needs_term && cur != index { + fb.ins().jump(self.blocks[index], &[]); + self.cur_needs_term = false; + } } fb.switch_to_block(self.blocks[index]); self.current_block_index = Some(index); + // New current block now requires a terminator before any further switch + self.cur_needs_term = true; }); } fn seal_block(&mut self, _index: usize) { /* final sealing handled in end_function */ } diff --git a/src/jit/lower/builder/object.rs b/src/jit/lower/builder/object.rs index fbab8793..3c94d554 100644 --- a/src/jit/lower/builder/object.rs +++ b/src/jit/lower/builder/object.rs @@ -65,6 +65,12 @@ impl IRBuilder for ObjectBuilder { self.current_name = Some(name.to_string()); self.value_stack.clear(); self.value_tags.clear(); + // Reset contexts to satisfy Cranelift requirements when reusing the builder + self.ctx = cranelift_codegen::Context::new(); + self.fbc = cranelift_frontend::FunctionBuilderContext::new(); + self.blocks.clear(); + self.entry_block = None; + self.current_block_index = None; if !self.typed_sig_prepared { let call_conv = self.module.isa().default_call_conv(); let mut sig = Signature::new(call_conv); @@ -93,6 +99,10 @@ impl IRBuilder for ObjectBuilder { let finished = std::mem::replace(&mut self.module, Self::fresh_module()); let product = finished.finish(); self.object_bytes = Some(product.emit().expect("emit object")); + // Clear per-function state to allow reuse + self.blocks.clear(); + self.entry_block = None; + self.current_block_index = None; } 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; } @@ -477,7 +487,7 @@ impl IRBuilder for ObjectBuilder { // 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)); } + 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); @@ -530,7 +540,7 @@ impl IRBuilder for ObjectBuilder { 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)); } + 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); diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index ddefdb71..df526bf8 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -494,6 +494,9 @@ impl LowerCore { if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); } if let Some(v) = self.known_f64.get(src).copied() { self.known_f64.insert(*dst, v); } if let Some(v) = self.known_str.get(src).cloned() { self.known_str.insert(*dst, v); } + // Propagate handle/type knowledge to keep BoxCall routing stable across copies + if self.handle_values.contains(src) { self.handle_values.insert(*dst); } + if let Some(bt) = self.box_type_map.get(src).cloned() { self.box_type_map.insert(*dst, bt); } // Propagate boolean classification through Copy if self.bool_values.contains(src) { self.bool_values.insert(*dst); } // If source is a parameter, materialize it on the stack for downstream ops and persist into dst slot @@ -713,7 +716,40 @@ impl LowerCore { } } let handled = self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())?; - if trace { eprintln!("[LOWER] BoxCall recv={:?} method={} handled={} box_type={:?} dst?={}", array, method, handled, self.box_type_map.get(&array), dst.is_some()); } + if trace { + eprintln!( + "[LOWER] BoxCall recv={:?} method={} handled={} box_type={:?} dst?={}", + array, method, handled, self.box_type_map.get(&array), dst.is_some() + ); + if !handled { + let bt = self.box_type_map.get(&array).cloned().unwrap_or_default(); + let is_param = self.param_index.contains_key(&array); + let has_local = self.local_index.contains_key(&array); + let is_handle = self.handle_values.contains(&array); + // Classify up to first 3 args: i(known_i64) s(known_str) p(param) l(local) h(handle) -(unknown) + let mut arg_kinds: Vec<&'static str> = Vec::new(); + for a in args.iter().take(3) { + let k = if self.known_i64.contains_key(a) { "i" } + else if self.known_str.contains_key(a) { "s" } + else if self.param_index.contains_key(a) { "p" } + else if self.local_index.contains_key(a) { "l" } + else if self.handle_values.contains(a) { "h" } + else { "-" }; + arg_kinds.push(k); + } + // Policy hint: whether a mapped HostCall exists for (box_type, method) + let policy = crate::jit::policy::invoke::decide_box_method(&bt, method.as_str(), 1 + args.len(), dst.is_some()); + let policy_str = match policy { + crate::jit::policy::invoke::InvokeDecision::HostCall { ref symbol, .. } => format!("hostcall:{}", symbol), + crate::jit::policy::invoke::InvokeDecision::PluginInvoke { .. } => "plugin_invoke".to_string(), + crate::jit::policy::invoke::InvokeDecision::Fallback { ref reason } => format!("fallback:{}", reason), + }; + eprintln!( + "[LOWER] fallback(reason=unhandled) box_type='{}' method='{}' recv[param?{} local?{} handle?{}] args={:?} policy={}", + bt, method, is_param, has_local, is_handle, arg_kinds, policy_str + ); + } + } if handled { return Ok(()); } diff --git a/src/jit/lower/core/ops_ext.rs b/src/jit/lower/core/ops_ext.rs index 25604a03..c5ff1e19 100644 --- a/src/jit/lower/core/ops_ext.rs +++ b/src/jit/lower/core/ops_ext.rs @@ -19,8 +19,9 @@ impl LowerCore { let argc = 1 + args.len(); if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { self.push_value_if_known_or_param(b, box_val); } let decision = crate::jit::policy::invoke::decide_box_method(&bt, m, argc, dst.is_some()); - if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision { + if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } = decision { b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some()); + crate::jit::observe::lower_plugin_invoke(&box_type, m, type_id, method_id, argc); if let Some(d) = dst { self.handle_values.insert(*d); } } else { if dst.is_some() { b.emit_const_i64(0); } } } else if (bt == "PyRuntimeBox" && (m == "getattr" || m == "call")) { @@ -82,8 +83,9 @@ impl LowerCore { if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); } // Resolve plugin invoke for ConsoleBox.method let decision = crate::jit::policy::invoke::decide_box_method("ConsoleBox", method_name, 2, dst.is_some()); - if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision { + if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } = decision { b.emit_plugin_invoke(type_id, method_id, 2, dst.is_some()); + crate::jit::observe::lower_plugin_invoke(&box_type, method_name, type_id, method_id, 2); } else if dst.is_some() { b.emit_const_i64(0); } return Ok(()); } @@ -172,8 +174,51 @@ impl LowerCore { dst.clone(), ); return Ok(true); + } + // 非コアBox(例: EguiBox など)は共通処理として名前ベースの plugin_invoke にフォールバック + // コアBoxの目安: StringBox/ArrayBox/MapBox(この後の分岐で処理)と PyRuntimeBox(専用分岐済) + if let Some(bt) = self.box_type_map.get(array).cloned() { + let is_core = bt == "StringBox" || bt == "ArrayBox" || bt == "MapBox" || bt == "PyRuntimeBox"; + if !is_core { + // receiver: prefer existing local slot/param; ensure a valid runtime handle + if let Some(slot) = self.local_index.get(array).copied() { + b.load_local_i64(slot); + } else if let Some(pidx) = self.param_index.get(array).copied() { + b.emit_param_i64(pidx); + b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true); + } else { + self.push_value_if_known_or_param(b, array); + b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true); + } + // push up to 2 args (name-shim supports at most 2 positional args beyond receiver) + let take_n = core::cmp::min(args.len(), 2); + for i in 0..take_n { if let Some(v) = args.get(i) { self.push_value_if_known_or_param(b, v); } } + let argc = 1 + take_n; + b.emit_plugin_invoke_by_name(method, argc, dst.is_some()); + if std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1") { + crate::jit::events::emit_lower( + serde_json::json!({ + "id": format!("plugin_name:{}:{}", bt, method), + "decision": "allow", + "reason": "plugin_invoke_by_name", + "argc": argc + }), + "plugin","" + ); + } + if let Some(d) = dst { + self.handle_values.insert(d); + let slot = *self.local_index.entry(d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id }); + b.store_local_i64(slot); + } + if std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1") { + eprintln!("[LOWER] {}.{} via name-invoke (argc={})", bt, method, argc); + } + return Ok(true); + } } // Builtins-to-plugin path (subset for String/Array/Map critical ops) +// Builtins-to-plugin path (subset for String/Array/Map critical ops) if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") { // StringBox (length/is_empty/charCodeAt) if matches!(method, "length" | "is_empty" | "charCodeAt") { @@ -661,6 +706,32 @@ impl LowerCore { _ => {} } // Not handled here + if std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1") { + let bt = self.box_type_map.get(array).cloned().unwrap_or_default(); + let is_param = self.param_index.contains_key(array); + let has_local = self.local_index.contains_key(array); + let is_handle = self.handle_values.contains(array); + let mut arg_kinds: Vec<&'static str> = Vec::new(); + for a in args.iter().take(3) { + let k = if self.known_i64.contains_key(a) { "i" } + else if self.known_str.contains_key(a) { "s" } + else if self.param_index.contains_key(a) { "p" } + else if self.local_index.contains_key(a) { "l" } + else if self.handle_values.contains(a) { "h" } + else { "-" }; + arg_kinds.push(k); + } + let policy = crate::jit::policy::invoke::decide_box_method(&bt, method, 1 + args.len(), dst.is_some()); + let policy_str = match policy { + crate::jit::policy::invoke::InvokeDecision::HostCall { ref symbol, .. } => format!("hostcall:{}", symbol), + crate::jit::policy::invoke::InvokeDecision::PluginInvoke { .. } => "plugin_invoke".to_string(), + crate::jit::policy::invoke::InvokeDecision::Fallback { ref reason } => format!("fallback:{}", reason), + }; + eprintln!( + "[LOWER] unhandled BoxCall: box_type='{}' method='{}' recv[param?{} local?{} handle?{}] args={:?} policy={}", + bt, method, is_param, has_local, is_handle, arg_kinds, policy_str + ); + } Ok(false) } } diff --git a/src/main.rs b/src/main.rs index a9a1ff04..bd0b514b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,7 @@ pub mod config; // Runtime system (plugins, registry, etc.) pub mod runtime; +pub mod runner_plugin_init; pub mod debug; pub mod grammar; // Phase 11.9 unified grammar scaffolding pub mod syntax; // Phase 12.7: syntax sugar config and helpers (mirror lib layout) diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index d2bb1ba3..788b7a88 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -6,6 +6,54 @@ use std::sync::Arc; impl NyashRunner { /// Execute VM mode (split) pub(crate) fn execute_vm_mode(&self, filename: &str) { + // Enforce plugin-first policy for VM on this branch (deterministic): + // - Initialize plugin host if not yet loaded + // - Prefer plugin implementations for core boxes + // - Optionally fail fast when plugins are missing (NYASH_VM_PLUGIN_STRICT=1) + { + // Initialize unified registry globals (idempotent) + nyash_rust::runtime::init_global_unified_registry(); + // Init plugin host from nyash.toml if not yet loaded + let need_init = { + let host = nyash_rust::runtime::get_global_plugin_host(); + host.read().map(|h| h.config_ref().is_none()).unwrap_or(true) + }; + if need_init { + let _ = nyash_rust::runtime::init_global_plugin_host("nyash.toml"); + crate::runner_plugin_init::init_bid_plugins(); + } + // Prefer plugin-builtins for core types unless explicitly disabled + if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() { + std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1"); + } + // Build stable override list + let mut override_types: Vec = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") { + list.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect() + } else { vec![] }; + for t in [ + "FileBox", "TOMLBox", // IO/config + "ConsoleBox", "StringBox", "IntegerBox", // core value-ish + "ArrayBox", "MapBox", // collections + "MathBox", "TimeBox" // math/time helpers + ] { + if !override_types.iter().any(|x| x == t) { override_types.push(t.to_string()); } + } + std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(",")); + + // Strict mode: verify providers exist for override types + if std::env::var("NYASH_VM_PLUGIN_STRICT").ok().as_deref() == Some("1") { + let v2 = nyash_rust::runtime::get_global_registry(); + let mut missing: Vec = Vec::new(); + for t in ["FileBox","ConsoleBox","ArrayBox","MapBox","StringBox","IntegerBox"] { + if v2.get_provider(t).is_none() { missing.push(t.to_string()); } + } + if !missing.is_empty() { + eprintln!("❌ VM plugin-first strict: missing providers for: {:?}", missing); + std::process::exit(1); + } + } + } + // Read the file let code = match fs::read_to_string(filename) { Ok(content) => content, @@ -78,43 +126,68 @@ impl NyashRunner { match vm.execute_module(&module_vm) { Ok(result) => { println!("✅ VM execution completed successfully!"); - // Pretty-print using MIR return type when available to avoid Void-looking floats/bools - if let Some(func) = compile_result.module.functions.get("main") { + // Pretty-print with coercions for plugin-backed values + // Prefer MIR signature when available, but fall back to runtime coercions to keep VM/JIT consistent. + let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") { use nyash_rust::mir::MirType; use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox}; use nyash_rust::boxes::FloatBox; - let (ety, sval) = match &func.signature.return_type { + match &func.signature.return_type { MirType::Float => { if let Some(fb) = result.as_any().downcast_ref::() { ("Float", format!("{}", fb.value)) } else if let Some(ib) = result.as_any().downcast_ref::() { ("Float", format!("{}", ib.value as f64)) - } else { ("Float", result.to_string_box().value) } + } else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) { + ("String", s) + } else { + (result.type_name(), result.to_string_box().value) + } } MirType::Integer => { if let Some(ib) = result.as_any().downcast_ref::() { ("Integer", ib.value.to_string()) - } else { ("Integer", result.to_string_box().value) } + } else if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) { + ("Integer", i.to_string()) + } else { + (result.type_name(), result.to_string_box().value) + } } MirType::Bool => { if let Some(bb) = result.as_any().downcast_ref::() { ("Bool", bb.value.to_string()) } else if let Some(ib) = result.as_any().downcast_ref::() { ("Bool", (ib.value != 0).to_string()) - } else { ("Bool", result.to_string_box().value) } + } else { + (result.type_name(), result.to_string_box().value) + } } MirType::String => { if let Some(sb) = result.as_any().downcast_ref::() { ("String", sb.value.clone()) - } else { ("String", result.to_string_box().value) } + } else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) { + ("String", s) + } else { + (result.type_name(), result.to_string_box().value) + } } - _ => { (result.type_name(), result.to_string_box().value) } - }; - println!("ResultType(MIR): {}", ety); - println!("Result: {}", sval); + _ => { + if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) { + ("Integer", i.to_string()) + } else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) { + ("String", s) + } else { (result.type_name(), result.to_string_box().value) } + } + } } else { - println!("Result: {:?}", result); - } + if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) { + ("Integer", i.to_string()) + } else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) { + ("String", s) + } else { (result.type_name(), result.to_string_box().value) } + }; + println!("ResultType(MIR): {}", ety); + println!("Result: {}", sval); }, Err(e) => { eprintln!("❌ VM execution error: {}", e); process::exit(1); } } diff --git a/src/runtime/plugin_loader_v2/enabled/loader.rs b/src/runtime/plugin_loader_v2/enabled/loader.rs index a42a07b0..72a61d98 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader.rs @@ -266,26 +266,49 @@ impl PluginLoaderV2 { // Encode TLV args via shared helper (numeric→string→toString) let tlv = crate::runtime::plugin_ffi_common::encode_args(args); let (_code, out_len, out) = super::host_bridge::invoke_alloc(plugin.invoke_fn, type_id, method.method_id, instance_id, &tlv); - // Minimal decoding by method name - match method_name { - // Expect UTF-8 string result in bytes → StringBox - "toUtf8" | "toString" => { - let s = String::from_utf8_lossy(&out[0..out_len]).to_string(); - return Ok(Some(Box::new(crate::box_trait::StringBox::new(s)))); - } - // Expect IntegerBox via little-endian i64 in first 8 bytes - "get" => { - if out_len >= 8 { let mut buf=[0u8;8]; buf.copy_from_slice(&out[0..8]); let n=i64::from_le_bytes(buf); return Ok(Some(Box::new(crate::box_trait::IntegerBox::new(n)))) } - return Ok(Some(Box::new(crate::box_trait::IntegerBox::new(0)))); - } - // Float path (approx): read 8 bytes as f64 and Box as FloatBox - "toDouble" => { - if out_len >= 8 { let mut buf=[0u8;8]; buf.copy_from_slice(&out[0..8]); let x=f64::from_le_bytes(buf); return Ok(Some(Box::new(crate::boxes::FloatBox::new(x)))) } - return Ok(Some(Box::new(crate::boxes::FloatBox::new(0.0)))); - } - _ => {} + // Decode TLV (first entry) generically + if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + let bx: Box = match tag { + 1 => Box::new(crate::box_trait::BoolBox::new(crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false))), + 2 => Box::new(crate::box_trait::IntegerBox::new(crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or(0) as i64)), + 3 => { + // i64 payload + if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); Box::new(crate::box_trait::IntegerBox::new(i64::from_le_bytes(b))) } + else { Box::new(crate::box_trait::IntegerBox::new(0)) } + } + 5 => { + let x = crate::runtime::plugin_ffi_common::decode::f64(payload).unwrap_or(0.0); + Box::new(crate::boxes::FloatBox::new(x)) + } + 6 | 7 => { + let s = crate::runtime::plugin_ffi_common::decode::string(payload); + Box::new(crate::box_trait::StringBox::new(s)) + } + 8 => { + // Plugin handle (type_id, instance_id) → wrap into PluginBoxV2 + if let Some((ret_type, inst)) = crate::runtime::plugin_ffi_common::decode::plugin_handle(payload) { + let handle = Arc::new(super::types::PluginHandleInner { + type_id: ret_type, + invoke_fn: plugin.invoke_fn, + instance_id: inst, + fini_method_id: None, + finalized: std::sync::atomic::AtomicBool::new(false), + }); + Box::new(super::types::PluginBoxV2 { box_type: box_type.to_string(), inner: handle }) + } else { Box::new(crate::box_trait::VoidBox::new()) } + } + 9 => { + // Host handle (u64) → try to map back to BoxRef, else void + if let Some(u) = crate::runtime::plugin_ffi_common::decode::u64(payload) { + if let Some(arc) = crate::runtime::host_handles::get(u) { return Ok(Some(arc.share_box())); } + } + Box::new(crate::box_trait::VoidBox::new()) + } + _ => Box::new(crate::box_trait::VoidBox::new()), + }; + return Ok(Some(bx)); } - Ok(None) + Ok(Some(Box::new(crate::box_trait::VoidBox::new()))) } pub fn create_box(&self, box_type: &str, _args: &[Box]) -> BidResult> { diff --git a/tools/aot_counter_smoke.sh b/tools/aot_counter_smoke.sh new file mode 100644 index 00000000..d305a199 --- /dev/null +++ b/tools/aot_counter_smoke.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT_DIR" + +APP="apps/tests/vm-plugin-smoke-counter/main.nyash" + +echo "[build] core + counter plugin" +cargo build --release --features cranelift-jit +cargo build -p nyash-counter-plugin --release + +echo "[AOT] emit + link + run" +bash tools/build_aot.sh "$APP" app_counter_aot + diff --git a/tools/build_aot.sh b/tools/build_aot.sh index 7a10b00a..5dc6ca40 100644 --- a/tools/build_aot.sh +++ b/tools/build_aot.sh @@ -1,88 +1,38 @@ #!/usr/bin/env bash set -euo pipefail -if [[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]]; then - set -x -fi +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT_DIR" -usage() { - cat << USAGE -Usage: tools/build_aot.sh [-o ] +APP=${1:-apps/tests/mir-branch-ret/main.nyash} +OUT=${2:-app_aot} +OBJ_DIR=${OBJ_DIR:-target/aot_objects} +OBJ_BASENAME=$(basename "$APP" .nyash) +OBJ_PATH="$OBJ_DIR/$OBJ_BASENAME.o" -Builds Nyash with Cranelift, emits an AOT object (.o) for the given program, -links it with libnyrt.a into a native executable, and prints the result path. +echo "[1/5] build nyash (cranelift-jit)" +cargo build --release --features cranelift-jit -Options: - -o Output executable name (default: app) +echo "[2/5] build nyrt (static lib)" +cargo build -p nyrt --release -Notes: - - Requires a Cranelift-enabled build. - - Runtime requires nyash.toml and plugin .so files resolvable by nyash.toml paths. -USAGE -} +echo "[3/5] emit object (.o) via jit-direct" +mkdir -p "$OBJ_DIR" +env -u NYASH_OPT_DIAG_FORBID_LEGACY NYASH_SKIP_TOML_ENV=1 NYASH_PLUGIN_ONLY=1 NYASH_AOT_OBJECT_OUT="$OBJ_DIR" ./target/release/nyash --jit-direct "$APP" -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 +if [[ ! -f "$OBJ_PATH" ]]; then + echo "❌ object not found: $OBJ_PATH" >&2 + echo "Contents of $OBJ_DIR:" >&2 + ls -la "$OBJ_DIR" >&2 || true exit 1 fi +ls -l "$OBJ_PATH" -echo "[1/4] Building nyash (Cranelift) ..." -if ! cargo build --release --features cranelift-jit >/dev/null; then - echo "error: failed to build nyash with Cranelift (feature cranelift-jit)" >&2 - exit 1 -fi - -echo "[2/4] Emitting object (.o) via JIT (jit-direct) ..." -rm -rf target/aot_objects && mkdir -p target/aot_objects -# Directly request main.o to be written (engine will treat non-directory path as exact output file) -NYASH_AOT_OBJECT_OUT=target/aot_objects/main.o \ -NYASH_USE_PLUGIN_BUILTINS=1 \ -NYASH_JIT_ONLY=1 \ -# Relax strict by default to allow partial lowering to still emit objects. -# Users can re-enable strict with: export NYASH_JIT_STRICT=1 -NYASH_JIT_STRICT=${NYASH_JIT_STRICT:-0} \ -NYASH_JIT_NATIVE_F64=1 \ -# Allow f64 shim for PyObjectBox.call (type_id=41, method_id=2) -NYASH_JIT_PLUGIN_F64="${NYASH_JIT_PLUGIN_F64:-41:2}" \ -NYASH_JIT_ARGS_HANDLE_ONLY=1 \ -NYASH_JIT_THRESHOLD=1 \ -./target/release/nyash --jit-direct "$INPUT" >/dev/null || true - -OBJ="target/aot_objects/main.o" -if [[ ! -f "$OBJ" ]]; then - echo "error: object not generated: $OBJ" >&2 - echo "hint: Ensure main() is lowerable under current JIT coverage." >&2 - echo "hint: Run jit-direct manually with the same envs to diagnose lowering coverage." >&2 - exit 2 -fi - -echo "[3/4] Building libnyrt.a ..." -if [[ ! -d crates/nyrt ]]; then - echo "error: crates/nyrt not found. Please ensure nyrt runtime crate exists." >&2 - exit 3 -fi -( cd crates/nyrt && cargo build --release >/dev/null ) - -echo "[4/4] Linking $OUT ..." -cc "$OBJ" \ +echo "[4/5] link with nyrt -> $OUT" +cc "$OBJ_PATH" \ -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)" -if [[ "${NYASH_CLI_VERBOSE:-0}" == "1" ]]; then - echo "info: run with NYASH_CLI_VERBOSE=1 to see detailed steps and commands" -fi +echo "[5/5] run $OUT" +./"$OUT" diff --git a/tools/jit_compare_smoke.sh b/tools/jit_compare_smoke.sh new file mode 100644 index 00000000..d7aa81a5 --- /dev/null +++ b/tools/jit_compare_smoke.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT_DIR" + +echo "[build] nyash (cranelift-jit)" +cargo build --release --features cranelift-jit + +run_case() { + local app="$1" + echo "[run] jit-direct: $app" + NYASH_JIT_THRESHOLD=1 ./target/release/nyash --jit-direct "$app" +} + +run_case apps/tests/mir-branch-ret/main.nyash +run_case apps/tests/mir-compare-multi/main.nyash + +echo "[ok] jit compare smokes completed" + diff --git a/tools/vm_filebox_smoke.sh b/tools/vm_filebox_smoke.sh new file mode 100644 index 00000000..b6db8124 --- /dev/null +++ b/tools/vm_filebox_smoke.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT_DIR" + +echo "[build] nyash (vm)" +cargo build --release + +echo "[build] plugins: filebox" +cargo build -p nyash-filebox-plugin --release + +mkdir -p tmp +echo -n "OK" > tmp/vm_filebox_smoke.txt + +APP="apps/tests/vm-plugin-smoke-filebox/main.nyash" +echo "[run] VM plugin-first strict: $APP" +NYASH_VM_PLUGIN_STRICT=1 ./target/release/nyash --backend vm "$APP" + diff --git a/tools/vm_plugin_smoke.sh b/tools/vm_plugin_smoke.sh new file mode 100644 index 00000000..6f9014ea --- /dev/null +++ b/tools/vm_plugin_smoke.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) +cd "$ROOT_DIR" + +echo "[build] nyash (vm)" +cargo build --release + +echo "[build] core plugins (subset)" +cargo build -p nyash-counter-plugin --release + +APP="apps/tests/vm-plugin-smoke-counter/main.nyash" +echo "[run] VM plugin-first strict: $APP" +NYASH_VM_PLUGIN_STRICT=1 ./target/release/nyash --backend vm "$APP" +