use std::sync::atomic::{AtomicI64, Ordering};
use crate::buffer::{HoleValue, JitBuffer, JitChain};
use crate::{
V_BINOP, V_BRANCH, V_CONST, V_FBINOP, V_FMOV, V_MOV, V_RET, V_SQRTF, V_DIVF, V_I2F, V_BRANCHF,
V_ARRLD_RPTR, V_ARRLD_RPTR_C, V_ARRST_RPTR, V_ARRST_RPTR_C,
ST_ADD3, ST_ADDF3, ST_AND3, ST_ARRLD, ST_ARRLD2_ADDF, ST_ARRLD2_MULF, ST_ARRLD2_SUBF, ST_ARRLDB, ST_ARRLDB_U, ST_ARRLD_U, ST_ARRST, ST_ARRSTB, ST_ARRSTB_U, ST_ARRST_U, ST_BREQ, ST_BREQF, ST_BRLEF, ST_BRLT, ST_CALL, ST_PUSH, ST_LIST_CLEAR,
ST_ARRLD_I32, ST_ARRLD_I32_U, ST_ARRST_I32, ST_ARRST_I32_U,
ST_ARRLD2_ADD, ST_ARRLD2_ADD_U, ST_ARRLD2_SUB, ST_ARRLD2_SUB_U, ST_ARRLD2_MUL, ST_ARRLD2_MUL_U,
ST_ARRLDAFF_NONE, ST_ARRLDAFF_NONE_U, ST_ARRLDAFF_ADD, ST_ARRLDAFF_ADD_U,
ST_ARRLDAFF_SUB, ST_ARRLDAFF_SUB_U, ST_ARRLDAFF_MUL, ST_ARRLDAFF_MUL_U,
ST_ARRRMW_ADD, ST_ARRRMW_ADD_U, ST_ARRRMW_SUB, ST_ARRRMW_SUB_U, ST_ARRRMW_MUL, ST_ARRRMW_MUL_U,
ST_ARRRMW_AND, ST_ARRRMW_AND_U, ST_ARRRMW_OR, ST_ARRRMW_OR_U, ST_ARRRMW_XOR, ST_ARRRMW_XOR_U,
ST_ARRRMW_ADDF, ST_ARRRMW_ADDF_U, ST_ARRRMW_SUBF, ST_ARRRMW_SUBF_U, ST_ARRRMW_MULF, ST_ARRRMW_MULF_U,
ST_FMAF,
ST_ARRCONDSWAP_GT, ST_ARRCONDSWAP_GT_U, ST_ARRCONDSWAP_LT, ST_ARRCONDSWAP_LT_U,
ST_ARRCONDSWAP_GE, ST_ARRCONDSWAP_GE_U, ST_ARRCONDSWAP_LE, ST_ARRCONDSWAP_LE_U,
ST_ARRSWAP, ST_ARRSWAP_U,
ST_BRLTF, ST_BRZ, ST_CALL_PRECISE, ST_CONSTST, ST_DEOPT, ST_DEOPT_AT, ST_DIV3C,
ST_DIVPOW2, ST_MAGICDIV,
ST_DIVF3C, ST_EQ3, ST_EQF3, ST_I2F2,
ST_JUMP, ST_LE3, ST_LEF3, ST_LT3, ST_LTF3, ST_MOD3C, ST_MOVSS, ST_MUL3, ST_MULF3, ST_NE3,
ST_ALLOCLIST, ST_CALL_SELF, ST_CALL_SELF_COPY, ST_LISTTRIPLE, ST_MAPHGET, ST_MAPHHAS, ST_MAPHSET, ST_MEMMEM, ST_NEF3,
ST_NOTB2,
ST_NOTI2, ST_OR3, ST_RET2,
ST_SHL3, ST_SHR3, ST_SQRTF2, ST_SUB3,
ST_SUBF3, ST_XOR3,
};
pub type Slot = u16;
pub const BAKED_CALL_DEPTH: i64 = 2_500;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cmp {
Lt,
Gt,
LtEq,
GtEq,
Eq,
NotEq,
}
impl Cmp {
pub fn eval(self, a: i64, b: i64) -> bool {
match self {
Cmp::Lt => a < b,
Cmp::Gt => a > b,
Cmp::LtEq => a <= b,
Cmp::GtEq => a >= b,
Cmp::Eq => a == b,
Cmp::NotEq => a != b,
}
}
pub fn negated(self) -> Cmp {
match self {
Cmp::Lt => Cmp::GtEq,
Cmp::Gt => Cmp::LtEq,
Cmp::LtEq => Cmp::Gt,
Cmp::GtEq => Cmp::Lt,
Cmp::Eq => Cmp::NotEq,
Cmp::NotEq => Cmp::Eq,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FOp {
Add,
Sub,
Mul,
}
impl FOp {
pub fn eval(self, a: f64, b: f64) -> f64 {
match self {
FOp::Add => a + b,
FOp::Sub => a - b,
FOp::Mul => a * b,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IOp {
Add,
Sub,
Mul,
}
impl IOp {
pub fn eval(self, a: i64, b: i64) -> i64 {
match self {
IOp::Add => a.wrapping_add(b),
IOp::Sub => a.wrapping_sub(b),
IOp::Mul => a.wrapping_mul(b),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AffOp {
None,
Add,
Sub,
Mul,
}
impl AffOp {
pub fn eval(self, a: i64, b: i64, c: i64) -> i64 {
match self {
AffOp::None => a.wrapping_add(c),
AffOp::Add => a.wrapping_add(b).wrapping_add(c),
AffOp::Sub => a.wrapping_sub(b).wrapping_add(c),
AffOp::Mul => a.wrapping_mul(b).wrapping_add(c),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RmwOp {
Add,
Sub,
Mul,
And,
Or,
Xor,
AddF,
SubF,
MulF,
}
impl RmwOp {
pub fn is_float(self) -> bool {
matches!(self, RmwOp::AddF | RmwOp::SubF | RmwOp::MulF)
}
pub fn eval(self, a: i64, b: i64) -> i64 {
match self {
RmwOp::Add => a.wrapping_add(b),
RmwOp::Sub => a.wrapping_sub(b),
RmwOp::Mul => a.wrapping_mul(b),
RmwOp::And => a & b,
RmwOp::Or => a | b,
RmwOp::Xor => a ^ b,
RmwOp::AddF => (f64::from_bits(a as u64) + f64::from_bits(b as u64)).to_bits() as i64,
RmwOp::SubF => (f64::from_bits(a as u64) - f64::from_bits(b as u64)).to_bits() as i64,
RmwOp::MulF => (f64::from_bits(a as u64) * f64::from_bits(b as u64)).to_bits() as i64,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StrSrc {
Byte(Slot),
Const {
ptr: i64,
len: i64,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MicroOp {
LoadConst {
dst: Slot,
value: i64,
},
Move {
dst: Slot,
src: Slot,
},
Add {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Sub {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Mul {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Lt {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Gt {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Eq {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Div {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
DivPow2 {
dst: Slot,
lhs: Slot,
k: u32,
},
MagicDivU {
dst: Slot,
lhs: Slot,
magic: u64,
more: u8,
mul_back: i64,
},
Mod {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
LtEq {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
GtEq {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Neq {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Branch {
cmp: Cmp,
lhs: Slot,
rhs: Slot,
target: usize,
},
BitAnd {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
BitOr {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
BitXor {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Shl {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
Shr {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
NotInt {
dst: Slot,
src: Slot,
},
NotBool {
dst: Slot,
src: Slot,
},
AddF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
SubF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
MulF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
DivF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
LtF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
GtF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
LtEqF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
GtEqF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
EqF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
NeqF {
dst: Slot,
lhs: Slot,
rhs: Slot,
},
BranchF {
cmp: Cmp,
lhs: Slot,
rhs: Slot,
target: usize,
},
IntToFloat {
dst: Slot,
src: Slot,
},
SqrtF {
dst: Slot,
src: Slot,
},
MapGet {
dst: Slot,
key: Slot,
map_slot: Slot,
helper_addr: i64,
},
MapSet {
src: Slot,
key: Slot,
map_slot: Slot,
helper_addr: i64,
},
CallSelf {
dst: Slot,
args_start: Slot,
depth_addr: i64,
status_addr: i64,
limit_slot: Slot,
frame_size: i64,
},
CallSelfCopy {
dst: Slot,
args_start: Slot,
src_start: Slot,
arg_count: u16,
depth_addr: i64,
status_addr: i64,
limit_slot: Slot,
frame_size: i64,
},
NewList {
vec_slot: Slot,
ptr_slot: Slot,
len_slot: Slot,
helper_addr: i64,
},
ListTriple {
handle_slot: Slot,
vec_slot: Slot,
ptr_slot: Slot,
len_slot: Slot,
helper_addr: i64,
},
MapHas {
dst: Slot,
key: Slot,
map_slot: Slot,
helper_addr: i64,
},
Call {
dst: Slot,
args_start: Slot,
table_addr: i64,
depth_addr: i64,
status_addr: i64,
limit_slot: Slot,
depth_limit: i64,
},
ArrPush {
src: Slot,
vec_slot: Slot,
ptr_slot: Slot,
len_slot: Slot,
helper_addr: i64,
byte: bool,
narrow32: bool,
},
ListClear {
vec_slot: Slot,
ptr_slot: Slot,
len_slot: Slot,
helper_addr: i64,
},
StrAppend {
text_handle_slot: Slot,
src: StrSrc,
helper_addr: i64,
},
MemMem {
h_ptr_slot: Slot,
h_len_slot: Slot,
n_ptr_slot: Slot,
n_len_slot: Slot,
needle_len_slot: Slot,
i_slot: Slot,
count_slot: Slot,
helper_addr: i64,
},
ArrLoad {
dst: Slot,
idx: Slot,
ptr_slot: Slot,
len_slot: Slot,
byte: bool,
narrow32: bool,
checked: bool,
},
ArrLoadAffine {
dst: Slot,
a: Slot,
op: AffOp,
b: Slot,
const_offset: i64,
ptr_slot: Slot,
len_slot: Slot,
checked: bool,
},
ArrLoad2F {
dst: Slot,
i: Slot,
j: Slot,
ptr_slot: Slot,
len_slot: Slot,
op: FOp,
},
ArrLoad2 {
dst: Slot,
i: Slot,
j: Slot,
ptr_a: Slot,
len_a: Slot,
ptr_b: Slot,
len_b: Slot,
op: IOp,
checked: bool,
},
ArrStore {
src: Slot,
idx: Slot,
ptr_slot: Slot,
len_slot: Slot,
byte: bool,
narrow32: bool,
checked: bool,
},
ArrRMW {
idx: Slot,
operand: Slot,
ptr_slot: Slot,
len_slot: Slot,
op: RmwOp,
checked: bool,
},
ArrCondSwap {
idx1: Slot,
idx2: Slot,
ptr_slot: Slot,
len_slot: Slot,
cmp: Cmp,
checked: bool,
},
ArrSwap {
idx1: Slot,
idx2: Slot,
ptr_slot: Slot,
len_slot: Slot,
checked: bool,
},
FmaF {
dst: Slot,
a: Slot,
b: Slot,
c: Slot,
},
Jump {
target: usize,
},
JumpIfFalse {
cond: Slot,
target: usize,
},
JumpIfTrue {
cond: Slot,
target: usize,
},
Return {
src: Slot,
},
}
#[derive(Debug, PartialEq, Eq)]
pub enum JitCompileError {
Empty,
FallsOffTheEnd,
BadJumpTarget {
op_index: usize,
target: usize,
},
Assembly(String),
Unsupported(&'static str),
}
impl std::fmt::Display for JitCompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
JitCompileError::Empty => write!(f, "jit: empty program"),
JitCompileError::FallsOffTheEnd => {
write!(f, "jit: the final op must be Return or Jump")
}
JitCompileError::BadJumpTarget { op_index, target } => {
write!(f, "jit: op {op_index} jumps to {target}, outside the program")
}
JitCompileError::Assembly(e) => write!(f, "jit: assembly failed: {e}"),
JitCompileError::Unsupported(name) => {
write!(f, "jit: the stencil tier has no lowering for {name}")
}
}
}
}
impl std::error::Error for JitCompileError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChainOutcome {
Return(i64),
Deopt(i64),
}
impl ChainOutcome {
pub fn expect_return(self) -> i64 {
match self {
ChainOutcome::Return(v) => v,
ChainOutcome::Deopt(_) => panic!("chain side-exited (deopt) — no return value"),
}
}
pub fn is_deopt(&self) -> bool {
matches!(self, ChainOutcome::Deopt(_))
}
}
#[derive(Debug)]
pub struct CompiledChain {
chain: JitChain,
status: Option<std::sync::Arc<AtomicI64>>,
#[allow(dead_code)]
keepalive: Vec<std::sync::Arc<AtomicI64>>,
}
impl CompiledChain {
pub(crate) fn from_chain(
chain: JitChain,
status: Option<std::sync::Arc<AtomicI64>>,
) -> Self {
CompiledChain { chain, status, keepalive: Vec::new() }
}
pub(crate) fn from_chain_keepalive(
chain: JitChain,
status: Option<std::sync::Arc<AtomicI64>>,
keepalive: Vec<std::sync::Arc<AtomicI64>>,
) -> Self {
CompiledChain { chain, status, keepalive }
}
pub fn run_with_frame(&self, frame: &mut [i64]) -> ChainOutcome {
let v = self.chain.run_with_frame(frame);
if let Some(cell) = &self.status {
let raw = cell.swap(0, Ordering::Relaxed);
if raw != 0 {
return ChainOutcome::Deopt(raw);
}
}
ChainOutcome::Return(v)
}
pub fn bytes(&self) -> &[u8] {
self.chain.bytes()
}
pub fn patch_marked(&self, value: u64) -> Result<(), crate::JitError> {
self.chain.patch_marked(value)
}
pub fn has_patch_marks(&self) -> bool {
self.chain.has_patch_marks()
}
pub fn base(&self) -> u64 {
self.chain.base()
}
pub fn piece_count(&self) -> usize {
self.chain.piece_count()
}
}
fn rmw_stencil(op: RmwOp, checked: bool) -> &'static crate::Stencil {
match (op, checked) {
(RmwOp::Add, true) => &ST_ARRRMW_ADD,
(RmwOp::Add, false) => &ST_ARRRMW_ADD_U,
(RmwOp::Sub, true) => &ST_ARRRMW_SUB,
(RmwOp::Sub, false) => &ST_ARRRMW_SUB_U,
(RmwOp::Mul, true) => &ST_ARRRMW_MUL,
(RmwOp::Mul, false) => &ST_ARRRMW_MUL_U,
(RmwOp::And, true) => &ST_ARRRMW_AND,
(RmwOp::And, false) => &ST_ARRRMW_AND_U,
(RmwOp::Or, true) => &ST_ARRRMW_OR,
(RmwOp::Or, false) => &ST_ARRRMW_OR_U,
(RmwOp::Xor, true) => &ST_ARRRMW_XOR,
(RmwOp::Xor, false) => &ST_ARRRMW_XOR_U,
(RmwOp::AddF, true) => &ST_ARRRMW_ADDF,
(RmwOp::AddF, false) => &ST_ARRRMW_ADDF_U,
(RmwOp::SubF, true) => &ST_ARRRMW_SUBF,
(RmwOp::SubF, false) => &ST_ARRRMW_SUBF_U,
(RmwOp::MulF, true) => &ST_ARRRMW_MULF,
(RmwOp::MulF, false) => &ST_ARRRMW_MULF_U,
}
}
fn ld2_int_stencil(op: IOp, checked: bool) -> &'static crate::Stencil {
match (op, checked) {
(IOp::Add, true) => &ST_ARRLD2_ADD,
(IOp::Add, false) => &ST_ARRLD2_ADD_U,
(IOp::Sub, true) => &ST_ARRLD2_SUB,
(IOp::Sub, false) => &ST_ARRLD2_SUB_U,
(IOp::Mul, true) => &ST_ARRLD2_MUL,
(IOp::Mul, false) => &ST_ARRLD2_MUL_U,
}
}
fn arrld_stencil(byte: bool, narrow32: bool, checked: bool) -> &'static crate::Stencil {
match (byte, narrow32, checked) {
(true, _, true) => &ST_ARRLDB,
(true, _, false) => &ST_ARRLDB_U,
(_, true, true) => &ST_ARRLD_I32,
(_, true, false) => &ST_ARRLD_I32_U,
(false, false, true) => &ST_ARRLD,
(false, false, false) => &ST_ARRLD_U,
}
}
fn arrst_stencil(byte: bool, narrow32: bool, checked: bool) -> &'static crate::Stencil {
match (byte, narrow32, checked) {
(true, _, true) => &ST_ARRSTB,
(true, _, false) => &ST_ARRSTB_U,
(_, true, true) => &ST_ARRST_I32,
(_, true, false) => &ST_ARRST_I32_U,
(false, false, true) => &ST_ARRST,
(false, false, false) => &ST_ARRST_U,
}
}
fn affine_stencil(op: AffOp, checked: bool) -> &'static crate::Stencil {
match (op, checked) {
(AffOp::None, true) => &ST_ARRLDAFF_NONE,
(AffOp::None, false) => &ST_ARRLDAFF_NONE_U,
(AffOp::Add, true) => &ST_ARRLDAFF_ADD,
(AffOp::Add, false) => &ST_ARRLDAFF_ADD_U,
(AffOp::Sub, true) => &ST_ARRLDAFF_SUB,
(AffOp::Sub, false) => &ST_ARRLDAFF_SUB_U,
(AffOp::Mul, true) => &ST_ARRLDAFF_MUL,
(AffOp::Mul, false) => &ST_ARRLDAFF_MUL_U,
}
}
fn condswap_stencil(cmp: Cmp, checked: bool) -> &'static crate::Stencil {
match (cmp, checked) {
(Cmp::Gt, true) => &ST_ARRCONDSWAP_GT,
(Cmp::Gt, false) => &ST_ARRCONDSWAP_GT_U,
(Cmp::Lt, true) => &ST_ARRCONDSWAP_LT,
(Cmp::Lt, false) => &ST_ARRCONDSWAP_LT_U,
(Cmp::GtEq, true) => &ST_ARRCONDSWAP_GE,
(Cmp::GtEq, false) => &ST_ARRCONDSWAP_GE_U,
(Cmp::LtEq, true) => &ST_ARRCONDSWAP_LE,
(Cmp::LtEq, false) => &ST_ARRCONDSWAP_LE_U,
(Cmp::Eq | Cmp::NotEq, _) => unreachable!("cond-swap peephole never emits Eq/NotEq"),
}
}
fn emit_mem_form(
buf: &mut JitBuffer,
op: &MicroOp,
next: crate::buffer::Label,
deopt_piece: usize,
status: &Option<std::sync::Arc<AtomicI64>>,
) {
match *op {
MicroOp::Div { dst, lhs, rhs } | MicroOp::Mod { dst, lhs, rhs } => {
let stencil = if matches!(op, MicroOp::Div { .. }) { &ST_DIV3C } else { &ST_MOD3C };
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
}
MicroOp::DivPow2 { dst, lhs, k } => {
buf.push_stencil(
&ST_DIVPOW2,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, k as i64),
HoleValue::Const(2, (1i64 << k) - 1),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::MagicDivU { dst, lhs, magic, more, mul_back } => {
buf.push_stencil(
&ST_MAGICDIV,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, magic as i64),
HoleValue::Const(2, more as i64),
HoleValue::Const(3, mul_back),
HoleValue::Const(4, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::AddF { dst, lhs, rhs }
| MicroOp::SubF { dst, lhs, rhs }
| MicroOp::MulF { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::AddF { .. } => &ST_ADDF3,
MicroOp::SubF { .. } => &ST_SUBF3,
_ => &ST_MULF3,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::DivF { dst, lhs, rhs } => {
buf.push_stencil(
&ST_DIVF3C,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
}
MicroOp::LtF { dst, lhs, rhs }
| MicroOp::LtEqF { dst, lhs, rhs }
| MicroOp::EqF { dst, lhs, rhs }
| MicroOp::NeqF { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::LtF { .. } => &ST_LTF3,
MicroOp::LtEqF { .. } => &ST_LEF3,
MicroOp::EqF { .. } => &ST_EQF3,
_ => &ST_NEF3,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::GtF { dst, lhs, rhs } | MicroOp::GtEqF { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::GtF { .. } => &ST_LTF3,
_ => &ST_LEF3,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, rhs as i64),
HoleValue::Const(1, lhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::NotInt { dst, src } | MicroOp::NotBool { dst, src }
| MicroOp::IntToFloat { dst, src } | MicroOp::SqrtF { dst, src } => {
let stencil = match op {
MicroOp::NotInt { .. } => &ST_NOTI2,
MicroOp::NotBool { .. } => &ST_NOTB2,
MicroOp::SqrtF { .. } => &ST_SQRTF2,
_ => &ST_I2F2,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, src as i64),
HoleValue::Const(1, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::Call {
dst,
args_start,
table_addr,
depth_addr,
status_addr,
limit_slot,
depth_limit,
} => {
debug_assert!(status.is_some(), "calls require the status cell");
buf.push_stencil(
&ST_CALL,
&[
HoleValue::Const(0, table_addr),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, depth_limit),
HoleValue::Cont(0, next),
],
);
}
MicroOp::MapGet { dst, key, map_slot, helper_addr } => {
buf.push_stencil(
&ST_MAPHGET,
&[
HoleValue::Const(0, map_slot as i64),
HoleValue::Const(1, key as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
}
MicroOp::MapSet { src, key, map_slot, helper_addr } => {
buf.push_stencil(
&ST_MAPHSET,
&[
HoleValue::Const(0, map_slot as i64),
HoleValue::Const(1, key as i64),
HoleValue::Const(2, src as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::MemMem {
h_ptr_slot,
h_len_slot,
n_ptr_slot,
n_len_slot,
needle_len_slot,
i_slot,
count_slot,
helper_addr,
} => {
debug_assert!(status.is_some(), "memmem requires the status cell for its deopt exit");
buf.push_stencil(
&ST_MEMMEM,
&[
HoleValue::Const(0, h_ptr_slot as i64),
HoleValue::Const(1, h_len_slot as i64),
HoleValue::Const(2, n_ptr_slot as i64),
HoleValue::Const(3, n_len_slot as i64),
HoleValue::Const(4, needle_len_slot as i64),
HoleValue::Const(5, i_slot as i64),
HoleValue::Const(6, count_slot as i64),
HoleValue::Const(7, helper_addr),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
}
MicroOp::CallSelf { dst, args_start, depth_addr, status_addr, limit_slot, frame_size } => {
buf.push_stencil(
&ST_CALL_SELF,
&[
HoleValue::Const(0, 0),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, frame_size),
HoleValue::Cont(0, next),
],
);
buf.mark_patch_hole(0);
}
MicroOp::CallSelfCopy {
dst,
args_start,
src_start,
arg_count,
depth_addr,
status_addr,
limit_slot,
frame_size,
} => {
buf.push_stencil(
&ST_CALL_SELF_COPY,
&[
HoleValue::Const(0, 0),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, frame_size),
HoleValue::Const(7, src_start as i64),
HoleValue::Const(8, arg_count as i64),
HoleValue::Cont(0, next),
],
);
buf.mark_patch_hole(0);
}
MicroOp::NewList { vec_slot, ptr_slot, len_slot, helper_addr } => {
buf.push_stencil(
&ST_ALLOCLIST,
&[
HoleValue::Const(0, vec_slot as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, helper_addr } => {
buf.push_stencil(
&ST_LISTTRIPLE,
&[
HoleValue::Const(0, handle_slot as i64),
HoleValue::Const(1, vec_slot as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Const(4, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::MapHas { dst, key, map_slot, helper_addr } => {
buf.push_stencil(
&ST_MAPHHAS,
&[
HoleValue::Const(0, map_slot as i64),
HoleValue::Const(1, key as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, byte, narrow32, checked } => {
if checked {
buf.push_stencil(
arrld_stencil(byte, narrow32, true),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
arrld_stencil(byte, narrow32, false),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrLoadAffine { dst, a, op, b, const_offset, ptr_slot, len_slot, checked } => {
if checked {
buf.push_stencil(
affine_stencil(op, true),
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Const(2, const_offset),
HoleValue::Const(3, ptr_slot as i64),
HoleValue::Const(4, len_slot as i64),
HoleValue::Const(5, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
affine_stencil(op, false),
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Const(2, const_offset),
HoleValue::Const(3, ptr_slot as i64),
HoleValue::Const(5, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrLoad2F { dst, i: ix, j: jx, ptr_slot, len_slot, op } => {
let stencil = match op {
FOp::Add => &ST_ARRLD2_ADDF,
FOp::Sub => &ST_ARRLD2_SUBF,
FOp::Mul => &ST_ARRLD2_MULF,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, ix as i64),
HoleValue::Const(1, jx as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Const(4, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
}
MicroOp::ArrLoad2 { dst, i: ix, j: jx, ptr_a, len_a, ptr_b, len_b, op, checked } => {
if checked {
buf.push_stencil(
ld2_int_stencil(op, true),
&[
HoleValue::Const(0, ix as i64),
HoleValue::Const(1, jx as i64),
HoleValue::Const(2, ptr_a as i64),
HoleValue::Const(3, len_a as i64),
HoleValue::Const(4, ptr_b as i64),
HoleValue::Const(5, len_b as i64),
HoleValue::Const(6, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
ld2_int_stencil(op, false),
&[
HoleValue::Const(0, ix as i64),
HoleValue::Const(1, jx as i64),
HoleValue::Const(2, ptr_a as i64),
HoleValue::Const(3, ptr_b as i64),
HoleValue::Const(4, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrStore { src, idx, ptr_slot, len_slot, byte, narrow32, checked } => {
if checked {
buf.push_stencil(
arrst_stencil(byte, narrow32, true),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, src as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
arrst_stencil(byte, narrow32, false),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(3, src as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrRMW { idx, operand, ptr_slot, len_slot, op, checked } => {
if checked {
buf.push_stencil(
rmw_stencil(op, true),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, operand as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
rmw_stencil(op, false),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(3, operand as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrCondSwap { idx1, idx2, ptr_slot, len_slot, cmp, checked } => {
if checked {
buf.push_stencil(
condswap_stencil(cmp, true),
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
condswap_stencil(cmp, false),
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrSwap { idx1, idx2, ptr_slot, len_slot, checked } => {
if checked {
buf.push_stencil(
&ST_ARRSWAP,
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(deopt_piece)),
],
);
} else {
buf.push_stencil(
&ST_ARRSWAP_U,
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::FmaF { dst, a, b, c } => {
buf.push_stencil(
&ST_FMAF,
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Const(2, c as i64),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, helper_addr, byte: _, narrow32: _ } => {
buf.push_stencil(
&ST_PUSH,
&[
HoleValue::Const(0, src as i64),
HoleValue::Const(1, vec_slot as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Const(4, helper_addr),
HoleValue::Cont(0, next),
],
);
}
ref other => unreachable!("emit_mem_form: unsupported op {other:?} (pinned chains exclude it)"),
}
}
pub fn compile_straightline_pinned(
ops: &[MicroOp],
pins: &[u16],
) -> Result<CompiledChain, JitCompileError> {
compile_straightline_pinned_with(ops, pins, None)
}
pub fn compile_straightline_pinned_with(
ops: &[MicroOp],
pins: &[u16],
shared_status: Option<std::sync::Arc<AtomicI64>>,
) -> Result<CompiledChain, JitCompileError> {
compile_straightline_pinned_float(ops, pins, &[], shared_status)
}
pub fn compile_straightline_pinned_float(
ops: &[MicroOp],
pins: &[u16],
fpins: &[u16],
shared_status: Option<std::sync::Arc<AtomicI64>>,
) -> Result<CompiledChain, JitCompileError> {
if pins.is_empty() && fpins.is_empty() {
return compile_straightline_coded(ops, shared_status, None, 0);
}
assert!(pins.len() <= 4, "at most four threaded GP registers");
assert!(fpins.len() <= 6, "at most six threaded XMM registers (f0..f5)");
debug_assert!(
pins.iter().all(|p| !fpins.contains(p)),
"a slot cannot be both GP- and float-pinned"
);
if ops.is_empty() {
return Err(JitCompileError::Empty);
}
if !matches!(ops.last(), Some(MicroOp::Return { .. }) | Some(MicroOp::Jump { .. })) {
return Err(JitCompileError::FallsOffTheEnd);
}
let loc = |slot: u16| -> usize {
pins.iter().position(|&p| p == slot).map(|i| i + 1).unwrap_or(0)
};
let floc = |slot: u16| -> usize {
fpins.iter().position(|&p| p == slot).map(|i| i + 1).unwrap_or(0)
};
let mem_form_touch = |op: &MicroOp| -> (Vec<u16>, Vec<u16>) {
let mut reads: Vec<u16> = Vec::new();
let mut writes: Vec<u16> = Vec::new();
match *op {
MicroOp::Div { dst, lhs, rhs } | MicroOp::Mod { dst, lhs, rhs } => {
reads.extend([lhs, rhs]);
writes.push(dst);
}
MicroOp::DivPow2 { dst, lhs, .. } | MicroOp::MagicDivU { dst, lhs, .. } => {
reads.push(lhs);
writes.push(dst);
}
MicroOp::DivF { dst, lhs, rhs }
| MicroOp::LtF { dst, lhs, rhs }
| MicroOp::LtEqF { dst, lhs, rhs }
| MicroOp::GtF { dst, lhs, rhs }
| MicroOp::GtEqF { dst, lhs, rhs }
| MicroOp::EqF { dst, lhs, rhs }
| MicroOp::NeqF { dst, lhs, rhs } => {
reads.extend([lhs, rhs]);
writes.push(dst);
}
MicroOp::NotInt { dst, src }
| MicroOp::NotBool { dst, src }
| MicroOp::IntToFloat { dst, src }
| MicroOp::SqrtF { dst, src } => {
reads.push(src);
writes.push(dst);
}
MicroOp::BranchF { lhs, rhs, .. } => reads.extend([lhs, rhs]),
MicroOp::ArrLoad { dst, idx, .. } => {
reads.push(idx);
writes.push(dst);
}
MicroOp::ArrLoadAffine { dst, a, op, b, .. } => {
reads.push(a);
if op != AffOp::None {
reads.push(b);
}
writes.push(dst);
}
MicroOp::ArrLoad2F { dst, i: ix, j: jx, .. } => {
reads.extend([ix, jx]);
writes.push(dst);
}
MicroOp::ArrLoad2 { dst, i: ix, j: jx, .. } => {
reads.extend([ix, jx]);
writes.push(dst);
}
MicroOp::ArrStore { src, idx, .. } => {
reads.extend([src, idx]);
}
MicroOp::ArrRMW { idx, operand, .. } => {
reads.extend([idx, operand]);
}
MicroOp::ArrCondSwap { idx1, idx2, .. } => {
reads.extend([idx1, idx2]);
}
MicroOp::ArrSwap { idx1, idx2, .. } => {
reads.extend([idx1, idx2]);
}
MicroOp::FmaF { dst, a, b, c } => {
reads.extend([a, b, c]);
writes.push(dst);
}
MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, .. } => {
reads.extend([src, vec_slot, ptr_slot, len_slot]);
writes.extend([ptr_slot, len_slot]);
}
MicroOp::MapGet { dst, key, map_slot, .. } => {
reads.extend([key, map_slot]);
writes.push(dst);
}
MicroOp::MapSet { src, key, map_slot, .. } => {
reads.extend([src, key, map_slot]);
}
MicroOp::MapHas { dst, key, map_slot, .. } => {
reads.extend([key, map_slot]);
writes.push(dst);
}
MicroOp::MemMem {
h_ptr_slot,
h_len_slot,
n_ptr_slot,
n_len_slot,
needle_len_slot,
i_slot,
count_slot,
..
} => {
reads.extend([
h_ptr_slot,
h_len_slot,
n_ptr_slot,
n_len_slot,
needle_len_slot,
i_slot,
count_slot,
]);
writes.extend([i_slot, count_slot]);
}
MicroOp::NewList { vec_slot, ptr_slot, len_slot, .. } => {
writes.extend([vec_slot, ptr_slot, len_slot]);
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, .. } => {
reads.push(handle_slot);
writes.extend([vec_slot, ptr_slot, len_slot]);
}
MicroOp::Call { dst, .. } | MicroOp::CallSelf { dst, .. } => {
writes.push(dst);
}
MicroOp::CallSelfCopy { dst, src_start, arg_count, .. } => {
reads.extend((src_start..src_start + arg_count).collect::<Vec<_>>());
writes.push(dst);
}
_ => {}
}
let pinned = |s: u16| loc(s) != 0 || floc(s) != 0;
reads.retain(|&r| pinned(r));
reads.dedup();
writes.retain(|&w| pinned(w));
writes.dedup();
(reads, writes)
};
let has_variant = |op: &MicroOp| -> bool {
matches!(
op,
MicroOp::Add { .. }
| MicroOp::Sub { .. }
| MicroOp::Mul { .. }
| MicroOp::BitAnd { .. }
| MicroOp::BitOr { .. }
| MicroOp::BitXor { .. }
| MicroOp::Shl { .. }
| MicroOp::Shr { .. }
| MicroOp::Lt { .. }
| MicroOp::LtEq { .. }
| MicroOp::Eq { .. }
| MicroOp::Neq { .. }
| MicroOp::Gt { .. }
| MicroOp::GtEq { .. }
| MicroOp::Move { .. }
| MicroOp::LoadConst { .. }
| MicroOp::Return { .. }
| MicroOp::Branch { .. }
| MicroOp::AddF { .. }
| MicroOp::SubF { .. }
| MicroOp::MulF { .. }
| MicroOp::DivF { .. }
| MicroOp::SqrtF { .. }
| MicroOp::IntToFloat { .. }
| MicroOp::BranchF { .. }
)
};
let n_pin = pins.len() + fpins.len();
let mut op_piece: Vec<usize> = Vec::with_capacity(ops.len());
let mut next_piece = n_pin;
for op in ops {
op_piece.push(next_piece);
next_piece += match *op {
MicroOp::Return { .. } => n_pin + 1,
_ if has_variant(op) => 1,
MicroOp::Jump { .. } => 1,
MicroOp::JumpIfFalse { cond, .. } | MicroOp::JumpIfTrue { cond, .. } => {
if loc(cond) != 0 { 2 } else { 1 }
}
_ => {
let (r, w) = mem_form_touch(op);
r.len() + 1 + w.len()
}
};
}
let total_op_pieces = next_piece;
let has_checked = ops.iter().any(|op| {
matches!(
op,
MicroOp::Add { .. }
| MicroOp::Sub { .. }
| MicroOp::Mul { .. }
| MicroOp::Div { .. }
| MicroOp::Mod { .. }
| MicroOp::DivF { .. }
| MicroOp::ArrLoad { .. }
| MicroOp::ArrLoadAffine { checked: true, .. }
| MicroOp::ArrLoad2F { .. }
| MicroOp::ArrLoad2 { checked: true, .. }
| MicroOp::ArrStore { .. }
| MicroOp::ArrRMW { checked: true, .. }
| MicroOp::ArrCondSwap { checked: true, .. }
| MicroOp::ArrSwap { checked: true, .. }
| MicroOp::Call { .. }
| MicroOp::CallSelf { .. }
| MicroOp::CallSelfCopy { .. }
| MicroOp::MapGet { .. }
| MicroOp::MemMem { .. }
)
});
let status: Option<std::sync::Arc<AtomicI64>> = if has_checked {
Some(shared_status.unwrap_or_else(|| std::sync::Arc::new(AtomicI64::new(0))))
} else {
None
};
let deopt_piece = total_op_pieces;
let mut buf = JitBuffer::new();
for (i, &slot) in pins.iter().enumerate() {
buf.push_stencil(
V_MOV[i + 1][0],
&[HoleValue::Const(0, slot as i64), HoleValue::Cont(0, buf.label(i + 1))],
);
}
for (j, &slot) in fpins.iter().enumerate() {
let here = pins.len() + j;
buf.push_stencil(
V_FMOV[j + 1][0],
&[HoleValue::Const(0, slot as i64), HoleValue::Cont(0, buf.label(here + 1))],
);
}
let spill = |buf: &mut JitBuffer, slot: u16, after: usize| {
if floc(slot) != 0 {
buf.push_stencil(
V_FMOV[0][floc(slot)],
&[HoleValue::Const(1, slot as i64), HoleValue::Cont(0, buf.label(after))],
);
} else {
buf.push_stencil(
V_MOV[0][loc(slot)],
&[HoleValue::Const(1, slot as i64), HoleValue::Cont(0, buf.label(after))],
);
}
};
let reload = |buf: &mut JitBuffer, slot: u16, after: usize| {
if floc(slot) != 0 {
buf.push_stencil(
V_FMOV[floc(slot)][0],
&[HoleValue::Const(0, slot as i64), HoleValue::Cont(0, buf.label(after))],
);
} else {
buf.push_stencil(
V_MOV[loc(slot)][0],
&[HoleValue::Const(0, slot as i64), HoleValue::Cont(0, buf.label(after))],
);
}
};
let no_thread = std::env::var_os("LOGOS_NOTHREAD").is_some();
let thread = |start: usize| -> usize {
if no_thread {
return start;
}
let mut idx = start;
for _ in 0..=ops.len() {
match ops.get(idx) {
Some(MicroOp::Jump { target }) => idx = *target,
_ => break,
}
}
idx
};
for (i, op) in ops.iter().enumerate() {
let here = op_piece[i];
debug_assert_eq!(
buf.pieces_pushed(),
here,
"PASS1/PASS2 piece divergence before op {i} ({op:?}); pins={pins:?} fpins={fpins:?}"
);
let next_op = op_piece.get(thread(i + 1)).copied().unwrap_or(total_op_pieces);
let lbl = |t: usize| -> usize { op_piece[thread(t)] };
match *op {
MicroOp::LoadConst { dst, value } => {
let d = loc(dst);
if d == 0 {
buf.push_stencil(
&ST_CONSTST,
&[
HoleValue::Const(0, value),
HoleValue::Const(1, dst as i64),
HoleValue::Cont(0, buf.label(next_op)),
],
);
} else {
buf.push_stencil(
V_CONST[d - 1],
&[HoleValue::Const(0, value), HoleValue::Cont(0, buf.label(next_op))],
);
}
}
MicroOp::Move { dst, src } => {
let (fd, fs) = (floc(dst), floc(src));
if fd != 0 || fs != 0 {
let mut holes: Vec<HoleValue> = Vec::new();
if fs == 0 {
holes.push(HoleValue::Const(0, src as i64));
}
if fd == 0 {
holes.push(HoleValue::Const(1, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
buf.push_stencil(V_FMOV[fd][fs], &holes);
} else {
let (d, sl) = (loc(dst), loc(src));
if d == 0 && sl == 0 {
buf.push_stencil(
&ST_MOVSS,
&[
HoleValue::Const(0, src as i64),
HoleValue::Const(1, dst as i64),
HoleValue::Cont(0, buf.label(next_op)),
],
);
} else {
let mut holes: Vec<HoleValue> = Vec::new();
if sl == 0 {
holes.push(HoleValue::Const(0, src as i64));
}
if d == 0 {
holes.push(HoleValue::Const(1, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
buf.push_stencil(V_MOV[d][sl], &holes);
}
}
}
MicroOp::Add { dst, lhs, rhs }
| MicroOp::Sub { dst, lhs, rhs }
| MicroOp::Mul { 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::Lt { dst, lhs, rhs }
| MicroOp::LtEq { dst, lhs, rhs }
| MicroOp::Eq { dst, lhs, rhs }
| MicroOp::Neq { dst, lhs, rhs }
| MicroOp::Gt { dst, lhs, rhs }
| MicroOp::GtEq { dst, lhs, rhs } => {
let (fam, a, b) = match *op {
MicroOp::Add { .. } => (0usize, lhs, rhs),
MicroOp::Sub { .. } => (1, lhs, rhs),
MicroOp::Mul { .. } => (2, lhs, rhs),
MicroOp::BitAnd { .. } => (3, lhs, rhs),
MicroOp::BitOr { .. } => (4, lhs, rhs),
MicroOp::BitXor { .. } => (5, lhs, rhs),
MicroOp::Shl { .. } => (6, lhs, rhs),
MicroOp::Shr { .. } => (7, lhs, rhs),
MicroOp::Lt { .. } => (8, lhs, rhs),
MicroOp::Gt { .. } => (8, rhs, lhs),
MicroOp::LtEq { .. } => (9, lhs, rhs),
MicroOp::GtEq { .. } => (9, rhs, lhs),
MicroOp::Eq { .. } => (10, lhs, rhs),
MicroOp::Neq { .. } => (11, lhs, rhs),
_ => unreachable!(),
};
let (d, l, r) = (loc(dst), loc(a), loc(b));
let mut holes: Vec<HoleValue> = Vec::new();
if l == 0 {
holes.push(HoleValue::Const(0, a as i64));
}
if r == 0 {
holes.push(HoleValue::Const(1, b as i64));
}
if d == 0 {
holes.push(HoleValue::Const(2, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
if fam <= 2 {
holes.push(HoleValue::Cont(1, buf.label(deopt_piece)));
}
buf.push_stencil(V_BINOP[fam][d][l][r], &holes);
}
MicroOp::AddF { dst, lhs, rhs }
| MicroOp::SubF { dst, lhs, rhs }
| MicroOp::MulF { dst, lhs, rhs } => {
let fam = match *op {
MicroOp::AddF { .. } => 0usize,
MicroOp::SubF { .. } => 1,
MicroOp::MulF { .. } => 2,
_ => unreachable!(),
};
let (d, l, r) = (floc(dst), floc(lhs), floc(rhs));
let mut holes: Vec<HoleValue> = Vec::new();
if l == 0 {
holes.push(HoleValue::Const(0, lhs as i64));
}
if r == 0 {
holes.push(HoleValue::Const(1, rhs as i64));
}
if d == 0 {
holes.push(HoleValue::Const(2, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
buf.push_stencil(V_FBINOP[fam][d][l][r], &holes);
}
MicroOp::SqrtF { dst, src } => {
let (d, s) = (floc(dst), floc(src));
let mut holes: Vec<HoleValue> = Vec::new();
if s == 0 {
holes.push(HoleValue::Const(0, src as i64));
}
if d == 0 {
holes.push(HoleValue::Const(1, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
buf.push_stencil(V_SQRTF[d][s], &holes);
}
MicroOp::DivF { dst, lhs, rhs } => {
let (d, l, r) = (floc(dst), floc(lhs), floc(rhs));
let mut holes: Vec<HoleValue> = Vec::new();
if l == 0 {
holes.push(HoleValue::Const(0, lhs as i64));
}
if r == 0 {
holes.push(HoleValue::Const(1, rhs as i64));
}
if d == 0 {
holes.push(HoleValue::Const(2, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
holes.push(HoleValue::Cont(1, buf.label(deopt_piece)));
buf.push_stencil(V_DIVF[d][l][r], &holes);
}
MicroOp::IntToFloat { dst, src } => {
let (d, s) = (floc(dst), loc(src));
let mut holes: Vec<HoleValue> = Vec::new();
if s == 0 {
holes.push(HoleValue::Const(0, src as i64));
}
if d == 0 {
holes.push(HoleValue::Const(1, dst as i64));
}
holes.push(HoleValue::Cont(0, buf.label(next_op)));
buf.push_stencil(V_I2F[d][s], &holes);
}
MicroOp::BranchF { cmp, lhs, rhs, target } => {
let t = buf.label(lbl(target));
let n = buf.label(next_op);
let (fam, a, b) = match cmp {
Cmp::Lt => (0usize, lhs, rhs),
Cmp::Gt => (0, rhs, lhs),
Cmp::LtEq => (1, lhs, rhs),
Cmp::GtEq => (1, rhs, lhs),
Cmp::Eq => (2, lhs, rhs),
Cmp::NotEq => (2, lhs, rhs),
};
let (c0, c1) = if cmp == Cmp::NotEq { (t, n) } else { (n, t) };
let (l, r) = (floc(a), floc(b));
let mut holes: Vec<HoleValue> = Vec::new();
if l == 0 {
holes.push(HoleValue::Const(0, a as i64));
}
if r == 0 {
holes.push(HoleValue::Const(1, b as i64));
}
holes.push(HoleValue::Cont(0, c0));
holes.push(HoleValue::Cont(1, c1));
buf.push_stencil(V_BRANCHF[fam][l][r], &holes);
}
MicroOp::Branch { cmp, lhs, rhs, target } => {
let t = buf.label(lbl(target));
let n = buf.label(next_op);
let (fam, a, b, c_true, c_false) = match cmp {
Cmp::Lt => (0usize, lhs, rhs, n, t),
Cmp::GtEq => (0, lhs, rhs, t, n),
Cmp::Gt => (0, rhs, lhs, n, t),
Cmp::LtEq => (0, rhs, lhs, t, n),
Cmp::Eq => (2, lhs, rhs, n, t),
Cmp::NotEq => (2, lhs, rhs, t, n),
};
let (l, r) = (loc(a), loc(b));
let mut holes: Vec<HoleValue> = Vec::new();
if l == 0 {
holes.push(HoleValue::Const(0, a as i64));
}
if r == 0 {
holes.push(HoleValue::Const(1, b as i64));
}
holes.push(HoleValue::Cont(0, c_true));
holes.push(HoleValue::Cont(1, c_false));
buf.push_stencil(V_BRANCH[fam][l][r], &holes);
}
MicroOp::Jump { target } => {
buf.push_stencil(&ST_JUMP, &[HoleValue::Cont(0, buf.label(lbl(target)))]);
}
MicroOp::JumpIfFalse { cond, target } | MicroOp::JumpIfTrue { cond, target } => {
let c = loc(cond);
let mut brz_piece = here;
if c != 0 {
spill(&mut buf, cond, here + 1);
brz_piece = here + 1;
}
let _ = brz_piece;
let (c0, c1) = if matches!(op, MicroOp::JumpIfFalse { .. }) {
(buf.label(next_op), buf.label(lbl(target)))
} else {
(buf.label(lbl(target)), buf.label(next_op))
};
buf.push_stencil(
&ST_BRZ,
&[HoleValue::Const(0, cond as i64), HoleValue::Cont(0, c0), HoleValue::Cont(1, c1)],
);
}
MicroOp::Return { src } => {
let mut cursor = here;
for &slot in pins.iter().chain(fpins.iter()) {
spill(&mut buf, slot, cursor + 1);
cursor += 1;
}
let sl = loc(src);
if sl == 0 {
buf.push_stencil(&ST_RET2, &[HoleValue::Const(0, src as i64)]);
} else {
buf.push_stencil(V_RET[sl - 1], &[]);
}
}
ref other => {
let (reads, writes) = mem_form_touch(other);
let mut cursor = here;
for &slot in &reads {
spill(&mut buf, slot, cursor + 1);
cursor += 1;
}
let after_op = cursor + 1;
let next_for_op = if writes.is_empty() { next_op } else { after_op };
let next_label = buf.label(next_for_op);
let rptr = |buf: &mut JitBuffer, table: &[&'static crate::Stencil; 5],
n: usize, idx: u16, vslot: u16, len_slot: u16, checked: bool| {
let mut holes = vec![
HoleValue::Const(0, idx as i64),
HoleValue::Const(3, vslot as i64),
HoleValue::Cont(0, next_label),
];
if checked {
holes.push(HoleValue::Const(2, len_slot as i64));
holes.push(HoleValue::Cont(1, buf.label(deopt_piece)));
}
buf.push_stencil(table[n], &holes);
};
let used_rptr = match *other {
MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, byte: false, narrow32: false, checked }
if loc(ptr_slot) != 0 =>
{
let n = loc(ptr_slot);
let t = if checked { &V_ARRLD_RPTR_C } else { &V_ARRLD_RPTR };
rptr(&mut buf, t, n, idx, dst, len_slot, checked);
true
}
MicroOp::ArrStore { src, idx, ptr_slot, len_slot, byte: false, narrow32: false, checked }
if loc(ptr_slot) != 0 =>
{
let n = loc(ptr_slot);
let t = if checked { &V_ARRST_RPTR_C } else { &V_ARRST_RPTR };
rptr(&mut buf, t, n, idx, src, len_slot, checked);
true
}
_ => false,
};
if !used_rptr {
emit_mem_form(&mut buf, other, next_label, deopt_piece, &status);
}
cursor += 1;
for (k, &slot) in writes.iter().enumerate() {
let after = if k + 1 == writes.len() { next_op } else { cursor + 1 };
reload(&mut buf, slot, after);
cursor += 1;
}
}
}
}
if let Some(cell) = &status {
let addr = cell.as_ref() as *const AtomicI64 as i64;
buf.push_stencil(&ST_DEOPT, &[HoleValue::Const(0, addr)]);
}
let chain = buf.finish().map_err(|e| JitCompileError::Assembly(e.to_string()))?;
Ok(CompiledChain { chain, status, keepalive: Vec::new() })
}
pub fn compile_straightline(ops: &[MicroOp]) -> Result<CompiledChain, JitCompileError> {
compile_straightline_with(ops, None)
}
pub fn compile_straightline_with(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
) -> Result<CompiledChain, JitCompileError> {
compile_straightline_coded(ops, shared_status, None, 0)
}
pub fn compile_straightline_coded(
ops: &[MicroOp],
shared_status: Option<std::sync::Arc<AtomicI64>>,
deopt_codes: Option<&[i64]>,
depth_addr: i64,
) -> Result<CompiledChain, JitCompileError> {
if ops.is_empty() {
return Err(JitCompileError::Empty);
}
if !matches!(ops.last(), Some(MicroOp::Return { .. }) | Some(MicroOp::Jump { .. })) {
return Err(JitCompileError::FallsOffTheEnd);
}
for (i, op) in ops.iter().enumerate() {
match *op {
MicroOp::Jump { target }
| MicroOp::JumpIfFalse { target, .. }
| MicroOp::JumpIfTrue { target, .. }
| MicroOp::Branch { target, .. }
| MicroOp::BranchF { target, .. } => {
if target >= ops.len() {
return Err(JitCompileError::BadJumpTarget { op_index: i, target });
}
}
_ => {}
}
}
let has_checked = ops.iter().any(|op| {
matches!(
op,
MicroOp::Add { .. }
| MicroOp::Sub { .. }
| MicroOp::Mul { .. }
| MicroOp::Div { .. }
| MicroOp::Mod { .. }
| MicroOp::DivF { .. }
| MicroOp::ArrLoad { .. }
| MicroOp::ArrLoadAffine { checked: true, .. }
| MicroOp::ArrLoad2F { .. }
| MicroOp::ArrLoad2 { checked: true, .. }
| MicroOp::ArrStore { .. }
| MicroOp::ArrRMW { checked: true, .. }
| MicroOp::ArrCondSwap { checked: true, .. }
| MicroOp::ArrSwap { checked: true, .. }
| MicroOp::Call { .. }
| MicroOp::CallSelf { .. }
| MicroOp::CallSelfCopy { .. }
| MicroOp::MapGet { .. }
| MicroOp::MemMem { .. }
)
});
let status: Option<std::sync::Arc<AtomicI64>> = if has_checked {
Some(shared_status.unwrap_or_else(|| std::sync::Arc::new(AtomicI64::new(0))))
} else {
None
};
let deopt_piece = ops.len();
let mut code_pieces: Vec<i64> = Vec::new();
if let Some(codes) = deopt_codes {
debug_assert_eq!(codes.len(), ops.len());
for (i, op) in ops.iter().enumerate() {
let coded = matches!(
op,
MicroOp::Add { .. }
| MicroOp::Sub { .. }
| MicroOp::Mul { .. }
| MicroOp::Div { .. }
| MicroOp::Mod { .. }
| MicroOp::DivF { .. }
| MicroOp::ArrLoad { .. }
| MicroOp::ArrLoadAffine { checked: true, .. }
| MicroOp::ArrLoad2F { .. }
| MicroOp::ArrLoad2 { checked: true, .. }
| MicroOp::ArrStore { .. }
| MicroOp::MapGet { .. }
);
if coded && codes[i] != 1 && !code_pieces.contains(&codes[i]) {
code_pieces.push(codes[i]);
}
}
}
let piece_for = |code: i64| -> usize {
if code == 1 {
deopt_piece
} else {
deopt_piece + 1 + code_pieces.iter().position(|&c| c == code).unwrap()
}
};
let code_at = |i: usize| -> i64 { deopt_codes.map(|c| c[i]).unwrap_or(1) };
let mut buf = JitBuffer::new();
for (i, op) in ops.iter().enumerate() {
let next = buf.label(i + 1);
match *op {
MicroOp::LoadConst { dst, value } => {
buf.push_stencil(
&ST_CONSTST,
&[
HoleValue::Const(0, value),
HoleValue::Const(1, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::Move { dst, src } => {
buf.push_stencil(
&ST_MOVSS,
&[
HoleValue::Const(0, src as i64),
HoleValue::Const(1, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::Add { dst, lhs, rhs }
| MicroOp::Sub { dst, lhs, rhs }
| MicroOp::Mul { dst, lhs, rhs }
| MicroOp::Lt { dst, lhs, rhs }
| MicroOp::LtEq { dst, lhs, rhs }
| MicroOp::Eq { dst, lhs, rhs }
| MicroOp::Neq { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::Add { .. } => &ST_ADD3,
MicroOp::Sub { .. } => &ST_SUB3,
MicroOp::Mul { .. } => &ST_MUL3,
MicroOp::Lt { .. } => &ST_LT3,
MicroOp::LtEq { .. } => &ST_LE3,
MicroOp::Eq { .. } => &ST_EQ3,
MicroOp::Neq { .. } => &ST_NE3,
_ => unreachable!(),
};
let mut holes = vec![
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
];
if matches!(op, MicroOp::Add { .. } | MicroOp::Sub { .. } | MicroOp::Mul { .. }) {
holes.push(HoleValue::Cont(1, buf.label(piece_for(code_at(i)))));
}
buf.push_stencil(stencil, &holes);
}
MicroOp::Gt { dst, lhs, rhs }
| MicroOp::GtEq { dst, lhs, rhs }
| MicroOp::GtF { dst, lhs, rhs }
| MicroOp::GtEqF { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::Gt { .. } => &ST_LT3,
MicroOp::GtEq { .. } => &ST_LE3,
MicroOp::GtF { .. } => &ST_LTF3,
_ => &ST_LEF3,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, rhs as i64),
HoleValue::Const(1, lhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
],
);
}
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::AddF { dst, lhs, rhs }
| MicroOp::SubF { dst, lhs, rhs }
| MicroOp::MulF { dst, lhs, rhs }
| MicroOp::LtF { dst, lhs, rhs }
| MicroOp::LtEqF { dst, lhs, rhs }
| MicroOp::EqF { dst, lhs, rhs }
| MicroOp::NeqF { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::BitAnd { .. } => &ST_AND3,
MicroOp::BitOr { .. } => &ST_OR3,
MicroOp::BitXor { .. } => &ST_XOR3,
MicroOp::Shl { .. } => &ST_SHL3,
MicroOp::Shr { .. } => &ST_SHR3,
MicroOp::AddF { .. } => &ST_ADDF3,
MicroOp::SubF { .. } => &ST_SUBF3,
MicroOp::MulF { .. } => &ST_MULF3,
MicroOp::LtF { .. } => &ST_LTF3,
MicroOp::LtEqF { .. } => &ST_LEF3,
MicroOp::EqF { .. } => &ST_EQF3,
_ => &ST_NEF3,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::DivF { dst, lhs, rhs } => {
buf.push_stencil(
&ST_DIVF3C,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
}
MicroOp::Call {
dst,
args_start,
table_addr,
depth_addr,
status_addr,
limit_slot,
depth_limit,
} => {
let code = code_at(i);
if code != 1 {
debug_assert_eq!(depth_limit, BAKED_CALL_DEPTH);
buf.push_stencil(
&ST_CALL_PRECISE,
&[
HoleValue::Const(0, table_addr),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, code),
HoleValue::Cont(0, next),
],
);
} else {
buf.push_stencil(
&ST_CALL,
&[
HoleValue::Const(0, table_addr),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, depth_limit),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::CallSelf { dst, args_start, depth_addr, status_addr, limit_slot, frame_size } => {
buf.push_stencil(
&ST_CALL_SELF,
&[
HoleValue::Const(0, 0),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, frame_size),
HoleValue::Cont(0, next),
],
);
buf.mark_patch_hole(0);
}
MicroOp::CallSelfCopy {
dst,
args_start,
src_start,
arg_count,
depth_addr,
status_addr,
limit_slot,
frame_size,
} => {
buf.push_stencil(
&ST_CALL_SELF_COPY,
&[
HoleValue::Const(0, 0),
HoleValue::Const(1, args_start as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, depth_addr),
HoleValue::Const(4, status_addr),
HoleValue::Const(5, limit_slot as i64),
HoleValue::Const(6, frame_size),
HoleValue::Const(7, src_start as i64),
HoleValue::Const(8, arg_count as i64),
HoleValue::Cont(0, next),
],
);
buf.mark_patch_hole(0);
}
MicroOp::NewList { vec_slot, ptr_slot, len_slot, helper_addr } => {
buf.push_stencil(
&ST_ALLOCLIST,
&[
HoleValue::Const(0, vec_slot as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ListTriple { handle_slot, vec_slot, ptr_slot, len_slot, helper_addr } => {
buf.push_stencil(
&ST_LISTTRIPLE,
&[
HoleValue::Const(0, handle_slot as i64),
HoleValue::Const(1, vec_slot as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Const(4, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::MapGet { dst, key, map_slot, helper_addr } => {
buf.push_stencil(
&ST_MAPHGET,
&[
HoleValue::Const(0, map_slot as i64),
HoleValue::Const(1, key as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
}
MicroOp::MapSet { src, key, map_slot, helper_addr } => {
buf.push_stencil(
&ST_MAPHSET,
&[
HoleValue::Const(0, map_slot as i64),
HoleValue::Const(1, key as i64),
HoleValue::Const(2, src as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::MemMem {
h_ptr_slot,
h_len_slot,
n_ptr_slot,
n_len_slot,
needle_len_slot,
i_slot,
count_slot,
helper_addr,
} => {
buf.push_stencil(
&ST_MEMMEM,
&[
HoleValue::Const(0, h_ptr_slot as i64),
HoleValue::Const(1, h_len_slot as i64),
HoleValue::Const(2, n_ptr_slot as i64),
HoleValue::Const(3, n_len_slot as i64),
HoleValue::Const(4, needle_len_slot as i64),
HoleValue::Const(5, i_slot as i64),
HoleValue::Const(6, count_slot as i64),
HoleValue::Const(7, helper_addr),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
}
MicroOp::MapHas { dst, key, map_slot, helper_addr } => {
buf.push_stencil(
&ST_MAPHHAS,
&[
HoleValue::Const(0, map_slot as i64),
HoleValue::Const(1, key as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ArrPush { src, vec_slot, ptr_slot, len_slot, helper_addr, byte: _, narrow32: _ } => {
buf.push_stencil(
&ST_PUSH,
&[
HoleValue::Const(0, src as i64),
HoleValue::Const(1, vec_slot as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Const(4, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ListClear { vec_slot, ptr_slot, len_slot, helper_addr } => {
buf.push_stencil(
&ST_LIST_CLEAR,
&[
HoleValue::Const(0, vec_slot as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, helper_addr),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, byte, narrow32, checked } => {
if checked {
buf.push_stencil(
arrld_stencil(byte, narrow32, true),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
arrld_stencil(byte, narrow32, false),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrLoadAffine { dst, a, op, b, const_offset, ptr_slot, len_slot, checked } => {
if checked {
buf.push_stencil(
affine_stencil(op, true),
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Const(2, const_offset),
HoleValue::Const(3, ptr_slot as i64),
HoleValue::Const(4, len_slot as i64),
HoleValue::Const(5, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
affine_stencil(op, false),
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Const(2, const_offset),
HoleValue::Const(3, ptr_slot as i64),
HoleValue::Const(5, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrStore { src, idx, ptr_slot, len_slot, byte, narrow32, checked } => {
if checked {
buf.push_stencil(
arrst_stencil(byte, narrow32, true),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, src as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
arrst_stencil(byte, narrow32, false),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(3, src as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrRMW { idx, operand, ptr_slot, len_slot, op, checked } => {
if checked {
buf.push_stencil(
rmw_stencil(op, true),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(2, len_slot as i64),
HoleValue::Const(3, operand as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
rmw_stencil(op, false),
&[
HoleValue::Const(0, idx as i64),
HoleValue::Const(1, ptr_slot as i64),
HoleValue::Const(3, operand as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrCondSwap { idx1, idx2, ptr_slot, len_slot, cmp, checked } => {
if checked {
buf.push_stencil(
condswap_stencil(cmp, true),
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
condswap_stencil(cmp, false),
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::ArrSwap { idx1, idx2, ptr_slot, len_slot, checked } => {
if checked {
buf.push_stencil(
&ST_ARRSWAP,
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
&ST_ARRSWAP_U,
&[
HoleValue::Const(0, idx1 as i64),
HoleValue::Const(1, idx2 as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::FmaF { dst, a, b, c } => {
buf.push_stencil(
&ST_FMAF,
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Const(2, c as i64),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::ArrLoad2F { dst, i: ix, j: jx, ptr_slot, len_slot, op } => {
let stencil = match op {
FOp::Add => &ST_ARRLD2_ADDF,
FOp::Sub => &ST_ARRLD2_SUBF,
FOp::Mul => &ST_ARRLD2_MULF,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, ix as i64),
HoleValue::Const(1, jx as i64),
HoleValue::Const(2, ptr_slot as i64),
HoleValue::Const(3, len_slot as i64),
HoleValue::Const(4, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
}
MicroOp::ArrLoad2 { dst, i: ix, j: jx, ptr_a, len_a, ptr_b, len_b, op, checked } => {
if checked {
buf.push_stencil(
ld2_int_stencil(op, true),
&[
HoleValue::Const(0, ix as i64),
HoleValue::Const(1, jx as i64),
HoleValue::Const(2, ptr_a as i64),
HoleValue::Const(3, len_a as i64),
HoleValue::Const(4, ptr_b as i64),
HoleValue::Const(5, len_b as i64),
HoleValue::Const(6, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
} else {
buf.push_stencil(
ld2_int_stencil(op, false),
&[
HoleValue::Const(0, ix as i64),
HoleValue::Const(1, jx as i64),
HoleValue::Const(2, ptr_a as i64),
HoleValue::Const(3, ptr_b as i64),
HoleValue::Const(4, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
MicroOp::NotInt { dst, src } | MicroOp::NotBool { dst, src }
| MicroOp::IntToFloat { dst, src } | MicroOp::SqrtF { dst, src } => {
let stencil = match op {
MicroOp::NotInt { .. } => &ST_NOTI2,
MicroOp::NotBool { .. } => &ST_NOTB2,
MicroOp::SqrtF { .. } => &ST_SQRTF2,
_ => &ST_I2F2,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, src as i64),
HoleValue::Const(1, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::BranchF { cmp, lhs, rhs, target } => {
let t = buf.label(target);
let (stencil, a, b) = match cmp {
Cmp::Lt => (&ST_BRLTF, lhs, rhs),
Cmp::Gt => (&ST_BRLTF, rhs, lhs),
Cmp::LtEq => (&ST_BRLEF, lhs, rhs),
Cmp::GtEq => (&ST_BRLEF, rhs, lhs),
Cmp::Eq => (&ST_BREQF, lhs, rhs),
Cmp::NotEq => (&ST_BREQF, lhs, rhs),
};
let (c0, c1) = if cmp == Cmp::NotEq {
(t, next)
} else {
(next, t)
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Cont(0, c0),
HoleValue::Cont(1, c1),
],
);
}
MicroOp::Div { dst, lhs, rhs } | MicroOp::Mod { dst, lhs, rhs } => {
let stencil = match op {
MicroOp::Div { .. } => &ST_DIV3C,
_ => &ST_MOD3C,
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, rhs as i64),
HoleValue::Const(2, dst as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(piece_for(code_at(i)))),
],
);
}
MicroOp::DivPow2 { dst, lhs, k } => {
buf.push_stencil(
&ST_DIVPOW2,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, k as i64),
HoleValue::Const(2, (1i64 << k) - 1),
HoleValue::Const(3, dst as i64),
HoleValue::Cont(0, next),
],
);
}
MicroOp::Jump { target } => {
buf.push_stencil(&ST_JUMP, &[HoleValue::Cont(0, buf.label(target))]);
}
MicroOp::JumpIfFalse { cond, target } => {
buf.push_stencil(
&ST_BRZ,
&[
HoleValue::Const(0, cond as i64),
HoleValue::Cont(0, next),
HoleValue::Cont(1, buf.label(target)),
],
);
}
MicroOp::JumpIfTrue { cond, target } => {
buf.push_stencil(
&ST_BRZ,
&[
HoleValue::Const(0, cond as i64),
HoleValue::Cont(0, buf.label(target)),
HoleValue::Cont(1, next),
],
);
}
MicroOp::Branch { cmp, lhs, rhs, target } => {
let t = buf.label(target);
let (stencil, a, b, c0, c1) = match cmp {
Cmp::Lt => (&ST_BRLT, lhs, rhs, next, t),
Cmp::GtEq => (&ST_BRLT, lhs, rhs, t, next),
Cmp::Gt => (&ST_BRLT, rhs, lhs, next, t),
Cmp::LtEq => (&ST_BRLT, rhs, lhs, t, next),
Cmp::Eq => (&ST_BREQ, lhs, rhs, next, t),
Cmp::NotEq => (&ST_BREQ, lhs, rhs, t, next),
};
buf.push_stencil(
stencil,
&[
HoleValue::Const(0, a as i64),
HoleValue::Const(1, b as i64),
HoleValue::Cont(0, c0),
HoleValue::Cont(1, c1),
],
);
}
MicroOp::Return { src } => {
buf.push_stencil(&ST_RET2, &[HoleValue::Const(0, src as i64)]);
}
MicroOp::StrAppend { .. } => {
return Err(JitCompileError::Unsupported("StrAppend"));
}
MicroOp::MagicDivU { dst, lhs, magic, more, mul_back } => {
buf.push_stencil(
&ST_MAGICDIV,
&[
HoleValue::Const(0, lhs as i64),
HoleValue::Const(1, magic as i64),
HoleValue::Const(2, more as i64),
HoleValue::Const(3, mul_back),
HoleValue::Const(4, dst as i64),
HoleValue::Cont(0, next),
],
);
}
}
}
if let Some(cell) = &status {
let addr = cell.as_ref() as *const AtomicI64 as i64;
buf.push_stencil(&ST_DEOPT, &[HoleValue::Const(0, addr)]);
for &code in &code_pieces {
buf.push_stencil(
&ST_DEOPT_AT,
&[
HoleValue::Const(0, addr),
HoleValue::Const(1, code),
HoleValue::Const(2, depth_addr),
],
);
}
}
let chain = buf.finish().map_err(|e| JitCompileError::Assembly(e.to_string()))?;
Ok(CompiledChain { chain, status, keepalive: Vec::new() })
}
#[inline]
pub fn magic_eval(x: i64, magic: u64, more: u8, mul_back: i64) -> i64 {
const SHIFT_MASK: u8 = 0x3F;
const ADD_MARKER: u8 = 0x40;
const SHIFT_PATH: u8 = 0x80;
let n = x as u64;
let q = if more & SHIFT_PATH != 0 {
n >> (more & SHIFT_MASK)
} else {
let hi = (((magic as u128) * (n as u128)) >> 64) as u64;
if more & ADD_MARKER != 0 {
let t = (n.wrapping_sub(hi) >> 1).wrapping_add(hi);
t >> (more & SHIFT_MASK)
} else {
hi >> (more & SHIFT_MASK)
}
};
if mul_back == 0 {
q as i64
} else {
x.wrapping_sub((q as i64).wrapping_mul(mul_back))
}
}
pub fn reference_eval(ops: &[MicroOp], frame: &mut [i64], mut fuel: u64) -> Option<i64> {
let mut pc = 0usize;
while pc < ops.len() {
if fuel == 0 {
return None;
}
fuel -= 1;
match ops[pc] {
MicroOp::LoadConst { dst, value } => frame[dst as usize] = value,
MicroOp::Move { dst, src } => frame[dst as usize] = frame[src as usize],
MicroOp::Add { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize].checked_add(frame[rhs as usize])?
}
MicroOp::Sub { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize].checked_sub(frame[rhs as usize])?
}
MicroOp::Mul { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize].checked_mul(frame[rhs as usize])?
}
MicroOp::Div { dst, lhs, rhs } => {
let b = frame[rhs as usize];
if b == 0 || (b == -1 && frame[lhs as usize] == i64::MIN) {
return None;
}
frame[dst as usize] = frame[lhs as usize].wrapping_div(b)
}
MicroOp::DivPow2 { dst, lhs, k } => {
let x = frame[lhs as usize];
let mask = (1i64 << k) - 1;
frame[dst as usize] = x.wrapping_add((x >> 63) & mask) >> k;
}
MicroOp::MagicDivU { dst, lhs, magic, more, mul_back } => {
frame[dst as usize] = magic_eval(frame[lhs as usize], magic, more, mul_back);
}
MicroOp::Mod { dst, lhs, rhs } => {
let b = frame[rhs as usize];
if b == 0 {
return None;
}
frame[dst as usize] = frame[lhs as usize].wrapping_rem(b)
}
MicroOp::Lt { dst, lhs, rhs } => {
frame[dst as usize] = (frame[lhs as usize] < frame[rhs as usize]) as i64
}
MicroOp::Gt { dst, lhs, rhs } => {
frame[dst as usize] = (frame[lhs as usize] > frame[rhs as usize]) as i64
}
MicroOp::LtEq { dst, lhs, rhs } => {
frame[dst as usize] = (frame[lhs as usize] <= frame[rhs as usize]) as i64
}
MicroOp::GtEq { dst, lhs, rhs } => {
frame[dst as usize] = (frame[lhs as usize] >= frame[rhs as usize]) as i64
}
MicroOp::Eq { dst, lhs, rhs } => {
frame[dst as usize] = (frame[lhs as usize] == frame[rhs as usize]) as i64
}
MicroOp::Neq { dst, lhs, rhs } => {
frame[dst as usize] = (frame[lhs as usize] != frame[rhs as usize]) as i64
}
MicroOp::BitAnd { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize] & frame[rhs as usize]
}
MicroOp::BitOr { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize] | frame[rhs as usize]
}
MicroOp::BitXor { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize] ^ frame[rhs as usize]
}
MicroOp::Shl { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize].wrapping_shl(frame[rhs as usize] as u32)
}
MicroOp::Shr { dst, lhs, rhs } => {
frame[dst as usize] = frame[lhs as usize].wrapping_shr(frame[rhs as usize] as u32)
}
MicroOp::NotInt { dst, src } => frame[dst as usize] = !frame[src as usize],
MicroOp::NotBool { dst, src } => frame[dst as usize] = frame[src as usize] ^ 1,
MicroOp::AddF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a + b).to_bits() as i64
}
MicroOp::SubF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a - b).to_bits() as i64
}
MicroOp::MulF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a * b).to_bits() as i64
}
MicroOp::DivF { dst, lhs, rhs } => {
let b = f64::from_bits(frame[rhs as usize] as u64);
if b == 0.0 {
return None;
}
let a = f64::from_bits(frame[lhs as usize] as u64);
frame[dst as usize] = (a / b).to_bits() as i64
}
MicroOp::LtF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a < b) as i64
}
MicroOp::GtF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a > b) as i64
}
MicroOp::LtEqF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a <= b) as i64
}
MicroOp::GtEqF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a >= b) as i64
}
MicroOp::EqF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a == b) as i64
}
MicroOp::NeqF { dst, lhs, rhs } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
frame[dst as usize] = (a != b) as i64
}
MicroOp::BranchF { cmp, lhs, rhs, target } => {
let a = f64::from_bits(frame[lhs as usize] as u64);
let b = f64::from_bits(frame[rhs as usize] as u64);
let truth = match cmp {
Cmp::Lt => a < b,
Cmp::Gt => a > b,
Cmp::LtEq => a <= b,
Cmp::GtEq => a >= b,
Cmp::Eq => a == b,
Cmp::NotEq => a != b,
};
if !truth {
pc = target;
continue;
}
}
MicroOp::IntToFloat { dst, src } => {
frame[dst as usize] = (frame[src as usize] as f64).to_bits() as i64
}
MicroOp::SqrtF { dst, src } => {
frame[dst as usize] =
f64::from_bits(frame[src as usize] as u64).sqrt().to_bits() as i64
}
MicroOp::Call { .. }
| MicroOp::CallSelf { .. }
| MicroOp::CallSelfCopy { .. }
| MicroOp::ArrPush { .. }
| MicroOp::ListClear { .. }
| MicroOp::StrAppend { .. }
| MicroOp::MapGet { .. }
| MicroOp::MapSet { .. }
| MicroOp::MapHas { .. }
| MicroOp::NewList { .. }
| MicroOp::ListTriple { .. } => return None,
MicroOp::ArrLoad { dst, idx, ptr_slot, len_slot, byte, narrow32, checked } => {
let i = frame[idx as usize];
let len = frame[len_slot as usize];
let im1 = i.wrapping_sub(1);
let _ = checked;
if (im1 as u64) >= (len as u64) {
return None;
}
frame[dst as usize] = unsafe {
if byte {
*(frame[ptr_slot as usize] as *const u8).add(im1 as usize) as i64
} else if narrow32 {
*(frame[ptr_slot as usize] as *const i32).add(im1 as usize) as i64
} else {
*(frame[ptr_slot as usize] as *const i64).add(im1 as usize)
}
};
}
MicroOp::ArrLoadAffine { dst, a, op, b, const_offset, ptr_slot, len_slot, checked } => {
let _ = checked;
let idx = op.eval(frame[a as usize], frame[b as usize], const_offset);
let len = frame[len_slot as usize];
let im1 = idx.wrapping_sub(1);
if (im1 as u64) >= (len as u64) {
return None;
}
frame[dst as usize] =
unsafe { *(frame[ptr_slot as usize] as *const i64).add(im1 as usize) };
}
MicroOp::ArrLoad2F { dst, i: ix, j: jx, ptr_slot, len_slot, op } => {
let len = frame[len_slot as usize];
let im1 = frame[ix as usize].wrapping_sub(1);
let jm1 = frame[jx as usize].wrapping_sub(1);
if (im1 as u64) >= (len as u64) || (jm1 as u64) >= (len as u64) {
return None;
}
let (a, b) = unsafe {
let ptr = frame[ptr_slot as usize] as *const i64;
(
f64::from_bits(*ptr.add(im1 as usize) as u64),
f64::from_bits(*ptr.add(jm1 as usize) as u64),
)
};
frame[dst as usize] = op.eval(a, b).to_bits() as i64;
}
MicroOp::ArrLoad2 { dst, i: ix, j: jx, ptr_a, len_a, ptr_b, len_b, op, checked } => {
let _ = checked;
let lena = frame[len_a as usize];
let lenb = frame[len_b as usize];
let im1 = frame[ix as usize].wrapping_sub(1);
let jm1 = frame[jx as usize].wrapping_sub(1);
if (im1 as u64) >= (lena as u64) || (jm1 as u64) >= (lenb as u64) {
return None;
}
let (a, b) = unsafe {
let pa = frame[ptr_a as usize] as *const i64;
let pb = frame[ptr_b as usize] as *const i64;
(*pa.add(im1 as usize), *pb.add(jm1 as usize))
};
frame[dst as usize] = op.eval(a, b);
}
MicroOp::ArrStore { src, idx, ptr_slot, len_slot, byte, narrow32, checked } => {
let _ = checked;
let i = frame[idx as usize];
let len = frame[len_slot as usize];
let im1 = i.wrapping_sub(1);
if (im1 as u64) >= (len as u64) {
return None;
}
unsafe {
if byte {
*(frame[ptr_slot as usize] as *mut u8).add(im1 as usize) =
(frame[src as usize] != 0) as u8;
} else if narrow32 {
*(frame[ptr_slot as usize] as *mut i32).add(im1 as usize) =
frame[src as usize] as i32;
} else {
*(frame[ptr_slot as usize] as *mut i64).add(im1 as usize) =
frame[src as usize];
}
}
}
MicroOp::ArrRMW { idx, operand, ptr_slot, len_slot, op, checked } => {
let _ = checked;
let i = frame[idx as usize];
let len = frame[len_slot as usize];
let im1 = i.wrapping_sub(1);
if (im1 as u64) >= (len as u64) {
return None;
}
unsafe {
let cell = (frame[ptr_slot as usize] as *mut i64).add(im1 as usize);
*cell = op.eval(*cell, frame[operand as usize]);
}
}
MicroOp::ArrCondSwap { idx1, idx2, ptr_slot, len_slot, cmp, checked } => {
let _ = checked;
let len = frame[len_slot as usize];
let m1 = frame[idx1 as usize].wrapping_sub(1);
let m2 = frame[idx2 as usize].wrapping_sub(1);
if (m1 as u64) >= (len as u64) || (m2 as u64) >= (len as u64) {
return None;
}
unsafe {
let ptr = frame[ptr_slot as usize] as *mut i64;
let a = *ptr.add(m1 as usize);
let b = *ptr.add(m2 as usize);
if cmp.eval(a, b) {
*ptr.add(m1 as usize) = b;
*ptr.add(m2 as usize) = a;
}
}
}
MicroOp::ArrSwap { idx1, idx2, ptr_slot, len_slot, checked } => {
let _ = checked;
let len = frame[len_slot as usize];
let m1 = frame[idx1 as usize].wrapping_sub(1);
let m2 = frame[idx2 as usize].wrapping_sub(1);
if (m1 as u64) >= (len as u64) || (m2 as u64) >= (len as u64) {
return None;
}
unsafe {
let ptr = frame[ptr_slot as usize] as *mut i64;
let a = *ptr.add(m1 as usize);
let b = *ptr.add(m2 as usize);
*ptr.add(m1 as usize) = b;
*ptr.add(m2 as usize) = a;
}
}
MicroOp::FmaF { dst, a, b, c } => {
let av = f64::from_bits(frame[a as usize] as u64);
let bv = f64::from_bits(frame[b as usize] as u64);
let cv = f64::from_bits(frame[c as usize] as u64);
frame[dst as usize] = ((av * bv) + cv).to_bits() as i64;
}
MicroOp::Jump { target } => {
pc = target;
continue;
}
MicroOp::JumpIfFalse { cond, target } => {
if frame[cond as usize] == 0 {
pc = target;
continue;
}
}
MicroOp::JumpIfTrue { cond, target } => {
if frame[cond as usize] != 0 {
pc = target;
continue;
}
}
MicroOp::Branch { cmp, lhs, rhs, target } => {
if !cmp.eval(frame[lhs as usize], frame[rhs as usize]) {
pc = target;
continue;
}
}
MicroOp::MemMem {
h_ptr_slot,
h_len_slot,
n_ptr_slot,
n_len_slot,
needle_len_slot,
i_slot,
count_slot,
..
} => {
let h_len = frame[h_len_slot as usize];
let n_buf_len = frame[n_len_slot as usize];
let needle_len = frame[needle_len_slot as usize];
let start = frame[i_slot as usize];
if needle_len > n_buf_len {
return None;
}
let bound = h_len - needle_len + 1;
let mut count = 0i64;
if needle_len == 0 {
if start <= bound {
count = bound - start + 1;
}
} else {
let hay = frame[h_ptr_slot as usize] as *const u8;
let ndl = frame[n_ptr_slot as usize] as *const u8;
let mut p = start; while p <= bound {
let mut m = true;
for j in 0..needle_len {
let hb = unsafe { *hay.add((p + j - 1) as usize) };
let nb = unsafe { *ndl.add(j as usize) };
if hb != nb {
m = false;
break;
}
}
if m {
count += 1;
}
p += 1;
}
}
frame[count_slot as usize] += count;
frame[i_slot as usize] = core::cmp::max(start, bound + 1);
}
MicroOp::Return { src } => return Some(frame[src as usize]),
}
pc += 1;
}
unreachable!("validated programs cannot fall off the end")
}