📚 Phase 12: Nyashスクリプトプラグインシステム設計と埋め込みVM構想

## 主な成果
- Nyashスクリプトでプラグイン作成可能という革命的発見
- C ABI制約の分析と埋め込みVMによる解決策
- MIR/VM/JIT層での箱引数サポートの詳細分析

## ドキュメント作成
- Phase 12基本構想(README.md)
- Gemini/Codex先生の技術分析
- C ABIとの整合性問題と解決策
- 埋め込みVM実装ロードマップ
- 箱引数サポートの技術詳細

## 重要な洞察
- 制約は「リンク時にC ABI必要」のみ
- 埋め込みVMでMIRバイトコード実行により解決可能
- Nyashスクリプト→C ABIプラグイン変換が実現可能

Everything is Box → Everything is Plugin → Everything is Possible!
This commit is contained in:
Moe Charm
2025-08-30 22:52:16 +09:00
parent 7a0f9bd432
commit c13d9c045e
82 changed files with 5842 additions and 138 deletions

View File

@ -98,7 +98,7 @@
*
* ## ⚠️ 注意
* - キーは自動的に文字列変換される
* - スレッドセーフ (Arc<Mutex>使用)
* - スレッドセーフ (Arc<RwLock>使用)
* - 大量データ格納時はメモリ使用量に注意
* - 存在しないキーの取得は "Key not found" メッセージ返却
*/

View File

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

View File

@ -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::Value>(&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<Box<dyn NyashBox>, 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<String> = 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::<crate::instance_v2::InstanceBox>()
.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<Box<dyn NyashBox>, RuntimeError> {
@ -111,4 +216,4 @@ impl NyashInterpreter {
Ok(Box::new(VoidBox::new()))
}
}
}

4
src/jit/extern/birth.rs vendored Normal file
View File

@ -0,0 +1,4 @@
//! Generic birth hostcall symbol names
pub const SYM_BOX_BIRTH_H: &str = "nyash.box.birth_h";

4
src/jit/extern/handles.rs vendored Normal file
View File

@ -0,0 +1,4 @@
//! Handle-related extern symbol names
pub const SYM_HANDLE_OF: &str = "nyash.handle.of";

View File

@ -5,4 +5,5 @@
//! these externs once call emission is added.
pub mod collections;
pub mod handles;
pub mod birth;

View File

@ -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<dyn crate::box_trait::NyashBox> = 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<usize, cranelift_codegen::ir::StackSlot>,
block_param_counts: std::collections::HashMap<usize, usize>,
pub stats: (u64,u64,u64,u64,u64),
pub object_bytes: Option<Vec<u8>>,
}
@ -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);

View File

@ -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_htype_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 availablea1, 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")

View File

@ -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<dyn crate::box_trait::NyashBox> = 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", "<jit>");
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", "<jit>");
}
}
} else {
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_h", "error": "type_map_failed", "type_id": tid}), "hostcall", "<jit>");
}
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: Option<unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize)->i32> = 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::<PluginBoxV2>() { 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", "<jit>");
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::<PluginBoxV2>() {
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::box_trait::StringBox>() { 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::box_trait::IntegerBox>() { 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", "<jit>"); 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<dyn crate::box_trait::NyashBox> = 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", "<jit>");
return h as i64;
}
}
events::emit_runtime(serde_json::json!({"id": "nyash.box.birth_i64", "error": "decode_failed", "type_id": type_id}), "hostcall", "<jit>");
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<dyn crate::box_trait::NyashBox> = 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", "<jit>"); 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::<PluginBoxV2>() {
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::box_trait::StringBox>() { 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::box_trait::IntegerBox>() { 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", "<jit>"); 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", "<jit>"); 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::<PluginBoxV2>() {
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::box_trait::StringBox>() { 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::box_trait::IntegerBox>() { 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::<PluginBoxV2>() {
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::box_trait::StringBox>() { 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::box_trait::IntegerBox>() { 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", "<jit>"); 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", "<jit>"); }
}
}
events::emit_runtime(serde_json::json!({"id": "plugin_invoke_by_name", "method": method, "box_type": box_type, "error": "decode_failed"}), "hostcall", "<jit>");
0
}

View File

@ -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<crate::backend::vm::VMValue> {
// Strict/FailFastモードでは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);

View File

@ -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::Value>(&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<String> = 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<ASTNode>) -> Result<ValueId, String> {
// 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)
}

View File

@ -286,6 +286,10 @@ impl NyashParser {
/// 基本式をパース: リテラル、変数、括弧、this、new
fn parse_primary(&mut self) -> Result<ASTNode, ParseError> {
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(),
})
}
}
}

View File

@ -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::Value>(&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); }

View File

@ -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<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, 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));

View File

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