wip(phase15): AOT修正作業中 - Nyプラグインと標準ライブラリ実装

Phase 15のAOT/ネイティブビルド修正作業を継続中。
ChatGPTによるstd実装とプラグインシステムの改修を含む。

主な変更点:
- apps/std/: string.nyashとarray.nyashの標準ライブラリ追加
- apps/smokes/: stdライブラリのスモークテスト追加
- プラグインローダーv2の実装改修
- BoxCallのハンドル管理改善
- JIT hostcall registryの更新
- ビルドスクリプト(build_aot.sh, build_llvm.sh)の調整

まだ修正作業中のため、一部の機能は不完全な状態。

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tomoaki
2025-09-06 06:24:08 +09:00
parent 020990463d
commit 7d88c04c0e
107 changed files with 4811 additions and 373 deletions

View File

@ -264,6 +264,10 @@ impl VM {
let mut next_block: Option<BasicBlockId> = None;
loop {
// Reset per-block control-flow decisions to avoid carrying over stale state
// from a previous block (which could cause infinite loops on if/return).
should_return = None;
next_block = None;
if let Some(block) = function.blocks.get(&current_block) {
for instruction in &block.instructions {
match self.execute_instruction(instruction)? {

View File

@ -52,6 +52,19 @@ impl VM {
// Debug logging if enabled
let debug_boxcall = std::env::var("NYASH_VM_DEBUG_BOXCALL").is_ok();
// Super-early fast-path: ArrayBox len/length (avoid competing branches)
if let VMValue::BoxRef(arc_box) = &recv {
if arc_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().is_some() {
if method == "len" || method == "length" || (method_id.is_some() && method_id == crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "len")) {
if let Some(arr) = arc_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let out = arr.length();
if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Ok(ControlFlow::Continue);
}
}
}
}
// Fast-path: ConsoleBox.readLine — provide safe stdin fallback with EOF→Void
if let VMValue::BoxRef(arc_box) = &recv {
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
@ -95,12 +108,20 @@ impl VM {
// Explicit fast-paths
if let VMValue::BoxRef(arc_box) = &recv {
// ArrayBox get/set
// ArrayBox get/set/length
if arc_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().is_some() {
let get_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "get");
let set_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "set");
let len_slot = crate::mir::slot_registry::resolve_slot_by_type_name("ArrayBox", "len");
let is_get = (method_id.is_some() && method_id == get_slot) || method == "get";
let is_set = (method_id.is_some() && method_id == set_slot) || method == "set";
let is_len = (method_id.is_some() && method_id == len_slot) || method == "len" || method == "length";
if is_len {
let arr = arc_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().unwrap();
let out = arr.length();
if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); }
return Ok(ControlFlow::Continue);
}
if is_get && args.len() >= 1 {
let idx_val = self.get_value(args[0])?;
let idx_box = idx_val.to_nyash_box();

View File

@ -120,7 +120,10 @@ impl VMValue {
/// Convert from NyashBox to VMValue
pub fn from_nyash_box(nyash_box: Box<dyn crate::box_trait::NyashBox>) -> VMValue {
if let Some(int_box) = nyash_box.as_any().downcast_ref::<IntegerBox>() {
if nyash_box.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() {
// Treat NullBox as Void in VMValue to align with `null` literal semantics
VMValue::Void
} else if let Some(int_box) = nyash_box.as_any().downcast_ref::<IntegerBox>() {
VMValue::Integer(int_box.value)
} else if let Some(bool_box) = nyash_box.as_any().downcast_ref::<BoolBox>() {
VMValue::Bool(bool_box.value)

View File

@ -118,11 +118,11 @@ impl UnifiedBoxRegistry {
// Prefer plugin-builtins when enabled and provider is available in v2 registry
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
use crate::runtime::{get_global_registry, BoxProvider};
// Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: ArrayBox,MapBox)
// Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none)
let allow: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
list.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
} else {
vec!["ArrayBox".into(), "MapBox".into()]
vec![]
};
if allow.iter().any(|t| t == name) {
let v2 = get_global_registry();

View File

@ -427,6 +427,8 @@ mod tests {
run_task: None,
load_ny_plugins: false,
parser_ny: false,
ny_parser_pipe: false,
json_file: None,
};
assert_eq!(config.backend, "interpreter");

View File

@ -199,6 +199,7 @@ impl NyashInterpreter {
// Local method on instance
if let Some(method_ast) = instance.get_method(method) {
if let ASTNode::FunctionDeclaration { params, body, .. } = method_ast.clone() {
eprintln!("[dbg] enter instance method {}.{}", instance.class_name, method);
// Evaluate args in current context
let mut arg_values = Vec::new();
for a in arguments {
@ -231,6 +232,7 @@ impl NyashInterpreter {
}
}
self.restore_local_vars(saved);
eprintln!("[dbg] exit instance method {}.{}", instance.class_name, method);
return Some(Ok(result));
} else {
return Some(Err(RuntimeError::InvalidOperation { message: format!("Method '{}' is not a valid function declaration", method) }));

View File

@ -234,19 +234,23 @@ impl NyashInterpreter {
let is_true = self.is_truthy(&condition_value);
if is_true {
eprintln!("[dbg] if-then enter");
for statement in then_body {
self.execute_statement(statement)?;
if !matches!(self.control_flow, super::ControlFlow::None) {
break;
}
}
eprintln!("[dbg] if-then exit");
} else if let Some(else_statements) = else_body {
eprintln!("[dbg] if-else enter");
for statement in else_statements {
self.execute_statement(statement)?;
if !matches!(self.control_flow, super::ControlFlow::None) {
break;
}
}
eprintln!("[dbg] if-else exit");
}
Ok(Box::new(VoidBox::new()))

View File

@ -24,6 +24,7 @@ fn ensure_default() {
// Read-only defaults
for s in [
"nyash.array.len_h",
"nyash.string.len_h",
"nyash.any.length_h",
"nyash.any.is_empty_h",
"nyash.map.size_h",
@ -54,6 +55,7 @@ fn ensure_default() {
r.sig.entry("nyash.array.get_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::Handle });
r.sig.entry("nyash.array.len_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
// String helpers
r.sig.entry("nyash.string.len_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle], ret: ArgKind::I64 });
r.sig.entry("nyash.string.charCodeAt_h".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::I64], ret: ArgKind::I64 });
r.sig.entry("nyash.string.concat_hh".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::Handle });
r.sig.entry("nyash.semantics.add_hh".to_string()).or_default().push(Signature { args: vec![ArgKind::Handle, ArgKind::Handle], ret: ArgKind::Handle });

View File

@ -80,26 +80,329 @@ impl IRBuilder for ObjectBuilder {
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
if let Some(b) = self.entry_block { fb.switch_to_block(b); }
fb.finalize();
let obj_id = self.module.declare_function(self.current_name.as_deref().unwrap_or("jit_aot"), cranelift_module::Linkage::Local, &self.ctx.func.signature).expect("declare func");
self.module.define_function(obj_id, &mut self.ctx).expect("define");
self.module.clear_context(&mut self.ctx);
let finished = std::mem::replace(&mut self.module, Self::fresh_module());
let product = finished.finish();
self.object_bytes = Some(product.emit().expect("emit object"));
// Export as ny_main so that nyrt can locate the entrypoint when linking AOT objects
let obj_id = self.module.declare_function("ny_main", cranelift_module::Linkage::Export, &self.ctx.func.signature).expect("declare func");
self.module.define_function(obj_id, &mut self.ctx).expect("define");
self.module.clear_context(&mut self.ctx);
let finished = std::mem::replace(&mut self.module, Self::fresh_module());
let product = finished.finish();
self.object_bytes = Some(product.emit().expect("emit object"));
}
fn prepare_signature_i64(&mut self, argc: usize, has_ret: bool) { self.desired_argc = argc; self.desired_has_ret = has_ret; }
fn prepare_signature_typed(&mut self, _params: &[ParamKind], _ret_is_f64: bool) { self.typed_sig_prepared = true; }
fn emit_param_i64(&mut self, index: usize) { if let Some(v) = self.entry_param(index) { self.value_stack.push(v); } }
fn emit_const_i64(&mut self, val: i64) { use cranelift_codegen::ir::types; use cranelift_frontend::FunctionBuilder; let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let v = fb.ins().iconst(types::I64, val); self.value_stack.push(v); self.stats.0 += 1; }
fn emit_const_f64(&mut self, val: f64) { use cranelift_frontend::FunctionBuilder; use cranelift_codegen::ir::types; let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc); let v = fb.ins().f64const(val); self.value_stack.push(v); }
fn emit_binop(&mut self, _op: super::BinOpKind) { self.stats.1 += 1; }
fn emit_compare(&mut self, _op: super::CmpKind) { self.stats.2 += 1; }
fn emit_const_i64(&mut self, val: i64) {
use cranelift_codegen::ir::types;
use cranelift_frontend::FunctionBuilder;
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 v = fb.ins().iconst(types::I64, val);
self.value_stack.push(v);
self.stats.0 += 1;
}
fn emit_const_f64(&mut self, val: f64) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
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 v = fb.ins().f64const(val);
self.value_stack.push(v);
}
fn emit_binop(&mut self, op: super::BinOpKind) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
if self.value_stack.len() < 2 { return; }
let mut rhs = self.value_stack.pop().unwrap();
let mut lhs = self.value_stack.pop().unwrap();
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); }
// Ensure i64 operands
if fb.func.dfg.value_type(lhs) != types::I64 { lhs = fb.ins().fcvt_to_sint(types::I64, lhs); }
if fb.func.dfg.value_type(rhs) != types::I64 { rhs = fb.ins().fcvt_to_sint(types::I64, rhs); }
let res = match op {
super::BinOpKind::Add => fb.ins().iadd(lhs, rhs),
super::BinOpKind::Sub => fb.ins().isub(lhs, rhs),
super::BinOpKind::Mul => fb.ins().imul(lhs, rhs),
super::BinOpKind::Div => fb.ins().sdiv(lhs, rhs),
super::BinOpKind::Mod => fb.ins().srem(lhs, rhs),
};
self.value_stack.push(res);
self.stats.1 += 1;
}
fn emit_compare(&mut self, op: super::CmpKind) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
if self.value_stack.len() < 2 { return; }
let mut rhs = self.value_stack.pop().unwrap();
let mut lhs = self.value_stack.pop().unwrap();
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); }
// Ensure i64 operands
if fb.func.dfg.value_type(lhs) != types::I64 { lhs = fb.ins().fcvt_to_sint(types::I64, lhs); }
if fb.func.dfg.value_type(rhs) != types::I64 { rhs = fb.ins().fcvt_to_sint(types::I64, rhs); }
let cc = match op {
super::CmpKind::Eq => IntCC::Equal,
super::CmpKind::Ne => IntCC::NotEqual,
super::CmpKind::Lt => IntCC::SignedLessThan,
super::CmpKind::Le => IntCC::SignedLessThanOrEqual,
super::CmpKind::Gt => IntCC::SignedGreaterThan,
super::CmpKind::Ge => IntCC::SignedGreaterThanOrEqual,
};
let b1 = fb.ins().icmp(cc, lhs, rhs);
let one = fb.ins().iconst(types::I64, 1);
let zero = fb.ins().iconst(types::I64, 0);
let sel = fb.ins().select(b1, one, zero);
self.value_stack.push(sel);
self.stats.2 += 1;
}
fn emit_jump(&mut self) { self.stats.3 += 1; }
fn emit_branch(&mut self) { self.stats.3 += 1; }
fn emit_return(&mut self) { self.stats.4 += 1; }
fn ensure_local_i64(&mut self, _index: usize) {}
fn store_local_i64(&mut self, _index: usize) {}
fn load_local_i64(&mut self, _index: usize) {}
fn emit_return(&mut self) {
use cranelift_frontend::FunctionBuilder;
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); }
if self.desired_has_ret {
if self.desired_ret_is_f64 {
use cranelift_codegen::ir::types;
let v = if let Some(v) = self.value_stack.pop() { v } else { fb.ins().f64const(0.0) };
// Coerce i64 to f64 if needed
let v2 = if fb.func.dfg.value_type(v) != types::F64 { fb.ins().fcvt_from_sint(types::F64, v) } else { v };
fb.ins().return_(&[v2]);
} else {
use cranelift_codegen::ir::types;
let v = if let Some(v) = self.value_stack.pop() { v } else { fb.ins().iconst(types::I64, 0) };
let v2 = if fb.func.dfg.value_type(v) != types::I64 { fb.ins().fcvt_to_sint(types::I64, v) } else { v };
fb.ins().return_(&[v2]);
}
} else {
fb.ins().return_(&[]);
}
self.stats.4 += 1;
}
fn ensure_local_i64(&mut self, index: usize) {
use cranelift_codegen::ir::StackSlotData;
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(cranelift_codegen::ir::StackSlotKind::ExplicitSlot, 8));
self.local_slots.insert(index, slot);
}
fn store_local_i64(&mut self, index: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
if let Some(mut v) = self.value_stack.pop() {
if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); }
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); }
// Coerce to i64 if needed
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) = self.local_slots.get(&index) { fb.ins().stack_store(v, slot, 0); }
}
}
fn load_local_i64(&mut self, index: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
if !self.local_slots.contains_key(&index) { self.ensure_local_i64(index); }
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); }
if let Some(&slot) = self.local_slots.get(&index) {
let v = fb.ins().stack_load(types::I64, slot, 0);
self.value_stack.push(v);
}
}
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()); } } }
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); }
fn ensure_block_params_i64(&mut self, index: usize, count: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
if index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let b = self.blocks[index];
let has_inst = fb.func.layout.first_inst(b).is_some();
if !has_inst {
let current = fb.func.dfg.block_params(b).len();
if count > current { for _ in current..count { let _ = fb.append_block_param(b, types::I64); } }
}
self.block_param_counts.insert(index, count);
}
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(i) = self.current_block_index { self.blocks[i] } else if let Some(e) = self.entry_block { e } else { return; };
let params = fb.func.dfg.block_params(b).to_vec();
let v = params.get(pos).copied().unwrap_or_else(|| fb.ins().iconst(types::I64, 0));
self.value_stack.push(v);
}
fn br_if_top_is_true(&mut self, then_index: usize, else_index: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
if then_index >= self.blocks.len() || else_index >= self.blocks.len() { return; }
let mut fb = FunctionBuilder::new(&mut self.ctx.func, &mut self.fbc);
let cond_val = if let Some(v) = self.value_stack.pop() { v } else { fb.ins().iconst(types::I64, 0) };
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 b1 = if fb.func.dfg.value_type(cond_val) == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, cond_val, 0) } else { fb.ins().icmp_imm(IntCC::NotEqual, cond_val, 0) };
fb.ins().brif(b1, self.blocks[then_index], &[], self.blocks[else_index], &[]);
self.stats.3 += 1;
}
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;
}
fn emit_select_i64(&mut self) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
if self.value_stack.len() < 3 { return; }
let mut else_v = self.value_stack.pop().unwrap();
let mut then_v = self.value_stack.pop().unwrap();
let cond_v = self.value_stack.pop().unwrap();
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 fb.func.dfg.value_type(cond_v) == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, cond_v, 0) } else { fb.ins().icmp_imm(IntCC::NotEqual, cond_v, 0) };
if fb.func.dfg.value_type(then_v) != types::I64 { then_v = fb.ins().fcvt_to_sint(types::I64, then_v); }
if fb.func.dfg.value_type(else_v) != types::I64 { else_v = fb.ins().fcvt_to_sint(types::I64, else_v); }
let sel = fb.ins().select(cond_b1, then_v, else_v);
self.value_stack.push(sel);
}
fn emit_host_call(&mut self, symbol: &str, argc: usize, has_ret: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder;
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 mut sig = Signature::new(self.module.isa().default_call_conv());
for _ in 0..argc { sig.params.push(AbiParam::new(types::I64)); }
if has_ret { sig.returns.push(AbiParam::new(types::I64)); }
let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare hostcall");
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::with_capacity(argc);
for _ in 0..argc { if let Some(v) = self.value_stack.pop() { args.push(v); } else { args.push(fb.ins().iconst(types::I64, 0)); } }
args.reverse();
// Ensure i64 for all
for a in args.iter_mut() { if fb.func.dfg.value_type(*a) != types::I64 { *a = fb.ins().fcvt_to_sint(types::I64, *a); } }
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &args);
if has_ret { if let Some(v) = fb.inst_results(call_inst).get(0).copied() { self.value_stack.push(v); } }
}
fn emit_host_call_typed(&mut self, symbol: &str, params: &[super::ParamKind], has_ret: bool, ret_is_f64: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder;
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 mut sig = Signature::new(self.module.isa().default_call_conv());
for &k in params {
match k { super::ParamKind::I64 => sig.params.push(AbiParam::new(types::I64)), super::ParamKind::F64 => sig.params.push(AbiParam::new(types::F64)), super::ParamKind::B1 => sig.params.push(AbiParam::new(types::I64)) }
}
if has_ret { if ret_is_f64 { sig.returns.push(AbiParam::new(types::F64)); } else { sig.returns.push(AbiParam::new(types::I64)); } }
let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare hostcall typed");
// Gather args from stack (reverse)
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::with_capacity(params.len());
for &k in params.iter().rev() {
let mut v = if let Some(v) = self.value_stack.pop() {
v
} else {
match k { super::ParamKind::I64 | super::ParamKind::B1 => fb.ins().iconst(types::I64, 0), super::ParamKind::F64 => fb.ins().f64const(0.0) }
};
// Coerce
v = match k {
super::ParamKind::I64 | super::ParamKind::B1 => { if fb.func.dfg.value_type(v) != types::I64 { fb.ins().fcvt_to_sint(types::I64, v) } else { v } },
super::ParamKind::F64 => { if fb.func.dfg.value_type(v) != types::F64 { fb.ins().fcvt_from_sint(types::F64, v) } else { v } },
};
args.push(v);
}
args.reverse();
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &args);
if has_ret {
if let Some(mut v) = fb.inst_results(call_inst).get(0).copied() {
if ret_is_f64 && fb.func.dfg.value_type(v) != types::F64 { v = fb.ins().fcvt_from_sint(types::F64, v); }
if !ret_is_f64 && fb.func.dfg.value_type(v) != types::I64 { v = fb.ins().fcvt_to_sint(types::I64, v); }
self.value_stack.push(v);
}
}
}
fn emit_host_call_fixed3(&mut self, symbol: &str, has_ret: bool) {
self.emit_host_call(symbol, 3, has_ret);
}
fn emit_string_handle_from_literal(&mut self, s: &str) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
use cranelift_frontend::FunctionBuilder;
// Pack up to 16 bytes of the literal into two u64 words
let bytes = s.as_bytes();
let mut lo: u64 = 0; let mut hi: u64 = 0;
let take = core::cmp::min(16, bytes.len());
for i in 0..take.min(8) { lo |= (bytes[i] as u64) << (8 * i as u32); }
for i in 8..take { hi |= (bytes[i] as u64) << (8 * (i - 8) as u32); }
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); }
// Declare import: nyash.string.from_u64x2(lo, hi, len) -> i64
let mut sig = Signature::new(self.module.isa().default_call_conv());
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.returns.push(AbiParam::new(types::I64));
let func_id = self.module.declare_function("nyash.string.from_u64x2", cranelift_module::Linkage::Import, &sig).expect("declare string.from_u64x2");
let lo_v = fb.ins().iconst(types::I64, lo as i64);
let hi_v = fb.ins().iconst(types::I64, hi as i64);
let len_v = fb.ins().iconst(types::I64, bytes.len() as i64);
let fref = self.module.declare_func_in_func(func_id, fb.func);
let call_inst = fb.ins().call(fref, &[lo_v, hi_v, len_v]);
if let Some(v) = fb.inst_results(call_inst).get(0).copied() { self.value_stack.push(v); }
}
fn br_if_with_args(&mut self, then_index: usize, else_index: usize, then_n: usize, else_n: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::{types, condcodes::IntCC};
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); }
// Pop else args, then then args (stack topに近い方から)
let mut else_args: Vec<cranelift_codegen::ir::Value> = Vec::new();
for _ in 0..else_n { if let Some(v) = self.value_stack.pop() { else_args.push(v); } else { else_args.push(fb.ins().iconst(types::I64, 0)); } }
else_args.reverse();
let mut then_args: Vec<cranelift_codegen::ir::Value> = Vec::new();
for _ in 0..then_n { if let Some(v) = self.value_stack.pop() { then_args.push(v); } else { then_args.push(fb.ins().iconst(types::I64, 0)); } }
then_args.reverse();
// Cond
let cond_val = if let Some(v) = self.value_stack.pop() { v } else { fb.ins().iconst(types::I64, 0) };
let b1 = if fb.func.dfg.value_type(cond_val) == types::I64 { fb.ins().icmp_imm(IntCC::NotEqual, cond_val, 0) } else { fb.ins().icmp_imm(IntCC::NotEqual, cond_val, 0) };
// Coerce args to i64
for a in then_args.iter_mut() { if fb.func.dfg.value_type(*a) != types::I64 { *a = fb.ins().fcvt_to_sint(types::I64, *a); } }
for a in else_args.iter_mut() { if fb.func.dfg.value_type(*a) != types::I64 { *a = fb.ins().fcvt_to_sint(types::I64, *a); } }
fb.ins().brif(b1, self.blocks[then_index], &then_args, self.blocks[else_index], &else_args);
self.stats.3 += 1;
}
fn jump_with_args(&mut self, target_index: usize, n: usize) {
use cranelift_frontend::FunctionBuilder;
use cranelift_codegen::ir::types;
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); }
let mut args: Vec<cranelift_codegen::ir::Value> = Vec::new();
for _ in 0..n { if let Some(v) = self.value_stack.pop() { args.push(v); } else { args.push(fb.ins().iconst(types::I64, 0)); } }
args.reverse();
for a in args.iter_mut() { if fb.func.dfg.value_type(*a) != types::I64 { *a = fb.ins().fcvt_to_sint(types::I64, *a); } }
fb.ins().jump(self.blocks[target_index], &args);
self.stats.3 += 1;
}
}

