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:
@ -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(¤t_block) {
|
||||
for instruction in &block.instructions {
|
||||
match self.execute_instruction(instruction)? {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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) }));
|
||||
|
||||
@ -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()))
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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())
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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") {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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]]);
|
||||
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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" }
|
||||
|
||||
29
src/tests/if_return_exec.rs
Normal file
29
src/tests/if_return_exec.rs
Normal 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");
|
||||
}
|
||||
Reference in New Issue
Block a user