#![cfg(target_arch = "x86_64")]
use std::sync::atomic::AtomicI64;
use crate::buffer::JitChain;
use crate::jit::{Cmp, CompiledChain, MicroOp, Slot};
use crate::x64asm::{Asm, Cond, LabelId, Reg, Xmm};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Loc {
Reg(Reg),
Xmm(Xmm),
Frame,
}
const BASE: Reg = Reg::R15;
const S0: Reg = Reg::Rax;
const S1: Reg = Reg::Rdx;
const SC: Reg = Reg::Rcx;
const FS0: Xmm = Xmm::Xmm14;
const FS1: Xmm = Xmm::Xmm15;
struct VecLayout {
cap_off: i32,
len_off: i32,
}
fn vec_layout() -> &'static VecLayout {
use std::sync::OnceLock;
static LAYOUT: OnceLock<VecLayout> = OnceLock::new();
LAYOUT.get_or_init(|| {
let mut v: Vec<i64> = Vec::with_capacity(8);
unsafe { v.set_len(3) };
let cap = v.capacity(); let len = v.len(); debug_assert_eq!(std::mem::size_of::<Vec<i64>>(), 24, "Vec is a 3-word triple");
let base = &v as *const Vec<i64> as *const usize;
let words = unsafe { std::slice::from_raw_parts(base, 3) };
let cap_off = words
.iter()
.position(|&w| w == cap)
.expect("Vec capacity field located") as i32
* 8;
let len_off = words
.iter()
.position(|&w| w == len)
.expect("Vec len field located") as i32
* 8;
unsafe { v.set_len(0) };
VecLayout { cap_off, len_off }
})
}
const FLOAT_REGS: [Xmm; 14] = [
Xmm::Xmm0, Xmm::Xmm1, Xmm::Xmm2, Xmm::Xmm3, Xmm::Xmm4, Xmm::Xmm5, Xmm::Xmm6,
Xmm::Xmm7, Xmm::Xmm8, Xmm::Xmm9, Xmm::Xmm10, Xmm::Xmm11, Xmm::Xmm12, Xmm::Xmm13,
];
const CALLEE_SAVED: [Reg; 4] = [Reg::Rbx, Reg::R12, Reg::R13, Reg::R14];
const CALLER_SAVED: [Reg; 6] = [Reg::R8, Reg::R9, Reg::R10, Reg::R11, Reg::Rdi, Reg::Rsi];
pub fn regalloc_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_REGALLOC").map_or(true, |v| v != "0"))
}
pub fn simd_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_SIMD").is_ok_and(|v| v == "1"))
}
fn call_weight_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_CALL_WEIGHT").map_or(true, |v| v != "0"))
}
fn inline_push_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_NO_INLINE_PUSH").map_or(true, |v| v == "0"))
}
fn off_cse_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_NO_OFF_CSE").map_or(true, |v| v != "1"))
}
fn ptr_hoist_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_NO_PTR_HOIST").map_or(true, |v| v != "1"))
}
fn const_hoist_enabled() -> bool {
std::env::var("LOGOS_NO_CONST_HOIST").map_or(true, |v| v != "1")
}
fn dump_class_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_DUMP_CLASS").map_or(false, |v| v != "0"))
}
fn linear_scan_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("LOGOS_LINEAR_SCAN").map_or(false, |v| v == "1"))
}
const SELF_CALL_DEPTH_LIMIT: i64 = crate::jit::BAKED_CALL_DEPTH;
fn supported(op: &MicroOp) -> bool {
matches!(
op,
MicroOp::Move { .. }
| MicroOp::LoadConst { .. }
| MicroOp::Add { .. }
| MicroOp::Sub { .. }
| MicroOp::Mul { .. }
| MicroOp::Lt { .. }
| MicroOp::Gt { .. }
| MicroOp::LtEq { .. }
| MicroOp::GtEq { .. }
| MicroOp::Eq { .. }
| MicroOp::Neq { .. }
| MicroOp::BitAnd { .. }
| MicroOp::BitOr { .. }
| MicroOp::BitXor { .. }
| MicroOp::Shl { .. }
| MicroOp::Shr { .. }
| MicroOp::NotInt { .. }
| MicroOp::NotBool { .. }
| MicroOp::Div { .. }
| MicroOp::Mod { .. }
| MicroOp::DivPow2 { .. }
| MicroOp::MagicDivU { .. }
| MicroOp::Branch { .. }
| MicroOp::Jump { .. }
| MicroOp::JumpIfFalse { .. }
| MicroOp::JumpIfTrue { .. }
| MicroOp::Return { .. }
| MicroOp::ArrLoad { byte: false, .. }
| MicroOp::ArrStore { byte: false, .. }
| MicroOp::ArrLoad { byte: true, .. }
| MicroOp::ArrStore { byte: true, .. }
| MicroOp::AddF { .. }
| MicroOp::SubF { .. }
| MicroOp::MulF { .. }
| MicroOp::DivF { .. }
| MicroOp::SqrtF { .. }
| MicroOp::IntToFloat { .. }
| MicroOp::FmaF { .. }
| MicroOp::LtF { .. }
| MicroOp::GtF { .. }
| MicroOp::LtEqF { .. }
| MicroOp::GtEqF { .. }
| MicroOp::EqF { .. }
| MicroOp::NeqF { .. }
| MicroOp::BranchF { .. }
| MicroOp::ArrPush { .. }
| MicroOp::ListClear { .. }
| MicroOp::StrAppend { .. }
)
}
fn supported_function(op: &MicroOp) -> bool {
supported(op) || matches!(op, MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. })
}
fn supported_function_precise(op: &MicroOp) -> bool {
supported(op)
|| matches!(
op,
MicroOp::Call { .. } | MicroOp::ListTriple { .. } | MicroOp::NewList { .. }
)
}
fn needs_deopt(op: &MicroOp) -> bool {
matches!(
op,
MicroOp::Div { .. }
| MicroOp::Mod { .. }
| MicroOp::DivF { .. }
| MicroOp::ArrLoad { checked: true, .. }
| MicroOp::ArrStore { checked: true, .. }
| MicroOp::Add { .. }
| MicroOp::Sub { .. }
| MicroOp::Mul { .. }
| MicroOp::CallSelf { .. }
| MicroOp::CallSelfCopy { .. }
| MicroOp::Call { .. }
)
}
fn slots_of(op: &MicroOp, out: &mut Vec<Slot>) {
match *op {
MicroOp::Move { dst, src } | MicroOp::NotInt { dst, src } | MicroOp::NotBool { dst, src } => {
out.extend([dst, src])
}
MicroOp::LoadConst { dst, .. } => out.push(dst),
MicroOp::Add { dst, lhs, rhs }
| MicroOp::Sub { dst, lhs, rhs }
| MicroOp::Mul { dst, lhs, rhs }
| MicroOp::Lt { dst, lhs, rhs }
| MicroOp::Gt { dst, lhs, rhs }
| MicroOp::LtEq { dst, lhs, rhs }
| MicroOp::GtEq { dst, lhs, rhs }
| MicroOp::Eq { dst, lhs, rhs }
| MicroOp::Neq { dst, lhs, rhs }
| MicroOp::BitAnd { dst, lhs, rhs }
| MicroOp::BitOr { dst, lhs, rhs }
| MicroOp::BitXor { dst, lhs, rhs }
| MicroOp::Shl { dst, lhs, rhs }
| MicroOp::Shr { dst, lhs, rhs }
| MicroOp::Div { dst, lhs, rhs }
| MicroOp::Mod { dst, lhs, rhs }
| MicroOp::AddF { dst, lhs, rhs }
| MicroOp::SubF { dst, lhs, rhs }
| MicroOp::MulF { dst, lhs, rhs }
| MicroOp::DivF { dst, lhs, rhs }
| MicroOp::LtF { dst, lhs, rhs }
| MicroOp::GtF { dst, lhs, rhs }
| MicroOp::LtEqF { dst, lhs, rhs }
| MicroOp::GtEqF { dst, lhs, rhs }
| MicroOp::EqF { dst, lhs, rhs }
| MicroOp::NeqF { dst, lhs, rhs } => out.extend([dst, lhs, rhs]),
MicroOp::FmaF { dst, a, b, c } => out.extend([dst, a, b, c]),
MicroOp::IntToFloat { dst, src } | MicroOp::SqrtF { dst, src } => out.extend([dst, src]),
MicroOp::DivPow2 { dst, lhs, .. } => out.extend([dst, lhs]),
MicroOp::MagicDivU { dst, lhs, .. } => out.extend([dst, lhs]),
MicroOp::Branch { lhs, rhs, .. } | MicroOp::BranchF { lhs, rhs, .. } => out.extend([lhs, rhs]),
MicroOp::JumpIfFalse { cond, .. } | MicroOp::JumpIfTrue { cond, .. } => out.push(cond),
MicroOp::Return { src } => out.push(src),
MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, checked, .. } => {
out.extend([dst, idx, ptr_slot]);
if checked {
out.push(len_slot);
}
}
MicroOp::ArrStore { src, idx, ptr_slot, len_slot, checked, .. } => {
out.extend([src, idx, ptr_slot]);
if checked {
out.push(len_slot);
}
}
MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, .. } => {
out.extend([src, vec_slot, ptr_slot, len_slot])
}
MicroOp::ListClear { vec_slot, ptr_slot, len_slot, .. } => {
out.extend([vec_slot, ptr_slot, len_slot])
}
MicroOp::NewList { vec_slot, ptr_slot, len_slot, .. } => {
out.extend([vec_slot, ptr_slot, len_slot])
}
MicroOp::StrAppend { text_handle_slot, src, .. } => {
out.push(text_handle_slot);
if let crate::jit::StrSrc::Byte(s) = src {
out.push(s);
}
}
MicroOp::Call { dst, limit_slot, .. } => out.extend([dst, limit_slot]),
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
out.extend([handle_slot, vec_slot, ptr_slot, len_slot])
}
MicroOp::CallSelf { dst, limit_slot, .. } => out.extend([dst, limit_slot]),
MicroOp::CallSelfCopy { dst, src_start, arg_count, limit_slot, .. } => {
out.extend([dst, limit_slot]);
for j in 0..arg_count {
out.push(src_start + j);
}
}
MicroOp::Jump { .. } => {}
_ => {}
}
}
fn op_target(op: &MicroOp) -> Option<usize> {
match *op {
MicroOp::Jump { target }
| MicroOp::JumpIfFalse { target, .. }
| MicroOp::JumpIfTrue { target, .. }
| MicroOp::Branch { target, .. }
| MicroOp::BranchF { target, .. } => Some(target),
_ => None,
}
}
fn loop_depths(ops: &[MicroOp]) -> Vec<u32> {
let n = ops.len();
let mut depth = vec![0u32; n];
for (idx, op) in ops.iter().enumerate() {
if let Some(target) = op_target(op) {
if target <= idx {
for d in depth.iter_mut().take(idx + 1).skip(target) {
*d += 1;
}
}
}
}
depth
}
fn has_shared_index_run(ops: &[MicroOp]) -> bool {
let mut is_target = vec![false; ops.len()];
for op in ops {
if let Some(t) = op_target(op) {
if let Some(slot) = is_target.get_mut(t) {
*slot = true;
}
}
}
let mut cache: Option<(Slot, bool)> = None; for (i, op) in ops.iter().enumerate() {
if is_target[i] {
cache = None;
}
match *op {
MicroOp::ArrLoad { idx, byte, .. } | MicroOp::ArrStore { idx, byte, .. } => {
if cache == Some((idx, byte)) {
return true; }
cache = Some((idx, byte));
}
_ => {}
}
if is_sysv_call(op) {
cache = None;
} else if let Some((idx, _)) = cache {
if dest_of(op) == Some(idx) {
cache = None;
}
}
}
false
}
fn is_sysv_call(op: &MicroOp) -> bool {
matches!(
op,
MicroOp::CallSelf { .. }
| MicroOp::CallSelfCopy { .. }
| MicroOp::Call { .. }
| MicroOp::NewList { .. }
| MicroOp::ArrPush { .. }
| MicroOp::ListClear { .. }
| MicroOp::ListTriple { .. }
| MicroOp::StrAppend { .. }
)
}
#[derive(Clone, Copy)]
struct HoistPlan {
head: usize,
back: usize,
ptr_slot: Slot,
len_slot: Slot,
needs_len: bool,
accesses: u32,
}
fn op_writes_slot(op: &MicroOp, s: Slot) -> bool {
if dest_of(op) == Some(s) {
return true;
}
match *op {
MicroOp::NewList { vec_slot, ptr_slot, len_slot, .. }
| MicroOp::ArrPush { vec_slot, ptr_slot, len_slot, .. }
| MicroOp::ListClear { vec_slot, ptr_slot, len_slot, .. } => {
s == vec_slot || s == ptr_slot || s == len_slot
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
s == handle_slot || s == vec_slot || s == ptr_slot || s == len_slot
}
_ => false,
}
}
fn loop_invariant_array_hoists(ops: &[MicroOp]) -> Vec<HoistPlan> {
let mut loops: Vec<(usize, usize)> = Vec::new();
for (back, op) in ops.iter().enumerate() {
if let Some(head) = op_target(op) {
if head <= back {
loops.push((head, back));
}
}
}
let mut plans: Vec<HoistPlan> = Vec::new();
for &(head, back) in &loops {
let entry_ok = ops.iter().enumerate().all(|(i, op)| match op_target(op) {
Some(t) if t == head => (head..=back).contains(&i),
_ => true,
});
if !entry_ok {
continue;
}
let mut accessed: std::collections::BTreeMap<Slot, (Slot, bool, u32)> =
std::collections::BTreeMap::new();
for op in &ops[head..=back] {
match *op {
MicroOp::ArrLoad { ptr_slot, len_slot, checked, .. }
| MicroOp::ArrStore { ptr_slot, len_slot, checked, .. } => {
let e = accessed.entry(ptr_slot).or_insert((len_slot, false, 0));
e.1 |= checked;
e.2 += 1;
}
_ => {}
}
}
for (ptr_slot, (len_slot, any_checked, accesses)) in accessed {
let invariant = !ops[head..=back]
.iter()
.any(|op| op_writes_slot(op, ptr_slot) || op_writes_slot(op, len_slot));
if invariant {
plans.push(HoistPlan {
head,
back,
ptr_slot,
len_slot,
needs_len: any_checked,
accesses,
});
}
}
}
plans.sort_by_key(|p| (p.ptr_slot, p.back - p.head));
let mut chosen: Vec<HoistPlan> = Vec::new();
for p in plans {
let overlaps_kept = chosen.iter().any(|c| {
c.ptr_slot == p.ptr_slot && c.head <= p.back && p.head <= c.back
});
if !overlaps_kept {
chosen.push(p);
}
}
chosen
}
#[derive(Clone, Copy)]
struct ConstHoist {
op: usize,
head: usize,
dst: Slot,
value: i64,
}
fn loop_invariant_const_hoists(ops: &[MicroOp]) -> Vec<ConstHoist> {
let mut loops: Vec<(usize, usize)> = Vec::new();
for (back, op) in ops.iter().enumerate() {
if let Some(head) = op_target(op) {
if head <= back {
loops.push((head, back));
}
}
}
let mut chosen: Vec<ConstHoist> = Vec::new();
for (i, op) in ops.iter().enumerate() {
let MicroOp::LoadConst { dst, value } = *op else { continue };
let inner = loops
.iter()
.copied()
.filter(|&(head, back)| (head..=back).contains(&i))
.min_by_key(|&(head, back)| back - head);
let Some((head, back)) = inner else { continue };
let entry_ok = ops.iter().enumerate().all(|(j, o)| match op_target(o) {
Some(t) if t == head => (head..=back).contains(&j),
_ => true,
});
if !entry_ok {
continue;
}
let sole_writer = ops[head..=back]
.iter()
.enumerate()
.all(|(off, o)| head + off == i || dest_of(o) != Some(dst));
if sole_writer {
chosen.push(ConstHoist { op: i, head, dst, value });
}
}
chosen
}
fn max_slot_of(ops: &[MicroOp]) -> usize {
let mut max_slot: usize = 0;
let mut buf = Vec::new();
for op in ops {
buf.clear();
slots_of(op, &mut buf);
for &s in &buf {
max_slot = max_slot.max(s as usize);
}
if let MicroOp::CallSelf { args_start, frame_size, .. }
| MicroOp::CallSelfCopy { args_start, frame_size, .. } = *op
{
let last = (args_start as i64) + frame_size - 1;
if last >= 0 {
max_slot = max_slot.max(last as usize);
}
}
}
max_slot
}
fn rank_slots(
ops: &[MicroOp],
loop_weight_base: u64,
loop_depth_cap: u32,
call_weight: u64,
live_after: Option<&[Vec<bool>]>,
) -> (Vec<(Slot, u64)>, usize) {
let depths = loop_depths(ops);
let max_slot = max_slot_of(ops);
let mut refs: std::collections::HashMap<Slot, u64> = std::collections::HashMap::new();
let mut call_surv: std::collections::HashMap<Slot, u64> = std::collections::HashMap::new();
let mut buf = Vec::new();
for (idx, op) in ops.iter().enumerate() {
let weight = loop_weight_base.saturating_pow(depths[idx].min(loop_depth_cap));
buf.clear();
slots_of(op, &mut buf);
for &s in &buf {
let e = refs.entry(s).or_insert(0);
*e = e.saturating_add(weight);
}
}
if let Some(la) = live_after {
for (idx, op) in ops.iter().enumerate() {
if !is_sysv_call(op) {
continue;
}
let call_depth_weight =
loop_weight_base.saturating_pow(depths[idx].min(loop_depth_cap));
let bonus = call_weight.saturating_mul(call_depth_weight);
for (s, &live) in la[idx].iter().enumerate() {
if live {
let e = call_surv.entry(s as Slot).or_insert(0);
*e = e.saturating_add(bonus);
}
}
}
}
let mut order: Vec<(Slot, u64)> = refs.iter().map(|(&s, &c)| (s, c)).collect();
order.sort_by(|a, b| {
b.1.cmp(&a.1)
.then_with(|| call_surv.get(&b.0).cmp(&call_surv.get(&a.0)))
.then(a.0.cmp(&b.0))
});
(order, max_slot)
}
fn read_slots_of(op: &MicroOp, out: &mut Vec<Slot>) {
match *op {
MicroOp::Move { src, .. } | MicroOp::NotInt { src, .. } | MicroOp::NotBool { src, .. } => {
out.push(src)
}
MicroOp::LoadConst { .. } => {}
MicroOp::Add { lhs, rhs, .. }
| MicroOp::Sub { lhs, rhs, .. }
| MicroOp::Mul { lhs, rhs, .. }
| MicroOp::Lt { lhs, rhs, .. }
| MicroOp::Gt { lhs, rhs, .. }
| MicroOp::LtEq { lhs, rhs, .. }
| MicroOp::GtEq { lhs, rhs, .. }
| MicroOp::Eq { lhs, rhs, .. }
| MicroOp::Neq { lhs, rhs, .. }
| MicroOp::BitAnd { lhs, rhs, .. }
| MicroOp::BitOr { lhs, rhs, .. }
| MicroOp::BitXor { lhs, rhs, .. }
| MicroOp::Shl { lhs, rhs, .. }
| MicroOp::Shr { lhs, rhs, .. }
| MicroOp::Div { lhs, rhs, .. }
| MicroOp::Mod { lhs, rhs, .. }
| MicroOp::AddF { lhs, rhs, .. }
| MicroOp::SubF { lhs, rhs, .. }
| MicroOp::MulF { lhs, rhs, .. }
| MicroOp::DivF { lhs, rhs, .. }
| MicroOp::LtF { lhs, rhs, .. }
| MicroOp::GtF { lhs, rhs, .. }
| MicroOp::LtEqF { lhs, rhs, .. }
| MicroOp::GtEqF { lhs, rhs, .. }
| MicroOp::EqF { lhs, rhs, .. }
| MicroOp::NeqF { lhs, rhs, .. } => out.extend([lhs, rhs]),
MicroOp::FmaF { a, b, c, .. } => out.extend([a, b, c]),
MicroOp::IntToFloat { src, .. } | MicroOp::SqrtF { src, .. } => out.push(src),
MicroOp::DivPow2 { lhs, .. } => out.push(lhs),
MicroOp::MagicDivU { lhs, .. } => out.push(lhs),
MicroOp::Branch { lhs, rhs, .. } | MicroOp::BranchF { lhs, rhs, .. } => out.extend([lhs, rhs]),
MicroOp::JumpIfFalse { cond, .. } | MicroOp::JumpIfTrue { cond, .. } => out.push(cond),
MicroOp::Return { src } => out.push(src),
MicroOp::ArrLoad { idx, ptr_slot, len_slot, checked, .. } => {
out.extend([idx, ptr_slot]);
if checked {
out.push(len_slot);
}
}
MicroOp::ArrStore { src, idx, ptr_slot, len_slot, checked, .. } => {
out.extend([src, idx, ptr_slot]);
if checked {
out.push(len_slot);
}
}
MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, .. } => {
out.extend([src, vec_slot, ptr_slot, len_slot])
}
MicroOp::ListClear { vec_slot, ptr_slot, len_slot, .. } => {
out.extend([vec_slot, ptr_slot, len_slot])
}
MicroOp::StrAppend { text_handle_slot, src, .. } => {
out.push(text_handle_slot);
if let crate::jit::StrSrc::Byte(s) = src {
out.push(s);
}
}
MicroOp::Call { limit_slot, .. } => out.push(limit_slot),
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
out.extend([handle_slot, vec_slot, ptr_slot, len_slot])
}
MicroOp::CallSelf { limit_slot, .. } => out.push(limit_slot),
MicroOp::CallSelfCopy { src_start, arg_count, limit_slot, .. } => {
out.push(limit_slot);
for j in 0..arg_count {
out.push(src_start + j);
}
}
MicroOp::Jump { .. } => {}
_ => {}
}
}
fn liveness_after(ops: &[MicroOp], max_slot: usize) -> Vec<Vec<bool>> {
let n = ops.len();
let width = max_slot + 1;
let mut live_in: Vec<Vec<bool>> = vec![vec![false; width]; n];
let mut uses: Vec<Vec<Slot>> = Vec::with_capacity(n);
let mut defs: Vec<Option<Slot>> = Vec::with_capacity(n);
for op in ops {
let mut u = Vec::new();
read_slots_of(op, &mut u);
uses.push(u);
defs.push(dest_of(op));
}
let mut changed = true;
while changed {
changed = false;
for i in (0..n).rev() {
let mut out = vec![false; width];
let unconditional_transfer = matches!(ops[i], MicroOp::Jump { .. } | MicroOp::Return { .. });
if !unconditional_transfer {
if let Some(succ) = live_in.get(i + 1) {
for (o, &s) in out.iter_mut().zip(succ.iter()) {
*o |= s;
}
}
}
if let Some(t) = op_target(&ops[i]) {
if let Some(succ) = live_in.get(t) {
for (o, &s) in out.iter_mut().zip(succ.iter()) {
*o |= s;
}
}
}
let mut new_in = out.clone();
if let Some(d) = defs[i] {
if (d as usize) < width {
new_in[d as usize] = false;
}
}
for &u in &uses[i] {
if (u as usize) < width {
new_in[u as usize] = true;
}
}
if new_in != live_in[i] {
live_in[i] = new_in;
changed = true;
}
}
}
let mut live_after: Vec<Vec<bool>> = vec![vec![false; width]; n];
for i in 0..n {
let unconditional_transfer = matches!(ops[i], MicroOp::Jump { .. } | MicroOp::Return { .. });
if !unconditional_transfer {
if let Some(succ) = live_in.get(i + 1) {
for (o, &s) in live_after[i].iter_mut().zip(succ.iter()) {
*o |= s;
}
}
}
if let Some(t) = op_target(&ops[i]) {
if let Some(succ) = live_in.get(t) {
for (o, &s) in live_after[i].iter_mut().zip(succ.iter()) {
*o |= s;
}
}
}
}
live_after
}
fn cond_of(cmp: Cmp) -> Cond {
match cmp {
Cmp::Lt => Cond::Lt,
Cmp::Gt => Cond::Gt,
Cmp::LtEq => Cond::Le,
Cmp::GtEq => Cond::Ge,
Cmp::Eq => Cond::Eq,
Cmp::NotEq => Cond::Ne,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Class {
Int,
Float,
Mixed,
}
#[derive(Clone, Copy, Default)]
struct Roles {
int: bool,
float: bool,
}
fn tally_roles(op: &MicroOp, roles: &mut [Roles]) {
macro_rules! mark_int {
($s:expr) => {
roles[$s as usize].int = true
};
}
match *op {
MicroOp::Add { dst, lhs, rhs }
| MicroOp::Sub { dst, lhs, rhs }
| MicroOp::Mul { dst, lhs, rhs }
| MicroOp::Div { dst, lhs, rhs }
| MicroOp::Mod { dst, lhs, rhs }
| MicroOp::Lt { dst, lhs, rhs }
| MicroOp::Gt { dst, lhs, rhs }
| MicroOp::LtEq { dst, lhs, rhs }
| MicroOp::GtEq { dst, lhs, rhs }
| MicroOp::Eq { dst, lhs, rhs }
| MicroOp::Neq { dst, lhs, rhs }
| MicroOp::BitAnd { dst, lhs, rhs }
| MicroOp::BitOr { dst, lhs, rhs }
| MicroOp::BitXor { dst, lhs, rhs }
| MicroOp::Shl { dst, lhs, rhs }
| MicroOp::Shr { dst, lhs, rhs } => {
mark_int!(dst);
mark_int!(lhs);
mark_int!(rhs);
}
MicroOp::DivPow2 { dst, lhs, .. } | MicroOp::MagicDivU { dst, lhs, .. } => {
mark_int!(dst);
mark_int!(lhs);
}
MicroOp::NotInt { dst, src } | MicroOp::NotBool { dst, src } => {
mark_int!(dst);
mark_int!(src);
}
MicroOp::Branch { lhs, rhs, .. } => {
mark_int!(lhs);
mark_int!(rhs);
}
MicroOp::JumpIfFalse { cond, .. } | MicroOp::JumpIfTrue { cond, .. } => mark_int!(cond),
MicroOp::ArrLoad { idx, ptr_slot, len_slot, checked, .. } => {
mark_int!(idx);
mark_int!(ptr_slot);
if checked {
mark_int!(len_slot);
}
}
MicroOp::ArrStore { idx, ptr_slot, len_slot, checked, .. } => {
mark_int!(idx);
mark_int!(ptr_slot);
if checked {
mark_int!(len_slot);
}
}
MicroOp::ArrPush { vec_slot, ptr_slot, len_slot, .. } => {
mark_int!(vec_slot);
mark_int!(ptr_slot);
mark_int!(len_slot);
}
MicroOp::ListClear { vec_slot, ptr_slot, len_slot, .. } => {
mark_int!(vec_slot);
mark_int!(ptr_slot);
mark_int!(len_slot);
}
MicroOp::AddF { dst, lhs, rhs }
| MicroOp::SubF { dst, lhs, rhs }
| MicroOp::MulF { dst, lhs, rhs }
| MicroOp::DivF { dst, lhs, rhs } => {
roles[dst as usize].float = true;
roles[lhs as usize].float = true;
roles[rhs as usize].float = true;
}
MicroOp::LtF { dst, lhs, rhs }
| MicroOp::GtF { dst, lhs, rhs }
| MicroOp::LtEqF { dst, lhs, rhs }
| MicroOp::GtEqF { dst, lhs, rhs }
| MicroOp::EqF { dst, lhs, rhs }
| MicroOp::NeqF { dst, lhs, rhs } => {
roles[dst as usize].int = true;
roles[lhs as usize].float = true;
roles[rhs as usize].float = true;
}
MicroOp::BranchF { lhs, rhs, .. } => {
roles[lhs as usize].float = true;
roles[rhs as usize].float = true;
}
MicroOp::FmaF { dst, a, b, c } => {
roles[dst as usize].float = true;
roles[a as usize].float = true;
roles[b as usize].float = true;
roles[c as usize].float = true;
}
MicroOp::SqrtF { dst, src } => {
roles[dst as usize].float = true;
roles[src as usize].float = true;
}
MicroOp::IntToFloat { dst, src } => {
roles[dst as usize].float = true;
mark_int!(src);
}
MicroOp::CallSelf { dst, limit_slot, .. } => {
mark_int!(dst);
mark_int!(limit_slot);
}
MicroOp::CallSelfCopy { dst, limit_slot, .. } => {
mark_int!(dst);
mark_int!(limit_slot);
}
MicroOp::Call { dst, limit_slot, .. } => {
mark_int!(dst);
mark_int!(limit_slot);
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
mark_int!(handle_slot);
mark_int!(vec_slot);
mark_int!(ptr_slot);
mark_int!(len_slot);
}
MicroOp::StrAppend { text_handle_slot, src, .. } => {
mark_int!(text_handle_slot);
if let crate::jit::StrSrc::Byte(s) = src {
mark_int!(s);
}
}
MicroOp::Move { .. }
| MicroOp::LoadConst { .. }
| MicroOp::Return { .. }
| MicroOp::Jump { .. } => {}
_ => {}
}
}
fn classify_slots(ops: &[MicroOp], max_slot: usize) -> Vec<Class> {
let mut roles = vec![Roles::default(); max_slot + 1];
for op in ops {
tally_roles(op, &mut roles);
}
roles
.iter()
.map(|r| match (r.int, r.float) {
(_, true) if r.int => Class::Mixed,
(false, true) => Class::Float,
_ => Class::Int,
})
.collect()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct LiveInterval {
slot: u16,
start: usize,
end: usize,
}
fn live_intervals(ops: &[MicroOp], max_slot: usize) -> Vec<LiveInterval> {
let mut first = vec![usize::MAX; max_slot + 1];
let mut last = vec![0usize; max_slot + 1];
let mut reads = Vec::new();
for (i, op) in ops.iter().enumerate() {
reads.clear();
read_slots_of(op, &mut reads);
let mut touch = |s: Slot| {
let s = s as usize;
if s <= max_slot {
if first[s] == usize::MAX {
first[s] = i;
}
last[s] = i;
}
};
for &s in &reads {
touch(s);
}
if let Some(d) = dest_of(op) {
touch(d);
}
}
(0..=max_slot)
.filter(|&s| first[s] != usize::MAX)
.map(|s| LiveInterval { slot: s as u16, start: first[s], end: last[s] })
.collect()
}
fn linscan_spills(intervals: &[LiveInterval], class: &[Class], want: Class, regs: usize) -> usize {
let mut iv: Vec<&LiveInterval> =
intervals.iter().filter(|i| class[i.slot as usize] == want).collect();
iv.sort_by_key(|i| (i.start, i.end));
let mut active: Vec<usize> = Vec::new();
let mut spills = 0usize;
for cur in iv {
active.retain(|&e| e >= cur.start); if regs == 0 {
spills += 1;
continue;
}
if active.len() < regs {
let pos = active.partition_point(|&e| e <= cur.end);
active.insert(pos, cur.end);
} else {
let far = *active.last().unwrap();
if far > cur.end {
active.pop();
let pos = active.partition_point(|&e| e <= cur.end);
active.insert(pos, cur.end);
}
spills += 1;
}
}
spills
}
fn live_ranges(ops: &[MicroOp], max_slot: usize) -> Vec<Option<(usize, usize)>> {
let la = liveness_after(ops, max_slot);
let mut start = vec![usize::MAX; max_slot + 1];
let mut end = vec![0usize; max_slot + 1];
let mut reads = Vec::new();
for i in 0..ops.len() {
for s in 0..=max_slot {
if la[i][s] {
if start[s] == usize::MAX {
start[s] = i;
}
end[s] = i;
}
}
reads.clear();
read_slots_of(&ops[i], &mut reads);
let mut touch = |s: usize| {
if s <= max_slot {
if start[s] == usize::MAX {
start[s] = i;
}
if i > end[s] {
end[s] = i;
}
}
};
for &u in &reads {
touch(u as usize);
}
if let Some(d) = dest_of(&ops[i]) {
touch(d as usize);
}
}
(0..=max_slot)
.map(|s| (start[s] != usize::MAX).then_some((start[s], end[s])))
.collect()
}
fn linscan_assign(ops: &[MicroOp], max_slot: usize, class: &[Class], gp: &[Reg], xmm: &[Xmm]) -> Vec<Loc> {
let n = ops.len();
let ranges = live_ranges(ops, max_slot);
let mut is_jt = vec![false; n];
for op in ops {
if let Some(t) = op_target(op) {
if t < n {
is_jt[t] = true;
}
}
}
let mut pref = vec![0u32; n + 1];
for j in 0..n {
pref[j + 1] = pref[j] + u32::from(op_target(&ops[j]).is_some() || is_jt[j]);
}
let ctrl_between = |e: usize, s: usize| -> bool {
let lo = (e + 1).min(n);
let hi = (s + 1).min(n);
pref[hi].saturating_sub(pref[lo]) > 0
};
let mut order: Vec<usize> = (0..=max_slot).filter(|&s| ranges[s].is_some()).collect();
order.sort_by_key(|&s| {
let (a, b) = ranges[s].unwrap();
(a, b, s)
});
let mut gp_fresh: Vec<Reg> = gp.iter().rev().copied().collect();
let mut xmm_fresh: Vec<Xmm> = xmm.iter().rev().copied().collect();
let mut gp_freed: Vec<(Reg, usize)> = Vec::new();
let mut xmm_freed: Vec<(Xmm, usize)> = Vec::new();
let mut active: Vec<(usize, usize)> = Vec::new(); let mut out = vec![Loc::Frame; max_slot + 1];
for &s in &order {
let (st, en) = ranges[s].unwrap();
let mut still = Vec::with_capacity(active.len());
for &(ae, asl) in &active {
if ae < st {
match out[asl] {
Loc::Reg(r) => gp_freed.push((r, ae)),
Loc::Xmm(x) => xmm_freed.push((x, ae)),
Loc::Frame => {}
}
} else {
still.push((ae, asl));
}
}
active = still;
match class[s] {
Class::Int => {
let pick = gp_fresh.pop().or_else(|| {
gp_freed
.iter()
.position(|&(_, e)| !ctrl_between(e, st))
.map(|i| gp_freed.swap_remove(i).0)
});
if let Some(r) = pick {
out[s] = Loc::Reg(r);
active.push((en, s));
}
}
Class::Float => {
let pick = xmm_fresh.pop().or_else(|| {
xmm_freed
.iter()
.position(|&(_, e)| !ctrl_between(e, st))
.map(|i| xmm_freed.swap_remove(i).0)
});
if let Some(x) = pick {
out[s] = Loc::Xmm(x);
active.push((en, s));
}
}
Class::Mixed => {}
}
}
out
}
struct SelfCall {
propagate_label: LabelId,
}
struct Precise<'a> {
codes: &'a [i64],
code_blocks: &'a std::collections::HashMap<i64, LabelId>,
}
struct Gen<'a> {
asm: Asm,
loc: Vec<Loc>,
op_labels: &'a [LabelId],
deopt_label: Option<LabelId>,
stack_pad: i32,
self_call: Option<SelfCall>,
call_gp: &'a [Vec<Slot>],
call_xmm: &'a [Vec<Slot>],
precise: Option<Precise<'a>>,
off_im1_reg: Option<Reg>,
off_scaled_reg: Option<Reg>,
off_cache: Option<OffCacheState>,
hoists: &'a [HoistEntry],
active_hoists: Vec<usize>,
}
#[derive(Clone, Copy)]
struct HoistEntry {
plan: HoistPlan,
ptr_reg: Reg,
len_reg: Option<Reg>,
}
#[derive(Clone, Copy)]
struct OffCacheState {
idx: Slot,
byte: bool,
}
impl Gen<'_> {
fn checked_exit(&self, idx: usize) -> Option<LabelId> {
if let Some(p) = &self.precise {
let code = p.codes[idx];
if code != 1 {
return Some(p.code_blocks[&code]);
}
}
self.deopt_label
}
fn invalidate_off_cache(&mut self) {
self.off_cache = None;
}
fn invalidate_off_cache_if_writes_idx(&mut self, op: &MicroOp) {
if let Some(state) = self.off_cache {
if dest_of(op) == Some(state.idx) {
self.off_cache = None;
}
}
}
fn hoisted_ptr(&self, ptr_slot: Slot) -> Option<Reg> {
self.active_hoists
.iter()
.map(|&k| self.hoists[k])
.find(|e| e.plan.ptr_slot == ptr_slot)
.map(|e| e.ptr_reg)
}
fn hoisted_len(&self, ptr_slot: Slot) -> Option<Reg> {
self.active_hoists
.iter()
.map(|&k| self.hoists[k])
.find(|e| e.plan.ptr_slot == ptr_slot)
.and_then(|e| e.len_reg)
}
}
impl Gen<'_> {
fn spill_volatiles_at(&mut self, idx: usize) {
let gp: &[Slot] = self.call_gp.get(idx).map_or(&[], Vec::as_slice);
for &s in gp {
if let Loc::Reg(r) = self.loc[s as usize] {
self.asm.mov_mr(BASE, (s as i32) * 8, r);
}
}
let xmm: &[Slot] = self.call_xmm.get(idx).map_or(&[], Vec::as_slice);
for &s in xmm {
if let Loc::Xmm(x) = self.loc[s as usize] {
self.asm.movsd_mr(BASE, (s as i32) * 8, x);
}
}
}
fn reload_volatiles_at(&mut self, idx: usize) {
let gp: &[Slot] = self.call_gp.get(idx).map_or(&[], Vec::as_slice);
for &s in gp {
if let Loc::Reg(r) = self.loc[s as usize] {
self.asm.mov_rm(r, BASE, (s as i32) * 8);
}
}
let xmm: &[Slot] = self.call_xmm.get(idx).map_or(&[], Vec::as_slice);
for &s in xmm {
if let Loc::Xmm(x) = self.loc[s as usize] {
self.asm.movsd_rm(x, BASE, (s as i32) * 8);
}
}
}
}
impl<'a> Gen<'a> {
fn load(&mut self, r: Reg, s: Slot) {
match self.loc[s as usize] {
Loc::Reg(src) => self.asm.mov_rr(r, src),
Loc::Frame => self.asm.mov_rm(r, BASE, (s as i32) * 8),
Loc::Xmm(x) => self.asm.movq_rx(r, x),
}
}
fn store(&mut self, s: Slot, r: Reg) {
match self.loc[s as usize] {
Loc::Xmm(x) => self.asm.movq_xr(x, r),
Loc::Reg(dst) => self.asm.mov_rr(dst, r),
Loc::Frame => self.asm.mov_mr(BASE, (s as i32) * 8, r),
}
}
fn operand(&mut self, s: Slot, scratch: Reg) -> Reg {
match self.loc[s as usize] {
Loc::Reg(r) => r,
Loc::Frame => {
self.asm.mov_rm(scratch, BASE, (s as i32) * 8);
scratch
}
Loc::Xmm(_) => {
debug_assert!(false, "int operand on an XMM-resident slot {s}");
self.asm.mov_rm(scratch, BASE, (s as i32) * 8);
scratch
}
}
}
fn fload(&mut self, x: Xmm, s: Slot) {
match self.loc[s as usize] {
Loc::Xmm(src) => self.asm.movsd_rr(x, src),
Loc::Frame => self.asm.movsd_rm(x, BASE, (s as i32) * 8),
Loc::Reg(r) => self.asm.movq_xr(x, r), }
}
fn fstore(&mut self, s: Slot, x: Xmm) {
match self.loc[s as usize] {
Loc::Xmm(dst) => self.asm.movsd_rr(dst, x),
Loc::Frame => self.asm.movsd_mr(BASE, (s as i32) * 8, x),
Loc::Reg(r) => self.asm.movq_rx(r, x), }
}
fn foperand(&mut self, s: Slot, scratch: Xmm) -> Xmm {
match self.loc[s as usize] {
Loc::Xmm(x) => x,
Loc::Frame => {
self.asm.movsd_rm(scratch, BASE, (s as i32) * 8);
scratch
}
Loc::Reg(r) => {
self.asm.movq_xr(scratch, r);
scratch
}
}
}
}
pub fn compile_region_regalloc(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
) -> Option<CompiledChain> {
compile_impl(ops, shared_status, false, 0, None)
}
pub fn compile_region_regalloc_precise(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
depth_addr: i64,
deopt_codes: &[i64],
) -> Option<CompiledChain> {
if deopt_codes.len() != ops.len() {
return None;
}
if shared_status.is_none() {
return None;
}
compile_impl(ops, shared_status, false, depth_addr, Some(deopt_codes))
}
pub fn compile_function_regalloc(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
depth_addr: i64,
) -> Option<CompiledChain> {
let has_self_call = ops
.iter()
.any(|op| matches!(op, MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. }));
if !has_self_call {
return compile_impl(ops, shared_status, true, depth_addr, None);
}
if shared_status.is_none() {
return None;
}
compile_impl(ops, shared_status, true, depth_addr, None)
}
pub fn compile_function_regalloc_precise(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
depth_addr: i64,
deopt_codes: &[i64],
) -> Option<CompiledChain> {
if deopt_codes.len() != ops.len() {
return None;
}
if shared_status.is_none() {
return None;
}
compile_impl(ops, shared_status, true, depth_addr, Some(deopt_codes))
}
fn infer_mode_b_rc(ops: &[MicroOp]) -> Option<u16> {
match ops.first() {
Some(MicroOp::LoadConst { dst, value: -1 }) if *dst >= 2 => Some(dst - 2),
_ => None,
}
}
fn compile_impl(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
is_function: bool,
depth_addr: i64,
precise: Option<&[i64]>,
) -> Option<CompiledChain> {
if ops.is_empty() {
return None;
}
if !is_function && simd_enabled() {
if let Some(plan) = crate::vectorize::recognize_elementwise_map(ops) {
if let Some(code) =
crate::vectorize::emit_map_kernel(&ops[plan.body_start..plan.body_end], &plan)
{
let chain = JitChain::from_code(&code, ops.len()).ok()?;
return Some(CompiledChain::from_chain(chain, None));
}
}
}
let mode_b_rc: Option<u16> = if precise.is_some() && is_function {
Some(infer_mode_b_rc(ops)?)
} else {
None
};
let gate: fn(&MicroOp) -> bool = match (is_function, precise.is_some()) {
(true, true) => supported_function_precise,
(true, false) => supported_function,
_ => supported,
};
if !ops.iter().all(gate) {
return None;
}
if !matches!(ops.last(), Some(MicroOp::Return { .. }) | Some(MicroOp::Jump { .. })) {
return None;
}
let has_self_call = ops
.iter()
.any(|op| matches!(op, MicroOp::CallSelf { .. } | MicroOp::CallSelfCopy { .. }));
let has_precise_call = ops.iter().any(|op| matches!(op, MicroOp::Call { .. }));
let has_list_call = ops.iter().any(|op| {
matches!(
op,
MicroOp::NewList { .. }
| MicroOp::ArrPush { .. }
| MicroOp::ListClear { .. }
| MicroOp::ListTriple { .. }
| MicroOp::StrAppend { .. }
)
});
let has_call = has_self_call || has_list_call || has_precise_call;
let mut force_frame_set: std::collections::HashSet<Slot> = std::collections::HashSet::new();
for op in ops {
match *op {
MicroOp::NewList { vec_slot, ptr_slot, len_slot, .. }
| MicroOp::ArrPush { vec_slot, ptr_slot, len_slot, .. }
| MicroOp::ListClear { vec_slot, ptr_slot, len_slot, .. } => {
force_frame_set.insert(vec_slot);
force_frame_set.insert(ptr_slot);
force_frame_set.insert(len_slot);
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
force_frame_set.insert(handle_slot);
force_frame_set.insert(vec_slot);
force_frame_set.insert(ptr_slot);
force_frame_set.insert(len_slot);
}
MicroOp::StrAppend { text_handle_slot, .. } => {
force_frame_set.insert(text_handle_slot);
}
_ => {}
}
}
let mut force_frame_above: Option<u16> = None;
for op in ops {
let args_start = match *op {
MicroOp::CallSelf { args_start, .. }
| MicroOp::CallSelfCopy { args_start, .. }
| MicroOp::Call { args_start, .. } => args_start,
_ => continue,
};
force_frame_above = Some(force_frame_above.map_or(args_start, |a| a.min(args_start)));
}
if let Some(rc) = mode_b_rc {
force_frame_above = Some(force_frame_above.map_or(rc, |a| a.min(rc)));
}
let needs_status = ops.iter().any(needs_deopt);
let status: Option<std::sync::Arc<AtomicI64>> = if needs_status {
Some(shared_status.unwrap_or_else(|| std::sync::Arc::new(AtomicI64::new(0))))
} else {
shared_status
};
let max_slot = max_slot_of(ops);
let linscan_active = linear_scan_enabled() && !has_call && precise.is_none();
let live_after: Option<Vec<Vec<bool>>> = has_call.then(|| liveness_after(ops, max_slot));
const LOOP_WEIGHT_BASE: u64 = 16;
const LOOP_DEPTH_CAP: u32 = 4;
const CALL_WEIGHT: u64 = 4;
let call_weight = if call_weight_enabled() { CALL_WEIGHT } else { 0 };
let (order, _) = rank_slots(
ops,
LOOP_WEIGHT_BASE,
LOOP_DEPTH_CAP,
call_weight,
live_after.as_deref(),
);
let class = classify_slots(ops, max_slot);
let mut pool: Vec<Reg> = Vec::new();
if has_call {
pool.extend_from_slice(&CALLEE_SAVED);
pool.extend_from_slice(&CALLER_SAVED);
} else {
pool.extend_from_slice(&CALLER_SAVED);
pool.extend_from_slice(&CALLEE_SAVED);
}
let (off_im1_reg, off_scaled_reg) = if off_cse_enabled() && has_shared_index_run(ops) {
let mut reservable: Vec<Reg> =
pool.iter().copied().filter(|r| CALLER_SAVED.contains(r)).collect();
let want = if reservable.len() >= 3 { 2 } else { usize::from(!reservable.is_empty()) };
let mut reserved = Vec::new();
for _ in 0..want {
if let Some(r) = reservable.pop() {
reserved.push(r);
}
}
pool.retain(|r| !reserved.contains(r));
(reserved.first().copied(), reserved.get(1).copied())
} else {
(None, None)
};
let mut used_callee: Vec<Reg> = Vec::new();
let mut hoists: Vec<HoistEntry> = Vec::new();
if ptr_hoist_enabled() {
let mut reservable_callee: Vec<Reg> =
pool.iter().copied().filter(|r| CALLEE_SAVED.contains(r)).collect();
let mut reserved_for_hoist: Vec<Reg> = Vec::new();
let plans = loop_invariant_array_hoists(ops);
for plan in plans {
let span_calls = ops[plan.head..=plan.back].iter().any(is_sysv_call);
if plan.accesses < 2 && !span_calls {
continue;
}
let need = 1 + usize::from(plan.needs_len);
if reservable_callee.len().saturating_sub(need) < 1 {
continue;
}
let ptr_reg = reservable_callee.remove(0);
reserved_for_hoist.push(ptr_reg);
let len_reg = if plan.needs_len {
let r = reservable_callee.remove(0);
reserved_for_hoist.push(r);
Some(r)
} else {
None
};
hoists.push(HoistEntry { plan, ptr_reg, len_reg });
}
pool.retain(|r| !reserved_for_hoist.contains(r));
for r in reserved_for_hoist {
used_callee.push(r);
}
}
let mut loc = vec![Loc::Frame; max_slot + 1];
{
let mut pi = 0usize; let mut fi = 0usize; for (s, _) in &order {
if let Some(thresh) = force_frame_above {
if *s >= thresh {
continue;
}
}
if force_frame_set.contains(s) {
continue;
}
match class[*s as usize] {
Class::Int => {
if pi >= pool.len() {
continue;
}
let r = pool[pi];
pi += 1;
loc[*s as usize] = Loc::Reg(r);
if CALLEE_SAVED.contains(&r) {
used_callee.push(r);
}
}
Class::Float => {
if fi >= FLOAT_REGS.len() {
continue;
}
loc[*s as usize] = Loc::Xmm(FLOAT_REGS[fi]);
fi += 1;
}
Class::Mixed => {} }
}
}
if linscan_active {
let gp_all: Vec<Reg> = CALLER_SAVED.iter().chain(CALLEE_SAVED.iter()).copied().collect();
let scan = linscan_assign(ops, max_slot, &class, &gp_all, &FLOAT_REGS);
for s in 0..=max_slot {
if class[s] == Class::Float {
let forced = force_frame_set.contains(&(s as u16))
|| force_frame_above.is_some_and(|t| s as u16 >= t);
loc[s] = if forced { Loc::Frame } else { scan[s] };
}
}
}
let (ls_range, ls_shared): (Vec<Option<(usize, usize)>>, Vec<bool>) = if linscan_active {
let ranges = live_ranges(ops, max_slot);
let mut shared = vec![false; max_slot + 1];
for a in 0..=max_slot {
if matches!(loc[a], Loc::Frame) || ranges[a].is_none() {
continue;
}
for b in 0..=max_slot {
if a != b && loc[b] == loc[a] && ranges[b].is_some() {
shared[a] = true;
break;
}
}
}
(ranges, shared)
} else {
(Vec::new(), Vec::new())
};
if linscan_active && std::env::var_os("LOGOS_DUMP_LS").is_some() {
eprintln!("LS-REGION ops={} max_slot={}", ops.len(), max_slot);
for (i, op) in ops.iter().enumerate() {
eprintln!(" op{i:>3}: {op:?}");
}
for s in 0..=max_slot {
if let Some((a, b)) = ls_range[s] {
eprintln!(" slot{s:>3} [{a:>3},{b:>3}] {:?} shared={}", loc[s], ls_shared[s]);
}
}
}
if dump_class_enabled() {
let (mut n_int, mut n_float, mut n_mixed) = (0usize, 0usize, 0usize);
let (mut f_xmm, mut f_frame) = (0usize, 0usize);
let mut mixed_slots: Vec<u16> = Vec::new();
for s in 0..=max_slot {
match class[s] {
Class::Int => n_int += 1,
Class::Float => {
n_float += 1;
match loc[s] {
Loc::Xmm(_) => f_xmm += 1,
_ => f_frame += 1,
}
}
Class::Mixed => {
n_mixed += 1;
mixed_slots.push(s as u16);
}
}
}
let la = liveness_after(ops, max_slot);
let (mut max_live_f, mut max_live_i) = (0usize, 0usize);
for row in &la {
let (mut lf, mut li) = (0usize, 0usize);
for s in 0..=max_slot {
if s < row.len() && row[s] {
match class[s] {
Class::Float => lf += 1,
Class::Int => li += 1,
Class::Mixed => {}
}
}
}
max_live_f = max_live_f.max(lf);
max_live_i = max_live_i.max(li);
}
let intervals = live_intervals(ops, max_slot);
let ls_float = linscan_spills(&intervals, &class, Class::Float, FLOAT_REGS.len());
let ls_int = linscan_spills(&intervals, &class, Class::Int, pool.len());
if n_float > 0 || n_mixed > 0 {
eprintln!(
"DUMP-CLASS region ops={} max_slot={} int={} float={} (xmm={} frame={}) mixed={} maxLiveF={} maxLiveI={} lsF={} lsI={} mixed_slots={:?}",
ops.len(), max_slot, n_int, n_float, f_xmm, f_frame, n_mixed, max_live_f, max_live_i, ls_float, ls_int, mixed_slots
);
}
}
let (gp_volatile, xmm_volatile): (Vec<Slot>, Vec<Slot>) = if has_call {
let mut gp = Vec::new();
let mut xmm = Vec::new();
for s in 0..=max_slot as u16 {
match loc[s as usize] {
Loc::Reg(r) if CALLER_SAVED.contains(&r) => gp.push(s),
Loc::Xmm(_) => xmm.push(s),
_ => {}
}
}
(gp, xmm)
} else {
(Vec::new(), Vec::new())
};
let mut written: Vec<bool> = vec![false; max_slot + 1];
for op in ops {
if let Some(d) = dest_of(op) {
written[d as usize] = true;
}
}
let (call_gp, call_xmm): (Vec<Vec<Slot>>, Vec<Vec<Slot>>) = if let Some(live_after) =
live_after.as_deref()
{
let keep = |s: Slot, la: &[bool]| -> bool {
written.get(s as usize).copied().unwrap_or(true)
|| la.get(s as usize).copied().unwrap_or(true)
};
let mut cgp: Vec<Vec<Slot>> = vec![Vec::new(); ops.len()];
let mut cxmm: Vec<Vec<Slot>> = vec![Vec::new(); ops.len()];
for idx in 0..ops.len() {
let la = &live_after[idx];
cgp[idx] = gp_volatile.iter().copied().filter(|&s| keep(s, la)).collect();
cxmm[idx] = xmm_volatile.iter().copied().filter(|&s| keep(s, la)).collect();
}
(cgp, cxmm)
} else {
(Vec::new(), Vec::new())
};
let mut asm = Asm::new();
let op_labels: Vec<LabelId> = (0..ops.len()).map(|_| asm.new_label()).collect();
let deopt_label = if needs_status { Some(asm.new_label()) } else { None };
let propagate_label = (has_self_call || has_precise_call).then(|| asm.new_label());
let mut code_blocks: std::collections::HashMap<i64, LabelId> = std::collections::HashMap::new();
let precise_epilogue = if precise.is_some() { Some(asm.new_label()) } else { None };
if let Some(codes) = precise {
for &c in codes {
if c != 1 {
code_blocks.entry(c).or_insert_with(|| asm.new_label());
}
}
}
let pushes = 1 + used_callee.len();
let stack_pad: i32 = if has_call && pushes % 2 == 0 { 8 } else { 0 };
asm.push(BASE);
for &r in &used_callee {
asm.push(r);
}
asm.mov_rr(BASE, Reg::Rdi);
if stack_pad != 0 {
asm.sub_ri(Reg::Rsp, stack_pad);
}
{
for s in 0..=max_slot as u16 {
if linscan_active {
let shared = ls_shared.get(s as usize).copied().unwrap_or(false);
let entry_live = matches!(ls_range.get(s as usize).copied().flatten(), Some((0, _)));
if shared && !entry_live {
continue;
}
}
match loc.get(s as usize) {
Some(Loc::Reg(r)) => asm.mov_rm(*r, BASE, (s as i32) * 8),
Some(Loc::Xmm(x)) => asm.movsd_rm(*x, BASE, (s as i32) * 8),
_ => {}
}
}
}
let status_addr = status
.as_ref()
.map(|c| c.as_ref() as *const AtomicI64 as i64)
.unwrap_or(0);
let entry_cell: Option<std::sync::Arc<AtomicI64>> =
(has_self_call || has_precise_call).then(|| std::sync::Arc::new(AtomicI64::new(0)));
let entry_addr = entry_cell
.as_ref()
.map(|c| c.as_ref() as *const AtomicI64 as i64)
.unwrap_or(0);
let self_call = propagate_label.map(|pl| SelfCall { propagate_label: pl });
let precise_ctx = precise.map(|codes| Precise { codes, code_blocks: &code_blocks });
let mut g = Gen {
asm,
loc: loc.clone(),
op_labels: &op_labels,
deopt_label,
stack_pad,
self_call,
call_gp: &call_gp,
call_xmm: &call_xmm,
precise: precise_ctx,
off_im1_reg,
off_scaled_reg,
off_cache: None,
hoists: &hoists,
active_hoists: Vec::new(),
};
let mut is_jump_target = vec![false; ops.len()];
for op in ops {
if let Some(t) = op_target(op) {
if let Some(slot) = is_jump_target.get_mut(t) {
*slot = true;
}
}
}
let mut const_hoist_by_op: Vec<Option<usize>> = vec![None; ops.len()];
let mut const_hoist_at_head: Vec<Vec<usize>> = vec![Vec::new(); ops.len()];
if const_hoist_enabled() {
for h in loop_invariant_const_hoists(ops) {
if matches!(loc[h.dst as usize], Loc::Reg(_)) {
const_hoist_by_op[h.op] = Some(h.head);
const_hoist_at_head[h.head].push(h.op);
}
}
}
for (idx, op) in ops.iter().enumerate() {
for k in 0..g.hoists.len() {
let e = g.hoists[k];
if e.plan.head == idx {
g.load(e.ptr_reg, e.plan.ptr_slot);
if let Some(lr) = e.len_reg {
g.load(lr, e.plan.len_slot);
}
}
}
for ci in 0..const_hoist_at_head[idx].len() {
let op_i = const_hoist_at_head[idx][ci];
if let MicroOp::LoadConst { dst, value } = ops[op_i] {
g.asm.mov_ri(S0, value);
if let Loc::Xmm(_) = g.loc[dst as usize] {
g.asm.movq_xr(FS0, S0);
g.fstore(dst, FS0);
} else {
g.store(dst, S0);
}
}
}
g.asm.bind(op_labels[idx]);
g.active_hoists.clear();
for k in 0..g.hoists.len() {
let p = g.hoists[k].plan;
if (p.head..=p.back).contains(&idx) {
g.active_hoists.push(k);
}
}
if is_jump_target[idx] {
g.invalidate_off_cache();
}
let hoisted_const = const_hoist_by_op[idx].is_some();
if !hoisted_const {
match op {
MicroOp::CallSelf { dst, args_start, limit_slot, frame_size, .. } => {
emit_self_call(
&mut g, idx, *dst, *args_start, None, *limit_slot, *frame_size, status_addr,
depth_addr, entry_addr,
);
}
MicroOp::CallSelfCopy {
dst, args_start, src_start, arg_count, limit_slot, frame_size, ..
} => {
emit_self_call(
&mut g, idx, *dst, *args_start, Some((*src_start, *arg_count)), *limit_slot,
*frame_size, status_addr, depth_addr, entry_addr,
);
}
MicroOp::Call { dst, args_start, limit_slot, .. } => {
let code = g
.precise
.as_ref()
.map(|p| p.codes[idx])
.expect("a precise Call requires precise codes");
emit_precise_call(
&mut g, idx, *dst, *args_start, *limit_slot, *args_start as i64, status_addr,
depth_addr, entry_addr, code,
);
}
_ => emit_op(&mut g, op, idx, &written, &used_callee)?,
}
}
if is_sysv_call(op) {
g.invalidate_off_cache();
} else {
g.invalidate_off_cache_if_writes_idx(op);
}
if linscan_active {
for s in 0..=max_slot {
if ls_shared[s] && matches!(ls_range[s], Some((_, e)) if e == idx) {
match g.loc[s] {
Loc::Reg(r) => g.asm.mov_mr(BASE, (s as i32) * 8, r),
Loc::Xmm(x) => g.asm.movsd_mr(BASE, (s as i32) * 8, x),
Loc::Frame => {}
}
g.loc[s] = Loc::Frame;
}
}
}
}
if let Some(dl) = deopt_label {
g.asm.bind(dl);
g.asm.mov_ri(S0, status_addr);
g.asm.mov_ri(S1, 1);
g.asm.mov_mr(S0, 0, S1);
emit_flush_and_return(&mut g, &written, &used_callee, None);
}
if let Some(pl) = propagate_label {
g.asm.bind(pl);
emit_flush_and_return(&mut g, &written, &used_callee, None);
}
if let Some(ep) = precise_epilogue {
let mut codes_sorted: Vec<(i64, LabelId)> =
code_blocks.iter().map(|(&c, &l)| (c, l)).collect();
codes_sorted.sort_by_key(|(c, _)| *c);
for (c, lbl) in codes_sorted {
g.asm.bind(lbl);
g.asm.mov_ri(S1, c);
g.asm.jmp(ep);
}
g.asm.bind(ep);
g.asm.mov_ri(S0, depth_addr);
g.asm.mov_rm(S0, S0, 0); g.asm.shl_ri(S0, 32);
g.asm.or_rr(S0, S1); g.asm.mov_ri(SC, status_addr);
g.asm.mov_mr(SC, 0, S0); emit_flush_and_return(&mut g, &written, &used_callee, None);
}
let code = g.asm.resolve();
let chain = JitChain::from_code(&code, ops.len()).ok()?;
if let Some(cell) = entry_cell {
cell.store(chain.base() as i64, std::sync::atomic::Ordering::SeqCst);
return Some(CompiledChain::from_chain_keepalive(chain, status, vec![cell]));
}
Some(CompiledChain::from_chain(chain, status))
}
fn dest_of(op: &MicroOp) -> Option<Slot> {
match *op {
MicroOp::Move { dst, .. }
| MicroOp::LoadConst { dst, .. }
| MicroOp::Add { dst, .. }
| MicroOp::Sub { dst, .. }
| MicroOp::Mul { dst, .. }
| MicroOp::Lt { dst, .. }
| MicroOp::Gt { dst, .. }
| MicroOp::LtEq { dst, .. }
| MicroOp::GtEq { dst, .. }
| MicroOp::Eq { dst, .. }
| MicroOp::Neq { dst, .. }
| MicroOp::BitAnd { dst, .. }
| MicroOp::BitOr { dst, .. }
| MicroOp::BitXor { dst, .. }
| MicroOp::Shl { dst, .. }
| MicroOp::Shr { dst, .. }
| MicroOp::NotInt { dst, .. }
| MicroOp::NotBool { dst, .. }
| MicroOp::Div { dst, .. }
| MicroOp::Mod { dst, .. }
| MicroOp::ArrLoad { dst, .. }
| MicroOp::DivPow2 { dst, .. }
| MicroOp::MagicDivU { dst, .. }
| MicroOp::AddF { dst, .. }
| MicroOp::SubF { dst, .. }
| MicroOp::MulF { dst, .. }
| MicroOp::DivF { dst, .. }
| MicroOp::SqrtF { dst, .. }
| MicroOp::IntToFloat { dst, .. }
| MicroOp::FmaF { dst, .. }
| MicroOp::LtF { dst, .. }
| MicroOp::GtF { dst, .. }
| MicroOp::LtEqF { dst, .. }
| MicroOp::GtEqF { dst, .. }
| MicroOp::EqF { dst, .. }
| MicroOp::NeqF { dst, .. }
| MicroOp::CallSelf { dst, .. }
| MicroOp::CallSelfCopy { dst, .. }
| MicroOp::Call { dst, .. } => Some(dst),
_ => None,
}
}
fn emit_flush_and_return(
g: &mut Gen,
written: &[bool],
used_callee: &[Reg],
ret_slot: Option<Slot>,
) {
for s in 0..g.loc.len() {
if written[s] {
match g.loc[s] {
Loc::Reg(r) => g.asm.mov_mr(BASE, (s as i32) * 8, r),
Loc::Xmm(x) => g.asm.movsd_mr(BASE, (s as i32) * 8, x),
Loc::Frame => {}
}
}
}
if let Some(rs) = ret_slot {
match g.loc[rs as usize] {
Loc::Reg(r) => g.asm.mov_rr(S0, r),
Loc::Xmm(x) => g.asm.movq_rx(S0, x),
Loc::Frame => g.asm.mov_rm(S0, BASE, (rs as i32) * 8),
}
}
if g.stack_pad != 0 {
g.asm.add_ri(Reg::Rsp, g.stack_pad);
}
for &r in used_callee.iter().rev() {
g.asm.pop(r);
}
g.asm.pop(BASE);
g.asm.ret();
}
#[allow(clippy::too_many_arguments)]
fn emit_self_call(
g: &mut Gen,
idx: usize,
dst: Slot,
args_start: Slot,
copy: Option<(Slot, u16)>,
limit_slot: Slot,
frame_size: i64,
status_addr: i64,
depth_addr: i64,
entry_addr: i64,
) {
let propagate = g.self_call.as_ref().expect("self-call without SelfCall context").propagate_label;
let entry_zero = g.asm.new_label();
let depth_bad = g.asm.new_label();
let arena_bad = g.asm.new_label();
g.asm.mov_ri(S0, entry_addr);
g.asm.mov_rm(S0, S0, 0);
g.asm.test_rr(S0, S0);
g.asm.jcc(Cond::Eq, entry_zero);
g.asm.mov_ri(S1, depth_addr);
g.asm.mov_rm(S1, S1, 0);
g.asm.cmp_ri(S1, SELF_CALL_DEPTH_LIMIT as i32);
g.asm.jcc(Cond::Ge, depth_bad);
g.asm.mov_rr(SC, BASE);
g.asm.add_ri(SC, (args_start as i32) * 8);
g.asm.mov_rm(S0, BASE, (limit_slot as i32) * 8);
g.asm.mov_rr(S1, SC);
g.asm.add_ri(S1, (frame_size as i32) * 8);
g.asm.cmp_rr(S1, S0); g.asm.jcc(Cond::Gt, arena_bad);
if let Some((src_start, arg_count)) = copy {
for j in 0..arg_count {
g.load(S1, src_start + j); g.asm.mov_mr(SC, (j as i32) * 8, S1); }
}
g.asm.mov_mr(SC, ((frame_size - 1) as i32) * 8, S0);
g.asm.mov_ri(SC, depth_addr);
g.asm.mov_rm(S0, SC, 0);
g.asm.add_ri(S0, 1);
g.asm.mov_mr(SC, 0, S0);
g.spill_volatiles_at(idx);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.add_ri(Reg::Rdi, (args_start as i32) * 8);
g.asm.mov_ri(Reg::Rsi, 0);
g.asm.mov_ri(Reg::Rdx, 0);
g.asm.mov_ri(Reg::Rcx, 0);
g.asm.mov_ri(Reg::R8, 0);
g.asm.mov_ri(Reg::R9, 0);
g.asm.mov_ri(S0, entry_addr);
g.asm.mov_rm(S0, S0, 0);
g.asm.call_r(S0);
g.asm.mov_ri(SC, depth_addr);
g.asm.mov_rm(S1, SC, 0);
g.asm.sub_ri(S1, 1);
g.asm.mov_mr(SC, 0, S1);
g.asm.mov_ri(S1, status_addr);
g.asm.mov_rm(S1, S1, 0);
g.asm.test_rr(S1, S1);
g.asm.jcc(Cond::Ne, propagate);
g.reload_volatiles_at(idx);
g.store(dst, S0);
let after = g.asm.new_label();
g.asm.jmp(after);
g.asm.bind(entry_zero);
emit_marker_then_propagate(g, status_addr, 1, propagate);
g.asm.bind(depth_bad);
emit_marker_then_propagate(g, status_addr, 5, propagate);
g.asm.bind(arena_bad);
emit_marker_then_propagate(g, status_addr, 9, propagate);
g.asm.bind(after);
}
fn emit_marker_then_propagate(g: &mut Gen, status_addr: i64, marker: i64, propagate: LabelId) {
g.asm.mov_ri(S0, status_addr);
g.asm.mov_ri(S1, marker);
g.asm.mov_mr(S0, 0, S1);
g.asm.jmp(propagate);
}
#[allow(clippy::too_many_arguments)]
fn emit_precise_call(
g: &mut Gen,
idx: usize,
dst: Slot,
args_start: Slot,
limit_slot: Slot,
frame_size: i64,
status_addr: i64,
depth_addr: i64,
entry_addr: i64,
code: i64,
) {
let propagate = g
.self_call
.as_ref()
.expect("precise call without a propagate epilogue")
.propagate_label;
let fail = g
.precise
.as_ref()
.map(|p| p.code_blocks[&code])
.expect("precise call without a code block");
let bound_slots = frame_size;
g.asm.mov_ri(S0, entry_addr);
g.asm.mov_rm(S0, S0, 0);
g.asm.test_rr(S0, S0);
g.asm.jcc(Cond::Eq, fail);
g.asm.mov_ri(S1, depth_addr);
g.asm.mov_rm(S1, S1, 0);
g.asm.cmp_ri(S1, SELF_CALL_DEPTH_LIMIT as i32);
g.asm.jcc(Cond::Ge, fail);
g.asm.mov_rr(SC, BASE);
g.asm.add_ri(SC, (args_start as i32) * 8);
g.asm.mov_rm(S0, BASE, (limit_slot as i32) * 8);
g.asm.mov_rr(S1, SC);
g.asm.add_ri(S1, (bound_slots as i32) * 8);
g.asm.cmp_rr(S1, S0);
g.asm.jcc(Cond::Gt, fail);
g.asm.mov_mr(SC, ((frame_size - 1) as i32) * 8, S0);
g.asm.mov_ri(SC, depth_addr);
g.asm.mov_rm(S0, SC, 0);
g.asm.add_ri(S0, 1);
g.asm.mov_mr(SC, 0, S0);
g.spill_volatiles_at(idx);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.add_ri(Reg::Rdi, (args_start as i32) * 8);
g.asm.mov_ri(Reg::Rsi, 0);
g.asm.mov_ri(Reg::Rdx, 0);
g.asm.mov_ri(Reg::Rcx, 0);
g.asm.mov_ri(Reg::R8, 0);
g.asm.mov_ri(Reg::R9, 0);
g.asm.mov_ri(S0, entry_addr);
g.asm.mov_rm(S0, S0, 0);
g.asm.call_r(S0);
g.asm.mov_ri(SC, depth_addr);
g.asm.mov_rm(S1, SC, 0);
g.asm.sub_ri(S1, 1);
g.asm.mov_mr(SC, 0, S1);
g.asm.mov_ri(S1, status_addr);
g.asm.mov_rm(S1, S1, 0);
g.asm.test_rr(S1, S1);
g.asm.jcc(Cond::Ne, propagate);
g.reload_volatiles_at(idx);
g.store(dst, S0);
}
fn emit_op(
g: &mut Gen,
op: &MicroOp,
idx: usize,
written: &[bool],
used_callee: &[Reg],
) -> Option<()> {
match *op {
MicroOp::LoadConst { dst, value } => {
g.asm.mov_ri(S0, value);
if let Loc::Xmm(_) = g.loc[dst as usize] {
g.asm.movq_xr(FS0, S0);
g.fstore(dst, FS0);
} else {
g.store(dst, S0);
}
}
MicroOp::Move { dst, src } => {
if matches!(g.loc[dst as usize], Loc::Xmm(_))
|| matches!(g.loc[src as usize], Loc::Xmm(_))
{
let x = g.foperand(src, FS0);
g.fstore(dst, x);
} else {
let r = g.operand(src, S0);
g.store(dst, r);
}
}
MicroOp::Add { dst, lhs, rhs } => emit_commutative(g, dst, lhs, rhs, AsmBinop::Add, idx),
MicroOp::Sub { dst, lhs, rhs } => emit_sub(g, dst, lhs, rhs, idx),
MicroOp::Mul { dst, lhs, rhs } => emit_commutative(g, dst, lhs, rhs, AsmBinop::Mul, idx),
MicroOp::BitAnd { dst, lhs, rhs } => emit_commutative(g, dst, lhs, rhs, AsmBinop::And, idx),
MicroOp::BitOr { dst, lhs, rhs } => emit_commutative(g, dst, lhs, rhs, AsmBinop::Or, idx),
MicroOp::BitXor { dst, lhs, rhs } => emit_commutative(g, dst, lhs, rhs, AsmBinop::Xor, idx),
MicroOp::Lt { dst, lhs, rhs } => emit_compare(g, dst, lhs, rhs, Cond::Lt),
MicroOp::Gt { dst, lhs, rhs } => emit_compare(g, dst, lhs, rhs, Cond::Gt),
MicroOp::LtEq { dst, lhs, rhs } => emit_compare(g, dst, lhs, rhs, Cond::Le),
MicroOp::GtEq { dst, lhs, rhs } => emit_compare(g, dst, lhs, rhs, Cond::Ge),
MicroOp::Eq { dst, lhs, rhs } => emit_compare(g, dst, lhs, rhs, Cond::Eq),
MicroOp::Neq { dst, lhs, rhs } => emit_compare(g, dst, lhs, rhs, Cond::Ne),
MicroOp::NotInt { dst, src } => {
g.load(S0, src);
g.asm.not_r(S0);
g.store(dst, S0);
}
MicroOp::NotBool { dst, src } => {
g.load(S0, src);
g.asm.mov_ri(S1, 1);
g.asm.xor_rr(S0, S1);
g.store(dst, S0);
}
MicroOp::Shl { dst, lhs, rhs } => emit_shift(g, dst, lhs, rhs, ShiftKind::Left),
MicroOp::Shr { dst, lhs, rhs } => emit_shift(g, dst, lhs, rhs, ShiftKind::Right),
MicroOp::DivPow2 { dst, lhs, k } => emit_div_pow2(g, dst, lhs, k),
MicroOp::MagicDivU { dst, lhs, magic, more, mul_back } => {
emit_magic_div(g, dst, lhs, magic, more, mul_back)
}
MicroOp::Div { dst, lhs, rhs } => {
let dl = g.checked_exit(idx).expect("Div without a deopt label");
emit_div_mod(g, dst, lhs, rhs, true, dl);
}
MicroOp::Mod { dst, lhs, rhs } => {
let dl = g.checked_exit(idx).expect("Mod without a deopt label");
emit_div_mod(g, dst, lhs, rhs, false, dl);
}
MicroOp::Branch { cmp, lhs, rhs, target } => {
let a = g.operand(lhs, S0);
let b = g.operand(rhs, S1);
g.asm.cmp_rr(a, b);
let neg = cond_of(cmp.negated());
g.asm.jcc(neg, g.op_labels[target]);
}
MicroOp::Jump { target } => {
if target != idx + 1 {
g.asm.jmp(g.op_labels[target]);
}
}
MicroOp::JumpIfFalse { cond, target } => {
let r = g.operand(cond, S0);
g.asm.test_rr(r, r);
g.asm.jcc(Cond::Eq, g.op_labels[target]); }
MicroOp::JumpIfTrue { cond, target } => {
let r = g.operand(cond, S0);
g.asm.test_rr(r, r);
g.asm.jcc(Cond::Ne, g.op_labels[target]); }
MicroOp::ArrLoad { dst, idx: idx_slot, ptr_slot, len_slot, byte: false, narrow32: true, checked } => {
let dl = g.checked_exit(idx);
emit_arr_load_i32(g, dst, idx_slot, ptr_slot, len_slot, checked, dl);
}
MicroOp::ArrStore { src, idx: idx_slot, ptr_slot, len_slot, byte: false, narrow32: true, checked } => {
let dl = g.checked_exit(idx);
emit_arr_store_i32(g, src, idx_slot, ptr_slot, len_slot, checked, dl);
}
MicroOp::ArrLoad { dst, idx: idx_slot, ptr_slot, len_slot, byte: false, narrow32: false, checked } => {
let dl = g.checked_exit(idx);
emit_arr_load(g, dst, idx_slot, ptr_slot, len_slot, checked, dl);
}
MicroOp::ArrStore { src, idx: idx_slot, ptr_slot, len_slot, byte: false, narrow32: false, checked } => {
let dl = g.checked_exit(idx);
emit_arr_store(g, src, idx_slot, ptr_slot, len_slot, checked, dl);
}
MicroOp::ArrLoad { dst, idx: idx_slot, ptr_slot, len_slot, byte: true, narrow32: _, checked } => {
let dl = g.checked_exit(idx);
emit_arr_load_byte(g, dst, idx_slot, ptr_slot, len_slot, checked, dl);
}
MicroOp::ArrStore { src, idx: idx_slot, ptr_slot, len_slot, byte: true, narrow32: _, checked } => {
let dl = g.checked_exit(idx);
emit_arr_store_byte(g, src, idx_slot, ptr_slot, len_slot, checked, dl);
}
MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, helper_addr, byte, narrow32 } => {
emit_list_push(g, idx, src, vec_slot, ptr_slot, len_slot, helper_addr, byte, narrow32);
}
MicroOp::ListClear { vec_slot, ptr_slot, len_slot, helper_addr } => {
emit_list_clear(g, idx, vec_slot, ptr_slot, len_slot, helper_addr);
}
MicroOp::StrAppend { text_handle_slot, src, helper_addr } => {
emit_str_append(g, idx, text_handle_slot, src, helper_addr);
}
MicroOp::NewList { vec_slot, ptr_slot, len_slot, helper_addr } => {
emit_new_list(g, idx, vec_slot, ptr_slot, len_slot, helper_addr);
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, helper_addr } => {
emit_list_triple(g, idx, handle_slot, vec_slot, ptr_slot, len_slot, helper_addr);
}
MicroOp::AddF { dst, lhs, rhs } => emit_fbinop(g, dst, lhs, rhs, FBinop::Add),
MicroOp::SubF { dst, lhs, rhs } => emit_fbinop(g, dst, lhs, rhs, FBinop::Sub),
MicroOp::MulF { dst, lhs, rhs } => emit_fbinop(g, dst, lhs, rhs, FBinop::Mul),
MicroOp::DivF { dst, lhs, rhs } => {
let dl = g.checked_exit(idx).expect("DivF without a deopt label");
emit_fdiv(g, dst, lhs, rhs, dl);
}
MicroOp::SqrtF { dst, src } => {
let s = g.foperand(src, FS1);
g.asm.sqrtsd_rr(FS0, s);
g.fstore(dst, FS0);
}
MicroOp::IntToFloat { dst, src } => {
let r = g.operand(src, S0);
g.asm.cvtsi2sd(FS0, r);
g.fstore(dst, FS0);
}
MicroOp::FmaF { dst, a, b, c } => {
g.fload(FS0, a); let bv = g.foperand(b, FS1);
g.asm.mulsd_rr(FS0, bv); let cv = g.foperand(c, FS1);
g.asm.addsd_rr(FS0, cv); g.fstore(dst, FS0);
}
MicroOp::LtF { dst, lhs, rhs } => emit_fcompare(g, dst, lhs, rhs, FCmp::Lt),
MicroOp::GtF { dst, lhs, rhs } => emit_fcompare(g, dst, lhs, rhs, FCmp::Gt),
MicroOp::LtEqF { dst, lhs, rhs } => emit_fcompare(g, dst, lhs, rhs, FCmp::Le),
MicroOp::GtEqF { dst, lhs, rhs } => emit_fcompare(g, dst, lhs, rhs, FCmp::Ge),
MicroOp::EqF { dst, lhs, rhs } => emit_feq(g, dst, lhs, rhs, false),
MicroOp::NeqF { dst, lhs, rhs } => emit_feq(g, dst, lhs, rhs, true),
MicroOp::BranchF { cmp, lhs, rhs, target } => {
emit_branchf(g, cmp, lhs, rhs, g.op_labels[target]);
}
MicroOp::Return { src } => {
emit_flush_and_return(g, written, used_callee, Some(src));
}
_ => return None,
}
Some(())
}
fn emit_arr_addr(g: &mut Gen, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, byte: bool, dl: Option<LabelId>) {
if let (Some(im1_reg), Some(state)) = (g.off_im1_reg, g.off_cache) {
if state.idx == idx && state.byte == byte {
if checked {
let dl = dl.expect("checked array op without a deopt label");
let len = g.hoisted_len(ptr_slot).unwrap_or_else(|| g.operand(len_slot, S1));
g.asm.cmp_rr(im1_reg, len); g.asm.jcc(Cond::AeU, dl); }
if byte {
g.asm.mov_rr(S0, im1_reg);
} else if let Some(scaled_reg) = g.off_scaled_reg {
g.asm.mov_rr(S0, scaled_reg); } else {
g.asm.mov_rr(S0, im1_reg);
g.asm.shl_ri(S0, 3); }
let ptr = g.hoisted_ptr(ptr_slot).unwrap_or_else(|| g.operand(ptr_slot, S1));
g.asm.add_rr(S0, ptr); return;
}
}
g.load(S0, idx);
g.asm.sub_ri(S0, 1);
if let Some(im1_reg) = g.off_im1_reg {
g.asm.mov_rr(im1_reg, S0);
g.off_cache = Some(OffCacheState { idx, byte });
}
if checked {
let dl = dl.expect("checked array op without a deopt label");
let len = g.hoisted_len(ptr_slot).unwrap_or_else(|| g.operand(len_slot, S1));
g.asm.cmp_rr(S0, len); g.asm.jcc(Cond::AeU, dl); }
if !byte {
g.asm.shl_ri(S0, 3); if g.off_im1_reg.is_some() {
if let Some(scaled_reg) = g.off_scaled_reg {
g.asm.mov_rr(scaled_reg, S0);
}
}
}
let ptr = g.hoisted_ptr(ptr_slot).unwrap_or_else(|| g.operand(ptr_slot, S1));
g.asm.add_rr(S0, ptr); }
fn emit_arr_load(g: &mut Gen, dst: Slot, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
emit_arr_addr(g, idx, ptr_slot, len_slot, checked, false, dl);
g.asm.mov_rm(S1, S0, 0); g.store(dst, S1);
}
fn emit_arr_load_byte(g: &mut Gen, dst: Slot, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
emit_arr_addr(g, idx, ptr_slot, len_slot, checked, true, dl);
g.asm.movzx_rm8(S1, S0, 0); g.store(dst, S1);
}
fn emit_arr_store(g: &mut Gen, src: Slot, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
emit_arr_addr(g, idx, ptr_slot, len_slot, checked, false, dl);
let v = match g.loc[src as usize] {
Loc::Xmm(x) => {
g.asm.movq_rx(S1, x);
S1
}
_ => g.operand(src, SC),
};
g.asm.mov_mr(S0, 0, v); }
fn emit_arr_store_byte(g: &mut Gen, src: Slot, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
emit_arr_addr(g, idx, ptr_slot, len_slot, checked, true, dl);
let v = match g.loc[src as usize] {
Loc::Xmm(x) => {
g.asm.movq_rx(SC, x);
SC
}
_ => g.operand(src, SC),
};
g.asm.test_rr(v, v);
g.asm.setcc8(Cond::Ne, S1);
g.asm.mov_mr8(S0, 0, S1); }
fn emit_arr_addr_i32(g: &mut Gen, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
g.load(S0, idx);
g.asm.sub_ri(S0, 1); if checked {
let dl = dl.expect("checked array op without a deopt label");
let len = g.hoisted_len(ptr_slot).unwrap_or_else(|| g.operand(len_slot, S1));
g.asm.cmp_rr(S0, len); g.asm.jcc(Cond::AeU, dl); }
g.asm.shl_ri(S0, 2); let ptr = g.hoisted_ptr(ptr_slot).unwrap_or_else(|| g.operand(ptr_slot, S1));
g.asm.add_rr(S0, ptr); }
fn emit_arr_load_i32(g: &mut Gen, dst: Slot, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
g.invalidate_off_cache();
emit_arr_addr_i32(g, idx, ptr_slot, len_slot, checked, dl);
g.asm.movsxd_rm(S1, S0, 0); g.store(dst, S1);
}
fn emit_arr_store_i32(g: &mut Gen, src: Slot, idx: Slot, ptr_slot: Slot, len_slot: Slot, checked: bool, dl: Option<LabelId>) {
g.invalidate_off_cache();
emit_arr_addr_i32(g, idx, ptr_slot, len_slot, checked, dl);
let v = match g.loc[src as usize] {
Loc::Xmm(x) => {
g.asm.movq_rx(S1, x);
S1
}
_ => g.operand(src, SC),
};
g.asm.mov_mr32(S0, 0, v); }
fn emit_list_push(
g: &mut Gen,
idx: usize,
src: Slot,
vec_slot: Slot,
ptr_slot: Slot,
len_slot: Slot,
helper_addr: i64,
byte: bool,
narrow32: bool,
) {
if !inline_push_enabled() {
g.spill_volatiles_at(idx);
g.load(Reg::R8, src);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.mov_ri(Reg::Rsi, vec_slot as i64);
g.asm.mov_ri(Reg::Rdx, ptr_slot as i64);
g.asm.mov_ri(Reg::Rcx, len_slot as i64);
g.asm.mov_ri(S0, helper_addr);
g.asm.call_r(S0);
g.reload_volatiles_at(idx);
return;
}
let layout = vec_layout();
let cold = g.asm.new_label();
let join = g.asm.new_label();
g.asm.mov_rm(S0, BASE, (vec_slot as i32) * 8);
g.asm.mov_rm(S1, BASE, (len_slot as i32) * 8);
g.asm.mov_rm(SC, S0, layout.cap_off); g.asm.cmp_rr(S1, SC); g.asm.jcc(Cond::AeU, cold);
match g.loc[src as usize] {
Loc::Xmm(x) => g.asm.movq_rx(SC, x),
_ => {
let v = g.operand(src, SC);
if v != SC {
g.asm.mov_rr(SC, v);
}
}
}
g.asm.mov_rm(S0, BASE, (ptr_slot as i32) * 8); if narrow32 {
g.asm.shl_ri(S1, 2); } else if !byte {
g.asm.shl_ri(S1, 3); }
g.asm.add_rr(S0, S1); if byte {
g.asm.test_rr(SC, SC);
g.asm.setcc8(Cond::Ne, S1);
g.asm.mov_mr8(S0, 0, S1); } else if narrow32 {
g.asm.mov_mr32(S0, 0, SC); } else {
g.asm.mov_mr(S0, 0, SC); }
g.asm.mov_rm(S1, BASE, (len_slot as i32) * 8); g.asm.add_ri(S1, 1); g.asm.mov_mr(BASE, (len_slot as i32) * 8, S1); g.asm.mov_rm(S0, BASE, (vec_slot as i32) * 8); g.asm.mov_mr(S0, layout.len_off, S1); g.asm.jmp(join);
g.asm.bind(cold);
g.spill_volatiles_at(idx);
g.load(Reg::R8, src);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.mov_ri(Reg::Rsi, vec_slot as i64);
g.asm.mov_ri(Reg::Rdx, ptr_slot as i64);
g.asm.mov_ri(Reg::Rcx, len_slot as i64);
g.asm.mov_ri(S0, helper_addr);
g.asm.call_r(S0);
g.reload_volatiles_at(idx);
g.asm.bind(join);
}
fn emit_list_clear(g: &mut Gen, idx: usize, vec_slot: Slot, ptr_slot: Slot, len_slot: Slot, helper_addr: i64) {
g.spill_volatiles_at(idx);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.mov_ri(Reg::Rsi, vec_slot as i64);
g.asm.mov_ri(Reg::Rdx, ptr_slot as i64);
g.asm.mov_ri(Reg::Rcx, len_slot as i64);
g.asm.mov_ri(S0, helper_addr);
g.asm.call_r(S0);
g.reload_volatiles_at(idx);
}
fn emit_new_list(g: &mut Gen, idx: usize, vec_slot: Slot, ptr_slot: Slot, len_slot: Slot, helper_addr: i64) {
g.spill_volatiles_at(idx);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.mov_ri(Reg::Rsi, vec_slot as i64);
g.asm.mov_ri(Reg::Rdx, ptr_slot as i64);
g.asm.mov_ri(Reg::Rcx, len_slot as i64);
g.asm.mov_ri(S0, helper_addr);
g.asm.call_r(S0);
g.reload_volatiles_at(idx);
}
fn emit_list_triple(
g: &mut Gen,
idx: usize,
handle_slot: Slot,
vec_slot: Slot,
ptr_slot: Slot,
len_slot: Slot,
helper_addr: i64,
) {
g.spill_volatiles_at(idx);
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.mov_ri(Reg::Rsi, handle_slot as i64);
g.asm.mov_ri(Reg::Rdx, vec_slot as i64);
g.asm.mov_ri(Reg::Rcx, ptr_slot as i64);
g.asm.mov_ri(Reg::R8, len_slot as i64);
g.asm.mov_ri(S0, helper_addr);
g.asm.call_r(S0);
g.reload_volatiles_at(idx);
}
fn emit_str_append(
g: &mut Gen,
idx: usize,
handle_slot: Slot,
src: crate::jit::StrSrc,
helper_addr: i64,
) {
g.spill_volatiles_at(idx);
match src {
crate::jit::StrSrc::Byte(s) => {
g.load(Reg::Rdx, s);
g.asm.mov_ri(Reg::Rcx, -1);
}
crate::jit::StrSrc::Const { ptr, len } => {
g.asm.mov_ri(Reg::Rdx, ptr);
g.asm.mov_ri(Reg::Rcx, len);
}
}
g.asm.mov_rr(Reg::Rdi, BASE);
g.asm.mov_ri(Reg::Rsi, handle_slot as i64);
g.asm.mov_ri(S0, helper_addr);
g.asm.call_r(S0);
g.reload_volatiles_at(idx);
}
#[derive(Clone, Copy)]
enum AsmBinop {
Add,
Mul,
And,
Or,
Xor,
}
fn emit_commutative(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, op: AsmBinop, idx: usize) {
g.load(S0, lhs);
let b = g.operand(rhs, S1);
match op {
AsmBinop::Add => g.asm.add_rr(S0, b),
AsmBinop::Mul => g.asm.imul_rr(S0, b),
AsmBinop::And => g.asm.and_rr(S0, b),
AsmBinop::Or => g.asm.or_rr(S0, b),
AsmBinop::Xor => g.asm.xor_rr(S0, b),
}
if matches!(op, AsmBinop::Add | AsmBinop::Mul) {
if let Some(dl) = g.checked_exit(idx) {
g.asm.jcc(Cond::Overflow, dl);
}
}
g.store(dst, S0);
}
fn emit_sub(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, idx: usize) {
g.load(S0, lhs);
let b = g.operand(rhs, S1);
g.asm.sub_rr(S0, b);
if let Some(dl) = g.checked_exit(idx) {
g.asm.jcc(Cond::Overflow, dl);
}
g.store(dst, S0);
}
fn emit_compare(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, cond: Cond) {
let a = g.operand(lhs, S0);
let b = g.operand(rhs, S1);
g.asm.cmp_rr(a, b);
g.asm.setcc_movzx(cond, S0);
g.store(dst, S0);
}
#[derive(Clone, Copy)]
enum FBinop {
Add,
Sub,
Mul,
}
fn emit_fbinop(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, op: FBinop) {
let apply = |asm: &mut Asm, target: Xmm, second: Xmm| match op {
FBinop::Add => asm.addsd_rr(target, second),
FBinop::Sub => asm.subsd_rr(target, second),
FBinop::Mul => asm.mulsd_rr(target, second),
};
if let Loc::Xmm(d) = g.loc[dst as usize] {
let b = g.foperand(rhs, FS1);
if g.loc[lhs as usize] == Loc::Xmm(d) {
apply(&mut g.asm, d, b);
return;
}
if d != b {
g.fload(d, lhs);
apply(&mut g.asm, d, b);
return;
}
g.fload(FS0, lhs);
apply(&mut g.asm, FS0, b);
g.fstore(dst, FS0);
return;
}
g.fload(FS0, lhs);
let b = g.foperand(rhs, FS1);
apply(&mut g.asm, FS0, b);
g.fstore(dst, FS0);
}
fn emit_fdiv(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, dl: LabelId) {
let b = g.foperand(rhs, FS1);
g.asm.mov_ri(S0, 0);
g.asm.movq_xr(FS0, S0);
g.asm.ucomisd_rr(b, FS0); let not_zero = g.asm.new_label();
g.asm.jcc(Cond::ParityEven, not_zero); g.asm.jcc(Cond::Eq, dl); g.asm.bind(not_zero);
g.fload(FS0, lhs);
let b2 = g.foperand(rhs, FS1);
g.asm.divsd_rr(FS0, b2);
g.fstore(dst, FS0);
}
#[derive(Clone, Copy)]
enum FCmp {
Lt,
Gt,
Le,
Ge,
}
fn emit_fcompare(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, cmp: FCmp) {
let (a, b, cond) = match cmp {
FCmp::Gt => (lhs, rhs, Cond::AU), FCmp::Ge => (lhs, rhs, Cond::AeU), FCmp::Lt => (rhs, lhs, Cond::AU), FCmp::Le => (rhs, lhs, Cond::AeU), };
let xa = g.foperand(a, FS0);
let xb = g.foperand(b, FS1);
g.asm.ucomisd_rr(xa, xb);
g.asm.setcc_movzx(cond, S0);
g.store(dst, S0);
}
fn emit_feq(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, neg: bool) {
let xa = g.foperand(lhs, FS0);
let xb = g.foperand(rhs, FS1);
g.asm.ucomisd_rr(xa, xb);
if neg {
g.asm.setcc_movzx(Cond::Ne, S0);
g.asm.setcc_movzx(Cond::ParityEven, S1);
g.asm.or_rr(S0, S1);
} else {
g.asm.setcc_movzx(Cond::Eq, S0);
g.asm.setcc_movzx(Cond::ParityOdd, S1);
g.asm.and_rr(S0, S1);
}
g.store(dst, S0);
}
fn emit_branchf(g: &mut Gen, cmp: Cmp, lhs: Slot, rhs: Slot, target: LabelId) {
match cmp {
Cmp::Lt | Cmp::Gt | Cmp::LtEq | Cmp::GtEq => {
let (a, b, false_cond) = match cmp {
Cmp::Gt => (lhs, rhs, Cond::BeU), Cmp::GtEq => (lhs, rhs, Cond::BU), Cmp::Lt => (rhs, lhs, Cond::BeU), Cmp::LtEq => (rhs, lhs, Cond::BU), _ => unreachable!(),
};
let xa = g.foperand(a, FS0);
let xb = g.foperand(b, FS1);
g.asm.ucomisd_rr(xa, xb);
g.asm.jcc(false_cond, target);
}
Cmp::Eq | Cmp::NotEq => {
let xa = g.foperand(lhs, FS0);
let xb = g.foperand(rhs, FS1);
g.asm.ucomisd_rr(xa, xb);
if matches!(cmp, Cmp::Eq) {
g.asm.jcc(Cond::Ne, target);
g.asm.jcc(Cond::ParityEven, target);
return;
}
g.asm.setcc_movzx(Cond::Ne, S0);
g.asm.setcc_movzx(Cond::ParityEven, S1);
g.asm.or_rr(S0, S1);
g.asm.test_rr(S0, S0);
g.asm.jcc(Cond::Eq, target); }
}
}
#[derive(Clone, Copy)]
enum ShiftKind {
Left,
Right,
}
fn emit_shift(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, kind: ShiftKind) {
g.load(S0, lhs);
g.load(SC, rhs);
match kind {
ShiftKind::Left => g.asm.shl_cl(S0),
ShiftKind::Right => g.asm.sar_cl(S0),
}
g.store(dst, S0);
}
fn emit_div_pow2(g: &mut Gen, dst: Slot, lhs: Slot, k: u32) {
g.load(S0, lhs); g.asm.mov_rr(S1, S0);
g.asm.mov_ri(SC, 63);
g.asm.sar_cl(S1);
let mask = (1i64 << k) - 1;
g.asm.mov_ri(SC, mask);
g.asm.and_rr(S1, SC);
g.asm.add_rr(S0, S1);
g.asm.mov_ri(SC, k as i64);
g.asm.sar_cl(S0);
g.store(dst, S0);
}
fn emit_magic_div(g: &mut Gen, dst: Slot, lhs: Slot, magic: u64, more: u8, mul_back: i64) {
const SHIFT_MASK: u8 = 0x3F;
const ADD_MARKER: u8 = 0x40;
const SHIFT_PATH: u8 = 0x80;
let shift = (more & SHIFT_MASK) as u8;
if more & SHIFT_PATH != 0 {
g.load(S0, lhs);
if shift != 0 {
g.asm.shr_ri(S0, shift);
}
finish_magic(g, dst, lhs, mul_back);
return;
}
g.asm.mov_ri(SC, magic as i64); g.load(S0, lhs); g.asm.mul_r(SC);
if more & ADD_MARKER != 0 {
g.load(SC, lhs); g.asm.sub_rr(SC, S1); g.asm.shr_ri(SC, 1); g.asm.add_rr(SC, S1); if shift != 0 {
g.asm.shr_ri(SC, shift); }
g.asm.mov_rr(S0, SC); } else {
if shift != 0 {
g.asm.shr_ri(S1, shift); }
g.asm.mov_rr(S0, S1); }
finish_magic(g, dst, lhs, mul_back);
}
fn finish_magic(g: &mut Gen, dst: Slot, lhs: Slot, mul_back: i64) {
if mul_back == 0 {
g.store(dst, S0); } else {
g.asm.mov_ri(SC, mul_back); g.asm.imul_rr(S0, SC); g.load(SC, lhs); g.asm.sub_rr(SC, S0); g.store(dst, SC); }
}
fn emit_div_mod(g: &mut Gen, dst: Slot, lhs: Slot, rhs: Slot, is_div: bool, dl: LabelId) {
g.load(SC, rhs); g.asm.test_rr(SC, SC);
g.asm.jcc(Cond::Eq, dl);
let neg_one = g.asm.new_label();
let done = g.asm.new_label();
g.asm.mov_ri(S1, -1);
g.asm.cmp_rr(SC, S1); g.asm.jcc(Cond::Eq, neg_one);
g.load(S0, lhs);
g.asm.cqo();
g.asm.idiv_r(SC); if is_div {
} else {
g.asm.mov_rr(S0, S1); }
g.asm.jmp(done);
g.asm.bind(neg_one);
if is_div {
g.load(S0, lhs);
g.asm.mov_ri(S1, i64::MIN);
g.asm.cmp_rr(S0, S1);
g.asm.jcc(Cond::Eq, dl);
g.asm.neg_r(S0);
} else {
g.asm.mov_ri(S0, 0);
}
g.asm.bind(done);
g.store(dst, S0);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jit::{reference_eval, ChainOutcome};
use std::sync::atomic::Ordering;
use std::sync::Arc;
fn run(ops: &[MicroOp], frame: &[i64]) -> (ChainOutcome, Vec<i64>) {
let status = Arc::new(AtomicI64::new(0));
let chain = compile_region_regalloc(ops, Some(status)).expect("supported region compiles");
let mut f = frame.to_vec();
let out = chain.run_with_frame(&mut f);
(out, f)
}
#[test]
fn live_intervals_span_first_to_last_touch() {
let ops = vec![
MicroOp::LoadConst { dst: 0, value: 7 },
MicroOp::Move { dst: 1, src: 0 },
MicroOp::Return { src: 1 },
];
let iv = live_intervals(&ops, 1);
assert_eq!(iv, vec![
LiveInterval { slot: 0, start: 0, end: 1 }, LiveInterval { slot: 1, start: 1, end: 2 }, ]);
}
#[test]
fn linscan_reuses_a_register_across_disjoint_ranges() {
let class = vec![Class::Float; 5];
let iv: Vec<LiveInterval> = (0..5)
.map(|s| LiveInterval { slot: s, start: (s as usize) * 2, end: (s as usize) * 2 + 1 })
.collect();
assert_eq!(linscan_spills(&iv, &class, Class::Float, 1), 0, "disjoint ranges share 1 reg");
assert_eq!(linscan_spills(&iv, &class, Class::Float, 0), 5, "0 regs spills all");
}
#[test]
fn linscan_spills_only_the_overlap_excess() {
let class = vec![Class::Float; 3];
let iv: Vec<LiveInterval> = (0..3).map(|s| LiveInterval { slot: s, start: 0, end: 9 }).collect();
assert_eq!(linscan_spills(&iv, &class, Class::Float, 3), 0);
assert_eq!(linscan_spills(&iv, &class, Class::Float, 2), 1);
assert_eq!(linscan_spills(&iv, &class, Class::Float, 1), 2);
}
fn float_loop_two_temps() -> (Vec<MicroOp>, usize) {
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 8 }, MicroOp::IntToFloat { dst: 3, src: 0 }, MicroOp::AddF { dst: 2, lhs: 2, rhs: 3 }, MicroOp::IntToFloat { dst: 4, src: 0 }, MicroOp::AddF { dst: 2, lhs: 2, rhs: 4 }, MicroOp::LoadConst { dst: 5, value: 1 },
MicroOp::Add { dst: 0, lhs: 0, rhs: 5 }, MicroOp::Jump { target: 0 }, MicroOp::Return { src: 2 },
];
(ops, 5)
}
#[test]
fn live_ranges_are_loop_aware() {
let (ops, max_slot) = float_loop_two_temps();
let r = live_ranges(&ops, max_slot);
assert_eq!(r[2], Some((0, 8)), "carried acc must span the loop");
assert_eq!(r[3], Some((1, 2)));
assert_eq!(r[4], Some((3, 4)));
}
#[test]
fn linscan_prefers_fresh_then_reuses_under_pressure() {
let (ops, max_slot) = float_loop_two_temps();
let class = classify_slots(&ops, max_slot);
let ranges = live_ranges(&ops, max_slot);
let ample = linscan_assign(&ops, max_slot, &class, &CALLER_SAVED, &FLOAT_REGS);
assert_ne!(ample[3], Loc::Frame, "temp must get a register");
assert_ne!(ample[3], ample[4], "with registers to spare, disjoint temps do NOT share");
let tight = linscan_assign(&ops, max_slot, &class, &CALLER_SAVED, &[Xmm::Xmm0, Xmm::Xmm1]);
assert_eq!(tight[3], tight[4], "under pressure, disjoint gap-clear temps reuse one register");
assert_ne!(tight[2], tight[3], "carried acc cannot share a live temp's register");
for assign in [&le, &tight] {
for a in 0..=max_slot {
for b in (a + 1)..=max_slot {
if let (Some((sa, ea)), Some((sb, eb))) = (ranges[a], ranges[b]) {
let overlap = sa <= eb && sb <= ea;
if overlap && assign[a] != Loc::Frame {
assert_ne!(assign[a], assign[b], "overlapping slots {a},{b} share a register");
}
}
}
}
}
}
#[test]
fn linscan_no_reuse_across_a_control_point() {
let ops = vec![
MicroOp::LoadConst { dst: 0, value: 1 }, MicroOp::Add { dst: 1, lhs: 0, rhs: 0 }, MicroOp::Jump { target: 4 }, MicroOp::LoadConst { dst: 9, value: 0 }, MicroOp::LoadConst { dst: 2, value: 2 }, MicroOp::Add { dst: 3, lhs: 2, rhs: 2 }, MicroOp::Return { src: 3 },
];
let max_slot = 9;
let class = classify_slots(&ops, max_slot);
let assign = linscan_assign(&ops, max_slot, &class, &CALLER_SAVED, &FLOAT_REGS);
assert_ne!(assign[0], assign[2], "reuse must not cross a control point");
}
#[test]
fn straightline_arith_matches_reference() {
let ops = vec![
MicroOp::Add { dst: 3, lhs: 0, rhs: 1 },
MicroOp::Mul { dst: 4, lhs: 3, rhs: 2 },
MicroOp::Sub { dst: 5, lhs: 4, rhs: 0 },
MicroOp::Return { src: 5 },
];
let frame = vec![7i64, 11, 13, 0, 0, 0];
let expected = reference_eval(&ops, &mut frame.clone(), 1000).unwrap();
let (out, _) = run(&ops, &frame);
assert_eq!(out, ChainOutcome::Return(expected));
}
#[test]
fn counting_loop_matches_reference() {
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 2, target: 5 }, MicroOp::Add { dst: 1, lhs: 1, rhs: 0 }, MicroOp::LoadConst { dst: 3, value: 1 },
MicroOp::Add { dst: 0, lhs: 0, rhs: 3 }, MicroOp::Jump { target: 0 },
MicroOp::Return { src: 1 },
];
let frame = vec![0i64, 0, 1000, 0];
let expected = reference_eval(&ops, &mut frame.clone(), 10_000_000).unwrap();
let (out, _) = run(&ops, &frame);
assert_eq!(out, ChainOutcome::Return(expected));
assert_eq!(out, ChainOutcome::Return(499_500));
}
#[test]
fn div_by_zero_side_exits() {
let status = Arc::new(AtomicI64::new(0));
let ops = vec![
MicroOp::Div { dst: 2, lhs: 0, rhs: 1 },
MicroOp::Return { src: 2 },
];
let chain = compile_region_regalloc(&ops, Some(status)).unwrap();
let mut frame = vec![100i64, 0, 0];
let out = chain.run_with_frame(&mut frame);
assert!(out.is_deopt(), "div by zero must side-exit, got {out:?}");
}
#[test]
fn div_min_neg_one_side_exits() {
let ops = vec![
MicroOp::Div { dst: 2, lhs: 0, rhs: 1 },
MicroOp::Return { src: 2 },
];
let frame = vec![i64::MIN, -1, 0];
assert!(
reference_eval(&ops, &mut frame.clone(), 1000).is_none(),
"reference must side-exit MIN / -1"
);
let (out, _) = run(&ops, &frame);
assert!(out.is_deopt(), "machine code must side-exit MIN / -1, got {out:?}");
}
#[test]
fn mod_min_neg_one_zero() {
let ops = vec![
MicroOp::Mod { dst: 2, lhs: 0, rhs: 1 },
MicroOp::Return { src: 2 },
];
let frame = vec![i64::MIN, -1, 0];
let expected = reference_eval(&ops, &mut frame.clone(), 1000).unwrap();
let (out, _) = run(&ops, &frame);
assert_eq!(out, ChainOutcome::Return(expected)); assert_eq!(out, ChainOutcome::Return(0));
}
#[test]
fn unsupported_op_returns_none() {
let ops = vec![
MicroOp::MapGet { dst: 1, key: 0, map_slot: 2, helper_addr: 0 },
MicroOp::Return { src: 1 },
];
assert!(compile_region_regalloc(&ops, None).is_none());
}
static TEST_PUSH_CALLS: AtomicI64 = AtomicI64::new(0);
unsafe extern "C" fn test_push_i64(
frame: *mut i64,
vec_slot: i64,
ptr_slot: i64,
len_slot: i64,
value: i64,
) {
TEST_PUSH_CALLS.fetch_add(1, Ordering::Relaxed);
let vec = *frame.add(vec_slot as usize) as *mut Vec<i64>;
(*vec).push(value);
*frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
*frame.add(len_slot as usize) = (*vec).len() as i64;
}
fn push_loop_ops(helper_addr: i64) -> Vec<MicroOp> {
vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 5 }, MicroOp::ArrPush {
src: 0,
vec_slot: 8,
ptr_slot: 9,
len_slot: 10,
helper_addr,
byte: false,
narrow32: false,
},
MicroOp::LoadConst { dst: 2, value: 1 },
MicroOp::Add { dst: 0, lhs: 0, rhs: 2 }, MicroOp::Jump { target: 0 },
MicroOp::Return { src: 10 }, ]
}
#[test]
fn inline_push_fast_path_never_calls_helper_within_capacity() {
let n = 5000i64;
let mut vec: Vec<i64> = Vec::with_capacity(n as usize);
let vec_ptr = &mut vec as *mut Vec<i64>;
let buf_before = vec.as_ptr() as i64;
let ops = push_loop_ops(test_push_i64 as usize as i64);
let status = Arc::new(AtomicI64::new(0));
let chain = compile_region_regalloc(&ops, Some(status)).expect("push region compiles");
let mut frame = vec![0i64; 11];
frame[1] = n;
frame[8] = vec_ptr as i64;
frame[9] = buf_before;
frame[10] = 0;
TEST_PUSH_CALLS.store(0, Ordering::Relaxed);
let out = chain.run_with_frame(&mut frame);
assert_eq!(out, ChainOutcome::Return(n), "final length must be n");
assert_eq!(
TEST_PUSH_CALLS.load(Ordering::Relaxed),
0,
"every push fit within capacity — the inline fast path must call the helper ZERO times"
);
assert_eq!(vec.len(), n as usize, "real Vec length");
assert_eq!(vec.as_ptr() as i64, buf_before, "no realloc moved the buffer");
for (i, &v) in vec.iter().enumerate() {
assert_eq!(v, i as i64, "buffer[{i}] must be the pushed value");
}
assert_eq!(frame[10], n, "mirrored length");
assert_eq!(frame[9], buf_before, "mirrored pointer unchanged (no realloc)");
}
#[test]
fn inline_push_realloc_cold_path_matches_vec_push() {
let n = 4000i64;
let mut vec: Vec<i64> = Vec::with_capacity(1);
let vec_ptr = &mut vec as *mut Vec<i64>;
let mut reference: Vec<i64> = Vec::with_capacity(1);
for i in 0..n {
reference.push(i);
}
let ops = push_loop_ops(test_push_i64 as usize as i64);
let status = Arc::new(AtomicI64::new(0));
let chain = compile_region_regalloc(&ops, Some(status)).expect("push region compiles");
let mut frame = vec![0i64; 11];
frame[1] = n;
frame[8] = vec_ptr as i64;
frame[9] = vec.as_ptr() as i64;
frame[10] = 0;
TEST_PUSH_CALLS.store(0, Ordering::Relaxed);
let out = chain.run_with_frame(&mut frame);
assert_eq!(out, ChainOutcome::Return(n), "final length must be n");
assert_eq!(vec, reference, "compiled push must be bit-identical to Vec::push");
assert_eq!(frame[10], n, "mirrored length matches");
assert_eq!(
frame[9],
vec.as_ptr() as i64,
"mirrored pointer matches the (post-realloc) buffer"
);
let calls = TEST_PUSH_CALLS.load(Ordering::Relaxed);
assert!(
calls < n / 2,
"the helper must fire only on realloc boundaries (got {calls} of {n} pushes)"
);
assert!(calls >= 1, "at least one realloc must have occurred");
}
#[test]
fn inline_push_byte_normalizes_like_helper() {
unsafe extern "C" fn test_push_bool(
frame: *mut i64,
vec_slot: i64,
ptr_slot: i64,
len_slot: i64,
value: i64,
) {
let vec = *frame.add(vec_slot as usize) as *mut Vec<bool>;
(*vec).push(value != 0);
*frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
*frame.add(len_slot as usize) = (*vec).len() as i64;
}
let n = 300i64;
let mut vec: Vec<bool> = Vec::with_capacity(n as usize);
let vec_ptr = &mut vec as *mut Vec<bool>;
let buf_before = vec.as_ptr() as i64;
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 7 }, MicroOp::LoadConst { dst: 3, value: 3 },
MicroOp::Mod { dst: 4, lhs: 0, rhs: 3 }, MicroOp::ArrPush {
src: 4,
vec_slot: 8,
ptr_slot: 9,
len_slot: 10,
helper_addr: test_push_bool as usize as i64,
byte: true,
narrow32: false,
},
MicroOp::LoadConst { dst: 2, value: 1 },
MicroOp::Add { dst: 0, lhs: 0, rhs: 2 }, MicroOp::Jump { target: 0 },
MicroOp::Return { src: 10 },
];
let status = Arc::new(AtomicI64::new(0));
let chain = compile_region_regalloc(&ops, Some(status)).expect("byte push region compiles");
let mut frame = vec![0i64; 11];
frame[1] = n;
frame[8] = vec_ptr as i64;
frame[9] = buf_before;
frame[10] = 0;
let out = chain.run_with_frame(&mut frame);
assert_eq!(out, ChainOutcome::Return(n));
let reference: Vec<bool> = (0..n).map(|i| (i % 3) != 0).collect();
assert_eq!(vec, reference, "byte push must normalize (v != 0) like the helper");
assert_eq!(vec.as_ptr() as i64, buf_before, "no realloc within capacity");
}
#[test]
fn inline_push_float_value_bitcopies() {
unsafe extern "C" fn test_push_f64(
frame: *mut i64,
vec_slot: i64,
ptr_slot: i64,
len_slot: i64,
value: i64,
) {
let vec = *frame.add(vec_slot as usize) as *mut Vec<f64>;
(*vec).push(f64::from_bits(value as u64));
*frame.add(ptr_slot as usize) = (*vec).as_mut_ptr() as i64;
*frame.add(len_slot as usize) = (*vec).len() as i64;
}
let n = 200i64;
let mut vec: Vec<f64> = Vec::with_capacity(n as usize);
let vec_ptr = &mut vec as *mut Vec<f64>;
let buf_before = vec.as_ptr() as i64;
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 6 }, MicroOp::IntToFloat { dst: 4, src: 0 }, MicroOp::ArrPush {
src: 4,
vec_slot: 8,
ptr_slot: 9,
len_slot: 10,
helper_addr: test_push_f64 as usize as i64,
byte: false,
narrow32: false,
},
MicroOp::LoadConst { dst: 2, value: 1 },
MicroOp::Add { dst: 0, lhs: 0, rhs: 2 }, MicroOp::Jump { target: 0 },
MicroOp::Return { src: 10 },
];
let status = Arc::new(AtomicI64::new(0));
let chain = compile_region_regalloc(&ops, Some(status)).expect("float push region compiles");
let mut frame = vec![0i64; 11];
frame[1] = n;
frame[8] = vec_ptr as i64;
frame[9] = buf_before;
frame[10] = 0;
let out = chain.run_with_frame(&mut frame);
assert_eq!(out, ChainOutcome::Return(n));
let reference: Vec<f64> = (0..n).map(|i| i as f64).collect();
assert_eq!(vec, reference, "float push must bit-copy the value like the helper");
assert_eq!(vec.as_ptr() as i64, buf_before, "no realloc within capacity");
}
fn lcg(state: &mut u64) -> u64 {
*state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*state >> 33
}
#[test]
fn random_straightline_matches_reference_all_pressures() {
for slots in [3u16, 6, 10, 20] {
for seed in 1..=80u64 {
let mut s = seed.wrapping_mul(2654435761);
let len = 8 + (lcg(&mut s) % 24) as usize;
let mut ops = Vec::with_capacity(len + 1);
for _ in 0..len {
let dst = (lcg(&mut s) % slots as u64) as u16;
let lhs = (lcg(&mut s) % slots as u64) as u16;
let rhs = (lcg(&mut s) % slots as u64) as u16;
let op = match lcg(&mut s) % 13 {
0 => MicroOp::Add { dst, lhs, rhs },
1 => MicroOp::Sub { dst, lhs, rhs },
2 => MicroOp::Mul { dst, lhs, rhs },
3 => MicroOp::BitAnd { dst, lhs, rhs },
4 => MicroOp::BitOr { dst, lhs, rhs },
5 => MicroOp::BitXor { dst, lhs, rhs },
6 => MicroOp::Lt { dst, lhs, rhs },
7 => MicroOp::Gt { dst, lhs, rhs },
8 => MicroOp::LtEq { dst, lhs, rhs },
9 => MicroOp::Eq { dst, lhs, rhs },
10 => MicroOp::Neq { dst, lhs, rhs },
11 => MicroOp::Move { dst, src: lhs },
_ => MicroOp::LoadConst {
dst,
value: (lcg(&mut s) as i64).wrapping_sub(1 << 40),
},
};
ops.push(op);
}
let ret = (lcg(&mut s) % slots as u64) as u16;
ops.push(MicroOp::Return { src: ret });
let mut frame = vec![0i64; slots as usize];
for (i, f) in frame.iter_mut().enumerate() {
*f = (i as i64 + 1).wrapping_mul(1_000_003) - 7;
}
let expected = reference_eval(&ops, &mut frame.clone(), 100_000);
let (out, post) = run(&ops, &frame);
match expected {
Some(e) => {
assert_eq!(
out,
ChainOutcome::Return(e),
"slots={slots} seed={seed}: return diverged"
);
let mut ref_frame = frame.clone();
reference_eval(&ops, &mut ref_frame, 100_000);
assert_eq!(post, ref_frame, "slots={slots} seed={seed}: frame diverged");
}
None => assert!(
out.is_deopt(),
"slots={slots} seed={seed}: overflow must deopt, got {out:?}"
),
}
}
}
}
#[test]
fn shifts_and_unary_match_reference() {
let ops = vec![
MicroOp::Shl { dst: 3, lhs: 0, rhs: 1 },
MicroOp::Shr { dst: 4, lhs: 3, rhs: 2 },
MicroOp::NotInt { dst: 5, src: 4 },
MicroOp::NotBool { dst: 6, src: 2 },
MicroOp::DivPow2 { dst: 7, lhs: 0, k: 3 },
MicroOp::Add { dst: 8, lhs: 5, rhs: 7 },
MicroOp::Return { src: 8 },
];
for &a in &[1i64, -1, 1024, i64::MIN, -255] {
let frame = vec![a, 5, 2, 0, 0, 0, 0, 0, 0];
let expected = reference_eval(&ops, &mut frame.clone(), 1000).unwrap();
let (out, _) = run(&ops, &frame);
assert_eq!(out, ChainOutcome::Return(expected), "a={a}");
}
}
fn magic_parts(d: u64) -> (u64, u8) {
if d & (d - 1) == 0 {
return (0, (d.trailing_zeros() as u8) | 0x80);
}
let l = 63 - d.leading_zeros();
let numer = (1u128 << l) << 64;
let m = (numer / d as u128) as u64;
let rem = (numer % d as u128) as u64;
if d - rem < (1u64 << l) {
(m + 1, l as u8)
} else {
let twice = rem.wrapping_mul(2);
let bump = (twice >= d || twice < rem) as u64;
(m.wrapping_mul(2).wrapping_add(bump) + 1, (l as u8) | 0x40)
}
}
#[test]
fn magic_div_matches_reference_div_and_mod() {
for &c in &[3i64, 7, 100, 1000, 1_000_000_007, 65521] {
let (magic, more) = magic_parts(c as u64);
let ops = vec![
MicroOp::MagicDivU { dst: 1, lhs: 0, magic, more, mul_back: 0 },
MicroOp::MagicDivU { dst: 2, lhs: 0, magic, more, mul_back: c },
MicroOp::Add { dst: 3, lhs: 1, rhs: 2 },
MicroOp::Return { src: 3 },
];
for n in [
0i64, 1, c - 1, c, c + 1, 2 * c + 3, 123_456_789, i64::MAX, i64::MAX - 1,
(i64::MAX / c) * c, (i64::MAX / c).saturating_sub(1) * c,
] {
assert!(n >= 0);
let frame = vec![n, 0, 0, 0];
let expected = reference_eval(&ops, &mut frame.clone(), 1000).unwrap();
let (out, _) = run(&ops, &frame);
assert_eq!(out, ChainOutcome::Return(expected), "c={c} n={n}");
assert_eq!(
expected,
n.wrapping_div(c).wrapping_add(n.wrapping_rem(c)),
"reference vs kernel c={c} n={n}"
);
}
}
}
#[test]
fn poly_loop_matches_reference_under_spill() {
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 11 }, MicroOp::Mul { dst: 7, lhs: 2, rhs: 3 }, MicroOp::Mul { dst: 8, lhs: 0, rhs: 4 }, MicroOp::Add { dst: 7, lhs: 7, rhs: 8 }, MicroOp::Sub { dst: 2, lhs: 7, rhs: 5 }, MicroOp::BitXor { dst: 2, lhs: 2, rhs: 6 }, MicroOp::Add { dst: 9, lhs: 2, rhs: 0 }, MicroOp::Mul { dst: 2, lhs: 2, rhs: 3 }, MicroOp::LoadConst { dst: 10, value: 1 },
MicroOp::Add { dst: 0, lhs: 0, rhs: 10 }, MicroOp::Jump { target: 0 },
MicroOp::Return { src: 2 },
];
let frame = vec![0i64, 5000, 1, 6364136223846793005u64 as i64, 1442695040888963407u64 as i64, 12345, 0x5DEECE66D, 0, 0, 0, 0];
let expected = reference_eval(&ops, &mut frame.clone(), 100_000_000);
let (out, _) = run(&ops, &frame);
match expected {
Some(e) => assert_eq!(out, ChainOutcome::Return(e)),
None => assert!(out.is_deopt(), "overflow must deopt, got {out:?}"),
}
}
#[test]
fn loop_depths_counts_back_edge_nesting() {
let flat = vec![
MicroOp::Add { dst: 1, lhs: 0, rhs: 0 },
MicroOp::Return { src: 1 },
];
assert_eq!(loop_depths(&flat), vec![0, 0]);
let single = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 4 }, MicroOp::Add { dst: 2, lhs: 2, rhs: 0 }, MicroOp::Add { dst: 0, lhs: 0, rhs: 3 }, MicroOp::Jump { target: 1 }, MicroOp::Return { src: 2 }, ];
assert_eq!(loop_depths(&single), vec![0, 1, 1, 1, 0]);
let nested = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 6 }, MicroOp::Move { dst: 4, src: 0 }, MicroOp::Add { dst: 2, lhs: 2, rhs: 4 }, MicroOp::Jump { target: 2 }, MicroOp::Add { dst: 0, lhs: 0, rhs: 3 }, MicroOp::Jump { target: 1 }, MicroOp::Return { src: 2 }, ];
assert_eq!(loop_depths(&nested), vec![0, 1, 2, 2, 1, 1, 0]);
}
#[test]
fn loop_weight_keeps_carried_slot_resident_and_exact() {
let ops = vec![
MicroOp::Add { dst: 5, lhs: 0, rhs: 1 }, MicroOp::Add { dst: 5, lhs: 5, rhs: 0 }, MicroOp::Add { dst: 5, lhs: 5, rhs: 1 }, MicroOp::Add { dst: 2, lhs: 2, rhs: 5 }, MicroOp::Branch { cmp: Cmp::Lt, lhs: 3, rhs: 4, target: 8 }, MicroOp::Add { dst: 2, lhs: 2, rhs: 3 }, MicroOp::Add { dst: 3, lhs: 3, rhs: 6 }, MicroOp::Jump { target: 4 }, MicroOp::Return { src: 2 }, ];
let frame = vec![3i64, 4, 0, 0, 100_000, 0, 1];
let depths = loop_depths(&ops);
assert_eq!(depths[5], 1, "the accumulator update is inside the loop");
let expected = reference_eval(&ops, &mut frame.clone(), 10_000_000).unwrap();
let (out, post) = run(&ops, &frame);
assert_eq!(out, ChainOutcome::Return(expected), "loop-weighted ranking must stay bit-identical");
let mut ref_frame = frame.clone();
reference_eval(&ops, &mut ref_frame, 10_000_000).unwrap();
assert_eq!(post, ref_frame, "frame diverged under loop-weighted ranking");
}
#[test]
fn call_weight_breaks_tie_toward_live_across_call_slot() {
let ops = vec![
MicroOp::Add { dst: 1, lhs: 0, rhs: 3 },
MicroOp::Add { dst: 2, lhs: 0, rhs: 3 },
MicroOp::Add { dst: 8, lhs: 2, rhs: 0 },
MicroOp::ListClear { vec_slot: 5, ptr_slot: 6, len_slot: 7, helper_addr: 0 },
MicroOp::Add { dst: 4, lhs: 1, rhs: 3 },
MicroOp::Return { src: 4 },
];
let max_slot = max_slot_of(&ops);
let la = liveness_after(&ops, max_slot);
assert!(la[3][1], "survivor must be live across the call");
assert!(!la[3][2], "transient must be dead across the call");
let (order, _) = rank_slots(&ops, 16, 4, 4, Some(&la));
let pos = |slot: Slot| order.iter().position(|&(s, _)| s == slot).unwrap();
let key = |slot: Slot| order.iter().find(|&&(s, _)| s == slot).unwrap().1;
assert_eq!(
key(1),
key(2),
"survivor and transient must share the SAME primary weight (got {} vs {})",
key(1),
key(2)
);
assert!(
pos(1) < pos(2),
"on equal primary weight the call-survivor must rank ahead of the call-dead slot"
);
}
#[test]
fn call_weight_never_outranks_a_hotter_loop_slot() {
let ops = vec![
MicroOp::Add { dst: 1, lhs: 0, rhs: 3 }, MicroOp::Branch { cmp: Cmp::Lt, lhs: 4, rhs: 5, target: 6 }, MicroOp::Add { dst: 2, lhs: 2, rhs: 4 }, MicroOp::Add { dst: 4, lhs: 4, rhs: 3 }, MicroOp::ListClear { vec_slot: 7, ptr_slot: 8, len_slot: 9, helper_addr: 0 }, MicroOp::Jump { target: 1 }, MicroOp::Add { dst: 10, lhs: 1, rhs: 2 }, ];
let mut ops = ops;
ops.push(MicroOp::Return { src: 10 });
let max_slot = max_slot_of(&ops);
let la = liveness_after(&ops, max_slot);
let (order, _) = rank_slots(&ops, 16, 4, 4, Some(&la));
let pos = |slot: Slot| order.iter().position(|&(s, _)| s == slot).unwrap();
assert!(
pos(2) < pos(1),
"the loop-hot slot must out-rank the cold call-survivor (primary key dominates)"
);
}
#[test]
fn call_weight_is_inert_without_a_call() {
let ops = vec![
MicroOp::Add { dst: 3, lhs: 0, rhs: 1 },
MicroOp::Mul { dst: 4, lhs: 3, rhs: 2 },
MicroOp::Sub { dst: 5, lhs: 4, rhs: 0 },
MicroOp::Return { src: 5 },
];
let (no_call, _) = rank_slots(&ops, 16, 4, 4, None);
let max_slot = max_slot_of(&ops);
let dead = vec![vec![false; max_slot + 1]; ops.len()];
let (dead_la, _) = rank_slots(&ops, 16, 4, 4, Some(&dead));
assert_eq!(no_call, dead_la, "the call bonus must vanish with no live-across-call slot");
}
#[test]
fn call_weight_self_call_region_compiles_and_ranks_survivor() {
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 5 }, MicroOp::CallSelf { dst: 3, args_start: 8, depth_addr: 0, status_addr: 0, limit_slot: 2, frame_size: 4 }, MicroOp::Add { dst: 4, lhs: 4, rhs: 3 }, MicroOp::Add { dst: 0, lhs: 0, rhs: 4 }, MicroOp::Jump { target: 0 }, MicroOp::Return { src: 4 }, ];
let max_slot = max_slot_of(&ops);
assert!(max_slot >= 11, "self-call window extent (8+4-1=11) must be covered, got {max_slot}");
let la = liveness_after(&ops, max_slot);
assert!(la[1][4], "acc must be live across the self-call");
let (order, _) = rank_slots(&ops, 16, 4, 4, Some(&la));
let key = |slot: Slot| order.iter().find(|&&(s, _)| s == slot).map(|&(_, k)| k).unwrap_or(0);
assert!(key(4) > key(1), "call-surviving loop slot must out-rank a cold bound");
}
#[test]
fn liveness_after_is_a_sound_backward_dataflow() {
let ops = vec![
MicroOp::Add { dst: 2, lhs: 0, rhs: 1 },
MicroOp::Mul { dst: 3, lhs: 0, rhs: 0 },
MicroOp::Sub { dst: 4, lhs: 2, rhs: 3 },
MicroOp::Return { src: 4 },
];
let la = liveness_after(&ops, 4);
assert!(la[1][3], "dead must be live right after it is defined (read by op 2)");
assert!(la[1][2], "t must still be live after op 1 (read by op 2)");
assert!(!la[2][2], "t is dead after its last read (op 2)");
assert!(!la[2][3], "dead is dead after its last read (op 2)");
assert!(la[2][4], "r is live after op 2 (Return reads it)");
assert!(la[3].iter().all(|&b| !b), "no slot is live after Return");
}
#[test]
fn liveness_after_propagates_across_loop_back_edge() {
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 2, target: 5 }, MicroOp::Add { dst: 1, lhs: 1, rhs: 0 }, MicroOp::LoadConst { dst: 3, value: 1 }, MicroOp::Add { dst: 0, lhs: 0, rhs: 3 }, MicroOp::Jump { target: 0 }, MicroOp::Return { src: 1 }, ];
let la = liveness_after(&ops, 3);
assert!(la[3][0], "i must be live after the increment (back-edge re-reads it)");
assert!(la[1][0], "i must be live after acc += i (used again at op 3)");
assert!(la[3][1], "acc must be live after op 3 (read next iteration / return)");
assert!(la[1][1], "acc must be live after its own update");
}
#[test]
fn spill_elision_keeps_written_and_live_drops_readonly_dead() {
let ops = vec![
MicroOp::Add { dst: 7, lhs: 5, rhs: 1 }, MicroOp::Add { dst: 8, lhs: 8, rhs: 1 }, MicroOp::ListClear { vec_slot: 2, ptr_slot: 3, len_slot: 4, helper_addr: 0 }, MicroOp::Add { dst: 9, lhs: 8, rhs: 1 }, MicroOp::Return { src: 9 }, ];
let max_slot = 9;
let la = liveness_after(&ops, max_slot);
let mut written = vec![false; max_slot + 1];
for op in &ops {
if let Some(d) = dest_of(op) {
written[d as usize] = true;
}
}
let keep = |s: Slot| {
written.get(s as usize).copied().unwrap_or(true)
|| la[2].get(s as usize).copied().unwrap_or(true)
};
assert!(!written[5], "cold is read-only");
assert!(!la[2][5], "cold is dead after the call");
assert!(!keep(5), "a read-only, dead-after-call resident must be elided");
assert!(written[8], "hot is written");
assert!(keep(8), "a written resident must be kept (flushed at exit)");
assert!(!written[1], "the constant operand is read-only");
assert!(la[2][1], "the constant operand is read after the call");
assert!(keep(1), "a read-only resident read after the call must be kept");
}
#[test]
fn precise_region_with_push_and_setindex_compiles() {
let ops = vec![
MicroOp::Branch { cmp: Cmp::Lt, lhs: 0, rhs: 1, target: 5 },
MicroOp::ArrStore { src: 2, idx: 0, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
MicroOp::ArrPush { src: 2, vec_slot: 8, ptr_slot: 9, len_slot: 10, helper_addr: 0, byte: false, narrow32: false },
MicroOp::Add { dst: 0, lhs: 0, rhs: 3 },
MicroOp::Jump { target: 0 },
MicroOp::Return { src: 0 },
];
let codes: Vec<i64> = ops
.iter()
.enumerate()
.map(|(i, op)| match op {
MicroOp::ArrStore { checked: true, .. } | MicroOp::ArrPush { .. } => {
((i as i64) << 2) | 3
}
_ => 1,
})
.collect();
let status = Arc::new(AtomicI64::new(0));
let chain = compile_region_regalloc_precise(&ops, Some(status), 0, &codes);
assert!(
chain.is_some(),
"a precise push+SetIndex REGION (no mode-B prologue) must compile through \
the contiguous regalloc backend"
);
}
#[test]
fn precise_region_declines_on_codes_length_mismatch() {
let ops = vec![
MicroOp::ArrStore { src: 2, idx: 0, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
MicroOp::Return { src: 0 },
];
let codes = vec![1i64]; let status = Arc::new(AtomicI64::new(0));
assert!(
compile_region_regalloc_precise(&ops, Some(status), 0, &codes).is_none(),
"a deopt-codes length mismatch must decline"
);
}
#[test]
fn precise_region_declines_without_status() {
let ops = vec![
MicroOp::ArrStore { src: 2, idx: 0, ptr_slot: 6, len_slot: 7, byte: false, narrow32: false, checked: true },
MicroOp::Return { src: 0 },
];
let codes = vec![3i64, 1];
assert!(
compile_region_regalloc_precise(&ops, None, 0, &codes).is_none(),
"a precise region with no status channel must decline"
);
}
#[test]
fn precise_region_declines_on_cross_call() {
let ops = vec![
MicroOp::Call {
dst: 1,
args_start: 4,
table_addr: 0,
depth_addr: 0,
status_addr: 0,
limit_slot: 3,
depth_limit: SELF_CALL_DEPTH_LIMIT,
},
MicroOp::Return { src: 1 },
];
let codes = vec![3i64, 1];
let status = Arc::new(AtomicI64::new(0));
assert!(
compile_region_regalloc_precise(&ops, Some(status), 0, &codes).is_none(),
"a precise REGION with a cross-function Call must decline (region gate)"
);
}
}