📚 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:
@ -98,7 +98,7 @@
|
||||
*
|
||||
* ## ⚠️ 注意
|
||||
* - キーは自動的に文字列変換される
|
||||
* - スレッドセーフ (Arc<Mutex>使用)
|
||||
* - スレッドセーフ (Arc<RwLock>使用)
|
||||
* - 大量データ格納時はメモリ使用量に注意
|
||||
* - 存在しないキーの取得は "Key not found" メッセージ返却
|
||||
*/
|
||||
|
||||
@ -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, .. } => {
|
||||
|
||||
@ -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
4
src/jit/extern/birth.rs
vendored
Normal 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
4
src/jit/extern/handles.rs
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
//! Handle-related extern symbol names
|
||||
|
||||
pub const SYM_HANDLE_OF: &str = "nyash.handle.of";
|
||||
|
||||
3
src/jit/extern/mod.rs
vendored
3
src/jit/extern/mod.rs
vendored
@ -5,4 +5,5 @@
|
||||
//! these externs once call emission is added.
|
||||
|
||||
pub mod collections;
|
||||
|
||||
pub mod handles;
|
||||
pub mod birth;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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/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);
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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};
|
||||
|
||||
Reference in New Issue
Block a user