View File

@ -182,7 +182,87 @@ impl LowerCore {
Ok(())
}
/// Emit robust length retrieval with fallback for String/Any:
/// 1) Prefer `nyash.string.len_h(recv)`
/// 2) If that yields 0 at runtime, select `nyash.any.length_h(recv)`
/// Returns: pushes selected length (i64) onto builder stack.
fn emit_len_with_fallback_param(&mut self, b: &mut dyn IRBuilder, pidx: usize) {
use super::builder::CmpKind;
// Temp locals
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h
b.emit_param_i64(pidx);
b.emit_host_call("nyash.string.len_h", 1, true);
b.store_local_i64(t_string);
// Any.length_h
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true);
b.store_local_i64(t_any);
// cond = (string_len == 0)
b.load_local_i64(t_string);
b.emit_const_i64(0);
b.emit_compare(CmpKind::Eq);
b.store_local_i64(t_cond);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond); // cond (bottom)
b.load_local_i64(t_any); // then
b.load_local_i64(t_string); // else
b.emit_select_i64();
}
fn emit_len_with_fallback_local_handle(&mut self, b: &mut dyn IRBuilder, slot: usize) {
use super::builder::CmpKind;
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h
b.load_local_i64(slot);
b.emit_host_call("nyash.string.len_h", 1, true);
b.store_local_i64(t_string);
// Any.length_h
b.load_local_i64(slot);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true);
b.store_local_i64(t_any);
// cond = (string_len == 0)
b.load_local_i64(t_string);
b.emit_const_i64(0);
b.emit_compare(CmpKind::Eq);
b.store_local_i64(t_cond);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
fn emit_len_with_fallback_literal(&mut self, b: &mut dyn IRBuilder, s: &str) {
use super::builder::CmpKind;
let t_string = self.next_local; self.next_local += 1;
let t_any = self.next_local; self.next_local += 1;
let t_cond = self.next_local; self.next_local += 1;
// String.len_h on literal handle
b.emit_string_handle_from_literal(s);
b.emit_host_call("nyash.string.len_h", 1, true);
b.store_local_i64(t_string);
// Any.length_h on literal handle (recreate handle; safe in v0)
b.emit_string_handle_from_literal(s);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, true);
b.store_local_i64(t_any);
// cond = (string_len == 0)
b.load_local_i64(t_string);
b.emit_const_i64(0);
b.emit_compare(CmpKind::Eq);
b.store_local_i64(t_cond);
// select(cond ? any_len : string_len)
b.load_local_i64(t_cond);
b.load_local_i64(t_any);
b.load_local_i64(t_string);
b.emit_select_i64();
}
fn try_emit(&mut self, b: &mut dyn IRBuilder, instr: &MirInstruction, cur_bb: crate::mir::BasicBlockId, func: &crate::mir::MirFunction) -> Result<(), String> {
use crate::mir::MirInstruction as I;
match instr {
@ -425,13 +505,21 @@ impl LowerCore {
if let Some(v) = self.known_i64.get(src).copied() { self.known_i64.insert(*dst, v); }
if let Some(v) = self.known_f64.get(src).copied() { self.known_f64.insert(*dst, v); }
if let Some(v) = self.known_str.get(src).cloned() { self.known_str.insert(*dst, v); }
// If source is a parameter, materialize it on the stack for downstream ops
if let Some(pidx) = self.param_index.get(src).copied() {
b.emit_param_i64(pidx);
}
// Propagate boolean classification through Copy
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
// Otherwise no-op for codegen (stack-machine handles sources directly later)
// If source is a parameter, materialize it on the stack for downstream ops and persist into dst slot
if let Some(pidx) = self.param_index.get(src).copied() {
b.emit_param_i64(pidx);
let slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(slot);
b.store_local_i64(slot);
} else if let Some(src_slot) = self.local_index.get(src).copied() {
// If source already has a local slot (e.g., a handle), copy into dst's slot
b.load_local_i64(src_slot);
let dst_slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(dst_slot);
b.store_local_i64(dst_slot);
}
}
I::BinOp { dst, op, lhs, rhs } => { self.lower_binop(b, op, lhs, rhs, dst, func); }
I::Compare { op, lhs, rhs, dst } => { self.lower_compare(b, op, lhs, rhs, dst, func); }
@ -470,11 +558,15 @@ impl LowerCore {
b.ensure_local_i64(slot);
b.store_local_i64(slot);
}
I::Load { dst: _, ptr } => {
// Minimal lowering: load from local slot keyed by ptr, default 0 if unset
let slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(slot);
b.load_local_i64(slot);
I::Load { dst, ptr } => {
// Minimal lowering: load from local slot keyed by ptr, then materialize into dst's own slot
let src_slot = *self.local_index.entry(*ptr).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(src_slot);
b.load_local_i64(src_slot);
// Persist into dst's slot to make subsequent uses find it via local_index
let dst_slot = *self.local_index.entry(*dst).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.ensure_local_i64(dst_slot);
b.store_local_i64(dst_slot);
}
I::Phi { dst, .. } => {
// PHI をローカルに materialize して後続の Return で安定参照
@ -525,9 +617,10 @@ impl LowerCore {
}
}
I::BoxCall { box_val: array, method, args, dst, .. } => {
// Clean path: delegate to ops_ext and return
let _ = self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())?;
return Ok(());
// Prefer ops_ext; if not handled, fall back to legacy path below
if self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())? {
return Ok(());
}
}
/* legacy BoxCall branch removed (now handled in ops_ext)
// handled in helper (read-only simple methods)
@ -663,21 +756,80 @@ impl LowerCore {
} else if crate::jit::config::current().hostcall {
match method.as_str() {
"len" | "length" => {
// Constant fold: if receiver is NewBox(StringBox, Const String), return its length directly
if let Some(did) = dst.as_ref() {
let mut lit_len: Option<i64> = None;
for (_bid, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst: ndst, box_type, args } = ins {
if ndst == array && box_type == "StringBox" && args.len() == 1 {
let src = args[0];
if let Some(s) = self.known_str.get(&src) { lit_len = Some(s.len() as i64); break; }
// scan Const directly
for (_b2, bb2) in func.blocks.iter() {
for ins2 in bb2.instructions.iter() {
if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins2 { if *cdst == src { if let crate::mir::ConstValue::String(sv) = value { lit_len = Some(sv.len() as i64); break; } } }
}
if lit_len.is_some() { break; }
}
}
}
}
if lit_len.is_some() { break; }
}
if let Some(n) = lit_len {
b.emit_const_i64(n);
self.known_i64.insert(*did, n);
return Ok(());
}
}
if let Some(pidx) = self.param_index.get(array).copied() {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
// Param 経路: string.len_h → 0 の場合 any.length_h へフォールバック
self.emit_len_with_fallback_param(b, pidx);
} else {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
// Try local handle (AOT/JIT-AOT) before legacy index fallback
if let Some(slot) = self.local_index.get(array).copied() {
// ローカルハンドル: string.len_h → any.length_h フォールバック
self.emit_len_with_fallback_local_handle(b, slot);
} else if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) {
// Attempt reconstruction for StringBox literal: scan NewBox(StringBox, Const String)
let mut lit: Option<String> = None;
for (_bid, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins {
if dst == array && box_type == "StringBox" && args.len() == 1 {
if let Some(src) = args.get(0) {
if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; }
// Also scan Const directly
for (_bid2, bb2) in func.blocks.iter() {
for ins2 in bb2.instructions.iter() {
if let crate::mir::MirInstruction::Const { dst: cdst, value } = ins2 { if cdst == src { if let crate::mir::ConstValue::String(sv) = value { lit = Some(sv.clone()); break; } } }
}
if lit.is_some() { break; }
}
}
}
}
}
if lit.is_some() { break; }
}
if let Some(s) = lit {
// リテラル復元: string.len_h → any.length_h フォールバック
self.emit_len_with_fallback_literal(b, &s);
} else {
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
}
} else {
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
}
}
}
// math.* minimal boundary: use registry signature to decide allow/fallback (no actual hostcall yet)

View File

@ -17,7 +17,7 @@ impl LowerCore {
let m = method;
if (bt == "PyRuntimeBox" && (m == "import")) {
let argc = 1 + args.len();
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
if let Some(pidx) = self.param_index.get(box_val).copied() { b.emit_param_i64(pidx); } else { self.push_value_if_known_or_param(b, box_val); }
let decision = crate::jit::policy::invoke::decide_box_method(&bt, m, argc, dst.is_some());
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some());
@ -35,7 +35,7 @@ impl LowerCore {
}
} else if self.handle_values.contains(box_val) && (m == "getattr" || m == "call") {
let argc = 1 + args.len();
b.emit_const_i64(-1);
if let Some(slot) = self.local_index.get(box_val).copied() { b.load_local_i64(slot); } else { 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 {
@ -228,7 +228,7 @@ impl LowerCore {
return Ok(true);
}
}
// String.len: (1) const string → 定数埋め込み、(2) StringBox → host-bridge
// String.len/length: robust handling
"len" => {
// (1) const string literal case
let mut lit_len: Option<i64> = None;
@ -247,20 +247,96 @@ impl LowerCore {
b.emit_const_i64(n);
return Ok(true);
}
// (2) StringBox via host-bridge
// (2) prefer host-bridge when enabled
if std::env::var("NYASH_JIT_HOST_BRIDGE").ok().as_deref() == Some("1") {
if let Some(bt) = self.box_type_map.get(array) {
if bt == "StringBox" {
if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") { eprintln!("[LOWER]string.len via host-bridge"); }
self.push_value_if_known_or_param(b, array);
b.emit_host_call(crate::jit::r#extern::host_bridge::SYM_HOST_STRING_LEN, 1, dst.is_some());
return Ok(true);
}
if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) {
if std::env::var("NYASH_JIT_TRACE_BRIDGE").ok().as_deref() == Some("1") { eprintln!("[LOWER]string.len via host-bridge"); }
self.push_value_if_known_or_param(b, array);
b.emit_host_call(crate::jit::r#extern::host_bridge::SYM_HOST_STRING_LEN, 1, dst.is_some());
return Ok(true);
}
}
// (3) Fallback: emit string.len_h with Any.length_h guard
if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) {
if let Some(pidx) = self.param_index.get(array).copied() {
self.emit_len_with_fallback_param(b, pidx);
return Ok(true);
}
if let Some(slot) = self.local_index.get(array).copied() {
self.emit_len_with_fallback_local_handle(b, slot);
return Ok(true);
}
// Try to reconstruct literal handle
let mut lit: Option<String> = None;
for (_bid, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins {
if dst == array && box_type == "StringBox" && args.len() == 1 {
if let Some(src) = args.get(0) {
if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; }
}
}
}
}
if lit.is_some() { break; }
}
if let Some(s) = lit { self.emit_len_with_fallback_literal(b, &s); return Ok(true); }
// As a last resort, convert receiver to handle via nyash.handle.of and apply fallback on temp slot
self.push_value_if_known_or_param(b, array);
b.emit_host_call("nyash.handle.of", 1, true);
let t_recv = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(t_recv);
self.emit_len_with_fallback_local_handle(b, t_recv);
return Ok(true);
}
// Not a StringBox: let other branches handle
return Ok(false);
}
// Array length variants (length/len)
// Alias: String.length → same as len
"length" => {
if self.box_type_map.get(array).map(|s| s == "StringBox").unwrap_or(false) {
// Reuse len handler
return self.lower_box_call(func, b, array, "len", args, dst);
}
// Array length is handled below; otherwise not handled here
return Ok(false);
}
// Array/String length variants (length/len)
"len" | "length" => {
match self.box_type_map.get(array).map(|s| s.as_str()) {
Some("StringBox") => {
if let Some(pidx) = self.param_index.get(array).copied() {
self.emit_len_with_fallback_param(b, pidx);
return Ok(true);
}
if let Some(slot) = self.local_index.get(array).copied() {
self.emit_len_with_fallback_local_handle(b, slot);
return Ok(true);
}
// Try literal reconstruction
let mut lit: Option<String> = None;
for (_bid, bb) in func.blocks.iter() {
for ins in bb.instructions.iter() {
if let crate::mir::MirInstruction::NewBox { dst, box_type, args } = ins {
if dst == array && box_type == "StringBox" && args.len() == 1 {
if let Some(src) = args.get(0) { if let Some(s) = self.known_str.get(src).cloned() { lit = Some(s); break; } }
}
}
}
if lit.is_some() { break; }
}
if let Some(s) = lit { self.emit_len_with_fallback_literal(b, &s); return Ok(true); }
// Last resort: handle.of
self.push_value_if_known_or_param(b, array);
b.emit_host_call("nyash.handle.of", 1, true);
let slot = { let id = self.next_local; self.next_local += 1; id };
b.store_local_i64(slot);
self.emit_len_with_fallback_local_handle(b, slot);
return Ok(true);
}
Some("ArrayBox") => {},
_ => { return Ok(false); }
}
if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {
if let Ok(h) = ph.resolve_method("ArrayBox", "length") {
if let Some(pidx) = self.param_index.get(array).copied() { b.emit_param_i64(pidx); } else { b.emit_const_i64(-1); }
@ -317,6 +393,18 @@ impl LowerCore {
return Ok(true);
}
let argc = match method { "size" => 1, "get" | "has" => 2, "set" => 3, _ => 1 };
// If receiver is a local handle (AOT/JIT-AOT), prefer handle-based hostcalls directly
if self.handle_values.contains(array) {
self.push_value_if_known_or_param(b, array);
match method {
"size" => b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, argc, dst.is_some()),
"get" => { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, argc, dst.is_some()) }
"has" => { if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_HAS_H, argc, dst.is_some()) }
"set" => { if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); } if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); } b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, argc, dst.is_some()) }
_ => {}
}
return Ok(true);
}
if let Ok(ph) = crate::runtime::plugin_loader_unified::get_global_plugin_host().read() {
if let Ok(h) = ph.resolve_method("MapBox", method) {
// receiver
@ -366,10 +454,10 @@ impl LowerCore {
_ => {}
}
} else {
// receiver unknown
b.emit_const_i64(-1);
// receiver unknown: try local handle (AOT/JIT-AOT)
self.push_value_if_known_or_param(b, array);
match method {
"size" => b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, argc, dst.is_some()),
"size" => b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, argc, dst.is_some()),
"get" => {
if let Some(v) = args.get(0) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_GET_H, argc, dst.is_some())
@ -381,7 +469,7 @@ impl LowerCore {
"set" => {
if let Some(k) = args.get(0) { self.push_value_if_known_or_param(b, k); } else { b.emit_const_i64(0); }
if let Some(v) = args.get(1) { self.push_value_if_known_or_param(b, v); } else { b.emit_const_i64(0); }
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET, argc, dst.is_some())
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SET_H, argc, dst.is_some())
}
_ => {}
}

View File

@ -326,100 +326,7 @@ pub fn lower_box_call(
}
}
// Handle simple read-only BoxCall methods. Returns true if handled.
pub fn lower_boxcall_simple_reads(
b: &mut dyn IRBuilder,
param_index: &HashMap<ValueId, usize>,
known_i64: &HashMap<ValueId, i64>,
recv: &ValueId,
method: &str,
args: &Vec<ValueId>,
dst: Option<ValueId>,
) -> bool {
if !crate::jit::config::current().hostcall { return false; }
// When plugin builtins are enabled, prefer plugin_invoke for length to exercise shim path
let use_plugin = std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1");
match method {
// Any.length / Array.length
"len" | "length" => {
if use_plugin { return false; }
if let Some(pidx) = param_index.get(recv).copied() {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_LEN_H, 1, dst.is_some());
} else {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_LEN_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
let arr_idx = -1;
b.emit_const_i64(arr_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ARRAY_LEN, 1, dst.is_some());
}
true
}
// Any.isEmpty
"isEmpty" | "empty" | "is_empty" => {
if let Some(pidx) = param_index.get(recv).copied() {
crate::jit::events::emit(
"hostcall","<jit>",None,None,
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]})
);
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, 1, dst.is_some());
} else {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_ANY_IS_EMPTY_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
}
true
}
// Map.size
"size" => {
if let Some(pidx) = param_index.get(recv).copied() {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"allow", "reason":"sig_ok", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE_H, 1, dst.is_some());
} else {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_MAP_SIZE_H, "decision":"fallback", "reason":"receiver_not_param", "argc":1, "arg_types":["Handle"]}),
"hostcall","<jit>"
);
let map_idx = -1;
b.emit_const_i64(map_idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_MAP_SIZE, 1, dst.is_some());
}
true
}
// String.charCodeAt(index)
"charCodeAt" => {
if let Some(pidx) = param_index.get(recv).copied() {
let idx = args.get(0).and_then(|v| known_i64.get(v).copied()).unwrap_or(0);
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"allow", "reason":"sig_ok", "argc":2, "arg_types":["Handle","I64"]}),
"hostcall","<jit>"
);
b.emit_param_i64(pidx);
b.emit_const_i64(idx);
b.emit_host_call(crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, 2, dst.is_some());
} else {
crate::jit::events::emit_lower(
serde_json::json!({"id": crate::jit::r#extern::collections::SYM_STRING_CHARCODE_AT_H, "decision":"fallback", "reason":"receiver_not_param", "argc":2, "arg_types":["Handle","I64"]}),
"hostcall","<jit>"
);
}
true
}
_ => false,
}
}
// (was: lower_boxcall_simple_reads) Removed; logic consolidated in core.rs length/charCodeAt handlers.
// Map.get(key): handle I64 and HH variants with registry check and events
pub fn lower_map_get(

View File

@ -18,7 +18,8 @@ fn use_plugin_builtins() -> bool {
pub fn decide_box_method(box_type: &str, method: &str, argc: usize, has_ret: bool) -> InvokeDecision {
// HostCall mapping for common collections/strings/instance ops
let symbol = match (box_type, method) {
("ArrayBox", "length") | ("StringBox", "length") | ("StringBox", "len") => crate::jit::r#extern::collections::SYM_ANY_LEN_H,
("ArrayBox", "length") => crate::jit::r#extern::collections::SYM_ANY_LEN_H,
("StringBox", "length") | ("StringBox", "len") => "nyash.string.len_h",
("ArrayBox", "get") => crate::jit::r#extern::collections::SYM_ARRAY_GET_H,
("ArrayBox", "set") => crate::jit::r#extern::collections::SYM_ARRAY_SET_H,
("ArrayBox", "push") => crate::jit::r#extern::collections::SYM_ARRAY_PUSH_H,

View File

@ -76,7 +76,14 @@ impl super::MirBuilder {
// Block: sequentially build statements and return last value or Void
pub(super) fn build_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
let mut last_value = None;
for statement in statements { last_value = Some(self.build_expression(statement)?); }
for statement in statements {
last_value = Some(self.build_expression(statement)?);
// If the current block was terminated by this statement (e.g., return/throw),
// do not emit any further instructions for this block.
if self.is_current_block_terminated() {
break;
}
}
Ok(last_value.unwrap_or_else(|| {
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void }).unwrap();
@ -97,6 +104,12 @@ impl super::MirBuilder {
let merge_block = self.block_gen.next();
self.emit_instruction(MirInstruction::Branch { condition: condition_val, then_bb: then_block, else_bb: else_block })?;
// Pre-analysis: detect then-branch assigned var and capture its pre-if value
let assigned_then_pre = extract_assigned_var(&then_branch);
let pre_then_var_value: Option<ValueId> = assigned_then_pre
.as_ref()
.and_then(|name| self.variable_map.get(name).copied());
// then
self.current_block = Some(then_block);
self.ensure_block_exists(then_block)?;
@ -107,7 +120,7 @@ impl super::MirBuilder {
// else
self.current_block = Some(else_block);
self.ensure_block_exists(else_block)?;
let (else_value, else_ast_for_analysis) = if let Some(else_ast) = else_branch {
let (mut else_value, else_ast_for_analysis) = if let Some(else_ast) = else_branch {
let val = self.build_expression(else_ast.clone())?;
(val, Some(else_ast))
} else {
@ -120,13 +133,31 @@ impl super::MirBuilder {
// merge + phi
self.current_block = Some(merge_block);
self.ensure_block_exists(merge_block)?;
let result_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
// heuristic: bind same var name to phi result
// If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else
// does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value).
let assigned_var_then = extract_assigned_var(&then_ast_for_analysis);
let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a));
if let (Some(a), Some(b)) = (assigned_var_then, assigned_var_else) { if a == b { self.variable_map.insert(a, result_val); } }
let mut result_val = self.value_gen.next();
if let Some(var_name) = assigned_var_then.clone() {
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
if !else_assigns_same {
if let Some(pre) = pre_then_var_value {
// Use pre-if value for else input so SSA is well-formed
else_value = pre;
}
// After merge, the variable should refer to the Phi result
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
self.variable_map.insert(var_name, result_val);
} else {
// Both sides assign same variable emit Phi normally and bind
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
self.variable_map.insert(var_name, result_val);
}
} else {
// No variable assignment pattern detected just emit Phi for expression result
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
}
Ok(result_val)
}

View File

@ -55,6 +55,12 @@ impl NyashRunner {
}
}
// Optional: dump MIR for diagnostics
if std::env::var("NYASH_VM_DUMP_MIR").ok().as_deref() == Some("1") {
let mut p = nyash_rust::mir::MirPrinter::new();
eprintln!("{}", p.print_module(&compile_result.module));
}
// Optional: VM-only escape analysis to elide barriers before execution
let mut module_vm = compile_result.module.clone();
if std::env::var("NYASH_VM_ESCAPE_ANALYSIS").ok().as_deref() == Some("1") {

View File

@ -15,6 +15,8 @@ pub fn init_bid_plugins() {
if let Ok(()) = init_global_plugin_host("nyash.toml") {
if plugin_debug || cli_verbose {
println!("🔌 plugin host initialized from nyash.toml");
// Show which plugin loader backend compiled in (enabled/stub)
println!("[plugin-loader] backend={}", crate::runtime::plugin_loader_v2::backend_kind());
}
let host = get_global_plugin_host();
let host = host.read().unwrap();

View File

@ -80,6 +80,9 @@ impl BoxFactoryRegistry {
use crate::runtime::get_global_plugin_host;
let host = get_global_plugin_host();
let host = host.read().unwrap();
if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") {
eprintln!("[BoxFactoryRegistry] create_plugin_box: plugin={} box_type={}", plugin_name, box_name);
}
host.create_box(box_name, args)
.map_err(|e| format!("Failed to create {} from plugin {}: {:?}", box_name, plugin_name, e))
}

View File

@ -79,7 +79,23 @@ impl PluginLoaderV2 {
candidates.push(base.with_extension("so"));
}
let lib_path = candidates.into_iter().find(|p| p.exists()).unwrap_or_else(|| base.to_path_buf());
// Prefer existing path; otherwise try to resolve via plugin_paths.search_paths
let mut lib_path = candidates.iter().find(|p| p.exists()).cloned();
if lib_path.is_none() {
if let Some(cfg) = &self.config {
// Try each candidate filename against search paths
for c in &candidates {
if let Some(fname) = c.file_name().and_then(|s| s.to_str()) {
if let Some(resolved) = cfg.resolve_plugin_path(fname) {
let pb = PathBuf::from(resolved);
if pb.exists() { lib_path = Some(pb); break; }
}
}
}
}
}
let lib_path = lib_path.unwrap_or_else(|| base.to_path_buf());
if dbg_on() { eprintln!("[PluginLoaderV2] load_plugin: lib='{}' path='{}'", lib_name, lib_path.display()); }
let lib = unsafe { Library::new(&lib_path) }.map_err(|_| BidError::PluginError)?;
let lib_arc = Arc::new(lib);
@ -275,8 +291,15 @@ impl PluginLoaderV2 {
let plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?;
// Call birth (no args TLV) and read returned instance id (little-endian u32 in bytes 0..4)
if dbg_on() {
eprintln!("[PluginLoaderV2] invoking birth: box_type={} type_id={} birth_id={}", box_type, type_id, birth_id);
}
let tlv = crate::runtime::plugin_ffi_common::encode_empty_args();
let (code, out_len, out_buf) = super::host_bridge::invoke_alloc(plugin.invoke_fn, type_id, birth_id, 0, &tlv);
if dbg_on() {
eprintln!("[PluginLoaderV2] create_box: box_type={} type_id={} birth_id={} code={} out_len={}", box_type, type_id, birth_id, code, out_len);
if out_len > 0 { eprintln!("[PluginLoaderV2] create_box: out[0..min(8)]={:02x?}", &out_buf[..out_len.min(8)]); }
}
if code != 0 || out_len < 4 { return Err(BidError::PluginError); }
let instance_id = u32::from_le_bytes([out_buf[0], out_buf[1], out_buf[2], out_buf[3]]);

View File

@ -7,3 +7,5 @@ mod host_bridge;
pub use types::{PluginBoxV2, PluginHandleInner, NyashTypeBoxFfi, make_plugin_box_v2, construct_plugin_box};
pub use loader::PluginLoaderV2;
pub use globals::{get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2};
pub fn backend_kind() -> &'static str { "enabled" }

View File

@ -33,3 +33,4 @@ pub fn get_global_loader_v2() -> Arc<RwLock<PluginLoaderV2>> { GLOBAL_LOADER_V2.
pub fn init_global_loader_v2(_config_path: &str) -> BidResult<()> { Ok(()) }
pub fn shutdown_plugins_v2() -> BidResult<()> { Ok(()) }
pub fn backend_kind() -> &'static str { "stub" }

View File

@ -0,0 +1,29 @@
use crate::backend::vm::VM;
use crate::parser::NyashParser;
use crate::runtime::NyashRuntime;
#[test]
fn vm_if_then_return_else_fallthrough_false() {
// If condition false: then is skipped, fallthrough returns 2
let code = "\nif (0) { return 1 }\nreturn 2\n";
let ast = NyashParser::parse_from_string(code).expect("parse failed");
let runtime = NyashRuntime::new();
let mut compiler = crate::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
assert_eq!(result.to_string_box().value, "2");
}
#[test]
fn vm_if_then_return_true() {
// If condition true: then branch returns 1
let code = "\nif (1) { return 1 }\nreturn 2\n";
let ast = NyashParser::parse_from_string(code).expect("parse failed");
let runtime = NyashRuntime::new();
let mut compiler = crate::mir::MirCompiler::new();
let compile_result = compiler.compile(ast).expect("mir compile failed");
let mut vm = VM::with_runtime(runtime);
let result = vm.execute_module(&compile_result.module).expect("vm exec failed");
assert_eq!(result.to_string_box().value, "1");
}