merge: selfhosting-dev <- origin/main; prefer main updates in cranelift builder (ARROW removal/SHR adoption)

This commit is contained in:
Selfhosting Dev
2025-09-08 04:33:50 +09:00
407 changed files with 15841 additions and 1015 deletions

View File

@ -333,6 +333,11 @@ pub enum BinaryOperator {
Multiply,
Divide,
Modulo,
BitAnd,
BitOr,
BitXor,
Shl, // << shift-left (Phase 1)
Shr,
Equal,
NotEqual,
Less,
@ -361,6 +366,11 @@ impl fmt::Display for BinaryOperator {
BinaryOperator::Multiply => "*",
BinaryOperator::Divide => "/",
BinaryOperator::Modulo => "%",
BinaryOperator::BitAnd => "&",
BinaryOperator::BitOr => "|",
BinaryOperator::BitXor => "^",
BinaryOperator::Shl => "<<",
BinaryOperator::Shr => ">>",
BinaryOperator::Equal => "==",
BinaryOperator::NotEqual => "!=",
BinaryOperator::Less => "<",

View File

@ -95,9 +95,39 @@ pub fn compile_and_execute(mir_module: &MirModule, _temp_name: &str) -> Result<B
MirInstruction::Copy { dst, src } => {
if let Some(v) = regs.get(src).cloned() { regs.insert(*dst, v); }
}
MirInstruction::Debug { .. } | MirInstruction::Print { .. } | MirInstruction::Barrier { .. } | MirInstruction::BarrierRead { .. } | MirInstruction::BarrierWrite { .. } | MirInstruction::Safepoint | MirInstruction::Load { .. } | MirInstruction::Store { .. } | MirInstruction::TypeOp { .. } | MirInstruction::Compare { .. } | MirInstruction::NewBox { .. } | MirInstruction::PluginInvoke { .. } | MirInstruction::BoxCall { .. } | MirInstruction::ExternCall { .. } | MirInstruction::RefGet { .. } | MirInstruction::RefSet { .. } | MirInstruction::WeakRef { .. } | MirInstruction::FutureNew { .. } | MirInstruction::FutureSet { .. } | MirInstruction::Await { .. } | MirInstruction::Throw { .. } | MirInstruction::Catch { .. } => {
MirInstruction::Debug { .. } | MirInstruction::Print { .. } | MirInstruction::Barrier { .. } | MirInstruction::BarrierRead { .. } | MirInstruction::BarrierWrite { .. } | MirInstruction::Safepoint | MirInstruction::Load { .. } | MirInstruction::Store { .. } | MirInstruction::TypeOp { .. } | MirInstruction::Compare { .. } | MirInstruction::NewBox { .. } | MirInstruction::PluginInvoke { .. } | MirInstruction::BoxCall { .. } | MirInstruction::RefGet { .. } | MirInstruction::RefSet { .. } | MirInstruction::WeakRef { .. } | MirInstruction::FutureNew { .. } | MirInstruction::FutureSet { .. } | MirInstruction::Await { .. } | MirInstruction::Throw { .. } | MirInstruction::Catch { .. } => {
// ignore for minimal path
}
MirInstruction::ExternCall { dst, iface_name, method_name, args, .. } => {
use crate::backend::vm::VMValue as V;
match (iface_name.as_str(), method_name.as_str()) {
("env.local", "get") => {
if let Some(d) = dst { if let Some(a0) = args.get(0) { if let Some(v) = regs.get(a0).cloned() { regs.insert(*d, v); } } }
}
("env.local", "set") => {
if args.len() >= 2 { if let Some(v) = regs.get(&args[1]).cloned() { regs.insert(args[0], v); } }
// dst ignored
}
("env.box", "new") => {
if let Some(d) = dst {
if let Some(a0) = args.get(0) {
if let Some(V::String(ty)) = regs.get(a0).cloned() {
let reg = crate::runtime::box_registry::get_global_registry();
// Collect args as NyashBox
let mut ny_args: Vec<Box<dyn crate::box_trait::NyashBox>> = Vec::new();
for vid in args.iter().skip(1) {
if let Some(v) = regs.get(vid).cloned() { ny_args.push(v.to_nyash_box()); }
}
if let Ok(b) = reg.create_box(&ty, &ny_args) {
regs.insert(*d, V::from_nyash_box(b));
}
}
}
}
}
_ => { /* ignore other externs in skeleton */ }
}
}
MirInstruction::Phi { .. } => { /* handled above */ }
_ => {}
}

View File

@ -127,16 +127,19 @@ impl LLVMCompiler {
if let (Some(l), Some(r)) = (left.as_any().downcast_ref::<IntegerBox>(),
right.as_any().downcast_ref::<IntegerBox>()) {
let result = match op {
BinaryOp::Add => l.value() + r.value(),
BinaryOp::Sub => l.value() - r.value(),
BinaryOp::Mul => l.value() * r.value(),
BinaryOp::Add => l.value + r.value,
BinaryOp::Sub => l.value - r.value,
BinaryOp::Mul => l.value * r.value,
BinaryOp::Div => {
if r.value() == 0 {
if r.value == 0 {
return Err("Division by zero".to_string());
}
l.value() / r.value()
l.value / r.value
}
BinaryOp::Mod => l.value % r.value,
_ => {
return Err("Binary operation not supported in mock".to_string());
}
BinaryOp::Mod => l.value() % r.value(),
};
self.values.insert(*dst, Box::new(IntegerBox::new(result)));
println!(" 📊 %{} = %{} {:?} %{} = {}", dst.0, lhs.0, op, rhs.0, result);
@ -221,6 +224,8 @@ use inkwell::context::Context;
#[cfg(feature = "llvm")]
use inkwell::{values::{BasicValueEnum, FloatValue, IntValue, PhiValue, FunctionValue, PointerValue}, types::{BasicTypeEnum, IntType, FloatType, PointerType}, AddressSpace};
#[cfg(feature = "llvm")]
use inkwell::types::BasicType; // for as_basic_type_enum()
#[cfg(feature = "llvm")]
use std::collections::HashMap as StdHashMap;
#[cfg(feature = "llvm")]
@ -900,68 +905,115 @@ impl LLVMCompiler {
let tyv = *vmap.get(&args[0]).ok_or("type name arg missing")?;
let ty_ptr = match tyv { BasicValueEnum::PointerValue(p) => p, _ => return Err("env.box.new type must be i8* string".to_string()) };
let i64t = codegen.context.i64_type();
let ret_to_ptr = |rv: BasicValueEnum| -> Result<BasicValueEnum, String> {
let i64v = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("env.box.new ret expected i64".to_string()); };
let pty = i8p;
let ptr = codegen.builder.build_int_to_ptr(i64v, pty, "box_handle_to_ptr").map_err(|e| e.to_string())?;
Ok(ptr.into())
};
// Helper: coerce arbitrary BasicValueEnum to i64; for i8* assume string and convert to box-handle via nyash.box.from_i8_string
let to_i64 = |v: BasicValueEnum| -> Result<inkwell::values::IntValue, String> {
match v {
BasicValueEnum::IntValue(iv) => Ok(iv),
BasicValueEnum::FloatValue(fv) => {
// Route via NyRT: i64 @nyash.box.from_f64(double)
let i64t = codegen.context.i64_type();
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg_f64_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { Ok(h) } else { Err("from_f64 ret expected i64".to_string()) }
}
BasicValueEnum::PointerValue(pv) => {
// If pointer is i8*, call nyash.box.from_i8_string to obtain a handle (i64)
let ty = pv.get_type();
let elem = ty.get_element_type();
if elem == codegen.context.i8_type().as_basic_type_enum() {
let i64t = codegen.context.i64_type();
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = codegen.builder.build_call(callee, &[pv.into()], "arg_i8_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { Ok(h) } else { Err("from_i8_string ret expected i64".to_string()) }
} else {
Ok(codegen.builder.build_ptr_to_int(pv, codegen.context.i64_type(), "p2i").map_err(|e| e.to_string())?)
}
}
_ => Err("unsupported arg value for env.box.new".to_string()),
}
};
let out_val = if args.len() == 1 {
// 1) new(type)
let out_ptr: PointerValue = if args.len() == 1 {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen.module.get_function("nyash.env.box.new").unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new", fnty, None));
let call = codegen.builder.build_call(callee, &[ty_ptr.into()], "env_box_new").map_err(|e| e.to_string())?;
call.try_as_basic_value().left().ok_or("env.box.new returned void".to_string())?
let rv = call.try_as_basic_value().left().ok_or("env.box.new returned void".to_string())?;
let i64v = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("env.box.new ret expected i64".to_string()); };
codegen.builder.build_int_to_ptr(i64v, i8p, "box_handle_to_ptr").map_err(|e| e.to_string())?
} else {
// Support up to 4 args for now
// 2) new_i64x(type, argc, a1..a4)
if args.len() - 1 > 4 { return Err("env.box.new supports up to 4 args in AOT shim".to_string()); }
let fnty = i64t.fn_type(&[i8p.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into(), i64t.into()], false);
let callee = codegen.module.get_function("nyash.env.box.new_i64x").unwrap_or_else(|| codegen.module.add_function("nyash.env.box.new_i64x", fnty, None));
let argc_val = i64t.const_int((args.len() - 1) as u64, false);
// helper to coerce to i64
let get_i64 = |vid: ValueId| -> Result<inkwell::values::IntValue, String> { to_i64(*vmap.get(&vid).ok_or("arg missing")?) };
// Inline-coerce up to 4 args to i64 handles (int pass-through, f64→box, i8*→box)
let mut a1 = i64t.const_zero();
if args.len() >= 2 {
let bv = *vmap.get(&args[1]).ok_or("arg missing")?;
a1 = match bv {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::FloatValue(fv) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg1_f64_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BasicValueEnum::PointerValue(pv) => {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = codegen.builder.build_call(callee, &[pv.into()], "arg1_i8_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => { return Err("unsupported arg value for env.box.new".to_string()); }
};
}
let mut a2 = i64t.const_zero();
if args.len() >= 2 { a1 = get_i64(args[1])?; }
if args.len() >= 3 { a2 = get_i64(args[2])?; }
if args.len() >= 3 {
let bv = *vmap.get(&args[2]).ok_or("arg missing")?;
a2 = match bv {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::FloatValue(fv) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg2_f64_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BasicValueEnum::PointerValue(pv) => {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = codegen.builder.build_call(callee, &[pv.into()], "arg2_i8_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => { return Err("unsupported arg value for env.box.new".to_string()); }
};
}
let mut a3 = i64t.const_zero();
if args.len() >= 4 {
let bv = *vmap.get(&args[3]).ok_or("arg missing")?;
a3 = match bv {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::FloatValue(fv) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg3_f64_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BasicValueEnum::PointerValue(pv) => {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = codegen.builder.build_call(callee, &[pv.into()], "arg3_i8_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => { return Err("unsupported arg value for env.box.new".to_string()); }
};
}
let mut a4 = i64t.const_zero();
if args.len() >= 4 { a3 = get_i64(args[3])?; }
if args.len() >= 5 { a4 = get_i64(args[4])?; }
if args.len() >= 5 {
let bv = *vmap.get(&args[4]).ok_or("arg missing")?;
a4 = match bv {
BasicValueEnum::IntValue(iv) => iv,
BasicValueEnum::FloatValue(fv) => {
let fnty = i64t.fn_type(&[codegen.context.f64_type().into()], false);
let callee = codegen.module.get_function("nyash.box.from_f64").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_f64", fnty, None));
let call = codegen.builder.build_call(callee, &[fv.into()], "arg4_f64_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_f64 returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_f64 ret expected i64".to_string()); }
}
BasicValueEnum::PointerValue(pv) => {
let fnty = i64t.fn_type(&[i8p.into()], false);
let callee = codegen.module.get_function("nyash.box.from_i8_string").unwrap_or_else(|| codegen.module.add_function("nyash.box.from_i8_string", fnty, None));
let call = codegen.builder.build_call(callee, &[pv.into()], "arg4_i8_to_box").map_err(|e| e.to_string())?;
let rv = call.try_as_basic_value().left().ok_or("from_i8_string returned void".to_string())?;
if let BasicValueEnum::IntValue(h) = rv { h } else { return Err("from_i8_string ret expected i64".to_string()); }
}
_ => { return Err("unsupported arg value for env.box.new".to_string()); }
};
}
let call = codegen.builder.build_call(callee, &[ty_ptr.into(), argc_val.into(), a1.into(), a2.into(), a3.into(), a4.into()], "env_box_new_i64x").map_err(|e| e.to_string())?;
call.try_as_basic_value().left().ok_or("env.box.new_i64 returned void".to_string())?
let rv = call.try_as_basic_value().left().ok_or("env.box.new_i64 returned void".to_string())?;
let i64v = if let BasicValueEnum::IntValue(iv) = rv { iv } else { return Err("env.box.new_i64 ret expected i64".to_string()); };
codegen.builder.build_int_to_ptr(i64v, i8p, "box_handle_to_ptr").map_err(|e| e.to_string())?
};
if let Some(d) = dst { vmap.insert(*d, ret_to_ptr(out_val)?); }
if let Some(d) = dst { vmap.insert(*d, out_ptr.into()); }
} else {
return Err(format!("ExternCall lowering unsupported: {}.{} (enable NYASH_LLVM_ALLOW_BY_NAME=1 to try by-name, or add a NyRT shim)", iface_name, method_name));
}
@ -969,7 +1021,7 @@ impl LLVMCompiler {
MirInstruction::UnaryOp { dst, op, operand } => {
let v = *vmap.get(operand).ok_or("operand missing")?;
let out = match op {
UnaryOp::Neg => {
UnaryOp::Neg => {
if let Some(iv) = as_int(v) { codegen.builder.build_int_neg(iv, "ineg").map_err(|e| e.to_string())?.into() }
else if let Some(fv) = as_float(v) { codegen.builder.build_float_neg(fv, "fneg").map_err(|e| e.to_string())?.into() }
else { return Err("neg on non-number".to_string()) }
@ -1374,16 +1426,23 @@ impl LLVMCompiler {
.get(rhs)
.and_then(|b| b.as_any().downcast_ref::<IntegerBox>())
.ok_or_else(|| format!("binop rhs %{} not integer", rhs.0))?;
let res = match op {
BinaryOp::Add => l.value() + r.value(),
BinaryOp::Sub => l.value() - r.value(),
BinaryOp::Mul => l.value() * r.value(),
BinaryOp::Div => {
if r.value() == 0 { return Err("division by zero".into()); }
l.value() / r.value()
}
BinaryOp::Mod => l.value() % r.value(),
};
let res = match op {
BinaryOp::Add => l.value + r.value,
BinaryOp::Sub => l.value - r.value,
BinaryOp::Mul => l.value * r.value,
BinaryOp::Div => {
if r.value == 0 { return Err("division by zero".into()); }
l.value / r.value
}
BinaryOp::Mod => l.value % r.value,
BinaryOp::BitAnd => l.value & r.value,
BinaryOp::BitOr => l.value | r.value,
BinaryOp::BitXor => l.value ^ r.value,
BinaryOp::Shl => l.value << r.value,
BinaryOp::Shr => l.value >> r.value,
BinaryOp::And => { if (l.value != 0) && (r.value != 0) { 1 } else { 0 } },
BinaryOp::Or => { if (l.value != 0) || (r.value != 0) { 1 } else { 0 } },
};
self.values.insert(*dst, Box::new(IntegerBox::new(res)));
}
I::Return { value } => {

View File

@ -6,12 +6,70 @@ use crate::backend::{VM, VMError, VMValue};
impl VM {
/// Execute ExternCall instruction
pub(crate) fn execute_extern_call(&mut self, dst: Option<ValueId>, iface_name: &str, method_name: &str, args: &[ValueId]) -> Result<ControlFlow, VMError> {
// Core-13 pure shims: env.local.{get,set}, env.box.new
match (iface_name, method_name) {
("env.local", "get") => {
if args.len() != 1 { return Err(VMError::InvalidInstruction("env.local.get arity".into())); }
let ptr = args[0];
let v = self.get_value(ptr).unwrap_or(crate::backend::vm::VMValue::Void);
if let Some(d) = dst { self.set_value(d, v); }
return Ok(ControlFlow::Continue);
}
("env.local", "set") => {
if args.len() != 2 { return Err(VMError::InvalidInstruction("env.local.set arity".into())); }
let ptr = args[0];
let val = self.get_value(args[1])?;
self.set_value(ptr, val);
if let Some(d) = dst { self.set_value(d, crate::backend::vm::VMValue::Void); }
return Ok(ControlFlow::Continue);
}
("env.box", "new") => {
if args.is_empty() { return Err(VMError::InvalidInstruction("env.box.new requires type name".into())); }
// first arg must be Const String type name
let ty = self.get_value(args[0])?;
let ty_name = match ty { crate::backend::vm::VMValue::String(s) => s, _ => return Err(VMError::InvalidInstruction("env.box.new first arg must be string".into())) };
// remaining args as NyashBox
let mut ny_args: Vec<Box<dyn NyashBox>> = Vec::new();
for id in args.iter().skip(1) {
let v = self.get_value(*id)?;
ny_args.push(v.to_nyash_box());
}
let reg = crate::runtime::box_registry::get_global_registry();
match reg.create_box(&ty_name, &ny_args) {
Ok(b) => { if let Some(d) = dst { self.set_value(d, crate::backend::vm::VMValue::from_nyash_box(b)); } }
Err(e) => { return Err(VMError::InvalidInstruction(format!("env.box.new failed for {}: {}", ty_name, e))); }
}
return Ok(ControlFlow::Continue);
}
_ => {}
}
// Optional routing to name→slot handlers for stability and diagnostics
if crate::config::env::extern_route_slots() {
if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) {
// Decode args to VMValue as needed by handlers below
let vm_args: Vec<VMValue> = args.iter().filter_map(|a| self.get_value(*a).ok()).collect();
match (iface_name, method_name, slot) {
("env.local", "get", 40) => {
if let Some(d) = dst { if let Some(a0) = args.get(0) { let v = self.get_value(*a0).unwrap_or(VMValue::Void); self.set_value(d, v); } }
return Ok(ControlFlow::Continue);
}
("env.local", "set", 41) => {
if args.len() >= 2 { let ptr = args[0]; let val = vm_args.get(1).cloned().unwrap_or(VMValue::Void); self.set_value(ptr, val); }
if let Some(d) = dst { self.set_value(d, VMValue::Void); }
return Ok(ControlFlow::Continue);
}
("env.box", "new", 50) => {
if vm_args.is_empty() { return Err(VMError::InvalidInstruction("env.box.new requires type".into())); }
let ty = &vm_args[0]; let ty_name = match ty { VMValue::String(s) => s.clone(), _ => return Err(VMError::InvalidInstruction("env.box.new first arg must be string".into())) };
let mut ny_args: Vec<Box<dyn NyashBox>> = Vec::new();
for v in vm_args.iter().skip(1) { ny_args.push(v.to_nyash_box()); }
let reg = crate::runtime::box_registry::get_global_registry();
match reg.create_box(&ty_name, &ny_args) {
Ok(b) => { if let Some(d) = dst { self.set_value(d, VMValue::from_nyash_box(b)); } }
Err(e) => { return Err(VMError::InvalidInstruction(format!("env.box.new failed for {}: {}", ty_name, e))); }
}
return Ok(ControlFlow::Continue);
}
("env.console", m @ ("log" | "warn" | "error" | "println"), 10) => {
if let Some(a0) = vm_args.get(0) {
match m { "warn" => eprintln!("[warn] {}", a0.to_string()), "error" => eprintln!("[error] {}", a0.to_string()), _ => println!("{}", a0.to_string()), }

View File

@ -87,9 +87,16 @@ impl VM {
let mut out_len: usize = out.len();
unsafe { (p.inner.invoke_fn)(p.inner.type_id, mh.method_id as u32, p.inner.instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) };
let vm_out_raw: VMValue = if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") {
eprintln!("[VM←Plugin] tag={} size={} bytes={}", tag, _sz, payload.len());
}
match tag {
1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(VMValue::Bool).unwrap_or(VMValue::Void),
2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|v| VMValue::Integer(v as i64)).unwrap_or(VMValue::Void),
2 => {
let v = crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or_default();
if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") { eprintln!("[VM←Plugin] decode i32={}", v); }
VMValue::Integer(v as i64)
},
5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(VMValue::Float).unwrap_or(VMValue::Void),
6 | 7 => VMValue::String(crate::runtime::plugin_ffi_common::decode::string(payload)),
8 => {

View File

@ -57,10 +57,6 @@ pub struct CliConfig {
// Phase-15: JSON IR v0 bridge
pub ny_parser_pipe: bool,
pub json_file: Option<String>,
// Using/module resolution helpers (MVP)
pub using: Option<String>,
pub using_path: Option<String>,
pub modules: Option<String>,
// Build system (MVP)
pub build_path: Option<String>,
pub build_app: Option<String>,
@ -107,24 +103,7 @@ impl CliConfig {
.value_name("FILE")
.help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter")
)
.arg(
Arg::new("using")
.long("using")
.value_name("LIST")
.help("Declare namespaces or aliases (comma-separated). Ex: 'acme.util, acme.math as M' or '\"apps/x.nyash\" as X'")
)
.arg(
Arg::new("using-path")
.long("using-path")
.value_name("PATHS")
.help("Search paths for using (':' separated). Ex: 'apps:lib:.'")
)
.arg(
Arg::new("module")
.long("module")
.value_name("MAP")
.help("Namespace to path mapping (comma-separated). Ex: 'acme.util=apps/acme/util.nyash'")
)
.arg(
Arg::new("debug-fuel")
.long("debug-fuel")
@ -427,9 +406,6 @@ impl CliConfig {
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
json_file: matches.get_one::<String>("json-file").cloned(),
using: matches.get_one::<String>("using").cloned(),
using_path: matches.get_one::<String>("using-path").cloned(),
modules: matches.get_one::<String>("module").cloned(),
// Build system (MVP)
build_path: matches.get_one::<String>("build").cloned(),
build_app: matches.get_one::<String>("build-app").cloned(),
@ -441,31 +417,9 @@ impl CliConfig {
}
}
/// Parse debug fuel value ("unlimited" or numeric)
fn parse_debug_fuel(value: &str) -> Option<usize> {
if value == "unlimited" {
None // No limit
} else {
value.parse::<usize>().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_debug_fuel() {
assert_eq!(parse_debug_fuel("unlimited"), None);
assert_eq!(parse_debug_fuel("1000"), Some(1000));
assert_eq!(parse_debug_fuel("invalid"), None);
}
#[test]
fn test_default_config() {
// This test would require mocking clap's behavior
// For now, we just ensure the structure is valid
let config = CliConfig {
impl Default for CliConfig {
fn default() -> Self {
Self {
file: None,
debug_fuel: Some(100000),
dump_ast: false,
@ -505,17 +459,39 @@ mod tests {
parser_ny: false,
ny_parser_pipe: false,
json_file: None,
using: None,
using_path: None,
modules: None,
build_path: None,
build_app: None,
build_out: None,
build_aot: None,
build_profile: None,
build_target: None,
};
}
}
}
/// Parse debug fuel value ("unlimited" or numeric)
fn parse_debug_fuel(value: &str) -> Option<usize> {
if value == "unlimited" {
None // No limit
} else {
value.parse::<usize>().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_debug_fuel() {
assert_eq!(parse_debug_fuel("unlimited"), None);
assert_eq!(parse_debug_fuel("1000"), Some(1000));
assert_eq!(parse_debug_fuel("invalid"), None);
}
#[test]
fn test_default_config() {
let config = CliConfig::default();
assert_eq!(config.backend, "interpreter");
assert_eq!(config.iterations, 10);
}

View File

@ -86,12 +86,27 @@ pub fn await_max_ms() -> u64 {
}
// ---- Phase 11.8 MIR cleanup toggles ----
pub fn mir_core13() -> bool { std::env::var("NYASH_MIR_CORE13").ok().as_deref() == Some("1") }
/// Core-13 minimal MIR mode toggle
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
pub fn mir_core13() -> bool {
match std::env::var("NYASH_MIR_CORE13").ok() {
Some(v) => {
let lv = v.to_ascii_lowercase();
!(lv == "0" || lv == "false" || lv == "off")
}
None => true,
}
}
pub fn mir_ref_boxcall() -> bool { std::env::var("NYASH_MIR_REF_BOXCALL").ok().as_deref() == Some("1") || mir_core13() }
pub fn mir_array_boxcall() -> bool { std::env::var("NYASH_MIR_ARRAY_BOXCALL").ok().as_deref() == Some("1") || mir_core13() }
pub fn mir_plugin_invoke() -> bool { std::env::var("NYASH_MIR_PLUGIN_INVOKE").ok().as_deref() == Some("1") }
pub fn plugin_only() -> bool { std::env::var("NYASH_PLUGIN_ONLY").ok().as_deref() == Some("1") }
/// Core-13 "pure" mode: after normalization, only the 13 canonical ops are allowed.
/// If enabled, the optimizer will try lightweight rewrites for Load/Store/NewBox/Unary,
/// and the final verifier will reject any remaining non-Core-13 ops.
pub fn mir_core13_pure() -> bool { std::env::var("NYASH_MIR_CORE13_PURE").ok().as_deref() == Some("1") }
// ---- Optimizer diagnostics ----
pub fn opt_debug() -> bool { std::env::var("NYASH_OPT_DEBUG").is_ok() }
pub fn opt_diag() -> bool { std::env::var("NYASH_OPT_DIAG").is_ok() }

View File

@ -118,6 +118,35 @@ impl NyashInterpreter {
// Register MethodBox invoker once (idempotent)
self::register_methodbox_invoker();
// Best-effort: initialize plugin host/config when running interpreter standalone
// This mirrors runner initialization so that `new FileBox()` etc. work without the CLI path.
// Policy: enable plugin-builtins for FileBox/TOMLBox by default; Array/Map remain builtin unless explicitly overridden.
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
if std::path::Path::new("nyash.toml").exists() {
let needs_init = {
let host = crate::runtime::get_global_plugin_host();
host.read().map(|h| h.config_ref().is_none()).unwrap_or(true)
};
if needs_init {
let _ = crate::runtime::init_global_plugin_host("nyash.toml");
// Apply config to BoxFactoryRegistry so UnifiedBoxRegistry can resolve plugin boxes
crate::runner_plugin_init::init_bid_plugins();
}
// Prefer plugin implementations for specific types when available
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() {
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
}
// Merge override list with FileBox/TOMLBox only (safe defaults for interpreter flows)
let mut override_types: 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![] };
for t in ["FileBox", "TOMLBox"] {
if !override_types.iter().any(|x| x == t) { override_types.push(t.to_string()); }
}
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
}
}
this
}

View File

@ -323,6 +323,53 @@ impl NyashInterpreter {
}
}
BinaryOperator::Shl => {
// Integer-only left shift
if let (Some(li), Some(ri)) = (
crate::runtime::semantics::coerce_to_i64(left_val.as_ref()),
crate::runtime::semantics::coerce_to_i64(right_val.as_ref()),
) {
let sh = (ri as u32) & 63;
return Ok(Box::new(IntegerBox::new(li.wrapping_shl(sh))));
}
Err(RuntimeError::TypeError {
message: format!("Shift-left '<<' requires integers (got {} and {})", left_val.type_name(), right_val.type_name())
})
}
BinaryOperator::Shr => {
if let (Some(li), Some(ri)) = (
crate::runtime::semantics::coerce_to_i64(left_val.as_ref()),
crate::runtime::semantics::coerce_to_i64(right_val.as_ref()),
) {
let sh = (ri as u32) & 63;
return Ok(Box::new(IntegerBox::new(((li as u64) >> sh) as i64)));
}
Err(RuntimeError::TypeError {
message: format!("Shift-right '>>' requires integers (got {} and {})", left_val.type_name(), right_val.type_name())
})
}
BinaryOperator::BitAnd => {
if let (Some(li), Some(ri)) = (
crate::runtime::semantics::coerce_to_i64(left_val.as_ref()),
crate::runtime::semantics::coerce_to_i64(right_val.as_ref()),
) { return Ok(Box::new(IntegerBox::new(li & ri))); }
Err(RuntimeError::TypeError { message: format!("Bitwise '&' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) })
}
BinaryOperator::BitOr => {
if let (Some(li), Some(ri)) = (
crate::runtime::semantics::coerce_to_i64(left_val.as_ref()),
crate::runtime::semantics::coerce_to_i64(right_val.as_ref()),
) { return Ok(Box::new(IntegerBox::new(li | ri))); }
Err(RuntimeError::TypeError { message: format!("Bitwise '|' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) })
}
BinaryOperator::BitXor => {
if let (Some(li), Some(ri)) = (
crate::runtime::semantics::coerce_to_i64(left_val.as_ref()),
crate::runtime::semantics::coerce_to_i64(right_val.as_ref()),
) { return Ok(Box::new(IntegerBox::new(li ^ ri))); }
Err(RuntimeError::TypeError { message: format!("Bitwise '^' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) })
}
BinaryOperator::Less => {
let result = CompareBox::less(left_val.as_ref(), right_val.as_ref());
Ok(Box::new(result))

View File

@ -256,6 +256,44 @@ impl NyashInterpreter {
Ok(Box::new(BoolBox::new(self.is_truthy(right_val))))
}
},
BinaryOperator::Shl => {
if let (Some(li), Some(ri)) = (
left_val.as_any().downcast_ref::<IntegerBox>(),
right_val.as_any().downcast_ref::<IntegerBox>(),
) {
Ok(Box::new(IntegerBox::new(li.value.wrapping_shl((ri.value as u32) & 63))))
} else {
Err(RuntimeError::InvalidOperation { message: format!("Shift-left '<<' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) })
}
},
BinaryOperator::Shr => {
if let (Some(li), Some(ri)) = (
left_val.as_any().downcast_ref::<IntegerBox>(),
right_val.as_any().downcast_ref::<IntegerBox>(),
) {
Ok(Box::new(IntegerBox::new(((li.value as u64) >> ((ri.value as u32) & 63)) as i64)))
} else {
Err(RuntimeError::InvalidOperation { message: format!("Shift-right '>>' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) })
}
},
BinaryOperator::BitAnd => {
if let (Some(li), Some(ri)) = (
left_val.as_any().downcast_ref::<IntegerBox>(),
right_val.as_any().downcast_ref::<IntegerBox>(),
) { Ok(Box::new(IntegerBox::new(li.value & ri.value))) } else { Err(RuntimeError::InvalidOperation { message: format!("Bitwise '&' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) }) }
},
BinaryOperator::BitOr => {
if let (Some(li), Some(ri)) = (
left_val.as_any().downcast_ref::<IntegerBox>(),
right_val.as_any().downcast_ref::<IntegerBox>(),
) { Ok(Box::new(IntegerBox::new(li.value | ri.value))) } else { Err(RuntimeError::InvalidOperation { message: format!("Bitwise '|' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) }) }
},
BinaryOperator::BitXor => {
if let (Some(li), Some(ri)) = (
left_val.as_any().downcast_ref::<IntegerBox>(),
right_val.as_any().downcast_ref::<IntegerBox>(),
) { Ok(Box::new(IntegerBox::new(li.value ^ ri.value))) } else { Err(RuntimeError::InvalidOperation { message: format!("Bitwise '^' requires integers (got {} and {})", left_val.type_name(), right_val.type_name()) }) }
},
}
}

View File

@ -512,7 +512,7 @@ impl IRBuilder for CraneliftBuilder {
while args.len() < _argc { args.push(fb.ins().iconst(types::I64, 0)); }
});
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 import failed");
if let Some(v) = tls_call_import_ret(&mut self.module, func_id, &args, has_ret) { self.value_stack.push(v); }
}
@ -615,10 +615,9 @@ impl IRBuilder for CraneliftBuilder {
if has_ret { sig.returns.push(AbiParam::new(if use_f64 { types::F64 } else { types::I64 })); }
let symbol = if use_f64 { "nyash_plugin_invoke3_f64" } else { "nyash_plugin_invoke3_i64" };
let func_id = self.module.declare_function(symbol, cranelift_module::Linkage::Import, &sig).expect("declare plugin shim failed");
// Ensure we are in a valid block context using the builder's safe switch
if let Some(idx) = self.current_block_index { self.switch_to_block(idx); }
else if self.entry_block.is_some() { self.switch_to_block(0); }
let ret_val = Self::with_fb(|fb| {
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); }
while arg_vals.len() < 3 { let z = fb.ins().iconst(types::I64, 0); arg_vals.push(z); }
// handle.of on receiver (redundant-safe)
let call_conv_h = self.module.isa().default_call_conv();
@ -640,10 +639,7 @@ impl IRBuilder for CraneliftBuilder {
}
fn emit_plugin_invoke_by_name(&mut self, method: &str, argc: usize, has_ret: bool) {
use cranelift_codegen::ir::{AbiParam, Signature, types};
// Ensure we are in a valid block context using the builder's safe switch
if let Some(idx) = self.current_block_index { self.switch_to_block(idx); }
else if self.entry_block.is_some() { self.switch_to_block(0); }
// Collect call args
// Collect call args
let mut arg_vals: Vec<cranelift_codegen::ir::Value> = {
let take_n = argc.min(self.value_stack.len());
let mut tmp = Vec::new();
@ -656,7 +652,8 @@ impl IRBuilder for CraneliftBuilder {
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
sig.params.push(AbiParam::new(types::I64));
if has_ret { sig.returns.push(AbiParam::new(types::I64)); }
sig.returns.push(AbiParam::new(types::I64));
let sym = match method { "getattr" => "nyash_plugin_invoke_name_getattr_i64", _ => "nyash_plugin_invoke_name_call_i64" };
let func_id = self.module.declare_function(sym, cranelift_module::Linkage::Import, &sig).expect("declare name shim failed");
let ret_val = Self::with_fb(|fb| {
@ -710,15 +707,20 @@ impl IRBuilder for CraneliftBuilder {
}
fn switch_to_block(&mut self, index: usize) {
if index >= self.blocks.len() { return; }
// If already on the target block, avoid re-switching (CLIF panics when switching from an unfilled block)
if let Some(cur) = self.current_block_index { if cur == index { return; } }
// Avoid redundant switch_to_block calls that can trip FunctionBuilder state
if self.current_block_index == Some(index) { return; }
Self::with_fb(|fb| {
// If switching away from a non-terminated block, inject jump to keep CFG sane
if let Some(cur) = self.current_block_index {
if self.cur_needs_term { fb.ins().jump(self.blocks[index], &[]); self.cur_needs_term = false; }
if self.cur_needs_term && cur != index {
fb.ins().jump(self.blocks[index], &[]);
self.cur_needs_term = false;
}
}
fb.switch_to_block(self.blocks[index]);
self.current_block_index = Some(index);
// New current block now requires a terminator before any further switch
self.cur_needs_term = true;
});
}
fn seal_block(&mut self, _index: usize) { /* final sealing handled in end_function */ }
@ -874,7 +876,6 @@ impl CraneliftBuilder {
builder.symbol(c::SYM_ARRAY_LEN_H, nyash_array_len_h as *const u8);
builder.symbol(c::SYM_ARRAY_GET_H, nyash_array_get_h as *const u8);
builder.symbol(c::SYM_ARRAY_SET_H, nyash_array_set_h as *const u8);
builder.symbol(c::SYM_ARRAY_SET_HH, super::super::extern_thunks::nyash_array_set_hh as *const u8);
builder.symbol(c::SYM_ARRAY_PUSH_H, nyash_array_push_h as *const u8);
builder.symbol(c::SYM_ARRAY_LAST_H, nyash_array_last_h as *const u8);
builder.symbol(c::SYM_MAP_SIZE_H, nyash_map_size_h as *const u8);

View File

@ -65,6 +65,12 @@ impl IRBuilder for ObjectBuilder {
self.current_name = Some(name.to_string());
self.value_stack.clear();
self.value_tags.clear();
// Reset contexts to satisfy Cranelift requirements when reusing the builder
self.ctx = cranelift_codegen::Context::new();
self.fbc = cranelift_frontend::FunctionBuilderContext::new();
self.blocks.clear();
self.entry_block = None;
self.current_block_index = None;
if !self.typed_sig_prepared {
let call_conv = self.module.isa().default_call_conv();
let mut sig = Signature::new(call_conv);
@ -93,6 +99,10 @@ impl IRBuilder for ObjectBuilder {
let finished = std::mem::replace(&mut self.module, Self::fresh_module());
let product = finished.finish();
self.object_bytes = Some(product.emit().expect("emit object"));
// Clear per-function state to allow reuse
self.blocks.clear();
self.entry_block = None;
self.current_block_index = None;
}
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; }
@ -477,7 +487,7 @@ impl IRBuilder for ObjectBuilder {
// Build signature and declare import
let mut sig = Signature::new(self.module.isa().default_call_conv());
for _ in 0..12 { sig.params.push(AbiParam::new(types::I64)); }
if has_ret { sig.returns.push(AbiParam::new(types::I64)); }
sig.returns.push(AbiParam::new(types::I64));
let func_id = self.module.declare_function("nyash_plugin_invoke3_tagged_i64", cranelift_module::Linkage::Import, &sig).expect("declare plugin invoke tagged");
let fref = self.module.declare_func_in_func(func_id, fb.func);
@ -530,7 +540,7 @@ impl IRBuilder for ObjectBuilder {
let mut sig = Signature::new(self.module.isa().default_call_conv());
for _ in 0..5 { sig.params.push(AbiParam::new(types::I64)); }
if has_ret { sig.returns.push(AbiParam::new(types::I64)); }
sig.returns.push(AbiParam::new(types::I64));
let func_id = self.module.declare_function("nyash.plugin.invoke_by_name_i64", cranelift_module::Linkage::Import, &sig).expect("declare plugin invoke by-name");
let fref = self.module.declare_func_in_func(func_id, fb.func);

View File

@ -494,6 +494,9 @@ 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); }
// Propagate handle/type knowledge to keep BoxCall routing stable across copies
if self.handle_values.contains(src) { self.handle_values.insert(*dst); }
if let Some(bt) = self.box_type_map.get(src).cloned() { self.box_type_map.insert(*dst, bt); }
// Propagate boolean classification through Copy
if self.bool_values.contains(src) { self.bool_values.insert(*dst); }
// If source is a parameter, materialize it on the stack for downstream ops and persist into dst slot
@ -746,7 +749,40 @@ impl LowerCore {
}
}
let handled = self.lower_box_call(func, b, &array, method.as_str(), args, dst.clone())?;
if trace { eprintln!("[LOWER] BoxCall recv={:?} method={} handled={} box_type={:?} dst?={}", array, method, handled, self.box_type_map.get(&array), dst.is_some()); }
if trace {
eprintln!(
"[LOWER] BoxCall recv={:?} method={} handled={} box_type={:?} dst?={}",
array, method, handled, self.box_type_map.get(&array), dst.is_some()
);
if !handled {
let bt = self.box_type_map.get(&array).cloned().unwrap_or_default();
let is_param = self.param_index.contains_key(&array);
let has_local = self.local_index.contains_key(&array);
let is_handle = self.handle_values.contains(&array);
// Classify up to first 3 args: i(known_i64) s(known_str) p(param) l(local) h(handle) -(unknown)
let mut arg_kinds: Vec<&'static str> = Vec::new();
for a in args.iter().take(3) {
let k = if self.known_i64.contains_key(a) { "i" }
else if self.known_str.contains_key(a) { "s" }
else if self.param_index.contains_key(a) { "p" }
else if self.local_index.contains_key(a) { "l" }
else if self.handle_values.contains(a) { "h" }
else { "-" };
arg_kinds.push(k);
}
// Policy hint: whether a mapped HostCall exists for (box_type, method)
let policy = crate::jit::policy::invoke::decide_box_method(&bt, method.as_str(), 1 + args.len(), dst.is_some());
let policy_str = match policy {
crate::jit::policy::invoke::InvokeDecision::HostCall { ref symbol, .. } => format!("hostcall:{}", symbol),
crate::jit::policy::invoke::InvokeDecision::PluginInvoke { .. } => "plugin_invoke".to_string(),
crate::jit::policy::invoke::InvokeDecision::Fallback { ref reason } => format!("fallback:{}", reason),
};
eprintln!(
"[LOWER] fallback(reason=unhandled) box_type='{}' method='{}' recv[param?{} local?{} handle?{}] args={:?} policy={}",
bt, method, is_param, has_local, is_handle, arg_kinds, policy_str
);
}
}
if handled {
return Ok(());
}

View File

@ -19,8 +19,9 @@ impl LowerCore {
let argc = 1 + args.len();
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 {
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, argc, dst.is_some());
crate::jit::observe::lower_plugin_invoke(&box_type, m, type_id, method_id, argc);
if let Some(d) = dst { self.handle_values.insert(*d); }
} else { if dst.is_some() { b.emit_const_i64(0); } }
} else if (bt == "PyRuntimeBox" && (m == "getattr" || m == "call")) {
@ -82,8 +83,9 @@ impl LowerCore {
if let Some(arg0) = args.get(0) { self.push_value_if_known_or_param(b, arg0); }
// Resolve plugin invoke for ConsoleBox.method
let decision = crate::jit::policy::invoke::decide_box_method("ConsoleBox", method_name, 2, dst.is_some());
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, .. } = decision {
if let crate::jit::policy::invoke::InvokeDecision::PluginInvoke { type_id, method_id, box_type, .. } = decision {
b.emit_plugin_invoke(type_id, method_id, 2, dst.is_some());
crate::jit::observe::lower_plugin_invoke(&box_type, method_name, type_id, method_id, 2);
} else if dst.is_some() { b.emit_const_i64(0); }
return Ok(());
}
@ -172,8 +174,51 @@ impl LowerCore {
dst.clone(),
);
return Ok(true);
}
// 非コアBox例: EguiBox など)は共通処理として名前ベースの plugin_invoke にフォールバック
// コアBoxの目安: StringBox/ArrayBox/MapBoxこの後の分岐で処理と PyRuntimeBox専用分岐済
if let Some(bt) = self.box_type_map.get(array).cloned() {
let is_core = bt == "StringBox" || bt == "ArrayBox" || bt == "MapBox" || bt == "PyRuntimeBox";
if !is_core {
// receiver: prefer existing local slot/param; ensure a valid runtime handle
if let Some(slot) = self.local_index.get(array).copied() {
b.load_local_i64(slot);
} else if let Some(pidx) = self.param_index.get(array).copied() {
b.emit_param_i64(pidx);
b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true);
} else {
self.push_value_if_known_or_param(b, array);
b.emit_host_call(crate::jit::r#extern::handles::SYM_HANDLE_OF, 1, true);
}
// push up to 2 args (name-shim supports at most 2 positional args beyond receiver)
let take_n = core::cmp::min(args.len(), 2);
for i in 0..take_n { if let Some(v) = args.get(i) { self.push_value_if_known_or_param(b, v); } }
let argc = 1 + take_n;
b.emit_plugin_invoke_by_name(method, argc, dst.is_some());
if std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1") {
crate::jit::events::emit_lower(
serde_json::json!({
"id": format!("plugin_name:{}:{}", bt, method),
"decision": "allow",
"reason": "plugin_invoke_by_name",
"argc": argc
}),
"plugin","<jit>"
);
}
if let Some(d) = dst {
self.handle_values.insert(d);
let slot = *self.local_index.entry(d).or_insert_with(|| { let id = self.next_local; self.next_local += 1; id });
b.store_local_i64(slot);
}
if std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1") {
eprintln!("[LOWER] {}.{} via name-invoke (argc={})", bt, method, argc);
}
return Ok(true);
}
}
// Builtins-to-plugin path (subset for String/Array/Map critical ops)
// Builtins-to-plugin path (subset for String/Array/Map critical ops)
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
// StringBox (length/is_empty/charCodeAt)
if matches!(method, "length" | "is_empty" | "charCodeAt") {
@ -661,6 +706,32 @@ impl LowerCore {
_ => {}
}
// Not handled here
if std::env::var("NYASH_JIT_TRACE_LOWER").ok().as_deref() == Some("1") {
let bt = self.box_type_map.get(array).cloned().unwrap_or_default();
let is_param = self.param_index.contains_key(array);
let has_local = self.local_index.contains_key(array);
let is_handle = self.handle_values.contains(array);
let mut arg_kinds: Vec<&'static str> = Vec::new();
for a in args.iter().take(3) {
let k = if self.known_i64.contains_key(a) { "i" }
else if self.known_str.contains_key(a) { "s" }
else if self.param_index.contains_key(a) { "p" }
else if self.local_index.contains_key(a) { "l" }
else if self.handle_values.contains(a) { "h" }
else { "-" };
arg_kinds.push(k);
}
let policy = crate::jit::policy::invoke::decide_box_method(&bt, method, 1 + args.len(), dst.is_some());
let policy_str = match policy {
crate::jit::policy::invoke::InvokeDecision::HostCall { ref symbol, .. } => format!("hostcall:{}", symbol),
crate::jit::policy::invoke::InvokeDecision::PluginInvoke { .. } => "plugin_invoke".to_string(),
crate::jit::policy::invoke::InvokeDecision::Fallback { ref reason } => format!("fallback:{}", reason),
};
eprintln!(
"[LOWER] unhandled BoxCall: box_type='{}' method='{}' recv[param?{} local?{} handle?{}] args={:?} policy={}",
bt, method, is_param, has_local, is_handle, arg_kinds, policy_str
);
}
Ok(false)
}
}

View File

@ -56,6 +56,7 @@ pub mod config;
// Runtime system (plugins, registry, etc.)
pub mod runtime;
pub mod runner_plugin_init;
pub mod debug;
pub mod grammar; // Phase 11.9 unified grammar scaffolding
pub mod syntax; // Phase 12.7: syntax sugar config and helpers (mirror lib layout)

View File

@ -157,6 +157,10 @@ impl MirBuilder {
/// Emit a weak reference creation (Unified: WeakRef(New))
#[allow(dead_code)]
pub(super) fn emit_weak_new(&mut self, box_val: ValueId) -> Result<ValueId, String> {
if crate::config::env::mir_core13_pure() {
// Pure mode: avoid WeakRef emission; pass-through
return Ok(box_val);
}
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::WeakRef { dst, op: super::WeakRefOp::New, value: box_val })?;
Ok(dst)
@ -165,6 +169,10 @@ impl MirBuilder {
/// Emit a weak reference load (Unified: WeakRef(Load))
#[allow(dead_code)]
pub(super) fn emit_weak_load(&mut self, weak_ref: ValueId) -> Result<ValueId, String> {
if crate::config::env::mir_core13_pure() {
// Pure mode: avoid WeakRef emission; pass-through
return Ok(weak_ref);
}
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::WeakRef { dst, op: super::WeakRefOp::Load, value: weak_ref })?;
Ok(dst)
@ -771,6 +779,27 @@ impl MirBuilder {
/// Build new expression: new ClassName(arguments)
pub(super) fn build_new_expression(&mut self, class: String, arguments: Vec<ASTNode>) -> Result<ValueId, String> {
// Phase 9.78a: Unified Box creation using NewBox instruction
// Core-13 pure mode: emit ExternCall(env.box.new) with type name const only
if crate::config::env::mir_core13_pure() {
// Emit Const String for type name
let ty_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: ty_id, value: ConstValue::String(class.clone()) })?;
// Evaluate arguments (pass through to env.box.new shim)
let mut arg_vals: Vec<ValueId> = Vec::with_capacity(arguments.len());
for a in arguments { arg_vals.push(self.build_expression(a)?); }
// Build arg list: [type, a1, a2, ...]
let mut args: Vec<ValueId> = Vec::with_capacity(1 + arg_vals.len());
args.push(ty_id);
args.extend(arg_vals);
// Call env.box.new
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(), args, effects: EffectMask::PURE,
})?;
// 型注釈(最小)
self.value_types.insert(dst, super::MirType::Box(class.clone()));
return Ok(dst);
}
// Optimization: Primitive wrappers → emit Const directly when possible
if class == "IntegerBox" && arguments.len() == 1 {

View File

@ -73,10 +73,35 @@ impl super::MirBuilder {
// Build a unary operation
pub(super) fn build_unary_op(&mut self, operator: String, operand: ASTNode) -> Result<ValueId, String> {
let operand_val = self.build_expression(operand)?;
// Core-13 純化: UnaryOp を直接 展開Neg/Not/BitNot
if crate::config::env::mir_core13_pure() {
match operator.as_str() {
"-" => {
let zero = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: zero, value: crate::mir::ConstValue::Integer(0) })?;
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::BinOp { dst, op: crate::mir::BinaryOp::Sub, lhs: zero, rhs: operand_val })?;
return Ok(dst);
}
"!" | "not" => {
let f = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: f, value: crate::mir::ConstValue::Bool(false) })?;
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::Compare { dst, op: crate::mir::CompareOp::Eq, lhs: operand_val, rhs: f })?;
return Ok(dst);
}
"~" => {
let all1 = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: all1, value: crate::mir::ConstValue::Integer(-1) })?;
let dst = self.value_gen.next();
self.emit_instruction(MirInstruction::BinOp { dst, op: crate::mir::BinaryOp::BitXor, lhs: operand_val, rhs: all1 })?;
return Ok(dst);
}
_ => {}
}
}
let dst = self.value_gen.next();
let mir_op = self.convert_unary_operator(operator)?;
self.emit_instruction(MirInstruction::UnaryOp { dst, op: mir_op, operand: operand_val })?;
Ok(dst)
}
@ -89,6 +114,11 @@ impl super::MirBuilder {
BinaryOperator::Multiply => Ok(BinaryOpType::Arithmetic(BinaryOp::Mul)),
BinaryOperator::Divide => Ok(BinaryOpType::Arithmetic(BinaryOp::Div)),
BinaryOperator::Modulo => Ok(BinaryOpType::Arithmetic(BinaryOp::Mod)),
BinaryOperator::Shl => Ok(BinaryOpType::Arithmetic(BinaryOp::Shl)),
BinaryOperator::Shr => Ok(BinaryOpType::Arithmetic(BinaryOp::Shr)),
BinaryOperator::BitAnd => Ok(BinaryOpType::Arithmetic(BinaryOp::BitAnd)),
BinaryOperator::BitOr => Ok(BinaryOpType::Arithmetic(BinaryOp::BitOr)),
BinaryOperator::BitXor => Ok(BinaryOpType::Arithmetic(BinaryOp::BitXor)),
BinaryOperator::Equal => Ok(BinaryOpType::Comparison(CompareOp::Eq)),
BinaryOperator::NotEqual => Ok(BinaryOpType::Comparison(CompareOp::Ne)),
BinaryOperator::Less => Ok(BinaryOpType::Comparison(CompareOp::Lt)),

View File

@ -126,6 +126,37 @@ impl MirCompiler {
return Err(format!("Core-13 strict: final MIR contains {} legacy ops", legacy_count));
}
}
// Core-13 pure: allow only the 13 canonical ops; reject all others
if crate::config::env::mir_core13_pure() {
let mut bad = 0usize;
let is_allowed = |i: &MirInstruction| -> bool {
matches!(i,
MirInstruction::Const { .. }
| MirInstruction::BinOp { .. }
| MirInstruction::Compare { .. }
| MirInstruction::Jump { .. }
| MirInstruction::Branch { .. }
| MirInstruction::Return { .. }
| MirInstruction::Phi { .. }
| MirInstruction::Call { .. }
| MirInstruction::BoxCall { .. }
| MirInstruction::ExternCall { .. }
| MirInstruction::TypeOp { .. }
| MirInstruction::Safepoint
| MirInstruction::Barrier { .. }
)
};
for (_fname, function) in &module.functions {
for (_bb, block) in &function.blocks {
for inst in &block.instructions { if !is_allowed(inst) { bad += 1; } }
if let Some(term) = &block.terminator { if !is_allowed(term) { bad += 1; } }
}
}
if bad > 0 {
return Err(format!("Core-13 pure strict: final MIR contains {} non-Core-13 ops", bad));
}
}
// Verify the generated MIR
let verification_result = self.verifier.verify_module(&module);
@ -292,6 +323,7 @@ mod tests {
#[test]
fn test_lowering_await_expression() {
if crate::config::env::mir_core13_pure() { eprintln!("[TEST] skip await under Core-13 pure mode"); return; }
// Build AST: await 1 (semantic is nonsensical but should emit Await)
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: crate::ast::Span::unknown() }), span: crate::ast::Span::unknown() };
let mut compiler = MirCompiler::new();
@ -302,6 +334,7 @@ mod tests {
#[test]
fn test_await_has_checkpoints() {
if crate::config::env::mir_core13_pure() { eprintln!("[TEST] skip await under Core-13 pure mode"); return; }
use crate::ast::{LiteralValue, Span};
// Build: await 1
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
@ -317,6 +350,7 @@ mod tests {
#[test]
fn test_rewritten_await_still_checkpoints() {
if crate::config::env::mir_core13_pure() { eprintln!("[TEST] skip await under Core-13 pure mode"); return; }
use crate::ast::{LiteralValue, Span};
// Enable rewrite so Await → ExternCall(env.future.await)
std::env::set_var("NYASH_REWRITE_FUTURE", "1");
@ -388,6 +422,11 @@ mod tests {
#[test]
fn test_try_catch_compilation() {
// Core-13 pure モードでは Try/Catch 命令は許容集合外のためスキップ
if crate::config::env::mir_core13_pure() {
eprintln!("[TEST] skip try/catch under Core-13 pure mode");
return;
}
let mut compiler = MirCompiler::new();
let try_catch_ast = ASTNode::TryCatch {

View File

@ -85,7 +85,12 @@ impl MirOptimizer {
// Lowererがより正確にBox種別を選べるようにする。
let updates = crate::mir::passes::type_hints::propagate_param_type_hints(module);
if updates > 0 { stats.intrinsic_optimizations += updates as usize; }
// Pass 7 (optional): Core-13 pure normalization
if crate::config::env::mir_core13_pure() {
stats.merge(self.normalize_pure_core13(module));
}
if self.debug {
println!("✅ Optimization complete: {}", stats);
}
@ -115,6 +120,120 @@ impl MirOptimizer {
stats
}
/// Core-13 "pure" normalization: rewrite a few non-13 ops to allowed forms.
/// - Load(dst, ptr) => ExternCall(Some dst, env.local.get, [ptr])
/// - Store(val, ptr) => ExternCall(None, env.local.set, [ptr, val])
/// - NewBox(dst, T, args...) => ExternCall(Some dst, env.box.new, [Const String(T), args...])
/// - UnaryOp:
/// Neg x => BinOp(Sub, Const 0, x)
/// Not x => Compare(Eq, x, Const false)
/// BitNot x => BinOp(BitXor, x, Const(-1))
fn normalize_pure_core13(&mut self, module: &mut MirModule) -> OptimizationStats {
use super::instruction::ConstValue;
use super::{MirInstruction as I, BinaryOp, CompareOp};
let mut stats = OptimizationStats::new();
for (_fname, function) in &mut module.functions {
for (_bb, block) in &mut function.blocks {
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 8);
let old = std::mem::take(&mut block.instructions);
for inst in old.into_iter() {
match inst {
I::Load { dst, ptr } => {
out.push(I::ExternCall {
dst: Some(dst), iface_name: "env.local".to_string(), method_name: "get".to_string(),
args: vec![ptr], effects: super::EffectMask::READ,
});
stats.intrinsic_optimizations += 1;
}
I::Store { value, ptr } => {
out.push(I::ExternCall {
dst: None, iface_name: "env.local".to_string(), method_name: "set".to_string(),
args: vec![ptr, value], effects: super::EffectMask::WRITE,
});
stats.intrinsic_optimizations += 1;
}
I::NewBox { dst, box_type, mut args } => {
// prepend type name as Const String
let ty_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
out.push(I::Const { dst: ty_id, value: ConstValue::String(box_type) });
let mut call_args = Vec::with_capacity(1 + args.len());
call_args.push(ty_id);
call_args.append(&mut args);
out.push(I::ExternCall {
dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(),
args: call_args, effects: super::EffectMask::PURE, // constructor is logically alloc; conservatively PURE here
});
stats.intrinsic_optimizations += 1;
}
I::UnaryOp { dst, op, operand } => {
match op {
super::UnaryOp::Neg => {
let zero = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
out.push(I::Const { dst: zero, value: ConstValue::Integer(0) });
out.push(I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand });
}
super::UnaryOp::Not => {
let f = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
out.push(I::Const { dst: f, value: ConstValue::Bool(false) });
out.push(I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f });
}
super::UnaryOp::BitNot => {
let all1 = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
out.push(I::Const { dst: all1, value: ConstValue::Integer(-1) });
out.push(I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1 });
}
}
stats.intrinsic_optimizations += 1;
}
other => out.push(other),
}
}
block.instructions = out;
if let Some(term) = block.terminator.take() {
block.terminator = Some(match term {
I::Load { dst, ptr } => {
I::ExternCall { dst: Some(dst), iface_name: "env.local".to_string(), method_name: "get".to_string(), args: vec![ptr], effects: super::EffectMask::READ }
}
I::Store { value, ptr } => {
I::ExternCall { dst: None, iface_name: "env.local".to_string(), method_name: "set".to_string(), args: vec![ptr, value], effects: super::EffectMask::WRITE }
}
I::NewBox { dst, box_type, mut args } => {
let ty_id = super::ValueId::new(function.next_value_id);
function.next_value_id += 1;
block.instructions.push(I::Const { dst: ty_id, value: ConstValue::String(box_type) });
let mut call_args = Vec::with_capacity(1 + args.len());
call_args.push(ty_id);
call_args.append(&mut args);
I::ExternCall { dst: Some(dst), iface_name: "env.box".to_string(), method_name: "new".to_string(), args: call_args, effects: super::EffectMask::PURE }
}
I::UnaryOp { dst, op, operand } => {
match op {
super::UnaryOp::Neg => {
let zero = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
block.instructions.push(I::Const { dst: zero, value: ConstValue::Integer(0) });
I::BinOp { dst, op: BinaryOp::Sub, lhs: zero, rhs: operand }
}
super::UnaryOp::Not => {
let f = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
block.instructions.push(I::Const { dst: f, value: ConstValue::Bool(false) });
I::Compare { dst, op: CompareOp::Eq, lhs: operand, rhs: f }
}
super::UnaryOp::BitNot => {
let all1 = super::ValueId::new(function.next_value_id); function.next_value_id += 1;
block.instructions.push(I::Const { dst: all1, value: ConstValue::Integer(-1) });
I::BinOp { dst, op: BinaryOp::BitXor, lhs: operand, rhs: all1 }
}
}
}
other => other,
});
}
}
}
stats
}
/// Eliminate dead code in a single function
fn eliminate_dead_code_in_function(&mut self, function: &mut MirFunction) -> usize {

View File

@ -133,7 +133,7 @@ impl NyashParser {
/// AND演算子をパース: &&
fn parse_and(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_equality()?;
let mut expr = self.parse_bit_or()?;
while self.match_token(&TokenType::AND) {
let operator = BinaryOperator::And;
@ -153,6 +153,42 @@ impl NyashParser {
Ok(expr)
}
/// ビットOR: |
fn parse_bit_or(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_bit_xor()?;
while self.match_token(&TokenType::BIT_OR) {
let operator = BinaryOperator::BitOr;
self.advance();
let right = self.parse_bit_xor()?;
expr = ASTNode::BinaryOp { operator, left: Box::new(expr), right: Box::new(right), span: Span::unknown() };
}
Ok(expr)
}
/// ビットXOR: ^
fn parse_bit_xor(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_bit_and()?;
while self.match_token(&TokenType::BIT_XOR) {
let operator = BinaryOperator::BitXor;
self.advance();
let right = self.parse_bit_and()?;
expr = ASTNode::BinaryOp { operator, left: Box::new(expr), right: Box::new(right), span: Span::unknown() };
}
Ok(expr)
}
/// ビットAND: &
fn parse_bit_and(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_equality()?;
while self.match_token(&TokenType::BIT_AND) {
let operator = BinaryOperator::BitAnd;
self.advance();
let right = self.parse_equality()?;
expr = ASTNode::BinaryOp { operator, left: Box::new(expr), right: Box::new(right), span: Span::unknown() };
}
Ok(expr)
}
/// 等値演算子をパース: == !=
fn parse_equality(&mut self) -> Result<ASTNode, ParseError> {
@ -229,45 +265,55 @@ impl NyashParser {
Ok(expr)
}
/// 項をパース: + - >>
/// 項をパース: + -
fn parse_term(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_factor()?;
let mut expr = self.parse_shift()?;
while self.match_token(&TokenType::PLUS) || self.match_token(&TokenType::MINUS) || self.match_token(&TokenType::ARROW) {
if self.match_token(&TokenType::ARROW) {
// >> Arrow演算子
self.advance();
let right = self.parse_factor()?;
expr = ASTNode::Arrow {
sender: Box::new(expr),
receiver: Box::new(right),
span: Span::unknown(),
};
} else {
let operator = match &self.current_token().token_type {
TokenType::PLUS => BinaryOperator::Add,
TokenType::MINUS => BinaryOperator::Subtract,
_ => unreachable!(),
};
self.advance();
let right = self.parse_factor()?;
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let name = match operator { BinaryOperator::Add=>"add", BinaryOperator::Subtract=>"sub", _=>"term" };
let ok = crate::grammar::engine::get().syntax_is_allowed_binop(name);
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop '{}' not allowed by syntax rules", name); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),
right: Box::new(right),
span: Span::unknown(),
};
while self.match_token(&TokenType::PLUS) || self.match_token(&TokenType::MINUS) {
let operator = match &self.current_token().token_type {
TokenType::PLUS => BinaryOperator::Add,
TokenType::MINUS => BinaryOperator::Subtract,
_ => unreachable!(),
};
self.advance();
let right = self.parse_shift()?;
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
let name = match operator { BinaryOperator::Add=>"add", BinaryOperator::Subtract=>"sub", _=>"term" };
let ok = crate::grammar::engine::get().syntax_is_allowed_binop(name);
if !ok { eprintln!("[GRAMMAR-DIFF][Parser] binop '{}' not allowed by syntax rules", name); }
}
expr = ASTNode::BinaryOp {
operator,
left: Box::new(expr),
right: Box::new(right),
span: Span::unknown(),
};
}
Ok(expr)
}
/// シフトをパース: << >>
fn parse_shift(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_factor()?;
loop {
if self.match_token(&TokenType::SHIFT_LEFT) {
self.advance();
let rhs = self.parse_factor()?;
expr = ASTNode::BinaryOp { operator: BinaryOperator::Shl, left: Box::new(expr), right: Box::new(rhs), span: Span::unknown() };
continue;
}
if self.match_token(&TokenType::SHIFT_RIGHT) {
self.advance();
let rhs = self.parse_factor()?;
expr = ASTNode::BinaryOp { operator: BinaryOperator::Shr, left: Box::new(expr), right: Box::new(rhs), span: Span::unknown() };
continue;
}
break;
}
Ok(expr)
}
/// 因子をパース: * /
fn parse_factor(&mut self) -> Result<ASTNode, ParseError> {
let mut expr = self.parse_unary()?;

View File

@ -83,57 +83,6 @@ impl NyashRunner {
}
return;
}
// CLI using/module overrides (MVP): apply early so JSON pipeline can observe them
if self.config.using.is_some() || self.config.using_path.is_some() || self.config.modules.is_some()
|| std::env::var("NYASH_USING_PATH").is_ok() || std::env::var("NYASH_MODULES").is_ok() {
let mut using_paths: Vec<String> = Vec::new();
if let Some(p) = self.config.using_path.clone() { for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } }
if let Ok(p) = std::env::var("NYASH_USING_PATH") { for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } }
if using_paths.is_empty() { using_paths.extend(["apps","lib","."].into_iter().map(|s| s.to_string())); }
// modules mapping
let mut modules: Vec<(String,String)> = Vec::new();
if let Some(m) = self.config.modules.clone() { for ent in m.split(',') { if let Some((k,v)) = ent.split_once('=') { let k=k.trim(); let v=v.trim(); if !k.is_empty() && !v.is_empty() { modules.push((k.to_string(), v.to_string())); } } } }
if let Ok(ms) = std::env::var("NYASH_MODULES") { for ent in ms.split(',') { if let Some((k,v)) = ent.split_once('=') { let k=k.trim(); let v=v.trim(); if !k.is_empty() && !v.is_empty() { modules.push((k.to_string(), v.to_string())); } } } }
for (ns, path) in &modules { let sb = crate::box_trait::StringBox::new(path.clone()); crate::runtime::modules_registry::set(ns.clone(), Box::new(sb)); }
// using specs
let mut pending_using: Vec<(String, Option<String>, bool)> = Vec::new(); // (target, alias, is_path)
if let Some(u) = self.config.using.clone() {
for ent in u.split(',') {
let s = ent.trim().trim_end_matches(';').trim(); if s.is_empty() { continue; }
let (tgt, alias) = if let Some(pos) = s.find(" as ") { (s[..pos].trim().to_string(), Some(s[pos+4..].trim().to_string())) } else { (s.to_string(), None) };
let is_path = tgt.starts_with('"') || tgt.starts_with("./") || tgt.starts_with('/') || tgt.ends_with(".nyash");
pending_using.push((tgt.trim_matches('"').to_string(), alias, is_path));
}
}
// Resolve using
for (tgt, alias, is_path) in pending_using.into_iter() {
if is_path {
let missing = !std::path::Path::new(&tgt).exists();
if missing {
if std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1") {
eprintln!("❌ using: path not found: {}", tgt);
std::process::exit(1);
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[using] path not found (continuing): {}", tgt);
}
}
}
let value = if is_path { tgt.clone() } else if let Some((_n,p)) = modules.iter().find(|(n,_)| n==&tgt) { p.clone() } else {
let rel = tgt.replace('.', "/") + ".nyash";
let mut found: Option<String> = None;
for base in &using_paths { let cand = std::path::Path::new(base).join(&rel); if cand.exists() { found = Some(cand.to_string_lossy().to_string()); break; } }
if found.is_none() && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!("[using] unresolved '{}'; tried {}", tgt, using_paths.join(":"));
}
found.unwrap_or(tgt.clone())
};
let sb = crate::box_trait::StringBox::new(value.clone());
crate::runtime::modules_registry::set(tgt.clone(), Box::new(sb));
if let Some(a) = alias { let sb2 = crate::box_trait::StringBox::new(value); crate::runtime::modules_registry::set(a, Box::new(sb2)); }
}
}
// Phase-15: JSON IR v0 bridge (stdin/file)
if self.config.ny_parser_pipe || self.config.json_file.is_some() {
let json = if let Some(path) = &self.config.json_file {
@ -153,12 +102,8 @@ impl NyashRunner {
Ok(module) => {
// Optional dump via env verbose
json_v0_bridge::maybe_dump_mir(&module);
// Execute via selected backend (vm or interpreter)
if self.config.backend == "vm" {
self.execute_vm_module(&module);
} else {
self.execute_mir_module(&module);
}
// Execute via MIR interpreter
self.execute_mir_module(&module);
return;
}
Err(e) => {
@ -185,9 +130,6 @@ impl NyashRunner {
if let Some(ref filename) = self.config.file {
if let Ok(code) = fs::read_to_string(filename) {
// Scan first 128 lines for directives
let mut pending_using: Vec<(String, Option<String>)> = Vec::new();
let mut pending_modules: Vec<(String, String)> = Vec::new();
let mut using_paths: Vec<String> = Vec::new();
for (i, line) in code.lines().take(128).enumerate() {
let l = line.trim();
if !(l.starts_with("//") || l.starts_with("#!") || l.is_empty()) {
@ -201,24 +143,6 @@ impl NyashRunner {
let key = k.trim(); let val = v.trim();
if !key.is_empty() { std::env::set_var(key, val); }
}
} else if let Some(dir) = rest.strip_prefix("@using ") {
// @using ns[ as Alias]
let s = dir.trim().trim_end_matches(';').trim();
let (ns, alias) = if let Some(pos) = s.find(" as ") {
(s[..pos].trim().to_string(), Some(s[pos+4..].trim().to_string()))
} else { (s.to_string(), None) };
pending_using.push((ns, alias));
} else if let Some(dir) = rest.strip_prefix("@module ") {
// @module ns=path
if let Some((ns, path)) = dir.split_once('=') {
let ns = ns.trim().to_string();
let path = path.trim().trim_matches('"').to_string();
pending_modules.push((ns, path));
}
} else if let Some(dir) = rest.strip_prefix("@using-path ") {
// @using-path apps:lib:. (':' separated)
let s = dir.trim();
for p in s.split(':') { let p = p.trim(); if !p.is_empty() { using_paths.push(p.to_string()); } }
} else if rest == "@jit-debug" {
std::env::set_var("NYASH_JIT_EXEC", "1");
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
@ -236,50 +160,6 @@ impl NyashRunner {
}
}
}
// Env overrides for using rules
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_paths.push(p.to_string()); } }
}
if let Ok(mods) = std::env::var("NYASH_MODULES") {
for ent in mods.split(',') {
if let Some((k,v)) = ent.split_once('=') {
let k = k.trim(); let v = v.trim();
if !k.is_empty() && !v.is_empty() { pending_modules.push((k.to_string(), v.to_string())); }
}
}
}
// Apply pending modules to registry as StringBox (path or ns token)
for (ns, path) in pending_modules.iter() {
let sb = nyash_rust::box_trait::StringBox::new(path.clone());
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
}
// Resolve pending using via modules map or using_paths (best-effort)
for (ns, alias) in pending_using.iter() {
// direct mapping first
let value = if let Some((_n, p)) = pending_modules.iter().find(|(n, _)| n == ns) {
p.clone()
} else {
// try search paths: <path>/<ns as a/b/c>.nyash
let rel = ns.replace('.', "/") + ".nyash";
let mut found = None;
if let Some(dir) = std::path::Path::new(filename).parent() {
let cand = dir.join(&rel);
if cand.exists() { found = Some(cand.to_string_lossy().to_string()); }
}
if found.is_none() {
for base in &using_paths { let cand = std::path::Path::new(base).join(&rel); if cand.exists() { found = Some(cand.to_string_lossy().to_string()); break; } }
}
found.unwrap_or_else(|| ns.clone())
};
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
if let Some(a) = alias {
let sb2 = nyash_rust::box_trait::StringBox::new(value);
nyash_rust::runtime::modules_registry::set(a.clone(), Box::new(sb2));
}
}
}
}
@ -1064,23 +944,9 @@ impl NyashRunner {
}
#[cfg(not(windows))]
{
// Ensure NyRT static library exists
let nyrt_root = cwd.join("target").join("release").join("libnyrt.a");
let nyrt_crate = cwd.join("crates").join("nyrt").join("target").join("release").join("libnyrt.a");
if !nyrt_root.exists() && !nyrt_crate.exists() {
// Try to build crates/nyrt
let mut cmd = std::process::Command::new("cargo");
cmd.arg("build");
cmd.arg("--release");
cmd.current_dir(cwd.join("crates").join("nyrt"));
println!("[link] building NyRT (libnyrt.a) ...");
let st = cmd.status().map_err(|e| format!("spawn cargo (nyrt): {}", e))?;
if !st.success() { return Err("failed to build NyRT (libnyrt.a)".into()); }
}
let status = std::process::Command::new("cc")
.arg(&obj_path)
.args(["-L", &cwd.join("target").join("release").display().to_string()])
.args(["-L", &cwd.join("crates").join("nyrt").join("target").join("release").display().to_string()])
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive", "-lpthread", "-ldl", "-lm"])
.args(["-o", &out_path.display().to_string()])
.status().map_err(|e| format!("spawn cc: {}", e))?;
@ -1092,55 +958,6 @@ impl NyashRunner {
}
impl NyashRunner {
/// Execute a prepared MIR module via the VM
fn execute_vm_module(&self, module: &crate::mir::MirModule) {
use crate::backend::VM;
use crate::mir::MirType;
use crate::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
use crate::boxes::FloatBox;
let mut vm = VM::new();
match vm.execute_module(module) {
Ok(result) => {
if let Some(func) = module.functions.get("main") {
let (ety, sval) = match &func.signature.return_type {
MirType::Float => {
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() {
("Float", format!("{}", fb.value))
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Float", format!("{}", ib.value as f64))
} else { ("Float", result.to_string_box().value) }
}
MirType::Integer => {
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Integer", ib.value.to_string())
} else { ("Integer", result.to_string_box().value) }
}
MirType::Bool => {
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
("Bool", bb.value.to_string())
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Bool", (ib.value != 0).to_string())
} else { ("Bool", result.to_string_box().value) }
}
MirType::String => {
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
("String", sb.value.clone())
} else { ("String", result.to_string_box().value) }
}
_ => { (result.type_name(), result.to_string_box().value) }
};
println!("ResultType(VM): {}", ety);
println!("Result: {}", sval);
} else {
println!("Result: {:?}", result);
}
}
Err(e) => {
eprintln!("❌ VM execution error: {}", e);
std::process::exit(1);
}
}
}
/// Run a file through independent JIT engine (no VM execute loop)
fn run_file_jit_direct(&self, filename: &str) {
use std::fs;

View File

@ -6,6 +6,54 @@ use std::sync::Arc;
impl NyashRunner {
/// Execute VM mode (split)
pub(crate) fn execute_vm_mode(&self, filename: &str) {
// Enforce plugin-first policy for VM on this branch (deterministic):
// - Initialize plugin host if not yet loaded
// - Prefer plugin implementations for core boxes
// - Optionally fail fast when plugins are missing (NYASH_VM_PLUGIN_STRICT=1)
{
// Initialize unified registry globals (idempotent)
nyash_rust::runtime::init_global_unified_registry();
// Init plugin host from nyash.toml if not yet loaded
let need_init = {
let host = nyash_rust::runtime::get_global_plugin_host();
host.read().map(|h| h.config_ref().is_none()).unwrap_or(true)
};
if need_init {
let _ = nyash_rust::runtime::init_global_plugin_host("nyash.toml");
crate::runner_plugin_init::init_bid_plugins();
}
// Prefer plugin-builtins for core types unless explicitly disabled
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() {
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
}
// Build stable override list
let mut override_types: 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![] };
for t in [
"FileBox", "TOMLBox", // IO/config
"ConsoleBox", "StringBox", "IntegerBox", // core value-ish
"ArrayBox", "MapBox", // collections
"MathBox", "TimeBox" // math/time helpers
] {
if !override_types.iter().any(|x| x == t) { override_types.push(t.to_string()); }
}
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
// Strict mode: verify providers exist for override types
if std::env::var("NYASH_VM_PLUGIN_STRICT").ok().as_deref() == Some("1") {
let v2 = nyash_rust::runtime::get_global_registry();
let mut missing: Vec<String> = Vec::new();
for t in ["FileBox","ConsoleBox","ArrayBox","MapBox","StringBox","IntegerBox"] {
if v2.get_provider(t).is_none() { missing.push(t.to_string()); }
}
if !missing.is_empty() {
eprintln!("❌ VM plugin-first strict: missing providers for: {:?}", missing);
std::process::exit(1);
}
}
}
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,
@ -78,43 +126,68 @@ impl NyashRunner {
match vm.execute_module(&module_vm) {
Ok(result) => {
println!("✅ VM execution completed successfully!");
// Pretty-print using MIR return type when available to avoid Void-looking floats/bools
if let Some(func) = compile_result.module.functions.get("main") {
// Pretty-print with coercions for plugin-backed values
// Prefer MIR signature when available, but fall back to runtime coercions to keep VM/JIT consistent.
let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") {
use nyash_rust::mir::MirType;
use nyash_rust::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
use nyash_rust::boxes::FloatBox;
let (ety, sval) = match &func.signature.return_type {
match &func.signature.return_type {
MirType::Float => {
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() {
("Float", format!("{}", fb.value))
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Float", format!("{}", ib.value as f64))
} else { ("Float", result.to_string_box().value) }
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
("String", s)
} else {
(result.type_name(), result.to_string_box().value)
}
}
MirType::Integer => {
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Integer", ib.value.to_string())
} else { ("Integer", result.to_string_box().value) }
} else if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) {
("Integer", i.to_string())
} else {
(result.type_name(), result.to_string_box().value)
}
}
MirType::Bool => {
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
("Bool", bb.value.to_string())
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Bool", (ib.value != 0).to_string())
} else { ("Bool", result.to_string_box().value) }
} else {
(result.type_name(), result.to_string_box().value)
}
}
MirType::String => {
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
("String", sb.value.clone())
} else { ("String", result.to_string_box().value) }
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
("String", s)
} else {
(result.type_name(), result.to_string_box().value)
}
}
_ => { (result.type_name(), result.to_string_box().value) }
};
println!("ResultType(MIR): {}", ety);
println!("Result: {}", sval);
_ => {
if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) {
("Integer", i.to_string())
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
("String", s)
} else { (result.type_name(), result.to_string_box().value) }
}
}
} else {
println!("Result: {:?}", result);
}
if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) {
("Integer", i.to_string())
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
("String", s)
} else { (result.type_name(), result.to_string_box().value) }
};
println!("ResultType(MIR): {}", ety);
println!("Result: {}", sval);
},
Err(e) => { eprintln!("❌ VM execution error: {}", e); process::exit(1); }
}

View File

@ -34,9 +34,10 @@ static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1, slot: Some(20) },
ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2, slot: Some(21) },
ExternSpec { iface: "env.future", method: "await", min_arity: 1, max_arity: 1, slot: Some(22) },
// modules (minimal registry)
ExternSpec { iface: "env.modules", method: "set", min_arity: 2, max_arity: 2, slot: None },
ExternSpec { iface: "env.modules", method: "get", min_arity: 1, max_arity: 1, slot: None },
// core-13 pure support shims
ExternSpec { iface: "env.local", method: "get", min_arity: 1, max_arity: 1, slot: Some(40) },
ExternSpec { iface: "env.local", method: "set", min_arity: 2, max_arity: 2, slot: Some(41) },
ExternSpec { iface: "env.box", method: "new", min_arity: 1, max_arity: 255, slot: Some(50) },
]);
pub fn resolve(iface: &str, method: &str) -> Option<ExternSpec> {

View File

@ -266,26 +266,49 @@ impl PluginLoaderV2 {
// Encode TLV args via shared helper (numeric→string→toString)
let tlv = crate::runtime::plugin_ffi_common::encode_args(args);
let (_code, out_len, out) = super::host_bridge::invoke_alloc(plugin.invoke_fn, type_id, method.method_id, instance_id, &tlv);
// Minimal decoding by method name
match method_name {
// Expect UTF-8 string result in bytes → StringBox
"toUtf8" | "toString" => {
let s = String::from_utf8_lossy(&out[0..out_len]).to_string();
return Ok(Some(Box::new(crate::box_trait::StringBox::new(s))));
}
// Expect IntegerBox via little-endian i64 in first 8 bytes
"get" => {
if out_len >= 8 { let mut buf=[0u8;8]; buf.copy_from_slice(&out[0..8]); let n=i64::from_le_bytes(buf); return Ok(Some(Box::new(crate::box_trait::IntegerBox::new(n)))) }
return Ok(Some(Box::new(crate::box_trait::IntegerBox::new(0))));
}
// Float path (approx): read 8 bytes as f64 and Box as FloatBox
"toDouble" => {
if out_len >= 8 { let mut buf=[0u8;8]; buf.copy_from_slice(&out[0..8]); let x=f64::from_le_bytes(buf); return Ok(Some(Box::new(crate::boxes::FloatBox::new(x)))) }
return Ok(Some(Box::new(crate::boxes::FloatBox::new(0.0))));
}
_ => {}
// Decode TLV (first entry) generically
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) {
let bx: Box<dyn NyashBox> = match tag {
1 => Box::new(crate::box_trait::BoolBox::new(crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false))),
2 => Box::new(crate::box_trait::IntegerBox::new(crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or(0) as i64)),
3 => {
// i64 payload
if payload.len() == 8 { let mut b=[0u8;8]; b.copy_from_slice(payload); Box::new(crate::box_trait::IntegerBox::new(i64::from_le_bytes(b))) }
else { Box::new(crate::box_trait::IntegerBox::new(0)) }
}
5 => {
let x = crate::runtime::plugin_ffi_common::decode::f64(payload).unwrap_or(0.0);
Box::new(crate::boxes::FloatBox::new(x))
}
6 | 7 => {
let s = crate::runtime::plugin_ffi_common::decode::string(payload);
Box::new(crate::box_trait::StringBox::new(s))
}
8 => {
// Plugin handle (type_id, instance_id) → wrap into PluginBoxV2
if let Some((ret_type, inst)) = crate::runtime::plugin_ffi_common::decode::plugin_handle(payload) {
let handle = Arc::new(super::types::PluginHandleInner {
type_id: ret_type,
invoke_fn: plugin.invoke_fn,
instance_id: inst,
fini_method_id: None,
finalized: std::sync::atomic::AtomicBool::new(false),
});
Box::new(super::types::PluginBoxV2 { box_type: box_type.to_string(), inner: handle })
} else { Box::new(crate::box_trait::VoidBox::new()) }
}
9 => {
// Host handle (u64) → try to map back to BoxRef, else void
if let Some(u) = crate::runtime::plugin_ffi_common::decode::u64(payload) {
if let Some(arc) = crate::runtime::host_handles::get(u) { return Ok(Some(arc.share_box())); }
}
Box::new(crate::box_trait::VoidBox::new())
}
_ => Box::new(crate::box_trait::VoidBox::new()),
};
return Ok(Some(bx));
}
Ok(None)
Ok(Some(Box::new(crate::box_trait::VoidBox::new())))
}
pub fn create_box(&self, box_type: &str, _args: &[Box<dyn NyashBox>]) -> BidResult<Box<dyn NyashBox>> {

View File

@ -68,6 +68,8 @@ mod tests {
#[test]
fn default_none_on_missing_or_invalid() {
// Ensure env override is not present for this test
env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL");
let dir = tempdir().unwrap();
let file = dir.path().join("nyash.toml");
fs::write(&file, "[syntax]\nsugar_level='unknown'\n").unwrap();
@ -77,4 +79,3 @@ mod tests {
assert_eq!(cfg2.level, SugarLevel::None);
}
}

View File

@ -0,0 +1,54 @@
#[test]
fn llvm_bitops_compile_and_exec() {
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirInstruction, BasicBlockId, ConstValue, MirType, instruction::BinaryOp};
use crate::backend::vm::VM;
// Build MIR: compute sum of bitwise/shift ops -> 48
let sig = FunctionSignature { name: "Main.main".into(), params: vec![], return_type: MirType::Integer, effects: Default::default() };
let mut f = MirFunction::new(sig, BasicBlockId::new(0));
let bb = f.entry_block;
// Constants
let c5 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c5, value: ConstValue::Integer(5) });
let c3 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c3, value: ConstValue::Integer(3) });
let c2 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c2, value: ConstValue::Integer(2) });
let c1 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c1, value: ConstValue::Integer(1) });
let c32 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c32, value: ConstValue::Integer(32) });
let c5_sh = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c5_sh, value: ConstValue::Integer(5) });
let c3_sh = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: c3_sh, value: ConstValue::Integer(3) });
// a = 5 & 3 -> 1
let a = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: a, op: BinaryOp::BitAnd, lhs: c5, rhs: c3 });
// b = 5 | 2 -> 7
let b = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: b, op: BinaryOp::BitOr, lhs: c5, rhs: c2 });
// c = 5 ^ 1 -> 4
let c = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: c, op: BinaryOp::BitXor, lhs: c5, rhs: c1 });
// d = 1 << 5 -> 32
let d = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: d, op: BinaryOp::Shl, lhs: c1, rhs: c5_sh });
// e = 32 >> 3 -> 4
let e = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: e, op: BinaryOp::Shr, lhs: c32, rhs: c3_sh });
// sum = a + b + c + d + e
let t1 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: t1, op: BinaryOp::Add, lhs: a, rhs: b });
let t2 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: t2, op: BinaryOp::Add, lhs: t1, rhs: c });
let t3 = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: t3, op: BinaryOp::Add, lhs: t2, rhs: d });
let sum = f.next_value_id(); f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BinOp { dst: sum, op: BinaryOp::Add, lhs: t3, rhs: e });
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(sum) });
let mut m = MirModule::new("bitops".into()); m.add_function(f);
// VM executes to 48
let mut vm = VM::new();
let out = vm.execute_module(&m).expect("vm exec");
assert_eq!(out.to_string_box().value, "48");
// LLVM: ensure lowering/emit succeeds; compile_and_execute should also return 48 (via MIR interpreter fallback)
#[cfg(feature = "llvm")]
{
use crate::backend::llvm;
let tmp = format!("{}/target/aot_objects/test_bitops", env!("CARGO_MANIFEST_DIR"));
llvm::compile_to_object(&m, &format!("{}.o", tmp)).expect("llvm emit");
let out2 = llvm::compile_and_execute(&m, &tmp).expect("llvm compile&exec");
assert_eq!(out2.to_string_box().value, "48");
}
}

View File

@ -0,0 +1,83 @@
mod tests {
use crate::mir::{
MirModule, MirFunction, FunctionSignature, MirType, BasicBlock, BasicBlockId, ValueId,
MirInstruction as I,
};
use crate::mir::optimizer::MirOptimizer;
fn mk_func(name: &str) -> (MirFunction, BasicBlockId) {
let sig = FunctionSignature { name: name.to_string(), params: vec![], return_type: MirType::Void, effects: crate::mir::effect::EffectMask::PURE };
let entry = BasicBlockId::new(0);
(MirFunction::new(sig, entry), entry)
}
#[test]
fn core13_normalizes_array_get_set_to_boxcall() {
// Build function with ArrayGet/ArraySet, then optimize under Core-13
let (mut f, bb0) = mk_func("core13_array_norm");
let mut b0 = BasicBlock::new(bb0);
let arr = ValueId::new(0);
let idx = ValueId::new(1);
let val = ValueId::new(2);
let dst = ValueId::new(3);
b0.add_instruction(I::ArrayGet { dst, array: arr, index: idx });
b0.add_instruction(I::ArraySet { array: arr, index: idx, value: val });
b0.add_instruction(I::Return { value: None });
f.add_block(b0);
let mut m = MirModule::new("test_core13_array".into());
m.add_function(f);
let mut opt = MirOptimizer::new();
let _ = opt.optimize_module(&mut m);
let func = m.get_function("core13_array_norm").unwrap();
let block = func.get_block(bb0).unwrap();
// Expect only BoxCall in place of ArrayGet/ArraySet
let mut saw_get = false;
let mut saw_set = false;
for inst in block.all_instructions() {
match inst {
I::BoxCall { method, .. } if method == "get" => { saw_get = true; },
I::BoxCall { method, .. } if method == "set" => { saw_set = true; },
I::ArrayGet { .. } | I::ArraySet { .. } => panic!("legacy Array* must be normalized under Core-13"),
_ => {}
}
}
assert!(saw_get && saw_set, "expected BoxCall(get) and BoxCall(set) present");
}
#[test]
fn core13_normalizes_ref_get_set_to_boxcall() {
// Build function with RefGet/RefSet, then optimize under Core-13
let (mut f, bb0) = mk_func("core13_ref_norm");
let mut b0 = BasicBlock::new(bb0);
let r = ValueId::new(10);
let v = ValueId::new(11);
let dst = ValueId::new(12);
b0.add_instruction(I::RefGet { dst, reference: r, field: "foo".to_string() });
b0.add_instruction(I::RefSet { reference: r, field: "bar".to_string(), value: v });
b0.add_instruction(I::Return { value: None });
f.add_block(b0);
let mut m = MirModule::new("test_core13_ref".into());
m.add_function(f);
let mut opt = MirOptimizer::new();
let _ = opt.optimize_module(&mut m);
let func = m.get_function("core13_ref_norm").unwrap();
let block = func.get_block(bb0).unwrap();
// Expect BoxCall(getField/setField), a Barrier(Write) before setField, and no RefGet/RefSet remain
let mut saw_get_field = false;
let mut saw_set_field = false;
for inst in block.all_instructions() {
match inst {
I::BoxCall { method, .. } if method == "getField" => { saw_get_field = true; },
I::BoxCall { method, .. } if method == "setField" => { saw_set_field = true; },
I::RefGet { .. } | I::RefSet { .. } => panic!("legacy Ref* must be normalized under Core-13"),
_ => {}
}
}
assert!(saw_get_field && saw_set_field, "expected BoxCall(getField) and BoxCall(setField) present");
}
}

View File

@ -0,0 +1,20 @@
use crate::parser::NyashParser;
use crate::ast::ASTNode;
#[test]
fn parse_bitops_and_shift_precedence() {
// Expression: 1 + 2 << 3 & 7
// Precedence: shift before add, then &: (1 + (2 << 3)) & 7
let code = "return 1 + 2 << 3 & 7";
let ast = NyashParser::parse_from_string(code).expect("parse ok");
// Just ensure it parses into a Program and contains a Return; deeper tree checks are optional here
fn has_return(n: &ASTNode) -> bool {
match n {
ASTNode::Program { statements, .. } => statements.iter().any(has_return),
ASTNode::Return { .. } => true,
_ => false,
}
}
assert!(has_return(&ast));
}

View File

@ -0,0 +1,25 @@
use crate::parser::NyashParser;
use crate::interpreter::NyashInterpreter;
#[test]
fn vm_exec_bitwise_and_shift() {
let code = r#"
return (5 & 3) + (5 | 2) + (5 ^ 1) + (1 << 5) + (32 >> 3)
"#;
let ast = NyashParser::parse_from_string(code).expect("parse ok");
let mut interp = NyashInterpreter::new();
let out = interp.execute(ast).expect("exec ok");
assert_eq!(out.to_string_box().value, "48");
}
#[test]
fn vm_exec_shift_masking() {
// 1 << 100 should mask to 1 << (100 & 63) = 1 << 36
let code = r#" return 1 << 100 "#;
let ast = NyashParser::parse_from_string(code).expect("parse ok");
let mut interp = NyashInterpreter::new();
let out = interp.execute(ast).expect("exec ok");
// compute expected as i64
let expected = (1_i64 as i64).wrapping_shl((100_u32) & 63);
assert_eq!(out.to_string_box().value, expected.to_string());
}

View File

@ -58,7 +58,11 @@ pub enum TokenType {
USING, // using (名前空間インポート)
// 演算子 (長いものから先に定義)
ARROW, // >> (legacy arrow)
SHIFT_LEFT, // << (bitwise shift-left)
SHIFT_RIGHT, // >> (bitwise shift-right)
BIT_AND, // & (bitwise and)
BIT_OR, // | (bitwise or)
BIT_XOR, // ^ (bitwise xor)
FAT_ARROW, // => (peek arms)
EQUALS, // ==
NotEquals, // !=
@ -247,7 +251,7 @@ impl NyashTokenizer {
Some('>') if self.peek_char() == Some('>') => {
self.advance();
self.advance();
Ok(Token::new(TokenType::ARROW, start_line, start_column))
Ok(Token::new(TokenType::SHIFT_RIGHT, start_line, start_column))
}
Some(':') if self.peek_char() == Some(':') => {
self.advance();
@ -269,6 +273,12 @@ impl NyashTokenizer {
self.advance();
Ok(Token::new(TokenType::NotEquals, start_line, start_column))
}
// Shift-left must be detected before <= and <
Some('<') if self.peek_char() == Some('<') => {
self.advance();
self.advance();
Ok(Token::new(TokenType::SHIFT_LEFT, start_line, start_column))
}
Some('<') if self.peek_char() == Some('=') => {
self.advance();
self.advance();
@ -289,6 +299,11 @@ impl NyashTokenizer {
self.advance();
Ok(Token::new(TokenType::OR, start_line, start_column))
}
Some('|') if self.peek_char() == Some('>') => {
self.advance();
self.advance();
return Ok(Token::new(TokenType::PIPE_FORWARD, start_line, start_column));
}
Some('<') => {
self.advance();
Ok(Token::new(TokenType::LESS, start_line, start_column))
@ -297,6 +312,18 @@ impl NyashTokenizer {
self.advance();
Ok(Token::new(TokenType::GREATER, start_line, start_column))
}
Some('&') => {
self.advance();
Ok(Token::new(TokenType::BIT_AND, start_line, start_column))
}
Some('|') => {
self.advance();
Ok(Token::new(TokenType::BIT_OR, start_line, start_column))
}
Some('^') => {
self.advance();
Ok(Token::new(TokenType::BIT_XOR, start_line, start_column))
}
Some('=') => {
self.advance();
Ok(Token::new(TokenType::ASSIGN, start_line, start_column))
@ -650,7 +677,7 @@ mod tests {
let mut tokenizer = NyashTokenizer::new(">> == != <= >= < >");
let tokens = tokenizer.tokenize().unwrap();
assert_eq!(tokens[0].token_type, TokenType::ARROW);
assert_eq!(tokens[0].token_type, TokenType::SHIFT_RIGHT);
assert_eq!(tokens[1].token_type, TokenType::EQUALS);
assert_eq!(tokens[2].token_type, TokenType::NotEquals);
assert_eq!(tokens[3].token_type, TokenType::LessEquals);