diff --git a/docs/development/current/CURRENT_TASK.md b/docs/development/current/CURRENT_TASK.md index b2a7523e..d99824e8 100644 --- a/docs/development/current/CURRENT_TASK.md +++ b/docs/development/current/CURRENT_TASK.md @@ -9,10 +9,11 @@ Phase 10.10 は完了(DoD確認済)。**重大な発見**:プラグイン - すべてのBoxをプラグイン化すれば、JIT→EXEが自然に実現可能 - "Everything is Box" → "Everything is Plugin" への進化 -## ⏱️ 今日のサマリ -- 発見: プラグインBox経由でのJIT→EXE実現可能性 -- 決定: Phase 10.1を「プラグインBox統一化」に変更 -- 移動: 旧Phase 10.1(Python統合)→ Phase 10.5へ +## ⏱️ 今日のサマリ(Array/Map プラグイン経路の安定化→10.2へ) +- 実装: Array/Map のプラグイン(BID-FFI v1)を作成し、nyash.toml に統合 +- Lower: `NYASH_USE_PLUGIN_BUILTINS=1` で Array(len/get/push/set), Map(size/get/has/set) を `emit_plugin_invoke(..)` に配線 +- サンプル: array/map デモを追加し VM 実行で正常動作確認 +- 次: 10.2(Craneliftの実呼び出し)に着手 ## 現在地(Done / Doing / Next) - ✅ Done(Phase 10.10) @@ -79,6 +80,23 @@ NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 \ # compileイベントのみ(必要時) NYASH_JIT_EVENTS_COMPILE=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS_PATH=events.jsonl \ ./target/release/nyash --backend vm examples/jit_map_get_param_hh.nyash + +# Plugin demos(Array/Map) +(cd plugins/nyash-array-plugin && cargo build --release) +(cd plugins/nyash-map-plugin && cargo build --release) +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/array_plugin_demo.nyash +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/array_plugin_set_demo.nyash +NYASH_CLI_VERBOSE=1 ./target/release/nyash --backend vm examples/map_plugin_ro_demo.nyash + +## ⏭️ Next(Phase 10.2: JIT実呼び出しの実体化) +- 目的: Craneliftの `emit_plugin_invoke` を実装し、JITでも実体のプラグインAPIを呼ぶ +- 方針: + - シム関数 `extern "C" nyash_plugin_invoke3_i64(type_id, method_id, argc, a0, a1, a2) -> i64` を実装 + - a0: 受け手(param index/負なら未解決) + - args: i64 を TLV にエンコードして plugin invoke_fn へ橋渡し + - 戻り: TLV(i64/Bool)の最初の値を i64 に正規化 + - CraneliftBuilder: `emit_plugin_invoke` で上記シムを import→call(常に6引数) + - 対象: Array(len/get/push/set), Map(size/get/has/set) の i64 1〜2引数経路 ``` ## 参考リンク @@ -92,4 +110,3 @@ NYASH_JIT_EVENTS_COMPILE=1 NYASH_JIT_HOSTCALL=1 NYASH_JIT_EVENTS_PATH=events.jso - 状態確認: `git status` / `git log --oneline -3` / `cargo check` - スモーク: `bash tools/smoke_phase_10_10.sh` - 次の一手: core_hostcall → core_ops の順に分割、毎回ビルド/スモークで確認 - diff --git a/docs/development/roadmap/phases/phase-10.1/README.md b/docs/development/roadmap/phases/phase-10.1/README.md index 4b85d200..38ddd8a9 100644 --- a/docs/development/roadmap/phases/phase-10.1/README.md +++ b/docs/development/roadmap/phases/phase-10.1/README.md @@ -60,9 +60,18 @@ plugins/ └── nyash-net-plugin/ ``` -## 🔗 関連資料 +## 🔗 関連資料(整備済み) - フェーズ計画の詳細: [phase_plan.md](./phase_plan.md) +- C ABI v0 仕様(JIT/AOT/Plugin共通): ../../../../docs/reference/abi/nyrt_c_abi_v0.md + - 命名: `nyrt_*`(コア)/ `nyplug_{name}_*`(プラグイン) + - 呼出規約: x86_64 SysV / aarch64 AAPCS64 / Win64 + - `*_abi_version()` で fail-fast(v0=1) + +## ストリームエラー対策(長文/大出力を避ける) +- 先頭に短い要約(サマリ)を置く(本READMEの冒頭にあり) +- 詳細設計や長いコードは分割して参照(phase_plan.md / nyrt_c_abi_v0.md) +- コマンドやコードは三連バッククォートで閉じ忘れ防止 - [革新的アプローチ詳細](../../../ideas/new-features/2025-08-28-jit-exe-via-plugin-unification.md) - [プラグインAPI仕様](../../../../reference/plugin-system/) diff --git a/docs/development/roadmap/phases/phase-10.1/c_abi_unified_design.md b/docs/development/roadmap/phases/phase-10.1/c_abi_unified_design.md index 59b0ff36..a73c4732 100644 --- a/docs/development/roadmap/phases/phase-10.1/c_abi_unified_design.md +++ b/docs/development/roadmap/phases/phase-10.1/c_abi_unified_design.md @@ -49,9 +49,8 @@ struct NyBox { - x86_64 SysV / aarch64 AAPCS64 / Win64 をターゲットごとに固定 - Craneliftの`call_conv`を上記に合わせる(JIT/AOT共通) -### 5. バージョン管理 -- `nyrt_abi_version()`を導入(壊れたら即fail) -- プラグインも`nyplug_{name}_abi_version()` +### 5. バージョン管理(fail-fast) +- `nyrt_abi_version()` / `nyplug_{name}_abi_version()`(v0=1)。不一致は起動時に即fail(ローダ側で検査)。 ## 📝 最小ヘッダ雛形 @@ -123,6 +122,8 @@ int32_t nyplug_array_push(NyBox arr, NyBox v); - Windows: `link mod.obj nyrt.lib nyplug_array.lib /OUT:app.exe` 4. 実行:`./app`でJIT無しに動作 +補足: 現行実装ではプラグインは `nyash_plugin_invoke`(BID-FFI v1, TLV)を用いる。v0ではこれを固定し、将来的に `nyplug_*` 直関数を併置する場合も `*_abi_version()` で互換を担保する。 + ## ⚡ 実装順序(重要!) 1. **必要なビルトインBoxをプラグインBoxに変換** @@ -153,4 +154,4 @@ int32_t nyplug_array_push(NyBox arr, NyBox v); --- -*最終更新: 2025-08-28* \ No newline at end of file +*最終更新: 2025-08-28* diff --git a/docs/reference/abi/nyrt_c_abi_v0.md b/docs/reference/abi/nyrt_c_abi_v0.md new file mode 100644 index 00000000..90bd8752 --- /dev/null +++ b/docs/reference/abi/nyrt_c_abi_v0.md @@ -0,0 +1,75 @@ +# NyRT C ABI v0 (JIT/AOT/Plugin 共通) + +- 命名規則: + - コア: `nyrt_*` + - プラグイン: `nyplug_{name}_*` +- 呼出規約: x86_64 SysV / aarch64 AAPCS64 / Win64(Cranelift `call_conv` と一致) +- 型: `i32/i64/u64/double/void*` に限定。`bool` は `u8`。構造体は不透明ハンドル。 +- 互換性: `nyrt_abi_version()` / `nyplug_{name}_abi_version()`(v0=1)で fail-fast。 + +## 最小ヘッダ例(コア: nyrt.h) +```c +#pragma once +#include +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NyBox { void* data; uint64_t typeid; uint32_t flags; uint32_t gen; } NyBox; + +int32_t nyrt_abi_version(void); + +// Box 基本 +NyBox nyrt_box_new(uint64_t typeid, uint64_t size); +void nyrt_box_free(NyBox b); +int32_t nyrt_adopt(NyBox parent, NyBox child); +int32_t nyrt_release(NyBox parent, NyBox child, NyBox* out_weak); +NyBox nyrt_weak_load(NyBox weak); + +// GC/Safepoint +void nyrt_epoch_collect(void); + +// Sync(最小) +void* nyrt_mutex_lock(NyBox sync); +void nyrt_mutex_unlock(void* guard); + +// Bus +int32_t nyrt_bus_send(NyBox port, NyBox msg); + +#ifdef __cplusplus +} +#endif +``` + +## プラグインヘッダ例(nyplug_array.h) +```c +#pragma once +#include "nyrt.h" +#ifdef __cplusplus +extern "C" { +#endif + +int32_t nyplug_array_abi_version(void); +NyBox nyplug_array_new(void); +int32_t nyplug_array_get(NyBox arr, uint64_t i, NyBox* out); +int32_t nyplug_array_set(NyBox arr, uint64_t i, NyBox v); +uint64_t nyplug_array_len(NyBox arr); + +#ifdef __cplusplus +} +#endif +``` + +## AOT(静的リンク)の流れ +- CLIF から `.o` を出力(未解決: `nyrt_*` / `nyplug_*`)。 +- リンク(Linux/macOS の例): + ```bash + cc app.o -static -L. -lnyrt -lnyplug_array -o app + ``` +- 実行: `./app` + +## 注意 +- 例外/パニックの越境は不可(戻り値/エラーコードで返す)。 +- C++ 側は必ず `extern "C"`、ELF は `visibility("default")`。 +- ABI 破壊変更は `*_v1` などの別シンボルで導入(v0は凍結)。 + diff --git a/nyash.toml b/nyash.toml index bda0fef5..6b5d9975 100644 --- a/nyash.toml +++ b/nyash.toml @@ -154,4 +154,5 @@ birth = { method_id = 0 } size = { method_id = 1 } get = { method_id = 2, args = ["key"] } has = { method_id = 3, args = ["key"] } +set = { method_id = 4, args = ["key", "value"] } fini = { method_id = 4294967295 } diff --git a/plugins/nyash-map-plugin/src/lib.rs b/plugins/nyash-map-plugin/src/lib.rs index f074b7bb..d6ff35d0 100644 --- a/plugins/nyash-map-plugin/src/lib.rs +++ b/plugins/nyash-map-plugin/src/lib.rs @@ -19,6 +19,7 @@ const METHOD_BIRTH: u32 = 0; const METHOD_SIZE: u32 = 1; const METHOD_GET: u32 = 2; // args: i64 key -> TLV i64 const METHOD_HAS: u32 = 3; // args: i64 key -> TLV bool +const METHOD_SET: u32 = 4; // args: i64 key, i64 value -> TLV i64 (size) const METHOD_FINI: u32 = u32::MAX; // Type id (nyash.toml に合わせる) @@ -78,6 +79,16 @@ pub extern "C" fn nyash_plugin_invoke( if let Some(inst) = map.get(&instance_id) { write_tlv_bool(inst.data.contains_key(&key), result, result_len) } else { NYB_E_INVALID_HANDLE } } else { NYB_E_PLUGIN_ERROR } } + METHOD_SET => { + let key = match read_arg_i64(args, args_len, 0) { Some(v) => v, None => return NYB_E_INVALID_ARGS }; + let val = match read_arg_i64(args, args_len, 1) { Some(v) => v, None => return NYB_E_INVALID_ARGS }; + if let Ok(mut map) = INSTANCES.lock() { + if let Some(inst) = map.get_mut(&instance_id) { + inst.data.insert(key, val); + return write_tlv_i64(inst.data.len() as i64, result, result_len); + } else { NYB_E_INVALID_HANDLE } + } else { NYB_E_PLUGIN_ERROR } + } _ => NYB_E_INVALID_METHOD, } } @@ -139,4 +150,3 @@ fn read_arg_i64(args: *const u8, args_len: usize, n: usize) -> Option { } None } - diff --git a/src/jit/lower/builder.rs b/src/jit/lower/builder.rs index 47364273..bc8e1ac6 100644 --- a/src/jit/lower/builder.rs +++ b/src/jit/lower/builder.rs @@ -141,6 +141,48 @@ use cranelift_codegen::ir::InstBuilder; #[cfg(feature = "cranelift-jit")] extern "C" fn nyash_host_stub0() -> i64 { 0 } #[cfg(feature = "cranelift-jit")] +extern "C" fn nyash_plugin_invoke3_i64(type_id: i64, method_id: i64, argc: i64, a0: i64, a1: i64, a2: i64) -> i64 { + use crate::runtime::plugin_loader_v2::PluginBoxV2; + // Resolve receiver instance from legacy VM args (param index) + let mut instance_id: u32 = 0; + let mut invoke: Optioni32> = None; + if a0 >= 0 { + crate::jit::rt::with_legacy_vm_args(|args| { + let idx = a0 as usize; + if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) { + if let Some(p) = b.as_any().downcast_ref::() { + instance_id = p.instance_id(); + invoke = Some(p.inner.invoke_fn); + } + } + }); + } + if invoke.is_none() { return 0; } + // Build TLV args from a1/a2 if present + let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header((argc.saturating_sub(1).max(0) as u16)); + let mut add_i64 = |v: i64| { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, v); }; + if argc >= 2 { add_i64(a1); } + if argc >= 3 { add_i64(a2); } + // Prepare output buffer + let mut out: [u8; 32] = [0; 32]; + let mut out_len: usize = out.len(); + let rc = unsafe { invoke.unwrap()(type_id as u32, method_id as u32, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) }; + if rc != 0 { return 0; } + if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + match tag { + 3 => { // I64 + if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } + if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } + } + 1 => { // Bool + return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; + } + _ => {} + } + } + 0 +} +#[cfg(feature = "cranelift-jit")] use super::extern_thunks::{ nyash_math_sin_f64, nyash_math_cos_f64, nyash_math_abs_f64, nyash_math_min_f64, nyash_math_max_f64, nyash_array_len_h, nyash_array_get_h, nyash_array_set_h, nyash_array_push_h, @@ -690,6 +732,52 @@ impl IRBuilder for CraneliftBuilder { fb.finalize(); } + 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; + use cranelift_module::{Linkage, Module}; + + // Pop argc values (right-to-left): receiver + up to 2 args + let mut arg_vals: Vec = Vec::new(); + let take_n = argc.min(self.value_stack.len()); + for _ in 0..take_n { if let Some(v) = self.value_stack.pop() { arg_vals.push(v); } } + arg_vals.reverse(); + // Pad to 3 values (receiver + a1 + a2) + while arg_vals.len() < 3 { arg_vals.push({ + 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); } + let z = fb.ins().iconst(types::I64, 0); + fb.finalize(); + z + }); } + + // Build signature: (i64 type_id, i64 method_id, i64 argc, i64 a0, i64 a1, i64 a2) -> i64 + let call_conv = self.module.isa().default_call_conv(); + let mut sig = Signature::new(call_conv); + for _ in 0..6 { sig.params.push(AbiParam::new(types::I64)); } + if has_ret { sig.returns.push(AbiParam::new(types::I64)); } + + let symbol = "nyash_plugin_invoke3_i64"; + let func_id = self.module + .declare_function(symbol, Linkage::Import, &sig) + .expect("declare plugin shim failed"); + + 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); } + let fref = self.module.declare_func_in_func(func_id, fb.func); + let c_type = fb.ins().iconst(types::I64, type_id as i64); + let c_meth = fb.ins().iconst(types::I64, method_id as i64); + let c_argc = fb.ins().iconst(types::I64, argc as i64); + let call_inst = fb.ins().call(fref, &[c_type, c_meth, c_argc, arg_vals[0], arg_vals[1], arg_vals[2]]); + if has_ret { + let results = fb.inst_results(call_inst).to_vec(); + if let Some(v) = results.get(0).copied() { self.value_stack.push(v); } + } + fb.finalize(); + } + // ==== Phase 10.7 block APIs ==== fn prepare_blocks(&mut self, count: usize) { use cranelift_frontend::FunctionBuilder; diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 0c523631..c8324110 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -656,15 +656,31 @@ impl LowerCore { } } } - // Map (RO): size/get/has - "size" | "get" | "has" => { + // Map: size/get/has (RO) and set (mutating; allowed only when policy.read_only=false) + "size" | "get" | "has" | "set" => { if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() { if let Ok(h) = ph.resolve_method("MapBox", method.as_str()) { + if method.as_str() == "set" && crate::jit::policy::current().read_only { + // Deny mutating under read-only policy + crate::jit::events::emit_lower( + serde_json::json!({ + "id": format!("plugin:{}:{}", "MapBox", "set"), + "decision":"fallback","reason":"policy_denied_mutating" + }), + "plugin","" + ); + // Do not emit plugin call; VM path will handle + return Ok(()); + } if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } let mut argc = 1usize; if matches!(method.as_str(), "get" | "has") { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } argc += 1; + } else if method.as_str() == "set" { + if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); } + if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } + argc += 2; } b.emit_plugin_invoke(h.type_id, h.method_id, argc, dst.is_some()); crate::jit::events::emit_lower( diff --git a/src/runtime/plugin_loader_v2.rs b/src/runtime/plugin_loader_v2.rs index 2e472844..e6a564ad 100644 --- a/src/runtime/plugin_loader_v2.rs +++ b/src/runtime/plugin_loader_v2.rs @@ -615,6 +615,17 @@ impl PluginBoxV2 { })? }; + // Optional ABI version check (v0: expect 1) + if let Ok(sym) = unsafe { lib.get:: u32>(b"nyash_plugin_abi") } { + let ver = unsafe { (*sym)() }; + if ver != 1 { + eprintln!("[PluginLoaderV2] nyash_plugin_abi version mismatch: {} (expected 1) for {}", ver, lib_name); + return Err(BidError::PluginError); + } + } else { + eprintln!("[PluginLoaderV2] nyash_plugin_abi not found for {} (assuming v0=1)", lib_name); + } + // Get required invoke function and dereference it let invoke_fn = unsafe { let symbol: libloading::Symbol i32> =