#![allow(improper_ctypes, improper_ctypes_definitions)]
use crate::chunk::Chunk;
use crate::op::Op;
use crate::value::Value;
use crate::vm::{VMResult, VM};
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::immediates::Ieee64;
use cranelift_codegen::ir::{types, AbiParam, Block, BlockArg, FuncRef, InstBuilder};
use cranelift_codegen::settings::{self, Configurable};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Switch, Variable};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{default_libcall_names, DataDescription, FuncId, Linkage, Module};
use cranelift_object::{ObjectBuilder, ObjectModule};
use std::collections::{BTreeSet, HashMap};
use std::path::Path;
pub const AOT_CHUNK_BLOB_SYMBOL: &str = "fusevm_aot_chunk_blob";
pub const AOT_CHUNK_LEN_SYMBOL: &str = "fusevm_aot_chunk_len";
pub const AOT_ENTRY_SYMBOL: &str = "fusevm_aot_entry";
#[no_mangle]
pub extern "C" fn fusevm_aot_exec_op(vm: *mut VM, ip: u64) -> i64 {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_exec_op(ip as usize) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_finish(vm: *mut VM) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_finish() }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_set_int_result(vm: *mut VM, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_set_int_result(n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_set_float_result(vm: *mut VM, f: f64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_set_float_result(f) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_box(vm: *mut VM) -> i64 {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_box() }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_unbox(vm: *mut VM, handle: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_unbox(handle) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_set_obj_result(vm: *mut VM, handle: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_set_obj_result(handle) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_clone(vm: *mut VM, handle: i64) -> i64 {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_clone(handle) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_free(vm: *mut VM, handle: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_free(handle) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_store_slot_obj(vm: *mut VM, idx: u32, handle: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_store_slot_obj(idx, handle) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_store_global_obj(vm: *mut VM, idx: u32, handle: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_store_global_obj(idx, handle) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_set_error(vm: *mut VM, code: u32) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_set_error(code) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_store_slot_int(vm: *mut VM, idx: u32, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_store_slot_int(idx, n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_store_slot_float(vm: *mut VM, idx: u32, f: f64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_store_slot_float(idx, f) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_store_global_int(vm: *mut VM, idx: u32, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_store_global_int(idx, n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_store_global_float(vm: *mut VM, idx: u32, f: f64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_store_global_float(idx, f) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_resume(vm: *mut VM, ip: u32) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_resume(ip) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_pop_float(vm: *mut VM) -> f64 {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_pop_float() }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_pop_int(vm: *mut VM) -> i64 {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_pop_int() }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_push_status(vm: *mut VM, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_push_status(n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_set_status_result(vm: *mut VM, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_set_status_result(n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_push_int(vm: *mut VM, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_push_int(n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_push_float(vm: *mut VM, f: f64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_push_float(f) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_push_bool(vm: *mut VM, n: i64) {
debug_assert!(!vm.is_null());
unsafe { (*vm).aot_push_bool(n) }
}
#[no_mangle]
pub extern "C" fn fusevm_aot_awk_warn(code: u32, value: f64) {
let func = if code == 0 { "sqrt" } else { "log" };
eprintln!("awk: warning: {func}: received negative argument {value}");
}
#[no_mangle]
pub extern "C" fn fusevm_aot_powf(a: f64, b: f64) -> f64 {
a.powf(b)
}
#[no_mangle]
pub extern "C" fn fusevm_aot_fmod(a: f64, b: f64) -> f64 {
a % b
}
#[no_mangle]
pub extern "C" fn fusevm_aot_unary_math(id: u32, x: f64) -> f64 {
match id {
0 => x.sin(),
1 => x.cos(),
2 => x.tan(),
3 => x.asin(),
4 => x.acos(),
5 => x.atan(),
6 => x.sinh(),
7 => x.cosh(),
8 => x.tanh(),
9 => x.exp(),
10 => x.ln(),
11 => x.log2(),
12 => x.log10(),
_ => f64::NAN,
}
}
#[no_mangle]
pub extern "C" fn fusevm_aot_atan2(y: f64, x: f64) -> f64 {
y.atan2(x)
}
fn host_isa() -> Result<cranelift_codegen::isa::OwnedTargetIsa, String> {
let mut fb = settings::builder();
let _ = fb.set("use_colocated_libcalls", "false");
let _ = fb.set("is_pic", "false");
let _ = fb.set("opt_level", "speed");
let flags = settings::Flags::new(fb);
let isa_builder = cranelift_native::builder().map_err(|e| format!("aot: native ISA: {e}"))?;
isa_builder
.finish(flags)
.map_err(|e| format!("aot: ISA finish: {e}"))
}
pub fn build_entry<M: Module>(module: &mut M, chunk: &Chunk) -> Result<FuncId, String> {
match analyze_native(chunk) {
Some(plan) => build_entry_native(module, chunk, &plan),
None => build_entry_threaded(module, chunk),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Int,
Bool,
Float,
Status,
Obj,
}
impl Kind {
fn is_numeric(self) -> bool {
matches!(self, Kind::Int | Kind::Float | Kind::Status)
}
fn is_intlike(self) -> bool {
matches!(self, Kind::Int | Kind::Bool)
}
fn promotes_float(self) -> bool {
!matches!(self, Kind::Int)
}
}
fn set_var_kind(map: &mut HashMap<u32, Kind>, key: u32, k: Kind) -> bool {
match map.get(&key) {
Some(&existing) => existing == k,
None => {
map.insert(key, k);
true
}
}
}
fn var_ty_kind(plan: &NativePlan, key: u32) -> Kind {
plan.var_kinds.get(&key).copied().unwrap_or(Kind::Int)
}
fn heap_op_effect(op: &Op) -> Option<(usize, Option<Kind>)> {
Some(match op {
Op::StringRepeat => (2, Some(Kind::Obj)),
Op::StringLen => (1, Some(Kind::Int)),
Op::StrEq | Op::StrNe | Op::StrLt | Op::StrGt | Op::StrLe | Op::StrGe => {
(2, Some(Kind::Bool))
}
Op::StrCmp => (2, Some(Kind::Int)),
Op::MakeArray(n) => (*n as usize, Some(Kind::Obj)),
Op::MakeHash(n) => (*n as usize, Some(Kind::Obj)),
Op::Range => (2, Some(Kind::Obj)),
Op::RangeStep => (3, Some(Kind::Obj)),
Op::ReadLine => (0, Some(Kind::Obj)),
Op::Glob | Op::GlobRecursive => (1, Some(Kind::Obj)),
Op::TildeExpand | Op::BraceExpand | Op::WordSplit => (1, Some(Kind::Obj)),
Op::StrMatch | Op::RegexMatch => (2, Some(Kind::Bool)),
Op::TestFile(_) => (1, Some(Kind::Bool)),
Op::ExpandParam(m) => {
use crate::op::param_mod::*;
let argc = match *m {
DEFAULT | ASSIGN | ERROR | ALTERNATE | STRIP_SHORT | STRIP_LONG
| RSTRIP_SHORT | RSTRIP_LONG => 1,
SUBST_FIRST | SUBST_ALL | SLICE => 2,
_ => 0,
};
(argc + 1, Some(Kind::Obj))
}
Op::AwkToLower | Op::AwkToUpper => (1, Some(Kind::Obj)),
Op::AwkIndex => (2, Some(Kind::Obj)),
Op::AwkLength(n) | Op::AwkSubstr(n) | Op::AwkSprintf(n) => {
(*n as usize, Some(Kind::Obj))
}
Op::AwkPrint(n) | Op::AwkPrintf(n) => (*n as usize, None),
Op::ArrayGet(_) => (1, Some(Kind::Obj)),
Op::ArraySet(_) => (2, None),
Op::ArrayPush(_) => (1, None),
Op::ArrayLen(_) => (0, Some(Kind::Int)),
Op::ArrayPop(_) => (0, Some(Kind::Obj)),
Op::ArrayShift(_) => (0, Some(Kind::Obj)),
Op::HashGet(_) => (1, Some(Kind::Obj)),
Op::HashSet(_) => (2, None),
Op::DeclareArray(_) => (0, None),
Op::DeclareHash(_) => (0, None),
Op::GetArray(_) => (0, Some(Kind::Obj)),
Op::SetArray(_) => (1, None),
Op::HashKeys(_) => (0, Some(Kind::Obj)),
Op::HashValues(_) => (0, Some(Kind::Obj)),
Op::HashDelete(_) => (1, Some(Kind::Obj)),
Op::HashExists(_) => (1, Some(Kind::Bool)),
_ => return None,
})
}
fn heap_op_name(op: &Op) -> Option<u16> {
match op {
Op::ArrayGet(n)
| Op::ArraySet(n)
| Op::ArrayPush(n)
| Op::ArrayLen(n)
| Op::ArrayPop(n)
| Op::ArrayShift(n)
| Op::HashGet(n)
| Op::HashSet(n)
| Op::DeclareArray(n)
| Op::DeclareHash(n)
| Op::GetArray(n)
| Op::SetArray(n)
| Op::HashKeys(n)
| Op::HashValues(n)
| Op::HashDelete(n)
| Op::HashExists(n) => Some(*n),
_ => None,
}
}
struct DeoptRefs {
store_slot_int: FuncRef,
store_slot_float: FuncRef,
store_slot_obj: FuncRef,
store_global_int: FuncRef,
store_global_float: FuncRef,
store_global_obj: FuncRef,
push_int: FuncRef,
push_float: FuncRef,
push_bool: FuncRef,
push_status: FuncRef,
unbox: FuncRef,
resume: FuncRef,
}
#[allow(clippy::too_many_arguments)]
fn emit_deopt(
b: &mut FunctionBuilder,
plan: &NativePlan,
vm_var: Variable,
ivars: &[Variable],
fvars: &[Variable],
slot_vars: &[Variable],
global_vars: &[Variable],
refs: &DeoptRefs,
kinds: &[Kind],
assigned: &BTreeSet<u32>,
deopt_ip: usize,
) {
for (i, &sv) in slot_vars.iter().enumerate() {
if !assigned.contains(&(i as u32)) {
continue;
}
let v = b.use_var(sv);
let vm = b.use_var(vm_var);
let idx = b.ins().iconst(types::I32, i as i64);
match var_ty_kind(plan, i as u32) {
Kind::Float => {
b.ins().call(refs.store_slot_float, &[vm, idx, v]);
}
Kind::Obj => {
b.ins().call(refs.store_slot_obj, &[vm, idx, v]);
}
_ => {
b.ins().call(refs.store_slot_int, &[vm, idx, v]);
}
}
}
for (i, &gv) in global_vars.iter().enumerate() {
if !assigned.contains(&(i as u32 | GLOBAL_TAG)) {
continue;
}
let v = b.use_var(gv);
let vm = b.use_var(vm_var);
let idx = b.ins().iconst(types::I32, i as i64);
match var_ty_kind(plan, i as u32 | GLOBAL_TAG) {
Kind::Float => {
b.ins().call(refs.store_global_float, &[vm, idx, v]);
}
Kind::Obj => {
b.ins().call(refs.store_global_obj, &[vm, idx, v]);
}
_ => {
b.ins().call(refs.store_global_int, &[vm, idx, v]);
}
}
}
for (pos, &k) in kinds.iter().enumerate() {
let vm = b.use_var(vm_var);
match k {
Kind::Float => {
let v = b.use_var(fvars[pos]);
b.ins().call(refs.push_float, &[vm, v]);
}
Kind::Bool => {
let v = b.use_var(ivars[pos]);
b.ins().call(refs.push_bool, &[vm, v]);
}
Kind::Status => {
let v = b.use_var(ivars[pos]);
b.ins().call(refs.push_status, &[vm, v]);
}
Kind::Int => {
let v = b.use_var(ivars[pos]);
b.ins().call(refs.push_int, &[vm, v]);
}
Kind::Obj => {
let v = b.use_var(ivars[pos]);
b.ins().call(refs.unbox, &[vm, v]);
}
}
}
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I32, deopt_ip as i64);
b.ins().call(refs.resume, &[vm, ipc]);
let z = b.ins().iconst(types::I64, 0);
b.ins().return_(&[z]);
}
const NATIVE_SLOT_LIMIT: usize = 4096;
const GLOBAL_TAG: u32 = 0x1_0000;
struct NativePlan {
leaders: BTreeSet<usize>,
entry_kinds: HashMap<usize, Vec<Kind>>,
max_depth: usize,
end_kinds: Vec<Kind>,
slot_count: usize,
global_count: usize,
var_kinds: HashMap<u32, Kind>,
deopt_points: BTreeSet<usize>,
inits_at: HashMap<usize, BTreeSet<u32>>,
}
#[cfg(test)]
fn native_lowerable(chunk: &Chunk) -> bool {
analyze_native(chunk).is_some()
}
fn native_successors(chunk: &Chunk, ip: usize, deopt_points: &BTreeSet<usize>) -> Vec<usize> {
if deopt_points.contains(&ip) {
return Vec::new();
}
match &chunk.ops[ip] {
Op::Jump(t) => vec![*t],
Op::JumpIfTrue(t)
| Op::JumpIfFalse(t)
| Op::JumpIfTrueKeep(t)
| Op::JumpIfFalseKeep(t)
| Op::SlotLtIntJumpIfFalse(_, _, t)
| Op::SlotIncLtIntJumpBack(_, _, t) => vec![*t, ip + 1],
_ => vec![ip + 1],
}
}
fn op_assigns(op: &Op, out: &mut BTreeSet<u32>) {
match op {
Op::SetSlot(s)
| Op::PreIncSlot(s)
| Op::PreDecSlot(s)
| Op::PostIncSlot(s)
| Op::PostDecSlot(s)
| Op::PreIncSlotVoid(s)
| Op::SlotIncLtIntJumpBack(s, _, _) => {
out.insert(u32::from(*s));
}
Op::SetVar(g) | Op::DeclareVar(g) => {
out.insert(u32::from(*g) | GLOBAL_TAG);
}
Op::AddAssignSlotVoid(a, _) => {
out.insert(u32::from(*a));
}
Op::AccumSumLoop(sum, i, _) => {
out.insert(u32::from(*sum));
out.insert(u32::from(*i));
}
_ => {}
}
}
fn may_assigned(
chunk: &Chunk,
n: usize,
deopt_points: &BTreeSet<usize>,
) -> HashMap<usize, BTreeSet<u32>> {
let mut may: HashMap<usize, BTreeSet<u32>> = HashMap::new();
may.insert(0, BTreeSet::new());
let mut work = vec![0usize];
while let Some(ip) = work.pop() {
if ip >= n {
continue;
}
let mut out = may.get(&ip).cloned().unwrap_or_default();
op_assigns(&chunk.ops[ip], &mut out);
for s in native_successors(chunk, ip, deopt_points) {
if s >= n {
continue;
}
let newly = !may.contains_key(&s);
let e = may.entry(s).or_default();
let before = e.len();
e.extend(out.iter().copied());
if newly || e.len() != before {
work.push(s);
}
}
}
may
}
fn analyze_native(chunk: &Chunk) -> Option<NativePlan> {
let ops = &chunk.ops;
let n = ops.len();
if n == 0 {
return Some(NativePlan {
leaders: BTreeSet::new(),
entry_kinds: HashMap::new(),
max_depth: 0,
end_kinds: Vec::new(),
slot_count: 0,
global_count: 0,
var_kinds: HashMap::new(),
deopt_points: BTreeSet::new(),
inits_at: HashMap::new(),
});
}
let mut state: HashMap<usize, (Vec<Kind>, BTreeSet<u32>)> = HashMap::new();
let mut leaders: BTreeSet<usize> = BTreeSet::new();
let mut max_depth = 0usize;
let mut max_slot: Option<u16> = None;
let mut max_global: Option<u16> = None;
let mut var_kind: HashMap<u32, Kind> = HashMap::new();
let mut deopt_points: BTreeSet<usize> = BTreeSet::new();
let mut end_state: Option<Vec<Kind>> = None;
state.insert(0, (Vec::new(), BTreeSet::new()));
leaders.insert(0);
let mut work = vec![0usize];
while let Some(ip) = work.pop() {
let (mut st, inits) = state.get(&ip)?.clone();
max_depth = max_depth.max(st.len());
macro_rules! deopt_unless {
($ok:expr) => {
if !($ok) {
deopt_points.insert(ip);
continue;
}
};
}
let mut tslot = [0u16; 2];
let mut nslot = 0usize;
let mut tglob: Option<u16> = None;
match &ops[ip] {
Op::GetSlot(s)
| Op::SetSlot(s)
| Op::PreIncSlot(s)
| Op::PreIncSlotVoid(s)
| Op::PreDecSlot(s)
| Op::PostIncSlot(s)
| Op::PostDecSlot(s)
| Op::SlotLtIntJumpIfFalse(s, _, _)
| Op::SlotIncLtIntJumpBack(s, _, _) => {
tslot[0] = *s;
nslot = 1;
}
Op::AddAssignSlotVoid(a, b) | Op::AccumSumLoop(a, b, _) => {
tslot[0] = *a;
tslot[1] = *b;
nslot = 2;
}
Op::GetVar(g) | Op::SetVar(g) | Op::DeclareVar(g) => tglob = Some(*g),
_ => {}
}
for &s in &tslot[..nslot] {
if s as usize >= NATIVE_SLOT_LIMIT {
return None;
}
max_slot = Some(max_slot.map_or(s, |m| m.max(s)));
}
if let Some(g) = tglob {
if g as usize >= NATIVE_SLOT_LIMIT {
return None;
}
max_global = Some(max_global.map_or(g, |m| m.max(g)));
}
let mut succs: Vec<(usize, Vec<Kind>, BTreeSet<u32>)> = Vec::new();
match &ops[ip] {
Op::Nop => succs.push((ip + 1, st, inits)),
Op::LoadInt(_) => {
st.push(Kind::Int);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::GetSlot(s) => {
let k = var_kind.get(&u32::from(*s)).copied().unwrap_or(Kind::Int);
st.push(k);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::GetVar(g) => {
let k = var_kind
.get(&(u32::from(*g) | GLOBAL_TAG))
.copied()
.unwrap_or(Kind::Int);
st.push(k);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::LoadFloat(_) => {
st.push(Kind::Float);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::LoadConst(idx) => {
match chunk.constants.get(*idx as usize)? {
Value::Int(_) => {
st.push(Kind::Int);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Value::Float(_) => {
st.push(Kind::Float);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
_ => {
st.push(Kind::Obj);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
}
}
Op::Concat => {
let _b = st.pop()?;
let _a = st.pop()?;
st.push(Kind::Obj);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::Add | Op::Sub | Op::Mul => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
let r = if a.promotes_float() || b.promotes_float() {
Kind::Float
} else {
Kind::Int
};
st.push(r);
succs.push((ip + 1, st, inits));
}
Op::Mod => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
let r = if a.promotes_float() || b.promotes_float() {
Kind::Float
} else {
Kind::Int
};
st.push(r);
succs.push((ip + 1, st, inits));
}
Op::Div => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::Pow => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::AbsFloat
| Op::SqrtFloat
| Op::CeilFloat
| Op::FloorFloat
| Op::TruncFloat
| Op::RoundFloat
| Op::SinFloat
| Op::CosFloat
| Op::TanFloat
| Op::AsinFloat
| Op::AcosFloat
| Op::AtanFloat
| Op::SinhFloat
| Op::CoshFloat
| Op::TanhFloat
| Op::ExpFloat
| Op::LogFloat
| Op::Log2Float
| Op::Log10Float => {
deopt_unless!(st.pop()?.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::TruncInt => {
deopt_unless!(st.pop()?.is_numeric());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::AbsInt => {
deopt_unless!(st.pop()?.is_intlike());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::PowFloat | Op::Atan2Float => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::GcdInt | Op::LcmInt => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_intlike() && b.is_intlike());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::AwkDivJit | Op::AwkModJit | Op::AwkDiv | Op::AwkMod => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::AwkLshiftJit | Op::AwkRshiftJit => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::AwkComplJit => {
deopt_unless!(st.pop()?.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::AwkSqrtJit | Op::AwkLogJit => {
deopt_unless!(st.pop()?.is_numeric());
st.push(Kind::Float);
succs.push((ip + 1, st, inits));
}
Op::Print(n) | Op::PrintLn(n) => {
let n = *n as usize;
if st.len() < n {
return None;
}
st.truncate(st.len() - n);
succs.push((ip + 1, st, inits));
}
Op::SetStatus => {
if st.pop().is_none() {
return None;
}
succs.push((ip + 1, st, inits));
}
Op::AwkGetFieldNum(_) => {
st.push(Kind::Float);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::Negate => {
let a = st.pop()?;
deopt_unless!(a.is_numeric());
st.push(if a == Kind::Int { Kind::Int } else { Kind::Float });
succs.push((ip + 1, st, inits));
}
Op::GetStatus => {
st.push(Kind::Status);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::NumEq | Op::NumNe | Op::NumLt | Op::NumGt | Op::NumLe | Op::NumGe => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Bool);
succs.push((ip + 1, st, inits));
}
Op::LoadTrue | Op::LoadFalse => {
st.push(Kind::Bool);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::Inc | Op::Dec => {
deopt_unless!(st.pop()?.is_intlike());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::LogNot => {
st.pop()?;
st.push(Kind::Bool);
succs.push((ip + 1, st, inits));
}
Op::BitAnd | Op::BitOr | Op::BitXor | Op::Shl | Op::Shr => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_intlike() && b.is_intlike());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::BitNot => {
deopt_unless!(st.pop()?.is_intlike());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::Dup2 => {
let len = st.len();
if len < 2 {
return None;
}
let a = st[len - 2];
let b = st[len - 1];
st.push(a);
st.push(b);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::Swap => {
let len = st.len();
if len < 2 {
return None;
}
st.swap(len - 1, len - 2);
succs.push((ip + 1, st, inits));
}
Op::Rot => {
let len = st.len();
if len < 3 {
return None;
}
st.swap(len - 3, len - 2);
st.swap(len - 2, len - 1);
succs.push((ip + 1, st, inits));
}
Op::LogAnd | Op::LogOr => {
st.pop()?;
st.pop()?;
st.push(Kind::Bool);
succs.push((ip + 1, st, inits));
}
Op::Spaceship => {
let b = st.pop()?;
let a = st.pop()?;
deopt_unless!(a.is_numeric() && b.is_numeric());
st.push(Kind::Int);
succs.push((ip + 1, st, inits));
}
Op::JumpIfTrueKeep(t) | Op::JumpIfFalseKeep(t) => {
let t = *t;
if t > n {
return None;
}
if st.is_empty() {
return None;
}
if t < n {
leaders.insert(t);
}
if ip + 1 < n {
leaders.insert(ip + 1);
}
succs.push((t, st.clone(), inits.clone()));
succs.push((ip + 1, st, inits));
}
Op::PreIncSlot(s) | Op::PreDecSlot(s) | Op::PostIncSlot(s) | Op::PostDecSlot(s) => {
if !set_var_kind(&mut var_kind, u32::from(*s), Kind::Int) {
return None; }
let mut ni = inits;
ni.insert(u32::from(*s));
st.push(Kind::Int);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, ni));
}
Op::PreIncSlotVoid(s) => {
if !set_var_kind(&mut var_kind, u32::from(*s), Kind::Int) {
return None;
}
let mut ni = inits;
ni.insert(u32::from(*s));
succs.push((ip + 1, st, ni));
}
Op::AddAssignSlotVoid(a, b) => {
if !set_var_kind(&mut var_kind, u32::from(*a), Kind::Int)
|| !set_var_kind(&mut var_kind, u32::from(*b), Kind::Int)
{
return None;
}
let mut ni = inits;
ni.insert(u32::from(*a)); succs.push((ip + 1, st, ni));
}
Op::AccumSumLoop(sum_s, i_s, _limit) => {
if !set_var_kind(&mut var_kind, u32::from(*sum_s), Kind::Int)
|| !set_var_kind(&mut var_kind, u32::from(*i_s), Kind::Int)
{
return None;
}
let mut ni = inits;
ni.insert(u32::from(*sum_s));
ni.insert(u32::from(*i_s));
succs.push((ip + 1, st, ni));
}
Op::SlotLtIntJumpIfFalse(s, _limit, t) => {
if !set_var_kind(&mut var_kind, u32::from(*s), Kind::Int) {
return None;
}
let t = *t;
if t > n {
return None;
}
if t < n {
leaders.insert(t);
}
if ip + 1 < n {
leaders.insert(ip + 1);
}
succs.push((t, st.clone(), inits.clone()));
succs.push((ip + 1, st, inits));
}
Op::SlotIncLtIntJumpBack(s, _limit, t) => {
if !set_var_kind(&mut var_kind, u32::from(*s), Kind::Int) {
return None;
}
let t = *t;
if t > n {
return None;
}
let mut ni = inits;
ni.insert(u32::from(*s)); if t < n {
leaders.insert(t);
}
if ip + 1 < n {
leaders.insert(ip + 1);
}
succs.push((t, st.clone(), ni.clone()));
succs.push((ip + 1, st, ni));
}
Op::SetSlot(s) => {
let k = st.pop()?;
deopt_unless!(k == Kind::Int || k == Kind::Float || k == Kind::Obj);
let key = u32::from(*s);
if !set_var_kind(&mut var_kind, key, k) {
return None; }
let mut ni = inits;
ni.insert(key); succs.push((ip + 1, st, ni));
}
Op::SetVar(g) | Op::DeclareVar(g) => {
let k = st.pop()?;
deopt_unless!(k == Kind::Int || k == Kind::Float || k == Kind::Obj);
let key = u32::from(*g) | GLOBAL_TAG;
if !set_var_kind(&mut var_kind, key, k) {
return None;
}
let mut ni = inits;
ni.insert(key);
succs.push((ip + 1, st, ni));
}
Op::Pop => {
st.pop()?;
succs.push((ip + 1, st, inits));
}
Op::Dup => {
let k = *st.last()?;
deopt_unless!(k != Kind::Obj);
st.push(k);
max_depth = max_depth.max(st.len());
succs.push((ip + 1, st, inits));
}
Op::Jump(t) => {
let t = *t;
if t > n {
return None;
}
if t < n {
leaders.insert(t);
}
succs.push((t, st, inits));
}
Op::JumpIfTrue(t) | Op::JumpIfFalse(t) => {
let t = *t;
if t > n {
return None;
}
st.pop()?; if t < n {
leaders.insert(t);
}
if ip + 1 < n {
leaders.insert(ip + 1);
}
succs.push((t, st.clone(), inits.clone()));
succs.push((ip + 1, st, inits));
}
op if heap_op_effect(op).is_some() => {
let (pops, res) = heap_op_effect(op).unwrap();
for _ in 0..pops {
st.pop()?;
}
if let Some(k) = res {
st.push(k);
max_depth = max_depth.max(st.len());
}
succs.push((ip + 1, st, inits));
}
_ => {
deopt_points.insert(ip);
}
}
for (s, sk, si) in succs {
if s == n {
match &end_state {
None => end_state = Some(sk),
Some(prev) if *prev != sk => return None,
Some(_) => {}
}
continue;
}
match state.get_mut(&s) {
None => {
state.insert(s, (sk, si));
work.push(s);
}
Some((pk, pi)) => {
if *pk != sk {
return None;
}
let before = pi.len();
pi.retain(|x| si.contains(x));
if pi.len() != before {
work.push(s);
}
}
}
}
}
for (&ip, (_kinds, inits)) in &state {
match &ops[ip] {
Op::GetSlot(s) if !inits.contains(&u32::from(*s)) => return None,
Op::GetVar(g) if !inits.contains(&(u32::from(*g) | GLOBAL_TAG)) => return None,
_ => {}
}
}
for &ip in state.keys() {
if let Some(name) = heap_op_name(&ops[ip]) {
if var_kind.contains_key(&(u32::from(name) | GLOBAL_TAG)) {
return None;
}
}
}
let end_kinds = match end_state {
Some(es) => {
if let Some(top) = es.last() {
if !top.is_numeric() && *top != Kind::Obj {
return None;
}
}
es
}
None => Vec::new(),
};
if state.len() == deopt_points.len() {
return None;
}
if !deopt_points.is_empty() {
let may = may_assigned(chunk, n, &deopt_points);
for &ip in &deopt_points {
let must = state.get(&ip).map(|(_, m)| m);
if must != may.get(&ip) {
return None;
}
}
}
let mut inits_at: HashMap<usize, BTreeSet<u32>> = HashMap::new();
for (&ip, (_k, inits)) in &state {
if deopt_points.contains(&ip) || matches!(ops[ip], Op::Div) {
inits_at.insert(ip, inits.clone());
}
}
let entry_kinds = leaders
.iter()
.map(|&l| (l, state.get(&l).map(|(k, _)| k.clone()).unwrap_or_default()))
.collect();
let slot_count = max_slot.map_or(0, |m| m as usize + 1);
let global_count = max_global.map_or(0, |m| m as usize + 1);
Some(NativePlan {
leaders,
entry_kinds,
max_depth,
end_kinds,
slot_count,
global_count,
var_kinds: var_kind,
deopt_points,
inits_at,
})
}
fn build_entry_native<M: Module>(
module: &mut M,
chunk: &Chunk,
plan: &NativePlan,
) -> Result<FuncId, String> {
let ptr_ty = module.target_config().pointer_type();
let mut ires_sig = module.make_signature();
ires_sig.params.push(AbiParam::new(ptr_ty));
ires_sig.params.push(AbiParam::new(types::I64));
let ires_id = module
.declare_function("fusevm_aot_set_int_result", Linkage::Import, &ires_sig)
.map_err(|e| format!("aot: declare set_int_result: {e}"))?;
let mut fres_sig = module.make_signature();
fres_sig.params.push(AbiParam::new(ptr_ty));
fres_sig.params.push(AbiParam::new(types::F64));
let fres_id = module
.declare_function("fusevm_aot_set_float_result", Linkage::Import, &fres_sig)
.map_err(|e| format!("aot: declare set_float_result: {e}"))?;
let mut serr_sig = module.make_signature();
serr_sig.params.push(AbiParam::new(ptr_ty));
serr_sig.params.push(AbiParam::new(types::I32));
let serr_id = module
.declare_function("fusevm_aot_set_error", Linkage::Import, &serr_sig)
.map_err(|e| format!("aot: declare set_error: {e}"))?;
let mut math_sig = module.make_signature();
math_sig.params.push(AbiParam::new(types::F64));
math_sig.params.push(AbiParam::new(types::F64));
math_sig.returns.push(AbiParam::new(types::F64));
let powf_id = module
.declare_function("fusevm_aot_powf", Linkage::Import, &math_sig)
.map_err(|e| format!("aot: declare powf: {e}"))?;
let fmod_id = module
.declare_function("fusevm_aot_fmod", Linkage::Import, &math_sig)
.map_err(|e| format!("aot: declare fmod: {e}"))?;
let atan2_id = module
.declare_function("fusevm_aot_atan2", Linkage::Import, &math_sig)
.map_err(|e| format!("aot: declare atan2: {e}"))?;
let mut unary_sig = module.make_signature();
unary_sig.params.push(AbiParam::new(types::I32));
unary_sig.params.push(AbiParam::new(types::F64));
unary_sig.returns.push(AbiParam::new(types::F64));
let unary_id = module
.declare_function("fusevm_aot_unary_math", Linkage::Import, &unary_sig)
.map_err(|e| format!("aot: declare unary_math: {e}"))?;
let mut warn_sig = module.make_signature();
warn_sig.params.push(AbiParam::new(types::I32));
warn_sig.params.push(AbiParam::new(types::F64));
let warn_id = module
.declare_function("fusevm_aot_awk_warn", Linkage::Import, &warn_sig)
.map_err(|e| format!("aot: declare awk_warn: {e}"))?;
let mut exec_sig = module.make_signature();
exec_sig.params.push(AbiParam::new(ptr_ty));
exec_sig.params.push(AbiParam::new(types::I64));
exec_sig.returns.push(AbiParam::new(types::I64));
let exec_id = module
.declare_function("fusevm_aot_exec_op", Linkage::Import, &exec_sig)
.map_err(|e| format!("aot: declare exec_op: {e}"))?;
let mut pushi_sig = module.make_signature();
pushi_sig.params.push(AbiParam::new(ptr_ty));
pushi_sig.params.push(AbiParam::new(types::I64));
let pushi_id = module
.declare_function("fusevm_aot_push_int", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare push_int: {e}"))?;
let pushb_id = module
.declare_function("fusevm_aot_push_bool", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare push_bool: {e}"))?;
let mut pushf_sig = module.make_signature();
pushf_sig.params.push(AbiParam::new(ptr_ty));
pushf_sig.params.push(AbiParam::new(types::F64));
let pushf_id = module
.declare_function("fusevm_aot_push_float", Linkage::Import, &pushf_sig)
.map_err(|e| format!("aot: declare push_float: {e}"))?;
let mut popf_sig = module.make_signature();
popf_sig.params.push(AbiParam::new(ptr_ty));
popf_sig.returns.push(AbiParam::new(types::F64));
let popf_id = module
.declare_function("fusevm_aot_pop_float", Linkage::Import, &popf_sig)
.map_err(|e| format!("aot: declare pop_float: {e}"))?;
let mut box_sig = module.make_signature();
box_sig.params.push(AbiParam::new(ptr_ty));
box_sig.returns.push(AbiParam::new(types::I64));
let box_id = module
.declare_function("fusevm_aot_box", Linkage::Import, &box_sig)
.map_err(|e| format!("aot: declare box: {e}"))?;
let unbox_id = module
.declare_function("fusevm_aot_unbox", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare unbox: {e}"))?;
let obj_res_id = module
.declare_function("fusevm_aot_set_obj_result", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare set_obj_result: {e}"))?;
let mut ssi_sig = module.make_signature();
ssi_sig.params.push(AbiParam::new(ptr_ty));
ssi_sig.params.push(AbiParam::new(types::I32));
ssi_sig.params.push(AbiParam::new(types::I64));
let ssi_id = module
.declare_function("fusevm_aot_store_slot_int", Linkage::Import, &ssi_sig)
.map_err(|e| format!("aot: declare store_slot_int: {e}"))?;
let sgi_id = module
.declare_function("fusevm_aot_store_global_int", Linkage::Import, &ssi_sig)
.map_err(|e| format!("aot: declare store_global_int: {e}"))?;
let mut ssf_sig = module.make_signature();
ssf_sig.params.push(AbiParam::new(ptr_ty));
ssf_sig.params.push(AbiParam::new(types::I32));
ssf_sig.params.push(AbiParam::new(types::F64));
let ssf_id = module
.declare_function("fusevm_aot_store_slot_float", Linkage::Import, &ssf_sig)
.map_err(|e| format!("aot: declare store_slot_float: {e}"))?;
let sgf_id = module
.declare_function("fusevm_aot_store_global_float", Linkage::Import, &ssf_sig)
.map_err(|e| format!("aot: declare store_global_float: {e}"))?;
let ssobj_id = module
.declare_function("fusevm_aot_store_slot_obj", Linkage::Import, &ssi_sig)
.map_err(|e| format!("aot: declare store_slot_obj: {e}"))?;
let sgobj_id = module
.declare_function("fusevm_aot_store_global_obj", Linkage::Import, &ssi_sig)
.map_err(|e| format!("aot: declare store_global_obj: {e}"))?;
let mut clone_sig = module.make_signature();
clone_sig.params.push(AbiParam::new(ptr_ty));
clone_sig.params.push(AbiParam::new(types::I64));
clone_sig.returns.push(AbiParam::new(types::I64));
let clone_id = module
.declare_function("fusevm_aot_clone", Linkage::Import, &clone_sig)
.map_err(|e| format!("aot: declare clone: {e}"))?;
let free_id = module
.declare_function("fusevm_aot_free", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare free: {e}"))?;
let mut resume_sig = module.make_signature();
resume_sig.params.push(AbiParam::new(ptr_ty));
resume_sig.params.push(AbiParam::new(types::I32));
let resume_id = module
.declare_function("fusevm_aot_resume", Linkage::Import, &resume_sig)
.map_err(|e| format!("aot: declare resume: {e}"))?;
let mut popi_sig = module.make_signature();
popi_sig.params.push(AbiParam::new(ptr_ty));
popi_sig.returns.push(AbiParam::new(types::I64));
let popi_id = module
.declare_function("fusevm_aot_pop_int", Linkage::Import, &popi_sig)
.map_err(|e| format!("aot: declare pop_int: {e}"))?;
let push_status_id = module
.declare_function("fusevm_aot_push_status", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare push_status: {e}"))?;
let sres_id = module
.declare_function("fusevm_aot_set_status_result", Linkage::Import, &pushi_sig)
.map_err(|e| format!("aot: declare set_status_result: {e}"))?;
let mut entry_sig = module.make_signature();
entry_sig.params.push(AbiParam::new(ptr_ty));
entry_sig.returns.push(AbiParam::new(types::I64));
let entry_id = module
.declare_function(AOT_ENTRY_SYMBOL, Linkage::Export, &entry_sig)
.map_err(|e| format!("aot: declare entry: {e}"))?;
let mut ctx = module.make_context();
ctx.func.signature = entry_sig;
let mut fbctx = FunctionBuilderContext::new();
{
let mut b = FunctionBuilder::new(&mut ctx.func, &mut fbctx);
let ires_ref = module.declare_func_in_func(ires_id, b.func);
let fres_ref = module.declare_func_in_func(fres_id, b.func);
let serr_ref = module.declare_func_in_func(serr_id, b.func);
let powf_ref = module.declare_func_in_func(powf_id, b.func);
let fmod_ref = module.declare_func_in_func(fmod_id, b.func);
let atan2_ref = module.declare_func_in_func(atan2_id, b.func);
let unary_ref = module.declare_func_in_func(unary_id, b.func);
let warn_ref = module.declare_func_in_func(warn_id, b.func);
let exec_ref = module.declare_func_in_func(exec_id, b.func);
let pushi_ref = module.declare_func_in_func(pushi_id, b.func);
let pushb_ref = module.declare_func_in_func(pushb_id, b.func);
let pushf_ref = module.declare_func_in_func(pushf_id, b.func);
let popf_ref = module.declare_func_in_func(popf_id, b.func);
let popi_ref = module.declare_func_in_func(popi_id, b.func);
let sres_ref = module.declare_func_in_func(sres_id, b.func);
let push_status_ref = module.declare_func_in_func(push_status_id, b.func);
let box_ref = module.declare_func_in_func(box_id, b.func);
let unbox_ref = module.declare_func_in_func(unbox_id, b.func);
let obj_res_ref = module.declare_func_in_func(obj_res_id, b.func);
let clone_ref = module.declare_func_in_func(clone_id, b.func);
let free_ref = module.declare_func_in_func(free_id, b.func);
let deopt_refs = DeoptRefs {
store_slot_int: module.declare_func_in_func(ssi_id, b.func),
store_slot_float: module.declare_func_in_func(ssf_id, b.func),
store_slot_obj: module.declare_func_in_func(ssobj_id, b.func),
store_global_int: module.declare_func_in_func(sgi_id, b.func),
store_global_float: module.declare_func_in_func(sgf_id, b.func),
store_global_obj: module.declare_func_in_func(sgobj_id, b.func),
push_int: pushi_ref,
push_float: pushf_ref,
push_bool: pushb_ref,
push_status: push_status_ref,
unbox: unbox_ref,
resume: module.declare_func_in_func(resume_id, b.func),
};
let vm_var = b.declare_var(ptr_ty);
let ivars: Vec<Variable> = (0..plan.max_depth)
.map(|_| b.declare_var(types::I64))
.collect();
let fvars: Vec<Variable> = (0..plan.max_depth)
.map(|_| b.declare_var(types::F64))
.collect();
let var_ty = |key: u32| -> types::Type {
if plan.var_kinds.get(&key) == Some(&Kind::Float) {
types::F64
} else {
types::I64
}
};
let slot_vars: Vec<Variable> = (0..plan.slot_count)
.map(|i| b.declare_var(var_ty(i as u32)))
.collect();
let global_vars: Vec<Variable> = (0..plan.global_count)
.map(|i| b.declare_var(var_ty(i as u32 | GLOBAL_TAG)))
.collect();
let n = chunk.ops.len();
let entry_block = b.create_block();
b.append_block_params_for_function_params(entry_block);
let ret_block = b.create_block();
let blocks: HashMap<usize, Block> = plan
.leaders
.iter()
.map(|&l| (l, b.create_block()))
.collect();
let block_for = |ip: usize| -> Block {
if ip >= n {
ret_block
} else {
blocks[&ip]
}
};
b.switch_to_block(entry_block);
let vm_param = b.block_params(entry_block)[0];
b.def_var(vm_var, vm_param);
if !slot_vars.is_empty() || !global_vars.is_empty() {
let zi = b.ins().iconst(types::I64, 0);
let zf = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
let neg1 = b.ins().iconst(types::I64, -1);
for (i, &sv) in slot_vars.iter().enumerate() {
let init = match var_ty_kind(plan, i as u32) {
Kind::Obj => neg1,
Kind::Float => zf,
_ => zi,
};
b.def_var(sv, init);
}
for (i, &gv) in global_vars.iter().enumerate() {
let init = match var_ty_kind(plan, i as u32 | GLOBAL_TAG) {
Kind::Obj => neg1,
Kind::Float => zf,
_ => zi,
};
b.def_var(gv, init);
}
}
if n == 0 {
b.ins().jump(ret_block, &[]);
} else {
b.ins().jump(blocks[&0], &[]);
}
let mut kinds: Vec<Kind> = Vec::new();
let mut terminated = true; for ip in 0..n {
if let Some(&blk) = blocks.get(&ip) {
if !terminated {
b.ins().jump(blk, &[]); }
b.switch_to_block(blk);
kinds = plan.entry_kinds[&ip].clone();
terminated = false;
} else if terminated {
continue; }
if plan.deopt_points.contains(&ip) {
emit_deopt(
&mut b,
plan,
vm_var,
&ivars,
&fvars,
&slot_vars,
&global_vars,
&deopt_refs,
&kinds,
&plan.inits_at[&ip],
ip,
);
terminated = true;
continue;
}
match &chunk.ops[ip] {
Op::Nop => {}
Op::LoadInt(k) => {
let v = b.ins().iconst(types::I64, *k);
let idx = kinds.len();
b.def_var(ivars[idx], v);
kinds.push(Kind::Int);
}
Op::LoadFloat(f) => {
let v = b.ins().f64const(Ieee64::with_bits(f.to_bits()));
let idx = kinds.len();
b.def_var(fvars[idx], v);
kinds.push(Kind::Float);
}
Op::LoadConst(ci) => {
let idx = kinds.len();
match &chunk.constants[*ci as usize] {
Value::Int(c) => {
let v = b.ins().iconst(types::I64, *c);
b.def_var(ivars[idx], v);
kinds.push(Kind::Int);
}
Value::Float(f) => {
let v = b.ins().f64const(Ieee64::with_bits(f.to_bits()));
b.def_var(fvars[idx], v);
kinds.push(Kind::Float);
}
_ => {
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, ip as i64);
b.ins().call(exec_ref, &[vm, ipc]);
let vm2 = b.use_var(vm_var);
let call = b.ins().call(box_ref, &[vm2]);
let h = b.inst_results(call)[0];
b.def_var(ivars[idx], h);
kinds.push(Kind::Obj);
}
}
}
Op::Concat => {
let ky = kinds.pop().unwrap();
let iy = kinds.len();
let kx = kinds.pop().unwrap();
let ix = kinds.len();
for (pos, k) in [(ix, kx), (iy, ky)] {
let vm = b.use_var(vm_var);
match k {
Kind::Float => {
let v = b.use_var(fvars[pos]);
b.ins().call(pushf_ref, &[vm, v]);
}
Kind::Bool => {
let v = b.use_var(ivars[pos]);
b.ins().call(pushb_ref, &[vm, v]);
}
Kind::Status => {
let v = b.use_var(ivars[pos]);
b.ins().call(push_status_ref, &[vm, v]);
}
Kind::Int => {
let v = b.use_var(ivars[pos]);
b.ins().call(pushi_ref, &[vm, v]);
}
Kind::Obj => {
let v = b.use_var(ivars[pos]);
b.ins().call(unbox_ref, &[vm, v]);
}
}
}
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, ip as i64);
b.ins().call(exec_ref, &[vm, ipc]);
let vm2 = b.use_var(vm_var);
let call = b.ins().call(box_ref, &[vm2]);
let h = b.inst_results(call)[0];
b.def_var(ivars[ix], h);
kinds.push(Kind::Obj);
}
op if heap_op_effect(op).is_some() => {
let (pops, res) = heap_op_effect(op).unwrap();
let base = kinds.len() - pops;
for pos in base..kinds.len() {
let vm = b.use_var(vm_var);
match kinds[pos] {
Kind::Float => {
let v = b.use_var(fvars[pos]);
b.ins().call(pushf_ref, &[vm, v]);
}
Kind::Bool => {
let v = b.use_var(ivars[pos]);
b.ins().call(pushb_ref, &[vm, v]);
}
Kind::Status => {
let v = b.use_var(ivars[pos]);
b.ins().call(push_status_ref, &[vm, v]);
}
Kind::Int => {
let v = b.use_var(ivars[pos]);
b.ins().call(pushi_ref, &[vm, v]);
}
Kind::Obj => {
let v = b.use_var(ivars[pos]);
b.ins().call(unbox_ref, &[vm, v]);
}
}
}
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, ip as i64);
b.ins().call(exec_ref, &[vm, ipc]);
kinds.truncate(base);
match res {
Some(Kind::Obj) => {
let vm2 = b.use_var(vm_var);
let call = b.ins().call(box_ref, &[vm2]);
b.def_var(ivars[base], b.inst_results(call)[0]);
kinds.push(Kind::Obj);
}
Some(Kind::Float) => {
let vm2 = b.use_var(vm_var);
let call = b.ins().call(popf_ref, &[vm2]);
b.def_var(fvars[base], b.inst_results(call)[0]);
kinds.push(Kind::Float);
}
Some(k) => {
let vm2 = b.use_var(vm_var);
let call = b.ins().call(popi_ref, &[vm2]);
b.def_var(ivars[base], b.inst_results(call)[0]);
kinds.push(k);
}
None => {}
}
}
op @ (Op::Add | Op::Sub | Op::Mul) => {
let ky = kinds.pop().unwrap();
let iy = kinds.len();
let kx = kinds.pop().unwrap();
let ix = kinds.len();
if ky.promotes_float() || kx.promotes_float() {
let y = load_f64(&mut b, &ivars, &fvars, iy, ky);
let x = load_f64(&mut b, &ivars, &fvars, ix, kx);
let r = match op {
Op::Add => b.ins().fadd(x, y),
Op::Sub => b.ins().fsub(x, y),
_ => b.ins().fmul(x, y),
};
b.def_var(fvars[ix], r);
kinds.push(Kind::Float);
} else {
let y = b.use_var(ivars[iy]);
let x = b.use_var(ivars[ix]);
let r = match op {
Op::Add => b.ins().iadd(x, y),
Op::Sub => b.ins().isub(x, y),
_ => b.ins().imul(x, y),
};
b.def_var(ivars[ix], r);
kinds.push(Kind::Int);
}
}
Op::Mod => {
let ky = kinds.pop().unwrap();
let iy = kinds.len();
let kx = kinds.pop().unwrap();
let ix = kinds.len();
if ky.promotes_float() || kx.promotes_float() {
let y = load_f64(&mut b, &ivars, &fvars, iy, ky);
let x = load_f64(&mut b, &ivars, &fvars, ix, kx);
let call = b.ins().call(fmod_ref, &[x, y]);
let r = b.inst_results(call)[0];
b.def_var(fvars[ix], r);
kinds.push(Kind::Float);
} else {
let y = b.use_var(ivars[iy]);
let x = b.use_var(ivars[ix]);
let zero = b.ins().iconst(types::I64, 0);
let one = b.ins().iconst(types::I64, 1);
let y_is_zero = b.ins().icmp_imm(IntCC::Equal, y, 0);
let y_is_neg1 = b.ins().icmp_imm(IntCC::Equal, y, -1);
let special = b.ins().bor(y_is_zero, y_is_neg1);
let safe_y = b.ins().select(special, one, y);
let rem = b.ins().srem(x, safe_y);
let r = b.ins().select(special, zero, rem);
b.def_var(ivars[ix], r);
kinds.push(Kind::Int);
}
}
Op::Div => {
let d = kinds.len();
let ky = kinds[d - 1];
let kx = kinds[d - 2];
let divisor = load_f64(&mut b, &ivars, &fvars, d - 1, ky);
let dividend = load_f64(&mut b, &ivars, &fvars, d - 2, kx);
let zero = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
let is_zero = b.ins().fcmp(FloatCC::Equal, divisor, zero);
let deopt_blk = b.create_block();
let ok_blk = b.create_block();
b.ins().brif(is_zero, deopt_blk, &[], ok_blk, &[]);
b.switch_to_block(deopt_blk);
emit_deopt(
&mut b,
plan,
vm_var,
&ivars,
&fvars,
&slot_vars,
&global_vars,
&deopt_refs,
&kinds,
&plan.inits_at[&ip],
ip,
);
b.switch_to_block(ok_blk);
let q = b.ins().fdiv(dividend, divisor);
kinds.pop();
kinds.pop();
let ix = kinds.len();
b.def_var(fvars[ix], q);
kinds.push(Kind::Float);
}
Op::Pow => {
let ky = kinds.pop().unwrap();
let iy = kinds.len();
let kx = kinds.pop().unwrap();
let ix = kinds.len();
let y = load_f64(&mut b, &ivars, &fvars, iy, ky);
let x = load_f64(&mut b, &ivars, &fvars, ix, kx);
let call = b.ins().call(powf_ref, &[x, y]);
let r = b.inst_results(call)[0];
b.def_var(fvars[ix], r);
kinds.push(Kind::Float);
}
op @ (Op::AbsFloat
| Op::SqrtFloat
| Op::CeilFloat
| Op::FloorFloat
| Op::TruncFloat
| Op::RoundFloat) => {
let ka = kinds.pop().unwrap();
let idx = kinds.len();
let x = load_f64(&mut b, &ivars, &fvars, idx, ka);
let r = match op {
Op::AbsFloat => b.ins().fabs(x),
Op::SqrtFloat => b.ins().sqrt(x),
Op::CeilFloat => b.ins().ceil(x),
Op::FloorFloat => b.ins().floor(x),
Op::TruncFloat => b.ins().trunc(x),
_ => b.ins().nearest(x), };
b.def_var(fvars[idx], r);
kinds.push(Kind::Float);
}
op @ (Op::SinFloat
| Op::CosFloat
| Op::TanFloat
| Op::AsinFloat
| Op::AcosFloat
| Op::AtanFloat
| Op::SinhFloat
| Op::CoshFloat
| Op::TanhFloat
| Op::ExpFloat
| Op::LogFloat
| Op::Log2Float
| Op::Log10Float) => {
let id: i64 = match op {
Op::SinFloat => 0,
Op::CosFloat => 1,
Op::TanFloat => 2,
Op::AsinFloat => 3,
Op::AcosFloat => 4,
Op::AtanFloat => 5,
Op::SinhFloat => 6,
Op::CoshFloat => 7,
Op::TanhFloat => 8,
Op::ExpFloat => 9,
Op::LogFloat => 10,
Op::Log2Float => 11,
_ => 12, };
let ka = kinds.pop().unwrap();
let idx = kinds.len();
let x = load_f64(&mut b, &ivars, &fvars, idx, ka);
let idc = b.ins().iconst(types::I32, id);
let call = b.ins().call(unary_ref, &[idc, x]);
let r = b.inst_results(call)[0];
b.def_var(fvars[idx], r);
kinds.push(Kind::Float);
}
Op::TruncInt => {
let ka = kinds.pop().unwrap();
let idx = kinds.len();
let r = if ka == Kind::Float {
let f = b.use_var(fvars[idx]);
b.ins().fcvt_to_sint_sat(types::I64, f)
} else {
b.use_var(ivars[idx])
};
b.def_var(ivars[idx], r);
kinds.push(Kind::Int);
}
Op::AbsInt => {
let _ = kinds.pop().unwrap();
let idx = kinds.len();
let x = b.use_var(ivars[idx]);
let neg = b.ins().ineg(x);
let is_neg = b.ins().icmp_imm(IntCC::SignedLessThan, x, 0);
let r = b.ins().select(is_neg, neg, x);
b.def_var(ivars[idx], r);
kinds.push(Kind::Int);
}
op @ (Op::PowFloat | Op::Atan2Float) => {
let kb = kinds.pop().unwrap();
let iy = kinds.len();
let ka = kinds.pop().unwrap();
let ix = kinds.len();
let top = load_f64(&mut b, &ivars, &fvars, iy, kb);
let low = load_f64(&mut b, &ivars, &fvars, ix, ka);
let call = match op {
Op::PowFloat => b.ins().call(powf_ref, &[low, top]),
_ => b.ins().call(atan2_ref, &[low, top]),
};
let r = b.inst_results(call)[0];
b.def_var(fvars[ix], r);
kinds.push(Kind::Float);
}
Op::GcdInt => {
let _ = kinds.pop().unwrap();
let iy = kinds.len();
let _ = kinds.pop().unwrap();
let ix = kinds.len();
let av = b.use_var(ivars[ix]);
let a0 = uabs(&mut b, av);
let bv = b.use_var(ivars[iy]);
let b0 = uabs(&mut b, bv);
let xv = b.declare_var(types::I64);
let yv = b.declare_var(types::I64);
b.def_var(xv, a0);
b.def_var(yv, b0);
let header = b.create_block();
let body = b.create_block();
let exit = b.create_block();
b.ins().jump(header, &[]);
b.switch_to_block(header);
let y = b.use_var(yv);
let cont = b.ins().icmp_imm(IntCC::NotEqual, y, 0);
b.ins().brif(cont, body, &[], exit, &[]);
b.switch_to_block(body);
let x = b.use_var(xv);
let y2 = b.use_var(yv);
let t = b.ins().urem(x, y2);
b.def_var(xv, y2); b.def_var(yv, t); b.ins().jump(header, &[]);
b.switch_to_block(exit);
let g = b.use_var(xv);
b.def_var(ivars[ix], g);
kinds.push(Kind::Int);
}
Op::LcmInt => {
let _ = kinds.pop().unwrap();
let iy = kinds.len();
let _ = kinds.pop().unwrap();
let ix = kinds.len();
let av = b.use_var(ivars[ix]);
let a0 = uabs(&mut b, av);
let bv = b.use_var(ivars[iy]);
let b0 = uabs(&mut b, bv);
let rv = b.declare_var(types::I64);
let a_is0 = b.ins().icmp_imm(IntCC::Equal, a0, 0);
let b_is0 = b.ins().icmp_imm(IntCC::Equal, b0, 0);
let any0 = b.ins().bor(a_is0, b_is0);
let zero_b = b.create_block();
let comp_b = b.create_block();
let done_b = b.create_block();
b.ins().brif(any0, zero_b, &[], comp_b, &[]);
b.switch_to_block(zero_b);
let z = b.ins().iconst(types::I64, 0);
b.def_var(rv, z);
b.ins().jump(done_b, &[]);
b.switch_to_block(comp_b);
let xv = b.declare_var(types::I64);
let yv = b.declare_var(types::I64);
b.def_var(xv, a0);
b.def_var(yv, b0);
let gh = b.create_block();
let gb = b.create_block();
let ge = b.create_block();
b.ins().jump(gh, &[]);
b.switch_to_block(gh);
let y = b.use_var(yv);
let cont = b.ins().icmp_imm(IntCC::NotEqual, y, 0);
b.ins().brif(cont, gb, &[], ge, &[]);
b.switch_to_block(gb);
let x = b.use_var(xv);
let y2 = b.use_var(yv);
let t = b.ins().urem(x, y2);
b.def_var(xv, y2);
b.def_var(yv, t);
b.ins().jump(gh, &[]);
b.switch_to_block(ge);
let g = b.use_var(xv);
let p = b.ins().udiv(a0, g); let prod = b.ins().imul(p, b0); let hi = b.ins().umulhi(p, b0); let ovf = b.ins().icmp_imm(IntCC::NotEqual, hi, 0);
let umax = b.ins().iconst(types::I64, -1); let sat = b.ins().select(ovf, umax, prod);
let imax = b.ins().iconst(types::I64, i64::MAX);
let lt = b.ins().icmp(IntCC::UnsignedLessThan, sat, imax);
let res = b.ins().select(lt, sat, imax); b.def_var(rv, res);
b.ins().jump(done_b, &[]);
b.switch_to_block(done_b);
let r = b.use_var(rv);
b.def_var(ivars[ix], r);
kinds.push(Kind::Int);
}
op @ (Op::AwkDivJit | Op::AwkModJit | Op::AwkDiv | Op::AwkMod) => {
let is_div = matches!(op, Op::AwkDivJit | Op::AwkDiv);
let kb = kinds.pop().unwrap();
let iy = kinds.len();
let ka = kinds.pop().unwrap();
let ix = kinds.len();
let divisor = load_f64(&mut b, &ivars, &fvars, iy, kb);
let dividend = load_f64(&mut b, &ivars, &fvars, ix, ka);
let zero = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
let is_zero = b.ins().fcmp(FloatCC::Equal, divisor, zero);
let err_blk = b.create_block();
let ok_blk = b.create_block();
b.ins().brif(is_zero, err_blk, &[], ok_blk, &[]);
b.switch_to_block(err_blk);
let vm = b.use_var(vm_var);
let code = b.ins().iconst(types::I32, if is_div { 0 } else { 1 });
b.ins().call(serr_ref, &[vm, code]);
let status = b.ins().iconst(types::I64, 0);
b.ins().return_(&[status]);
b.switch_to_block(ok_blk);
let r = if is_div {
b.ins().fdiv(dividend, divisor)
} else {
let call = b.ins().call(fmod_ref, &[dividend, divisor]);
b.inst_results(call)[0]
};
b.def_var(fvars[ix], r);
kinds.push(Kind::Float);
}
op @ (Op::AwkLshiftJit | Op::AwkRshiftJit) => {
let kb = kinds.pop().unwrap();
let iy = kinds.len();
let ka = kinds.pop().unwrap();
let ix = kinds.len();
let n = load_f64(&mut b, &ivars, &fvars, iy, kb);
let a = load_f64(&mut b, &ivars, &fvars, ix, ka);
let zero = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
let a_neg = b.ins().fcmp(FloatCC::LessThan, a, zero);
let n_neg = b.ins().fcmp(FloatCC::LessThan, n, zero);
let bad = b.ins().bor(a_neg, n_neg);
let err_blk = b.create_block();
let ok_blk = b.create_block();
b.ins().brif(bad, err_blk, &[], ok_blk, &[]);
b.switch_to_block(err_blk);
let vm = b.use_var(vm_var);
let code = b
.ins()
.iconst(types::I32, if matches!(op, Op::AwkLshiftJit) { 2 } else { 3 });
b.ins().call(serr_ref, &[vm, code]);
let st0 = b.ins().iconst(types::I64, 0);
b.ins().return_(&[st0]);
b.switch_to_block(ok_blk);
let ai = b.ins().fcvt_to_sint_sat(types::I64, a);
let shift_u32 = b.ins().fcvt_to_uint_sat(types::I32, n);
let shift_u64 = b.ins().uextend(types::I64, shift_u32);
let shift = b.ins().band_imm(shift_u64, 0x3f);
let r = if matches!(op, Op::AwkLshiftJit) {
let sh = b.ins().ishl(ai, shift);
b.ins().fcvt_from_sint(types::F64, sh)
} else {
let sh = b.ins().ushr(ai, shift);
b.ins().fcvt_from_uint(types::F64, sh)
};
b.def_var(fvars[ix], r);
kinds.push(Kind::Float);
}
Op::AwkComplJit => {
let ka = kinds.pop().unwrap();
let idx = kinds.len();
let a = load_f64(&mut b, &ivars, &fvars, idx, ka);
let zero = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
let a_neg = b.ins().fcmp(FloatCC::LessThan, a, zero);
let err_blk = b.create_block();
let ok_blk = b.create_block();
b.ins().brif(a_neg, err_blk, &[], ok_blk, &[]);
b.switch_to_block(err_blk);
let vm = b.use_var(vm_var);
let code = b.ins().iconst(types::I32, 4);
b.ins().call(serr_ref, &[vm, code]);
let st0 = b.ins().iconst(types::I64, 0);
b.ins().return_(&[st0]);
b.switch_to_block(ok_blk);
let ai = b.ins().fcvt_to_sint_sat(types::I64, a);
let v = b.ins().bnot(ai);
let r = b.ins().fcvt_from_sint(types::F64, v);
b.def_var(fvars[idx], r);
kinds.push(Kind::Float);
}
op @ (Op::AwkSqrtJit | Op::AwkLogJit) => {
let ka = kinds.pop().unwrap();
let idx = kinds.len();
let a = load_f64(&mut b, &ivars, &fvars, idx, ka);
let zero = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
let neg = b.ins().fcmp(FloatCC::LessThan, a, zero);
let warn_blk = b.create_block();
let ok_blk = b.create_block();
let cont_blk = b.create_block();
b.ins().brif(neg, warn_blk, &[], ok_blk, &[]);
b.switch_to_block(warn_blk);
let code = b
.ins()
.iconst(types::I32, if matches!(op, Op::AwkSqrtJit) { 0 } else { 1 });
b.ins().call(warn_ref, &[code, a]);
let nan = b.ins().f64const(Ieee64::with_bits(f64::NAN.to_bits()));
b.def_var(fvars[idx], nan);
b.ins().jump(cont_blk, &[]);
b.switch_to_block(ok_blk);
let r = if matches!(op, Op::AwkSqrtJit) {
b.ins().sqrt(a)
} else {
let ln_id = b.ins().iconst(types::I32, 10); let call = b.ins().call(unary_ref, &[ln_id, a]);
b.inst_results(call)[0]
};
b.def_var(fvars[idx], r);
b.ins().jump(cont_blk, &[]);
b.switch_to_block(cont_blk);
kinds.push(Kind::Float);
}
Op::Print(_) | Op::PrintLn(_) | Op::SetStatus => {
let n = match &chunk.ops[ip] {
Op::Print(c) | Op::PrintLn(c) => *c as usize,
_ => 1, };
let base = kinds.len() - n;
for pos in base..kinds.len() {
let vm = b.use_var(vm_var);
match kinds[pos] {
Kind::Float => {
let v = b.use_var(fvars[pos]);
b.ins().call(pushf_ref, &[vm, v]);
}
Kind::Bool => {
let v = b.use_var(ivars[pos]);
b.ins().call(pushb_ref, &[vm, v]);
}
Kind::Status => {
let v = b.use_var(ivars[pos]);
b.ins().call(push_status_ref, &[vm, v]);
}
Kind::Int => {
let v = b.use_var(ivars[pos]);
b.ins().call(pushi_ref, &[vm, v]);
}
Kind::Obj => {
let v = b.use_var(ivars[pos]);
b.ins().call(unbox_ref, &[vm, v]);
}
}
}
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, ip as i64);
b.ins().call(exec_ref, &[vm, ipc]);
kinds.truncate(base);
}
Op::AwkGetFieldNum(_) => {
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, ip as i64);
b.ins().call(exec_ref, &[vm, ipc]);
let vm2 = b.use_var(vm_var);
let call = b.ins().call(popf_ref, &[vm2]);
let v = b.inst_results(call)[0];
let idx = kinds.len();
b.def_var(fvars[idx], v);
kinds.push(Kind::Float);
}
Op::GetStatus => {
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, ip as i64);
b.ins().call(exec_ref, &[vm, ipc]);
let vm2 = b.use_var(vm_var);
let call = b.ins().call(popi_ref, &[vm2]);
let v = b.inst_results(call)[0];
let idx = kinds.len();
b.def_var(ivars[idx], v);
kinds.push(Kind::Status);
}
Op::Negate => {
let k = kinds.pop().unwrap();
let idx = kinds.len();
if k == Kind::Int {
let x = b.use_var(ivars[idx]);
let r = b.ins().ineg(x);
b.def_var(ivars[idx], r);
kinds.push(Kind::Int);
} else {
let x = load_f64(&mut b, &ivars, &fvars, idx, k);
let r = b.ins().fneg(x);
b.def_var(fvars[idx], r);
kinds.push(Kind::Float);
}
}
op @ (Op::NumEq | Op::NumNe | Op::NumLt | Op::NumGt | Op::NumLe | Op::NumGe) => {
let ky = kinds.pop().unwrap();
let iy = kinds.len();
let kx = kinds.pop().unwrap();
let ix = kinds.len();
let pred = if ky == Kind::Float || kx == Kind::Float {
let y = load_f64(&mut b, &ivars, &fvars, iy, ky);
let x = load_f64(&mut b, &ivars, &fvars, ix, kx);
let cc = match op {
Op::NumEq => FloatCC::Equal,
Op::NumNe => FloatCC::NotEqual,
Op::NumLt => FloatCC::LessThan,
Op::NumGt => FloatCC::GreaterThan,
Op::NumLe => FloatCC::LessThanOrEqual,
_ => FloatCC::GreaterThanOrEqual,
};
b.ins().fcmp(cc, x, y)
} else {
let y = b.use_var(ivars[iy]);
let x = b.use_var(ivars[ix]);
let cc = match op {
Op::NumEq => IntCC::Equal,
Op::NumNe => IntCC::NotEqual,
Op::NumLt => IntCC::SignedLessThan,
Op::NumGt => IntCC::SignedGreaterThan,
Op::NumLe => IntCC::SignedLessThanOrEqual,
_ => IntCC::SignedGreaterThanOrEqual,
};
b.ins().icmp(cc, x, y)
};
let one = b.ins().iconst(types::I64, 1);
let zero = b.ins().iconst(types::I64, 0);
let r = b.ins().select(pred, one, zero);
b.def_var(ivars[ix], r);
kinds.push(Kind::Bool);
}
Op::GetSlot(slot) => {
let k = var_ty_kind(plan, *slot as u32);
let v = b.use_var(slot_vars[*slot as usize]);
let idx = kinds.len();
if k == Kind::Obj {
let vm = b.use_var(vm_var);
let call = b.ins().call(clone_ref, &[vm, v]);
b.def_var(ivars[idx], b.inst_results(call)[0]);
} else {
store_raw(&mut b, &ivars, &fvars, idx, k, v);
}
kinds.push(k);
}
Op::SetSlot(slot) => {
let k = kinds.pop().unwrap(); let idx = kinds.len();
let v = load_raw(&mut b, &ivars, &fvars, idx, k);
let sv = slot_vars[*slot as usize];
if k == Kind::Obj {
let old = b.use_var(sv);
let vm = b.use_var(vm_var);
b.ins().call(free_ref, &[vm, old]);
}
b.def_var(sv, v);
}
Op::GetVar(g) => {
let k = var_ty_kind(plan, *g as u32 | GLOBAL_TAG);
let v = b.use_var(global_vars[*g as usize]);
let idx = kinds.len();
if k == Kind::Obj {
let vm = b.use_var(vm_var);
let call = b.ins().call(clone_ref, &[vm, v]);
b.def_var(ivars[idx], b.inst_results(call)[0]);
} else {
store_raw(&mut b, &ivars, &fvars, idx, k, v);
}
kinds.push(k);
}
Op::SetVar(g) | Op::DeclareVar(g) => {
let k = kinds.pop().unwrap();
let idx = kinds.len();
let v = load_raw(&mut b, &ivars, &fvars, idx, k);
let gv = global_vars[*g as usize];
if k == Kind::Obj {
let old = b.use_var(gv);
let vm = b.use_var(vm_var);
b.ins().call(free_ref, &[vm, old]);
}
b.def_var(gv, v);
}
op @ (Op::PreIncSlot(slot) | Op::PreDecSlot(slot)) => {
let sv = slot_vars[*slot as usize];
let old = b.use_var(sv);
let nv = b
.ins()
.iadd_imm(old, if matches!(op, Op::PreIncSlot(_)) { 1 } else { -1 });
b.def_var(sv, nv);
let idx = kinds.len();
b.def_var(ivars[idx], nv); kinds.push(Kind::Int);
}
op @ (Op::PostIncSlot(slot) | Op::PostDecSlot(slot)) => {
let sv = slot_vars[*slot as usize];
let old = b.use_var(sv);
let nv = b
.ins()
.iadd_imm(old, if matches!(op, Op::PostIncSlot(_)) { 1 } else { -1 });
b.def_var(sv, nv);
let idx = kinds.len();
b.def_var(ivars[idx], old); kinds.push(Kind::Int);
}
Op::PreIncSlotVoid(slot) => {
let sv = slot_vars[*slot as usize];
let old = b.use_var(sv);
let nv = b.ins().iadd_imm(old, 1);
b.def_var(sv, nv);
}
Op::AddAssignSlotVoid(a, b_slot) => {
let av = slot_vars[*a as usize];
let x = b.use_var(av);
let y = b.use_var(slot_vars[*b_slot as usize]);
let sum = b.ins().iadd(x, y);
b.def_var(av, sum);
}
Op::AccumSumLoop(sum_s, i_s, limit) => {
let sum_v = slot_vars[*sum_s as usize];
let i_v = slot_vars[*i_s as usize];
let header = b.create_block();
let body = b.create_block();
let exit = b.create_block();
b.ins().jump(header, &[]);
b.switch_to_block(header);
let lim = b.ins().iconst(types::I64, *limit as i64);
let i_cur = b.use_var(i_v);
let cond = b.ins().icmp(IntCC::SignedLessThan, i_cur, lim);
b.ins().brif(cond, body, &[], exit, &[]);
b.switch_to_block(body);
let s = b.use_var(sum_v);
let i2 = b.use_var(i_v);
let ns = b.ins().iadd(s, i2);
b.def_var(sum_v, ns);
let ni = b.ins().iadd_imm(i2, 1);
b.def_var(i_v, ni);
b.ins().jump(header, &[]);
b.switch_to_block(exit);
}
Op::SlotLtIntJumpIfFalse(slot, limit, t) => {
let v = b.use_var(slot_vars[*slot as usize]);
let lt = b.ins().icmp_imm(IntCC::SignedLessThan, v, *limit as i64);
b.ins()
.brif(lt, block_for(ip + 1), &[], block_for(*t), &[]);
terminated = true;
}
Op::SlotIncLtIntJumpBack(slot, limit, t) => {
let sv = slot_vars[*slot as usize];
let old = b.use_var(sv);
let nv = b.ins().iadd_imm(old, 1);
b.def_var(sv, nv);
let lt = b.ins().icmp_imm(IntCC::SignedLessThan, nv, *limit as i64);
b.ins()
.brif(lt, block_for(*t), &[], block_for(ip + 1), &[]);
terminated = true;
}
Op::Pop => {
let k = kinds.pop().unwrap();
if k == Kind::Obj {
let h = b.use_var(ivars[kinds.len()]);
let vm = b.use_var(vm_var);
b.ins().call(free_ref, &[vm, h]);
}
}
Op::Dup => {
let k = *kinds.last().unwrap();
let src = kinds.len() - 1;
let dst = kinds.len();
if k == Kind::Float {
let v = b.use_var(fvars[src]);
b.def_var(fvars[dst], v);
} else {
let v = b.use_var(ivars[src]);
b.def_var(ivars[dst], v);
}
kinds.push(k);
}
Op::LoadTrue | Op::LoadFalse => {
let v = b
.ins()
.iconst(types::I64, matches!(&chunk.ops[ip], Op::LoadTrue) as i64);
let idx = kinds.len();
b.def_var(ivars[idx], v);
kinds.push(Kind::Bool);
}
op @ (Op::Inc | Op::Dec) => {
let _ = kinds.pop().unwrap(); let idx = kinds.len();
let x = b.use_var(ivars[idx]);
let r = match op {
Op::Inc => b.ins().iadd_imm(x, 1),
_ => b.ins().iadd_imm(x, -1),
};
b.def_var(ivars[idx], r);
kinds.push(Kind::Int);
}
Op::LogNot => {
let k = kinds.pop().unwrap();
let idx = kinds.len();
let pred = truthy(&mut b, &ivars, &fvars, idx, k);
let one = b.ins().iconst(types::I64, 1);
let zero = b.ins().iconst(types::I64, 0);
let r = b.ins().select(pred, zero, one);
b.def_var(ivars[idx], r);
kinds.push(Kind::Bool);
}
op @ (Op::BitAnd | Op::BitOr | Op::BitXor | Op::Shl | Op::Shr) => {
let _ = kinds.pop().unwrap();
let iy = kinds.len();
let _ = kinds.pop().unwrap();
let ix = kinds.len();
let y = b.use_var(ivars[iy]);
let x = b.use_var(ivars[ix]);
let r = match op {
Op::BitAnd => b.ins().band(x, y),
Op::BitOr => b.ins().bor(x, y),
Op::BitXor => b.ins().bxor(x, y),
Op::Shl => b.ins().ishl(x, y),
_ => b.ins().sshr(x, y),
};
b.def_var(ivars[ix], r);
kinds.push(Kind::Int);
}
Op::BitNot => {
let _ = kinds.pop().unwrap();
let idx = kinds.len();
let x = b.use_var(ivars[idx]);
let r = b.ins().bnot(x);
b.def_var(ivars[idx], r);
kinds.push(Kind::Int);
}
Op::Dup2 => {
let len = kinds.len();
let ka = kinds[len - 2];
let kb = kinds[len - 1];
let a = load_raw(&mut b, &ivars, &fvars, len - 2, ka);
let bv = load_raw(&mut b, &ivars, &fvars, len - 1, kb);
store_raw(&mut b, &ivars, &fvars, len, ka, a);
store_raw(&mut b, &ivars, &fvars, len + 1, kb, bv);
kinds.push(ka);
kinds.push(kb);
}
Op::Swap => {
let len = kinds.len();
let (ix, iy) = (len - 2, len - 1);
let kx = kinds[ix];
let ky = kinds[iy];
let x = load_raw(&mut b, &ivars, &fvars, ix, kx);
let y = load_raw(&mut b, &ivars, &fvars, iy, ky);
store_raw(&mut b, &ivars, &fvars, ix, ky, y);
store_raw(&mut b, &ivars, &fvars, iy, kx, x);
kinds.swap(ix, iy);
}
Op::Rot => {
let len = kinds.len();
let (ia, ib, ic) = (len - 3, len - 2, len - 1);
let ka = kinds[ia];
let kb = kinds[ib];
let kc = kinds[ic];
let a = load_raw(&mut b, &ivars, &fvars, ia, ka);
let bv = load_raw(&mut b, &ivars, &fvars, ib, kb);
let c = load_raw(&mut b, &ivars, &fvars, ic, kc);
store_raw(&mut b, &ivars, &fvars, ia, kb, bv);
store_raw(&mut b, &ivars, &fvars, ib, kc, c);
store_raw(&mut b, &ivars, &fvars, ic, ka, a);
kinds[ia] = kb;
kinds[ib] = kc;
kinds[ic] = ka;
}
op @ (Op::LogAnd | Op::LogOr) => {
let kb = kinds.pop().unwrap();
let iy = kinds.len();
let ka = kinds.pop().unwrap();
let ix = kinds.len();
let ta = truthy(&mut b, &ivars, &fvars, ix, ka);
let tb = truthy(&mut b, &ivars, &fvars, iy, kb);
let combined = match op {
Op::LogAnd => b.ins().band(ta, tb),
_ => b.ins().bor(ta, tb),
};
let one = b.ins().iconst(types::I64, 1);
let zero = b.ins().iconst(types::I64, 0);
let r = b.ins().select(combined, one, zero);
b.def_var(ivars[ix], r);
kinds.push(Kind::Bool);
}
Op::Spaceship => {
let kb = kinds.pop().unwrap();
let iy = kinds.len();
let ka = kinds.pop().unwrap();
let ix = kinds.len();
let m1 = b.ins().iconst(types::I64, -1);
let zero = b.ins().iconst(types::I64, 0);
let p1 = b.ins().iconst(types::I64, 1);
let (lt, gt) = if ka.promotes_float() || kb.promotes_float() {
let y = load_f64(&mut b, &ivars, &fvars, iy, kb);
let x = load_f64(&mut b, &ivars, &fvars, ix, ka);
(
b.ins().fcmp(FloatCC::LessThan, x, y),
b.ins().fcmp(FloatCC::GreaterThan, x, y),
)
} else {
let y = b.use_var(ivars[iy]);
let x = b.use_var(ivars[ix]);
(
b.ins().icmp(IntCC::SignedLessThan, x, y),
b.ins().icmp(IntCC::SignedGreaterThan, x, y),
)
};
let mid = b.ins().select(gt, p1, zero);
let r = b.ins().select(lt, m1, mid);
b.def_var(ivars[ix], r);
kinds.push(Kind::Int);
}
Op::JumpIfTrueKeep(t) => {
let k = *kinds.last().unwrap();
let idx = kinds.len() - 1;
let cond = truthy(&mut b, &ivars, &fvars, idx, k);
b.ins()
.brif(cond, block_for(*t), &[], block_for(ip + 1), &[]);
terminated = true;
}
Op::JumpIfFalseKeep(t) => {
let k = *kinds.last().unwrap();
let idx = kinds.len() - 1;
let cond = truthy(&mut b, &ivars, &fvars, idx, k);
b.ins()
.brif(cond, block_for(ip + 1), &[], block_for(*t), &[]);
terminated = true;
}
Op::Jump(t) => {
b.ins().jump(block_for(*t), &[]);
terminated = true;
}
Op::JumpIfTrue(t) => {
let k = kinds.pop().unwrap();
let idx = kinds.len();
let cond = truthy(&mut b, &ivars, &fvars, idx, k);
b.ins()
.brif(cond, block_for(*t), &[], block_for(ip + 1), &[]);
terminated = true;
}
Op::JumpIfFalse(t) => {
let k = kinds.pop().unwrap();
let idx = kinds.len();
let cond = truthy(&mut b, &ivars, &fvars, idx, k);
b.ins()
.brif(cond, block_for(ip + 1), &[], block_for(*t), &[]);
terminated = true;
}
other => unreachable!("analyze_native admitted unsupported op {other:?}"),
}
}
if !terminated {
b.ins().jump(ret_block, &[]);
}
b.switch_to_block(ret_block);
if let Some(&topk) = plan.end_kinds.last() {
let idx = plan.end_kinds.len() - 1;
let vm = b.use_var(vm_var);
match topk {
Kind::Float => {
let v = b.use_var(fvars[idx]);
b.ins().call(fres_ref, &[vm, v]);
}
Kind::Status => {
let v = b.use_var(ivars[idx]);
b.ins().call(sres_ref, &[vm, v]);
}
Kind::Obj => {
let v = b.use_var(ivars[idx]);
b.ins().call(obj_res_ref, &[vm, v]);
}
_ => {
let v = b.use_var(ivars[idx]);
b.ins().call(ires_ref, &[vm, v]);
}
}
}
let status = b.ins().iconst(types::I64, 0);
b.ins().return_(&[status]);
b.seal_all_blocks();
b.finalize();
}
module
.define_function(entry_id, &mut ctx)
.map_err(|e| format!("aot: define native entry: {e}"))?;
module.clear_context(&mut ctx);
Ok(entry_id)
}
fn load_f64(
b: &mut FunctionBuilder,
ivars: &[Variable],
fvars: &[Variable],
idx: usize,
k: Kind,
) -> cranelift_codegen::ir::Value {
if k == Kind::Float {
b.use_var(fvars[idx])
} else {
let v = b.use_var(ivars[idx]);
b.ins().fcvt_from_sint(types::F64, v)
}
}
fn load_raw(
b: &mut FunctionBuilder,
ivars: &[Variable],
fvars: &[Variable],
idx: usize,
k: Kind,
) -> cranelift_codegen::ir::Value {
if k == Kind::Float {
b.use_var(fvars[idx])
} else {
b.use_var(ivars[idx])
}
}
fn uabs(b: &mut FunctionBuilder, v: cranelift_codegen::ir::Value) -> cranelift_codegen::ir::Value {
let neg = b.ins().ineg(v);
let is_neg = b.ins().icmp_imm(IntCC::SignedLessThan, v, 0);
b.ins().select(is_neg, neg, v)
}
fn store_raw(
b: &mut FunctionBuilder,
ivars: &[Variable],
fvars: &[Variable],
idx: usize,
k: Kind,
v: cranelift_codegen::ir::Value,
) {
if k == Kind::Float {
b.def_var(fvars[idx], v);
} else {
b.def_var(ivars[idx], v);
}
}
fn truthy(
b: &mut FunctionBuilder,
ivars: &[Variable],
fvars: &[Variable],
idx: usize,
k: Kind,
) -> cranelift_codegen::ir::Value {
match k {
Kind::Float => {
let f = b.use_var(fvars[idx]);
let z = b.ins().f64const(Ieee64::with_bits(0f64.to_bits()));
b.ins().fcmp(FloatCC::NotEqual, f, z)
}
Kind::Status => {
let v = b.use_var(ivars[idx]);
b.ins().icmp_imm(IntCC::Equal, v, 0)
}
_ => {
let v = b.use_var(ivars[idx]);
b.ins().icmp_imm(IntCC::NotEqual, v, 0)
}
}
}
fn build_entry_threaded<M: Module>(module: &mut M, chunk: &Chunk) -> Result<FuncId, String> {
let ptr_ty = module.target_config().pointer_type();
let mut exec_sig = module.make_signature();
exec_sig.params.push(AbiParam::new(ptr_ty));
exec_sig.params.push(AbiParam::new(types::I64));
exec_sig.returns.push(AbiParam::new(types::I64));
let exec_id = module
.declare_function("fusevm_aot_exec_op", Linkage::Import, &exec_sig)
.map_err(|e| format!("aot: declare exec_op: {e}"))?;
let mut fin_sig = module.make_signature();
fin_sig.params.push(AbiParam::new(ptr_ty));
let fin_id = module
.declare_function("fusevm_aot_finish", Linkage::Import, &fin_sig)
.map_err(|e| format!("aot: declare finish: {e}"))?;
let mut entry_sig = module.make_signature();
entry_sig.params.push(AbiParam::new(ptr_ty));
entry_sig.returns.push(AbiParam::new(types::I64));
let entry_id = module
.declare_function(AOT_ENTRY_SYMBOL, Linkage::Export, &entry_sig)
.map_err(|e| format!("aot: declare entry: {e}"))?;
let mut ctx = module.make_context();
ctx.func.signature = entry_sig;
let mut fbctx = FunctionBuilderContext::new();
{
let mut b = FunctionBuilder::new(&mut ctx.func, &mut fbctx);
let exec_ref = module.declare_func_in_func(exec_id, b.func);
let fin_ref = module.declare_func_in_func(fin_id, b.func);
let n = chunk.ops.len();
let entry_block = b.create_block();
b.append_block_params_for_function_params(entry_block);
let dispatch_block = b.create_block();
b.append_block_param(dispatch_block, types::I64); let ret_block = b.create_block();
let op_blocks: Vec<_> = (0..n).map(|_| b.create_block()).collect();
let vm_var = b.declare_var(ptr_ty);
b.switch_to_block(entry_block);
let vm_param = b.block_params(entry_block)[0];
b.def_var(vm_var, vm_param);
if n == 0 {
b.ins().jump(ret_block, &[]);
} else {
let zero = b.ins().iconst(types::I64, 0);
b.ins().jump(dispatch_block, &[BlockArg::Value(zero)]);
}
b.switch_to_block(dispatch_block);
let ip_val = b.block_params(dispatch_block)[0];
if n == 0 {
b.ins().jump(ret_block, &[]);
} else {
let mut switch = Switch::new();
for (i, blk) in op_blocks.iter().enumerate() {
switch.set_entry(i as u128, *blk);
}
switch.emit(&mut b, ip_val, ret_block);
}
for (i, blk) in op_blocks.iter().enumerate() {
b.switch_to_block(*blk);
let vm = b.use_var(vm_var);
let ipc = b.ins().iconst(types::I64, i as i64);
let call = b.ins().call(exec_ref, &[vm, ipc]);
let next = b.inst_results(call)[0];
let is_term = b.ins().icmp_imm(IntCC::SignedLessThan, next, 0);
b.ins().brif(
is_term,
ret_block,
&[],
dispatch_block,
&[BlockArg::Value(next)],
);
}
b.switch_to_block(ret_block);
let vm = b.use_var(vm_var);
b.ins().call(fin_ref, &[vm]);
let status = b.ins().iconst(types::I64, 0);
b.ins().return_(&[status]);
b.seal_all_blocks();
b.finalize();
}
module
.define_function(entry_id, &mut ctx)
.map_err(|e| format!("aot: define entry: {e}"))?;
module.clear_context(&mut ctx);
Ok(entry_id)
}
pub fn run_chunk_native(chunk: &Chunk, register: impl FnOnce(&mut VM)) -> Result<VMResult, String> {
let isa = host_isa()?;
let mut builder = JITBuilder::with_isa(isa, default_libcall_names());
builder.symbol("fusevm_aot_exec_op", fusevm_aot_exec_op as *const u8);
builder.symbol("fusevm_aot_finish", fusevm_aot_finish as *const u8);
builder.symbol(
"fusevm_aot_set_int_result",
fusevm_aot_set_int_result as *const u8,
);
builder.symbol(
"fusevm_aot_set_float_result",
fusevm_aot_set_float_result as *const u8,
);
builder.symbol("fusevm_aot_powf", fusevm_aot_powf as *const u8);
builder.symbol("fusevm_aot_fmod", fusevm_aot_fmod as *const u8);
builder.symbol("fusevm_aot_unary_math", fusevm_aot_unary_math as *const u8);
builder.symbol("fusevm_aot_atan2", fusevm_aot_atan2 as *const u8);
builder.symbol("fusevm_aot_set_error", fusevm_aot_set_error as *const u8);
builder.symbol("fusevm_aot_awk_warn", fusevm_aot_awk_warn as *const u8);
builder.symbol("fusevm_aot_push_int", fusevm_aot_push_int as *const u8);
builder.symbol("fusevm_aot_push_float", fusevm_aot_push_float as *const u8);
builder.symbol("fusevm_aot_push_bool", fusevm_aot_push_bool as *const u8);
builder.symbol("fusevm_aot_pop_float", fusevm_aot_pop_float as *const u8);
builder.symbol(
"fusevm_aot_store_slot_int",
fusevm_aot_store_slot_int as *const u8,
);
builder.symbol(
"fusevm_aot_store_slot_float",
fusevm_aot_store_slot_float as *const u8,
);
builder.symbol(
"fusevm_aot_store_global_int",
fusevm_aot_store_global_int as *const u8,
);
builder.symbol(
"fusevm_aot_store_global_float",
fusevm_aot_store_global_float as *const u8,
);
builder.symbol("fusevm_aot_resume", fusevm_aot_resume as *const u8);
builder.symbol("fusevm_aot_pop_int", fusevm_aot_pop_int as *const u8);
builder.symbol("fusevm_aot_push_status", fusevm_aot_push_status as *const u8);
builder.symbol("fusevm_aot_box", fusevm_aot_box as *const u8);
builder.symbol("fusevm_aot_unbox", fusevm_aot_unbox as *const u8);
builder.symbol("fusevm_aot_clone", fusevm_aot_clone as *const u8);
builder.symbol("fusevm_aot_free", fusevm_aot_free as *const u8);
builder.symbol(
"fusevm_aot_store_slot_obj",
fusevm_aot_store_slot_obj as *const u8,
);
builder.symbol(
"fusevm_aot_store_global_obj",
fusevm_aot_store_global_obj as *const u8,
);
builder.symbol(
"fusevm_aot_set_obj_result",
fusevm_aot_set_obj_result as *const u8,
);
builder.symbol(
"fusevm_aot_set_status_result",
fusevm_aot_set_status_result as *const u8,
);
let mut module = JITModule::new(builder);
let entry_id = build_entry(&mut module, chunk)?;
module
.finalize_definitions()
.map_err(|e| format!("aot: finalize: {e}"))?;
let code = module.get_finalized_function(entry_id);
let entry: extern "C" fn(*mut VM) -> i64 = unsafe { std::mem::transmute(code) };
let mut vm = VM::new(chunk.clone());
register(&mut vm);
let _ = entry(&mut vm as *mut VM);
Ok(vm.take_aot_result())
}
fn object_isa() -> Result<cranelift_codegen::isa::OwnedTargetIsa, String> {
let mut fb = settings::builder();
let _ = fb.set("use_colocated_libcalls", "false");
let _ = fb.set("is_pic", "true");
let _ = fb.set("opt_level", "speed");
let flags = settings::Flags::new(fb);
let isa_builder = cranelift_native::builder().map_err(|e| format!("aot: native ISA: {e}"))?;
isa_builder
.finish(flags)
.map_err(|e| format!("aot: ISA finish: {e}"))
}
pub fn compile_object(chunk: &Chunk, out_path: &Path) -> Result<(), String> {
let isa = object_isa()?;
let builder = ObjectBuilder::new(isa, "fusevm_aot", default_libcall_names())
.map_err(|e| format!("aot: object builder: {e}"))?;
let mut module = ObjectModule::new(builder);
let blob = bincode::serialize(chunk).map_err(|e| format!("aot: serialize chunk: {e}"))?;
let blob_id = module
.declare_data(AOT_CHUNK_BLOB_SYMBOL, Linkage::Export, false, false)
.map_err(|e| format!("aot: declare blob: {e}"))?;
let mut blob_desc = DataDescription::new();
blob_desc.define(blob.clone().into_boxed_slice());
module
.define_data(blob_id, &blob_desc)
.map_err(|e| format!("aot: define blob: {e}"))?;
let len_id = module
.declare_data(AOT_CHUNK_LEN_SYMBOL, Linkage::Export, false, false)
.map_err(|e| format!("aot: declare len: {e}"))?;
let mut len_desc = DataDescription::new();
len_desc.define(
(blob.len() as u64)
.to_le_bytes()
.to_vec()
.into_boxed_slice(),
);
module
.define_data(len_id, &len_desc)
.map_err(|e| format!("aot: define len: {e}"))?;
build_entry(&mut module, chunk)?;
let product = module.finish();
let bytes = product
.emit()
.map_err(|e| format!("aot: emit object: {e}"))?;
std::fs::write(out_path, bytes)
.map_err(|e| format!("aot: write {}: {e}", out_path.display()))?;
Ok(())
}
#[no_mangle]
pub extern "C" fn fusevm_aot_run_embedded() -> i64 {
extern "C" {
static fusevm_aot_chunk_blob: u8;
static fusevm_aot_chunk_len: u64;
fn fusevm_aot_entry(vm: *mut VM) -> i64;
fn fusevm_aot_register_builtins(vm: *mut VM);
}
let chunk: Chunk = unsafe {
let len = fusevm_aot_chunk_len as usize;
let bytes = std::slice::from_raw_parts(&fusevm_aot_chunk_blob as *const u8, len);
match bincode::deserialize(bytes) {
Ok(c) => c,
Err(e) => {
eprintln!("fusevm aot: corrupt embedded chunk: {e}");
return 1;
}
}
};
let mut vm = VM::new(chunk);
unsafe { fusevm_aot_register_builtins(&mut vm as *mut VM) };
unsafe { fusevm_aot_entry(&mut vm as *mut VM) };
match vm.take_aot_result() {
VMResult::Ok(Value::Int(n)) => n,
VMResult::Ok(Value::Status(s)) => s as i64,
VMResult::Ok(_) => 0,
VMResult::Halted => 0,
VMResult::Error(e) => {
eprintln!("fusevm aot: {e}");
1
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::ChunkBuilder;
use crate::op::Op;
use crate::value::Value;
fn assert_native_matches_interp(chunk: Chunk) {
let interp = {
let mut vm = VM::new(chunk.clone());
vm.run()
};
let native = run_chunk_native(&chunk, |_| {}).expect("native compile/run");
match (interp, native) {
(VMResult::Ok(a), VMResult::Ok(b)) => assert_eq!(a, b, "value mismatch"),
(VMResult::Halted, VMResult::Halted) => {}
(VMResult::Error(a), VMResult::Error(b)) => assert_eq!(a, b, "error mismatch"),
(i, n) => panic!("interp {i:?} != native {n:?}"),
}
}
#[test]
fn native_straight_line_arithmetic() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Add, 1);
b.emit(Op::LoadInt(4), 1);
b.emit(Op::Mul, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "arithmetic chunk should lower natively");
assert_native_matches_interp(chunk);
}
#[test]
fn native_arithmetic_with_negate_dup_pop() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(7), 1);
b.emit(Op::Dup, 1);
b.emit(Op::Add, 1);
b.emit(Op::Negate, 1);
b.emit(Op::LoadInt(99), 1);
b.emit(Op::Pop, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
#[test]
fn native_lowerable_rejects_unsupported_and_unsafe() {
let mut s = ChunkBuilder::new();
let c = s.add_constant(Value::str("x"));
s.emit(Op::LoadConst(c), 1);
let schunk = s.build();
assert!(native_lowerable(&schunk), "heap LoadConst lowers as Obj");
assert_native_matches_interp(schunk);
let mut cmp = ChunkBuilder::new();
cmp.emit(Op::LoadInt(1), 1);
cmp.emit(Op::LoadInt(2), 1);
cmp.emit(Op::NumLt, 1);
assert!(!native_lowerable(&cmp.build()), "bool result rejected");
let mut bslot = ChunkBuilder::new();
bslot.emit(Op::LoadInt(1), 1);
bslot.emit(Op::LoadInt(2), 1);
bslot.emit(Op::NumLt, 1);
bslot.emit(Op::SetSlot(0), 1);
assert_native_matches_interp(bslot.build());
let mut bad = ChunkBuilder::new();
bad.emit(Op::LoadInt(1), 1);
bad.emit(Op::Add, 1);
assert!(!native_lowerable(&bad.build()), "underflow must be rejected");
}
#[test]
fn native_unset_slot_read_falls_back() {
let mut b = ChunkBuilder::new();
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(!native_lowerable(&chunk), "unset slot read must fall back");
assert_native_matches_interp(chunk);
}
#[test]
fn native_slot_set_on_one_branch_falls_back() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1); let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::SetSlot(0), 1);
let skip = b.current_pos();
b.patch_jump(jf, skip);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(
!native_lowerable(&chunk),
"slot assigned on only one path must fall back"
);
assert_native_matches_interp(chunk);
}
#[test]
fn native_slot_set_on_both_branches_lowers() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::SetSlot(0), 1);
let j = b.emit(Op::Jump(0), 1);
let else_ip = b.current_pos();
b.patch_jump(jf, else_ip);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::SetSlot(0), 1);
let end_ip = b.current_pos();
b.patch_jump(j, end_ip);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "both-path assignment should lower");
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Int(2)),
other => panic!("expected Ok(2), got {other:?}"),
}
}
#[test]
fn native_float_slot_round_trip() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(1.5), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadFloat(0.25), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "float slot should lower");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(1.75)),
other => panic!("got {other:?}"),
}
}
#[test]
fn native_float_accumulator_loop() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1); let top = b.current_pos();
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::NumLt, 1);
let exit = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::GetSlot(0), 1); b.emit(Op::LoadFloat(0.5), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::Jump(top), 1);
let end = b.current_pos();
b.patch_jump(exit, end);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "float accumulator loop should lower");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(2.5)),
other => panic!("got {other:?}"),
}
}
#[test]
fn native_float_global() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(3.5), 1);
b.emit(Op::SetVar(0), 1);
b.emit(Op::GetVar(0), 1);
b.emit(Op::LoadFloat(1.5), 1);
b.emit(Op::Add, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(5.0)),
other => panic!("got {other:?}"),
}
}
#[test]
fn native_mixed_kind_slot_falls_back() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadFloat(2.5), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(!native_lowerable(&chunk), "mixed-kind slot must fall back");
assert_native_matches_interp(chunk);
}
#[test]
fn native_int_slots_round_trip() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(41), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
#[test]
fn native_empty_is_lowerable() {
assert!(native_lowerable(&ChunkBuilder::new().build()));
}
#[test]
fn native_conditional_branch() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::NumLt, 1);
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(10), 1);
let j = b.emit(Op::Jump(0), 1);
let else_ip = b.emit(Op::LoadInt(20), 1);
let end_ip = b.current_pos();
b.patch_jump(jf, else_ip);
b.patch_jump(j, end_ip);
let chunk = b.build();
assert!(native_lowerable(&chunk), "branch chunk should lower natively");
assert_native_matches_interp(chunk);
}
#[test]
fn native_counted_loop() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
let top = b.current_pos();
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::NumLt, 1);
let exit = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::Jump(top), 1);
let end_ip = b.current_pos();
b.patch_jump(exit, end_ip);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "loop chunk should lower natively");
assert_native_matches_interp(chunk);
}
#[test]
fn native_loop_sum_value_is_correct() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
let top = b.current_pos();
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::NumLt, 1);
let exit = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::Jump(top), 1);
let end_ip = b.current_pos();
b.patch_jump(exit, end_ip);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Int(10), "0+1+2+3+4 == 10"),
other => panic!("expected Ok(10), got {other:?}"),
}
}
#[test]
fn native_differential_arithmetic_and_branches() {
let mut seed: u64 = 0x9E37_79B9_7F4A_7C15;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
fn emit_expr(b: &mut ChunkBuilder, next: &mut impl FnMut() -> u64, terms: usize) {
b.emit(Op::LoadInt((next() % 21) as i64 - 10), 1);
for _ in 0..terms {
b.emit(Op::LoadInt((next() % 21) as i64 - 10), 1);
match next() % 3 {
0 => b.emit(Op::Add, 1),
1 => b.emit(Op::Sub, 1),
_ => b.emit(Op::Mul, 1),
};
}
}
for _ in 0..400 {
let mut b = ChunkBuilder::new();
if next() % 2 == 0 {
let terms = (next() % 5) as usize;
emit_expr(&mut b, &mut next, terms);
} else {
b.emit(Op::LoadInt((next() % 11) as i64 - 5), 1);
b.emit(Op::LoadInt((next() % 11) as i64 - 5), 1);
match next() % 4 {
0 => b.emit(Op::NumLt, 1),
1 => b.emit(Op::NumGt, 1),
2 => b.emit(Op::NumEq, 1),
_ => b.emit(Op::NumGe, 1),
};
let jf = b.emit(Op::JumpIfFalse(0), 1);
let then_terms = (next() % 4) as usize;
emit_expr(&mut b, &mut next, then_terms);
let j = b.emit(Op::Jump(0), 1);
let else_ip = b.current_pos();
b.patch_jump(jf, else_ip);
let else_terms = (next() % 4) as usize;
emit_expr(&mut b, &mut next, else_terms);
let end_ip = b.current_pos();
b.patch_jump(j, end_ip);
}
let chunk = b.build();
assert!(native_lowerable(&chunk), "generated chunk must lower natively");
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_differential_integer_bitwise() {
let mut seed: u64 = 0x1234_5678_9ABC_DEF0;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..400 {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt((next() % 17) as i64 - 8), 1);
let terms = next() % 8;
for _ in 0..terms {
if next() % 3 == 0 {
match next() % 3 {
0 => b.emit(Op::Inc, 1),
1 => b.emit(Op::Dec, 1),
_ => b.emit(Op::BitNot, 1),
};
} else {
b.emit(Op::LoadInt((next() % 17) as i64 - 8), 1);
match next() % 9 {
0 => b.emit(Op::Add, 1),
1 => b.emit(Op::Sub, 1),
2 => b.emit(Op::Mul, 1),
3 => b.emit(Op::BitAnd, 1),
4 => b.emit(Op::BitOr, 1),
5 => b.emit(Op::BitXor, 1),
6 => b.emit(Op::Shl, 1),
7 => b.emit(Op::Shr, 1),
_ => b.emit(Op::Mod, 1),
};
}
}
let chunk = b.build();
assert!(native_lowerable(&chunk), "integer chunk must lower");
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_bool_lognot_and_bitwise() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadTrue, 1);
b.emit(Op::LogNot, 1); b.emit(Op::LoadInt(5), 1);
b.emit(Op::BitAnd, 1); let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Int(0)),
other => panic!("expected Ok(0), got {other:?}"),
}
let mut bb = ChunkBuilder::new();
bb.emit(Op::LoadFalse, 1);
bb.emit(Op::LogNot, 1); let chunk = bb.build();
assert!(!native_lowerable(&chunk), "bare bool result falls back");
assert_native_matches_interp(chunk);
}
fn assert_native_int(chunk: Chunk, expect: i64) {
assert!(native_lowerable(&chunk), "chunk should lower natively");
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Int(expect)),
other => panic!("expected Ok({expect}), got {other:?}"),
}
}
fn assert_native_int_or_float(chunk: Chunk, expect: f64) {
assert!(native_lowerable(&chunk), "chunk should lower natively");
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(expect)),
other => panic!("expected Ok({expect}), got {other:?}"),
}
}
#[test]
fn native_partial_deopt() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Add, 1); b.emit(Op::LoadInt(4), 1); b.emit(Op::Concat, 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "mixed chunk should lower (with deopt)");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::str("54")),
other => panic!("got {other:?}"),
}
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(99), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Concat, 1); b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Int(99), "slot survived partial deopt"),
other => panic!("got {other:?}"),
}
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadUndef, 1); b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(Value::Undef) => {}
other => panic!("expected Undef (unassigned slot), got {other:?}"),
}
assert_native_matches_interp(chunk);
}
#[test]
fn native_differential_partial_deopt() {
let mut seed: u64 = 0x6C62_72E0_7E1A_55D9;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
let mut deopted = 0;
for _ in 0..400 {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt((next() % 21) as i64 - 10), 1);
for _ in 0..(next() % 4) {
b.emit(Op::LoadInt((next() % 21) as i64 - 10), 1);
match next() % 3 {
0 => b.emit(Op::Add, 1),
1 => b.emit(Op::Sub, 1),
_ => b.emit(Op::Mul, 1),
};
}
if next() % 2 == 0 {
b.emit(Op::LoadInt((next() % 21) as i64 - 10), 1);
b.emit(Op::Concat, 1);
deopted += 1;
}
let chunk = b.build();
assert!(native_lowerable(&chunk), "expr (±Concat) should lower");
assert_native_matches_interp(chunk);
}
assert!(deopted > 100, "expected many deopting chunks, got {deopted}");
}
#[test]
fn native_conditional_slot_before_deopt_is_sound() {
let build = |cond: i64| {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(cond), 1);
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetSlot(0), 1); let skip = b.current_pos();
b.patch_jump(jf, skip);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::LoadInt(8), 1);
b.emit(Op::Concat, 1); b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1);
b.build()
};
let chunk = build(1);
assert!(
!native_lowerable(&chunk),
"conditional slot before deopt must fall back, not miscompile"
);
assert_native_matches_interp(build(1)); assert_native_matches_interp(build(0)); }
#[test]
fn native_differential_cond_assign_deopt() {
let mut seed: u64 = 0x9F1C_2B3D_4E5A_6071;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..400 {
let mut b = ChunkBuilder::new();
let init = next() % 2 == 0; if init {
b.emit(Op::LoadInt((next() % 9) as i64), 1);
b.emit(Op::SetSlot(0), 1);
}
b.emit(Op::LoadInt((next() % 2) as i64), 1); let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt((next() % 9) as i64 + 10), 1); b.emit(Op::SetSlot(0), 1);
let j = b.emit(Op::Jump(0), 1);
let else_ip = b.current_pos();
b.patch_jump(jf, else_ip);
if next() % 2 == 0 {
b.emit(Op::LoadInt((next() % 9) as i64 + 20), 1);
b.emit(Op::SetSlot(0), 1);
}
let end_ip = b.current_pos();
b.patch_jump(j, end_ip);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::LoadInt(8), 1);
b.emit(Op::Concat, 1);
b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1);
assert_native_matches_interp(b.build());
}
}
#[test]
fn native_boxed_concat_lowers() {
let mut b = ChunkBuilder::new();
let a = b.add_constant(Value::str("a"));
let c = b.add_constant(Value::str("b"));
b.emit(Op::LoadConst(a), 1);
b.emit(Op::LoadConst(c), 1);
b.emit(Op::Concat, 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "boxed concat should lower natively");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let pre = b.add_constant(Value::str("n="));
b.emit(Op::LoadConst(pre), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Add, 1);
b.emit(Op::Concat, 1);
assert_native_matches_interp(b.build());
let mut b = ChunkBuilder::new();
let k = |s| Value::str(s);
let (x, y, z) = (b.add_constant(k("a")), b.add_constant(k("b")), b.add_constant(k("c")));
b.emit(Op::LoadConst(x), 1);
b.emit(Op::LoadConst(y), 1);
b.emit(Op::Concat, 1);
b.emit(Op::LoadConst(z), 1);
b.emit(Op::Concat, 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_named_array_hash_ops_lower() {
let mut b = ChunkBuilder::new();
b.emit(Op::DeclareArray(0), 1);
b.emit(Op::LoadInt(42), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::ArraySet(0), 1); b.emit(Op::LoadInt(0), 1);
b.emit(Op::ArrayGet(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "array element ops lower");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
b.emit(Op::DeclareArray(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::GetSlot(0), 1); b.emit(Op::ArrayPush(0), 1); b.emit(Op::SlotIncLtIntJumpBack(0, 3, 3), 1); b.emit(Op::ArrayLen(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "array push loop + len lowers");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let k = b.add_constant(Value::str("k"));
b.emit(Op::DeclareHash(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadConst(k), 1);
b.emit(Op::HashSet(0), 1); b.emit(Op::LoadConst(k), 1);
b.emit(Op::HashGet(0), 1); assert_native_matches_interp(b.build());
}
#[test]
fn native_expand_param_ops_lower() {
let mut b = ChunkBuilder::new();
let name = b.add_constant(Value::str("v"));
let def = b.add_constant(Value::str("d"));
b.emit(Op::LoadConst(name), 1);
b.emit(Op::LoadConst(def), 1);
b.emit(Op::ExpandParam(crate::op::param_mod::DEFAULT), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "ExpandParam(DEFAULT) lowers");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let name = b.add_constant(Value::str("v"));
b.emit(Op::LoadConst(name), 1);
b.emit(Op::ExpandParam(crate::op::param_mod::LENGTH), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "ExpandParam(LENGTH) lowers");
assert_native_matches_interp(chunk);
}
#[test]
fn native_shell_predicate_ops_lower() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("abc"));
let p = b.add_constant(Value::str("abc"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::LoadConst(p), 1);
b.emit(Op::StrMatch, 1); b.emit(Op::MakeArray(1), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "StrMatch lowers");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let root = b.add_constant(Value::str("/"));
b.emit(Op::LoadConst(root), 1);
b.emit(Op::TestFile(crate::op::file_test::IS_DIR), 1);
b.emit(Op::MakeArray(1), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "TestFile lowers");
assert_native_matches_interp(chunk);
}
#[test]
fn native_expansion_source_ops_lower() {
for op in [Op::BraceExpand, Op::WordSplit, Op::TildeExpand] {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("a b c"));
b.emit(Op::LoadConst(s), 1);
b.emit(op, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "expansion op lowers");
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_string_compares_lower() {
let mut b = ChunkBuilder::new();
let a = b.add_constant(Value::str("abc"));
let c = b.add_constant(Value::str("abd"));
b.emit(Op::LoadConst(a), 1);
b.emit(Op::LoadConst(c), 1);
b.emit(Op::StrCmp, 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "StrCmp lowers");
assert_native_matches_interp(chunk);
for (op, l, r) in [
(Op::StrEq, "x", "x"),
(Op::StrEq, "x", "y"),
(Op::StrLt, "a", "b"),
(Op::StrGe, "b", "a"),
] {
let mut b = ChunkBuilder::new();
let lc = b.add_constant(Value::str(l));
let rc = b.add_constant(Value::str(r));
b.emit(Op::LoadConst(lc), 1);
b.emit(Op::LoadConst(rc), 1);
b.emit(op, 1);
b.emit(Op::MakeArray(1), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "string compare lowers");
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_whole_array_and_hash_view_ops_lower() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::MakeArray(2), 1);
b.emit(Op::SetArray(0), 1); b.emit(Op::GetArray(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "GetArray/SetArray lower");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let x = b.add_constant(Value::str("x"));
b.emit(Op::DeclareHash(0), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::LoadConst(x), 1);
b.emit(Op::HashSet(0), 1);
b.emit(Op::LoadConst(x), 1);
b.emit(Op::HashDelete(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "HashDelete lowers");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let x = b.add_constant(Value::str("x"));
b.emit(Op::DeclareHash(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadConst(x), 1);
b.emit(Op::HashSet(0), 1);
b.emit(Op::LoadConst(x), 1);
b.emit(Op::HashExists(0), 1); b.emit(Op::MakeArray(1), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "HashExists (bool result) lowers");
assert_native_matches_interp(chunk);
}
#[test]
fn native_array_global_alias_bails() {
let mut b = ChunkBuilder::new();
b.emit(Op::DeclareArray(0), 1);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::SetVar(0), 1); b.emit(Op::LoadInt(9), 1);
b.emit(Op::ArrayPush(0), 1); b.emit(Op::GetVar(0), 1);
let chunk = b.build();
assert!(!native_lowerable(&chunk), "global/array name overlap must bail");
assert_native_matches_interp(chunk);
}
#[test]
fn native_heap_ops_lower() {
let mut b = ChunkBuilder::new();
let ab = b.add_constant(Value::str("ab"));
b.emit(Op::LoadConst(ab), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::StringRepeat, 1); assert_native_matches_interp(b.build());
let mut b = ChunkBuilder::new();
let hello = b.add_constant(Value::str("hello"));
b.emit(Op::LoadConst(hello), 1);
b.emit(Op::StringLen, 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "StringLen lowers");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let x = b.add_constant(Value::str("x"));
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadConst(x), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Add, 1);
b.emit(Op::MakeArray(3), 1);
assert_native_matches_interp(b.build());
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::Range, 1); assert_native_matches_interp(b.build());
let mut b = ChunkBuilder::new();
let kk = b.add_constant(Value::str("k"));
b.emit(Op::LoadConst(kk), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::MakeHash(2), 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_obj_slot_accumulator_loop_lowers() {
let mut b = ChunkBuilder::new();
let empty = b.add_constant(Value::str(""));
let x = b.add_constant(Value::str("x"));
b.emit(Op::LoadInt(0), 1); b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadConst(empty), 1); b.emit(Op::SetSlot(1), 1);
b.emit(Op::GetSlot(1), 1); b.emit(Op::LoadConst(x), 1); b.emit(Op::Concat, 1); b.emit(Op::SetSlot(1), 1); b.emit(Op::SlotIncLtIntJumpBack(0, 3, 4), 1); b.emit(Op::GetSlot(1), 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "Obj-slot accumulator loop lowers");
assert_native_matches_interp(chunk);
}
#[test]
fn native_obj_accumulator_differential_fuzz() {
let mut seed: u64 = 0x1234_5678_9ABC_DEF0;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..200 {
let iters = (next() % 12) as i32; let piece = ["x", "ab", "", "Z9"][(next() % 4) as usize];
let mut b = ChunkBuilder::new();
let empty = b.add_constant(Value::str("acc:"));
let p = b.add_constant(Value::str(piece));
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadConst(empty), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::GetSlot(1), 1); b.emit(Op::LoadConst(p), 1);
b.emit(Op::Concat, 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::SlotIncLtIntJumpBack(0, iters, 4), 1);
b.emit(Op::GetSlot(1), 1);
let chunk = b.build();
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_obj_global_roundtrip_lowers() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("hi"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::SetVar(0), 1);
b.emit(Op::GetVar(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "Obj global round-trips");
assert_native_matches_interp(chunk);
}
#[test]
fn native_boxed_value_then_deopt_spills() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("ab"));
b.emit(Op::LoadConst(s), 1);
let two = b.add_constant(Value::str("x"));
b.emit(Op::LoadConst(two), 1);
b.emit(Op::Concat, 1); b.emit(Op::LoadInt(2), 1);
b.emit(Op::StringRepeat, 1); assert_native_matches_interp(b.build());
}
#[test]
fn native_type_mismatch_deopts() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::NumLt, 1); b.emit(Op::LoadInt(5), 1);
b.emit(Op::Add, 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "type-mismatch chunk partial-lowers");
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(3), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::NumEq, 1); b.emit(Op::SetSlot(0), 1); b.emit(Op::GetSlot(0), 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_heap_const_deopts_not_bails() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(40), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Add, 1); let c = b.add_constant(Value::str("!"));
b.emit(Op::LoadConst(c), 1); b.emit(Op::Concat, 1); let chunk = b.build();
assert!(native_lowerable(&chunk), "native prefix + heap const should lower");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::str("42!")),
other => panic!("got {other:?}"),
}
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
let c = b.add_constant(Value::str("x"));
b.emit(Op::LoadConst(c), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "heap LoadConst lowers as Obj");
assert_native_matches_interp(chunk);
}
#[test]
fn native_div() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(7.0), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::Div, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "Div should lower");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(3.5)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(5.0), 1);
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::Div, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(Value::Undef) => {}
other => panic!("expected Undef from div-by-zero, got {other:?}"),
}
assert_native_matches_interp(chunk);
}
#[test]
fn native_div_deopt_reconstructs_slot_state() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(99), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadFloat(5.0), 1);
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::Div, 1); b.emit(Op::Pop, 1); b.emit(Op::GetSlot(0), 1); let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Int(99), "slot survived deopt"),
other => panic!("got {other:?}"),
}
assert_native_matches_interp(chunk);
}
#[test]
fn native_differential_div() {
let mut seed: u64 = 0x53C5_AA21_99BE_4471;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..300 {
let a = (next() % 21) as i64 - 10;
let bdiv = (next() % 9) as i64 - 4;
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(a), 1);
b.emit(Op::LoadInt(bdiv), 1);
b.emit(Op::Div, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_integer_mod() {
for (x, y, want) in [
(7, 3, 1),
(-7, 3, -1), (7, -3, 1),
(10, 0, 0), (i64::MIN, -1, 0), (123_456, 1000, 456),
] {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(x), 1);
b.emit(Op::LoadInt(y), 1);
b.emit(Op::Mod, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "int mod should lower");
assert_native_int(chunk, want);
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(7.5), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::Mod, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "float mod lowers via fmod");
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(1.5)),
other => panic!("expected Ok(1.5), got {other:?}"),
}
}
#[test]
fn native_float_math_ops() {
let cases: &[(Op, f64)] = &[
(Op::AbsFloat, -3.5),
(Op::SqrtFloat, 2.0),
(Op::CeilFloat, 2.3),
(Op::FloorFloat, 2.7),
(Op::TruncFloat, -2.7),
(Op::RoundFloat, 2.5), (Op::SinFloat, 1.0),
(Op::CosFloat, 1.0),
(Op::TanFloat, 1.0),
(Op::AsinFloat, 0.5),
(Op::AcosFloat, 0.5),
(Op::AtanFloat, 2.0),
(Op::SinhFloat, 1.0),
(Op::CoshFloat, 1.0),
(Op::TanhFloat, 1.0),
(Op::ExpFloat, 1.5),
(Op::LogFloat, 2.0),
(Op::Log2Float, 8.0),
(Op::Log10Float, 1000.0),
];
for (op, input) in cases {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(*input), 1);
b.emit(op.clone(), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "{op:?} should lower");
assert_native_matches_interp(chunk);
}
for (input, want) in [(2.5_f64, 2.0_f64), (3.5, 4.0)] {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(input), 1);
b.emit(Op::RoundFloat, 1);
match run_chunk_native(&b.build(), |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(want)),
other => panic!("got {other:?}"),
}
}
}
#[test]
fn native_awk_div_mod() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(7.0), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::AwkDivJit, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(3.5)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(7.5), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::AwkModJit, 1);
match run_chunk_native(&b.build(), |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(1.5)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(5.0), 1);
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::AwkDivJit, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Error(e) => assert_eq!(e, "division by zero attempted"),
other => panic!("expected div-by-zero error, got {other:?}"),
}
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(5.0), 1);
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::AwkModJit, 1);
let chunk = b.build();
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Error(e) => assert_eq!(e, "division by zero attempted in `%'"),
other => panic!("expected mod-by-zero error, got {other:?}"),
}
assert_native_matches_interp(chunk);
}
#[test]
fn native_sink_print() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(99), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Add, 1);
b.emit(Op::PrintLn(1), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "scalar+Print should now lower");
assert_native_int(chunk, 99);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(7), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Print(2), 1);
assert_native_int(b.build(), 7);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(42), 1);
b.emit(Op::LoadFloat(2.5), 1);
b.emit(Op::PrintLn(1), 1);
assert_native_int(b.build(), 42);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(42), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::NumLt, 1); b.emit(Op::PrintLn(1), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_int(chunk, 42);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(10), 1);
b.emit(Op::LoadInt(20), 1);
b.emit(Op::Add, 1);
b.emit(Op::PrintLn(1), 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_getstatus_source() {
let mut b = ChunkBuilder::new();
b.emit(Op::GetStatus, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "GetStatus should lower");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Status(0)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(3), 1);
b.emit(Op::SetStatus, 1);
b.emit(Op::GetStatus, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Status(3)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::GetStatus, 1);
b.emit(Op::LoadFloat(2.5), 1);
b.emit(Op::Add, 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_getstatus_inverted_truthiness() {
let build = |code: i64| {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(code), 1);
b.emit(Op::SetStatus, 1);
b.emit(Op::GetStatus, 1);
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(10), 1); let j = b.emit(Op::Jump(0), 1);
let else_ip = b.emit(Op::LoadInt(20), 1);
let end = b.current_pos();
b.patch_jump(jf, else_ip);
b.patch_jump(j, end);
b.build()
};
for code in [0, 1, 2, -1] {
let chunk = build(code);
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_differential_getstatus() {
let mut seed: u64 = 0xABCD_1234_5678_9F01;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..300 {
let code = (next() % 5) as i64 - 2; let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(code), 1);
b.emit(Op::SetStatus, 1);
b.emit(Op::GetStatus, 1);
match next() % 4 {
0 => {
b.emit(Op::LoadInt((next() % 7) as i64 - 3), 1);
b.emit(Op::Add, 1);
}
1 => {
b.emit(Op::LoadInt(0), 1);
b.emit(Op::NumGt, 1);
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(1), 1);
let j = b.emit(Op::Jump(0), 1);
let e = b.emit(Op::LoadInt(2), 1);
let end = b.current_pos();
b.patch_jump(jf, e);
b.patch_jump(j, end);
}
2 => {
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadInt(100), 1);
let j = b.emit(Op::Jump(0), 1);
let e = b.emit(Op::LoadInt(200), 1);
let end = b.current_pos();
b.patch_jump(jf, e);
b.patch_jump(j, end);
}
_ => { }
}
let chunk = b.build();
if native_lowerable(&chunk) {
assert_native_matches_interp(chunk);
}
}
}
#[test]
fn native_source_awk_field() {
let mut b = ChunkBuilder::new();
b.emit(Op::AwkGetFieldNum(1), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::Add, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "awk field source should lower");
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(2.0)),
other => panic!("got {other:?}"),
}
assert_native_matches_interp(chunk);
let mut b = ChunkBuilder::new();
b.emit(Op::AwkGetFieldNum(2), 1);
b.emit(Op::AwkGetFieldNum(3), 1);
b.emit(Op::NumLt, 1); b.emit(Op::LoadInt(5), 1);
b.emit(Op::Add, 1); let chunk = b.build();
assert_native_matches_interp(chunk);
}
#[test]
fn native_setstatus_sink() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(77), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetStatus, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "SetStatus should lower as a sink");
assert_native_int(chunk, 77);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(77), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetStatus, 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_loop_with_print_lowers() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1); let top = b.current_pos();
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::NumLt, 1);
let exit = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Print(1), 1); b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::Jump(top), 1);
let end = b.current_pos();
b.patch_jump(exit, end);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "loop with embedded sink should lower");
assert_native_int(chunk, 3); }
#[test]
fn native_awk_sqrt_log() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(16.0), 1);
b.emit(Op::AwkSqrtJit, 1);
assert_native_int_or_float(b.build(), 4.0);
for x in [2.0_f64, 0.5, 100.0, 1.0, 0.0] {
for op in [Op::AwkSqrtJit, Op::AwkLogJit] {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(x), 1);
b.emit(op, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
}
for op in [Op::AwkSqrtJit, Op::AwkLogJit] {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(-4.0), 1);
b.emit(op, 1);
match run_chunk_native(&b.build(), |_| {}).expect("run") {
VMResult::Ok(Value::Float(v)) => assert!(v.is_nan(), "expected NaN"),
other => panic!("expected NaN float, got {other:?}"),
}
}
}
#[test]
fn native_awk_div_mod_nonjit() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(7.0), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::AwkDiv, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(3.5)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(7.5), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::AwkMod, 1);
match run_chunk_native(&b.build(), |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(1.5)),
other => panic!("got {other:?}"),
}
for (op, msg) in [
(Op::AwkDiv, "division by zero attempted"),
(Op::AwkMod, "division by zero attempted in `%'"),
] {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(5.0), 1);
b.emit(Op::LoadFloat(0.0), 1);
b.emit(op, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Error(e) => assert_eq!(e, msg),
other => panic!("expected error, got {other:?}"),
}
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_awk_bitwise() {
let bin = |a: f64, n: f64, op: Op| {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(a), 1);
b.emit(Op::LoadFloat(n), 1);
b.emit(op, 1);
b.build()
};
assert_native_int_or_float(bin(3.0, 4.0, Op::AwkLshiftJit), 48.0);
assert_native_int_or_float(bin(48.0, 4.0, Op::AwkRshiftJit), 3.0);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::AwkComplJit, 1);
assert_native_int_or_float(b.build(), -1.0);
for (op, msg) in [
(Op::AwkLshiftJit, "lshift: negative values are not allowed"),
(Op::AwkRshiftJit, "rshift: negative values are not allowed"),
] {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(-1.0), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(op, 1);
let chunk = b.build();
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Error(e) => assert_eq!(e, msg),
other => panic!("expected error, got {other:?}"),
}
assert_native_matches_interp(chunk);
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(-1.0), 1);
b.emit(Op::AwkComplJit, 1);
let chunk = b.build();
match run_chunk_native(&chunk, |_| {}).expect("run") {
VMResult::Error(e) => assert_eq!(e, "compl: negative value is not allowed"),
other => panic!("expected error, got {other:?}"),
}
assert_native_matches_interp(chunk);
}
#[test]
fn native_differential_awk_bitwise() {
let mut seed: u64 = 0x84A3_11C5_7E29_B6F1;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..300 {
let a = (next() % 1_000_000) as f64;
let n = (next() % 70) as f64; let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(a), 1);
if next() & 1 == 0 {
b.emit(Op::LoadFloat(n), 1);
b.emit(if next() & 2 == 0 { Op::AwkLshiftJit } else { Op::AwkRshiftJit }, 1);
} else {
b.emit(Op::AwkComplJit, 1);
}
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_gcd_lcm() {
let gcd = |a: i64, bb: i64| {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(a), 1);
b.emit(Op::LoadInt(bb), 1);
b.emit(Op::GcdInt, 1);
b.build()
};
assert_native_int(gcd(12, 18), 6);
assert_native_int(gcd(17, 5), 1);
assert_native_int(gcd(0, 5), 5);
assert_native_int(gcd(-12, 18), 6);
let lcm = |a: i64, bb: i64| {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(a), 1);
b.emit(Op::LoadInt(bb), 1);
b.emit(Op::LcmInt, 1);
b.build()
};
assert_native_int(lcm(4, 6), 12);
assert_native_int(lcm(21, 6), 42);
assert_native_int(lcm(0, 5), 0);
assert_native_int(lcm(i64::MAX, 2), i64::MAX); }
#[test]
fn native_differential_gcd_lcm() {
let mut seed: u64 = 0xD1B5_4A32_D192_ED03;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
for _ in 0..300 {
let a = next() as i64;
let bb = next() as i64;
let op = if next() & 1 == 0 { Op::GcdInt } else { Op::LcmInt };
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(a), 1);
b.emit(Op::LoadInt(bb), 1);
b.emit(op, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
}
#[test]
fn native_int_math_ops() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(3.9), 1);
b.emit(Op::TruncInt, 1);
assert_native_int(b.build(), 3);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(-3.9), 1);
b.emit(Op::TruncInt, 1);
assert_native_int(b.build(), -3);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(-7), 1);
b.emit(Op::AbsInt, 1);
assert_native_int(b.build(), 7);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(i64::MIN), 1);
b.emit(Op::AbsInt, 1);
assert_native_int(b.build(), i64::MIN);
}
#[test]
fn native_binary_float_math() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::LoadFloat(10.0), 1);
b.emit(Op::PowFloat, 1);
match run_chunk_native(&b.build(), |_| {}).expect("run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(1024.0)),
other => panic!("got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(2.0), 1); b.emit(Op::LoadFloat(3.0), 1); b.emit(Op::Atan2Float, 1);
assert_native_matches_interp(b.build());
}
#[test]
fn native_pow() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(10), 1);
b.emit(Op::Pow, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "pow lowers via powf");
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(1024.0)),
other => panic!("expected Ok(1024.0), got {other:?}"),
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(9.0), 1);
b.emit(Op::LoadFloat(0.5), 1);
b.emit(Op::Pow, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
#[test]
fn native_globals_round_trip() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(42), 1);
b.emit(Op::SetVar(0), 1);
b.emit(Op::GetVar(0), 1);
assert_native_int(b.build(), 42);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(7), 1);
b.emit(Op::DeclareVar(0), 1);
b.emit(Op::GetVar(0), 1);
assert_native_int(b.build(), 7);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(10), 1);
b.emit(Op::SetVar(0), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetVar(1), 1);
b.emit(Op::GetVar(0), 1);
b.emit(Op::GetVar(1), 1);
b.emit(Op::Add, 1);
assert_native_int(b.build(), 15);
}
#[test]
fn native_uninit_global_read_falls_back() {
let mut b = ChunkBuilder::new();
b.emit(Op::GetVar(0), 1);
let chunk = b.build();
assert!(!native_lowerable(&chunk), "unset global read must fall back");
assert_native_matches_interp(chunk);
}
#[test]
fn native_slot_incdec_superops() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PreIncSlot(0), 1);
assert_native_int(b.build(), 6);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PostIncSlot(0), 1);
assert_native_int(b.build(), 5);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PostIncSlot(0), 1);
b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1);
assert_native_int(b.build(), 6);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PreDecSlot(0), 1);
assert_native_int(b.build(), 4);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PostDecSlot(0), 1);
assert_native_int(b.build(), 5);
let mut b = ChunkBuilder::new();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1);
assert_native_int(b.build(), 1);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(10), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::AddAssignSlotVoid(0, 1), 1);
b.emit(Op::GetSlot(0), 1);
assert_native_int(b.build(), 17);
}
#[test]
fn native_accum_sum_loop() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1); b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1); b.emit(Op::AccumSumLoop(0, 1, 5), 1);
b.emit(Op::GetSlot(0), 1);
assert_native_int(b.build(), 10);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::AccumSumLoop(0, 1, 5), 1);
b.emit(Op::GetSlot(1), 1);
assert_native_int(b.build(), 5);
}
#[test]
fn native_fused_loop_ops() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::SlotLtIntJumpIfFalse(1, 5, 10), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::SlotIncLtIntJumpBack(1, 5, 5), 1);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "fused loop should lower natively");
assert_native_int(chunk, 10);
}
#[test]
fn native_stack_shuffles() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(3), 1);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::Swap, 1);
b.emit(Op::Sub, 1);
assert_native_int(b.build(), 4);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(5), 1);
b.emit(Op::Dup2, 1);
b.emit(Op::Add, 1);
b.emit(Op::Add, 1);
b.emit(Op::Add, 1);
assert_native_int(b.build(), 14);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Rot, 1);
b.emit(Op::Sub, 1);
b.emit(Op::Sub, 1);
assert_native_int(b.build(), 0);
}
#[test]
fn native_logical_and_spaceship() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::LogAnd, 1);
b.emit(Op::Inc, 1);
assert_native_int(b.build(), 2);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::LogOr, 1);
b.emit(Op::Inc, 1);
assert_native_int(b.build(), 1);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(3), 1);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::Spaceship, 1);
assert_native_int(b.build(), -1);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
b.emit(Op::LoadFloat(5.0), 1);
b.emit(Op::Spaceship, 1);
assert_native_int(b.build(), 0);
}
#[test]
fn native_keep_jumps() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(5), 1);
let jt = b.emit(Op::JumpIfTrueKeep(0), 1);
b.emit(Op::Pop, 1); b.emit(Op::LoadInt(20), 1);
let l = b.current_pos();
b.patch_jump(jt, l);
assert_native_int(b.build(), 5);
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
let jf = b.emit(Op::JumpIfFalseKeep(0), 1);
b.emit(Op::Pop, 1);
b.emit(Op::LoadInt(7), 1);
let l = b.current_pos();
b.patch_jump(jf, l);
assert_native_int(b.build(), 0);
}
#[test]
fn native_float_arithmetic() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(1.5), 1);
b.emit(Op::LoadFloat(2.0), 1);
b.emit(Op::Mul, 1);
b.emit(Op::LoadFloat(1.0), 1);
b.emit(Op::Add, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
match run_chunk_native(&chunk, |_| {}).expect("native run") {
VMResult::Ok(v) => assert_eq!(v, Value::Float(4.0)),
other => panic!("expected Ok(4.0), got {other:?}"),
}
}
#[test]
fn native_mixed_int_float_promotion() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(3), 1);
b.emit(Op::LoadFloat(0.5), 1);
b.emit(Op::Add, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
#[test]
fn native_float_comparison_branch() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(1.5), 1);
b.emit(Op::LoadFloat(2.5), 1);
b.emit(Op::NumLt, 1);
let jf = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::LoadFloat(3.0), 1);
let j = b.emit(Op::Jump(0), 1);
let else_ip = b.emit(Op::LoadFloat(4.0), 1);
let end_ip = b.current_pos();
b.patch_jump(jf, else_ip);
b.patch_jump(j, end_ip);
let chunk = b.build();
assert!(native_lowerable(&chunk));
assert_native_matches_interp(chunk);
}
#[test]
fn native_float_const_pool() {
let mut b = ChunkBuilder::new();
let c = b.add_constant(Value::Float(2.25));
b.emit(Op::LoadConst(c), 1);
b.emit(Op::LoadFloat(4.0), 1);
b.emit(Op::Mul, 1);
let chunk = b.build();
assert!(native_lowerable(&chunk), "numeric const should lower");
assert_native_matches_interp(chunk);
}
#[test]
fn native_differential_floats() {
let mut seed: u64 = 0x2545_F491_4F6C_DD1D;
let mut next = || {
seed = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = seed;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
};
fn push_num(b: &mut ChunkBuilder, next: &mut impl FnMut() -> u64) {
if next() % 2 == 0 {
b.emit(Op::LoadInt((next() % 13) as i64 - 6), 1);
} else {
let q = (next() % 25) as f64 / 4.0 - 3.0; b.emit(Op::LoadFloat(q), 1);
}
}
fn push_binop(b: &mut ChunkBuilder, next: &mut impl FnMut() -> u64) {
match next() % 3 {
0 => b.emit(Op::Add, 1),
1 => b.emit(Op::Sub, 1),
_ => b.emit(Op::Mul, 1),
};
}
fn emit_expr(b: &mut ChunkBuilder, next: &mut impl FnMut() -> u64, terms: usize) {
push_num(b, next);
for _ in 0..terms {
push_num(b, next);
push_binop(b, next);
}
}
let mut lowered = 0;
for _ in 0..400 {
let mut b = ChunkBuilder::new();
if next() % 2 == 0 {
let terms = (next() % 4) as usize;
emit_expr(&mut b, &mut next, terms);
} else {
push_num(&mut b, &mut next);
push_num(&mut b, &mut next);
match next() % 4 {
0 => b.emit(Op::NumLt, 1),
1 => b.emit(Op::NumGt, 1),
2 => b.emit(Op::NumLe, 1),
_ => b.emit(Op::NumGe, 1),
};
let jf = b.emit(Op::JumpIfFalse(0), 1);
let then_terms = (next() % 3) as usize;
emit_expr(&mut b, &mut next, then_terms);
let j = b.emit(Op::Jump(0), 1);
let else_ip = b.current_pos();
b.patch_jump(jf, else_ip);
let else_terms = (next() % 3) as usize;
emit_expr(&mut b, &mut next, else_terms);
let end_ip = b.current_pos();
b.patch_jump(j, end_ip);
}
let chunk = b.build();
if native_lowerable(&chunk) {
lowered += 1;
assert_native_matches_interp(chunk);
}
}
assert!(lowered > 50, "expected many float chunks to lower, got {lowered}");
}
#[test]
fn native_empty_chunk() {
let b = ChunkBuilder::new();
assert_native_matches_interp(b.build());
}
#[test]
fn native_const_pool() {
let mut b = ChunkBuilder::new();
let c = b.add_constant(Value::str("hello"));
b.emit(Op::LoadConst(c), 1);
assert_native_matches_interp(b.build());
}
#[test]
fn emits_relocatable_object_with_exported_symbols() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(42), 1);
let chunk = b.build();
let obj = std::env::temp_dir().join(format!("fusevm_aot_t_{}.o", std::process::id()));
compile_object(&chunk, &obj).expect("emit object");
assert!(
std::fs::metadata(&obj)
.map(|m| m.len() > 0)
.unwrap_or(false),
"non-empty .o"
);
if let Ok(o) = std::process::Command::new("nm")
.arg("-g")
.arg(&obj)
.output()
{
let syms = String::from_utf8_lossy(&o.stdout);
assert!(syms.contains("fusevm_aot_entry"), "entry exported:\n{syms}");
assert!(
syms.contains("fusevm_aot_chunk_blob"),
"chunk blob exported:\n{syms}"
);
assert!(
syms.contains("fusevm_aot_chunk_len"),
"chunk len exported:\n{syms}"
);
}
let _ = std::fs::remove_file(&obj);
}
}