merge: selfhosting-dev <- origin/main; prefer main updates in cranelift builder (ARROW removal/SHR adoption)
This commit is contained in:
10
src/ast.rs
10
src/ast.rs
@ -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 => "<",
|
||||
|
||||
@ -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 */ }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -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 } => {
|
||||
|
||||
@ -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()), }
|
||||
|
||||
@ -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 => {
|
||||
|
||||
86
src/cli.rs
86
src/cli.rs
@ -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);
|
||||
}
|
||||
|
||||
@ -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() }
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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()) }) }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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(());
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()?;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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); }
|
||||
}
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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>> {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
54
src/tests/llvm_bitops_test.rs
Normal file
54
src/tests/llvm_bitops_test.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
83
src/tests/mir_core13_normalize.rs
Normal file
83
src/tests/mir_core13_normalize.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
20
src/tests/parser_bitops_test.rs
Normal file
20
src/tests/parser_bitops_test.rs
Normal 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));
|
||||
}
|
||||
|
||||
25
src/tests/vm_bitops_test.rs
Normal file
25
src/tests/vm_bitops_test.rs
Normal 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());
|
||||
}
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user