diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md new file mode 100644 index 00000000..5a5005d9 --- /dev/null +++ b/CURRENT_TASK.md @@ -0,0 +1,44 @@ +# CURRENT TASK (Phase 10.5c) + +目的: Handle-First + by-name を軸に、Python統合(PyRuntimeBox/PyObjectBox)を汎用・安全に実装する。最適化は後段。 + +ステータス(2025-08-30 更新) + +- フェーズ: 10.5c 汎用Handle/TLV実装の拡張(Python統合開始) +- 方針: 「綺麗に作って動かす」= ハードコーディング排除・Handle/TLV統一・最適化は後回し + +10.5b 完了項目(橋渡し済み) + +- by-name シム(getattr/call)を実装(JIT/AOT)し、Lowerer から a0 を `nyash.handle.of` で確実にハンドル化して呼び出し +- 引数 a1/a2 はハンドル優先/なければレガシー参照から TLV 構築(String/Integer はプリミティブ化) +- 汎用 birth シムを追加 + - `nyash.box.birth_h(type_id:i64)->i64`(JIT/AOT) + - `nyash.box.birth_i64(type_id:i64, argc:i64, a1:i64, a2:i64)->i64`(JIT/AOT) + - Lowerer: NewBox(引数無し)は birth_h に統一。引数ありは安全なケース(Integer const/引数が既にハンドル)だけ birth_i64 に段階導入 +- AOT: examples/aot_py_math_sqrt_min.nyash で Strict でも .o 生成を確認(target/aot_objects/main.o) +- ログ + - AOT: NYASH_CLI_VERBOSE=1 で birth_h の可視化 + - JIT: events で by-name/birth の観測(必要十分の最小限) + +10.5c 着手項目(進行中) +- Lowerer: PluginInvoke(type_id/method_id & by-name)の Handle-First 配線を統一(a0を常にnyash.handle.of) +- JIT/AOT: birth(_h/_i64)と by-name シムでTLV生成を汎用化(String/Integerはプリミティブ化、他はHandle) +- Strict時のJIT実行停止(コンパイル専用)でVM=仕様の原則を徹底 + +非対応(後回し・最適化) + +- StringBox 専用の known_string/再利用最適化 +- 汎用的な定数プール/birth の可変長 TLV 一括最適化 + +次の作業(10.5c 続き) + +1) FFI仕様の短文化(a0/a1/a2=Handle優先→TLV、レガシー抑止フラグ、戻りTLVのdecodeポリシー) +2) birth引数の一般化メモ(可変長TLV、例外時ハンドリング) +3) Python統合の最小チェーン(import→getattr→call)のAOT/VM双方での実装確認サンプル追加 +4) ドキュメント更新(10.5c README/INDEX、FFIガイド) + +合意済みルール + +- まず汎用・安全に動かす(最適化は内部に隠し、後段) +- StringBox 等の個別特化は入れない。Handle/TLV で統一し、Box 追加を阻害しない +- Strict/Fail‑Fast を維持(fallback で隠さない) diff --git a/app b/app deleted file mode 100644 index 242d1032..00000000 Binary files a/app and /dev/null differ diff --git a/crates/nyrt/src/lib.rs b/crates/nyrt/src/lib.rs index 4160f6dd..16c990e6 100644 --- a/crates/nyrt/src/lib.rs +++ b/crates/nyrt/src/lib.rs @@ -11,10 +11,18 @@ pub extern "C" fn nyash_plugin_invoke3_i64( a2: i64, ) -> i64 { use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2; - // Resolve receiver instance from legacy VM args (param index) + // Resolve receiver instance from handle first; fallback to legacy VM args (param index) let mut instance_id: u32 = 0; let mut invoke: Optioni32> = None; - if a0 >= 0 { + if a0 > 0 { + if let Some(obj) = nyash_rust::jit::rt::handles::get(a0 as u64) { + if let Some(p) = obj.as_any().downcast_ref::() { + instance_id = p.instance_id(); + invoke = Some(p.inner.invoke_fn); + } + } + } + if invoke.is_none() && a0 >= 0 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") { nyash_rust::jit::rt::with_legacy_vm_args(|args| { let idx = a0 as usize; if let Some(nyash_rust::backend::vm::VMValue::BoxRef(b)) = args.get(idx) { @@ -39,18 +47,23 @@ pub extern "C" fn nyash_plugin_invoke3_i64( VMValue::Float(f) => nyash_rust::runtime::plugin_ffi_common::encode::f64(&mut buf, *f), VMValue::Bool(b) => nyash_rust::runtime::plugin_ffi_common::encode::bool(&mut buf, *b), VMValue::BoxRef(b) => { + // BufferBox → TLV bytes + if let Some(bufbox) = b.as_any().downcast_ref::() { + nyash_rust::runtime::plugin_ffi_common::encode::bytes(&mut buf, &bufbox.to_vec()); + return; + } if let Some(p) = b.as_any().downcast_ref::() { // Prefer StringBox/IntegerBox primitives when possible let host = nyash_rust::runtime::get_global_plugin_host(); if let Ok(hg) = host.read() { - if p.box_type() == "StringBox" { + if p.box_type == "StringBox" { if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { if let Some(s) = sb.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; } } - } else if p.box_type() == "IntegerBox" { + } else if p.box_type == "IntegerBox" { if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { if let Some(i) = ibx.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); @@ -77,17 +90,22 @@ pub extern "C" fn nyash_plugin_invoke3_i64( // Try handle first if val > 0 { if let Some(obj) = handles::get(val as u64) { + // BufferBox handle → TLV bytes + if let Some(bufbox) = obj.as_any().downcast_ref::() { + nyash_rust::runtime::plugin_ffi_common::encode::bytes(&mut buf, &bufbox.to_vec()); + appended = true; return; + } if let Some(p) = obj.as_any().downcast_ref::() { let host = nyash_rust::runtime::get_global_plugin_host(); if let Ok(hg) = host.read() { - if p.box_type() == "StringBox" { + if p.box_type == "StringBox" { if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { if let Some(s) = sb.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); appended = true; return; } } - } else if p.box_type() == "IntegerBox" { + } else if p.box_type == "IntegerBox" { if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { if let Some(i) = ibx.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); appended = true; @@ -111,6 +129,10 @@ pub extern "C" fn nyash_plugin_invoke3_i64( }; if nargs >= 1 { encode_arg(a1, 1); } if nargs >= 2 { encode_arg(a2, 2); } + // Extra args from legacy VM args (positions 3..nargs) + if nargs > 2 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") { + for pos in 3..=nargs { encode_from_legacy(pos); } + } // Prepare output buffer (dynamic growth on short buffer) let mut cap: usize = 256; let (mut tag_ret, mut sz_ret, mut payload_ret): (u8, usize, Vec) = (0, 0, Vec::new()); @@ -136,6 +158,25 @@ pub extern "C" fn nyash_plugin_invoke3_i64( if let Some(v) = nyash_rust::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); } } + 8 => { // Handle(tag=8) -> register and return handle id (i64) + if sz == 8 { + let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]); + let mut i = [0u8;4]; i.copy_from_slice(&payload[4..8]); + let r_type = u32::from_le_bytes(t); + let r_inst = u32::from_le_bytes(i); + // Build PluginBoxV2 and register into handle-registry + let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host() + .read() + .ok() + .and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())) + .and_then(|m| m.into_iter().find(|(_k,v)| *v == r_type).map(|(k,_v)| k)) + .unwrap_or_else(|| "PluginBox".to_string()); + let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap()); + let arc: std::sync::Arc = std::sync::Arc::new(pb); + let h = nyash_rust::jit::rt::handles::to_handle(arc); + return h as i64; + } + } 1 => { // Bool return if nyash_rust::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; } @@ -217,17 +258,21 @@ pub extern "C" fn nyash_plugin_invoke3_f64( VMValue::Float(f) => nyash_rust::runtime::plugin_ffi_common::encode::f64(&mut buf, *f), VMValue::Bool(b) => nyash_rust::runtime::plugin_ffi_common::encode::bool(&mut buf, *b), VMValue::BoxRef(b) => { + if let Some(bufbox) = b.as_any().downcast_ref::() { + nyash_rust::runtime::plugin_ffi_common::encode::bytes(&mut buf, &bufbox.to_vec()); + return; + } if let Some(p) = b.as_any().downcast_ref::() { let host = nyash_rust::runtime::get_global_plugin_host(); if let Ok(hg) = host.read() { - if p.box_type() == "StringBox" { + if p.box_type == "StringBox" { if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { if let Some(s) = sb.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; } } - } else if p.box_type() == "IntegerBox" { + } else if p.box_type == "IntegerBox" { if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { if let Some(i) = ibx.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); @@ -251,17 +296,21 @@ pub extern "C" fn nyash_plugin_invoke3_f64( let mut appended = false; if val > 0 { if let Some(obj) = handles::get(val as u64) { + if let Some(bufbox) = obj.as_any().downcast_ref::() { + nyash_rust::runtime::plugin_ffi_common::encode::bytes(&mut buf, &bufbox.to_vec()); + appended = true; return; + } if let Some(p) = obj.as_any().downcast_ref::() { let host = nyash_rust::runtime::get_global_plugin_host(); if let Ok(hg) = host.read() { - if p.box_type() == "StringBox" { + if p.box_type == "StringBox" { if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { if let Some(s) = sb.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); appended = true; return; } } - } else if p.box_type() == "IntegerBox" { + } else if p.box_type == "IntegerBox" { if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { if let Some(i) = ibx.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); appended = true; @@ -282,6 +331,9 @@ pub extern "C" fn nyash_plugin_invoke3_f64( }; if nargs >= 1 { encode_arg(a1, 1); } if nargs >= 2 { encode_arg(a2, 2); } + if nargs > 2 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") { + for pos in 3..=nargs { encode_from_legacy(pos); } + } // Prepare output buffer (dynamic growth on short buffer) let mut cap: usize = 256; let (mut tag_ret, mut sz_ret, mut payload_ret): (u8, usize, Vec) = (0, 0, Vec::new()); @@ -386,6 +438,9 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64 V::Float(f) => nyash_rust::runtime::plugin_ffi_common::encode::f64(&mut buf, *f), V::Bool(b) => nyash_rust::runtime::plugin_ffi_common::encode::bool(&mut buf, *b), V::BoxRef(b) => { + if let Some(bufbox) = b.as_any().downcast_ref::() { + nyash_rust::runtime::plugin_ffi_common::encode::bytes(&mut buf, &bufbox.to_vec()); return; + } if let Some(p) = b.as_any().downcast_ref::() { let host = nyash_rust::runtime::get_global_plugin_host(); if let Ok(hg) = host.read() { @@ -416,6 +471,7 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64 }; if argc >= 2 { add_from_legacy(1); } if argc >= 3 { add_from_legacy(2); } + if argc > 3 { for pos in 3..(argc as usize) { add_from_legacy(pos); } } let mut out = vec![0u8; 4096]; let mut out_len: usize = out.len(); let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) }; if rc != 0 { return 0; } @@ -434,6 +490,168 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64 // ---- Handle-based birth shims for AOT/JIT object linkage ---- // These resolve symbols like "nyash.string.birth_h" referenced by ObjectModule. +// Generic birth by type_id -> handle (no args). Exported as nyash.box.birth_h +#[export_name = "nyash.box.birth_h"] +pub extern "C" fn nyash_box_birth_h_export(type_id: i64) -> i64 { + if type_id <= 0 { return 0; } + let tid = type_id as u32; + // Map type_id back to type name + let name_opt = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host() + .read() + .ok() + .and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())) + .and_then(|m| m.into_iter().find(|(_k,v)| *v == tid).map(|(k,_v)| k)); + if let Some(box_type) = name_opt { + if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() { + if let Ok(b) = host_g.create_box(&box_type, &[]) { + let arc: std::sync::Arc = std::sync::Arc::from(b); + let h = nyash_rust::jit::rt::handles::to_handle(arc); + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("nyrt: birth_h {} (type_id={}) -> handle={}", box_type, tid, h); + } + return h as i64; + } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("nyrt: birth_h {} (type_id={}) FAILED: create_box", box_type, tid); + } + } + } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("nyrt: birth_h (type_id={}) FAILED: type map not found", tid); + } + 0 +} +// Generic birth with args: (type_id, argc, a1, a2) -> handle +// Export name: nyash.box.birth_i64 +#[export_name = "nyash.box.birth_i64"] +pub extern "C" fn nyash_box_birth_i64_export(type_id: i64, argc: i64, a1: i64, a2: i64) -> i64 { + use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2; + if type_id <= 0 { return 0; } + let mut invoke: Optioni32> = None; + // Resolve invoke_fn via temporary instance + let box_type_name = nyash_rust::runtime::plugin_loader_unified::get_global_plugin_host() + .read().ok() + .and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())) + .and_then(|m| m.into_iter().find(|(_k,v)| *v == (type_id as u32)).map(|(k,_v)| k)) + .unwrap_or_else(|| "PluginBox".to_string()); + if let Ok(host_g) = nyash_rust::runtime::get_global_plugin_host().read() { + if let Ok(b) = host_g.create_box(&box_type_name, &[]) { + if let Some(p) = b.as_any().downcast_ref::() { invoke = Some(p.inner.invoke_fn); } + } + } + if invoke.is_none() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("nyrt: birth_i64 (type_id={}) FAILED: no invoke", type_id); } + return 0; + } + let method_id: u32 = 0; // birth + let instance_id: u32 = 0; // static + // Build TLV args + use nyash_rust::jit::rt::handles; + let nargs = argc.max(0) as usize; + let mut buf = nyash_rust::runtime::plugin_ffi_common::encode_tlv_header(nargs as u16); + let mut encode_handle = |h: i64| { + if h > 0 { + if let Some(obj) = handles::get(h as u64) { + if let Some(p) = obj.as_any().downcast_ref::() { + let host = nyash_rust::runtime::get_global_plugin_host(); + if let Ok(hg) = host.read() { + if p.box_type == "StringBox" { + if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { + if let Some(s) = sb.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; } + } + } else if p.box_type == "IntegerBox" { + if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { + if let Some(i) = ibx.as_any().downcast_ref::() { nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; } + } + } + } + nyash_rust::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id()); + return; + } + } + } + nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, h); + }; + if nargs >= 1 { encode_handle(a1); } + if nargs >= 2 { encode_handle(a2); } + // Extra birth args from legacy VM when present + if nargs > 2 && std::env::var("NYASH_JIT_ARGS_HANDLE_ONLY").ok().as_deref() != Some("1") { + for pos in 3..=nargs { + nyash_rust::jit::rt::with_legacy_vm_args(|args| { + if let Some(v) = args.get(pos) { + use nyash_rust::backend::vm::VMValue as V; + match v { + V::String(s) => nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, s), + V::Integer(i) => nyash_rust::runtime::plugin_ffi_common::encode::i64(&mut buf, *i), + V::Float(f) => nyash_rust::runtime::plugin_ffi_common::encode::f64(&mut buf, *f), + V::Bool(b) => nyash_rust::runtime::plugin_ffi_common::encode::bool(&mut buf, *b), + V::BoxRef(bx) => { + if let Some(pb) = bx.as_any().downcast_ref::() { + if let Some(bufbox) = bx.as_any().downcast_ref::() { + nyash_rust::runtime::plugin_ffi_common::encode::bytes(&mut buf, &bufbox.to_vec()); + } else { + nyash_rust::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, pb.inner.type_id, pb.instance_id()); + } + } else { + let s = bx.to_string_box().value; nyash_rust::runtime::plugin_ffi_common::encode::string(&mut buf, &s) + } + } + _ => {} + } + } + }); + } + } + let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len(); + let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) }; + if rc != 0 { if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("nyrt: birth_i64 (type_id={}) FAILED: invoke rc={}", type_id, rc); } return 0; } + if let Some((tag, _sz, payload)) = nyash_rust::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + if tag == 8 && payload.len() == 8 { + let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); + let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); + let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); + let pb = nyash_rust::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap()); + let arc: std::sync::Arc = std::sync::Arc::new(pb); + let h = nyash_rust::jit::rt::handles::to_handle(arc); + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("nyrt: birth_i64 {} (type_id={}) argc={} -> handle={}", box_type_name, type_id, nargs, h); + } + return h as i64; + } + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("nyrt: birth_i64 (type_id={}) FAILED: decode", type_id); } + 0 +} + +// Convert a VM argument (param index or existing handle) into a runtime handle +// Exported as: nyash.handle.of +#[export_name = "nyash.handle.of"] +pub extern "C" fn nyash_handle_of_export(v: i64) -> i64 { + use nyash_rust::jit::rt::{handles, with_legacy_vm_args}; + use nyash_rust::box_trait::NyashBox; + use nyash_rust::runtime::plugin_loader_v2::PluginBoxV2; + // If already a positive handle, pass through + if v > 0 { + return v; + } + // Otherwise treat as legacy param index and box-ref → handleize + if v >= 0 { + let idx = v as usize; + let mut out: i64 = 0; + with_legacy_vm_args(|args| { + if let Some(nyash_rust::backend::vm::VMValue::BoxRef(b)) = args.get(idx) { + // If it's a PluginBoxV2 or any NyashBox, register into handle registry + // Note: store as NyashBox for uniform access + let arc: std::sync::Arc = std::sync::Arc::from(b.clone()); + out = handles::to_handle(arc) as i64; + } else if let Some(nyash_rust::backend::vm::VMValue::BoxRef(b)) = args.get(idx) { + let arc: std::sync::Arc = std::sync::Arc::from(b.clone()); + out = handles::to_handle(arc) as i64; + } + }); + return out; + } + 0 +} + #[export_name = "nyash.string.birth_h"] pub extern "C" fn nyash_string_birth_h_export() -> i64 { // Create a new StringBox via unified plugin host; return runtime handle as i64 @@ -476,6 +694,42 @@ pub extern "C" fn nyash_console_birth_h_export() -> i64 { pub extern "C" fn main() -> i32 { // Initialize plugin host: prefer nyash.toml next to the executable; fallback to CWD let exe_dir = std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.to_path_buf())); + + // Windows: assist DLL/plugin discovery by extending PATH and normalizing PYTHONHOME + #[cfg(target_os = "windows")] + if let Some(dir) = &exe_dir { + use std::path::PathBuf; + // Extend PATH with exe_dir and exe_dir\plugins if not already present + let mut path_val = std::env::var("PATH").unwrap_or_default(); + let add_path = |pv: &mut String, p: &PathBuf| { + let ps = p.display().to_string(); + if !pv.split(';').any(|seg| seg.eq_ignore_ascii_case(&ps)) { + if !pv.is_empty() { pv.push(';'); } + pv.push_str(&ps); + } + }; + add_path(&mut path_val, dir); + let plug = dir.join("plugins"); if plug.is_dir() { add_path(&mut path_val, &plug); } + std::env::set_var("PATH", &path_val); + + // Normalize PYTHONHOME: if unset, point to exe_dir\python when present. + match std::env::var("PYTHONHOME") { + Ok(v) => { + // If relative, make absolute under exe_dir + let pb = PathBuf::from(&v); + if pb.is_relative() { + let abs = dir.join(pb); + std::env::set_var("PYTHONHOME", abs.display().to_string()); + } + } + Err(_) => { + let cand = dir.join("python"); + if cand.is_dir() { + std::env::set_var("PYTHONHOME", cand.display().to_string()); + } + } + } + } let mut inited = false; if let Some(dir) = &exe_dir { let candidate = dir.join("nyash.toml"); diff --git a/docs/VM_README.md b/docs/VM_README.md index a5f8ec4e..75f1670b 100644 --- a/docs/VM_README.md +++ b/docs/VM_README.md @@ -3,12 +3,11 @@ - プラグインBox引数の最小対応を追加(TLV: BoxRef) - TLVタグ: 1=Bool, 2=I32, 3=I64, 4=F32, 5=F64, 6=String, 7=Bytes, 8=Handle(BoxRef) - BoxRefはプラグインBox参照(type_id:u32, instance_id:u32)を8バイトでエンコード - - ユーザー定義Box・複雑なビルトインは当面非対応(toStringフォールバック) + - ユーザー定義/複雑なBoxは当面一部非対応(toStringフォールバック)。標準Boxはプラグイン経由で統一 -現状のルーティング: +現状のルーティング(Plugin-First): - User-defined: MIR関数({Box}.{method}/{N}) にCall化(関数存在時)。それ以外はBoxCall。 -- Builtin: BoxCall → VM内の簡易ディスパッチ。 -- Plugin: BoxCall → PluginLoaderV2.invoke_instance_method。 +- Plugin: BoxCall → PluginInvoke(method_idが解決可能)→ それ以外は名前解決で PluginHost.invoke_instance_method。 今後のタスク: - VM側のfrom Parent.method対応(Builder/VM両対応) @@ -74,7 +73,7 @@ NYASH_VM_PIC_DEBUG=1 # PICヒットのしきい値通過時にログ ``` 今後の拡張: -- 一般`method_id`(ユーザー/ビルトイン/プラグイン)に対するvtableスロット→thunk直呼び。 +- 一般`method_id`(ユーザー/プラグイン)に対するvtableスロット→thunk直呼び。 - PICのキャッシュ無効化(型version)と多相PICへの拡張(Phase 10)。 - SocketBox(VM) - 基本API: `bind/listen/accept/connect/read/write/close/isServer/isConnected` diff --git a/docs/development/current/CURRENT_TASK.md b/docs/development/current/CURRENT_TASK.md index 47be177e..c79c5b0d 100644 --- a/docs/development/current/CURRENT_TASK.md +++ b/docs/development/current/CURRENT_TASK.md @@ -80,6 +80,55 @@ 6) ドキュメント/サンプル更新 - Handle-First のガイドと最小AOT手順の追記。 +### 10.5c ドキュメント/サンプル 追加(本スナップショット) +- FFI最小仕様(a0/a1/a2, 戻りTLV)を短文化: `docs/reference/abi/ffi_calling_convention_min.md` +- birth引数の一般化メモ(可変長TLV/例外伝搬): `docs/ideas/new-features/2025-08-30-birth-args-tlv-generalization.md` +- Python最小チェーンの追加: + - VM: `examples/py_min_chain_vm.nyash` + - AOT: `examples/aot_py_min_chain.nyash` + +### 10.5d AOT統合 仕上げ(今回) +- ガイド追加: `docs/guides/build/aot_quickstart.md`(CLI/スクリプト/内部フロー/FAQ) +- by-nameシム整理: nyrtの `nyash_plugin_invoke_name_{getattr,call}_i64` をFFI要約へ反映 +- スモーク更新: `tools/smoke_aot_vs_vm.sh` に Python最小チェーンを追加(VM側は `NYASH_PY_AUTODECODE=1`) +- 今後: nyrtシムのTLV拡充(bytes/N引数)、Windowsのプラグイン探索微調整 + +### 10.5e 小仕上げ(今回) +- nyrtシム TLV拡充: BufferBox→bytes(tag=7) 自動エンコード、3引数目以降はレガシー引数からTLVに詰める暫定N引数対応 +- Windows探索調整: EXE起動時に PATHへexe/`plugins/`を追加、`PYTHONHOME` 未設定時は `exe\python` を自動採用(存在時)。相対PYTHONHOMEはexe基準に正規化 + +### 次フェーズ: 10.6(Thread-Safety / Scheduler) +- 計画: docs/development/roadmap/phases/phase-10.6/PLAN.txt(新規) +- 10.6a 監査: Array/Map/Buffer/NyashRuntime/Scheduler の共有戦略(Arc+RwLock/Mutex)を確認し、未整備箇所をTODO化 +- 10.6b スケジューラ: SingleThreadScheduler を Safepoint で `poll()` 連携(観測: `NYASH_SCHED_DEMO/TRACE/POLL_BUDGET`) +- 10.6c 並列GC設計: per-thread roots / safepoint協調 / カードマーキングの段階導入メモ確定 + +### 橋渡し: 10.7 Python Native(トランスパイル / All-or-Nothing) +- 方針と計画: docs/development/roadmap/phases/phase-10.7/PLAN.txt(新規) +- 二本立て明確化: 実行系(現行PyRuntimeBox)と トランスパイル系(Python→Nyash→MIR→AOT)を併走。自動フォールバック無し +- サブフェーズ: C1 Parser(1週)→ C2 Compiler Core(2週)→ C3 CLI配線(3日)→ C4 テスト(並行) +- 既存導線の活用: 生成Nyashは既存 `--compile-native` でAOT化(Strict) + +### Nyash-only パイプライン(作業場 / 最小導線) +- 目的: すべてNyashで書き、即実行・即修正できる足場を先に用意 +- 追加ファイル: tools/pyc/ + - PythonParserNy.nyash(PyRuntimeBox経由で ast.parse/dump。NYASH_PY_CODE を参照) + - PyIR.nyash(IR最小ヘルパ)/ PyCompiler.nyash(Nyash側コンパイラ骨組み)/ pyc.nyash(エントリ) +- Parser/Compiler Rustプラグインは雛形として併存(将来削減)。当面はNyash実装を優先 + +### 次の順番(小粒で進める) +1) Parser JSON→IR 変換の最小実装(def/return)。tools/pyc/PyCompiler.nyash に追加(env NYASH_PY_CODE を Pythonで解析→IR生成) +2) IR→Nyash 生成の最小拡張(Return定数→Return文字列/数値に対応、If/Assignは後続) +3) All-or-NothingのStrictスイッチ(unsupported_nodes 非空ならErr)。開閉はenvで制御 +4) CLI隠しフラグ `--pyc/--pyc-native` を追加し、Parser→Compiler→AOT を一本化(内部で現行AOTを使用) +5) サンプル/回帰: tools/pyc の最小ケースをVM/AOTで回し、差分を記録 + +### Python AOTサンプルの追加(最小) +- `examples/aot_py_min_chain.nyash`(import→getattr→call) +- `examples/aot_py_result_ok.nyash`(returns_result: Ok) +- `examples/aot_py_result_err.nyash`(returns_result: Err) +- kwargs暫定ブリッジ(env eval + **dict): `examples/aot_py_eval_kwargs_env.nyash` + ## 🔧 実行方法(再起動手順) ```bash cargo build --release --features cranelift-jit diff --git a/docs/development/roadmap/phases/00_MASTER_ROADMAP.md b/docs/development/roadmap/phases/00_MASTER_ROADMAP.md index 1f6bc09c..2630eff4 100644 --- a/docs/development/roadmap/phases/00_MASTER_ROADMAP.md +++ b/docs/development/roadmap/phases/00_MASTER_ROADMAP.md @@ -6,9 +6,9 @@ Purpose: Claude×Copilot×ChatGPT協調開発の総合ロードマップ ## 📍 現在位置 -- **現在フェーズ**: Phase 8.6 VM性能改善(進行中) -- **次フェーズ**: Phase 9 JIT実装 -- **詳細タスク**: [CURRENT_TASK.md](../../current/CURRENT_TASK.md) +- 現在フェーズ: Phase 10.5b ネイティブビルド基盤の固め(MIR→VM→AOTの足固め) +- 次フェーズ: Phase 10.5c Handle-First PluginInvoke(Python統合の実装着手) +- 備考: 旧10.1系(10.1c/d)は「PythonをNyashで動かすフェーズ」の設計資料(Archived)。順番を入れ替え、先にネイティブビルド基盤を完成させています。 ## 🗺️ フェーズ概要 @@ -219,4 +219,4 @@ nyash bid gen --target llvm bid.yaml # AOT用declare生成(LLVM実装時) - 🔄 **Phase 8.6 VM性能改善を最優先** (進行中) - 📦 **Phase 9.8 BIDレジストリ** (Phase 8.6完了後の次期重点) - 🔍 **Phase 10 Cranelift JIT** (主経路として確定) -- 🌟 **統一ロードマップ化** (phasesフォルダに集約) \ No newline at end of file +- 🌟 **統一ロードマップ化** (phasesフォルダに集約) diff --git a/docs/development/roadmap/phases/phase-10.5/10.1c_parser_integration/README.md b/docs/development/roadmap/phases/phase-10.5/10.1c_parser_integration/README.md index 581fc875..7f0c17bd 100644 --- a/docs/development/roadmap/phases/phase-10.5/10.1c_parser_integration/README.md +++ b/docs/development/roadmap/phases/phase-10.5/10.1c_parser_integration/README.md @@ -1,5 +1,7 @@ [Archived] 旧10.1系ドキュメントです。最新は ../INDEX.md を参照してください。 +Note: 本来は「PythonをNyashで動かすフェーズ(パーサー統合)」の位置づけでしたが、現在は順番を変更し、先に 10.5b(MIR→VM→ネイティブビルド基盤)を進めています。 + # Phase 10.1c - パーサー統合実装 ## 🎯 このフェーズの目的 @@ -60,4 +62,4 @@ print(json_ast) // JSON ASTが表示される ``` ## ⏭️ 次のフェーズ -→ Phase 10.1d (Core実装) \ No newline at end of file +→ Phase 10.1d (Core実装) diff --git a/docs/development/roadmap/phases/phase-10.5/10.1d_core_implementation/README.md b/docs/development/roadmap/phases/phase-10.5/10.1d_core_implementation/README.md index e4e26491..804fe3c2 100644 --- a/docs/development/roadmap/phases/phase-10.5/10.1d_core_implementation/README.md +++ b/docs/development/roadmap/phases/phase-10.5/10.1d_core_implementation/README.md @@ -1,5 +1,7 @@ [Archived] 旧10.1系ドキュメントです。最新は ../INDEX.md を参照してください。 +Note: 本来は「PythonをNyashで動かすフェーズ(Core実装)」の位置づけでしたが、現在は順番を変更し、先に 10.5b(MIR→VM→ネイティブビルド基盤)を進めています。 + # Phase 10.1d - Core実装(Phase 1機能) ## 🎯 このフェーズの目的 @@ -71,4 +73,4 @@ def append_to_list(item, lst=[]): # 定義時に評価! ``` ## ⏭️ 次のフェーズ -→ Phase 10.1e (トランスパイラー) \ No newline at end of file +→ Phase 10.1e (トランスパイラー) diff --git a/docs/development/roadmap/phases/phase-10.5/INDEX.md b/docs/development/roadmap/phases/phase-10.5/INDEX.md index e4657a38..f9d264a9 100644 --- a/docs/development/roadmap/phases/phase-10.5/INDEX.md +++ b/docs/development/roadmap/phases/phase-10.5/INDEX.md @@ -6,15 +6,16 @@ - 10.5 README(全体像): ./README.md - 10.5a – Python ABI 設計: ./10.5a-ABI-DESIGN.md - 10.5b – ネイティブビルド基盤: ./10.5b-native-build-consolidation.md -- 10.5c – PyRuntimeBox / PyObjectBox 実装(予定) + - 現在フォーカス: MIR→VM→ネイティブビルド(AOT/EXE)を先に堅牢化 +- 10.5c – Handle-First PluginInvoke / PyRuntimeBox / PyObjectBox(次段) - 10.5d – JIT/AOT 統合(予定) - 10.5e – サンプル / テスト / ドキュメント(予定) ## Archived(旧10.1系・参照用) - chatgpt5 統合計画(旧称 Phase 10.1): ./chatgpt5_integrated_plan.md - 10.1a_planning ~ 10.1g_documentation 各READMEと資料 + - 10.1c / 10.1d は「PythonをNyashで動かすフェーズ」の設計・実装メモです(順番変更により後段へ)。 整理方針: - Active ドキュメントに計画と用語を集約。旧10.1系は背景情報として参照のみ。 - 実装の優先は「必要最小の箱(PyRuntimeBox / PyObjectBox)」→ 後から最適化。 - diff --git a/docs/development/roadmap/phases/phase-10.5/README.md b/docs/development/roadmap/phases/phase-10.5/README.md index 7518dd02..49a0c9c7 100644 --- a/docs/development/roadmap/phases/phase-10.5/README.md +++ b/docs/development/roadmap/phases/phase-10.5/README.md @@ -56,6 +56,12 @@ - 参照管理: `Py_INCREF`/`Py_DECREF` をBoxライフサイクル(fini)に接続 - プラグイン化: `nyash-python-plugin`(cdylib/staticlib)で `nyplug_python_invoke` を提供(将来の静的同梱に対応) +追加方針(10.5c Handle-First/TLV 統一) +- Lowerer は Handle-First を徹底(a0 は常に `nyash.handle.of(receiver)`)。 +- 引数TLVは String/Integer をプリミティブ化、その他は Handle(tag=8) に統一。 +- 受け手箱名が未確定な経路には by-name シムを導入(後方安全の回避路)。 +- 参考: `docs/reference/abi/ffi_calling_convention_min.md` + ### 10.5c 境界の双方向化(3–5日) - Nyash→Python: BoxCall→plugin_invokeでCPython C-APIに橋渡し - Python→Nyash: `nyashrt`(CPython拡張)で `nyash.call(func, args)` を提供 @@ -72,6 +78,10 @@ - テスト: GILの再入・参照カウントリーク検知・例外伝搬・多プラットフォーム - ドキュメント: 使用例、制約(GIL/スレッド)、AOT時のリンク・ランタイム要件 +追加済みサンプル(最小チェーン) +- VM: `examples/py_min_chain_vm.nyash`(import→getattr→call→println) +- AOT: `examples/aot_py_min_chain.nyash`(import→getattr→call→return) + ## 🎯 DoD(定義) - NyashからPythonコードを評価し、PyObjectをHandleで往復できる - 代表的なプロパティ取得/呼び出し(RO)がJIT/VMで動作 diff --git a/docs/development/roadmap/phases/phase-10.6/PLAN.txt b/docs/development/roadmap/phases/phase-10.6/PLAN.txt new file mode 100644 index 00000000..0b742845 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.6/PLAN.txt @@ -0,0 +1,87 @@ +# Phase 10.6 計画(整理版 / txt) + +目的: Thread-Safety(監査→仕込み)と協調スケジューラ(Safepoint連携)を最小構成で通し、次段の並列GC/並列実行に備える。 + +==================== +1) ゴール(DoD) +==================== +- 10.6a: Thread-Safety Audit の完了(一次) + - NyashBox/Runtime の共有戦略(Arc+RwLock/Mutex)を棚卸し、未整備箇所はTODO化 + - Plugin-First 前提(PluginBoxV2 はクロススレッド未サポ運用を明記) +- 10.6b: 協調スケジューラの足場 + - SingleThreadScheduler(queue + delayed + poll)を Safepoint に接続済み + - デモ/トレース(NYASH_SCHED_DEMO=1, NYASH_SCHED_TRACE=1, 予算NYASH_SCHED_POLL_BUDGET)で観測可 +- 10.6c: 並列GC 設計の下準備 + - Per-thread roots / Safepoint協調 / カードマーキングの設計メモを確定(段階導入方針) + +==================== +2) スコープ +==================== +- 実装は“最小で通す”に限定(最適化は後続)。 +- 既存のVM=仕様、JIT=AOT生成専用という原則は維持。 +- Python/Plugin 経路は Plugin-First/Handle-First/TLV 統一の上に載せる(10.5で固定済)。 + +==================== +3) サブフェーズとタスク +==================== +3.1) 10.6a Thread-Safety Audit(1–2日) +- ドキュメント: phase-10/phase_10_6a_thread_safety_audit.md(既存)に一次棚卸しを反映 +- Grepチェック: Arc/Mutex/RwLock/Send/Sync の確認と未整備箇所の列挙 +- 確認対象: + - ArrayBox/MapBox/BufferBox の共有戦略(Arc>) + - NyashRuntime メンバのSend+Sync前提(Arcで包む) + - Scheduler/GC Hooks のSend+Sync前提 + - PluginBoxV2: クロススレッド未サポ運用を明記(将来設計のTODO) + +3.2) 10.6b Scheduler Prep(1–2日) +- 仕様固定: poll() を MIR Safepoint で呼ぶ(既実装の確認) +- 観測: NYASH_SCHED_DEMO=1 / NYASH_SCHED_TRACE=1 / NYASH_SCHED_POLL_BUDGET で動作確認 +- 最小API: spawn/spawn_after/poll/yield_now(SingleThreadScheduler) +- ドキュメント: phase-10/phase_10_6b_scheduler_prep.txt 更新(Trace/観測例追加済) + +3.3) 10.6c Parallel GC Design(2–3日) +- 設計メモ: phase-10/phase_10_6c_parallel_gc_design.txt(既存)を最終化 +- 方針: + - Per-thread roots, Safepoint協調、カードマーキングの段階導入 + - デフォルトは単一スレッド継続(featureで並列ON) + - API拡張は後方互換シムを用意(barrier引数拡張など) + +==================== +4) 成果物(Artifacts) +==================== +- Docs + - 10.6a 監査メモ: phase-10/phase_10_6a_thread_safety_audit.md(一次棚卸し完了) + - 10.6b スケジューラ: phase-10/phase_10_6b_scheduler_prep.txt(Trace/デモ手順) + - 10.6c 並列GC設計: phase-10/phase_10_6c_parallel_gc_design.txt(確定版) +- Code + - src/runtime/scheduler.rs(SingleThreadScheduler / Send+Sync) + - MIR Safepoint → VM Runtime.scheduler.poll()(済) + +==================== +5) リスクと緩和 +==================== +- 共有の粒度: 競合を避けるため、Box内部は最小限のロックで運用(RwLock優先)。 +- 並列前倒しの誘惑: 10.6では並列化を“設計と足場”に限定、実実装は次フェーズ(feature) +- Plugin/FFI: クロススレッド呼出しは明示的に非対応(ドキュメントで制約明記)。 + +==================== +6) タイムライン(目安) +==================== +- 10.6a: 1–2日 +- 10.6b: 1–2日 +- 10.6c: 2–3日 + +==================== +7) 依存関係 +==================== +- 10.5 完了(AOT/nyrt/Handle-First/TLV統一/Strict運用)を前提 +- Docsの最新導線: docs/development/roadmap/phases/phase-10.5/INDEX.md + +==================== +8) 参照リンク +==================== +- phase-10/phase_10_6_thread_safe_revolution.md(設計準備) +- phase-10/phase_10_6a_thread_safety_audit.md(監査) +- phase-10/phase_10_6b_scheduler_prep.txt(スケジューラ) +- phase-10/phase_10_6c_parallel_gc_design.txt(並列GC) + diff --git a/docs/development/roadmap/phases/phase-10.6/PYTHON_NATIVE_PLAN.txt b/docs/development/roadmap/phases/phase-10.6/PYTHON_NATIVE_PLAN.txt new file mode 100644 index 00000000..1b436be6 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.6/PYTHON_NATIVE_PLAN.txt @@ -0,0 +1,79 @@ +# Phase 10.6 → Pythonネイティブ接続プラン(整理版 / txt) + +目的: 10.6で整えたThread-Safety/Schedulerの最小足場の上に、Pythonプラグイン(PyRuntimeBox/PyObjectBox)をネイティブEXE(AOT)まで安定接続する。 + +==================== +1) 到達イメージ(DoD) +==================== +- Linux/Windows で以下が安定動作: + - AOT: `examples/aot_py_min_chain.nyash` → `Result: 4`(math.sqrt(16)) + - VM: `examples/py_min_chain_vm.nyash` → 4.0 表示(NYASH_PY_AUTODECODE=1) + - returns_result 系サンプル(importR/getattrR/callR)で Ok/Err が期待通りに表示 +- AOTビルドの配布体験が明確: + - `tools/build_aot.{sh,ps1}` で .o → EXE、`nyash.toml`/plugins 解決、Windowsでは PATH/PYTHONHOME 調整 + - 配布ガイド(依存コピー/環境変数/動作確認手順)が `docs/guides/build/aot_quickstart.md` に追記済み + +==================== +2) 方針(変えない原則) +==================== +- Plugin-First / Handle-First / TLV 統一(String/Integerはプリミティブ、Bufferはbytes(tag=7)、他はHandle(tag=8))。 +- Strict: 実行はVM、JITはAOT生成専用(フォールバック禁止)。 +- by-name経路: 受け手箱名未確定時は `nyash_plugin_invoke_name_{getattr,call}_i64` で実行時解決。 + +==================== +3) 実装タスク(M1→M5) +==================== +M1: AOT最小ルートの安定(完了確認/微修正) +- nyrt シム:BufferBox→bytes(tag=7) 対応、3引数目以降をレガシーVM引数からTLV追補(暫定N引数) +- Windows探索:EXE起動時に PATH へ exe/`plugins/` を追加、`PYTHONHOME` が未設定なら `exe\python` を採用(相対はexe基準に正規化) +- ドキュメント:AOTクイックスタートにWindows注意点を追記(済) + +M2: TLV/FFIカバレッジ拡大(最小) +- kwargs/辞書のパス(callKwR)をbytes(tag=7) or string(tag=6, JSON)で暫定対応(将来 BID-1拡張) +- N引数の一般化(invoke3→invokeN 設計、実装は段階導入。先行はレガシー補完で可) +- returns_result の統一処理(VM既存→AOTでも同等に扱う) + +M3: Lowerer/Policy の整流 +- Handle-First統一の確認:Python特化の型伝搬は撤去済み(戻りはHandle or プリミティブのみ参照) +- birth引数の一般化(TLVメモに沿い、Integer/Stringはプリミティブ、他はHandle) +- by-name の適用範囲と導線(`getattr`/`call` はby-name固定、importはmethod_id直参照) + +M4: 配布導線とサンプル +- スクリプト整備:`tools/build_python_aot.sh` の統合(`build_aot.sh` に一本化 or ラッパー) +- サンプル最小化:returns_result(Ok/Err)、例外伝搬(Error文字列)、bytes引数、context共有(per-runtime globals) +- ガイド整理:`docs/guides/build/aot_quickstart.md` に「Pythonネイティブ」節を追加(動作要件・環境変数) + +M5: 観測/CI(軽量) +- スモーク:VMチェーン / AOTチェーンの比較(Result行)を `tools/smoke_aot_vs_vm.sh` に追加(Python系は最小のみ) +- ログ:`NYASH_JIT_EVENTS*`/`NYASH_JIT_NATIVE_F64`/`NYASH_PY_AUTODECODE` による差分観測 + +==================== +4) リスク/制約 +==================== +- CPython依存の配布:WindowsのDLL探索(PATH/PYTHONHOME)は最小整備。完全同梱(embedded)は後段で検討。 +- KW/辞書のTLV表現:暫定はbytes/stringでブリッジ。正式BIDタグは将来導入(後方互換のためJSON連携を許容)。 +- ネイティブN引数:v0はレガシーVM引数からのTLV補完でしのぎ、invokeNは次期導入。 + +==================== +5) タイムライン(目安) +==================== +- M1(安定): 0.5日(確認/微修正) +- M2(TLV拡充): 1–2日(kwargsは暫定・bytes/JSON) +- M3(Lowerer整流): 0.5–1日 +- M4(配布/サンプル): 1日 +- M5(観測/CI): 0.5日 + +==================== +6) 成果物(Artifacts) +==================== +- 例: `examples/aot_py_min_chain.nyash`, `examples/py_min_chain_vm.nyash`(既存) +- ツール: `tools/build_aot.{sh,ps1}`(Python節)、`tools/smoke_aot_vs_vm.sh`(Python最小) +- Docs: `docs/guides/build/aot_quickstart.md`(Python節)、`docs/reference/abi/ffi_calling_convention_min.md`(bytes/N引数注記) + +==================== +7) 参照 +==================== +- 10.5c: Handle-First/PluginInvoke 設計(by-name シム) +- 10.5d/e: AOT統合/最終仕上げ(nyrt/Windows探索/TLV拡張) +- 10.6: Thread-Safety/Scheduler(並列化前の足場) + diff --git a/docs/development/roadmap/phases/phase-10.7/PLAN.txt b/docs/development/roadmap/phases/phase-10.7/PLAN.txt new file mode 100644 index 00000000..ad7135c8 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/PLAN.txt @@ -0,0 +1,67 @@ +# Phase 10.7 – Python Native 再スタート計画(合意版 / txt) + +目的: 現行の Plugin-First(PyRuntimeBox/PyObjectBox, Handle-First/TLV)を維持しつつ、トランスパイル路線(Python→Nyash)を“All or Nothing”原則で段階導入。10.6の足場(Thread-Safety/Scheduler)上で、AOT配布体験に直結する最短ラインを構築する。 + +==================== +A) 方針(判断) +==================== +- 二本立てを明確化: + 1) 実行系(現行): PyRuntimeBox 経由(VM=仕様、JIT=AOT生成のみ)。配布/運用の実用ライン。 + 2) トランスパイル系(10.7): Python→Nyash→MIR→AOT。コンパイル成功/失敗の二択(フォールバック自動無し)。 +- 役割分担:未対応Pythonはユーザーが明示的に PyRuntimeBox を使う。トランスパイルはコンパイル成功率を段階的に拡大。 +- Plugin-Firstは維持(Parser/CompilerもプラグインBox化)。CLI/VMから統一呼び出し。 + +==================== +B) 最小成功像(Phase 1 / DoD) +==================== +- サンプルpy(Phase 1 範囲内)を `pythonc`(仮)で Nyashスクリプトへ生成 → `--compile-native` で EXE 生成 → 実行。 +- 機能カバレッジ(Phase 1): def/if/for/while/return/bool/算術/比較/関数呼び出し/LEGB/デフォルト引数/for-else。 +- Differential(限定): Phase 1 サンプル群で CPython と一致(出力/戻り/例外の有無)。 + +==================== +C) サブフェーズとタスク +==================== +C1) Parser Plugin(1週) +- `plugins/nyash-python-parser-plugin`: Python→AST(pyo3)。 +- AST→CorePy IR(JSON): 構文の正規化(with→try/finally などはPhase 2)。 +- Telemetry: ノード統計/未対応ノードを列挙。 + +C2) Compiler Core(2週) +- IR→Nyash AST 生成(Box化/クロージャ/LEGB/デフォ引数の再現)。 +- peephole最小(定数畳み込み)。 +- 生成NyashのPretty-print + 簡易ソースマップ。 + +C3) 配線/CLI/実行(3日) +- `nyash --pyc file.py -o out.ny`(Nyash出力)/ `--pyc-native file.py -o app`(EXE直行)を追加(内部で既存 `--compile-native` を利用)。 +- 生成Nyashは既存コンパイラ経由で MIR→AOT を通す(Strict)。 + +C4) テスト/観測(1週並行) +- `phase-10.7/testing-plan.md` の Phase 1 部分を小粒に実装。 +- VM vs AOT の「Result:」比較ラインを流用(既存スモークベース)。 + +==================== +D) インターフェース / 成果物 +==================== +- ParserBox: `parse(code: String) -> AstBox/CorePyBox`(内部JSON保持 or to_json) +- CompilerBox: `compile(ir: CorePyBox) -> Result` +- CLI: `--pyc/--pyc-native`(初期は隠しフラグでも可) +- Docs: README/implementation/testing-plan を PLAN に沿って更新。 + +==================== +E) リスク/緩和 +==================== +- 範囲膨張: Phase 1 の構文/意味論を固定し、Beyondは即Err。PyRuntimeBoxは明示利用。 +- 例外/with/comp/async: Phase 2/3の対象。IR設計時に将来ノードを予約(後方互換)。 +- Windows配布: 10.5で整えた PATH/PYTHONHOME 補助はPyRuntime向け。トランスパイル後はCPython依存なし。 + +==================== +F) タイムライン(目安) +==================== +- C1: 1週 / C2: 2週 / C3: 3日 / C4: 1週(並行)。 + +==================== +G) 現行との接続 +==================== +- 10.6の足場(Thread-Safety/Scheduler)は維持。トランスパイル系は単一スレッド/VM基準で十分。 +- 10.5のAOT導線/nyrtシムはそのまま活用(生成Nyashに対して適用)。 + diff --git a/docs/development/roadmap/phases/phase-10.7/README.md b/docs/development/roadmap/phases/phase-10.7/README.md new file mode 100644 index 00000000..6f5c64f4 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/README.md @@ -0,0 +1,148 @@ +# Phase 10.7 - True Python Native via Plugin Boxes + +## 🎯 概要 + +PythonコードをNyashで**本当にネイティブ実行**する。CPythonへの依存なしに、Pythonコードが完全にNyash MIR/JIT経由で機械語として実行される。 + +### 現状 vs 理想 + +**現状(Phase 10.6)**: PyRuntimeBox → libpython呼び出し +**理想(Phase 10.7)**: Python → Nyashスクリプト → MIR → ネイティブ + +## 🏗️ アーキテクチャ:トランスパイル方式 + +``` +Python AST → CorePy IR → Nyash AST → Nyashスクリプト +``` + +### なぜトランスパイル? + +1. **透明性**: 生成コードが見える・デバッグできる・手を加えられる +2. **既存資産活用**: Nyashコンパイラの最適化を自動享受 +3. **教育的価値**: PythonとNyashの対応が学習価値を持つ +4. **段階的改善**: 生成コードの品質を徐々に向上 + +### プラグインBox群 + +- **PythonParserBox**: Python → AST変換 +- **PythonCompilerBox**: AST → Nyashスクリプト生成 +- **py_runtime.ny**: Pythonセマンティクス保持ライブラリ + +## ⚠️ All or Nothing設計(フォールバックなし) + +**コンパイルできる or できない の2択のみ** + +```nyash +compiler = new PythonCompilerBox() +result = compiler.compile(ast) + +if result.isOk() { + // 100%コンパイル成功 → ネイティブ実行 + print("Success! Native execution ready.") +} else { + // 未対応機能あり → 完全拒否 + print("Cannot compile: " + result.getError()) + print("Use PyRuntimeBox instead.") +} +``` + +理由:開発時と本番時で挙動が変わるのは最悪の設計 + +## 📋 実装フェーズ + +### Phase 10.7a - Parser Plugin(1週間) +- PythonParserBoxの実装 +- Python AST → ASTBox変換 +- テレメトリー基盤 + +### Phase 10.7b - Compiler Core(2週間) +**Phase 1機能(必須)** +- 関数定義、条件分岐、ループ +- 演算子、関数呼び出し +- Python固有:LEGB、デフォルト引数、for/else + +### Phase 10.7c - Validation & Testing(1週間) +- コンパイル可能性の事前検証 +- Differential testing(CPythonと比較) +- 明確なエラーメッセージ + +### Phase 10.7d - Coverage拡大(3-4週間) +**Phase 2**: 例外処理、with文、comprehensions +**Phase 3**: async/await、デコレータ、ジェネレータ + +## 🧪 py_runtime設計 + +```nyash +// Pythonセマンティクスを忠実に再現 +box PyRuntime { + py_truthy(x) { + // Python的真偽値判定 + if x == null or x == false { return false } + if x.hasMethod("__bool__") { return x.__bool__() } + if x.hasMethod("__len__") { return x.__len__() != 0 } + return true + } + + py_getattr(obj, name) { + // ディスクリプタプロトコル、MRO探索 + } + + py_call(f, args, kwargs) { + // デフォルト引数、*args/**kwargs処理 + } +} +``` + +## 📊 成功指標 + +### Phase 1完了時 +``` +Compilable files: 15/100 (15%) +Performance (numeric): 10x faster than CPython +Correctness: 100% (differential testing) +``` + +### 最終目標(Phase 3) +``` +Coverage: 95%+ of common patterns +Performance: 5-20x faster +Distribution: Single binary, no CPython +``` + +## 🚀 クイックスタート + +```bash +# プラグイン作成 +cd plugins/ +cargo new nyash-python-parser-plugin --lib + +# 最小実装 +[dependencies] +pyo3 = { version = "0.22", features = ["auto-initialize"] } +nyash-plugin-sdk = { path = "../../crates/plugin-sdk" } + +# テスト実行 +cargo build --release +../../target/release/nyash test_parser.nyash +``` + +## 💡 創造的可能性 + +### ハイブリッドプログラミング +```python +@nyash.vectorize # PythonデコレータがNyashのSIMD生成! +def matrix_multiply(a, b): + return a @ b +``` + +### 言語の共進化 +- Nyashが「Pythonで最も使われるイディオム」から学習 +- Pythonに「Nyash-aware」コーディングスタイル誕生 + +### 教育的インパクト +左にPython、右にリアルタイムNyash変換のPlayground + +## 📚 参考資料 + +- **archive/gemini-analysis-transpile-beauty.md** - 創造性分析 +- **archive/codex-analysis-technical-implementation.md** - 技術分析 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-10.7/archive/codex-analysis-technical-implementation.md b/docs/development/roadmap/phases/phase-10.7/archive/codex-analysis-technical-implementation.md new file mode 100644 index 00000000..81f3a592 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/archive/codex-analysis-technical-implementation.md @@ -0,0 +1,252 @@ +# Codex (GPT-5)の技術分析:Python→Nyashトランスパイルの実装詳細 + +2025-08-30 - OpenAI Codex v0.25.0による技術的深掘り + +## Big Picture + +- **Purpose**: Generate Nyash source from Python to leverage Nyash's optimizer, keep debugging simple, and "think in Nyash" early without locking into MIR details. +- **Core idea**: Normalize Python to a small CorePy IR, then lower to idiomatic Nyash with a thin "Py runtime shim" in Nyash that preserves Python's dynamic semantics where required. + +## AST Conversion + +### Two-stage lowering +``` +Python AST → CorePy IR → Nyash AST +``` + +CorePy should be expression-friendly, desugared, and semantics-explicit (e.g., with→try/finally, for→iterator loop, boolean ops→if/else). + +### Visitor + environment +Implement a node visitor that carries scope info (locals/free/globals), evaluation order, and source spans. Use Python's `symtable` to seed symbol kinds; validate against your own pass. + +### Semantic shims +Lower Python ops to Nyash intrinsics that preserve semantics: +- `py_truthy(x)` +- `py_getattr(o,"a")` +- `py_setattr(o,"a",v)` +- `py_binop("add", a, b)` +- `py_cmp("lt", a, b)` +- `py_iter(x)` +- `py_call(f, args, kwargs)` +- `py_slice(x, i, j, k)` + +### Boxes and cells +Model Python variables and closures with Box/Cell objects. Rule of thumb: locals are unboxed unless captured or aliased; promote to Box when needed. Everything-is-Box in Nyash aligns well with Python's mutability/aliasing. + +### Control flow +Normalize to a small set: `if`, `block`, `loop`, `break`, `continue`, `try/catch/finally`, `throw`. Lower `and`/`or` with short-circuit temp; turn comprehensions into explicit loops with dedicated inner scope. + +## Transpile Quality + +### Readability vs optimize +Offer modes. Default emits idiomatic Nyash constructs and meaningful identifiers, comments with source spans, and simple temporaries. "Optimize" mode switches to `py_*` intrinsics fusion and fewer temps. + +### Idiomatic Nyash +Prefer Nyash control constructs over procedural labels. Use native blocks for `if/else`, `match` if Nyash has it; only fall back to runtime calls where semantics demand. + +### Stable pretty-printer +Round-trip friendly formatter with consistent whitespace, trailing comma rules, and deterministic temp naming (`_t1`, `_t2…`). Keep def/class emitted in declaration-order. + +### Debug info +Attach `span(file, line, col)` to every IR node, carry through to Nyash AST, then emit a sidecar source map. Optionally embed lightweight `#line` directives or inline comments per statement in debug builds. + +## Python Feature Mapping + +### Default args +Evaluate at def-time; store tuple/dict on the function object. At call-time, fill missing with stored defaults. Beware mutable defaults: do not clone; reuse exact object. + +### LEGB scoping +Build symbol table with flags for `global`/`nonlocal`. Emit closure "cells" (Boxes) for free vars; functions capture a vector of cells. Globals map to the module dict; builtins fallback when name miss in global. + +### for/else, while/else +Introduce `broken=false`. On `break`, set and exit; after loop, `if !broken { else_block }`. + +### Comprehensions +Create inner function/scope per comprehension (Python 3 semantics). Assignment targets exist only in that scope. Preserve evaluation order and late binding behavior. + +### With statement +Desugar to try/finally per Python spec: evaluate context expr, call `__enter__`, bind target, run body, always call `__exit__`, and suppress exception only if `__exit__` returns truthy. + +### Decorators +Evaluate bottom-up at def-time: `fn = decoN(...(deco1(fn)))` then assign back. Keep evaluation order of decorator expressions. + +### Generators +Lower to a state machine object implementing Nyash's iterator protocol, with saved instruction pointer, stack slots, and exception injection (`throw`, `close`). Support `yield from` by delegation trampoline. + +### Pattern matching (PEP 634) +If supported by Nyash, map directly; else lower to nested guards and extractor calls in a `py_match` helper library. + +### Data model +Attribute access honors descriptors; method binding creates bound objects; arithmetic and comparisons dispatch to `__op__`/`__rop__` and rich comparisons; truthiness via `__bool__`/`__len__`. + +## Performance Opportunities + +### At transpile-time +- Constant fold literals, f-strings (format plan precomputation), simple arithmetic if types are literal. +- Invariant hoisting for loop-invariant comprehensions and attribute chains when no side-effects (guarded). +- Direct calls to Nyash intrinsics for selected builtins (`len`, `isinstance`, `range`) only if not shadowed (prove via symbol table). +- Peephole: collapse nested `py_truthy(py_truthy(x))`, merge adjacent `if` with literal conditions, drop dead temporaries. + +### Defer to Nyash compiler +- Inlining across Nyash functions, register allocation, loop unrolling, vectorization, constant propagation at MIR level. +- DCE/CSE once `py_*` helpers are inlined or annotated as pure/idempotent where legal. + +### Types as hints +- Consume Python annotations/`typing` to emit specialized fast paths: e.g., `int` → direct Nyash integer ops, else fallback to `py_binop`. Plumb types through IR as optional metadata for MIR to exploit. +- Profile-guided guards: optional mode emits guards around hot calls to enable Nyash JIT/AOT to speculate and deopt to generic `py_*`. + +## Error Handling & Debug + +### Source maps +Emit a compact mapping (e.g., VLQ JSON) from Nyash line/col → Python original; include segment mappings per expression for precise stepping. + +### Exception rewriting +Wrap Nyash runtime entrypoints to translate stack frames via the source map, hiding frames from helpers (`py_*`) unless verbose mode is on. + +### Stage diagnostics +- CorePy dump: toggle to print normalized IR with spans. +- Nyash preview: post-format preview with original Python line hints. +- Trace toggles: selective tracing of `py_call`, `py_getattr`, iteration; throttle to avoid noise. + +### Friendly messages +On unsupported nodes or ambiguous semantics, show minimal repro, Python snippet, and link to a doc page. Include symbol table excerpt when scoping fails. + +## Architecture & DX + +### Pass pipeline +``` +Parse Python AST → Symbol table → Normalize to CorePy → +Scope/closure analysis → Type/meta attach → Lower to Nyash AST → +Optimize (peephole/simplify) → Pretty-print + source map +``` + +### Runtime shim (`nyash/lib/py_runtime.ny`) +Core APIs: +- `py_call(f, pos, kw, star, dstar)` +- `py_truthy(x)` +- `py_getattr/py_setattr` +- `py_binop(op, a, b)` +- `py_cmp(op,a,b)` +- `py_iter(x)` +- `py_next(it)` +- `py_slice(x,i,j,k)` +- `py_with(mgr, body_fn)` +- `py_raise` +- `py_is` +- `py_eq` + +Data model support: descriptor get/set, method binding, MRO lookup, exception hierarchy, StopIteration protocol. + +Perf annotations: mark pure or inline candidates; keep stable ABI. + +### CLI/flags +Modes: +- `--readable` +- `--optimized` +- `--debug` +- `--emit-sourcemap` +- `--dump-corepy` +- `--strict-builtins` + +Caching: hash of Python AST + flags to cache Nyash output, source map, and diagnostics. + +Watch/incremental: re-transpile changed modules, preserve source map continuity. + +### Tests +- Golden tests: Python snippet → Nyash output diff, with normalization. +- Differential: run under CPython vs Nyash runtime for functional parity on a corpus (unit/property tests). +- Conformance: edge cases (scoping, descriptors, generators, exceptions) and evaluation order tests. + +## Pitfalls & Remedies + +### Evaluation order +Python's left-to-right arg eval, starred/unpacking, and kw conflict checks. Enforce by sequencing temps precisely before `py_call`. + +### Shadowing builtins/globals +Only specialize when proven not shadowed in any reachable scope. Provide `--strict-builtins` to disable specialization unless guaranteed. + +### Identity vs equality +`is` is reference identity; avoid folding or substituting. + +### Integer semantics +Python's bigints; ensure Nyash numeric layer matches or route to bigints in `py_*`. + +## Future Extensibility + +### Plugins +Pass manager with hooks (`before_lower`, `after_lower`, `on_node_`). Allow project-local rewrites and macros, with access to symbol/type info. + +### Custom rules +DSL for pattern→rewrite with predicates (types, purity), e.g., rewrite `dataclass` patterns to Nyash records. + +### Multi-language +Keeping the Nyash script as a stable contract invites other frontends (e.g., a subset of JS/TypeScript or Lua) to target Nyash; keep `py_*` separate from language-agnostic intrinsics to avoid contamination. + +### Gradual migration +As Nyash grows Pythonic libraries, progressively replace `py_*` with native Nyash idioms; keep a compatibility layer for mixed projects. + +## Concrete Translation Sketches + +### Attribute +```python +a.b +``` +→ +```nyash +py_getattr(a, "b") +``` + +### Call +```python +f(x, y=1, *as, **kw) +``` +→ +```nyash +py_call(f, [x], {"y":1}, as, kw) +``` + +### If +```python +if a and b: +``` +→ +```nyash +let _t=py_truthy(a); if _t { if py_truthy(b) { ... } } +``` + +### For/else +```python +for x in xs: + if cond: + break +else: + else_block +``` +→ +```nyash +let _it = py_iter(xs); +let _broken=false; +loop { + let _n = py_next(_it) catch StopIteration { break }; + x = _n; + ... + if cond { _broken=true; break } +} +if !_broken { else_block } +``` + +### With +Evaluate mgr, call `__enter__`, bind val; try body; on exception, call `__exit__(type,e,tb)` and suppress if returns true; finally call `__exit__(None,None,None)` when no exception. + +### Decorators +```nyash +let f = ; +f = decoN(...(deco1(f))); +name = f +``` + +## Why Nyash Script First + +- **Debuggability**: Human-readable Nyash is easier to inspect, diff, and map errors to; source maps stay small and precise. +- **Optimization leverage**: Nyash compiler/MIR can keep improving independently; your Python frontend benefits automatically. +- **Ecosystem fit**: Generates idiomatic Nyash that other tools (formatters, linters, analyzers) can understand; fosters a consistent DX. \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-10.7/archive/gemini-analysis-transpile-beauty.md b/docs/development/roadmap/phases/phase-10.7/archive/gemini-analysis-transpile-beauty.md new file mode 100644 index 00000000..16e0958c --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/archive/gemini-analysis-transpile-beauty.md @@ -0,0 +1,72 @@ +# Gemini先生の分析:Python→Nyashトランスパイルの「面白さ」と「可能性」 + +2025-08-30 - Geminiによる深い洞察 + +## 1. 創造的な活用方法 - 「ハイブリッドプログラミング」の新しい形 + +このアプローチの最大の面白さは、**PythonとNyashの境界を曖昧にし、両者の長所を自在に組み合わせられる「ハイブリッドプログラミング」環境**が生まれる点にあります。 + +### パフォーマンスの「目利き」チューニング +開発者はまず、書き慣れたPythonで迅速にプロトタイプを構築します。パフォーマンスが問題になる箇所だけ、トランスパイルされたNyashコードを覗き見ます。「なるほど、このリスト内包表記はこういうループになるのか。ここをNyashの並列処理機能に書き換えれば速くなりそうだ」といった具合に、**生成されたコードを"最適化のためのヒント"として活用**し、手動で高性能なNyashコードに置き換えることができます。これは、Pythonの手軽さとネイティブの速度を、開発者が主体的にコントロールできる、非常に楽しいプロセスです。 + +### Nyashを操るためのDSL(ドメイン固有言語)としてPythonを使う +Pythonの強力なメタプログラミング能力(デコレータ、AST操作など)を使い、「Nyashコードジェネレータ」をPythonで記述できます。例えば、特定の計算パターンを最適化するNyashコードを生成するPythonデコレータを作るのはどうでしょう。 + +```python +# このデコレータがNyashのSIMD命令を使うコードを生成する +@nyash.vectorize +def python_function(a, b): + return a + b +``` + +このように、Pythonの簡潔な記述から、裏では非常に複雑で高性能なNyashコードが生成される。これはまさに、**Pythonを「Nyashのための高級マクロ言語」として使う**創造的なハックです。 + +### 動的なコード生成と実行 +実行時にPythonコード文字列を生成し、それをNyashにトランスパイルして即座にネイティブ実行する、といった芸当も可能です。これにより、設定ファイルやユーザー入力に基づいて動的に高性能な処理ロジックを組み立てるような、柔軟なアプリケーションが実現できます。 + +## 2. 言語進化への影響 - 共進化するエコシステム + +この方式は、一方的な変換ではなく、両言語が互いに影響を与え合う「共進化」の触媒となります。 + +### Nyashへのフィードバック +Pythonの標準的なイディオム(例: `with`文、ジェネレータ、リスト内包表記)をNyashに変換する際、「どうもしっくりくる表現がNyashにない」「もっとエレガントに書ける構文が欲しい」という課題が必ず見つかります。これは、**Pythonという巨大なユースケースからNyashが学ぶ絶好の機会**です。このフィードバックループが、Nyashをより表現力豊かで実用的な言語へと進化させます。 + +### Pythonコミュニティへの影響 +「この書き方をすれば、Nyashで速くなる」という知見が広まると、Pythonプログラマーの中に**「Nyash-aware」なコーディングスタイル**が生まれる可能性があります。これは、Pythonのサブセットや方言のようなもので、パフォーマンスを意識した新しいPythonの書き方として定着するかもしれません。Pythonのコードが、静的解析や型ヒントだけでなく、「ネイティブ変換効率」という新しい評価軸を持つようになります。 + +## 3. 実装の優雅さ - 「AST to AST」という美しい設計 + +トランスパイラの内部実装そのものにも、開発者を楽しませるエレガントなパターンが存在します。 + +### AST(抽象構文木)レベルでの変換 +最もクリーンな実装は、Pythonの`ast`モジュールでソースコードをASTに変換し、そのASTの各ノードを再帰的に辿りながら、対応するNyashのASTノードを構築していく方法です。これは、文字列置換のような場当たり的な方法とは異なり、非常に構造的で堅牢です。Pythonの`for`ループのASTノードが、Nyashの`for`ループのASTノードに1対1で対応付けられる様は、パズルを解くような知的な面白さがあります。 + +### 変換パターンの分離と合成 +Pythonの各構文要素(関数定義、if文、クラスなど)に対応する変換ロジックを、それぞれ独立した小さな関数やクラスとして実装します。これにより、トランスパイラ全体の見通しが良くなり、テストも容易になります。新しい構文への対応も、新しい変換パターンを追加するだけで済みます。このモジュール性は、大規模なソフトウェアを設計する上での美しさそのものです。 + +## 4. 教育的インパクト - 生きた「コンパイラ入門」 + +このツールは、プログラミング学習者にとって最高の教材となり得ます。 + +### 概念の可視化 +「高級言語の裏側では、実際にはもっと低レベルな処理が行われている」というコンピュータサイエンスの基本概念を、これ以上なく明確に示せます。Pythonのたった1行が、Nyashの数行のコードに展開されるのを見ることで、抽象化のコストとメリットを直感的に理解できます。 + +### 言語間の「ロゼッタストーン」 +PythonとNyashという2つの言語の思考様式の違いを学ぶための、動的な「ロゼッタストーン」になります。「Pythonの辞書は、Nyashではハッシュマップとしてこう表現されるのか」「Pythonの動的型付けは、Nyashの型推論によってこう解決されるのか」といった発見は、学習者にとって大きな喜びとなるでしょう。 + +### インタラクティブな学習環境 +Web上で、左にPython、右にリアルタイムで変換されたNyashコードが表示されるような「Nyash Playground」を作れば、非常に人気の出る学習ツールになるはずです。 + +## 5. 長期的な展望 - 5年後、10年後の可能性 + +このアプローチは、短期的な実装の容易さだけでなく、長期的に大きな可能性を秘めています。 + +### 5年後: Pythonエコシステムのシームレスな高速化 +トランスパイラの完成度が高まり、主要なPythonライブラリ(NumPy, Pandasの一部など)をNyashに変換できるようになっているかもしれません。`pip install`するだけで、内部的にNyashへ変換・コンパイルされ、ユーザーは意識することなくPythonコードのままネイティブの速度を手に入れる、という未来が考えられます。Pythonの型ヒントが、単なる静的解析のためだけでなく、**Nyashへの最適化コンパイルのための重要なヒント**として活用されているでしょう。 + +### 10年後: 「ハイブリッド言語」としての地位確立 +PythonとNyashの関係は、TypeScriptとJavaScriptの関係に似たものになっているかもしれません。開発者は、プロジェクトの大部分をPythonで書き、パフォーマンスクリティカルな部分はNyashで書く、あるいはPythonで書いたものをトランスパイルして微調整する、という開発スタイルが当たり前になっている可能性があります。Nyashは「Pythonをネイティブ速度で動かすための最高のパートナー言語」としての地位を確立し、両言語は互いに補完し合う強力なエコシステムを形成しているでしょう。最終的には、**Pythonの書きやすさと、ネイティブコードの実行性能を両立させた、究極のスクリプト環境**が実現しているかもしれません。 + +## まとめ + +B案(トランスパイル方式)は、単に技術的に堅実なだけでなく、開発者の知的好奇心を刺激し、言語コミュニティ全体を巻き込んで成長していく「面白さ」と「可能性」に満ちた選択です。生成されたNyashコードが「ブラックボックス」ではなく「ホワイトボックス」であることが、デバッグ、最適化、学習、そして未来の創造的なハックへと繋がる鍵となります。この選択は、Nyashプロジェクトの成功に大きく貢献する戦略的な一手だと確信します。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-10.7/examples.md b/docs/development/roadmap/phases/phase-10.7/examples.md new file mode 100644 index 00000000..678c8b9f --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/examples.md @@ -0,0 +1,295 @@ +# Python Native実装例 + +## 🎯 実装イメージ + +### 使用例1: 基本的な関数のネイティブ化 + +```nyash +// example1_basic.nyash +// Pythonコードをネイティブコンパイル + +// Step 1: Pythonコードを用意 +code = """ +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n-1) + fibonacci(n-2) + +def factorial(n): + if n == 0: + return 1 + return n * factorial(n-1) +""" + +// Step 2: パース +parser = new PythonParserBox() +ast = parser.parse(code) +print("Parsed functions: " + parser.getStats().get("functions")) + +// Step 3: コンパイル +compiler = new PythonCompilerBox() +mir_module = compiler.compile(ast) + +// Step 4: 実行 +if mir_module.isOk() { + // ネイティブ実行! + module = mir_module.get() + + // 関数を取得して実行 + fib = module.getFunction("fibonacci") + result = fib.call(10) + print("fibonacci(10) = " + result) // 55 + + fact = module.getFunction("factorial") + result = fact.call(5) + print("factorial(5) = " + result) // 120 +} else { + print("Compilation failed: " + mir_module.getError()) +} +``` + +### 使用例2: コンパイル可否の明確な判定 + +```nyash +// example2_clear_separation.nyash +// コンパイルできるかどうか事前に判定 + +// Phase 1対応のコード +code_phase1 = """ +def compute_sum(n): + total = 0 + for i in range(n): + total += i * i + return total + +def factorial(n): + if n == 0: + return 1 + return n * factorial(n-1) +""" + +// Phase 1未対応のコード +code_unsupported = """ +def fibonacci_generator(n): + a, b = 0, 1 + for _ in range(n): + yield a + a, b = b, a + b +""" + +// コンパイラーで判定 +parser = new PythonParserBox() +compiler = new PythonCompilerBox() + +// Phase 1対応コードのチェック +ast1 = parser.parse(code_phase1) +result1 = compiler.compile(ast1) +if result1.isOk() { + print("✅ Phase 1 code compiled successfully!") + module = result1.get() + print("compute_sum(100) = " + module.call("compute_sum", 100)) +} else { + print("❌ Compilation failed: " + result1.getError()) +} + +// 未対応コードのチェック +ast2 = parser.parse(code_unsupported) +result2 = compiler.compile(ast2) +if result2.isOk() { + print("✅ Compiled successfully!") +} else { + print("❌ Cannot compile: " + result2.getError()) + print(" Reason: yield expression not supported in Phase 1") + print(" Please use PyRuntimeBox instead") +} +``` + +### 使用例3: プログレッシブ最適化 + +```nyash +// example3_progressive.nyash +// 実行しながら徐々に最適化 + +// 型推論付きコンパイラー +compiler = new PythonCompilerBox() +compiler.enableTypeInference(true) +compiler.enableProfiling(true) + +// 初回実行(型情報収集) +code = """ +def matrix_multiply(A, B): + # 最初は型が不明 + result = [] + for i in range(len(A)): + row = [] + for j in range(len(B[0])): + sum = 0 + for k in range(len(B)): + sum += A[i][k] * B[k][j] + row.append(sum) + result.append(row) + return result +""" + +// プロファイル付き実行 +for i in range(5) { + mir = compiler.compile(parser.parse(code)) + + // 実行してプロファイル収集 + module = mir.get() + A = [[1, 2], [3, 4]] + B = [[5, 6], [7, 8]] + result = module.call("matrix_multiply", A, B) + + // 型情報が蓄積される + print("Iteration " + i + ": ") + print(" Type confidence: " + compiler.getTypeConfidence()) + print(" Optimization level: " + compiler.getOptimizationLevel()) +} + +// 5回実行後、完全に最適化されたコードが生成される +``` + +### 使用例4: 言語間相互運用 + +```nyash +// example4_interop.nyash +// PythonコードとNyashコードのシームレスな連携 + +// Pythonで数値計算関数を定義 +python_math = """ +import math + +def distance(x1, y1, x2, y2): + return math.sqrt((x2-x1)**2 + (y2-y1)**2) + +def normalize(vector): + magnitude = math.sqrt(sum(x**2 for x in vector)) + return [x/magnitude for x in vector] +""" + +// コンパイルしてNyashから使用 +module = compile_python(python_math) + +// Nyash側のゲームロジック +box GameObject { + init { x, y, vx, vy } + + update(dt) { + // Python関数をネイティブ速度で呼び出し + me.x = me.x + me.vx * dt + me.y = me.y + me.vy * dt + + // 正規化(Pythonの関数を使用) + local normalized = module.normalize([me.vx, me.vy]) + me.vx = normalized[0] + me.vy = normalized[1] + } + + distanceTo(other) { + // Pythonの距離計算関数を使用 + return module.distance(me.x, me.y, other.x, other.y) + } +} + +// 完全にネイティブコードとして実行される! +``` + +### 使用例5: デバッグとプロファイリング + +```nyash +// example5_debug.nyash +// 開発時のデバッグ支援 + +// デバッグモード有効 +parser = new PythonParserBox() +parser.enableDebug(true) + +compiler = new PythonCompilerBox() +compiler.enableDebug(true) +compiler.enableSourceMap(true) // 元のPythonコードへのマッピング + +problematic_code = """ +def buggy_function(items): + total = 0 + for item in items: + # バグ: itemが文字列の場合エラー + total += item * 2 + return total / len(items) +""" + +// コンパイル試行 +result = compiler.compile(parser.parse(problematic_code)) + +if result.isErr() { + // 詳細なエラー情報 + diag = compiler.getDiagnostics() + print("Compilation failed at line " + diag.line) + print("Issue: " + diag.message) + print("Suggestion: " + diag.suggestion) + + // フォールバックで実行してランタイムエラーを確認 + runtime = new PythonRuntimeBox() + try { + runtime.exec(problematic_code) + runtime.call("buggy_function", ["a", "b", "c"]) + } catch (e) { + print("Runtime error: " + e.message) + print("This would have been caught at compile time!") + } +} + +// プロファイリング情報 +profiler = new PythonProfiler() +profiler.attach(module) +profiler.run() + +print("Hot spots:") +print(profiler.getHotSpots()) +print("Type instability:") +print(profiler.getTypeInstability()) +``` + +## 🎯 実装の進化 + +### Phase 1(現在) +```python +# これらがネイティブ化可能 +def add(x, y): return x + y +def factorial(n): ... +def fibonacci(n): ... +``` + +### Phase 2(予定) +```python +# 特殊メソッド対応 +class Vector: + def __add__(self, other): ... + def __len__(self): ... + +# 内包表記 +squares = [x**2 for x in range(10)] +``` + +### Phase 3(将来) +```python +# 完全な言語機能 +async def fetch_data(): ... +@decorator +def enhanced_function(): ... +yield from generator +``` + +## 🚀 パフォーマンス期待値 + +``` +Benchmark: Fibonacci(30) +CPython: 1.234s +PyPy: 0.123s +Nyash Native: 0.012s (100x faster!) + +Benchmark: Matrix Multiplication (100x100) +CPython: 5.678s +NumPy: 0.234s +Nyash Native: 0.198s (NumPyに匹敵!) +``` \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-10.7/implementation.md b/docs/development/roadmap/phases/phase-10.7/implementation.md new file mode 100644 index 00000000..288d539f --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/implementation.md @@ -0,0 +1,249 @@ +# Phase 10.7 実装詳細 + +## 🛠️ 技術アーキテクチャ + +### 2段階変換パイプライン + +``` +Python AST → CorePy IR → Nyash AST → Nyashスクリプト +``` + +**CorePy IR**の役割: +- Pythonの複雑な構文を正規化 +- セマンティクスを明示的に(with→try/finally等) +- 最適化しやすい中間表現 + +### 実装構造 + +```rust +// plugins/nyash-python-parser-plugin/src/lib.rs +#[plugin_box] +pub struct PythonParserBox { + base: BoxBase, +} + +#[plugin_methods] +impl PythonParserBox { + pub fn parse(&self, code: &str) -> Result> { + Python::with_gil(|py| { + let ast_mod = py.import("ast")?; + let tree = ast_mod.call_method1("parse", (code,))?; + Ok(self.convert_ast(tree)?) + }) + } +} +``` + +## 📐 Python固有機能の実装戦略 + +### 1. デフォルト引数の罠 + +```python +# Python: 定義時に一度だけ評価 +def bad_default(lst=[]): + lst.append(1) + return lst +``` + +```nyash +// 生成されるNyash +box GeneratedModule { + init { _default_lst } + + constructor() { + me._default_lst = new ArrayBox() // 定義時に一度だけ + } + + bad_default(lst) { + if lst == null { + lst = me._default_lst // 同じインスタンスを再利用! + } + lst.append(1) + return lst + } +} +``` + +### 2. LEGB スコーピング + +```python +# Local → Enclosing → Global → Builtin +global_var = 1 +def outer(): + enclosing_var = 2 + def inner(): + local_var = 3 +``` + +実装: +- シンボルテーブルでスコープ管理 +- クロージャはBox/Cellで実装 +- global/nonlocalフラグを追跡 + +### 3. for/else, while/else + +```python +for i in range(10): + if i == 5: + break +else: + print("No break") +``` + +```nyash +// 生成されるNyash +local _broken = false +local _iter = py_iter(range(10)) +loop(true) { + local _next = py_next(_iter) + if _next.isStopIteration() { break } + local i = _next.value + + if i == 5 { + _broken = true + break + } +} +if not _broken { + print("No break") +} +``` + +## 🔧 パスパイプライン + +``` +Parse Python AST + ↓ +Symbol table analysis + ↓ +Normalize to CorePy IR + ↓ +Scope/closure analysis + ↓ +Type metadata attachment + ↓ +Lower to Nyash AST + ↓ +Peephole optimization + ↓ +Pretty-print + source map +``` + +## 📊 最適化戦略 + +### トランスパイル時の最適化 +- 定数畳み込み +- ループ不変式の巻き上げ +- ビルトイン関数の直接呼び出し(シャドウイングなし時) +- 冗長な`py_truthy()`の除去 + +### Nyashコンパイラに委ねる最適化 +- インライン展開 +- レジスタ割り当て +- ループアンローリング +- ベクトル化 + +### 型情報の活用 +```python +def add(x: int, y: int) -> int: + return x + y +``` +→ 型ヒントがあれば`py_binop`ではなく直接整数演算 + +## 🐛 エラー処理とデバッグ + +### ソースマップ +```json +{ + "version": 3, + "sources": ["example.py"], + "mappings": "AAAA,IAAM,CAAC,GAAG...", + "names": ["add", "x", "y"] +} +``` + +### デバッグモード +```bash +nyash-transpile --debug example.py +# 出力: +# - CorePy IRダンプ +# - Nyashプレビュー(元のPython行ヒント付き) +# - 変換トレース +``` + +### エラーメッセージ +``` +ERROR: Cannot compile function 'async_func' at line 10 +Reason: async/await not supported in Phase 1 +AST Node: AsyncFunctionDef +Suggestion: Use PyRuntimeBox or wait for Phase 3 +``` + +## ⚡ パフォーマンス最適化 + +### ホットパス識別 +```nyash +// プロファイル情報を活用 +if compiler.isHotPath(func) { + // 積極的な最適化 + result = compiler.optimizeAggressive(func) +} else { + // 標準的な変換 + result = compiler.compile(func) +} +``` + +### JIT連携 +```nyash +// 型特化コード生成 +@jit_specialize(int, int) +def add(x, y): + return x + y +``` + +## 🔌 プラグインAPI + +### 変換フック +```rust +trait TransformHook { + fn before_lower(&mut self, node: &CorePyNode); + fn after_lower(&mut self, node: &NyashNode); + fn on_function(&mut self, func: &FunctionDef); +} +``` + +### カスタムルール +```yaml +# custom_rules.yaml +rules: + - pattern: "dataclass" + action: "convert_to_nyash_box" + - pattern: "numpy.array" + action: "use_native_array" +``` + +## 📋 実装チェックリスト + +### Phase 1(必須) +- [ ] 関数定義(def) +- [ ] 条件分岐(if/elif/else) +- [ ] ループ(for/while with else) +- [ ] 基本演算子 +- [ ] 関数呼び出し +- [ ] return/break/continue +- [ ] LEGB スコーピング +- [ ] デフォルト引数 + +### Phase 2(拡張) +- [ ] 例外処理(try/except/finally) +- [ ] with文 +- [ ] list/dict/set comprehensions +- [ ] lambda式 +- [ ] *args, **kwargs + +### Phase 3(高度) +- [ ] async/await +- [ ] yield/yield from +- [ ] デコレータ +- [ ] クラス定義(基本) +- [ ] import文 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-10.7/testing-plan.md b/docs/development/roadmap/phases/phase-10.7/testing-plan.md new file mode 100644 index 00000000..139439bd --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/testing-plan.md @@ -0,0 +1,285 @@ +# Python Native Testing Plan + +## 🎯 テスト戦略の全体像 + +「世界中のPythonコードがNyashのテストケース」という思想のもと、CPythonをオラクルとして使用する包括的なテスト戦略。 + +## 🧪 テストレベル + +### 1. プラグインレベルテスト + +#### PythonParserBox Tests +```rust +// plugins/nyash-python-parser-plugin/tests/parser_tests.rs +#[test] +fn test_parse_simple_function() { + let parser = create_parser_box(); + let code = "def add(x, y): return x + y"; + let ast = parser.parse(create_string_box(code)); + + assert_eq!(ast.get_type().to_string(), "Module"); + let functions = ast.get_children(); + assert_eq!(functions.length(), 1); +} + +#[test] +fn test_parse_with_telemetry() { + let parser = create_parser_box(); + parser.enable_telemetry(true); + + let code = r#" +def supported(): return 1 +async def unsupported(): await foo() + "#; + + parser.parse(create_string_box(code)); + let stats = parser.get_stats(); + + assert_eq!(stats.get("total_functions"), 2); + assert_eq!(stats.get("supported_functions"), 1); +} +``` + +#### PythonCompilerBox Tests +```rust +#[test] +fn test_compile_arithmetic() { + let compiler = create_compiler_box(); + let ast = /* ... */; + + let mir = compiler.compile(ast); + assert!(mir.is_ok()); + + // MIR検証 + let module = mir.unwrap(); + assert!(module.has_function("add")); +} +``` + +### 2. Differential Testing Framework + +```nyash +// tests/differential/framework.nyash +box DifferentialTester { + init { oracle, implementation, results } + + constructor() { + me.oracle = new PythonRuntimeBox() // CPython + me.implementation = new NativeEngine() + me.results = new ArrayBox() + } + + test(code) { + local oracle_result, impl_result + + // CPythonで実行 + oracle_result = me.oracle.eval(code) + + // Native実装で実行 + impl_result = me.implementation.exec(code) + + // 結果比較 + return me.compare(oracle_result, impl_result) + } + + compare(expected, actual) { + // 出力、戻り値、例外を比較 + local match = new MapBox() + match.set("output", expected.output == actual.output) + match.set("return", expected.return == actual.return) + match.set("exception", expected.exception == actual.exception) + return match + } +} +``` + +### 3. テストケース生成 + +#### 基本テストスイート +```python +# tests/suites/phase1_tests.py + +# 算術演算 +def test_arithmetic(): + assert add(2, 3) == 5 + assert multiply(4, 5) == 20 + assert divide(10, 2) == 5.0 # true division + +# 制御フロー +def test_control_flow(): + # if/else + result = conditional_logic(True, 10, 20) + assert result == 10 + + # for/else + found = search_with_else([1, 2, 3], 5) + assert found == "not found" # else節実行 + +# デフォルト引数の罠 +def test_default_args(): + list1 = append_to_default(1) + list2 = append_to_default(2) + assert list1 is list2 # 同じリスト! +``` + +#### Fuzzing with Hypothesis +```python +# tests/fuzzing/property_tests.py +from hypothesis import given, strategies as st + +@given(st.integers(), st.integers()) +def test_arithmetic_properties(x, y): + """算術演算の性質をテスト""" + # Commutativity + assert add(x, y) == add(y, x) + + # Identity + assert add(x, 0) == x + + # Differential testing + native_result = native_add(x, y) + cpython_result = x + y + assert native_result == cpython_result +``` + +### 4. ベンチマークスイート + +```nyash +// benchmarks/numeric_suite.nyash +box NumericBenchmark { + run() { + local suite = new BenchmarkSuite() + + // Fibonacci + suite.add("fibonacci", { + "cpython": { return me.runCPython("fib.py") }, + "native": { return me.runNative("fib.py") } + }) + + // Matrix multiplication + suite.add("matrix_mult", { + "cpython": { return me.runCPython("matrix.py") }, + "native": { return me.runNative("matrix.py") } + }) + + return suite.execute() + } +} + +// 実行結果例 +// fibonacci: +// CPython: 1.234s +// Native: 0.123s (10.0x faster) +// matrix_mult: +// CPython: 5.678s +// Native: 0.456s (12.4x faster) +``` + +### 5. 回帰テスト + +```yaml +# .github/workflows/python-native-tests.yml +name: Python Native Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Differential Tests + run: | + cargo test --package nyash-python-parser-plugin + cargo test --package nyash-python-compiler-plugin + + - name: Coverage Report + run: | + ./tools/measure_compilation_coverage.sh + # Expected output: + # Phase 1 compatible files: 15% + # Phase 2 functions: 40% compilable + # Phase 3 functions: 10% compilable +``` + +## 📊 メトリクス収集 + +### コンパイル成功率 +```nyash +// 自動計測ツール +box CoverageAnalyzer { + analyze(directory) { + local parser = new PythonParserBox() + local compiler = new PythonCompilerBox() + local stats = new MapBox() + + for file in directory.glob("*.py") { + local ast = parser.parseFile(file) + local result = compiler.compile(ast) + + stats.increment("total") + if result.isOk() { + stats.increment("success") + } else { + stats.increment("not_compilable") + stats.record("unsupported", result.getError()) + } + } + + return stats + } +} +``` + +### パフォーマンス追跡 +```sql +-- メトリクスDB +CREATE TABLE benchmark_results ( + id SERIAL PRIMARY KEY, + test_name VARCHAR(255), + implementation VARCHAR(50), -- 'cpython' or 'native' + execution_time FLOAT, + memory_usage BIGINT, + timestamp TIMESTAMP, + git_hash VARCHAR(40) +); +``` + +## 🚨 失敗時の診断 + +### デバッグ情報収集 +```nyash +// コンパイル失敗時の詳細情報 +compiler.enableDebug(true) +result = compiler.compile(ast) + +if result.isErr() { + local diag = compiler.getDiagnostics() + print("Failed at: " + diag.get("location")) + print("Reason: " + diag.get("reason")) + print("AST node: " + diag.get("node_type")) + print("Suggestion: " + diag.get("suggestion")) +} +``` + +### トレース機能 +``` +NYASH_PYTHON_TRACE=1 ./target/release/nyash test.py +[Parser] Parsing function 'compute' at line 5 +[Compiler] Compiling BinOp: Add at line 7 +[Compiler] Unsupported: YieldFrom at line 15 +[Error] Cannot compile function 'generator_func' - yield not supported +``` + +## ✅ 受け入れ基準 + +### Phase 1完了 +- [ ] 基本テストスイート100%パス +- [ ] Differential testing 100%一致 +- [ ] Phase 1対応コードの100%コンパイル成功 +- [ ] 10x性能向上(数値計算ベンチマーク) + +### 各PR必須 +- [ ] 新機能の単体テスト +- [ ] Differential testケース追加 +- [ ] ベンチマーク結果(該当する場合) +- [ ] カバレッジ低下なし \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-10.7/workbench/DECISIONS.md b/docs/development/roadmap/phases/phase-10.7/workbench/DECISIONS.md new file mode 100644 index 00000000..d9f72ed7 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/workbench/DECISIONS.md @@ -0,0 +1,13 @@ +# DECISIONS (Phase 10.7) + +## 2025-08-30 — 二本立て運用(決定) +- 決定: 現行の実行系(PyRuntimeBox, Plugin-First)は維持し、トランスパイル系(Python→Nyash)は All-or-Nothing で併走。 +- 代替案: トランスパイルの部分フォールバック(実行時にPyRuntimeへ落とす)。 +- 理由: 実行時の不一致/隠れ分岐を避ける。デプロイ時の挙動を単純に保つ。 +- 影響: 生成Nyashの品質責任はトランスパイラ側。利用者は明示的に系を選択。 + +## 2025-08-30 — Parser/CompilerもプラグインBox(決定) +- 決定: PythonParserBox/PythonCompilerBox としてプラグイン化し、CLIから呼び出す。 +- 代替案: コア組込み。 +- 理由: Plugin-First原則、配布容易性、差し替え性、隔離テスト。 +- 影響: plugins/ 以下に新規プラグインを追加。SDKの最小拡張が必要になる場合あり。 diff --git a/docs/development/roadmap/phases/phase-10.7/workbench/IR_SCHEMA_MIN.md b/docs/development/roadmap/phases/phase-10.7/workbench/IR_SCHEMA_MIN.md new file mode 100644 index 00000000..4c25c8a2 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/workbench/IR_SCHEMA_MIN.md @@ -0,0 +1,39 @@ +# CorePy IR 最小スキーマ(C2草案) + +目的: Phase 1 の End-to-End を最短で通すための暫定IR。将来は構造化・拡張(with/try/comp/async等)。 + +## JSON 形式(暫定) +```json +{ + "module": { + "functions": [ + { + "name": "main", // 省略可(既定: "main") + "return_value": 0, // 省略可(bodyと排他) + "body": [ // 省略可(return_valueと排他) + { "Return": { "value": 0 } } + ] + } + ] + } +} +``` + +ショートカット(デバッグ/ブリッジ用) +```json +{ "nyash_source": "static box Generated { main() { return 0 } }" } +``` + +## 変換規則(最小) +- module.functions[0] だけを見る(複数関数は将来対応) +- name があれば `static box Generated { () { ... } }` +- return_value が数値/文字列なら `return ` を生成 +- body があれば先頭の Return.value を探し、`return ` を生成 +- 上記が無ければ `return 0` + +## 将来(予約) +- statements: If/While/For/Assign/Expr などの節を追加 +- expressions: BinOp/Call/Name/Constant などを構造化 +- functions配列の複数対応、クロージャは別Box化の方針を検討 + +注意: All-or-Nothing 原則のもと、未対応ノードはCompiler側で明示的にエラーにする(現段階では未実装のため、return 0にフォールバックするが、C2終盤でStrict化する)。 diff --git a/docs/development/roadmap/phases/phase-10.7/workbench/README.md b/docs/development/roadmap/phases/phase-10.7/workbench/README.md new file mode 100644 index 00000000..13d21608 --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/workbench/README.md @@ -0,0 +1,21 @@ +# Phase 10.7 Workbench + +このフォルダは Python Native(トランスパイル路線, All-or-Nothing)専用の作業台です。仕様・決定・スパイク・タスクをここに集約し、雑多にならないようにします。 + +構成 +- TODO.md: 直近の作業キュー(小粒で管理) +- DECISIONS.md: 決定事項(理由/代替案/影響) +- SPIKES/: 検証スパイクの成果(小さなPoCやプロト) +- notes-YYYYMMDD.md: 打合せ/検討メモ(必要に応じて) + +関連 +- 計画: ../PLAN.txt +- 実装: ../implementation.md +- テスト: ../testing-plan.md +- 背景: ../README.md + +運用ルール(最小) +- 一度に大きくしない(5〜30分単位の成果で刻む) +- 決定は DECISIONS.md に残す(誰でも後から辿れる) +- スパイクは SPIKES に隔離(本流に混ぜない) + diff --git a/docs/development/roadmap/phases/phase-10.7/workbench/TODO.md b/docs/development/roadmap/phases/phase-10.7/workbench/TODO.md new file mode 100644 index 00000000..17adc09c --- /dev/null +++ b/docs/development/roadmap/phases/phase-10.7/workbench/TODO.md @@ -0,0 +1,13 @@ +# TODO (Phase 10.7 Workbench) + +短期(C1〜C3に向けた小粒タスク) +- [ ] C1: Parser plugin 雛形スケルトンを作る(pyo3, parse(code)->AstBox/to_json) +- [ ] C1: Telemetry最小(node種別カウント, 未対応ノード列挙) +- [ ] C2: CorePy IR最小スキーマ(JSON)を commit(with/async系は予約) +- [ ] C2: IR→Nyash ASTの最小変換(def/if/for/while/return/算術/比較/呼出し) +- [ ] C3: CLI隠しフラグ prototyping(--pyc/--pyc-native) +- [ ] Docs: PLANとimplementationの差分同期(週次) + +メモ +- All-or-Nothing原則:未対応は即Err(自動フォールバックなし) +- 生成Nyashは現行AOT導線で配布可能(Strict) diff --git a/docs/development/roadmap/phases/phase-10/phase_10_6a_thread_safety_audit.md b/docs/development/roadmap/phases/phase-10/phase_10_6a_thread_safety_audit.md new file mode 100644 index 00000000..ecff8ccd --- /dev/null +++ b/docs/development/roadmap/phases/phase-10/phase_10_6a_thread_safety_audit.md @@ -0,0 +1,46 @@ +# Phase 10.6a — Thread-Safety Audit (Checklist) + +目的: NyashBox/ランタイムのスレッド安全性を棚卸しし、将来の並列化(10.6b/c以降)に備える。 + +## 方針 +- 既定は単一スレッド実行(VM/Interpreter)。並列化は opt-in。 +- 共有状態は `Arc<...>` と `RwLock/Mutex` により内的可変を確保。 +- クロススレッド境界に出る型は `Send + Sync` を満たす(必要に応じてラッパで担保)。 + +## チェックリスト +- Box実装(src/boxes/*) + - [ ] 共有内部状態を持つ型は `Arc>` のようにラップされているか + - [ ] `to_string_box()` が重い処理やグローバル可変に依存しないか + - [ ] FFI/プラグイン橋渡し時に非同期イベント/コールバックを保持しないか(保持する場合は送受戦略を文書化) +- ランタイム(src/runtime/*) + - [ ] `NyashRuntime` のメンバは `Send + Sync` 要件を満たす(`Arc<...>`) + - [ ] `GcHooks` 実装は `Send + Sync`(CountingGc/NullGc はOK) + - [ ] Scheduler 実装は `Send + Sync`(SingleThreadSchedulerはOK) +- VM/Interpreter + - [ ] MIR `Safepoint` で `runtime.scheduler.poll()` を呼ぶ(協調スケジューラの結合点) + - [ ] Grep: `rg -n "Safepoint" src` で配置確認 + +## Grep支援 +```bash +rg -n "Arc<|Mutex<|RwLock<|Send|Sync" src/boxes src/runtime +``` + +## 既知の注意点 +- Python/外部DLLとの橋渡しはGIL/PATH管理で単一スレッド優先(AOT時はPATH/PYTHONHOME調整済)。 +- BufferBox は共有化のために `Arc>>` を採用済み。 + +## クイック監査(第一次) +- ArrayBox: `Arc>>>` → OK(共有+内的可変) +- MapBox: `Arc>>>` → OK +- BufferBox: `Arc>>` → OK +- NyashRuntime: `box_registry: Arc>`, `box_declarations: Arc>`, `gc: Arc`, `scheduler: Option>` → OK +- Scheduler: `SingleThreadScheduler` 内部に `Arc>>` → OK +- GC Hooks: `NullGc/CountingGc` は `Send+Sync` 実装方針 → OK + +未確認/注意: +- プラグインBox(PluginBoxV2)の内部FFIハンドルはVM/EXE側で共有参照のみ(実体はFFI側)。クロススレッド呼出しは未サポート運用(明記要)。 +- 一部のBoxで外部資源(ファイル/ネット)を扱う場合、スレッド越境のI/O同期設計は別途(Phase 10.6d+)。 + +## 次の一手(提案) +- マーカーTraits(例: `ThreadSafeBox`)の導入は保留(破壊的)。現時点は監査+ドキュメントで運用。 +- 並列スケジューラ(M:N)の実装は `feature` フラグで段階導入。 diff --git a/docs/development/roadmap/phases/phase-10/phase_10_6b_scheduler_prep.txt b/docs/development/roadmap/phases/phase-10/phase_10_6b_scheduler_prep.txt index 2f390a6f..c09db04b 100644 --- a/docs/development/roadmap/phases/phase-10/phase_10_6b_scheduler_prep.txt +++ b/docs/development/roadmap/phases/phase-10/phase_10_6b_scheduler_prep.txt @@ -5,6 +5,7 @@ What’s added - Queue + delayed tasks (spawn/spawn_after) and `poll()` to run work. - VM calls `scheduler.poll()` at MIR `Safepoint` to integrate cooperative scheduling. - Poll budget via env `NYASH_SCHED_POLL_BUDGET` (default: 1) + - Trace via `NYASH_SCHED_TRACE=1` (diagnostic) How to use (dev) - Build runtime with default SingleThreadScheduler (already default via builder), or inject custom via: diff --git a/docs/development/roadmap/phases/phase-12/BOX-AS-ARGUMENT-SUPPORT.md b/docs/development/roadmap/phases/phase-12/BOX-AS-ARGUMENT-SUPPORT.md new file mode 100644 index 00000000..491745aa --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/BOX-AS-ARGUMENT-SUPPORT.md @@ -0,0 +1,142 @@ +# 埋め込みVMでの箱引数サポート + +## 🎯 結論:完全にサポート可能 + +現在のMIR/VM/JIT/プラグインすべてで**箱は箱を引数にできる**仕組みが確立されており、埋め込みVMでも同じパターンで実装可能です。 + +## 📊 各層での箱引数の扱い + +### 1. Nyashスクリプトレベル +```nyash +// 箱を引数に取るメソッド +box Processor { + process(inputBox, configBox) { + // inputBoxもconfigBoxも箱インスタンス + local data = inputBox.getData() + local settings = configBox.getSettings() + return me.transform(data, settings) + } +} +``` + +### 2. MIRレベル +``` +; box1.process(box2, box3) +%4 = BoxCall %1.process(%2, %3) +``` + +### 3. 埋め込みVMでの実装 + +#### バイトコード表現 +```c +// BoxCall命令: [op][dst][recv][method_id:2][argc][args...] +// 例: box1.process(box2, box3) +0x20 // OP_BOXCALL +0x04 // dst: %4 +0x01 // recv: %1 (box1) +0x00 0x10 // method_id: 16 (process) +0x02 // argc: 2 +0x02 // arg[0]: %2 (box2) +0x03 // arg[1]: %3 (box3) +``` + +#### 実行時処理 +```c +// 埋め込みVMでの箱引数処理 +int nyvm_execute_boxcall(NyashEmbeddedVM* vm, ...) { + // レシーバー取得 + NyVMValue* recv = &vm->values[recv_idx]; + + // 引数をTLVエンコード + for (int i = 0; i < argc; i++) { + NyVMValue* arg = &vm->values[args[i]]; + + if (arg->type == NYVM_TYPE_HANDLE) { + // 箱引数はハンドルとしてエンコード + uint32_t type_id = get_type_id_from_handle(arg->value.h); + uint32_t inst_id = get_instance_id_from_handle(arg->value.h); + encode_handle(&tlv, type_id, inst_id); + } else { + // プリミティブ値 + encode_primitive(&tlv, arg); + } + } + + // C ABIプラグイン呼び出し + return nyash_plugin_invoke(...); +} +``` + +## 🔄 ハンドル管理の詳細 + +### 1. ハンドルレジストリ +```c +// グローバルハンドルテーブル +typedef struct { + uint32_t type_id; // Box型ID + uint32_t instance_id; // インスタンスID + uint8_t flags; // GC/所有権フラグ +} HandleEntry; + +static HandleEntry g_handles[MAX_HANDLES]; + +// 新しい箱インスタンスの登録 +uint64_t register_box_handle(uint32_t type_id, uint32_t instance_id) { + uint64_t handle = allocate_handle(); + g_handles[handle] = (HandleEntry){ + .type_id = type_id, + .instance_id = instance_id, + .flags = HANDLE_OWNED + }; + return handle; +} +``` + +### 2. プラグイン間の箱共有 +```c +// プラグインAが箱を返す +int plugin_a_create_box(uint8_t* result, size_t* result_len) { + // 新しい箱を作成 + uint32_t type_id = 100; // CustomBox + uint32_t inst_id = create_instance(); + + // TLVエンコード + encode_handle(result, type_id, inst_id); + return 0; +} + +// プラグインBが箱を受け取る +int plugin_b_process(const uint8_t* args, size_t args_len) { + // TLVデコード + uint32_t type_id, inst_id; + decode_handle(args, &type_id, &inst_id); + + // 箱を使用 + process_box(type_id, inst_id); + return 0; +} +``` + +## 💡 重要なポイント + +### 1. 型安全性 +- ハンドルには型情報(type_id)が含まれる +- 実行時の型チェックが可能 + +### 2. 所有権管理 +- ハンドルは参照カウント or GC管理 +- プラグイン間で安全に共有 + +### 3. 相互運用性 +- ネイティブBox ↔ スクリプトBox間で透過的 +- 同じハンドル機構を使用 + +## 🎯 結論 + +埋め込みVMでも: +1. **箱は箱を引数に取れる**(ハンドル経由) +2. **型情報を保持**(type_id) +3. **プラグイン間で共有可能**(instance_id) +4. **C ABIと完全互換**(TLVエンコード) + +これにより、Nyashスクリプトで書いた高度なBoxコンポジションも、C ABIプラグインとして動作します! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/CRITICAL-ISSUE.md b/docs/development/roadmap/phases/phase-12/CRITICAL-ISSUE.md new file mode 100644 index 00000000..92033a22 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/CRITICAL-ISSUE.md @@ -0,0 +1,106 @@ +# 🚨 重大な設計問題:スクリプトプラグインとMIR/EXEの非互換性 + +## 問題の本質 + +**MIR/JIT/AOT(EXE)は、C ABIの関数しか呼び出せない。** + +``` +現実のフロー: +MIR → JIT → C関数呼び出し(固定アドレス)→ ネイティブコード実行 +MIR → AOT → EXE内のC関数(静的リンク)→ ネイティブコード実行 + +不可能なフロー: +MIR → ??? → Nyashスクリプト実行(インタープリター必要) +``` + +## なぜスクリプトプラグインは動作しないのか + +### 1. インタープリター依存 +```nyash +// スクリプトプラグイン +export box CustomMath { + sin(x) { return me._math.sin(x) } +} +``` +↓ +**実行にはNyash VMが必要** → EXEに埋め込めない + +### 2. 動的型システム +- C ABI: 静的型(i32, i64, double等) +- Nyashスクリプト: 動的型(Box) +- 型変換にはランタイムが必要 + +### 3. メモリ管理 +- C ABI: 手動管理またはシンプルなGC +- Nyashスクリプト: Arc> +- GC/参照カウント管理にランタイムが必要 + +## 実例:なぜFileBoxはC ABIなのか + +```rust +// FileBoxプラグイン(ネイティブ) +#[no_mangle] +pub extern "C" fn nyash_plugin_invoke(...) -> i32 { + // 直接システムコール可能 + // VMなしで動作 + // EXEに静的リンク可能 +} +``` + +対して、Nyashスクリプトは: +- VM必須 +- 動的評価 +- EXEに埋め込み不可 + +## 結論:Phase 12の方向転換が必要 + +### ❌ 不可能なこと +- スクリプトプラグインをJIT/AOTから直接呼び出し +- スクリプトプラグインをEXEに埋め込み +- ネイティブプラグインと完全に透過的な利用 + +### ✅ 可能なこと +1. **インタープリターモード限定** + - スクリプトプラグインはインタープリター実行時のみ + - 開発/プロトタイピング用途 + +2. **トランスパイル方式** + - Nyashスクリプト → C/Rust → ネイティブプラグイン + - ビルドステップが必要 + +3. **ハイブリッド実行** + - 開発時: スクリプトプラグイン(高速イテレーション) + - 本番時: ネイティブプラグイン(高性能) + +## 修正された価値提案 + +### 開発フローの改善 +``` +1. アイデア → Nyashスクリプトでプロトタイプ +2. 動作確認 → インタープリターでテスト +3. 性能要求 → Rust/Cで再実装 +4. 配布 → ネイティブプラグインとして +``` + +### 制限事項の明確化 +- **JIT/AOT**: ネイティブプラグインのみ +- **インタープリター**: スクリプトプラグインも可 +- **EXE生成**: ネイティブプラグインのみ含む + +## 推奨アクション + +1. **Phase 12の再定義** + - 「開発支援ツール」として位置づけ + - JIT/AOT統合は諦める + +2. **ドキュメント修正** + - 制限事項を明確に記載 + - 誤解を招く「透過的利用」を削除 + +3. **代替案の検討** + - Nyash→Rustトランスパイラー + - プラグインテンプレート生成ツール + +--- + +**重要な教訓:C ABIの制約は、システムプログラミングの本質的な制約である。** \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/EMBEDDED-VM-BOX-HANDLING.md b/docs/development/roadmap/phases/phase-12/EMBEDDED-VM-BOX-HANDLING.md new file mode 100644 index 00000000..53745db6 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/EMBEDDED-VM-BOX-HANDLING.md @@ -0,0 +1,207 @@ +# 埋め込みVMでのBox処理設計 + +## 🎯 核心:MIRレベルでのBox処理を再現 + +### 現在のMIR/VMでのBox処理フロー + +``` +1. MIR: BoxCall/PluginInvoke命令 + ↓ +2. VM: ValueId → VMValue変換 + ↓ +3. VMValue → Box or TLVエンコード + ↓ +4. メソッド実行 + ↓ +5. 結果をVMValueに戻す +``` + +## 📊 埋め込みVMの設計 + +### 1. 軽量VMValue定義 + +```c +// 埋め込みVM用の値表現 +typedef enum { + NYVM_TYPE_INT, + NYVM_TYPE_FLOAT, + NYVM_TYPE_BOOL, + NYVM_TYPE_STRING, + NYVM_TYPE_HANDLE, // Box参照(ハンドル) + NYVM_TYPE_VOID +} NyVMType; + +typedef struct { + NyVMType type; + union { + int64_t i; + double f; + uint8_t b; + struct { const char* data; size_t len; } s; + uint64_t h; // ハンドル(Box参照) + } value; +} NyVMValue; +``` + +### 2. MIRバイトコード形式 + +```c +// BoxCall命令のエンコード +enum { + OP_BOXCALL = 0x20, + OP_PLUGIN_INVOKE = 0x21, + // ... +}; + +// BoxCall: [op:1] [dst:1] [box_val:1] [method_id:2] [argc:1] [args...] +// 例: BoxCall %2 = %1.toString() +// → 0x20 0x02 0x01 0x00 0x00 0x00 +``` + +### 3. 埋め込みVMでのBoxCall実行 + +```c +int nyvm_execute_boxcall( + NyashEmbeddedVM* vm, + uint8_t dst, + uint8_t box_val, + uint16_t method_id, + uint8_t argc, + uint8_t* args +) { + // 1. レシーバー取得 + NyVMValue* recv = &vm->values[box_val]; + + // 2. プリミティブ型の場合 + if (recv->type != NYVM_TYPE_HANDLE) { + // プリミティブ→TLV変換 + uint8_t tlv_buf[256]; + size_t tlv_len = encode_primitive_to_tlv(recv, tlv_buf); + + // 組み込み実装を呼び出し + return call_builtin_method(recv->type, method_id, tlv_buf, tlv_len); + } + + // 3. Box(ハンドル)の場合 + uint64_t handle = recv->value.h; + + // 引数をTLVエンコード + uint8_t args_tlv[1024]; + size_t args_len = 0; + for (int i = 0; i < argc; i++) { + NyVMValue* arg = &vm->values[args[i]]; + args_len += encode_value_to_tlv(arg, &args_tlv[args_len]); + } + + // 4. プラグイン呼び出し(C ABI) + uint8_t result[4096]; + size_t result_len = sizeof(result); + + int rc = nyash_plugin_invoke( + get_type_id_from_handle(handle), + method_id, + get_instance_id_from_handle(handle), + args_tlv, args_len, + result, &result_len + ); + + // 5. 結果をVMValueに変換 + if (rc == 0) { + decode_tlv_to_value(result, result_len, &vm->values[dst]); + } + + return rc; +} +``` + +## 🔄 Box引数の処理 + +### 現在のVM(Rust) +```rust +// VMValue → Box変換 +let val = self.get_value(*arg)?; +Ok(val.to_nyash_box()) +``` + +### 埋め込みVM(C) +```c +// NyVMValue → TLVエンコード +switch (value->type) { + case NYVM_TYPE_INT: + encode_i64(tlv, value->value.i); + break; + case NYVM_TYPE_HANDLE: + encode_handle(tlv, + get_type_id_from_handle(value->value.h), + get_instance_id_from_handle(value->value.h) + ); + break; + // ... +} +``` + +## 💡 実装のポイント + +### 1. ハンドル管理 +```c +// グローバルハンドルテーブル +typedef struct { + uint32_t type_id; + uint32_t instance_id; + void* native_ptr; // 実際のBoxポインタ(必要な場合) +} HandleEntry; + +static HandleEntry g_handles[MAX_HANDLES]; +static uint64_t g_next_handle = 1; + +uint64_t register_handle(uint32_t type_id, uint32_t instance_id) { + uint64_t h = g_next_handle++; + g_handles[h].type_id = type_id; + g_handles[h].instance_id = instance_id; + return h; +} +``` + +### 2. 組み込みメソッド +```c +// 頻出メソッドは埋め込みVMに直接実装 +int call_builtin_method(NyVMType type, uint16_t method_id, ...) { + switch (type) { + case NYVM_TYPE_INT: + if (method_id == 0) { // toString + // 整数→文字列変換 + } + break; + // ... + } +} +``` + +### 3. プラグインとの統合 +```c +// 生成されるCコード +extern "C" int32_t nyplug_mybox_invoke(...) { + // MIRバイトコード実行 + NyashEmbeddedVM vm; + nyvm_init(&vm, BYTECODE, sizeof(BYTECODE)); + + // 引数をVMスタックに設定 + nyvm_decode_args(&vm, args, args_len); + + // メソッド実行 + nyvm_execute_method(&vm, method_id); + + // 結果をTLVエンコード + return nyvm_encode_result(&vm, result, result_len); +} +``` + +## 🎯 結論 + +埋め込みVMは: +1. **MIRのBoxCall/PluginInvoke命令を忠実に実装** +2. **TLVエンコード/デコードでC ABIと通信** +3. **ハンドルでBox参照を管理** +4. **頻出処理は最適化実装** + +これにより、Nyashスクリプトで書いたプラグインも、ネイティブプラグインと同じC ABIで動作します! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/IMPLEMENTATION-ROADMAP.md b/docs/development/roadmap/phases/phase-12/IMPLEMENTATION-ROADMAP.md new file mode 100644 index 00000000..91f0834e --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/IMPLEMENTATION-ROADMAP.md @@ -0,0 +1,195 @@ +# 埋め込みVM実装ロードマップ + +## 🎯 目標:スクリプトプラグインのC ABI化 + +**Nyashスクリプト → C ABIプラグイン変換の完全自動化** + +## 📊 技術スタック + +``` +[Nyashスクリプト] + ↓ パース・型チェック +[MIR (中間表現)] + ↓ 最適化・定数畳み込み +[MIRバイトコード] + ↓ 埋め込み +[Cソースコード] ← nyash-to-c ツール + ↓ コンパイル (cc/clang/gcc) +[.so/.dll/.a] ← 通常のプラグイン! +``` + +## 🚀 実装フェーズ + +### Phase 12.1: 最小埋め込みVM(2-3週間) + +#### 1. MIRバイトコード設計 +```rust +// mir_bytecode.rs +pub enum CompactInstruction { + // 1バイト命令(頻出) + LoadLocal(u8), // 0x00-0x7F + StoreLocal(u8), // 0x80-0xFF + + // 2バイト命令 + LoadConst(u8), // 0x01 XX + Call(u8), // 0x02 XX + + // 可変長 + LoadString, // 0x03 [len:u16] [data] + Jump, // 0x04 [offset:i16] +} +``` + +#### 2. 埋め込みVMコア +```c +// nyash_embedded_vm.h +typedef struct { + const uint8_t* bytecode; + size_t bytecode_len; + + // 実行時状態(最小) + void* stack[256]; + int sp; + void* locals[16]; +} NyashEmbeddedVM; + +int32_t nyash_embedded_execute( + const uint8_t* bytecode, + size_t bytecode_len, + uint32_t method_id, + const uint8_t* args, + size_t args_len, + uint8_t* result, + size_t* result_len +); +``` + +### Phase 12.2: Nyash→Cトランスパイラー(3-4週間) + +#### 1. 基本変換 +```bash +$ nyash-to-c math_plugin.ny -o math_plugin.c +Generating C plugin from Nyash script... +- Parsing... OK +- Type checking... OK +- MIR generation... OK +- Bytecode emission... OK +- C code generation... OK +Output: math_plugin.c (2.3KB) +``` + +#### 2. 生成コード例 +```c +// Generated from: math_plugin.ny +#include + +// MIRバイトコード(最適化済み) +static const uint8_t BYTECODE[] = { + 0x01, 0x00, // Version 1.0 + 0x01, 0x00, // 1 function + + // Function: cached_sin + 0x00, 0x08, // Function header + 0x80, 0x00, // StoreLocal 0 (x) + 0x02, 0x10, // Call sin + 0x90, // Return +}; + +// プラグインエントリポイント +extern "C" int32_t nyplug_math_plugin_invoke( + uint32_t type_id, + uint32_t method_id, + uint32_t instance_id, + const uint8_t* args, + size_t args_len, + uint8_t* result, + size_t* result_len +) { + return nyash_embedded_execute( + BYTECODE, sizeof(BYTECODE), + method_id, + args, args_len, + result, result_len + ); +} +``` + +### Phase 12.3: 最適化とツールチェーン(4-6週間) + +#### 1. ビルドシステム統合 +```toml +# nyash.toml +[[plugins]] +name = "math_plugin" +source = "plugins/math_plugin.ny" # Nyashソース +type = "script" # 自動的にC変換 + +[[plugins]] +name = "file_plugin" +source = "plugins/file_plugin/Cargo.toml" +type = "native" # 従来のRustプラグイン +``` + +#### 2. 自動ビルドパイプライン +```bash +$ nyash build --plugins +Building plugins... +[1/2] math_plugin (script) + - Transpiling to C... OK + - Compiling... OK + - Output: target/plugins/libmath_plugin.so +[2/2] file_plugin (native) + - Building with cargo... OK + - Output: target/plugins/libfile_plugin.so +Done! +``` + +## 📈 パフォーマンス目標 + +| 操作 | ネイティブ | 埋め込みVM | 目標比率 | +|------|-----------|------------|----------| +| 単純計算 | 10ns | 50ns | 5x | +| メソッド呼び出し | 20ns | 100ns | 5x | +| 文字列操作 | 100ns | 200ns | 2x | +| I/O操作 | 10μs | 10.1μs | 1.01x | + +## 🔧 開発ツール + +### 1. デバッガ +```bash +$ nyash-debug math_plugin.ny --method cached_sin --args "[3.14]" +Executing cached_sin(3.14)... +[PC:0000] LoadLocal 0 ; x = 3.14 +[PC:0002] Call sin ; sin(3.14) +[PC:0004] Return ; 0.0015926... +Result: 0.0015926 +``` + +### 2. プロファイラ +```bash +$ nyash-profile math_plugin.so +Method statistics: +- cached_sin: 1000 calls, avg 120ns +- cached_cos: 500 calls, avg 115ns +Bottlenecks: None detected +``` + +## 🎉 最終形 + +```bash +# 開発者の体験 +$ cat my_plugin.ny +export box MyPlugin { + init { cache = new MapBox() } + process(x) { return x * 2 } +} + +$ nyash build my_plugin.ny +✓ Generated: my_plugin.so + +$ nyash run --plugin my_plugin.so test.ny +✓ Plugin loaded (C ABI) +✓ Result: 42 +``` + +**Nyashで書いて、どこでも動く!** \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/README.md b/docs/development/roadmap/phases/phase-12/README.md new file mode 100644 index 00000000..cac9b10a --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/README.md @@ -0,0 +1,115 @@ +# Phase 12: Nyashスクリプトプラグインシステム革命 + +## 🚀 概要 + +Nyashスクリプト自体でプラグインを作成できる革命的発見!ビルド不要で、既存のネイティブプラグインを組み合わせて新機能を作成可能。 + +## 💡 発見の経緯 + +include/export仕様の検討中に、以下の重要な気づきが: + +```nyash +# custom_math_plugin.ny +export box CustomMathPlugin { + init { + _math = new MathBox() # 既存プラグイン活用 + _cache = new MapBox() # 結果キャッシュ + } + + // カスタム拡張 + cached_sin(x) { + local key = x.toString() + if me._cache.has(key) { + return me._cache.get(key) + } + local result = me._math.sin(x) + me._cache.set(key, result) + return result + } +} +``` + +これにより、Rust/C++のビルドなしでプラグイン開発が可能に! + +## 🎯 統一Box ABI設計 + +### 基本インターフェース + +```rust +// Rust側の統一インターフェース +trait BoxInterface { + fn invoke(&self, method_id: u32, args: NyashValue) -> NyashValue; + fn get_methods(&self) -> Vec; + fn init(&mut self, ctx: Context); + fn drop(&mut self); +} +``` + +### Nyashスクリプトプラグインの要件 + +```nyash +export box MyPlugin { + // 必須:初期化 + init { ... } + + // 推奨:FFI互換インターフェース + invoke(method_id, args) { + // method_idに基づいてディスパッチ + } + + // オプション:メソッド情報 + get_methods() { + return [ + { name: "method1", id: 1 }, + { name: "method2", id: 2 } + ] + } +} +``` + +## 📊 エコシステムへの影響 + +### 開発の民主化 +- **参入障壁の劇的低下**: Rust/C++環境不要 +- **即座の開発**: ビルド待ち時間ゼロ +- **コミュニティ拡大**: より多くの開発者が参加可能 + +### 新しい開発パターン +1. **プラグインの合成**: 複数のネイティブプラグインを組み合わせ +2. **ラピッドプロトタイピング**: アイデアを即座に実装 +3. **ホットリロード**: 実行中の更新が可能 + +## 🛣️ 実装ロードマップ + +### Phase 12.1: 基盤構築 +- [ ] Box ABI仕様の最終決定 +- [ ] export box構文のパーサー実装 +- [ ] 基本的なPluginRegistry実装 + +### Phase 12.2: 統一インターフェース +- [ ] FFIプラグインのBoxInterface対応 +- [ ] NyashスクリプトのBoxInterface実装 +- [ ] 相互運用テスト + +### Phase 12.3: 動的機能 +- [ ] 動的ロード/アンロード機能 +- [ ] ホットリロード対応 +- [ ] プラグイン間依存関係管理 + +### Phase 12.4: セキュリティと最適化 +- [ ] サンドボックス実装 +- [ ] ケイパビリティベース権限 +- [ ] パフォーマンス最適化 + +## 📚 関連ドキュメント +- [Gemini先生の分析](./gemini-analysis-script-plugins.md) +- [Codex先生の技術提案](./codex-technical-proposal.md) +- [統合分析まとめ](./synthesis-script-plugin-revolution.md) + +## 🎯 次のアクション +1. Box ABI仕様書の作成 +2. export box構文の実装開始 +3. 既存FFIプラグイン1つを統一インターフェースに移行 + +--- +*Everything is Box - そしてプラグインもBoxになる!* \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/REVISED-PROPOSAL.md b/docs/development/roadmap/phases/phase-12/REVISED-PROPOSAL.md new file mode 100644 index 00000000..161e41cc --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/REVISED-PROPOSAL.md @@ -0,0 +1,147 @@ +# Phase 12(改訂版):Nyashスクリプトプラグイン - 開発支援ツールとして + +## 🎯 現実的な位置づけ + +**スクリプトプラグインは、JIT/AOT/EXEとは独立した開発支援機能として実装する。** + +## 📊 制約と可能性の整理 + +### ❌ できないこと(技術的制約) +- MIR/VM/JIT/AOTからのスクリプトプラグイン呼び出し +- スクリプトプラグインのEXE埋め込み +- ネイティブプラグインとの完全な相互運用性 + +### ✅ できること(現実的な価値) +- インタープリターモードでの高速プロトタイピング +- 既存プラグインの組み合わせによる新機能開発 +- ビルド不要な機能拡張(開発時のみ) + +## 🔄 修正された開発フロー + +``` +┌─────────────────┐ +│ アイデア/要件 │ +└────────┬────────┘ + ↓ +┌─────────────────┐ +│ Nyashスクリプト │ ← 高速イテレーション +│ プラグイン作成 │ ビルド不要 +└────────┬────────┘ + ↓ +┌─────────────────┐ +│ インタープリター│ +│ でテスト/検証 │ +└────────┬────────┘ + ↓ + 性能要求? + ↙ ↘ + No Yes + ↓ ↓ +そのまま Rust/Cで +使用 再実装 + ↓ + ネイティブ + プラグイン + ↓ + JIT/AOT/EXE +``` + +## 📝 実装方針 + +### 1. インタープリター専用機能として実装 + +```nyash +// script_plugin.ny +export box CustomLogic { + init { + _math = new MathBox() // ネイティブプラグイン利用 + _cache = new MapBox() + } + + process(data) { + // 複雑なビジネスロジック + // インタープリターでのみ実行 + } +} +``` + +### 2. 明確な使用場面の区別 + +```nyash +// development.ny(開発時) +local plugin = include("custom_logic.ny") // ✅ OK + +// production.ny(本番時) +local plugin = new CustomLogicBox() // ネイティブ版を使用 +``` + +### 3. トランスパイル支援ツール(将来) + +```bash +# Nyashスクリプト → Rustテンプレート生成 +nyash-to-rust custom_logic.ny > custom_logic_plugin/src/lib.rs +``` + +## 🎯 価値提案(修正版) + +### 開発者にとっての価値 +1. **探索的プログラミング** - アイデアを即座に試せる +2. **プロトタイピング** - ビルドなしで機能検証 +3. **学習曲線の緩和** - Rust/C知識不要で拡張開発 + +### エコシステムへの貢献 +1. **アイデアの具現化** - スクリプトで検証→ネイティブで実装 +2. **コミュニティ参加** - より多くの開発者が貢献可能 +3. **ベストプラクティス** - 成功パターンの蓄積 + +## 🚀 実装計画(現実的版) + +### Phase 12.1: 基盤構築(2週間) +- [ ] export box構文(インタープリター専用) +- [ ] include()関数の拡張 +- [ ] 基本的なプラグインレジストリ + +### Phase 12.2: 開発体験向上(3週間) +- [ ] ホットリロード(開発モード) +- [ ] エラーメッセージ改善 +- [ ] デバッグ支援機能 + +### Phase 12.3: 移行支援(4週間) +- [ ] パフォーマンス分析ツール +- [ ] Rust変換テンプレート +- [ ] 移行ガイドライン + +## 📚 ドキュメント戦略 + +### 明確な制約の説明 +```markdown +# Nyashスクリプトプラグイン + +⚠️ **重要な制約** +- インタープリターモードでのみ動作 +- JIT/AOT/EXEでは使用不可 +- 本番環境ではネイティブプラグインへの移行推奨 +``` + +### 使用例の充実 +- プロトタイピング例 +- ネイティブ移行例 +- パフォーマンス比較 + +## 🎉 期待される成果(現実的版) + +### 短期(3ヶ月) +- 開発効率の向上(プロトタイピング時間80%削減) +- 新規開発者の参入(Rust不要) +- アイデア検証の高速化 + +### 中期(1年) +- 成功パターンの確立 +- ネイティブプラグインの品質向上 +- コミュニティ主導の機能提案増加 + +## 結論 + +**スクリプトプラグインは、C ABIの制約を認識した上で、開発支援ツールとして大きな価値を提供できる。** + +「Everything is Box」の哲学は、実行時の制約はあれど、開発時の自由度として実現される。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/SOLUTION-EMBEDDED-VM.md b/docs/development/roadmap/phases/phase-12/SOLUTION-EMBEDDED-VM.md new file mode 100644 index 00000000..53dffa1a --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/SOLUTION-EMBEDDED-VM.md @@ -0,0 +1,164 @@ +# 解決策:埋め込みVMによるスクリプトプラグイン実現 + +## 💡 発想の転換 + +**制約は「リンク時にC ABIが必要」だけ。つまり、C ABI関数の中でVMを動かせばいい!** + +## 🎯 アーキテクチャ + +```c +// C ABI関数(静的リンク可能) +extern "C" int32_t nyplug_custom_math_invoke( + uint32_t method_id, + const uint8_t* args, + size_t args_len, + uint8_t* result, + size_t* result_len +) { + // 埋め込みVM起動 + static NyashVM* embedded_vm = NULL; + if (!embedded_vm) { + embedded_vm = nyash_vm_create_minimal(); + nyash_vm_load_script(embedded_vm, EMBEDDED_SCRIPT); + } + + // スクリプト実行 + return nyash_vm_invoke(embedded_vm, method_id, args, args_len, result, result_len); +} + +// スクリプトは文字列リテラルとして埋め込み +static const char* EMBEDDED_SCRIPT = R"( +export box CustomMath { + cached_sin(x) { + // Nyashコード + } +} +)"; +``` + +## 🔄 実現方法 + +### 1. Nyash→C トランスパイラー + +```bash +# Nyashスクリプト → C関数 +nyash-to-c custom_math.ny > custom_math_plugin.c + +# 生成されるC +// custom_math_plugin.c +#include "nyash_embedded_vm.h" + +static const char* SCRIPT = "..."; // Nyashコード埋め込み + +extern "C" int32_t nyplug_custom_math_invoke(...) { + return nyash_embedded_invoke(SCRIPT, method_id, ...); +} +``` + +### 2. 最小VM実装 + +```rust +// crates/nyash-embedded-vm +pub struct EmbeddedVM { + // 最小限の実行環境 + values: Vec, + // スクリプトはプリコンパイル済みMIR + mir: MirModule, +} + +#[no_mangle] +pub extern "C" fn nyash_embedded_invoke( + script: *const c_char, + method_id: u32, + // ... TLV args/result +) -> i32 { + // MIR実行(インタープリター) +} +``` + +## 📊 利点と制約 + +### ✅ 可能になること +- **スクリプトプラグインがEXEに埋め込み可能** +- **JIT/AOTから呼び出し可能**(C ABI経由) +- **既存のプラグインシステムと完全互換** + +### ⚠️ 制約 +- **パフォーマンス**: 埋め込みVMのオーバーヘッド +- **サイズ**: 最小VMランタイムが必要(~500KB?) +- **機能制限**: フルVMの一部機能のみ + +## 🚀 段階的実装 + +### Phase 1: 最小埋め込みVM +```rust +// 必要最小限の機能 +- MIR実行(インタープリター) +- 基本型(Integer, String, Bool) +- メソッド呼び出し +- TLVエンコード/デコード +``` + +### Phase 2: Nyash→Cトランスパイラー +```nyash +// input: custom_math.ny +export box CustomMath { + sin(x) { ... } +} + +// output: custom_math_plugin.c +extern "C" int32_t nyplug_custom_math_invoke(...) { + static const uint8_t MIR_BYTECODE[] = { ... }; + return nyash_embedded_execute(MIR_BYTECODE, ...); +} +``` + +### Phase 3: 最適化 +- MIRプリコンパイル +- 頻出パスのネイティブ化 +- 選択的JITコンパイル + +## 💡 実装例 + +```c +// 生成されたプラグイン +#include + +// MIRバイトコード(事前コンパイル) +static const uint8_t CUSTOM_MATH_MIR[] = { + 0x01, 0x00, // version + 0x10, 0x00, // function count + // ... MIR instructions +}; + +extern "C" int32_t nyplug_custom_math_abi_version() { + return 1; +} + +extern "C" int32_t nyplug_custom_math_invoke( + uint32_t method_id, + const uint8_t* args, + size_t args_len, + uint8_t* result, + size_t* result_len +) { + // 埋め込みVM実行 + return nyash_mir_execute( + CUSTOM_MATH_MIR, + sizeof(CUSTOM_MATH_MIR), + method_id, + args, args_len, + result, result_len + ); +} +``` + +## 🎯 結論 + +**「リンク時にC ABI」という制約は、埋め込みVMで解決可能!** + +- Nyashスクリプト → MIR → Cコード → ネイティブプラグイン +- 開発の容易さ(Nyash)と配布の利便性(C ABI)を両立 +- 既存のプラグインエコシステムと完全互換 + +これで「Everything is Box」が真に実現する! \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/c-abi-compatibility.md b/docs/development/roadmap/phases/phase-12/c-abi-compatibility.md new file mode 100644 index 00000000..f4064606 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/c-abi-compatibility.md @@ -0,0 +1,175 @@ +# C ABIとの整合性:Phase 12スクリプトプラグインシステム + +## 🚨 重要な発見 + +Phase 10.1で既に**C ABI v0**が定義されており、これとPhase 12の提案を整合させる必要があります。 + +## 📊 現状のC ABI(Phase 10.1) + +### 既存のBID-FFI(プラグイン用) +```c +// 現在のプラグインFFI(TLVベース) +extern "C" fn nyash_plugin_invoke( + type_id: u32, + method_id: u32, + instance_id: u32, + args: *const u8, // TLVエンコード + args_len: usize, + result: *mut u8, // TLVエンコード + result_len: *mut usize, +) -> i32 +``` + +### 新しいNyRT C ABI v0 +```c +// コア関数 +int32_t nyrt_abi_version(void); +NyBox nyrt_box_new(uint64_t typeid, uint64_t size); +void nyrt_box_free(NyBox b); + +// プラグイン関数(例:Array) +int32_t nyplug_array_abi_version(void); +NyBox nyplug_array_new(void); +int32_t nyplug_array_get(NyBox arr, uint64_t i, NyBox* out); +``` + +## 🎯 Phase 12の修正案 + +### 問題点 +- Gemini/Codexの提案した`BoxInterface`トレイトは**Rust専用** +- C ABIとの相互運用性が考慮されていない +- TLVエンコーディングとの整合性が不明 + +### 解決策:C ABIラッパー戦略 + +```rust +// ❌ 元の提案(Rust専用) +trait BoxInterface { + fn invoke(&self, method_id: u32, args: NyashValue) -> NyashValue; +} + +// ✅ 修正案(C ABI互換) +pub struct ScriptPluginWrapper { + // Nyashスクリプトインスタンス + script_box: NyashValue, + + // C ABI互換性のためのFFI関数 + ffi_invoke: extern "C" fn( + type_id: u32, + method_id: u32, + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, + ) -> i32, +} + +impl ScriptPluginWrapper { + // 既存のBID-FFIと完全互換 + pub extern "C" fn invoke_ffi( + &self, + type_id: u32, + method_id: u32, + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, + ) -> i32 { + // 1. TLVデコード + let nyash_args = decode_tlv(args, args_len); + + // 2. Nyashスクリプト呼び出し + let result_value = self.script_box.invoke(method_id, nyash_args); + + // 3. TLVエンコード + encode_tlv(result_value, result, result_len) + } +} +``` + +## 🔄 統合アーキテクチャ + +``` +[JIT/AOT] ---> C ABI (nyrt_*/nyplug_*) --+--> [ネイティブプラグイン] + | + +--> [ScriptPluginWrapper] --> [Nyashスクリプト] +``` + +### 利点 +1. **完全な後方互換性** - 既存のプラグインがそのまま動作 +2. **統一されたFFI** - JIT/AOT/プラグインすべて同じC ABI +3. **透過的な利用** - 呼び出し側はネイティブ/スクリプトを区別しない + +## 📝 実装修正案 + +### Phase 12.1(修正版) +1. **ScriptPluginWrapperの実装** + - BID-FFI互換のC関数エクスポート + - TLVエンコード/デコード処理 + - Nyashスクリプトへの橋渡し + +2. **プラグインレジストリ拡張** + ```rust + pub struct PluginRegistry { + // 既存のネイティブプラグイン(C ABI) + native_plugins: HashMap, + + // スクリプトプラグイン(C ABIラッパー経由) + script_plugins: HashMap, + } + ``` + +3. **export box構文の実装** + ```nyash + export box CustomMathPlugin { + // BID-FFI互換のためのメタ情報 + __type_id__ = 100 // 動的割り当てor設定ファイル + __methods__ = { + "cached_sin": 1, + "cached_cos": 2 + } + + // 通常のNyashコード + init { ... } + cached_sin(x) { ... } + } + ``` + +## 🚀 移行パス + +### 段階1:既存プラグインの動作確認 +- FileBox、NetBox等がC ABI経由で正常動作 +- パフォーマンステスト + +### 段階2:簡単なスクリプトプラグイン +- MathBoxの一部機能をNyashで再実装 +- C ABIラッパー経由での動作確認 + +### 段階3:高度な統合 +- ネイティブとスクリプトの混在 +- 動的ロード/アンロード + +## ⚡ パフォーマンス影響 + +``` +呼び出しチェーン: +1. JIT → C ABI関数呼び出し(既存) +2. C ABI → ScriptPluginWrapper(追加) +3. Wrapper → TLVデコード(追加) +4. Wrapper → Nyashスクリプト実行(追加) +5. Wrapper → TLVエンコード(追加) + +予想オーバーヘッド: 100-500ns/呼び出し +``` + +## 🎯 結論 + +Phase 12のスクリプトプラグインシステムは、**C ABIを尊重**しつつ実装可能です。 + +- BoxInterfaceトレイトは内部実装詳細に留める +- 外部インターフェースは既存のC ABI(BID-FFI)を維持 +- ScriptPluginWrapperがブリッジとして機能 + +これにより、**「Everything is Plugin」**の哲学を保ちながら、スクリプトプラグインを実現できます。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/codex-technical-proposal.md b/docs/development/roadmap/phases/phase-12/codex-technical-proposal.md new file mode 100644 index 00000000..386967bf --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/codex-technical-proposal.md @@ -0,0 +1,334 @@ +# Codex先生の技術提案:Nyashスクリプトプラグインシステム実装 + +## エグゼクティブサマリー + +Nyashスクリプトをプラグインとして使用する提案は、技術的に極めて実現可能であり、Nyashエコシステムに革命的な価値をもたらします。「Everything is Box」哲学の究極の実現として、実装言語に依存しない統一インターフェースを提供することで、開発の民主化とエコシステムの爆発的成長が期待できます。 + +## 技術アーキテクチャ提案 + +### 1. 統一Box ABIの詳細設計 + +```rust +// コアインターフェース定義 +pub trait UnifiedBoxInterface: Send + Sync { + // 基本メソッド + fn invoke(&self, ctx: &mut Context, method_id: u32, args: &[NyashValue]) -> Result; + fn get_metadata(&self) -> BoxMetadata; + + // ライフサイクル管理 + fn initialize(&mut self, config: &BoxConfig) -> Result<(), BoxError>; + fn shutdown(&mut self) -> Result<(), BoxError>; + + // 動的機能(オプション) + fn hot_reload(&mut self, new_code: &str) -> Result<(), BoxError> { + Err(BoxError::NotSupported) + } +} + +// メタデータ構造 +pub struct BoxMetadata { + pub name: String, + pub version: String, + pub methods: Vec, + pub capabilities: Vec, + pub dependencies: Vec, +} +``` + +### 2. プラグインレジストリアーキテクチャ + +```rust +pub struct PluginRegistry { + // ネイティブプラグイン + native_plugins: HashMap>, + + // スクリプトプラグイン + script_plugins: HashMap, + + // 動的ID管理 + id_allocator: IdAllocator, + + // 依存関係グラフ + dependency_graph: DependencyGraph, +} + +impl PluginRegistry { + pub fn register_native(&mut self, plugin: impl UnifiedBoxInterface + 'static) -> u32 { + let id = self.id_allocator.allocate(); + self.native_plugins.insert(id, Arc::new(plugin)); + id + } + + pub fn register_script(&mut self, source: &str) -> Result { + let plugin = ScriptPlugin::compile(source)?; + let id = self.id_allocator.allocate(); + self.script_plugins.insert(id, plugin); + Ok(id) + } +} +``` + +### 3. スクリプトプラグインラッパー実装 + +```rust +pub struct ScriptPlugin { + vm: NyashVM, + box_instance: NyashValue, + method_cache: HashMap, +} + +impl UnifiedBoxInterface for ScriptPlugin { + fn invoke(&self, ctx: &mut Context, method_id: u32, args: &[NyashValue]) -> Result { + // メソッドキャッシュから高速検索 + if let Some(handle) = self.method_cache.get(&method_id) { + return self.vm.call_cached(handle, args); + } + + // 動的メソッド解決 + let method = self.resolve_method(method_id)?; + self.vm.call_method(&self.box_instance, &method, args) + } +} +``` + +## 実装戦略 + +### Phase 1: MVP実装(2-3週間) + +1. **基本インターフェース実装** + - UnifiedBoxInterfaceトレイトの実装 + - 既存FFIプラグイン1つを移行(MathBox推奨) + - ScriptPluginラッパーの基本実装 + +2. **export box構文の実装** + ```nyash + export box MyPlugin { + init { _version = "1.0.0" } + + // 必須:プラグインメタデータ + get_metadata() { + return { + name: "MyPlugin", + version: me._version, + methods: ["process", "transform"] + } + } + + // ビジネスロジック + process(data) { ... } + transform(input) { ... } + } + ``` + +3. **基本的なレジストリ** + - 静的登録のみ + - 依存関係解決なし + +### Phase 2: 動的機能(3-4週間) + +1. **動的ロード/アンロード** + ```nyash + local registry = new PluginRegistry() + local id = registry.load_script("path/to/plugin.ny") + registry.unload(id) + ``` + +2. **ホットリロード** + ```nyash + registry.enable_hot_reload("path/to/plugin.ny") + // ファイル変更時に自動リロード + ``` + +3. **依存関係管理** + - 循環依存検出 + - バージョン互換性チェック + +### Phase 3: 最適化とセキュリティ(4-6週間) + +1. **パフォーマンス最適化** + - メソッドキャッシング + - JITコンパイル統合 + - プリコンパイルオプション + +2. **セキュリティサンドボックス** + ```rust + pub struct Sandbox { + memory_limit: usize, + cpu_quota: Duration, + allowed_capabilities: HashSet, + } + ``` + +3. **ケイパビリティベースセキュリティ** + - ファイルアクセス制限 + - ネットワーク制限 + - システムコール制限 + +## パフォーマンス考察 + +### ベンチマーク予測 + +``` +操作 | ネイティブ | スクリプト | 比率 +--------------------|-----------|-----------|----- +単純メソッド呼び出し | 10ns | 100ns | 10x +複雑な計算(1000ops) | 1μs | 5μs | 5x +I/O操作 | 100μs | 102μs | 1.02x +``` + +### 最適化戦略 + +1. **ホットパスの識別** + - 頻繁に呼ばれるメソッドを自動検出 + - JITコンパイル優先度付け + +2. **ハイブリッドアプローチ** + - コア機能:ネイティブ実装 + - カスタマイズ層:スクリプト実装 + +## エコシステムへの影響 + +### 開発者体験の革新 + +1. **即座のフィードバックループ** + ```bash + # 編集 + vim my_plugin.ny + + # 即座にテスト(ビルド不要) + nyash test_plugin.ny + ``` + +2. **プラグインマーケットプレイス** + - GitHubから直接インストール + - バージョン管理統合 + - 自動更新機能 + +### コミュニティ成長予測 + +- **現在**: 10-20人のコアコントリビューター(Rust必須) +- **1年後**: 100-500人のプラグイン開発者(Nyashのみ) +- **3年後**: 1000+のプラグインエコシステム + +## リスクと緩和策 + +### 技術的リスク + +1. **パフォーマンス劣化** + - 緩和策:重要部分のネイティブ実装維持 + - プロファイリングツール提供 + +2. **セキュリティ脆弱性** + - 緩和策:デフォルトサンドボックス + - 署名付きプラグイン + +### エコシステムリスク + +1. **品質のばらつき** + - 緩和策:公式プラグインガイドライン + - 自動品質チェックツール + +2. **互換性問題** + - 緩和策:セマンティックバージョニング強制 + - 自動互換性テスト + +## 結論と推奨事項 + +### 即時実行すべきアクション + +1. **Box ABI仕様書の作成**(1週間) +2. **export box構文の実装**(2週間) +3. **MathBoxの統一インターフェース移行**(1週間) + +### 長期ビジョン + +Nyashスクリプトプラグインシステムは、単なる機能追加ではなく、Nyashを**プログラミング言語**から**拡張可能なプラットフォーム**へと進化させる革命的な一歩です。 + +「Everything is Box」の哲学が、実装言語の壁を超えて真に実現される時、Nyashは次世代のプログラミングエコシステムのモデルケースとなるでしょう。 + +## 付録:実装例 + +### A. 完全なスクリプトプラグイン例 + +```nyash +# advanced_math_plugin.ny +export box AdvancedMathPlugin { + init { + _math = new MathBox() + _cache = new MapBox() + _stats = new MapBox() + } + + // プラグインメタデータ(必須) + get_metadata() { + return { + name: "AdvancedMathPlugin", + version: "1.0.0", + methods: ["cached_sin", "cached_cos", "fibonacci", "factorial"], + capabilities: ["compute"], + dependencies: [{ + name: "MathBox", + version: ">=1.0.0" + }] + } + } + + // キャッシュ付き三角関数 + cached_sin(x) { + local key = "sin:" + x.toString() + if me._cache.has(key) { + me._update_stats("cache_hit") + return me._cache.get(key) + } + + local result = me._math.sin(x) + me._cache.set(key, result) + me._update_stats("cache_miss") + return result + } + + // 再帰的フィボナッチ(メモ化) + fibonacci(n) { + if n <= 1 { return n } + + local key = "fib:" + n.toString() + if me._cache.has(key) { + return me._cache.get(key) + } + + local result = me.fibonacci(n-1) + me.fibonacci(n-2) + me._cache.set(key, result) + return result + } + + // 統計情報 + get_stats() { + return me._stats + } + + // プライベートメソッド + _update_stats(event) { + local count = me._stats.get(event) or 0 + me._stats.set(event, count + 1) + } +} +``` + +### B. ネイティブとスクリプトの透過的利用 + +```nyash +// 使用側のコード(プラグインの実装言語を意識しない) +local math1 = new MathBox() // ネイティブプラグイン +local math2 = include("advanced_math_plugin.ny") // スクリプトプラグイン + +// 同じインターフェースで利用 +print(math1.sin(3.14)) // ネイティブ実装 +print(math2.cached_sin(3.14)) // スクリプト実装 + +// 動的に切り替え可能 +local math = get_config("use_cached") ? math2 : math1 +print(math.sin(1.57)) +``` + +--- +*"Write plugins in Nyash, for Nyash, by Nyash!"* \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/gemini-analysis-script-plugins.md b/docs/development/roadmap/phases/phase-12/gemini-analysis-script-plugins.md new file mode 100644 index 00000000..0311f301 --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/gemini-analysis-script-plugins.md @@ -0,0 +1,90 @@ +# Gemini先生の分析:Nyashスクリプトプラグインシステム + +## 技術的妥当性評価 + +### 結論:極めて実現可能性は高く、技術的にも非常に妥当 + +このアプローチは、多くのモダンな言語やエンジン(Lua, JavaScript/Node.js, Pythonなど)で採用されている「ネイティブコアとスクリプト拡張」という実績あるモデルを踏襲しています。 + +### 「Everything is Box」哲学との整合性 + +このアプローチは、Boxを「外部から観測可能なインターフェースを持つオブジェクト」と定義するならば、その実装がネイティブ(Rust/C++)であろうとスクリプト(Nyash)であろうと区別しない、という哲学の究極的な現れです。 + +## 統一インターフェース設計 + +### BoxInterface Traitの提案 + +```rust +// Rust側に、すべてのプラグインが実装すべきtraitを定義 +trait BoxInterface { + fn invoke(&self, method_id: u32, args: NyashValue) -> NyashValue; + // その他、初期化やメタデータ取得などの共通メソッド +} +``` + +### アーキテクチャ + +``` +[Nyashコード] -> [BoxInterface Trait] --+--> [FFIラッパー] -> [ネイティブコード] + | + +--> [Nyashスクリプトラッパー] -> [Nyash VM実行] +``` + +これにより、Nyashのコードからプラグインを利用する側は、相手がネイティブかスクリプトかを一切意識する必要がなくなります。 + +## エコシステムへの影響 + +### 開発の民主化 + +- **参入障壁の劇的な低下**: Rust/C++の環境構築やビルドプロセスが不要 +- **迅速なプロトタイピング**: アイデアをすぐにNyashスクリプトで形にし、テスト可能 + +### 新しいプラグインの形態 + +- **プラグインの合成**: 複数の既存プラグインを組み合わせて新しい機能を持つ「メタプラグイン」 +- **アプリケーションの「設定」としてのプラグイン**: ユーザーが自身のアプリケーションの動作をカスタマイズ + +### 動的性の向上 + +アプリケーションの実行中に、Nyashスクリプトプラグインをリロードしたり、新しいものを追加したりすることが容易になります。 + +## 実装ロードマップ + +### フェーズ1:コアランタイムの実現(MVP) +1. `BoxInterface` Traitの設計と実装(Rust側) +2. 既存FFIの`BoxInterface`への対応 +3. Nyashオブジェクトの`BoxInterface`対応 +4. `import` / `export` の実装 + +### フェーズ2:動的機能と管理 +1. プラグインレジストリの実装 +2. 動的ロード/アンロードAPIの提供 +3. ID管理の洗練 + +### フェーズ3:セキュリティと堅牢性 +1. サンドボックスの導入 +2. パフォーマンス分析ツールの提供 + +### フェーズ4:開発者体験(DX)の向上 +1. ドキュメントの整備 +2. LSP/静的解析の対応 + +## 他言語からの学び + +### Lua +- C APIが非常にクリーンで、Cの関数をLuaから、Luaの関数をCから呼び出すのが容易 +- **学ぶべき点**: ネイティブとスクリプト間の境界(API)をいかにシンプルで強力に保つか + +### Node.js (JavaScript) +- `require()`システムが、C++で書かれたネイティブアドオンも、JavaScriptで書かれたモジュールも、全く同じように読み込む +- **学ぶべき点**: 統一されたモジュール解決システムとインターフェースの重要性 + +### Python +- Cで書かれた拡張モジュールと、Pure Pythonで書かれたモジュールが共存 +- **学ぶべき点**: パフォーマンスが重要な部分はCで、それ以外は柔軟なPythonで書くという、実用的な使い分け + +## 総括 + +この発見はNyashの方向性を決定づける重要なマイルストーンです。パフォーマンスが最重要視されるコアな機能はRust/C++のFFIプラグインで、それ以外の大部分の機能、ビジネスロジック、UI、ユーザーカスタマイズなどはNyashスクリプトプラグインで開発する、という美しい棲み分けが実現します。 + +これはNyashを単なるプログラミング言語から、**拡張可能なプラットフォーム**へと昇華させる可能性を秘めています。 \ No newline at end of file diff --git a/docs/development/roadmap/phases/phase-12/synthesis-script-plugin-revolution.md b/docs/development/roadmap/phases/phase-12/synthesis-script-plugin-revolution.md new file mode 100644 index 00000000..aa6a2a4c --- /dev/null +++ b/docs/development/roadmap/phases/phase-12/synthesis-script-plugin-revolution.md @@ -0,0 +1,172 @@ +# Nyashスクリプトプラグイン革命:統合分析まとめ + +## 🚀 革命的発見の本質 + +Nyashスクリプト自体でプラグインを作成できるという発見は、単なる機能追加ではなく、**Nyashの本質的な進化**を意味します。 + +### Everything is Boxの究極形 + +``` +従来: Box = Rust/C++実装のオブジェクト +革命: Box = 実装言語を問わないインターフェース +``` + +この発見により、「Everything is Box」哲学が実装レイヤーの制約から解放され、真の意味で実現されます。 + +## 🎯 両先生の分析の統合 + +### 共通の評価ポイント + +1. **技術的妥当性**: 極めて高い実現可能性 +2. **エコシステムへの影響**: 開発者数の爆発的増加が期待 +3. **実装アプローチ**: 統一インターフェースによる透過的利用 + +### 相補的な視点 + +| 観点 | Gemini先生 | Codex先生 | +|------|-----------|-----------| +| 焦点 | 哲学的整合性・他言語事例 | 具体的実装・パフォーマンス | +| 強み | エコシステム影響分析 | 技術アーキテクチャ設計 | +| 提案 | 段階的ロードマップ | 詳細な実装戦略 | + +## 📊 統合実装計画 + +### 即時着手(1-2週間) + +1. **Box ABI仕様策定** + - Gemini案のBoxInterfaceトレイト + - Codex案のUnifiedBoxInterface + - 両案を統合した最終仕様 + +2. **export box構文実装** + ```nyash + export box PluginName { + // 必須:メタデータ提供 + get_metadata() { ... } + + // ビジネスロジック + method1() { ... } + method2() { ... } + } + ``` + +3. **プロトタイプ実装** + - MathBoxを統一インターフェースに移行 + - 簡単なNyashスクリプトプラグイン作成 + +### 中期目標(1-2ヶ月) + +1. **動的プラグインシステム** + - ホットリロード機能 + - 依存関係管理 + - バージョン互換性 + +2. **開発ツール整備** + - プラグインテンプレート + - デバッグツール + - パフォーマンスプロファイラ + +3. **セキュリティ基盤** + - サンドボックス実装 + - ケイパビリティベース権限 + +### 長期ビジョン(6ヶ月-1年) + +1. **プラグインエコシステム** + - マーケットプレイス構築 + - 自動品質チェック + - コミュニティガイドライン + +2. **高度な最適化** + - JIT統合 + - プリコンパイル機能 + - ネイティブ/スクリプトハイブリッド + +## 🔑 成功の鍵 + +### 技術的成功要因 + +1. **シンプルな統一インターフェース** + - 学習コストを最小化 + - 既存プラグインの移行を容易に + +2. **段階的移行パス** + - 既存のFFIプラグインと共存 + - 破壊的変更を避ける + +3. **パフォーマンス配慮** + - ホットパスはネイティブ維持 + - I/O boundタスクから適用 + +### エコシステム成功要因 + +1. **開発体験の劇的改善** + - ビルド不要 + - 即座のフィードバック + - 豊富なサンプル + +2. **コミュニティ形成** + - 初心者に優しいドキュメント + - アクティブなサポート + - 貢献への明確なパス + +## 🎊 期待される成果 + +### 短期(3-6ヶ月) +- プラグイン開発者: 10→100人 +- プラグイン数: 20→200個 +- 開発速度: 10倍向上 + +### 中期(1年) +- 主要機能の8割がプラグイン化 +- サードパーティエコシステム確立 +- 企業採用事例の出現 + +### 長期(3年) +- デファクトスタンダード化 +- 1000+のプラグイン +- 自立的エコシステム + +## 🏁 結論 + +Nyashスクリプトプラグインシステムは、**技術的に実現可能**であり、**戦略的に必須**の進化です。 + +「Everything is Box」哲学の真の実現により、Nyashは単なるプログラミング言語から、**次世代の拡張可能プラットフォーム**へと進化します。 + +### 合言葉 + +> **"Write plugins in Nyash, for Nyash, by Nyash!"** + +この革命により、Nyashコミュニティは爆発的な成長を遂げ、真に民主的なプログラミングエコシステムが誕生するでしょう。 + +--- + +## 📎 付録:クイックスタートガイド + +### 最初のスクリプトプラグイン(5分で作成) + +```nyash +# my_first_plugin.ny +export box MyFirstPlugin { + init { + _name = "My First Plugin" + _count = 0 + } + + greet(name) { + me._count = me._count + 1 + return "Hello, " + name + "! (call #" + me._count.toString() + ")" + } +} +``` + +### 使用例 + +```nyash +# main.ny +local plugin = include("my_first_plugin.ny") +print(plugin.greet("World")) // "Hello, World! (call #1)" +print(plugin.greet("Nyash")) // "Hello, Nyash! (call #2)" +``` + +**ビルド不要、即実行!** これがNyashスクリプトプラグインの力です。 \ No newline at end of file diff --git a/docs/ideas/new-features/2025-08-30-birth-args-tlv-generalization.md b/docs/ideas/new-features/2025-08-30-birth-args-tlv-generalization.md new file mode 100644 index 00000000..309c6bba --- /dev/null +++ b/docs/ideas/new-features/2025-08-30-birth-args-tlv-generalization.md @@ -0,0 +1,40 @@ +# Birth 引数の一般化メモ(可変長TLV / 例外ハンドリング) + +目的(10.5c): birth/by-name を汎用化し、String/Integer はプリミティブ、他は Handle で TLV 化。将来的な可変長と例外連携を見据えた最小要件を定義。 + +## ゴール +- 可変長引数の birth(例: `new FileBox(path, mode)` / `new MapBox(initial_pairs...)`)。 +- by-name birth: `type_name.birth(args...)` を TLV 経由で統一。 +- 失敗は Result/エラー文字列で伝搬し、パニック/例外の越境は行わない。 + +## TLV レイアウト(案) +- Header: `u16 entry_count` +- Entries: `[tag:u16][len:u16][payload..] * entry_count` +- 許容型: bool(1), i64(3), f64(5), string(6), bytes(7), handle(8) + +## 変換規約 +- StringBox → TLV string / IntegerBox → TLV i64(プリミティブ化) +- その他の Box → TLV handle (type_id:u32 + instance_id:u32) +- 即値(i64/f64/bool)はそのまま TLV 化 + +## 例 +- `new StringBox()` → 引数0 +- `new IntegerBox(42)` → [i64:42] +- `new FileBox("/tmp/x", "w")` → [string:"/tmp/x"], [string:"w"] +- `new ArrayBox(1, 2, 3)` → [i64:1], [i64:2], [i64:3] + +## 例外/エラーの扱い +- birth 失敗時は、 + - 可能なら `returns_result=true` とし、Ok(Handle)/Err(String) の TLV 形状を返す(VMは Result で包む)。 + - それが未設定の場合は、TLV string(エラーメッセージ)を返し、呼び手が `try_birth` 等で明示的に扱う(将来)。 +- パニック/例外の越境は禁止(C-ABI上はコード戻り or TLV に限定)。 + +## by-name birth +- `nyash_plugin_birth_by_name(box_type_name, args_tlv, out_tlv)`(将来/任意)。 +- Lowerer は型名が静的に分かるときは従来の `type_id` 経路、未確定時は by-name を使用可。 + +## 今後の拡張 +- 可変長 >2 の aN 受け: 既存 `*_invoke3_*` シムを拡張(`invokeN`/スタック経由/一時領域)。 +- Named 引数: TLV に name ハッシュ or 別エントリで拡張(後段)。 +- 既定値: Plugin 側の既定値解決と TLV 省略の整合性を設計。 + diff --git a/docs/reference/abi/ffi_calling_convention_min.md b/docs/reference/abi/ffi_calling_convention_min.md new file mode 100644 index 00000000..de4c8d62 --- /dev/null +++ b/docs/reference/abi/ffi_calling_convention_min.md @@ -0,0 +1,66 @@ +# Nyash FFI Calling Convention (Handle-First, TLV, v0) + +目的: 10.5c の指針に沿って、JIT/AOT/VM 共通の最小・実用的な呼び出し規約を短文化。a0/a1/a2 は Handle-First を前提とし、戻りは TLV で統一。 + +## 要点(TL;DR) +- Handle-First: a0 は常に `nyash.handle.of(receiver)`(i64)。他の引数は TLV でエンコード。 +- 引数TLV: String/Integer はプリミティブ化、それ以外は Handle(tag=8)。 +- 戻りTLV: i64/f64/bool/string/bytes/handle をサポート。`returns_result` 指定時は Result 形状(VMが尊重)。 +- by-name 経路: 型名未確定時は `*_invoke_by_name_*` シムで実行時解決(任意、推奨)。 +- Strict 原則: Strict 時は JIT 実行を停止(VM=仕様の唯一の基準)。AOT 生成のみ許可。 + +## シム関数(最小) +```c +// 受け手は a0(i64: handle)で指定。a1/a2 は任意引数のプレースホルダ。 +extern "C" long long nyash_plugin_invoke3_i64( + long long type_id, + long long method_id, + long long argc, // 受け手を含む総数(>=1) + long long a0, // receiver: nyash.handle.of(...) + long long a1, + long long a2 +); + +extern "C" double nyash_plugin_invoke3_f64( + long long type_id, + long long method_id, + long long argc, + long long a0, + long long a1, + long long a2 +); + +// by-name(型名未確定時に利用可能)最小シム +// 実装済み: getattr/call の i64系 +extern "C" long long nyash_plugin_invoke_name_getattr_i64(long long argc, long long a0, long long a1, long long a2); +extern "C" long long nyash_plugin_invoke_name_call_i64(long long argc, long long a0, long long a1, long long a2); +``` + +## 引数の規約(TLV) +- a0 は常に受け手のハンドル(i64)。レシーバは TLV には含めず、直接 a0 から解決。 +- a1/a2 は必要に応じて TLV バッファへ詰める(最大2引数の最小シム。将来拡張で可変長)。 +- エンコード方針(VM/nyrt共通) + - StringBox → TLV string(tag=6) + - IntegerBox → TLV i64(tag=3) + - BufferBox → TLV bytes(tag=7)(nyrtシムが自動検出) + - それ以外の Box → TLV handle(tag=8, payload: type_id:u32 + instance_id:u32) + - i64/f64/bool 等の即値 → 対応する TLV プリミティブ + +## 戻り値の規約(TLV) +- 戻り TLV の先頭タグにより分岐し、必要ならネイティブへデコード: + - tag=3 → i64、tag=5 → f64(`*_i64`/`*_f64` 経由) + - tag=6 → string、tag=7 → bytes + - tag=8 → handle(type_id/instance_id を登録し BoxRef 構築) +- `returns_result` のメソッドは、VM で Ok/Err を Result に包む(AOT も同等方針)。 + +## N引数サポート(暫定) +- `*_invoke3_*` は a1/a2 までの即値/ハンドルを直積み、3引数目以降はレガシーVM引数(位置3..N)からTLVに詰めることで対応(開発向け)。 +- ネイティブEXE(AOT)でのN引数の完全化は将来の `*_invokeN_*` で対応予定。 + +## Lowerer の前提(Handle-First) +- `emit_plugin_invoke(type_id, method_id, argc, has_ret)` を一本化。a0 は常に `nyash.handle.of(receiver)` を積む。 +- 受け手箱名が未確定な場面は by-name シムを使用可(最適化は後段)。 + +## メモ +- TLV タグの例: 1=Bool, 3=I64, 5=F64, 6=String, 7=Bytes, 8=Handle。 +- 既存詳細は `docs/papers/nyash-unified-lifecycle/technical-details.md` 参照。 diff --git a/docs/spec/include_export.md b/docs/spec/include_export.md new file mode 100644 index 00000000..7929fa9c --- /dev/null +++ b/docs/spec/include_export.md @@ -0,0 +1,40 @@ +Nyash Include/Export Specification (Phase 1) + +Overview +- Goal: Simple, box-first module system for code split and reuse. +- Principle: One file exports one module (Box). include(path) returns that Box. + +Syntax +- Include expression: + local Math = include "lib/math.nyash" + local r = Math.add(1, 2) + +Rules +- One static box per file: The included file must define exactly one static box. Zero or multiple is an error. +- Expression form: include(...) is an expression that evaluates to the included module’s Box instance. +- Caching: The same file is evaluated at most once per run (subsequent includes return the cached module). +- Path resolution: + - Relative: include("./x.nyash") resolves relative to the current project root. + - Roots (nyash.toml): + [include.roots] + std = "./stdlib" + lib = "./lib" + Then include("std/string") -> ./stdlib/string.nyash, include("lib/utils") -> ./lib/utils.nyash + - Extension/index: If extension is omitted, .nyash is appended. If the path is a directory, index.nyash is used. + +Backends +- Interpreter: include is executed at runtime (implemented via execute_include_expr). Returns the Box instance. +- VM/AOT: include is handled by the MIR builder by reading and parsing the target file and lowering its static box into the same MIR module (no new MIR op added). The include expression lowers into a new Box() for the included static box type. +- No new MIR instruction was added; existing instructions (Const/NewBox/BoxCall/etc.) are used. + +Limitations (Phase 1) +- Cycles: Circular includes are not yet reported with a dedicated error path. A follow-up change will add active-load tracking and a clear error message with the cycle path. +- Export box: Reserved for Phase 2. For now, the single static box in the file is the public API. + +Examples +- See examples/include_math.nyash and examples/include_main.nyash. + +Rationale +- Keeps MIR spec stable and relies on build-time module linking for VM/AOT. +- Aligns with Everything-is-Box: modules are Boxes; methods/fields are the API. + diff --git a/examples/README.md b/examples/README.md index c72b6582..e0243e54 100644 --- a/examples/README.md +++ b/examples/README.md @@ -55,3 +55,26 @@ NYASH_JIT_THRESHOLD=1 NYASH_JIT_HOSTCALL=1 \ - DebugConfigBox(events/stats/dump/dot)と GcConfigBox は Box から `apply()` で環境へ反映できます。 - `--emit-cfg path.dot` または `DebugConfigBox.setPath("jit_dot", path)` でCFGのDOT出力。いずれもdumpを自動有効化。 - イベントは `phase` フィールドで区別(lower/execute)。`jit_events_path` でJSONL出力先を指定可能。 + +## 5) AOT最小手順(--compile-native) +- 目的: Craneliftでオブジェクトを生成し、`libnyrt` とリンクしてEXE化。 +- 事前: `cargo build --release --features cranelift-jit` +- 実行例(String/Integer/Consoleの最小): +``` +./target/release/nyash --compile-native examples/aot_min_string_len.nyash -o app && ./app +# 結果は `Result: ` として標準出力に表示 +``` +- Python最小チェーン(RO): +``` +./target/release/nyash --compile-native examples/aot_py_min_chain.nyash -o app && ./app +``` +- スクリプト版(詳細な手順): `tools/build_aot.sh -o `(Windowsは `tools/build_aot.ps1`) + +## 6) Scheduler(Phase 10.6b 準備) +- 目的: 協調スケジューラのSafepoint連携を観測 +- 実行(デモ): +``` +NYASH_SCHED_DEMO=1 NYASH_SCHED_POLL_BUDGET=2 \ + ./target/release/nyash --backend vm examples/scheduler_demo.nyash +``` +- 期待: `[SCHED] immediate task ran at safepoint` と `[SCHED] delayed task ran at safepoint` が出力 diff --git a/examples/aot_py_eval_kwargs_env.nyash b/examples/aot_py_eval_kwargs_env.nyash new file mode 100644 index 00000000..414ac9c1 --- /dev/null +++ b/examples/aot_py_eval_kwargs_env.nyash @@ -0,0 +1,19 @@ +// AOT Python kwargs via env-injected eval code (暫定ブリッジ) +// 目的: TLVでのkwargs未整備の間、evalコードに **dict で渡す +// Build: +// cargo build --release --features cranelift-jit +// Run: +// NYASH_PY_EVAL_CODE="(__import__('builtins').int)(**{'x':'FF','base':16})" \ +// ./target/release/nyash --compile-native examples/aot_py_eval_kwargs_env.nyash -o app && \ +// NYASH_PY_EVAL_CODE="(__import__('builtins').int)(**{'x':'FF','base':16})" ./app + +static box Main { + main() { + local py, r + py = new PyRuntimeBox() + r = py.eval() // code is read from NYASH_PY_EVAL_CODE (host side) + me.console.println(r) + return 0 + } +} + diff --git a/examples/aot_py_min_chain.nyash b/examples/aot_py_min_chain.nyash new file mode 100644 index 00000000..b3b905b4 --- /dev/null +++ b/examples/aot_py_min_chain.nyash @@ -0,0 +1,17 @@ +// AOT Python minimal chain: import -> getattr -> call +// Build AOT (example): +// cargo build --release --features cranelift-jit +// ./target/release/nyash --compile-native examples/aot_py_min_chain.nyash -o app && ./app + +static box Main { + main() { + local py, math, sqrt_fn, x, r + py = new PyRuntimeBox() + math = py.import("math") + sqrt_fn = py.getattr(math, "sqrt") + x = new IntegerBox(16) + r = py.call(sqrt_fn, x) + return r // expects 4.0 (autodecode may convert FloatBox→f64) + } +} + diff --git a/examples/aot_py_result_err.nyash b/examples/aot_py_result_err.nyash new file mode 100644 index 00000000..2b9f53e2 --- /dev/null +++ b/examples/aot_py_result_err.nyash @@ -0,0 +1,16 @@ +// AOT Python evalR Err demo (returns Result.Err) +// Build: +// cargo build --release --features cranelift-jit +// ./target/release/nyash --compile-native examples/aot_py_result_err.nyash -o app && ./app + +static box Main { + main() { + local py, r + py = new PyRuntimeBox() + // Division by zero triggers a Python exception → Err(...) + r = py.evalR(1/0) + me.console.println(r) + return 0 + } +} + diff --git a/examples/aot_py_result_ok.nyash b/examples/aot_py_result_ok.nyash new file mode 100644 index 00000000..6335d589 --- /dev/null +++ b/examples/aot_py_result_ok.nyash @@ -0,0 +1,16 @@ +// AOT Python evalR OK demo (returns Result.Ok) +// Build: +// cargo build --release --features cranelift-jit +// ./target/release/nyash --compile-native examples/aot_py_result_ok.nyash -o app && ./app + +static box Main { + main() { + local py, r + py = new PyRuntimeBox() + // evalR returns a Result-like box; VM prints Ok(...), AOT prints numeric when autodecode is enabled + r = py.evalR("1 + 2") + me.console.println(r) + return 0 + } +} + diff --git a/examples/include_expr_test.nyash b/examples/include_expr_test.nyash new file mode 100644 index 00000000..8ffc99e4 --- /dev/null +++ b/examples/include_expr_test.nyash @@ -0,0 +1,6 @@ +static box Main { + main() { + return include "examples/include_math.nyash" + } +} + diff --git a/examples/include_main.nyash b/examples/include_main.nyash new file mode 100644 index 00000000..bb642901 --- /dev/null +++ b/examples/include_main.nyash @@ -0,0 +1,7 @@ +static box Main { + main() { + local Math = include "examples/include_math.nyash" + local r = Math.add(1, 2) + return r + } +} diff --git a/examples/include_math.nyash b/examples/include_math.nyash new file mode 100644 index 00000000..a96fc054 --- /dev/null +++ b/examples/include_math.nyash @@ -0,0 +1,6 @@ +static box Math { + add(a, b) { + return a + b + } +} + diff --git a/examples/py_min_chain_vm.nyash b/examples/py_min_chain_vm.nyash new file mode 100644 index 00000000..aa0a6e05 --- /dev/null +++ b/examples/py_min_chain_vm.nyash @@ -0,0 +1,19 @@ +// Python minimal chain (VM): import -> getattr -> call +// @env NYASH_PLUGIN_ONLY=1 +// @env NYASH_PY_AUTODECODE=1 +// Run: +// cargo build --release && ./target/release/nyash --backend vm examples/py_min_chain_vm.nyash + +static box Main { + main() { + local py, math, sqrt_fn, x, r + py = new PyRuntimeBox() + math = py.import("math") + sqrt_fn = py.getattr(math, "sqrt") + x = new IntegerBox(16) + r = py.call(sqrt_fn, x) + print(r) // expects 4.0 + return 0 + } +} + diff --git a/examples/python_compiler_box.nyash b/examples/python_compiler_box.nyash new file mode 100644 index 00000000..5d132e42 --- /dev/null +++ b/examples/python_compiler_box.nyash @@ -0,0 +1,197 @@ +// PythonCompilerBox - C2: Python AST → Nyash変換ロジック +// Rust製パーサープラグイン(C1)からのJSONを受け取って処理 + +box PythonCompilerBox { + init { supportedNodes, corePyIR } + + birth() { + // サポートするノードタイプを定義 + me.supportedNodes = new MapBox() + me.supportedNodes.set("Module", true) + me.supportedNodes.set("FunctionDef", true) + me.supportedNodes.set("Return", true) + me.supportedNodes.set("Constant", true) + me.supportedNodes.set("If", true) + me.supportedNodes.set("While", true) + me.supportedNodes.set("For", true) + me.supportedNodes.set("BinOp", true) + me.supportedNodes.set("Add", true) + me.supportedNodes.set("Sub", true) + me.supportedNodes.set("Mult", true) + me.supportedNodes.set("Div", true) + } + + // メインのコンパイル関数 + compile(astJson) { + local console = new ConsoleBox() + + // JSONパース(現在はスタブ) + // TODO: JSONBoxが実装されたら使用 + local ast = me.parseJson(astJson) + + if ast == null { + return me.error("Failed to parse AST JSON") + } + + // All-or-Nothingチェック + local checkResult = me.checkSupported(ast) + if not checkResult.isOk { + return me.error("Unsupported features: " + checkResult.unsupported) + } + + // CorePy IR生成 + me.corePyIR = me.generateCorePyIR(ast) + + // Nyashコード生成 + local nyashCode = me.generateNyash(me.corePyIR) + + return me.ok(nyashCode) + } + + // AST全体のサポートチェック + checkSupported(ast) { + local result = new MapBox() + result.set("isOk", true) + result.set("unsupported", new ArrayBox()) + + // 再帰的にチェック(簡易版) + if ast.get("counts") { + local counts = ast.get("counts") + if counts.get("unsupported") > 0 { + result.set("isOk", false) + local unsupportedList = ast.get("unsupported") + result.set("unsupported", unsupportedList) + } + } + + return result + } + + // CorePy IR生成(中間表現) + generateCorePyIR(ast) { + local ir = new MapBox() + ir.set("version", "0.1") + ir.set("module", new MapBox()) + + // シンプルな例:main関数のみ + local functions = new ArrayBox() + + local mainFunc = new MapBox() + mainFunc.set("name", "main") + mainFunc.set("params", new ArrayBox()) + mainFunc.set("body", new ArrayBox()) + + // return 0 を追加 + local returnStmt = new MapBox() + returnStmt.set("type", "return") + returnStmt.set("value", 0) + mainFunc.get("body").push(returnStmt) + + functions.push(mainFunc) + ir.get("module").set("functions", functions) + + return ir + } + + // Nyashコード生成 + generateNyash(ir) { + local code = new StringBox() + code.append("// Generated from Python by PythonCompilerBox\n") + code.append("// CorePy IR version: " + ir.get("version") + "\n\n") + + // static box Main生成 + code.append("static box Main {\n") + + // 関数生成 + local module = ir.get("module") + if module and module.get("functions") { + local functions = module.get("functions") + local i = 0 + loop(i < functions.length()) { + local func = functions.get(i) + code.append(me.generateFunction(func)) + i = i + 1 + } + } + + code.append("}\n") + + return code.toString() + } + + // 個別関数の生成 + generateFunction(func) { + local code = new StringBox() + + local name = func.get("name") + local params = func.get("params") + local body = func.get("body") + + // 関数シグネチャ + code.append(" " + name + "(") + // TODO: パラメータ処理 + code.append(") {\n") + + // 関数本体 + if body { + local i = 0 + loop(i < body.length()) { + local stmt = body.get(i) + code.append(me.generateStatement(stmt)) + i = i + 1 + } + } + + code.append(" }\n") + + return code.toString() + } + + // 文の生成 + generateStatement(stmt) { + local type = stmt.get("type") + + if type == "return" { + return " return " + stmt.get("value") + "\n" + } + + // TODO: 他の文タイプを追加 + + return " // TODO: " + type + "\n" + } + + // ヘルパー関数 + ok(value) { + local result = new MapBox() + result.set("success", true) + result.set("value", value) + return result + } + + error(message) { + local result = new MapBox() + result.set("success", false) + result.set("error", message) + return result + } + + // 仮のJSONパーサー(スタブ) + parseJson(jsonStr) { + // TODO: 実際のJSONパース実装 + // 今は固定のテスト用データを返す + local mock = new MapBox() + mock.set("success", true) + mock.set("dump", "Module(...)") + + local counts = new MapBox() + counts.set("total_nodes", 5) + counts.set("functions", 1) + counts.set("supported", 5) + counts.set("unsupported", 0) + mock.set("counts", counts) + + mock.set("unsupported", new ArrayBox()) + + return mock + } +} \ No newline at end of file diff --git a/examples/test_python_compiler b/examples/test_python_compiler new file mode 100644 index 00000000..ee2aa24e Binary files /dev/null and b/examples/test_python_compiler differ diff --git a/examples/test_python_compiler.c b/examples/test_python_compiler.c new file mode 100644 index 00000000..802bbe9d --- /dev/null +++ b/examples/test_python_compiler.c @@ -0,0 +1,35 @@ +// PythonCompilerBox 簡易テスト +// 環境変数 NYASH_PY_IR からJSON IRを読み取ってNyashコードを生成 + +#include +#include +#include + +// 超簡易的なコンパイラ(JSONパースなし) +const char* compile_simple(const char* ir) { + // JSONをちゃんとパースせずに、簡単なパターンマッチング + if (strstr(ir, "\"name\":\"main\"") && strstr(ir, "\"return_value\":0")) { + return "// Generated from Python\n" + "static box Main {\n" + " main() {\n" + " return 0\n" + " }\n" + "}\n"; + } + return "// Unsupported IR\n"; +} + +int main() { + const char* ir = getenv("NYASH_PY_IR"); + if (!ir) { + ir = "{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":0}]}}"; + printf("Using default IR: %s\n\n", ir); + } else { + printf("Compiling IR from NYASH_PY_IR: %s\n\n", ir); + } + + printf("=== Generated Nyash Code ===\n"); + printf("%s", compile_simple(ir)); + + return 0; +} \ No newline at end of file diff --git a/examples/test_python_parser.nyash b/examples/test_python_parser.nyash new file mode 100644 index 00000000..4164e040 --- /dev/null +++ b/examples/test_python_parser.nyash @@ -0,0 +1,42 @@ +// Test Python Parser Plugin +// 環境変数 NYASH_PY_CODE からPythonコードを読み取ってパース + +static box Main { + main() { + local console = new ConsoleBox() + + // 環境変数からPythonコードを取得 + local env_code = ConsoleBox.getEnv("NYASH_PY_CODE") + if env_code == null { + console.log("Usage: NYASH_PY_CODE='def main(): return 0' nyash test_python_parser.nyash") + return 1 + } + + console.log("=== Python Code ===") + console.log(env_code) + console.log("") + + // パーサープラグインを読み込み(仮想的に) + // 実際にはプラグインローダー経由で呼び出す必要がある + console.log("=== Parse Result ===") + console.log("(Parser plugin integration pending)") + + // 期待される出力形式を表示 + console.log("") + console.log("Expected output format:") + console.log("{") + console.log(' "success": true,') + console.log(' "dump": "Module(body=[FunctionDef(...)])",') + console.log(' "counts": {') + console.log(' "total_nodes": 5,') + console.log(' "functions": 1,') + console.log(' "classes": 0,') + console.log(' "supported": 1,') + console.log(' "unsupported": 0') + console.log(' },') + console.log(' "unsupported": []') + console.log("}") + + return 0 + } +} \ No newline at end of file diff --git a/nyash.toml b/nyash.toml index f6c298f7..9213694d 100644 --- a/nyash.toml +++ b/nyash.toml @@ -149,6 +149,8 @@ MathBox = 50 TimeBox = 51 PyRuntimeBox= 40 PyObjectBox = 41 +PythonParserBox = 60 +PythonCompilerBox = 61 # 新スタイルのプラグインルート(併用可・[libraries]は後方互換) [plugins] @@ -162,6 +164,8 @@ PyObjectBox = 41 "libnyash_counter_plugin" = "./plugins/nyash-counter-plugin" "libnyash_net_plugin" = "./plugins/nyash-net-plugin" "libnyash_math_plugin" = "./plugins/nyash-math-plugin" +"libnyash_python_parser_plugin" = "./plugins/nyash-python-parser-plugin" +"libnyash_python_compiler_plugin" = "./plugins/nyash-python-compiler-plugin" [libraries."libnyash_array_plugin"] boxes = ["ArrayBox"] path = "./plugins/nyash-array-plugin/target/release/libnyash_array_plugin" diff --git a/plugins/nyash-python-compiler-plugin/Cargo.toml b/plugins/nyash-python-compiler-plugin/Cargo.toml new file mode 100644 index 00000000..96f3aa80 --- /dev/null +++ b/plugins/nyash-python-compiler-plugin/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nyash-python-compiler-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +once_cell = "1.20" +serde_json = "1" diff --git a/plugins/nyash-python-compiler-plugin/nyash_box.toml b/plugins/nyash-python-compiler-plugin/nyash_box.toml new file mode 100644 index 00000000..234b1f4d --- /dev/null +++ b/plugins/nyash-python-compiler-plugin/nyash_box.toml @@ -0,0 +1,29 @@ +[box] +name = "Nyash Python Compiler Plugin" +version = "0.1.0" +description = "CorePy IR -> Nyash Source (Phase 10.7 C2)" +author = "Nyash Team" + +[provides] +boxes = ["PythonCompilerBox"] + +[PythonCompilerBox] +type_id = 61 + +[PythonCompilerBox.lifecycle] +birth = { id = 0 } +fini = { id = 4294967295 } + +[PythonCompilerBox.methods.compile] +id = 1 +args = [ { name = "ir_json", type = "string" } ] +returns = { type = "string" } # Nyash source (暫定) + +[implementation] +ffi_version = 1 +thread_safe = false + +[artifacts] +windows = "target/x86_64-pc-windows-msvc/release/nyash_python_compiler_plugin.dll" +linux = "target/release/libnyash_python_compiler_plugin.so" +macos = "target/release/libnyash_python_compiler_plugin.dylib" diff --git a/plugins/nyash-python-compiler-plugin/src/lib.rs b/plugins/nyash-python-compiler-plugin/src/lib.rs new file mode 100644 index 00000000..04e8c0f8 --- /dev/null +++ b/plugins/nyash-python-compiler-plugin/src/lib.rs @@ -0,0 +1,103 @@ +use once_cell::sync::Lazy; +use std::sync::Mutex; +use serde_json::Value as Json; + +const NYB_SUCCESS: i32 = 0; +const NYB_E_INVALID_METHOD: i32 = -3; +const NYB_E_SHORT_BUFFER: i32 = -1; + +const TYPE_ID_COMPILER: u32 = 61; +const METHOD_BIRTH: u32 = 0; +const METHOD_COMPILE: u32 = 1; +const METHOD_FINI: u32 = u32::MAX; + +static NEXT_ID: Lazy> = Lazy::new(|| Mutex::new(1)); + +#[no_mangle] +pub extern "C" fn nyash_plugin_abi() -> u32 { 1 } + +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { NYB_SUCCESS } + +#[no_mangle] +pub extern "C" fn nyash_plugin_invoke( + type_id: u32, + method_id: u32, + _instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + if type_id != TYPE_ID_COMPILER { return NYB_E_INVALID_METHOD; } + match method_id { + METHOD_BIRTH => { + unsafe { + let mut id_g = NEXT_ID.lock().unwrap(); + let id = *id_g; *id_g += 1; + let need = 4usize; + if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..4].copy_from_slice(&(id as u32).to_le_bytes()); + *result_len = need; + } + NYB_SUCCESS + } + METHOD_COMPILE => { + // Decode TLV first string arg as JSON IR + let ir = unsafe { + if args.is_null() || args_len < 8 { None } else { + let buf = std::slice::from_raw_parts(args, args_len); + let tag = u16::from_le_bytes([buf[4], buf[5]]); + let len = u16::from_le_bytes([buf[6], buf[7]]) as usize; + if tag == 6 && 8 + len <= buf.len() { + match std::str::from_utf8(&buf[8..8+len]) { + Ok(s) => Some(s.to_string()), + Err(_) => None + } + } else { None } + } + }; + let nyash_source = if let Some(s) = ir.or_else(|| std::env::var("NYASH_PY_IR").ok()) { + // Minimal: accept either {"nyash_source": "..."} shortcut, or a tiny IR + match serde_json::from_str::(&s).ok() { + Some(Json::Object(map)) => { + if let Some(Json::String(src)) = map.get("nyash_source") { + src.clone() + } else if let Some(module) = map.get("module") { + // Try module.functions[0].name and maybe return value + let mut ret_expr = "0".to_string(); + if let Some(funcs) = module.get("functions").and_then(|v| v.as_array()) { + if let Some(fun0) = funcs.get(0) { + if let Some(retv) = fun0.get("return_value") { + if retv.is_number() { ret_expr = retv.to_string(); } + else if let Some(s) = retv.as_str() { ret_expr = s.to_string(); } + } + } + } + format!("static box Generated {{\n main() {{\n return {}\n }}\n}}", ret_expr) + } else { + "static box Generated { main() { return 0 } }".to_string() + } + } + _ => "static box Generated { main() { return 0 } }".to_string(), + } + } else { + "static box Generated { main() { return 0 } }".to_string() + }; + unsafe { + let bytes = nyash_source.as_bytes(); + let need = 4 + bytes.len(); + if *result_len < need { *result_len = need; return NYB_E_SHORT_BUFFER; } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..2].copy_from_slice(&6u16.to_le_bytes()); + out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes()); + out[4..4+bytes.len()].copy_from_slice(bytes); + *result_len = need; + } + NYB_SUCCESS + } + METHOD_FINI => NYB_SUCCESS, + _ => NYB_E_INVALID_METHOD, + } +} diff --git a/plugins/nyash-python-parser-plugin/Cargo.toml b/plugins/nyash-python-parser-plugin/Cargo.toml new file mode 100644 index 00000000..e0f73a1e --- /dev/null +++ b/plugins/nyash-python-parser-plugin/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nyash-python-parser-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.22", features = ["auto-initialize"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[dev-dependencies] \ No newline at end of file diff --git a/plugins/nyash-python-parser-plugin/nyash_box.toml b/plugins/nyash-python-parser-plugin/nyash_box.toml new file mode 100644 index 00000000..94003d82 --- /dev/null +++ b/plugins/nyash-python-parser-plugin/nyash_box.toml @@ -0,0 +1,29 @@ +[box] +name = "Nyash Python Parser Plugin" +version = "0.1.0" +description = "Python -> AST/IR (Phase 10.7 C1)" +author = "Nyash Team" + +[provides] +boxes = ["PythonParserBox"] + +[PythonParserBox] +type_id = 60 + +[PythonParserBox.lifecycle] +birth = { id = 0 } +fini = { id = 4294967295 } + +[PythonParserBox.methods.parse] +id = 1 +args = [ { name = "code", type = "string" } ] +returns = { type = "string" } # JSON AST (暫定) + +[implementation] +ffi_version = 1 +thread_safe = false + +[artifacts] +windows = "target/x86_64-pc-windows-msvc/release/nyash_python_parser_plugin.dll" +linux = "target/release/libnyash_python_parser_plugin.so" +macos = "target/release/libnyash_python_parser_plugin.dylib" diff --git a/plugins/nyash-python-parser-plugin/src/lib.rs b/plugins/nyash-python-parser-plugin/src/lib.rs new file mode 100644 index 00000000..b3bc40e8 --- /dev/null +++ b/plugins/nyash-python-parser-plugin/src/lib.rs @@ -0,0 +1,318 @@ +use pyo3::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +struct ParseResult { + success: bool, + dump: Option, + counts: ParseCounts, + unsupported: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +struct ParseCounts { + total_nodes: usize, + functions: usize, + classes: usize, + supported: usize, + unsupported: usize, +} + +/// FFI: プラグインABIバージョン +#[no_mangle] +pub extern "C" fn nyash_plugin_abi_version() -> u32 { + 1 +} + +/// FFI: プラグイン初期化 +#[no_mangle] +pub extern "C" fn nyash_plugin_init() -> i32 { + // Python初期化は pyo3 の auto-initialize が処理 + 0 +} + +/// FFI: プラグインメソッド呼び出し(BID形式) +#[no_mangle] +pub extern "C" fn nyash_plugin_invoke( + type_id: u32, + method_id: u32, + _instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + const TYPE_ID_PARSER: u32 = 60; + const METHOD_BIRTH: u32 = 0; + const METHOD_PARSE: u32 = 1; + const METHOD_FINI: u32 = u32::MAX; + + if type_id != TYPE_ID_PARSER { + return -3; // NYB_E_INVALID_METHOD + } + + match method_id { + METHOD_BIRTH => { + // インスタンスIDを返す + unsafe { + let instance_id = 1u32; // 簡易実装 + if *result_len < 4 { + *result_len = 4; + return -1; // NYB_E_SHORT_BUFFER + } + let out = std::slice::from_raw_parts_mut(result, *result_len); + out[0..4].copy_from_slice(&instance_id.to_le_bytes()); + *result_len = 4; + } + 0 + } + METHOD_PARSE => { + // 引数からコードを取得(TLV形式の文字列を期待) + let code = unsafe { + if args.is_null() || args_len < 4 { + // 引数なしの場合は環境変数から取得 + std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } else { + // TLVデコード(簡易版) + let buf = std::slice::from_raw_parts(args, args_len); + if args_len >= 8 { + let tag = u16::from_le_bytes([buf[0], buf[1]]); + let len = u16::from_le_bytes([buf[2], buf[3]]) as usize; + if tag == 6 && 4 + len <= args_len { + match std::str::from_utf8(&buf[4..4+len]) { + Ok(s) => s.to_string(), + Err(_) => std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } + } else { + std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } + } else { + std::env::var("NYASH_PY_CODE").unwrap_or_else(|_| "def main():\n return 0".to_string()) + } + } + }; + + // パース実行 + let parse_result = Python::with_gil(|py| { + parse_python_code(py, &code) + }); + + // JSONにシリアライズ + match serde_json::to_string(&parse_result) { + Ok(json) => { + unsafe { + let bytes = json.as_bytes(); + let need = 4 + bytes.len(); + if *result_len < need { + *result_len = need; + return -1; // NYB_E_SHORT_BUFFER + } + let out = std::slice::from_raw_parts_mut(result, *result_len); + // TLVエンコード(tag=6:string) + out[0..2].copy_from_slice(&6u16.to_le_bytes()); + out[2..4].copy_from_slice(&(bytes.len() as u16).to_le_bytes()); + out[4..4+bytes.len()].copy_from_slice(bytes); + *result_len = need; + } + 0 + } + Err(_) => -4 // エラー + } + } + METHOD_FINI => 0, + _ => -3 // NYB_E_INVALID_METHOD + } +} + +/// FFI: Pythonコードをパース +#[no_mangle] +pub extern "C" fn nyash_python_parse(code: *const std::os::raw::c_char) -> *mut std::os::raw::c_char { + let code = unsafe { + if code.is_null() { + return std::ptr::null_mut(); + } + match std::ffi::CStr::from_ptr(code).to_str() { + Ok(s) => s, + Err(_) => return std::ptr::null_mut(), + } + }; + + let result = Python::with_gil(|py| { + parse_python_code(py, code) + }); + + match serde_json::to_string(&result) { + Ok(json) => { + let c_str = std::ffi::CString::new(json).unwrap(); + c_str.into_raw() + } + Err(_) => std::ptr::null_mut(), + } +} + +/// FFI: 文字列解放 +#[no_mangle] +pub extern "C" fn nyash_python_free_string(ptr: *mut std::os::raw::c_char) { + if !ptr.is_null() { + unsafe { + let _ = std::ffi::CString::from_raw(ptr); + } + } +} + +fn parse_python_code(py: Python, code: &str) -> ParseResult { + let mut result = ParseResult { + success: false, + dump: None, + counts: ParseCounts { + total_nodes: 0, + functions: 0, + classes: 0, + supported: 0, + unsupported: 0, + }, + unsupported: Vec::new(), + }; + + // Pythonのastモジュールをインポート + let ast_module = match py.import_bound("ast") { + Ok(m) => m, + Err(e) => { + result.dump = Some(format!("Failed to import ast module: {}", e)); + return result; + } + }; + + // コードをパース + let tree = match ast_module.call_method1("parse", (code,)) { + Ok(t) => t, + Err(e) => { + result.dump = Some(format!("Parse error: {}", e)); + return result; + } + }; + + // ASTをダンプ(文字列表現) + if let Ok(dump_str) = ast_module.call_method1("dump", (&tree,)) { + if let Ok(s) = dump_str.extract::() { + result.dump = Some(s); + } + } + + // ASTを解析してカウント + analyze_ast(py, &tree, &mut result); + + result.success = true; + result +} + +fn analyze_ast(_py: Python, node: &Bound<'_, PyAny>, result: &mut ParseResult) { + result.counts.total_nodes += 1; + + // ノードタイプを取得 - __class__.__name__ を使用 + if let Ok(class_obj) = node.getattr("__class__") { + if let Ok(name_obj) = class_obj.getattr("__name__") { + if let Ok(type_name) = name_obj.extract::() { + match type_name.as_str() { + "FunctionDef" => { + result.counts.functions += 1; + result.counts.supported += 1; + } + "AsyncFunctionDef" => { + result.counts.functions += 1; + result.counts.unsupported += 1; + result.unsupported.push("async function".to_string()); + } + "ClassDef" => { + result.counts.classes += 1; + result.counts.unsupported += 1; + result.unsupported.push("class definition".to_string()); + } + "For" | "While" | "If" => { + result.counts.supported += 1; + } + "Yield" | "YieldFrom" => { + result.counts.unsupported += 1; + result.unsupported.push("generator".to_string()); + } + _ => {} + } + } + } + } + + // 子ノードを再帰的に解析 + // ast.walk() を使って全ノードを取得 + if let Ok(ast_module) = node.py().import_bound("ast") { + if let Ok(walk_iter) = ast_module.call_method1("walk", (node,)) { + if let Ok(nodes) = walk_iter.iter() { + for child_result in nodes { + if let Ok(child) = child_result { + // 自分自身はスキップ(すでにカウント済み) + if !child.is(node) { + // 再帰的に解析(ただし walk は全ノードを返すので、 + // 実際には再帰なしでフラットに処理される) + result.counts.total_nodes += 1; + + if let Ok(class_obj) = child.getattr("__class__") { + if let Ok(name_obj) = class_obj.getattr("__name__") { + if let Ok(type_name) = name_obj.extract::() { + match type_name.as_str() { + "FunctionDef" => { + result.counts.functions += 1; + result.counts.supported += 1; + } + "AsyncFunctionDef" => { + result.counts.functions += 1; + result.counts.unsupported += 1; + if !result.unsupported.contains(&"async function".to_string()) { + result.unsupported.push("async function".to_string()); + } + } + "ClassDef" => { + result.counts.classes += 1; + result.counts.unsupported += 1; + if !result.unsupported.contains(&"class definition".to_string()) { + result.unsupported.push("class definition".to_string()); + } + } + "For" | "While" | "If" => { + result.counts.supported += 1; + } + "Yield" | "YieldFrom" => { + result.counts.unsupported += 1; + if !result.unsupported.contains(&"generator".to_string()) { + result.unsupported.push("generator".to_string()); + } + } + _ => {} + } + } + } + } + } + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple_parse() { + pyo3::prepare_freethreaded_python(); + + Python::with_gil(|py| { + let code = "def main():\n return 0"; + let result = parse_python_code(py, code); + + assert!(result.success); + assert_eq!(result.counts.functions, 1); + assert_eq!(result.counts.supported, 1); + }); + } +} \ No newline at end of file diff --git a/plugins/nyash-python-parser-plugin/test_parser b/plugins/nyash-python-parser-plugin/test_parser new file mode 100644 index 00000000..8e16a71f Binary files /dev/null and b/plugins/nyash-python-parser-plugin/test_parser differ diff --git a/plugins/nyash-python-parser-plugin/test_parser.c b/plugins/nyash-python-parser-plugin/test_parser.c new file mode 100644 index 00000000..8b03384c --- /dev/null +++ b/plugins/nyash-python-parser-plugin/test_parser.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +// FFI関数型定義 +typedef int (*plugin_init_fn)(void); +typedef char* (*parse_fn)(const char*); +typedef void (*free_fn)(char*); + +int main() { + // プラグインをロード + void* handle = dlopen("./target/release/libnyash_python_parser_plugin.so", RTLD_LAZY); + if (!handle) { + fprintf(stderr, "Failed to load plugin: %s\n", dlerror()); + return 1; + } + + // 関数を取得 + plugin_init_fn init_fn = (plugin_init_fn)dlsym(handle, "nyash_plugin_init"); + parse_fn parse_func = (parse_fn)dlsym(handle, "nyash_python_parse"); + free_fn free_func = (free_fn)dlsym(handle, "nyash_python_free_string"); + + if (!init_fn || !parse_func || !free_func) { + fprintf(stderr, "Failed to load functions\n"); + dlclose(handle); + return 1; + } + + // 初期化 + if (init_fn() != 0) { + fprintf(stderr, "Plugin init failed\n"); + dlclose(handle); + return 1; + } + + // 環境変数からコードを取得 + const char* code = getenv("NYASH_PY_CODE"); + if (!code) { + code = "def main():\n return 0"; + printf("Using default code: %s\n", code); + } else { + printf("Parsing code from NYASH_PY_CODE: %s\n", code); + } + + // パース実行 + char* result = parse_func(code); + if (result) { + printf("\n=== Parse Result ===\n%s\n", result); + free_func(result); + } else { + printf("Parse failed\n"); + } + + dlclose(handle); + return 0; +} \ No newline at end of file diff --git a/src/boxes/map_box.rs b/src/boxes/map_box.rs index c52073dc..735adfcc 100644 --- a/src/boxes/map_box.rs +++ b/src/boxes/map_box.rs @@ -98,7 +98,7 @@ * * ## ⚠️ 注意 * - キーは自動的に文字列変換される - * - スレッドセーフ (Arc使用) + * - スレッドセーフ (Arc使用) * - 大量データ格納時はメモリ使用量に注意 * - 存在しないキーの取得は "Key not found" メッセージ返却 */ diff --git a/src/interpreter/expressions/mod.rs b/src/interpreter/expressions/mod.rs index 33cfe555..a554c91f 100644 --- a/src/interpreter/expressions/mod.rs +++ b/src/interpreter/expressions/mod.rs @@ -131,8 +131,8 @@ impl NyashInterpreter { } ASTNode::Include { filename, .. } => { - self.execute_include(filename)?; - Ok(Box::new(VoidBox::new())) + // include式: 最初のstatic boxを返す + self.execute_include_expr(filename) } ASTNode::FromCall { parent, method, arguments, .. } => { diff --git a/src/interpreter/io.rs b/src/interpreter/io.rs index c9acc8c1..f339076c 100644 --- a/src/interpreter/io.rs +++ b/src/interpreter/io.rs @@ -10,14 +10,53 @@ use super::*; use crate::parser::NyashParser; impl NyashInterpreter { + /// Resolve include path using nyash.toml [include.roots] + fn resolve_include_path(&self, filename: &str, caller_dir: Option<&str>) -> String { + // If explicit relative path, resolve relative to caller when provided + if filename.starts_with("./") || filename.starts_with("../") { + return filename.to_string(); + } + // Try nyash.toml roots: key/path where key is first segment before '/' + let parts: Vec<&str> = filename.splitn(2, '/').collect(); + if parts.len() == 2 { + let root = parts[0]; + let rest = parts[1]; + let cfg_path = "nyash.toml"; + if let Ok(toml_str) = std::fs::read_to_string(cfg_path) { + if let Ok(toml_val) = toml::from_str::(&toml_str) { + if let Some(include) = toml_val.get("include") { + if let Some(roots) = include.get("roots").and_then(|v| v.as_table()) { + if let Some(root_path_val) = roots.get(root).and_then(|v| v.as_str()) { + let mut base = root_path_val.to_string(); + if !base.ends_with('/') && !base.ends_with('\\') { base.push('/'); } + let joined = format!("{}{}", base, rest); + return joined; + } + } + } + } + } + } + // Fallback: if caller_dir provided, join relative + if let Some(dir) = caller_dir { + if !filename.starts_with('/') && !filename.contains(":\\") && !filename.contains(":/") { + return format!("{}/{}", dir.trim_end_matches('/'), filename); + } + } + // Default to ./filename + format!("./{}", filename) + } /// include文を実行:ファイル読み込み・パース・実行 - File inclusion system pub(super) fn execute_include(&mut self, filename: &str) -> Result<(), RuntimeError> { - // パス正規化(簡易版) - let canonical_path = if filename.starts_with("./") || filename.starts_with("../") { - filename.to_string() - } else { - format!("./{}", filename) - }; + // パス解決(nyash.toml include.roots + 相対) + let mut canonical_path = self.resolve_include_path(filename, None); + // 拡張子補完・index対応 + if std::path::Path::new(&canonical_path).is_dir() { + let idx = format!("{}/index.nyash", canonical_path.trim_end_matches('/')); + canonical_path = idx; + } else if std::path::Path::new(&canonical_path).extension().is_none() { + canonical_path.push_str(".nyash"); + } // 重複読み込みチェック if self.shared.included_files.lock().unwrap().contains(&canonical_path) { @@ -45,6 +84,72 @@ impl NyashInterpreter { Ok(()) } + /// include式を実行:ファイルを評価し、最初のstatic boxを返す + pub(super) fn execute_include_expr(&mut self, filename: &str) -> Result, RuntimeError> { + // パス解決(nyash.toml include.roots + 相対) + let mut canonical_path = self.resolve_include_path(filename, None); + // 拡張子補完・index対応 + if std::path::Path::new(&canonical_path).is_dir() { + let idx = format!("{}/index.nyash", canonical_path.trim_end_matches('/')); + canonical_path = idx; + } else if std::path::Path::new(&canonical_path).extension().is_none() { + canonical_path.push_str(".nyash"); + } + + // ファイル読み込み(static box名検出用) + let content = std::fs::read_to_string(&canonical_path) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("Failed to read file '{}': {}", filename, e), + })?; + + // パースして最初のstatic box名を特定 + let ast = NyashParser::parse_from_string(&content) + .map_err(|e| RuntimeError::InvalidOperation { + message: format!("Parse error in '{}': {:?}", filename, e), + })?; + + let mut static_names: Vec = Vec::new(); + if let crate::ast::ASTNode::Program { statements, .. } = &ast { + for st in statements { + if let crate::ast::ASTNode::BoxDeclaration { name, is_static, .. } = st { + if *is_static { static_names.push(name.clone()); } + } + } + } + + if static_names.is_empty() { + return Err(RuntimeError::InvalidOperation { message: format!("include target '{}' does not define a static box", filename) }); + } + if static_names.len() > 1 { + return Err(RuntimeError::InvalidOperation { message: format!("include target '{}' defines multiple static boxes; exactly one is required", filename) }); + } + let box_name = static_names.remove(0); + + // まだ未読なら評価(重複読み込みはスキップ) + let already = { + let set = self.shared.included_files.lock().unwrap(); + set.contains(&canonical_path) + }; + if !already { + self.shared.included_files.lock().unwrap().insert(canonical_path); + self.execute(ast)?; + } + + // static boxを初期化・取得して返す + self.ensure_static_box_initialized(&box_name)?; + + // statics名前空間からインスタンスを取り出す + let global_box = self.shared.global_box.lock() + .map_err(|_| RuntimeError::RuntimeFailure { message: "Failed to acquire global box lock".to_string() })?; + let statics = global_box.get_field("statics").ok_or(RuntimeError::TypeError { message: "statics namespace not found in GlobalBox".to_string() })?; + let statics_inst = statics.as_any().downcast_ref::() + .ok_or(RuntimeError::TypeError { message: "statics field is not an InstanceBox".to_string() })?; + let value = statics_inst.get_field(&box_name) + .ok_or(RuntimeError::InvalidOperation { message: format!("Static box '{}' not found after include", box_name) })?; + + Ok((*value).clone_or_share()) + } + /// Arrow演算子を実行: sender >> receiver - Channel communication pub(super) fn execute_arrow(&mut self, sender: &ASTNode, receiver: &ASTNode) -> Result, RuntimeError> { @@ -111,4 +216,4 @@ impl NyashInterpreter { Ok(Box::new(VoidBox::new())) } -} \ No newline at end of file +} diff --git a/src/jit/extern/birth.rs b/src/jit/extern/birth.rs new file mode 100644 index 00000000..c9a5a6a8 --- /dev/null +++ b/src/jit/extern/birth.rs @@ -0,0 +1,4 @@ +//! Generic birth hostcall symbol names + +pub const SYM_BOX_BIRTH_H: &str = "nyash.box.birth_h"; + diff --git a/src/jit/extern/handles.rs b/src/jit/extern/handles.rs new file mode 100644 index 00000000..c3b9413a --- /dev/null +++ b/src/jit/extern/handles.rs @@ -0,0 +1,4 @@ +//! Handle-related extern symbol names + +pub const SYM_HANDLE_OF: &str = "nyash.handle.of"; + diff --git a/src/jit/extern/mod.rs b/src/jit/extern/mod.rs index 1456c9be..7e21d1c1 100644 --- a/src/jit/extern/mod.rs +++ b/src/jit/extern/mod.rs @@ -5,4 +5,5 @@ //! these externs once call emission is added. pub mod collections; - +pub mod handles; +pub mod birth; diff --git a/src/jit/lower/builder.rs b/src/jit/lower/builder.rs index c3017eb9..71bfcdbf 100644 --- a/src/jit/lower/builder.rs +++ b/src/jit/lower/builder.rs @@ -251,22 +251,38 @@ extern "C" fn nyash_plugin_invoke3_i64(type_id: i64, method_id: i64, argc: i64, crate::jit::observe::trace_push(format!("i64.end rc={} out_len={} pre_ok={} post_ok={}", rc, out_len, pre_ok, post_ok)); if rc != 0 { return 0; } let out_slice = unsafe { std::slice::from_raw_parts(out_ptr, out_len) }; - if let Some((tag, sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) { - if trace { eprintln!("[JIT-SHIM i64] TLV tag={} sz={}", tag, sz); } - crate::jit::observe::trace_push(format!("i64.tlv tag={} sz={}", tag, sz)); - match tag { - 2 => { // I32 - if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } - } - 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 }; - } - 5 => { // F64 → optional conversion to i64 when enabled - if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { + if let Some((tag, sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) { + if trace { eprintln!("[JIT-SHIM i64] TLV tag={} sz={}", tag, sz); } + crate::jit::observe::trace_push(format!("i64.tlv tag={} sz={}", tag, sz)); + match tag { + 2 => { // I32 + if let Some(v) = crate::runtime::plugin_ffi_common::decode::i32(payload) { return v as i64; } + } + 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); } + } + 8 => { // Handle(tag=8) + if sz == 8 { + let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); + let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); + let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); + let box_type_name = crate::runtime::plugin_loader_unified::get_global_plugin_host() + .read().ok() + .and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())) + .and_then(|m| m.into_iter().find(|(_k,v)| *v == r_type).map(|(k,_v)| k)) + .unwrap_or_else(|| "PluginBox".to_string()); + let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type_name, r_type, r_inst, invoke.unwrap()); + let arc: std::sync::Arc = std::sync::Arc::new(pb); + let h = crate::jit::rt::handles::to_handle(arc); + return h as i64; + } + } + 1 => { // Bool + return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; + } + 5 => { // F64 → optional conversion to i64 when enabled + if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref() == Some("1") { if sz == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f = f64::from_le_bytes(b); @@ -1084,6 +1100,22 @@ impl IRBuilder for CraneliftBuilder { arg_vals.push(z); } + // Ensure receiver (a0) is a runtime handle via nyash.handle.of (Handle-First) + { + use cranelift_module::Linkage; + use crate::jit::r#extern::handles as h; + let call_conv_h = self.module.isa().default_call_conv(); + let mut sig_h = Signature::new(call_conv_h); + sig_h.params.push(AbiParam::new(types::I64)); + sig_h.returns.push(AbiParam::new(types::I64)); + let func_id_h = self.module + .declare_function(h::SYM_HANDLE_OF, Linkage::Import, &sig_h) + .expect("declare handle.of failed"); + let fref_h = self.module.declare_func_in_func(func_id_h, fb.func); + let call_h = fb.ins().call(fref_h, &[arg_vals[0]]); + if let Some(rv) = fb.inst_results(call_h).get(0).copied() { arg_vals[0] = rv; } + } + // Choose f64 shim if allowlisted let use_f64 = if has_ret { if let Ok(list) = std::env::var("NYASH_JIT_PLUGIN_F64") { @@ -1136,6 +1168,23 @@ impl IRBuilder for CraneliftBuilder { arg_vals.push(z); } + // Ensure receiver (a0) is a runtime handle via nyash.handle.of + { + use cranelift_module::Linkage; + use crate::jit::r#extern::handles as h; + let call_conv_h = self.module.isa().default_call_conv(); + let mut sig_h = Signature::new(call_conv_h); + sig_h.params.push(AbiParam::new(types::I64)); + sig_h.returns.push(AbiParam::new(types::I64)); + let func_id_h = self.module + .declare_function(h::SYM_HANDLE_OF, Linkage::Import, &sig_h) + .expect("declare handle.of failed"); + let fref_h = self.module.declare_func_in_func(func_id_h, fb.func); + let call_h = fb.ins().call(fref_h, &[arg_vals[0]]); + // Replace a0 with handle result + if let Some(rv) = fb.inst_results(call_h).get(0).copied() { arg_vals[0] = rv; } + } + // Signature: (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); @@ -1400,6 +1449,7 @@ pub struct ObjectBuilder { desired_ret_is_f64: bool, ret_hint_is_b1: bool, local_slots: std::collections::HashMap, + block_param_counts: std::collections::HashMap, pub stats: (u64,u64,u64,u64,u64), pub object_bytes: Option>, } @@ -1435,6 +1485,7 @@ impl ObjectBuilder { 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, } @@ -1632,9 +1683,57 @@ impl IRBuilder for ObjectBuilder { 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()); } } fb.finalize(); } fn switch_to_block(&mut self, index: usize) { use cranelift_frontend::FunctionBuilder; if index >= self.blocks.len() { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); fb.switch_to_block(self.blocks[index]); self.current_block_index = Some(index); fb.finalize(); } fn seal_block(&mut self, index: usize) { use cranelift_frontend::FunctionBuilder; if index >= self.blocks.len() { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); fb.seal_block(self.blocks[index]); fb.finalize(); } - fn br_if_top_is_true(&mut self, _: usize, _: usize) { /* control-flow omitted for AOT PoC */ } - fn ensure_block_params_i64(&mut self, _index: usize, _count: usize) { /* PHI omitted for AOT PoC */ } - fn push_block_param_i64_at(&mut self, _pos: usize) { /* omitted */ } + fn br_if_top_is_true(&mut self, then_index: usize, else_index: usize) { + use cranelift_codegen::ir::{types, condcodes::IntCC}; + use cranelift_frontend::FunctionBuilder; + if then_index >= self.blocks.len() || else_index >= self.blocks.len() { return; } + 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 cond_b1 = if let Some(v) = self.value_stack.pop() { + let ty = fb.func.dfg.value_type(v); + if ty == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, v, 0) } else { v } + } else { + let z = fb.ins().iconst(types::I64, 0); + fb.ins().icmp_imm(IntCC::NotEqual, z, 0) + }; + fb.ins().brif(cond_b1, self.blocks[then_index], &[], self.blocks[else_index], &[]); + self.stats.3 += 1; + fb.finalize(); + } + fn ensure_block_params_i64(&mut self, index: usize, count: usize) { + use cranelift_frontend::FunctionBuilder; + if index >= self.blocks.len() { return; } + let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); + let have = self.block_param_counts.get(&index).copied().unwrap_or(0); + if count > have { + let b = self.blocks[index]; + for _ in have..count { let _ = fb.append_block_param(b, cranelift_codegen::ir::types::I64); } + self.block_param_counts.insert(index, count); + } + fb.finalize(); + } + fn push_block_param_i64_at(&mut self, pos: usize) { + use cranelift_frontend::FunctionBuilder; + use cranelift_codegen::ir::types; + let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); + let b = if let Some(idx) = self.current_block_index { self.blocks[idx] } else if let Some(b) = self.entry_block { b } else { fb.create_block() }; + fb.switch_to_block(b); + let params = fb.func.dfg.block_params(b).to_vec(); + if let Some(v) = params.get(pos).copied() { self.value_stack.push(v); } + else { let z = fb.ins().iconst(types::I64, 0); self.value_stack.push(z); } + fb.finalize(); + } + fn jump_to(&mut self, target_index: usize) { + use cranelift_frontend::FunctionBuilder; + if target_index >= self.blocks.len() { return; } + 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); } + fb.ins().jump(self.blocks[target_index], &[]); + self.stats.3 += 1; + fb.finalize(); + } fn hint_ret_bool(&mut self, is_b1: bool) { self.ret_hint_is_b1 = is_b1; } fn ensure_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{StackSlotData, StackSlotKind}; use cranelift_frontend::FunctionBuilder; if self.local_slots.contains_key(&index) { return; } let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let slot = fb.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8)); self.local_slots.insert(index, slot); fb.finalize(); } fn store_local_i64(&mut self, index: usize) { use cranelift_codegen::ir::{types, condcodes::IntCC}; use cranelift_frontend::FunctionBuilder; if let Some(mut v) = self.value_stack.pop() { if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); } let slot = self.local_slots.get(&index).copied(); 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 ty = fb.func.dfg.value_type(v); if ty != types::I64 { if ty == types::F64 { v = fb.ins().fcvt_to_sint(types::I64, v); } else { let one = fb.ins().iconst(types::I64, 1); let zero = fb.ins().iconst(types::I64, 0); let b1 = fb.ins().icmp_imm(IntCC::NotEqual, v, 0); v = fb.ins().select(b1, one, zero); } } if let Some(slot) = slot { fb.ins().stack_store(v, slot, 0); } fb.finalize(); } } @@ -1653,7 +1752,8 @@ impl CraneliftBuilder { builder.symbol("nyash.host.stub0", nyash_host_stub0 as *const u8); { use crate::jit::r#extern::collections as c; - use super::extern_thunks::{nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64}; + use crate::jit::r#extern::{handles as h, birth as b}; + use super::extern_thunks::{nyash_plugin_invoke_name_getattr_i64, nyash_plugin_invoke_name_call_i64, nyash_handle_of, nyash_box_birth_h, nyash_box_birth_i64}; builder.symbol(c::SYM_ARRAY_LEN, nyash_array_len as *const u8); builder.symbol(c::SYM_ARRAY_GET, nyash_array_get as *const u8); builder.symbol(c::SYM_ARRAY_SET, nyash_array_set as *const u8); @@ -1683,6 +1783,10 @@ impl CraneliftBuilder { builder.symbol(c::SYM_STRING_CHARCODE_AT_H, nyash_string_charcode_at_h as *const u8); builder.symbol(c::SYM_STRING_BIRTH_H, nyash_string_birth_h as *const u8); builder.symbol(c::SYM_INTEGER_BIRTH_H, nyash_integer_birth_h as *const u8); + builder.symbol(b::SYM_BOX_BIRTH_H, nyash_box_birth_h as *const u8); + builder.symbol("nyash.box.birth_i64", nyash_box_birth_i64 as *const u8); + // Handle helpers + builder.symbol(h::SYM_HANDLE_OF, nyash_handle_of as *const u8); // Plugin invoke shims (i64/f64) builder.symbol("nyash_plugin_invoke3_i64", nyash_plugin_invoke3_i64 as *const u8); builder.symbol("nyash_plugin_invoke3_f64", nyash_plugin_invoke3_f64 as *const u8); diff --git a/src/jit/lower/core.rs b/src/jit/lower/core.rs index 7960ab7d..3f899f4c 100644 --- a/src/jit/lower/core.rs +++ b/src/jit/lower/core.rs @@ -467,6 +467,12 @@ impl LowerCore { } if let Some(v) = self.known_i64.get(id).copied() { b.emit_const_i64(v); + return; + } + // Load from a local slot if this ValueId was previously materialized (e.g., handle results) + if let Some(slot) = self.local_index.get(id).copied() { + b.load_local_i64(slot); + return; } } @@ -534,36 +540,61 @@ impl LowerCore { } } I::NewBox { dst, box_type, args } => { - // Minimal JIT lowering for builtin pluginized boxes: birth() via handle-based shim - if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") && args.is_empty() { - let bt = box_type.as_str(); - match bt { - "StringBox" => { - // Emit host-call to create a new StringBox handle; push as i64 - b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_BIRTH_H, 0, true); - } - "IntegerBox" => { - b.emit_host_call(crate::jit::r#extern::collections::SYM_INTEGER_BIRTH_H, 0, true); - } - _ => { - // Any other NewBox (e.g., ArrayBox/MapBox/etc.) is UNSUPPORTED in JIT for now - // Allow plugin boxes to be created at runtime; treat as no-op for lowering - if bt != "PyRuntimeBox" && bt != "StringBox" && bt != "ConsoleBox" { self.unsupported += 1; } - } - } - } else { - // NewBox with args or NYASH_USE_PLUGIN_BUILTINS!=1 - // Special-case: IntegerBox(v) → track known i64, but do not treat as unsupported - if box_type == "IntegerBox" { - if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, iv); } } - // no-op lowering; avoid marking unsupported - } else if box_type == "PyRuntimeBox" && args.is_empty() { - // Allow PyRuntimeBox creation as no-op in strict AOT path - } else if box_type == "StringBox" || box_type == "ConsoleBox" { - // Allow StringBox creation (with/without arg) as no-op; valueはシム/実行時にTLVへ + // 最適化は後段へ(現状は汎用・安全な実装に徹する) + // 通常経路: + // - 引数なし: 汎用 birth_h(type_idのみ)でハンドル生成 + // - 引数あり: 既存のチェーン(直後の plugin_invoke birth で初期化)を維持(段階的導入) + if args.is_empty() { + let decision = crate::jit::policy::invoke::decide_box_method(box_type, "birth", 0, true); + if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = decision { + b.emit_const_i64(type_id as i64); + b.emit_host_call(crate::jit::r#extern::birth::SYM_BOX_BIRTH_H, 1, true); + self.handle_values.insert(*dst); + 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); } else { self.unsupported += 1; } + } else { + // 引数あり: 安全なパターンから段階的に birth_i64 に切替 + // 1) IntegerBox(const i64) + if box_type == "IntegerBox" && args.len() == 1 { + if let Some(src) = args.get(0) { + if let Some(iv) = self.known_i64.get(src).copied() { + // 汎用 birth_i64(type_id, argc=1, a1=iv) + if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", 1, true) { + b.emit_const_i64(type_id as i64); + b.emit_const_i64(1); + b.emit_const_i64(iv); + b.emit_host_call("nyash.box.birth_i64", 3, true); + self.handle_values.insert(*dst); + 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.known_i64.insert(*dst, iv); + return Ok(()); + } + } + } + } + // 2) 引数がハンドル(StringBox等)で既に存在する場合(最大2引数) + if args.len() <= 2 && args.iter().all(|a| self.handle_values.contains(a)) { + if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, .. } = crate::jit::policy::invoke::decide_box_method(box_type, "birth", args.len(), true) { + b.emit_const_i64(type_id as i64); + b.emit_const_i64(args.len() as i64); + // a1, a2 を push(ローカルに保存済みのハンドルをロード) + for a in args.iter().take(2) { self.push_value_if_known_or_param(b, a); } + b.emit_host_call("nyash.box.birth_i64", 2 + args.len(), true); + self.handle_values.insert(*dst); + 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); + return Ok(()); + } + } + // フォールバック: 既存チェーンに委譲(互換)+ 既知値伝搬のみ + if box_type == "IntegerBox" { + if let Some(src) = args.get(0) { if let Some(iv) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, iv); } } + } } // Track boxed numeric literals to aid signature checks (FloatBox/IntegerBox) if box_type == "FloatBox" { @@ -603,15 +634,27 @@ impl LowerCore { let argc = 1 + args.len(); // push receiver param index (a0) if known if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); } + // push primary arguments if available(a1, a2 ...) + for a in args.iter() { self.push_value_if_known_or_param(b, a); } b.emit_plugin_invoke_by_name(m, argc, dst.is_some()); - if let Some(d) = dst { self.handle_values.insert(*d); } + if let Some(d) = dst { + self.handle_values.insert(*d); + // Store handle result into a local slot so it can be used as argument later + 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); + } } else if self.handle_values.contains(box_val) && (m == "getattr" || m == "call") { let argc = 1 + args.len(); // push receiver handle/param index if possible (here receiver is a handle result previously returned) // We cannot reconstruct handle here; pass -1 to allow shim fallback. b.emit_const_i64(-1); + for a in args.iter() { self.push_value_if_known_or_param(b, a); } b.emit_plugin_invoke_by_name(m, argc, dst.is_some()); - if let Some(d) = dst { self.handle_values.insert(*d); } + 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); + } } else if (bt == "PyRuntimeBox" && (m == "birth" || m == "eval")) || (bt == "IntegerBox" && m == "birth") || (bt == "StringBox" && m == "birth") diff --git a/src/jit/lower/extern_thunks.rs b/src/jit/lower/extern_thunks.rs index 8fc471a9..e1a9a5c9 100644 --- a/src/jit/lower/extern_thunks.rs +++ b/src/jit/lower/extern_thunks.rs @@ -11,6 +11,124 @@ use crate::runtime::plugin_loader_unified; #[cfg(feature = "cranelift-jit")] use crate::runtime::plugin_loader_v2::PluginBoxV2; +// ---- Generic Birth (handle) ---- +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_box_birth_h(type_id: i64) -> i64 { + // Map type_id -> type name and create via plugin host; return runtime handle + if type_id <= 0 { return 0; } + let tid = type_id as u32; + let name_opt = crate::runtime::plugin_loader_unified::get_global_plugin_host() + .read().ok() + .and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())) + .and_then(|m| m.into_iter().find(|(_k,v)| *v == tid).map(|(k,_v)| k)); + if let Some(box_type) = name_opt { + if let Ok(host) = crate::runtime::get_global_plugin_host().read() { + if let Ok(b) = host.create_box(&box_type, &[]) { + let arc: std::sync::Arc = std::sync::Arc::from(b); + let h = crate::jit::rt::handles::to_handle(arc); + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "box_type": box_type, "type_id": tid, "handle": h}), "hostcall", ""); + return h as i64; + } else { + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "create_failed", "box_type": box_type, "type_id": tid}), "hostcall", ""); + } + } + } else { + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "type_map_failed", "type_id": tid}), "hostcall", ""); + } + 0 +} +// Generic birth with args on JIT side: (type_id, argc, a1, a2) -> handle +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_box_birth_i64(type_id: i64, argc: i64, a1: i64, a2: i64) -> i64 { + use crate::runtime::plugin_loader_v2::PluginBoxV2; + if type_id <= 0 { return 0; } + // Resolve invoke for the type by creating a temp instance + let mut invoke: Optioni32> = None; + let mut box_type = String::new(); + if let Some(name) = crate::runtime::plugin_loader_unified::get_global_plugin_host() + .read().ok() + .and_then(|h| h.config_ref().map(|cfg| cfg.box_types.clone())) + .and_then(|m| m.into_iter().find(|(_k,v)| *v == (type_id as u32)).map(|(k,_v)| k)) + { + box_type = name; + if let Ok(host) = crate::runtime::get_global_plugin_host().read() { + if let Ok(b) = host.create_box(&box_type, &[]) { + if let Some(p) = b.as_any().downcast_ref::() { invoke = Some(p.inner.invoke_fn); } + } + } + } + if invoke.is_none() { + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "no_invoke", "type_id": type_id}), "hostcall", ""); + return 0; + } + let method_id: u32 = 0; let instance_id: u32 = 0; + // Build TLV from a1/a2 + let nargs = argc.max(0) as usize; + let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(nargs as u16); + let mut encode_val = |h: i64| { + if h > 0 { + if let Some(obj) = crate::jit::rt::handles::get(h as u64) { + if let Some(p) = obj.as_any().downcast_ref::() { + let host = crate::runtime::get_global_plugin_host(); + if let Ok(hg) = host.read() { + if p.box_type == "StringBox" { + if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { + if let Some(s) = sb.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; } + } + } else if p.box_type == "IntegerBox" { + if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { + if let Some(i) = ibx.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; } + } + } + } + crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id()); + return; + } + } + } + crate::runtime::plugin_ffi_common::encode::i64(&mut buf, h); + }; + if nargs >= 1 { encode_val(a1); } + if nargs >= 2 { encode_val(a2); } + // Invoke + let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len(); + let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) }; + if rc != 0 { events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "invoke_failed", "type_id": type_id}), "hostcall", ""); return 0; } + if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) { + if tag == 8 && payload.len()==8 { + let mut t=[0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i=[0u8;4]; i.copy_from_slice(&payload[4..8]); + let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); + let pb = crate::runtime::plugin_loader_v2::make_plugin_box_v2(box_type.clone(), r_type, r_inst, invoke.unwrap()); + let arc: std::sync::Arc = std::sync::Arc::new(pb); + let h = crate::jit::rt::handles::to_handle(arc); + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "box_type": box_type, "type_id": type_id, "argc": nargs, "handle": h}), "hostcall", ""); + return h as i64; + } + } + events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "decode_failed", "type_id": type_id}), "hostcall", ""); + 0 +} + +// ---- Handle helpers ---- +#[cfg(feature = "cranelift-jit")] +pub(super) extern "C" fn nyash_handle_of(v: i64) -> i64 { + // If already a positive handle, pass through + if v > 0 { return v; } + // Otherwise interpret as legacy param index and convert BoxRef -> handle + if v >= 0 { + let idx = v as usize; + let mut out: i64 = 0; + crate::jit::rt::with_legacy_vm_args(|args| { + if let Some(crate::backend::vm::VMValue::BoxRef(b)) = args.get(idx) { + let arc: std::sync::Arc = std::sync::Arc::from(b.clone()); + out = crate::jit::rt::handles::to_handle(arc) as i64; + } + }); + return out; + } + 0 +} + // ---- Math (native f64) ---- #[cfg(feature = "cranelift-jit")] pub(super) extern "C" fn nyash_math_sin_f64(x: f64) -> f64 { x.sin() } @@ -243,7 +361,7 @@ pub(super) extern "C" fn nyash_plugin_invoke_name_call_i64(argc: i64, a0: i64, a nyash_plugin_invoke_name_common_i64("call", argc, a0, a1, a2) } #[cfg(feature = "cranelift-jit")] -fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, _a1: i64, _a2: i64) -> i64 { +fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, a1: i64, a2: i64) -> i64 { // Resolve receiver let mut instance_id: u32 = 0; let mut type_id: u32 = 0; @@ -280,60 +398,87 @@ fn nyash_plugin_invoke_name_common_i64(method: &str, argc: i64, a0: i64, _a1: i6 } }); } - if invoke.is_none() { return 0; } + if invoke.is_none() { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "error": "no_invoke"}), "hostcall", ""); return 0; } let box_type = box_type.unwrap_or_default(); // Resolve method_id via PluginHost - let mh = if let Ok(host) = plugin_loader_unified::get_global_plugin_host().read() { host.resolve_method(&box_type, method) } else { return 0 }; - let method_id = match mh { Ok(h) => h.method_id, Err(_) => return 0 } as u32; - // Build TLV args from legacy (skip receiver=pos0) - let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header((argc.saturating_sub(1).max(0) as u16)); - let mut add_from_legacy = |pos: usize| { - crate::jit::rt::with_legacy_vm_args(|args| { - if let Some(v) = args.get(pos) { - use crate::backend::vm::VMValue as V; - match v { - V::String(s) => crate::runtime::plugin_ffi_common::encode::string(&mut buf, s), - V::Integer(i) => crate::runtime::plugin_ffi_common::encode::i64(&mut buf, *i), - V::Float(f) => crate::runtime::plugin_ffi_common::encode::f64(&mut buf, *f), - V::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut buf, *b), - V::BoxRef(b) => { - if let Some(p) = b.as_any().downcast_ref::() { - let host = crate::runtime::get_global_plugin_host(); - if let Ok(hg) = host.read() { - if p.box_type == "StringBox" { - if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { - if let Some(s) = sb.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; } - } - } else if p.box_type == "IntegerBox" { - if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { - if let Some(i) = ibx.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; } - } - } + let mh = if let Ok(host) = plugin_loader_unified::get_global_plugin_host().read() { host.resolve_method(&box_type, method) } else { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "host_read_failed"}), "hostcall", ""); return 0 }; + let method_id = match mh { Ok(h) => h.method_id, Err(_) => { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "resolve_failed"}), "hostcall", ""); return 0 } } as u32; + // Build TLV args from a1/a2 preferring handles; fallback to legacy (skip receiver=pos0) + let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(argc.saturating_sub(1).max(0) as u16); + let mut encode_arg = |val: i64, pos: usize| { + let mut appended = false; + if val > 0 { + if let Some(obj) = crate::jit::rt::handles::get(val as u64) { + if let Some(p) = obj.as_any().downcast_ref::() { + let host = crate::runtime::get_global_plugin_host(); + if let Ok(hg) = host.read() { + if p.box_type == "StringBox" { + if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { + if let Some(s) = sb.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); appended = true; } + } + } else if p.box_type == "IntegerBox" { + if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { + if let Some(i) = ibx.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); appended = true; } } - crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id()); - } else { - let s = b.to_string_box().value; crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s) } } - _ => {} + if !appended { crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id()); appended = true; } } } - }); + } + if !appended { + // Fallback: encode from legacy VM args at position + crate::jit::rt::with_legacy_vm_args(|args| { + if let Some(v) = args.get(pos) { + use crate::backend::vm::VMValue as V; + match v { + V::String(s) => crate::runtime::plugin_ffi_common::encode::string(&mut buf, s), + V::Integer(i) => crate::runtime::plugin_ffi_common::encode::i64(&mut buf, *i), + V::Float(f) => crate::runtime::plugin_ffi_common::encode::f64(&mut buf, *f), + V::Bool(b) => crate::runtime::plugin_ffi_common::encode::bool(&mut buf, *b), + V::BoxRef(b) => { + if let Some(p) = b.as_any().downcast_ref::() { + let host = crate::runtime::get_global_plugin_host(); + if let Ok(hg) = host.read() { + if p.box_type == "StringBox" { + if let Ok(Some(sb)) = hg.invoke_instance_method("StringBox", "toUtf8", p.instance_id(), &[]) { + if let Some(s) = sb.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); return; } + } + } else if p.box_type == "IntegerBox" { + if let Ok(Some(ibx)) = hg.invoke_instance_method("IntegerBox", "get", p.instance_id(), &[]) { + if let Some(i) = ibx.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); return; } + } + } + } + crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.instance_id()); + } else { + let s = b.to_string_box().value; crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s) + } + } + _ => {} + } + } else { + // No legacy arg: encode raw i64 as last resort + crate::runtime::plugin_ffi_common::encode::i64(&mut buf, val); + } + }); + } }; - if argc >= 2 { add_from_legacy(1); } - if argc >= 3 { add_from_legacy(2); } + if argc >= 2 { encode_arg(a1, 1); } + if argc >= 3 { encode_arg(a2, 2); } let mut out = vec![0u8; 4096]; let mut out_len: usize = out.len(); let rc = unsafe { invoke.unwrap()(type_id as u32, method_id, instance_id, buf.as_ptr(), buf.len(), out.as_mut_ptr(), &mut out_len) }; - if rc != 0 { return 0; } + if rc != 0 { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "invoke_failed"}), "hostcall", ""); return 0; } let out_slice = &out[..out_len]; if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(out_slice) { match tag { 3 => { if payload.len()==8 { let mut b=[0u8;8]; b.copy_from_slice(payload); return i64::from_le_bytes(b); } } 1 => { return if crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false) { 1 } else { 0 }; } 5 => { if std::env::var("NYASH_JIT_NATIVE_F64").ok().as_deref()==Some("1") { if payload.len()==8 { let mut b=[0u8;8]; b.copy_from_slice(payload); let f=f64::from_le_bytes(b); return f as i64; } } } - _ => {} + _ => { events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "warn": "first_tlv_not_primitive_or_handle", "tag": tag}), "hostcall", ""); } } } + events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "decode_failed"}), "hostcall", ""); 0 } diff --git a/src/jit/manager.rs b/src/jit/manager.rs index 1432a37c..b9beae02 100644 --- a/src/jit/manager.rs +++ b/src/jit/manager.rs @@ -137,6 +137,18 @@ impl JitManager { /// 10_c: execute compiled function if present (stub: empty args). Returns Some(VMValue) if JIT path was taken. pub fn execute_compiled(&mut self, func: &str, ret_ty: &crate::mir::MirType, args: &[crate::backend::vm::VMValue]) -> Option { + // Strict/Fail‑FastモードではJITは"コンパイル専用"(実行しない) + if std::env::var("NYASH_JIT_STRICT").ok().as_deref() == Some("1") { + // 観測のためイベントだけ出す + crate::jit::events::emit_runtime( + serde_json::json!({ + "id": "jit_skip_execute_strict", + "func": func + }), + "jit", func + ); + return None; + } if let Some(h) = self.handle_of(func) { // Expose args to both legacy VM hostcalls and new JIT ABI TLS crate::jit::rt::set_legacy_vm_args(args); diff --git a/src/mir/builder.rs b/src/mir/builder.rs index d3b4e5bd..56d71323 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -14,6 +14,36 @@ use super::slot_registry::resolve_slot_by_type_name; use crate::ast::{ASTNode, LiteralValue, BinaryOperator}; use std::collections::HashMap; use std::collections::HashSet; +use std::fs; + +fn resolve_include_path_builder(filename: &str) -> String { + // If relative path provided, keep as is + if filename.starts_with("./") || filename.starts_with("../") { + return filename.to_string(); + } + // Try nyash.toml roots: key/rest + let parts: Vec<&str> = filename.splitn(2, '/').collect(); + if parts.len() == 2 { + let root = parts[0]; + let rest = parts[1]; + let cfg_path = "nyash.toml"; + if let Ok(toml_str) = fs::read_to_string(cfg_path) { + if let Ok(toml_val) = toml::from_str::(&toml_str) { + if let Some(include) = toml_val.get("include") { + if let Some(roots) = include.get("roots").and_then(|v| v.as_table()) { + if let Some(root_path) = roots.get(root).and_then(|v| v.as_str()) { + let mut base = root_path.to_string(); + if !base.ends_with('/') && !base.ends_with('\\') { base.push('/'); } + return format!("{}{}", base, rest); + } + } + } + } + } + } + // Default to ./filename + format!("./{}", filename) +} fn builder_debug_enabled() -> bool { std::env::var("NYASH_BUILDER_DEBUG").is_ok() @@ -469,6 +499,35 @@ impl MirBuilder { self.build_await_expression(*expression.clone()) }, + ASTNode::Include { filename, .. } => { + // Resolve and read included file + let mut path = resolve_include_path_builder(&filename); + if std::path::Path::new(&path).is_dir() { + path = format!("{}/index.nyash", path.trim_end_matches('/')); + } else if std::path::Path::new(&path).extension().is_none() { + path.push_str(".nyash"); + } + let content = fs::read_to_string(&path) + .map_err(|e| format!("Include read error '{}': {}", filename, e))?; + // Parse to AST + let included_ast = crate::parser::NyashParser::parse_from_string(&content) + .map_err(|e| format!("Include parse error '{}': {:?}", filename, e))?; + // Find first static box name + let mut box_name: Option = None; + if let crate::ast::ASTNode::Program { statements, .. } = &included_ast { + for st in statements { + if let crate::ast::ASTNode::BoxDeclaration { name, is_static, .. } = st { + if *is_static { box_name = Some(name.clone()); break; } + } + } + } + let bname = box_name.ok_or_else(|| format!("Include target '{}' has no static box", filename))?; + // Lower included AST into current MIR (register types/methods) + let _ = self.build_expression(included_ast)?; + // Return a new instance of included box (no args) + self.build_new_expression(bname, vec![]) + }, + _ => { Err(format!("Unsupported AST node type: {:?}", ast)) } @@ -1140,6 +1199,16 @@ impl MirBuilder { fn build_new_expression(&mut self, class: String, arguments: Vec) -> Result { // Phase 9.78a: Unified Box creation using NewBox instruction + // Optimization: Primitive wrappers → emit Const directly when possible + if class == "IntegerBox" && arguments.len() == 1 { + if let ASTNode::Literal { value: LiteralValue::Integer(n), .. } = arguments[0].clone() { + let dst = self.value_gen.next(); + self.emit_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(n) })?; + self.value_types.insert(dst, super::MirType::Integer); + return Ok(dst); + } + } + // First, evaluate all arguments to get their ValueIds let mut arg_values = Vec::new(); for arg in arguments { @@ -1169,17 +1238,18 @@ impl MirBuilder { // Record origin for optimization: dst was created by NewBox of class self.value_origin_newbox.insert(dst, class.clone()); - // Immediately call birth(...) on the created instance to run constructor semantics. - // birth typically returns void; we don't capture the result here (dst: None) - let birt_mid = resolve_slot_by_type_name(&class, "birth"); - self.emit_box_or_plugin_call( - None, - dst, - "birth".to_string(), - birt_mid, - arg_values, - EffectMask::READ.add(Effect::ReadHeap), - )?; + // For plugin/builtin boxes, call birth(...). For user-defined boxes, skip (InstanceBox already constructed) + if !self.user_defined_boxes.contains(&class) { + let birt_mid = resolve_slot_by_type_name(&class, "birth"); + self.emit_box_or_plugin_call( + None, + dst, + "birth".to_string(), + birt_mid, + arg_values, + EffectMask::READ.add(Effect::ReadHeap), + )?; + } Ok(dst) } diff --git a/src/parser/expressions.rs b/src/parser/expressions.rs index 95ce103a..34a2c79a 100644 --- a/src/parser/expressions.rs +++ b/src/parser/expressions.rs @@ -286,6 +286,10 @@ impl NyashParser { /// 基本式をパース: リテラル、変数、括弧、this、new fn parse_primary(&mut self) -> Result { match &self.current_token().token_type { + TokenType::INCLUDE => { + // Allow include as an expression: include "path" + self.parse_include() + } TokenType::STRING(s) => { let value = s.clone(); self.advance(); @@ -555,4 +559,4 @@ impl NyashParser { span: Span::unknown(), }) } -} \ No newline at end of file +} diff --git a/src/runner/modes/vm.rs b/src/runner/modes/vm.rs index 44100518..1b2e8459 100644 --- a/src/runner/modes/vm.rs +++ b/src/runner/modes/vm.rs @@ -104,10 +104,79 @@ impl NyashRunner { /// Collect Box declarations from AST and register into runtime pub(crate) fn collect_box_declarations(&self, ast: &ASTNode, runtime: &NyashRuntime) { + fn resolve_include_path(filename: &str) -> String { + if filename.starts_with("./") || filename.starts_with("../") { return filename.to_string(); } + let parts: Vec<&str> = filename.splitn(2, '/').collect(); + if parts.len() == 2 { + let root = parts[0]; let rest = parts[1]; + let cfg_path = "nyash.toml"; + if let Ok(toml_str) = std::fs::read_to_string(cfg_path) { + if let Ok(toml_val) = toml::from_str::(&toml_str) { + if let Some(include) = toml_val.get("include") { + if let Some(roots) = include.get("roots").and_then(|v| v.as_table()) { + if let Some(base) = roots.get(root).and_then(|v| v.as_str()) { + let mut b = base.to_string(); if !b.ends_with('/') && !b.ends_with('\\') { b.push('/'); } + return format!("{}{}", b, rest); + } + } + } + } + } + } + format!("./{}", filename) + } + fn walk(node: &ASTNode, runtime: &NyashRuntime) { match node { ASTNode::Program { statements, .. } => { for st in statements { walk(st, runtime); } } ASTNode::FunctionDeclaration { body, .. } => { for st in body { walk(st, runtime); } } + ASTNode::Include { filename, .. } => { + let mut path = resolve_include_path(filename); + if std::path::Path::new(&path).is_dir() { + path = format!("{}/index.nyash", path.trim_end_matches('/')); + } else if std::path::Path::new(&path).extension().is_none() { + path.push_str(".nyash"); + } + if let Ok(content) = std::fs::read_to_string(&path) { + if let Ok(inc_ast) = NyashParser::parse_from_string(&content) { + walk(&inc_ast, runtime); + } + } + } + ASTNode::Assignment { target, value, .. } => { + walk(target, runtime); walk(value, runtime); + } + ASTNode::Return { value, .. } => { if let Some(v) = value { walk(v, runtime); } } + ASTNode::Print { expression, .. } => { walk(expression, runtime); } + ASTNode::If { condition, then_body, else_body, .. } => { + walk(condition, runtime); + for st in then_body { walk(st, runtime); } + if let Some(eb) = else_body { for st in eb { walk(st, runtime); } } + } + ASTNode::Loop { condition, body, .. } => { + walk(condition, runtime); for st in body { walk(st, runtime); } + } + ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { + for st in try_body { walk(st, runtime); } + for cc in catch_clauses { for st in &cc.body { walk(st, runtime); } } + if let Some(fb) = finally_body { for st in fb { walk(st, runtime); } } + } + ASTNode::Throw { expression, .. } => { walk(expression, runtime); } + ASTNode::Local { initial_values, .. } => { + for iv in initial_values { if let Some(v) = iv { walk(v, runtime); } } + } + ASTNode::Outbox { initial_values, .. } => { + for iv in initial_values { if let Some(v) = iv { walk(v, runtime); } } + } + ASTNode::FunctionCall { arguments, .. } => { for a in arguments { walk(a, runtime); } } + ASTNode::MethodCall { object, arguments, .. } => { walk(object, runtime); for a in arguments { walk(a, runtime); } } + ASTNode::FieldAccess { object, .. } => { walk(object, runtime); } + ASTNode::New { arguments, .. } => { for a in arguments { walk(a, runtime); } } + ASTNode::BinaryOp { left, right, .. } => { walk(left, runtime); walk(right, runtime); } + ASTNode::UnaryOp { operand, .. } => { walk(operand, runtime); } + ASTNode::AwaitExpression { expression, .. } => { walk(expression, runtime); } + ASTNode::Arrow { sender, receiver, .. } => { walk(sender, runtime); walk(receiver, runtime); } + ASTNode::Nowait { expression, .. } => { walk(expression, runtime); } ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => { for (_mname, mnode) in methods { walk(mnode, runtime); } for (_ckey, cnode) in constructors { walk(cnode, runtime); } diff --git a/src/runtime/box_registry.rs b/src/runtime/box_registry.rs index 8f1d3c65..60d54596 100644 --- a/src/runtime/box_registry.rs +++ b/src/runtime/box_registry.rs @@ -1,7 +1,7 @@ //! Boxファクトリレジストリ - Box生成の中央管理 //! -//! ビルトインBoxとプラグインBoxを統一的に管理し、 -//! 透過的な置き換えを実現する +//! プラグインBoxを中心にBox生成を管理する(Plugin-First)。 +//! 旧ビルトイン経路は互換目的のAPIとして最小限に保持(テスト用途)。 use crate::box_trait::NyashBox; use crate::runtime::plugin_config::PluginConfig; @@ -10,14 +10,14 @@ use std::sync::{Arc, RwLock}; /// Box生成方法を表す列挙型 pub enum BoxProvider { - /// ビルトイン実装(Rust関数) + /// 互換用ビルトイン実装(Rust関数、現在は原則未使用) Builtin(BoxConstructor), /// プラグイン実装(プラグイン名を保持) Plugin(String), } -/// ビルトインBoxのコンストラクタ関数型 +/// 互換用ビルトインBoxのコンストラクタ関数型 pub type BoxConstructor = fn(&[Box]) -> Result, String>; /// Boxファクトリレジストリ @@ -34,7 +34,7 @@ impl BoxFactoryRegistry { } } - /// ビルトインBoxを登録 + /// 互換用ビルトインBoxを登録(通常は使用しない) pub fn register_builtin(&self, name: &str, constructor: BoxConstructor) { let mut providers = self.providers.write().unwrap(); providers.insert(name.to_string(), BoxProvider::Builtin(constructor)); diff --git a/src/runtime/plugin_loader_v2.rs b/src/runtime/plugin_loader_v2.rs index 7dfde8a0..d2ac1963 100644 --- a/src/runtime/plugin_loader_v2.rs +++ b/src/runtime/plugin_loader_v2.rs @@ -34,6 +34,8 @@ mod enabled { invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, } +// (moved: public constructor wrapper is declared after the enabled module) + /// v2 Plugin Box wrapper - temporary implementation #[derive(Debug)] pub struct PluginHandleInner { @@ -103,6 +105,11 @@ mod enabled { } } + /// Helper to construct a PluginBoxV2 from raw ids and invoke pointer safely + pub fn make_plugin_box_v2_inner(box_type: String, type_id: u32, instance_id: u32, invoke_fn: unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize) -> i32) -> PluginBoxV2 { + PluginBoxV2 { box_type, inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) } + } + #[derive(Debug, Clone)] pub struct PluginBoxV2 { pub box_type: String, @@ -1207,6 +1214,10 @@ impl PluginLoaderV2 { } } +// Public constructor wrapper for PluginBoxV2 (enabled build) +#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] +pub use self::enabled::make_plugin_box_v2_inner as make_plugin_box_v2; + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] mod stub { use crate::bid::{BidResult, BidError}; diff --git a/tools/pyc/PyCompiler.nyash b/tools/pyc/PyCompiler.nyash new file mode 100644 index 00000000..ded4fcc5 --- /dev/null +++ b/tools/pyc/PyCompiler.nyash @@ -0,0 +1,51 @@ +// Nyash-side Python compiler (Phase 10.7 C2 - minimal) +// Orchestrates: Parser(JSON) -> IR(JSON) -> Nyash source (string) + +static box PyCompiler { + parseToJson() { + // Use Nyash-only parser via PyRuntimeBox (no Rust build) + local p + p = new PythonParserNy() + return p.parse("") + } + + buildIRFromParse(json) { + // Minimal: analyze Python source from env and build IR with a constant return when possible + // Python snippet: parse code from os.getenv("NYASH_PY_CODE"), find first Return(Constant) + local os, getenv, src, ast, parsef, walkf, first_ret + local py = new PyRuntimeBox() + os = py.import("os") + getenv = os.getattr("getenv") + src = getenv.call("NYASH_PY_CODE").toString() + if (src == null || src == "") { + return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":0}]}}") + } + + ast = py.import("ast") + parsef = ast.getattr("parse") + local tree + tree = parsef.call(src) + + // Walk via Python: extract first constant return value if present + local res + res = py.eval("next((n.value.value for n in __import__('ast').walk(__import__('ast').parse(__import__('os').getenv('NYASH_PY_CODE') or '')) if isinstance(n, __import__('ast').Return) and isinstance(n.value, __import__('ast').Constant)), None)") + // If number -> return that number; if string -> quoted + local val + val = res.str() + if (val.matches("^[0-9]+$")) { + return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":" + val + "]}}") + } else if (val.length() > 0) { + return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":\"" + val + "\"}]}}") + } else { + return new StringBox("{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":0}]}}") + } + } + + irToNyashSource(irJson) { + // Build Nyash source from IR (minimal: assume IR has return_value embedded by PyIR.buildReturn) + local src + src = "static box Generated {\n main() {\n return 0\n }\n}" + // Future: parse irJson and extract return_value + return new StringBox(src) + } +} diff --git a/tools/pyc/PyIR.nyash b/tools/pyc/PyIR.nyash new file mode 100644 index 00000000..6c9462b8 --- /dev/null +++ b/tools/pyc/PyIR.nyash @@ -0,0 +1,11 @@ +// Minimal IR helper (Phase 10.7 C2) +// Builds small JSON snippets for the compiler pipeline + +static box PyIR { + buildReturn(value) { + local s + // value can be integer or string literal already + s = "{\"module\":{\"functions\":[{\"name\":\"main\",\"return_value\":" + value + "}]}}" + return new StringBox(s) + } +} diff --git a/tools/pyc/PythonParserNy.nyash b/tools/pyc/PythonParserNy.nyash new file mode 100644 index 00000000..7a4d108b --- /dev/null +++ b/tools/pyc/PythonParserNy.nyash @@ -0,0 +1,32 @@ +// Nyash-only Python parser using PyRuntimeBox (no Rust build) +static box PythonParserNy { + parse(code) { + local src + if (code == null || code.toString() == "") { + // Fallback: read from env via Python os.getenv + local os, getenv + local py = new PyRuntimeBox() + os = py.import("os") + getenv = os.getattr("getenv") + src = getenv.call("NYASH_PY_CODE").toString() + } else { + src = code.toString() + } + + // ast.dump(ast.parse(src)) + local ast, parsef, dumpf, tree, dump, dump_str + local py2 = new PyRuntimeBox() + ast = py2.import("ast") + parsef = ast.getattr("parse") + tree = parsef.call(src) + dumpf = ast.getattr("dump") + dump = dumpf.call(tree) + dump_str = dump.str() + + // Minimal JSON (escape quotes minimally by replacing \" -> \\\" ) + // Note: for robust escaping, prefer Python json.dumps in a later pass + local esc + esc = dump_str.replace("\"", "\\\"") + return new StringBox("{\"ast\":\"Module\",\"dump\":\"" + esc + "\"}") + } +} diff --git a/tools/pyc/README.md b/tools/pyc/README.md new file mode 100644 index 00000000..2cf631e6 --- /dev/null +++ b/tools/pyc/README.md @@ -0,0 +1,24 @@ +# Nyash Python Compiler (Phase 10.7 Workbench) + +目的: Parser(プラグイン) → Nyash側コンパイラ → Nyashソース → 既存AOT までの最短ルートを、Nyashだけで段階実装する作業場。 + +## 構成 +- `pyc.nyash` — エントリ(最小パイプライン実行) +- `PyCompiler.nyash` — Nyash側コンパイラ本体(C2で拡張) +- `PyIR.nyash` — IR生成/整形のヘルパ(最小) + +## 使い方(最小) +```bash +# 1) NYASH_PY_CODE に Python コードを入れる(Parserプラグインが拾う) +NYASH_PY_CODE=$'def main():\n return 0' \ + ./target/release/nyash --backend vm tools/pyc/pyc.nyash +``` + +出力 +- Parser JSON(dump/counts/unsupported) +- 生成された Nyash ソース(現状は最小: return 0) + +## 次の拡張 +- Parser JSON → IR(JSON) への変換(def/return最小) +- IR → Nyash 生成(If/Return/Assign へ拡張) +- All-or-Nothing 運用(unsupported_nodes を見て Strict に弾くスイッチ) diff --git a/tools/pyc/pyc.nyash b/tools/pyc/pyc.nyash new file mode 100644 index 00000000..b77a6b29 --- /dev/null +++ b/tools/pyc/pyc.nyash @@ -0,0 +1,24 @@ +// Nyash Python Compiler entry (Phase 10.7 workbench) +// Nyash-only pipeline: Parser/Compiler are implemented in Nyash using PyRuntimeBox +// Usage: +// NYASH_PY_CODE=$'def main():\n return 0' ./target/release/nyash --backend vm tools/pyc/pyc.nyash + +static box Main { + main() { + // Load modules via include (returns module boxes) + local PyIR = include "tools/pyc/PyIR.nyash" + local Parser = include "tools/pyc/PythonParserNy.nyash" + local Compiler = include "tools/pyc/PyCompiler.nyash" + + local json, ir, src + + // Skip echo of source to avoid plugin toString issues + + json = Compiler.parseToJson() + + ir = Compiler.buildIRFromParse(json) + + src = Compiler.irToNyashSource(ir) + return 0 + } +} diff --git a/tools/smoke_aot_vs_vm.sh b/tools/smoke_aot_vs_vm.sh index b81a525e..34fd124a 100644 --- a/tools/smoke_aot_vs_vm.sh +++ b/tools/smoke_aot_vs_vm.sh @@ -17,6 +17,7 @@ TEST_FILES=( "examples/aot_min_string_len.nyash" "examples/aot_string_len_simple.nyash" "examples/jit_stats_bool_ret.nyash" + "examples/aot_py_min_chain.nyash" ) # Counter for results @@ -35,7 +36,7 @@ run_test() { # Run with VM backend echo -n " VM execution... " - if NYASH_USE_PLUGIN_BUILTINS=1 ./target/release/nyash --backend vm "$test_file" > /tmp/${test_name}_vm.out 2>&1; then + if NYASH_USE_PLUGIN_BUILTINS=1 NYASH_PY_AUTODECODE=1 ./target/release/nyash --backend vm "$test_file" > /tmp/${test_name}_vm.out 2>&1; then VM_RESULT=$(tail -1 /tmp/${test_name}_vm.out | grep -oP 'Result: \K.*' || echo "NO_RESULT") echo "OK (Result: $VM_RESULT)" else