use std::collections::HashMap;
use std::sync::Arc;
#[cfg(test)]
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::exec::{CompareSide, EnvironmentFaultKind, TaskFault, fault_site};
use crate::jit::{NativeProgram, StencilLayout, task_stencils};
use crate::task::{
Advance, ArgCopy, FnId, HostFn, Op, Program, TaskEvent, TaskStep, TraceMode, ValueMemories,
};
use crate::{CallSiteFacts, FunctionContract, ProgramContract, ValueShapeKind, VerifiedProgram};
#[repr(C)]
struct Ctx {
prog: *const u64,
frame: *mut u8,
ready: *mut i64,
ready_count: usize,
awaited: *const i64,
awaited_count: usize,
resume: *mut u64,
await_index: *mut u64,
exit: *mut i64,
store_value_memories: *const crate::task::RawValueMemory,
store_value_memory_count: usize,
lent_molten_value_memories: *const crate::task::RawValueMemory,
lent_molten_value_memory_count: usize,
molten: *mut core::ffi::c_void,
molten_bytes: unsafe extern "C" fn(*const core::ffi::c_void, i64, *mut usize) -> *const u8,
array_new: unsafe extern "C" fn(*mut core::ffi::c_void, i64, usize, i64, *mut i64) -> i64,
array_store:
unsafe extern "C" fn(*mut core::ffi::c_void, i64, i64, *const u8, usize, i64) -> i64,
array_load: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut u8,
usize,
i64,
) -> i64,
array_len: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut i64,
) -> i64,
ordered_begin_probe:
unsafe extern "C" fn(*mut core::ffi::c_void, i64, i64, *mut i64, *mut i64) -> i64,
ordered_probe_key: unsafe extern "C" fn(
*mut core::ffi::c_void,
i64,
i64,
i64,
usize,
*mut i64,
*mut i64,
*mut i64,
*mut u8,
) -> i64,
ordered_probe_value: unsafe extern "C" fn(
*mut core::ffi::c_void,
i64,
i64,
i64,
usize,
*mut i64,
*mut u8,
) -> i64,
ordered_begin_insert:
unsafe extern "C" fn(*mut core::ffi::c_void, i64, i64, *mut i64, *mut i64) -> i64,
ordered_insert_inspect: unsafe extern "C" fn(
*mut core::ffi::c_void,
i64,
i64,
i64,
usize,
*mut i64,
*mut u8,
) -> i64,
ordered_insert_advance:
unsafe extern "C" fn(*mut core::ffi::c_void, i64, i64, i64, i64, *mut i64) -> i64,
ordered_insert_commit: unsafe extern "C" fn(
*mut core::ffi::c_void,
i64,
i64,
i64,
*const u8,
usize,
*const u8,
usize,
i64,
i64,
*mut i64,
) -> i64,
ordered_begin_iterate:
unsafe extern "C" fn(*mut core::ffi::c_void, i64, i64, *mut i64, *mut i64) -> i64,
ordered_iterate_row: unsafe extern "C" fn(
*mut core::ffi::c_void,
i64,
i64,
i64,
usize,
*mut i64,
*mut u8,
) -> i64,
ordered_len: unsafe extern "C" fn(*mut core::ffi::c_void, i64, i64, *mut i64) -> i64,
string_concat: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut i64,
) -> i64,
string_trim: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
*mut i64,
) -> i64,
string_lines: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut i64,
) -> i64,
string_contains: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut i64,
) -> i64,
string_is_numeric: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
*mut i64,
) -> i64,
string_split_once: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut i64,
*mut i64,
) -> i64,
string_parse_int: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
*mut i64,
) -> i64,
byte_project: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
*mut i64,
) -> i64,
int_to_string: unsafe extern "C" fn(*mut core::ffi::c_void, i64, *mut i64) -> i64,
path_join: unsafe extern "C" fn(
*const crate::task::RawValueMemory,
usize,
*const crate::task::RawValueMemory,
usize,
*mut core::ffi::c_void,
i64,
i64,
*mut i64,
) -> i64,
publications: *mut core::ffi::c_void,
publish: unsafe extern "C" fn(*mut core::ffi::c_void, u64, i64, *const u8, usize) -> i64,
env_alloc: unsafe extern "C" fn(
*mut core::ffi::c_void,
*const u8,
*const u64,
usize,
usize,
*mut i64,
) -> i64,
env_bytes:
unsafe extern "C" fn(*const core::ffi::c_void, i64, *mut usize, *mut i64) -> *const u8,
}
const EXIT_AWAIT_PARKED: i64 = 1;
const EXIT_CALL: i64 = 2;
const EXIT_RET: i64 = 3;
const EXIT_HOST_CALL: i64 = 4;
const EXIT_TRACE_MARK: i64 = 5;
const EXIT_HOST_CALL_YIELD: i64 = 6;
const EXIT_COMPARE_LEFT_UNRESIDENT: i64 = 7;
const EXIT_COMPARE_RIGHT_UNRESIDENT: i64 = 8;
const EXIT_INVALID_ENUM_SELECTOR: i64 = 9;
const EXIT_ENUM_PROJECTION_MISMATCH: i64 = 10;
const EXIT_INVALID_ARRAY_STATUS: i64 = 11;
const EXIT_INVALID_ORDERED_STATUS: i64 = 12;
const EXIT_STRING_CONCAT_LEFT_UNRESIDENT: i64 = 13;
const EXIT_STRING_CONCAT_RIGHT_UNRESIDENT: i64 = 14;
const EXIT_STRING_CONCAT_ALLOCATION: i64 = 15;
const EXIT_PUBLICATION_ALLOCATION: i64 = 16;
const EXIT_INVALID_STRING_STATUS: i64 = 17;
const EXIT_BYTE_PROJECT_SOURCE_UNRESIDENT: i64 = 18;
const EXIT_BYTE_PROJECT_ALLOCATION: i64 = 19;
const EXIT_PATH_JOIN_BASE_UNRESIDENT: i64 = 20;
const EXIT_PATH_JOIN_SEGMENT_UNRESIDENT: i64 = 21;
const EXIT_PATH_JOIN_ALLOCATION: i64 = 22;
const EXIT_INT_TO_STRING_ALLOCATION: i64 = 23;
const EXIT_ENV_UNRESIDENT: i64 = 24;
const EXIT_ENV_STALE: i64 = 25;
const EXIT_ENV_OUT_OF_RANGE: i64 = 26;
const EXIT_ENV_ALLOCATION: i64 = 27;
pub fn available() -> bool {
!task_stencils::CONST.is_empty() && crate::jit::NATIVE_COPY_PATCH_AVAILABLE
}
#[derive(Clone, Debug)]
enum CallTarget {
Static(FnId),
Frame(u32),
}
#[derive(Clone, Debug)]
struct CallDesc {
target: CallTarget,
args: Vec<ArgCopy>,
ret: u32,
pc: usize,
}
struct CompiledFn {
native: NativeProgram,
calls: HashMap<u64, CallDesc>,
frame_size: usize,
frame_align: usize,
}
pub struct JitProgram {
fns: Vec<CompiledFn>,
}
impl JitProgram {
pub fn compile(program: &Program) -> Option<JitProgram> {
Self::compile_with_mode(program, TraceMode::Innards)
}
pub fn compile_with_mode(program: &Program, mode: TraceMode) -> Option<JitProgram> {
if !available() {
return None;
}
#[cfg(test)]
JIT_PROGRAM_COMPILE_COUNT.fetch_add(1, Ordering::Relaxed);
let fns = program
.fns
.iter()
.map(|f| compile_fn(f, None, None, mode))
.collect();
Some(JitProgram { fns })
}
}
pub(crate) struct JitExecutable {
verified: Arc<VerifiedProgram>,
program: JitProgram,
}
impl JitExecutable {
pub(crate) fn compile(verified: Arc<VerifiedProgram>, mode: TraceMode) -> Option<Self> {
if !available() {
return None;
}
#[cfg(test)]
JIT_PROGRAM_COMPILE_COUNT.fetch_add(1, Ordering::Relaxed);
let fns = verified
.program()
.fns
.iter()
.zip(&verified.contract().functions)
.map(|(function, contract)| {
compile_fn(function, Some(contract), Some(verified.contract()), mode)
})
.collect();
let program = JitProgram { fns };
Some(Self { verified, program })
}
fn verified(&self) -> &VerifiedProgram {
&self.verified
}
fn program(&self) -> &JitProgram {
&self.program
}
}
#[cfg(test)]
static JIT_PROGRAM_COMPILE_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
pub(crate) fn reset_jit_program_compile_count() {
JIT_PROGRAM_COMPILE_COUNT.store(0, Ordering::Relaxed);
}
#[cfg(test)]
pub(crate) fn jit_program_compile_count() -> usize {
JIT_PROGRAM_COMPILE_COUNT.load(Ordering::Relaxed)
}
fn next_emitted(starts: &[Option<usize>], i: usize, done: usize) -> usize {
starts
.iter()
.skip(i)
.flatten()
.next()
.copied()
.unwrap_or(done)
}
fn next_emitted_prog(
starts: &[Option<usize>],
prog_starts: &[Option<usize>],
i: usize,
done: usize,
) -> usize {
starts
.iter()
.enumerate()
.skip(i)
.find_map(|(j, start)| start.and_then(|_| prog_starts[j]))
.unwrap_or(done)
}
fn prog_delta(from: usize, to: usize) -> u64 {
(to as i64 - from as i64) as u64
}
#[derive(Clone, Copy)]
enum Continuations {
Fallthrough(&'static [usize]),
Jump(&'static [usize]),
JumpIfZero {
taken: &'static [usize],
fallthrough: &'static [usize],
},
}
fn compile_fn(
f: &crate::task::Fn,
function_contract: Option<&FunctionContract>,
program_contract: Option<&ProgramContract>,
mode: TraceMode,
) -> CompiledFn {
let mut layout = StencilLayout::new();
let root = layout.start_chain();
let stripped = |op: &Op| matches!(op, Op::Trace { .. }) && mode == TraceMode::Production;
let mut starts: Vec<Option<usize>> = Vec::with_capacity(f.code.len());
let mut sites: Vec<(usize, Continuations)> = Vec::new();
for op in &f.code {
if stripped(op) {
starts.push(None);
continue;
}
let (bytes, cont): (&[u8], Continuations) = match op {
Op::ProductConstruct { .. } => (
task_stencils::PRODUCT_CONSTRUCT,
Continuations::Fallthrough(task_stencils::PRODUCT_CONSTRUCT_CONT),
),
Op::ProductProject { .. } | Op::CopyValue { .. } => (
task_stencils::COPY_VALUE,
Continuations::Fallthrough(task_stencils::COPY_VALUE_CONT),
),
Op::EnvBox { .. } => (
task_stencils::ENV_BOX,
Continuations::Fallthrough(task_stencils::ENV_BOX_CONT),
),
Op::EnvLoad { .. } => (
task_stencils::ENV_LOAD,
Continuations::Fallthrough(task_stencils::ENV_LOAD_CONT),
),
Op::EnumConstruct { .. } => (
task_stencils::ENUM_CONSTRUCT,
Continuations::Fallthrough(task_stencils::ENUM_CONSTRUCT_CONT),
),
Op::EnumIsVariant { .. } => (
task_stencils::ENUM_IS_VARIANT,
Continuations::Fallthrough(task_stencils::ENUM_IS_VARIANT_CONT),
),
Op::EnumProjectChecked { .. } => (
task_stencils::ENUM_PROJECT_CHECKED,
Continuations::Fallthrough(task_stencils::ENUM_PROJECT_CHECKED_CONT),
),
Op::ConstI64 { .. } => (
task_stencils::CONST,
Continuations::Fallthrough(task_stencils::CONST_CONT),
),
Op::AddI64 { .. } => (
task_stencils::ADD,
Continuations::Fallthrough(task_stencils::ADD_CONT),
),
Op::MulI64 { .. } => (
task_stencils::MUL,
Continuations::Fallthrough(task_stencils::MUL_CONT),
),
Op::SubI64 { .. } => (
task_stencils::SUB,
Continuations::Fallthrough(task_stencils::SUB_CONT),
),
Op::DivI64 { .. } => (
task_stencils::DIV,
Continuations::Fallthrough(task_stencils::DIV_CONT),
),
Op::CopyI64 { .. } => (
task_stencils::COPY,
Continuations::Fallthrough(task_stencils::COPY_CONT),
),
Op::EqI64 { .. } => (
task_stencils::EQ,
Continuations::Fallthrough(task_stencils::EQ_CONT),
),
Op::NeI64 { .. } => (
task_stencils::NE,
Continuations::Fallthrough(task_stencils::NE_CONT),
),
Op::LtI64 { .. } => (
task_stencils::LT,
Continuations::Fallthrough(task_stencils::LT_CONT),
),
Op::LeI64 { .. } => (
task_stencils::LE,
Continuations::Fallthrough(task_stencils::LE_CONT),
),
Op::GtI64 { .. } => (
task_stencils::GT,
Continuations::Fallthrough(task_stencils::GT_CONT),
),
Op::GeI64 { .. } => (
task_stencils::GE,
Continuations::Fallthrough(task_stencils::GE_CONT),
),
Op::Jump { .. } => (
task_stencils::JUMP,
Continuations::Jump(task_stencils::JUMP_CONT),
),
Op::JumpIfZero { .. } => (
task_stencils::JUMP_IF_ZERO,
Continuations::JumpIfZero {
taken: task_stencils::JUMP_IF_ZERO_TAKEN_CONT,
fallthrough: task_stencils::JUMP_IF_ZERO_FALLTHROUGH_CONT,
},
),
Op::LoadIndexedI64 { .. } => (
task_stencils::LOAD_IX,
Continuations::Fallthrough(task_stencils::LOAD_IX_CONT),
),
Op::StoreIndexedI64 { .. } => (
task_stencils::STORE_IX,
Continuations::Fallthrough(task_stencils::STORE_IX_CONT),
),
Op::ArrayNew { .. } => (
task_stencils::ARRAY_NEW,
Continuations::Fallthrough(task_stencils::ARRAY_NEW_CONT),
),
Op::OrderedEmpty { .. } => (
task_stencils::ORDERED_EMPTY,
Continuations::Fallthrough(task_stencils::ORDERED_EMPTY_CONT),
),
Op::OrderedBeginProbe { .. } => (
task_stencils::ORDERED_BEGIN_PROBE,
Continuations::Fallthrough(task_stencils::ORDERED_BEGIN_PROBE_CONT),
),
Op::OrderedProbeKey { .. } => (
task_stencils::ORDERED_PROBE_KEY,
Continuations::Fallthrough(task_stencils::ORDERED_PROBE_KEY_CONT),
),
Op::OrderedProbeValue { .. } => (
task_stencils::ORDERED_PROBE_VALUE,
Continuations::Fallthrough(task_stencils::ORDERED_PROBE_VALUE_CONT),
),
Op::OrderedBeginInsert { .. } => (
task_stencils::ORDERED_BEGIN_INSERT,
Continuations::Fallthrough(task_stencils::ORDERED_BEGIN_INSERT_CONT),
),
Op::OrderedInsertInspect { .. } => (
task_stencils::ORDERED_INSERT_INSPECT,
Continuations::Fallthrough(task_stencils::ORDERED_INSERT_INSPECT_CONT),
),
Op::OrderedInsertAdvance { .. } => (
task_stencils::ORDERED_INSERT_ADVANCE,
Continuations::Fallthrough(task_stencils::ORDERED_INSERT_ADVANCE_CONT),
),
Op::OrderedInsertCommit { .. } => (
task_stencils::ORDERED_INSERT_COMMIT,
Continuations::Fallthrough(task_stencils::ORDERED_INSERT_COMMIT_CONT),
),
Op::OrderedBeginIterate { .. } => (
task_stencils::ORDERED_BEGIN_ITERATE,
Continuations::Fallthrough(task_stencils::ORDERED_BEGIN_ITERATE_CONT),
),
Op::OrderedIterateRow { .. } => (
task_stencils::ORDERED_ITERATE_ROW,
Continuations::Fallthrough(task_stencils::ORDERED_ITERATE_ROW_CONT),
),
Op::OrderedLen { .. } => (
task_stencils::ORDERED_LEN,
Continuations::Fallthrough(task_stencils::ORDERED_LEN_CONT),
),
Op::OrderedStatusIs { .. } => (
task_stencils::ORDERED_STATUS_IS,
Continuations::Fallthrough(task_stencils::ORDERED_STATUS_IS_CONT),
),
Op::ArrayStoreWord { .. } | Op::ArrayStore { .. } => (
task_stencils::ARRAY_STORE_WORD,
Continuations::Fallthrough(task_stencils::ARRAY_STORE_WORD_CONT),
),
Op::LoadArrayWord { .. } => (
task_stencils::LOAD_ARRAY_WORD,
Continuations::Fallthrough(task_stencils::LOAD_ARRAY_WORD_CONT),
),
Op::LoadArray { .. } => (
task_stencils::LOAD_ARRAY,
Continuations::Fallthrough(task_stencils::LOAD_ARRAY_CONT),
),
Op::LoadArrayLen { .. } => (
task_stencils::LOAD_ARRAY_LEN,
Continuations::Fallthrough(task_stencils::LOAD_ARRAY_LEN_CONT),
),
Op::ArrayStatusIs { .. } => (
task_stencils::ARRAY_STATUS_IS,
Continuations::Fallthrough(task_stencils::ARRAY_STATUS_IS_CONT),
),
Op::CompareValueBytes { .. } => (
task_stencils::COMPARE_VALUE_BYTES,
Continuations::Fallthrough(task_stencils::COMPARE_VALUE_BYTES_CONT),
),
Op::StringConcat { .. } => (
task_stencils::STRING_CONCAT,
Continuations::Fallthrough(task_stencils::STRING_CONCAT_CONT),
),
Op::StringTrim { .. } => (
task_stencils::STRING_TRIM,
Continuations::Fallthrough(task_stencils::STRING_TRIM_CONT),
),
Op::StringLines { .. } => (
task_stencils::STRING_LINES,
Continuations::Fallthrough(task_stencils::STRING_LINES_CONT),
),
Op::StringContains { .. } => (
task_stencils::STRING_CONTAINS,
Continuations::Fallthrough(task_stencils::STRING_CONTAINS_CONT),
),
Op::StringIsNumeric { .. } => (
task_stencils::STRING_IS_NUMERIC,
Continuations::Fallthrough(task_stencils::STRING_IS_NUMERIC_CONT),
),
Op::StringSplitOnce { .. } => (
task_stencils::STRING_SPLIT_ONCE,
Continuations::Fallthrough(task_stencils::STRING_SPLIT_ONCE_CONT),
),
Op::StringParseInt { .. } => (
task_stencils::STRING_PARSE_INT,
Continuations::Fallthrough(task_stencils::STRING_PARSE_INT_CONT),
),
Op::StringStatusIs { .. } => (
task_stencils::STRING_STATUS_IS,
Continuations::Fallthrough(task_stencils::STRING_STATUS_IS_CONT),
),
Op::IntToString { .. } => (
task_stencils::INT_TO_STRING,
Continuations::Fallthrough(task_stencils::INT_TO_STRING_CONT),
),
Op::ByteProject { .. } => (
task_stencils::BYTE_PROJECT,
Continuations::Fallthrough(task_stencils::BYTE_PROJECT_CONT),
),
Op::PathJoin { .. } => (
task_stencils::PATH_JOIN,
Continuations::Fallthrough(task_stencils::PATH_JOIN_CONT),
),
Op::Publish { .. } => (
task_stencils::PUBLISH,
Continuations::Fallthrough(task_stencils::PUBLISH_CONT),
),
Op::Await { .. } => (
task_stencils::AWAIT,
Continuations::Fallthrough(task_stencils::AWAIT_CONT),
),
Op::Call { .. } | Op::CallIndirect { .. } => (
task_stencils::CALL,
Continuations::Fallthrough(task_stencils::CALL_CONT),
),
Op::Ret { .. } => (
task_stencils::RET,
Continuations::Fallthrough(task_stencils::RET_CONT),
),
Op::HostCall { .. } => (
task_stencils::HOSTCALL,
Continuations::Fallthrough(task_stencils::HOSTCALL_CONT),
),
Op::HostCallYield { .. } => (
task_stencils::HOSTCALL_YIELD,
Continuations::Fallthrough(task_stencils::HOSTCALL_YIELD_CONT),
),
Op::ConstF64 { .. } => (
task_stencils::CONST,
Continuations::Fallthrough(task_stencils::CONST_CONT),
),
Op::AddF64 { .. } => (
task_stencils::ADD_F64,
Continuations::Fallthrough(task_stencils::ADD_F64_CONT),
),
Op::MulF64 { .. } => (
task_stencils::MUL_F64,
Continuations::Fallthrough(task_stencils::MUL_F64_CONT),
),
Op::Trace { .. } => (
task_stencils::TRACE,
Continuations::Fallthrough(task_stencils::TRACE_CONT),
),
};
let start = layout.emit_stencil(bytes);
starts.push(Some(start));
sites.push((start, cont));
}
let done = layout.emit_stencil(task_stencils::DONE);
let mut emitted_ix = 0usize;
for (i, start_opt) in starts.iter().enumerate() {
let Some(start) = *start_opt else { continue };
let (_, cont) = sites[emitted_ix];
emitted_ix += 1;
match cont {
Continuations::Fallthrough(relocs) => {
let target = next_emitted(&starts, i + 1, done);
for &rel in relocs {
layout.patch_continuation(start + rel, target);
}
}
Continuations::Jump(relocs) => {
let Op::Jump { target } = &f.code[i] else {
unreachable!("jump continuation kind only assigned to Jump")
};
let target = next_emitted(&starts, *target as usize, done);
for &rel in relocs {
layout.patch_continuation(start + rel, target);
}
}
Continuations::JumpIfZero { taken, fallthrough } => {
let Op::JumpIfZero { target, .. } = &f.code[i] else {
unreachable!("conditional continuation kind only assigned to JumpIfZero")
};
let taken_target = next_emitted(&starts, *target as usize, done);
let fallthrough_target = next_emitted(&starts, i + 1, done);
for &rel in taken {
layout.patch_continuation(start + rel, taken_target);
}
for &rel in fallthrough {
layout.patch_continuation(start + rel, fallthrough_target);
}
}
}
}
let mut prog_starts = Vec::with_capacity(f.code.len());
let mut prog_len = 0usize;
for op in &f.code {
if stripped(op) {
prog_starts.push(None);
continue;
}
prog_starts.push(Some(prog_len));
prog_len += match op {
Op::ProductConstruct { fields, .. } => 1 + fields.len() * 3,
Op::ProductProject { .. } | Op::CopyValue { .. } => 3,
Op::EnvBox { fields, .. } => 4 + fields.len() * 3,
Op::EnvLoad { .. } => 5,
Op::EnumConstruct { fields, .. } => 5 + fields.len() * 3,
Op::EnumIsVariant { .. } => 6,
Op::EnumProjectChecked { .. } => 8,
Op::ConstI64 { .. } | Op::ConstF64 { .. } => 2,
Op::CopyI64 { .. } => 2,
Op::AddI64 { .. }
| Op::MulI64 { .. }
| Op::SubI64 { .. }
| Op::DivI64 { .. }
| Op::EqI64 { .. }
| Op::NeI64 { .. }
| Op::LtI64 { .. }
| Op::LeI64 { .. }
| Op::GtI64 { .. }
| Op::GeI64 { .. }
| Op::AddF64 { .. }
| Op::MulF64 { .. } => 3,
Op::Jump { .. } => 1,
Op::JumpIfZero { .. } => 3,
Op::LoadIndexedI64 { .. } | Op::StoreIndexedI64 { .. } => 4,
Op::ArrayNew { .. } => 5,
Op::OrderedEmpty { .. } => 2,
Op::OrderedBeginProbe { .. } => 4,
Op::OrderedProbeKey { .. } => 8,
Op::OrderedProbeValue { .. } => 6,
Op::OrderedBeginInsert { .. } => 4,
Op::OrderedInsertInspect { .. } => 6,
Op::OrderedInsertAdvance { .. } => 5,
Op::OrderedInsertCommit { .. } => 9,
Op::OrderedBeginIterate { .. } => 4,
Op::OrderedIterateRow { .. } => 6,
Op::OrderedLen { .. } | Op::OrderedStatusIs { .. } => 4,
Op::ArrayStoreWord { .. } | Op::LoadArray { .. } | Op::ArrayStore { .. } => 6,
Op::LoadArrayWord { .. } => 5,
Op::IntToString { .. } => 3,
Op::LoadArrayLen { .. } => 4,
Op::ArrayStatusIs { .. } => 4,
Op::CompareValueBytes { .. } => 4,
Op::StringConcat { .. } => 4,
Op::StringTrim { .. } => 3,
Op::StringLines { .. } => 4,
Op::StringContains { .. } | Op::StringParseInt { .. } => 4,
Op::StringIsNumeric { .. } => 3,
Op::StringStatusIs { .. } => 4,
Op::StringSplitOnce { .. } => 6,
Op::ByteProject { .. } => 3,
Op::PathJoin { .. } => 4,
Op::Publish { .. } => 5,
Op::Await { .. } => 3,
Op::Call { .. } | Op::CallIndirect { .. } => 1,
Op::Ret { .. } => 2,
Op::HostCall { .. } | Op::HostCallYield { .. } | Op::Trace { .. } => 2,
};
}
let mut calls = HashMap::new();
for (i, op) in f.code.iter().enumerate() {
match op {
Op::EnvBox {
dst,
callee,
fields,
} => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let environment = &program_contract.functions[callee.0 as usize].environment;
let total_len = environment
.iter()
.map(|field| field.offset as usize + field.shape.words.len() * 8)
.max()
.unwrap_or(0);
let destination = &function_contract.frame.regions[dst.0 as usize];
layout.push_prog_word(root.prog_index, u64::from(destination.offset));
layout.push_prog_word(root.prog_index, i as u64);
layout.push_prog_word(root.prog_index, total_len as u64);
layout.push_prog_word(root.prog_index, fields.len() as u64);
for (index, source) in fields.iter().enumerate() {
let source_region = &function_contract.frame.regions[source.0 as usize];
let field = &environment[index];
for value in [
u64::from(source_region.offset),
u64::from(field.offset),
(field.shape.words.len() * 8) as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
}
Op::EnvLoad {
dst,
env,
callee,
field,
} => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let environment = &program_contract.functions[callee.0 as usize].environment;
let field_desc = &environment[*field as usize];
let destination = &function_contract.frame.regions[dst.0 as usize];
let env_region = &function_contract.frame.regions[env.0 as usize];
for value in [
u64::from(destination.offset),
u64::from(env_region.offset),
u64::from(field_desc.offset),
(field_desc.shape.words.len() * 8) as u64,
i as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
Op::ProductConstruct { dst, fields } => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let destination = &function_contract.frame.regions[dst.0 as usize];
let value_shape = destination.value_shape.unwrap();
let ValueShapeKind::Product { fields: declared } =
&program_contract.value_shapes[value_shape.0 as usize].kind
else {
unreachable!();
};
layout.push_prog_word(root.prog_index, fields.len() as u64);
for source in fields {
let field = &declared[source.field as usize];
let source_region = &function_contract.frame.regions[source.source.0 as usize];
for value in [
u64::from(destination.offset + field.offset),
u64::from(source_region.offset),
(field.shape.words.len() * 8) as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
}
Op::ProductProject {
dst,
product,
field,
} => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let destination = &function_contract.frame.regions[dst.0 as usize];
let product = &function_contract.frame.regions[product.0 as usize];
let value_shape = product.value_shape.unwrap();
let ValueShapeKind::Product { fields } =
&program_contract.value_shapes[value_shape.0 as usize].kind
else {
unreachable!()
};
let field = &fields[*field as usize];
for value in [
u64::from(destination.offset),
u64::from(product.offset + field.offset),
(field.shape.words.len() * 8) as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
Op::CopyValue { dst, src } => {
let (function_contract, _) =
verified_compile_contracts(function_contract, program_contract);
let destination = &function_contract.frame.regions[dst.0 as usize];
let source = &function_contract.frame.regions[src.0 as usize];
for value in [
u64::from(destination.offset),
u64::from(source.offset),
(source.shape.words.len() * 8) as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
Op::EnumConstruct {
dst,
variant,
fields,
} => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let destination = &function_contract.frame.regions[dst.0 as usize];
let value_shape = destination.value_shape.unwrap();
let ValueShapeKind::Enum { selector, variants } =
&program_contract.value_shapes[value_shape.0 as usize].kind
else {
unreachable!()
};
for value in [
u64::from(destination.offset),
(destination.shape.words.len() * 8) as u64,
u64::from(selector.offset),
u64::from(*variant),
fields.len() as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
for source in fields {
let field = &variants[*variant as usize].fields[source.field as usize];
let source_region = &function_contract.frame.regions[source.source.0 as usize];
for value in [
u64::from(destination.offset + field.offset),
u64::from(source_region.offset),
(field.shape.words.len() * 8) as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
}
Op::EnumIsVariant {
dst,
value,
variant,
} => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let destination = &function_contract.frame.regions[dst.0 as usize];
let value_region = &function_contract.frame.regions[value.0 as usize];
let value_shape = value_region.value_shape.unwrap();
let ValueShapeKind::Enum { selector, variants } =
&program_contract.value_shapes[value_shape.0 as usize].kind
else {
unreachable!()
};
for immediate in [
u64::from(destination.offset),
u64::from(value_region.offset),
u64::from(selector.offset),
u64::from(*variant),
variants.len() as u64,
i as u64,
] {
layout.push_prog_word(root.prog_index, immediate);
}
}
Op::EnumProjectChecked {
dst,
value,
variant,
field,
} => {
let (function_contract, program_contract) =
verified_compile_contracts(function_contract, program_contract);
let destination = &function_contract.frame.regions[dst.0 as usize];
let value_region = &function_contract.frame.regions[value.0 as usize];
let value_shape = value_region.value_shape.unwrap();
let ValueShapeKind::Enum { selector, variants } =
&program_contract.value_shapes[value_shape.0 as usize].kind
else {
unreachable!()
};
let field = &variants[*variant as usize].fields[*field as usize];
for immediate in [
u64::from(destination.offset),
u64::from(value_region.offset),
u64::from(selector.offset),
u64::from(*variant),
variants.len() as u64,
u64::from(field.offset),
(field.shape.words.len() * 8) as u64,
i as u64,
] {
layout.push_prog_word(root.prog_index, immediate);
}
}
Op::ConstI64 { dst, value } => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, *value as u64);
}
Op::CopyI64 { dst, src } => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, u64::from(*src));
}
Op::AddI64 { dst, a, b }
| Op::MulI64 { dst, a, b }
| Op::SubI64 { dst, a, b }
| Op::DivI64 { dst, a, b }
| Op::EqI64 { dst, a, b }
| Op::NeI64 { dst, a, b }
| Op::LtI64 { dst, a, b }
| Op::LeI64 { dst, a, b }
| Op::GtI64 { dst, a, b }
| Op::GeI64 { dst, a, b } => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, u64::from(*a));
layout.push_prog_word(root.prog_index, u64::from(*b));
}
Op::Jump { target } => {
let here = prog_starts[i].expect("jumps are never stripped");
let target = next_emitted_prog(&starts, &prog_starts, *target as usize, prog_len);
layout.push_prog_word(root.prog_index, prog_delta(here, target));
}
Op::JumpIfZero { value, target } => {
let here = prog_starts[i].expect("branches are never stripped");
let taken = next_emitted_prog(&starts, &prog_starts, *target as usize, prog_len);
let fallthrough = next_emitted_prog(&starts, &prog_starts, i + 1, prog_len);
layout.push_prog_word(root.prog_index, u64::from(*value));
layout.push_prog_word(root.prog_index, prog_delta(here, taken));
layout.push_prog_word(root.prog_index, prog_delta(here, fallthrough));
}
Op::LoadIndexedI64 {
dst,
base,
index,
stride,
} => {
for v in [dst, base, index, stride] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
}
Op::StoreIndexedI64 {
base,
index,
stride,
src,
} => {
for v in [base, index, stride, src] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
}
Op::ArrayNew {
dst,
status,
count_slot,
elem_width,
elem_schema_ref,
} => {
for v in [
u64::from(*dst),
u64::from(*status),
u64::from(*count_slot),
u64::from(*elem_width),
*elem_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedEmpty {
dst,
collection_schema_ref,
} => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, *collection_schema_ref as u64);
}
Op::OrderedBeginProbe {
cursor,
status,
collection,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*status),
u64::from(*collection),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedProbeKey {
cursor,
present,
key,
left,
right,
status,
key_width,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*present),
u64::from(*key),
u64::from(*left),
u64::from(*right),
u64::from(*status),
u64::from(*key_width),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedProbeValue {
cursor,
present,
value,
status,
value_width,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*present),
u64::from(*value),
u64::from(*status),
u64::from(*value_width),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedBeginInsert {
cursor,
status,
collection,
collection_schema_ref,
}
| Op::OrderedBeginIterate {
cursor,
status,
collection,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*status),
u64::from(*collection),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedInsertInspect {
cursor,
present,
key,
status,
key_width,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*present),
u64::from(*key),
u64::from(*status),
u64::from(*key_width),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedInsertAdvance {
cursor,
ordering,
ready,
status,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*ordering),
u64::from(*ready),
u64::from(*status),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedInsertCommit {
dst,
cursor,
key,
value,
status,
key_width,
value_width,
collection_schema_ref,
replace,
} => {
for v in [
u64::from(*dst),
u64::from(*cursor),
u64::from(*key),
value.map_or(u64::MAX, u64::from),
u64::from(*status),
u64::from(*key_width),
u64::from(*value_width),
*collection_schema_ref as u64,
u64::from(*replace),
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedIterateRow {
cursor,
present,
row,
status,
row_width,
collection_schema_ref,
} => {
for v in [
u64::from(*cursor),
u64::from(*present),
u64::from(*row),
u64::from(*status),
u64::from(*row_width),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedLen {
dst,
status,
collection,
collection_schema_ref,
} => {
for v in [
u64::from(*dst),
u64::from(*status),
u64::from(*collection),
*collection_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::OrderedStatusIs {
dst,
status,
expected,
} => {
for v in [
u64::from(*dst),
u64::from(*status),
*expected as u64,
i as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::LoadArrayWord {
dst,
present,
array,
index,
elem_schema_ref,
} => {
for v in [
u64::from(*dst),
u64::from(*present),
u64::from(*array),
u64::from(*index),
*elem_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::ArrayStoreWord {
status,
array,
index,
src,
elem_schema_ref,
} => {
for v in [
u64::from(*status),
u64::from(*array),
u64::from(*index),
u64::from(*src),
8,
*elem_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::ArrayStore {
status,
array,
index,
src,
elem_width,
elem_schema_ref,
} => {
for v in [
u64::from(*status),
u64::from(*array),
u64::from(*index),
u64::from(*src),
u64::from(*elem_width),
*elem_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::LoadArray {
dst,
status,
array,
index,
elem_width,
elem_schema_ref,
} => {
for v in [
u64::from(*dst),
u64::from(*status),
u64::from(*array),
u64::from(*index),
u64::from(*elem_width),
*elem_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::LoadArrayLen {
dst,
status,
array,
elem_schema_ref,
} => {
for v in [
u64::from(*dst),
u64::from(*status),
u64::from(*array),
*elem_schema_ref as u64,
] {
layout.push_prog_word(root.prog_index, v);
}
}
Op::ArrayStatusIs {
dst,
status,
expected,
} => {
for value in [
u64::from(*dst),
u64::from(*status),
*expected as i64 as u64,
i as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
Op::CompareValueBytes { dst, a, b } => {
for v in [dst, a, b] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringConcat { dst, a, b } => {
for v in [dst, a, b] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringTrim { dst, text } => {
for value in [dst, text] {
layout.push_prog_word(root.prog_index, u64::from(*value));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringLines {
dst,
text,
element_schema_ref,
} => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, u64::from(*text));
layout.push_prog_word(root.prog_index, *element_schema_ref as u64);
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringContains { dst, text, needle } => {
for value in [dst, text, needle] {
layout.push_prog_word(root.prog_index, u64::from(*value));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringIsNumeric { dst, text } => {
for value in [dst, text] {
layout.push_prog_word(root.prog_index, u64::from(*value));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringSplitOnce {
left,
right,
status,
text,
delimiter,
} => {
for value in [left, right, status, text, delimiter] {
layout.push_prog_word(root.prog_index, u64::from(*value));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringParseInt { dst, status, text } => {
for value in [dst, status, text] {
layout.push_prog_word(root.prog_index, u64::from(*value));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::StringStatusIs {
dst,
status,
expected,
} => {
for value in [
u64::from(*dst),
u64::from(*status),
*expected as i64 as u64,
i as u64,
] {
layout.push_prog_word(root.prog_index, value);
}
}
Op::ByteProject { dst, source } => {
for v in [dst, source] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::IntToString { dst, src } => {
for v in [dst, src] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::PathJoin { dst, base, segment } => {
for v in [dst, base, segment] {
layout.push_prog_word(root.prog_index, u64::from(*v));
}
layout.push_prog_word(root.prog_index, i as u64);
}
Op::Publish {
site,
record,
record_width,
record_schema_ref,
} => {
layout.push_prog_word(root.prog_index, *site);
layout.push_prog_word(root.prog_index, u64::from(*record));
layout.push_prog_word(root.prog_index, u64::from(*record_width));
layout.push_prog_word(root.prog_index, *record_schema_ref as u64);
layout.push_prog_word(root.prog_index, i as u64);
}
Op::Await { dst, input } => {
layout.push_prog_word(
root.prog_index,
starts[i].expect("awaits are never stripped") as u64,
);
layout.push_prog_word(root.prog_index, u64::from(*input));
layout.push_prog_word(root.prog_index, u64::from(*dst));
}
Op::Call { callee, args, ret } => {
let continuation = next_emitted(&starts, i + 1, done) as u64;
layout.push_prog_word(root.prog_index, continuation);
calls.insert(
continuation,
CallDesc {
target: CallTarget::Static(*callee),
args: args.clone(),
ret: *ret,
pc: i,
},
);
}
Op::CallIndirect { callee, args, ret } => {
let continuation = next_emitted(&starts, i + 1, done) as u64;
layout.push_prog_word(root.prog_index, continuation);
calls.insert(
continuation,
CallDesc {
target: CallTarget::Frame(*callee),
args: args.clone(),
ret: *ret,
pc: i,
},
);
}
Op::Ret { src, size } => {
layout.push_prog_word(root.prog_index, u64::from(*src));
layout.push_prog_word(root.prog_index, u64::from(*size));
}
Op::HostCall { host } => {
let continuation = next_emitted(&starts, i + 1, done) as u64;
layout.push_prog_word(root.prog_index, continuation);
layout.push_prog_word(root.prog_index, u64::from(*host));
}
Op::HostCallYield { host } => {
let continuation = next_emitted(&starts, i + 1, done) as u64;
layout.push_prog_word(root.prog_index, continuation);
layout.push_prog_word(root.prog_index, u64::from(*host));
}
Op::Trace { id } => {
if !stripped(op) {
let continuation = next_emitted(&starts, i + 1, done) as u64;
layout.push_prog_word(root.prog_index, continuation);
layout.push_prog_word(root.prog_index, u64::from(*id));
}
}
Op::ConstF64 { dst, bits } => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, *bits);
}
Op::AddF64 { dst, a, b } | Op::MulF64 { dst, a, b } => {
layout.push_prog_word(root.prog_index, u64::from(*dst));
layout.push_prog_word(root.prog_index, u64::from(*a));
layout.push_prog_word(root.prog_index, u64::from(*b));
}
}
}
let native = NativeProgram::new(layout, root);
CompiledFn {
native,
calls,
frame_size: f.frame.size,
frame_align: f.frame.align,
}
}
fn verified_compile_contracts<'a>(
function: Option<&'a FunctionContract>,
program: Option<&'a ProgramContract>,
) -> (&'a FunctionContract, &'a ProgramContract) {
match (function, program) {
(Some(function), Some(program)) => (function, program),
_ => panic!("typed structural operation requires VerifiedProgram"),
}
}
#[derive(Clone, Debug)]
struct JitFrame {
fn_id: FnId,
base: usize,
resume: usize,
prog_pos: usize,
ret_to: Option<usize>,
}
pub struct JitTask {
arena: Vec<u8>,
molten: crate::task::MoltenArena,
publications: crate::task::PublicationLog,
frames: Vec<JitFrame>,
pub result: Vec<u8>,
pub trace: Vec<TaskEvent>,
parked_on: Option<u32>,
ready_scratch: Vec<i64>,
}
impl JitTask {
pub fn spawn(program: &JitProgram, entry: FnId) -> Self {
let mut task = JitTask {
arena: Vec::new(),
molten: crate::task::MoltenArena::default(),
publications: crate::task::PublicationLog::default(),
frames: Vec::new(),
result: Vec::new(),
trace: Vec::new(),
parked_on: None,
ready_scratch: Vec::new(),
};
let base = task.alloc_frame(&program.fns[entry.0 as usize]);
task.frames.push(JitFrame {
fn_id: entry,
base,
resume: 0,
prog_pos: 0,
ret_to: None,
});
task.trace.push(TaskEvent::FrameEntered(entry));
task
}
pub(crate) fn spawn_verified(executable: &JitExecutable, entry: FnId) -> Self {
Self::spawn(executable.program(), entry)
}
pub fn depth(&self) -> usize {
self.frames.len()
}
#[must_use]
pub fn active_function(&self) -> FnId {
self.frames.last().expect("live frame").fn_id
}
#[must_use]
pub fn frame_arena_bytes(&self) -> usize {
self.arena.len()
}
pub fn result_i64(&self) -> i64 {
i64::from_le_bytes(self.result[..8].try_into().expect("8-byte result"))
}
#[must_use]
pub(crate) fn publications(&self) -> &crate::task::PublicationLog {
&self.publications
}
pub(crate) fn molten(&self) -> &crate::task::MoltenArena {
&self.molten
}
pub(crate) fn molten_mut(&mut self) -> &mut crate::task::MoltenArena {
&mut self.molten
}
pub fn write_i64(&mut self, offset: u32, value: i64) {
let base = self.frames.last().expect("live frame").base;
let at = base + offset as usize;
self.arena[at..at + 8].copy_from_slice(&value.to_le_bytes());
}
pub(crate) fn write_bytes(&mut self, offset: u32, bytes: &[u8]) {
let base = self.frames.last().expect("live frame").base;
let at = base + offset as usize;
self.arena[at..at + bytes.len()].copy_from_slice(bytes);
}
fn alloc_frame(&mut self, f: &CompiledFn) -> usize {
let align = f.frame_align.max(1);
let base = self.arena.len().div_ceil(align) * align;
self.arena.resize(base + f.frame_size, 0);
base
}
pub fn run(&mut self, program: &JitProgram, ready: &mut [bool], awaited: &[i64]) -> TaskStep {
self.run_hosted(program, ready, awaited, &mut [])
}
pub fn run_hosted(
&mut self,
program: &JitProgram,
ready: &mut [bool],
awaited: &[i64],
hosts: &mut [HostFn<'_>],
) -> TaskStep {
self.run_hosted_with_value_memories(program, ready, awaited, hosts, ValueMemories::empty())
}
pub fn run_hosted_with_value_memories(
&mut self,
program: &JitProgram,
ready: &mut [bool],
awaited: &[i64],
hosts: &mut [HostFn<'_>],
value_memories: ValueMemories<'_>,
) -> TaskStep {
self.run_hosted_with_value_memories_inner(
None,
program,
ready,
awaited,
hosts,
value_memories,
)
.unwrap_or_else(|fault| panic!("legacy raw JIT task fault: {fault:?}"))
}
pub(crate) fn run_verified_with_value_memories(
&mut self,
executable: &JitExecutable,
ready: &mut [bool],
awaited: &[i64],
hosts: &mut [HostFn<'_>],
value_memories: ValueMemories<'_>,
) -> Result<TaskStep, TaskFault> {
self.run_hosted_with_value_memories_inner(
Some(executable.verified()),
executable.program(),
ready,
awaited,
hosts,
value_memories,
)
}
fn run_hosted_with_value_memories_inner(
&mut self,
verified: Option<&VerifiedProgram>,
program: &JitProgram,
ready: &mut [bool],
awaited: &[i64],
hosts: &mut [HostFn<'_>],
value_memories: ValueMemories<'_>,
) -> Result<TaskStep, TaskFault> {
self.ready_scratch.clear();
self.ready_scratch
.extend(ready.iter().map(|&r| i64::from(r)));
if let Some(input) = self.parked_on
&& ready.get(input as usize).copied().unwrap_or(false)
{
self.parked_on = None;
self.trace.push(TaskEvent::Resumed);
}
let store_value_memories: Vec<_> = value_memories
.store
.iter()
.map(|memory| memory.raw())
.collect();
let lent_molten_value_memories: Vec<_> = value_memories
.molten
.iter()
.map(|memory| memory.raw())
.collect();
loop {
let frame = self
.frames
.last()
.expect("running task has a frame")
.clone();
let compiled = &program.fns[frame.fn_id.0 as usize];
let entry_prog = compiled.native.entry_prog();
let mut resume_scratch = 0u64;
let mut index_scratch = 0u64;
let mut exit_scratch = 0i64;
let arena_base = self.arena.as_mut_ptr();
let mut ctx = Ctx {
prog: unsafe { entry_prog.add(frame.prog_pos) },
frame: unsafe { arena_base.add(frame.base) },
ready: self.ready_scratch.as_mut_ptr(),
ready_count: self.ready_scratch.len(),
awaited: awaited.as_ptr(),
awaited_count: awaited.len(),
resume: &mut resume_scratch,
await_index: &mut index_scratch,
exit: &mut exit_scratch,
store_value_memories: store_value_memories.as_ptr(),
store_value_memory_count: store_value_memories.len(),
lent_molten_value_memories: lent_molten_value_memories.as_ptr(),
lent_molten_value_memory_count: lent_molten_value_memories.len(),
molten: (&raw mut self.molten).cast::<core::ffi::c_void>(),
molten_bytes: crate::task::molten_bytes_abi,
array_new: crate::task::array_new_abi,
array_store: crate::task::array_store_abi,
array_load: crate::task::array_load_abi,
array_len: crate::task::array_len_abi,
ordered_begin_probe: crate::task::ordered_begin_probe_abi,
ordered_probe_key: crate::task::ordered_probe_key_abi,
ordered_probe_value: crate::task::ordered_probe_value_abi,
ordered_begin_insert: crate::task::ordered_begin_insert_abi,
ordered_insert_inspect: crate::task::ordered_insert_inspect_abi,
ordered_insert_advance: crate::task::ordered_insert_advance_abi,
ordered_insert_commit: crate::task::ordered_insert_commit_abi,
ordered_begin_iterate: crate::task::ordered_begin_iterate_abi,
ordered_iterate_row: crate::task::ordered_iterate_row_abi,
ordered_len: crate::task::ordered_len_abi,
string_concat: crate::task::string_concat_abi,
string_trim: crate::task::string_trim_abi,
string_lines: crate::task::string_lines_abi,
int_to_string: crate::task::int_to_string_abi,
string_contains: crate::task::string_contains_abi,
string_is_numeric: crate::task::string_is_numeric_abi,
string_split_once: crate::task::string_split_once_abi,
string_parse_int: crate::task::string_parse_int_abi,
byte_project: crate::task::byte_project_abi,
path_join: crate::task::path_join_abi,
publications: (&raw mut self.publications).cast::<core::ffi::c_void>(),
publish: crate::task::publish_abi,
env_alloc: crate::task::env_alloc_abi,
env_bytes: crate::task::env_bytes_abi,
};
let f = unsafe { compiled.native.chain_fn::<Ctx>(frame.resume) };
unsafe { f(&mut ctx) };
for (dst, &src) in ready.iter_mut().zip(&self.ready_scratch) {
*dst = src != 0;
}
let new_prog_pos = (ctx.prog as usize - entry_prog as usize) / size_of::<u64>();
{
let top = self.frames.last_mut().expect("frame");
top.prog_pos = new_prog_pos;
}
match exit_scratch {
EXIT_AWAIT_PARKED => {
let input = u32::try_from(index_scratch).expect("input fits u32");
let top = self.frames.last_mut().expect("frame");
top.resume = usize::try_from(resume_scratch).expect("offset");
if self.parked_on != Some(input) {
self.parked_on = Some(input);
self.trace.push(TaskEvent::Parked { input });
}
return Ok(TaskStep::Parked { input });
}
EXIT_CALL => {
let continuation = resume_scratch;
let desc = compiled.calls[&continuation].clone();
{
let top = self.frames.last_mut().expect("frame");
top.resume = usize::try_from(continuation).expect("offset");
}
let target_is_frame = matches!(desc.target, CallTarget::Frame(_));
let callable_offset = match &desc.target {
CallTarget::Frame(offset) => Some(*offset),
CallTarget::Static(_) => None,
};
let callee_id = match desc.target {
CallTarget::Static(callee) => callee,
CallTarget::Frame(offset) => {
let at = frame.base + offset as usize;
let raw = i64::from_le_bytes(
self.arena[at..at + 8]
.try_into()
.expect("closure function id occupies one word"),
);
if raw < 0 {
let Some(verified) = verified else {
panic!("indirect callee is a non-negative local function id");
};
return Err(TaskFault::IndirectCalleeNegative {
site: fault_site(verified, frame.fn_id, desc.pc)?,
value: raw,
});
}
match u32::try_from(raw) {
Ok(callee) => FnId(callee),
Err(_) => {
let Some(verified) = verified else {
panic!("indirect callee fits a local function id");
};
let site = fault_site(verified, frame.fn_id, desc.pc)?;
let function_count = site
.call
.and_then(|call| match call {
CallSiteFacts::Indirect { obligation, .. } => {
Some(obligation.function_count)
}
CallSiteFacts::Direct { .. } => None,
})
.unwrap_or_else(|| verified.program().fns.len());
return Err(TaskFault::IndirectCalleeOutOfRange {
site,
callee: raw,
function_count,
});
}
}
}
};
if target_is_frame && let Some(verified) = verified {
check_indirect_callee_contract(verified, frame.fn_id, desc.pc, callee_id)?;
}
let callee = &program.fns[callee_id.0 as usize];
let callee_base = self.alloc_frame(callee);
for (index, copy) in desc.args.iter().enumerate() {
let src = frame.base + copy.src as usize;
let destination = if target_is_frame {
verified.map_or(copy.dst, |verified| {
crate::task::indirect_call_destination(
verified, callee_id, index, copy.dst,
)
})
} else {
copy.dst
};
let dst = callee_base + destination as usize;
self.arena.copy_within(src..src + copy.size as usize, dst);
}
if let (Some(offset), Some(verified)) = (callable_offset, verified) {
crate::task::unbox_call_environment(
&mut self.arena,
&self.molten,
verified,
callee_id,
&crate::task::CallEnvironmentSite {
env_word: frame.base + offset as usize + 8,
callee_base,
arg_count: desc.args.len(),
caller: frame.fn_id,
pc: desc.pc,
},
)?;
}
self.frames.push(JitFrame {
fn_id: callee_id,
base: callee_base,
resume: 0,
prog_pos: 0,
ret_to: Some(frame.base + desc.ret as usize),
});
self.trace.push(TaskEvent::FrameEntered(callee_id));
}
EXIT_RET => {
let src = frame.base + usize::try_from(resume_scratch).expect("src");
let size = usize::try_from(index_scratch).expect("size");
let popped = self.frames.pop().expect("frame to return from");
self.trace.push(TaskEvent::FrameExited(popped.fn_id));
match popped.ret_to {
Some(ret_to) => {
self.arena.copy_within(src..src + size, ret_to);
}
None => {
self.result = self.arena[src..src + size].to_vec();
return Ok(TaskStep::Done);
}
}
}
EXIT_TRACE_MARK => {
let continuation = usize::try_from(resume_scratch).expect("offset");
let id = u32::try_from(index_scratch).expect("mark id");
let top = self.frames.last_mut().expect("frame");
top.resume = continuation;
self.trace.push(TaskEvent::Mark(id));
}
EXIT_HOST_CALL => {
let continuation = usize::try_from(resume_scratch).expect("offset");
let host = usize::try_from(index_scratch).expect("host index");
{
let top = self.frames.last_mut().expect("frame");
top.resume = continuation;
}
let end = frame.base + compiled.frame_size;
hosts[host](&mut self.arena[frame.base..end]);
}
EXIT_HOST_CALL_YIELD => {
let continuation = usize::try_from(resume_scratch).expect("offset");
let host = usize::try_from(index_scratch).expect("host index");
{
let top = self.frames.last_mut().expect("frame");
top.resume = continuation;
}
let end = frame.base + compiled.frame_size;
hosts[host](&mut self.arena[frame.base..end]);
return Ok(TaskStep::Yielded);
}
EXIT_COMPARE_LEFT_UNRESIDENT | EXIT_COMPARE_RIGHT_UNRESIDENT => {
let Some(verified) = verified else {
panic!("legacy raw CompareValueBytes operand is not resident");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::UnresidentCompareValueBytes {
site: fault_site(verified, frame.fn_id, pc)?,
side: if exit_scratch == EXIT_COMPARE_LEFT_UNRESIDENT {
CompareSide::Left
} else {
CompareSide::Right
},
handle: resume_scratch as i64,
});
}
EXIT_STRING_CONCAT_LEFT_UNRESIDENT | EXIT_STRING_CONCAT_RIGHT_UNRESIDENT => {
let Some(verified) = verified else {
panic!("legacy raw StringConcat operand is not resident");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::UnresidentStringConcatOperand {
site: fault_site(verified, frame.fn_id, pc)?,
side: if exit_scratch == EXIT_STRING_CONCAT_LEFT_UNRESIDENT {
CompareSide::Left
} else {
CompareSide::Right
},
handle: resume_scratch as i64,
});
}
EXIT_STRING_CONCAT_ALLOCATION => {
let Some(verified) = verified else {
panic!("legacy raw StringConcat allocation failed");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::StringConcatAllocationFailed {
site: fault_site(verified, frame.fn_id, pc)?,
});
}
EXIT_BYTE_PROJECT_SOURCE_UNRESIDENT => {
let Some(verified) = verified else {
panic!("legacy raw ByteProject source is not resident");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::UnresidentByteProjectSource {
site: fault_site(verified, frame.fn_id, pc)?,
handle: resume_scratch as i64,
});
}
EXIT_BYTE_PROJECT_ALLOCATION => {
let Some(verified) = verified else {
panic!("legacy raw ByteProject allocation failed");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::ByteProjectionAllocationFailed {
site: fault_site(verified, frame.fn_id, pc)?,
});
}
EXIT_INT_TO_STRING_ALLOCATION => {
let Some(verified) = verified else {
panic!("legacy raw IntToString allocation failed");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::IntToStringAllocationFailed {
site: fault_site(verified, frame.fn_id, pc)?,
});
}
EXIT_PATH_JOIN_BASE_UNRESIDENT | EXIT_PATH_JOIN_SEGMENT_UNRESIDENT => {
let Some(verified) = verified else {
panic!("legacy raw PathJoin operand is not resident");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::UnresidentPathJoinOperand {
site: fault_site(verified, frame.fn_id, pc)?,
side: if exit_scratch == EXIT_PATH_JOIN_BASE_UNRESIDENT {
CompareSide::Left
} else {
CompareSide::Right
},
handle: resume_scratch as i64,
});
}
EXIT_PATH_JOIN_ALLOCATION => {
let Some(verified) = verified else {
panic!("legacy raw PathJoin allocation failed");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::PathJoinAllocationFailed {
site: fault_site(verified, frame.fn_id, pc)?,
});
}
EXIT_PUBLICATION_ALLOCATION => {
let Some(verified) = verified else {
panic!("legacy raw Publish allocation failed");
};
let pc = usize::try_from(index_scratch).expect("pc");
return Err(TaskFault::PublicationAllocationFailed {
site: fault_site(verified, frame.fn_id, pc)?,
});
}
EXIT_ENV_UNRESIDENT
| EXIT_ENV_STALE
| EXIT_ENV_OUT_OF_RANGE
| EXIT_ENV_ALLOCATION => {
let Some(verified) = verified else {
panic!("boxed environment access requires VerifiedProgram");
};
let pc = usize::try_from(index_scratch).expect("pc");
let kind = match exit_scratch {
EXIT_ENV_STALE => EnvironmentFaultKind::Stale,
EXIT_ENV_OUT_OF_RANGE => EnvironmentFaultKind::OutOfRange,
EXIT_ENV_ALLOCATION => EnvironmentFaultKind::AllocationFailed,
_ => EnvironmentFaultKind::Unresident,
};
return Err(TaskFault::Environment {
site: fault_site(verified, frame.fn_id, pc)?,
kind,
handle: resume_scratch as i64,
});
}
EXIT_INVALID_ENUM_SELECTOR | EXIT_ENUM_PROJECTION_MISMATCH => {
let Some(verified) = verified else {
panic!("typed structural operation requires VerifiedProgram");
};
let pc = usize::try_from(index_scratch).expect("verified pc fits usize");
let site = fault_site(verified, frame.fn_id, pc)?;
let actual = resume_scratch as i64;
let function = &verified.contract().functions[frame.fn_id.0 as usize];
let (value, expected) = match site.op.as_ref() {
Op::EnumIsVariant { value, .. } => (*value, None),
Op::EnumProjectChecked { value, variant, .. } => {
(*value, Some(i64::from(*variant)))
}
_ => {
return Err(TaskFault::NativeFaultExit {
function: frame.fn_id,
code: exit_scratch,
});
}
};
let region = &function.frame.regions[value.0 as usize];
let value_shape = region.value_shape.unwrap();
if exit_scratch == EXIT_ENUM_PROJECTION_MISMATCH {
return Err(TaskFault::EnumProjectionMismatch {
site,
value_shape,
expected: expected.unwrap(),
actual,
});
}
let ValueShapeKind::Enum { variants, .. } =
&verified.contract().value_shapes[value_shape.0 as usize].kind
else {
unreachable!();
};
return Err(TaskFault::InvalidEnumSelector {
site,
value_shape,
expected: (0..variants.len()).map(|variant| variant as i64).collect(),
actual,
});
}
EXIT_INVALID_ARRAY_STATUS => {
let Some(verified) = verified else {
panic!("array status validation requires VerifiedProgram");
};
let pc = usize::try_from(index_scratch).expect("verified pc fits usize");
return Err(TaskFault::InvalidArrayStatus {
site: fault_site(verified, frame.fn_id, pc)?,
actual: resume_scratch as i64,
});
}
EXIT_INVALID_ORDERED_STATUS => {
let Some(verified) = verified else {
panic!("ordered status validation requires VerifiedProgram");
};
let pc = usize::try_from(index_scratch).expect("verified pc fits usize");
return Err(TaskFault::InvalidOrderedStatus {
site: fault_site(verified, frame.fn_id, pc)?,
actual: resume_scratch as i64,
});
}
EXIT_INVALID_STRING_STATUS => {
let Some(verified) = verified else {
panic!("string status validation requires VerifiedProgram");
};
let pc = usize::try_from(index_scratch).expect("verified pc fits usize");
return Err(TaskFault::InvalidStringStatus {
site: fault_site(verified, frame.fn_id, pc)?,
actual: resume_scratch as i64,
});
}
code => {
if verified.is_some() {
return Err(TaskFault::NativeFaultExit {
function: frame.fn_id,
code,
});
}
panic!("task chain exited with code {code} (fell through without Ret?)");
}
}
}
}
}
pub struct JitRunning<'p> {
pub program: &'p JitProgram,
pub task: JitTask,
}
impl Advance for JitRunning<'_> {
fn advance(
&mut self,
ready: &mut [bool],
awaited: &[i64],
hosts: &mut [HostFn<'_>],
value_memories: ValueMemories<'_>,
) -> TaskStep {
self.task.run_hosted_with_value_memories(
self.program,
ready,
awaited,
hosts,
value_memories,
)
}
fn result_bytes(&self) -> &[u8] {
&self.task.result
}
}
fn check_indirect_callee_contract(
verified: &VerifiedProgram,
function: FnId,
pc: usize,
callee: FnId,
) -> Result<(), TaskFault> {
let site = fault_site(verified, function, pc)?;
let Some(CallSiteFacts::Indirect { obligation, .. }) = site.call else {
return Err(TaskFault::MissingIndirectCallFacts { site });
};
let callee_index = callee.0 as usize;
if callee_index >= obligation.function_count {
return Err(TaskFault::IndirectCalleeOutOfRange {
site,
callee: i64::from(callee.0),
function_count: obligation.function_count,
});
}
let actual = verified
.facts()
.function(callee)
.and_then(|function| function.call_contract());
if actual != Some(obligation.contract) {
return Err(TaskFault::IndirectCalleeContractMismatch {
site,
callee,
expected: obligation.contract,
actual,
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mem::Layout;
use crate::task::{
ARRAY_POISON_HANDLE, ArrayOpStatus, Fn as TaskFn, Task, ValueMemories, ValueMemory,
};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn jit_tasks_await_real_futures() {
let program = Program {
fns: vec![TaskFn {
frame: Layout { size: 24, align: 8 },
code: vec![
Op::Await { dst: 0, input: 0 },
Op::Await { dst: 8, input: 1 },
Op::AddI64 {
dst: 16,
a: 0,
b: 8,
},
Op::Ret { src: 16, size: 8 },
],
}],
};
let Some(jit) = JitProgram::compile(&program) else {
return;
};
let running = JitRunning {
program: &jit,
task: JitTask::spawn(&jit, FnId(0)),
};
let slow: core::pin::Pin<Box<dyn core::future::Future<Output = i64>>> = Box::pin(async {
tokio::time::sleep(std::time::Duration::from_millis(40)).await;
40
});
let fast: core::pin::Pin<Box<dyn core::future::Future<Output = i64>>> =
Box::pin(async { 2 });
let result = crate::task::TaskExec::new(running, vec![slow, fast], vec![]).await;
assert_eq!(i64::from_le_bytes(result[..8].try_into().unwrap()), 42);
}
#[test]
fn trace_marks_record_in_innards_and_vanish_in_production() {
let program = Program {
fns: vec![
TaskFn {
frame: Layout { size: 16, align: 8 },
code: vec![
Op::Trace { id: 10 },
Op::ConstI64 { dst: 0, value: 5 },
Op::Call {
callee: FnId(1),
args: vec![ArgCopy {
src: 0,
dst: 0,
size: 8,
}],
ret: 8,
},
Op::Trace { id: 11 },
Op::Ret { src: 8, size: 8 },
],
},
TaskFn {
frame: Layout { size: 16, align: 8 },
code: vec![
Op::Trace { id: 20 },
Op::AddI64 { dst: 8, a: 0, b: 0 },
Op::Ret { src: 8, size: 8 },
],
},
],
};
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(interp.run(&program, &mut [], &[]), TaskStep::Done);
assert_eq!(interp.result_i64(), 10);
let marks: Vec<_> = interp
.trace
.iter()
.filter_map(|e| match e {
TaskEvent::Mark(id) => Some(*id),
_ => None,
})
.collect();
assert_eq!(marks, vec![10, 20, 11]);
let mut prod = Task::spawn_with_mode(&program, FnId(0), TraceMode::Production);
assert_eq!(prod.run(&program, &mut [], &[]), TaskStep::Done);
assert_eq!(prod.result_i64(), 10);
assert!(!prod.trace.iter().any(|e| matches!(e, TaskEvent::Mark(_))));
let stripped_of_marks: Vec<_> = interp
.trace
.iter()
.copied()
.filter(|e| !matches!(e, TaskEvent::Mark(_)))
.collect();
assert_eq!(prod.trace, stripped_of_marks);
if let Some(jit) = JitProgram::compile(&program) {
let mut t = JitTask::spawn(&jit, FnId(0));
assert_eq!(t.run(&jit, &mut [], &[]), TaskStep::Done);
assert_eq!(t.result_i64(), 10);
assert_eq!(t.trace, interp.trace);
}
if let Some(jit) = JitProgram::compile_with_mode(&program, TraceMode::Production) {
let mut t = JitTask::spawn(&jit, FnId(0));
assert_eq!(t.run(&jit, &mut [], &[]), TaskStep::Done);
assert_eq!(t.result_i64(), 10);
assert_eq!(t.trace, prod.trace);
}
}
#[test]
fn f64_arithmetic_matches_the_interpreter_bitwise() {
let program = Program {
fns: vec![TaskFn {
frame: Layout { size: 32, align: 8 },
code: vec![
Op::ConstF64 {
dst: 0,
bits: 2.5f64.to_bits(),
},
Op::Await { dst: 8, input: 0 },
Op::MulF64 {
dst: 16,
a: 0,
b: 8,
},
Op::ConstF64 {
dst: 24,
bits: 0.125f64.to_bits(),
},
Op::AddF64 {
dst: 16,
a: 16,
b: 24,
},
Op::Ret { src: 16, size: 8 },
],
}],
};
let awaited_bits = 3.25f64.to_bits() as i64;
differential(
&program,
FnId(0),
&[(&[false], &[0]), (&[true], &[awaited_bits])],
);
let mut interp = Task::spawn(&program, FnId(0));
let mut ready = [true];
interp.run(&program, &mut ready, &[awaited_bits]);
let bits = interp.result_i64() as u64;
assert_eq!(f64::from_bits(bits), 2.5 * 3.25 + 0.125);
}
#[test]
fn sync_host_calls_match_the_interpreter_and_never_park() {
let program = Program {
fns: vec![TaskFn {
frame: Layout { size: 16, align: 8 },
code: vec![
Op::ConstI64 { dst: 0, value: 20 },
Op::HostCall { host: 0 },
Op::AddI64 { dst: 8, a: 8, b: 0 },
Op::Ret { src: 8, size: 8 },
],
}],
};
let host_impl = |frame: &mut [u8]| {
let v = i64::from_le_bytes(frame[0..8].try_into().unwrap());
frame[8..16].copy_from_slice(&(v * 2 + 1).to_le_bytes());
};
let mut interp_calls = 0u32;
let mut interp_host = |frame: &mut [u8]| {
interp_calls += 1;
host_impl(frame);
};
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(
interp.run_hosted(&program, &mut [], &[], &mut [&mut interp_host]),
TaskStep::Done
);
assert_eq!(interp.result_i64(), 61);
assert_eq!(interp_calls, 1);
assert!(
!interp
.trace
.iter()
.any(|e| matches!(e, TaskEvent::Parked { .. }))
);
let Some(jit) = JitProgram::compile(&program) else {
return;
};
let mut jit_calls = 0u32;
let mut jit_host = |frame: &mut [u8]| {
jit_calls += 1;
host_impl(frame);
};
let mut task = JitTask::spawn(&jit, FnId(0));
assert_eq!(
task.run_hosted(&jit, &mut [], &[], &mut [&mut jit_host]),
TaskStep::Done
);
assert_eq!(task.result_i64(), 61);
assert_eq!(jit_calls, 1);
assert_eq!(task.trace, interp.trace);
}
#[test]
fn await_reads_an_input_beyond_the_initial_readiness_capacity() {
let program = Program {
fns: vec![TaskFn {
frame: Layout {
size: 1528,
align: 8,
},
code: vec![
Op::Await {
dst: 1488,
input: 18,
},
Op::Ret { src: 1488, size: 8 },
],
}],
};
let pending_ready = vec![false; 16];
let pending_awaited = vec![0; 16];
let mut ready = vec![false; 20];
ready[18] = true;
let mut awaited = vec![0; 20];
awaited[18] = 8;
differential(
&program,
FnId(0),
&[(&pending_ready, &pending_awaited), (&ready, &awaited)],
);
}
fn differential(program: &Program, entry: FnId, schedule: &[(&[bool], &[i64])]) {
differential_with_mode(program, entry, schedule, TraceMode::Innards);
}
fn differential_with_mode(
program: &Program,
entry: FnId,
schedule: &[(&[bool], &[i64])],
mode: TraceMode,
) {
let mut interp = Task::spawn_with_mode(program, entry, mode);
let mut interp_steps = Vec::new();
for (ready, awaited) in schedule {
let mut ready = ready.to_vec();
let step = interp.run(program, &mut ready, awaited);
interp_steps.push(step);
if step == TaskStep::Done {
break;
}
}
let Some(jit) = JitProgram::compile_with_mode(program, mode) else {
assert!(
!available(),
"task JIT refused a program on a native copy-and-patch target"
);
return;
};
let mut task = JitTask::spawn(&jit, entry);
let mut jit_steps = Vec::new();
for (ready, awaited) in schedule {
let mut ready = ready.to_vec();
let step = task.run(&jit, &mut ready, awaited);
jit_steps.push(step);
if step == TaskStep::Done {
break;
}
}
assert_eq!(jit_steps, interp_steps, "step sequences diverge");
assert_eq!(task.result, interp.result, "results diverge");
assert_eq!(task.trace, interp.trace, "frame traces diverge");
}
#[test]
fn indirect_calls_match_the_interpreter() {
let program = Program {
fns: vec![
TaskFn {
frame: frame_of_i64s(3),
code: vec![
Op::ConstI64 { dst: 0, value: 1 },
Op::ConstI64 { dst: 8, value: 21 },
Op::CallIndirect {
callee: 0,
args: vec![ArgCopy {
src: 8,
dst: 0,
size: 8,
}],
ret: 16,
},
Op::Ret { src: 16, size: 8 },
],
},
TaskFn {
frame: frame_of_i64s(2),
code: vec![
Op::AddI64 { dst: 8, a: 0, b: 0 },
Op::Ret { src: 8, size: 8 },
],
},
],
};
differential(&program, FnId(0), &[(&[], &[])]);
}
#[test]
fn total_wrapping_i64_division_matches_the_interpreter() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(12),
code: vec![
Op::ConstI64 { dst: 0, value: 10 },
Op::ConstI64 { dst: 8, value: 2 },
Op::ConstI64 { dst: 16, value: 10 },
Op::ConstI64 { dst: 24, value: 0 },
Op::ConstI64 {
dst: 32,
value: i64::MIN,
},
Op::ConstI64 { dst: 40, value: -1 },
Op::ConstI64 { dst: 48, value: -9 },
Op::ConstI64 { dst: 56, value: 2 },
Op::DivI64 {
dst: 64,
a: 0,
b: 8,
},
Op::DivI64 {
dst: 72,
a: 16,
b: 24,
},
Op::DivI64 {
dst: 80,
a: 32,
b: 40,
},
Op::DivI64 {
dst: 88,
a: 48,
b: 56,
},
Op::Ret { src: 64, size: 32 },
],
}],
};
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(interp.run(&program, &mut [], &[]), TaskStep::Done);
let values = interp
.result
.chunks_exact(8)
.map(|word| i64::from_le_bytes(word.try_into().expect("one result word")))
.collect::<Vec<_>>();
assert_eq!(values, [5, 0, i64::MIN, -4]);
differential(&program, FnId(0), &[(&[], &[])]);
}
#[test]
fn seeded_root_args_match_the_interpreter_and_feed_branches() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(5),
code: vec![
Op::GtI64 {
dst: 24,
a: 0,
b: 8,
},
Op::JumpIfZero {
value: 24,
target: 5,
},
Op::SubI64 {
dst: 32,
a: 0,
b: 8,
},
Op::MulI64 {
dst: 32,
a: 32,
b: 16,
},
Op::Jump { target: 7 },
Op::SubI64 {
dst: 32,
a: 8,
b: 0,
},
Op::MulI64 {
dst: 32,
a: 32,
b: 16,
},
Op::Ret { src: 32, size: 8 },
],
}],
};
let mut interp = Task::spawn(&program, FnId(0));
interp.write_i64(0, 11);
interp.write_i64(8, 4);
interp.write_i64(16, 3);
let interp_steps = vec![interp.run(&program, &mut [], &[])];
let Some(jit) = JitProgram::compile(&program) else {
assert!(
!available(),
"task JIT refused a seeded-args program on a native copy-and-patch target"
);
return;
};
let mut task = JitTask::spawn(&jit, FnId(0));
task.write_i64(0, 11);
task.write_i64(8, 4);
task.write_i64(16, 3);
let jit_steps = vec![task.run(&jit, &mut [], &[])];
assert_eq!(jit_steps.len(), interp_steps.len(), "step counts diverge");
assert_eq!(jit_steps, interp_steps, "step sequences diverge");
assert_eq!(task.result, interp.result, "results diverge");
assert_eq!(task.result_i64(), 21);
assert_eq!(task.trace, interp.trace, "frame traces diverge");
}
#[test]
fn i64_comparisons_match_the_interpreter() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(10),
code: vec![
Op::ConstI64 { dst: 0, value: 3 },
Op::ConstI64 { dst: 8, value: 5 },
Op::ConstI64 { dst: 16, value: 5 },
Op::EqI64 {
dst: 24,
a: 8,
b: 16,
},
Op::NeI64 {
dst: 32,
a: 0,
b: 8,
},
Op::LtI64 {
dst: 40,
a: 0,
b: 8,
},
Op::LeI64 {
dst: 48,
a: 8,
b: 16,
},
Op::GtI64 {
dst: 56,
a: 8,
b: 0,
},
Op::GeI64 {
dst: 64,
a: 8,
b: 16,
},
Op::AddI64 {
dst: 72,
a: 24,
b: 32,
},
Op::AddI64 {
dst: 72,
a: 72,
b: 40,
},
Op::AddI64 {
dst: 72,
a: 72,
b: 48,
},
Op::AddI64 {
dst: 72,
a: 72,
b: 56,
},
Op::AddI64 {
dst: 72,
a: 72,
b: 64,
},
Op::Ret { src: 72, size: 8 },
],
}],
};
differential(&program, FnId(0), &[(&[], &[])]);
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(interp.run(&program, &mut [], &[]), TaskStep::Done);
assert_eq!(interp.result_i64(), 6);
}
#[test]
fn value_byte_comparison_matches_the_interpreter_and_checks_identity_residency() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(6),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 1 },
Op::ConstI64 { dst: 16, value: 0 },
Op::CompareValueBytes {
dst: 24,
a: 0,
b: 8,
},
Op::CompareValueBytes {
dst: 32,
a: 8,
b: 0,
},
Op::CompareValueBytes {
dst: 40,
a: 16,
b: 16,
},
Op::Ret { src: 24, size: 24 },
],
}],
};
let store = [ValueMemory::from_slice(b"b"), ValueMemory::from_slice(b"a")];
let memories = ValueMemories {
store: &store,
molten: &[],
};
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(
interp.run_hosted_with_value_memories(&program, &mut [], &[], &mut [], memories,),
TaskStep::Done
);
assert_eq!(
interp
.result
.chunks_exact(8)
.map(|word| i64::from_le_bytes(word.try_into().expect("one result word")))
.collect::<Vec<_>>(),
[2, 0, 1]
);
let Some(jit) = JitProgram::compile(&program) else {
assert!(
!available(),
"task JIT refused value-byte comparison on a native target"
);
return;
};
let mut task = JitTask::spawn(&jit, FnId(0));
assert_eq!(
task.run_hosted_with_value_memories(&jit, &mut [], &[], &mut [], memories),
TaskStep::Done
);
assert_eq!(task.result, interp.result);
assert_eq!(task.trace, interp.trace);
}
fn array_words_payload(elem_schema_ref: i64, elements: &[i64]) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&0i64.to_le_bytes());
bytes.extend_from_slice(&elem_schema_ref.to_le_bytes());
bytes.extend_from_slice(&(elements.len() as i64).to_le_bytes());
for element in elements {
bytes.extend_from_slice(&element.to_le_bytes());
}
bytes
}
fn array_elements_payload(
elem_schema_ref: i64,
elem_width: i64,
elements: &[&[u8]],
) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&1i64.to_le_bytes());
bytes.extend_from_slice(&elem_schema_ref.to_le_bytes());
bytes.extend_from_slice(&(elements.len() as i64).to_le_bytes());
bytes.extend_from_slice(&elem_width.to_le_bytes());
for element in elements {
assert_eq!(element.len(), elem_width as usize);
bytes.extend_from_slice(element);
}
bytes
}
fn result_words(bytes: &[u8]) -> Vec<i64> {
bytes
.chunks_exact(8)
.map(|word| i64::from_le_bytes(word.try_into().expect("one result word")))
.collect()
}
fn run_array_program_with_memories(program: &Program, memories: ValueMemories<'_>) -> Vec<i64> {
let mut interp = Task::spawn(program, FnId(0));
assert_eq!(
interp.run_hosted_with_value_memories(program, &mut [], &[], &mut [], memories),
TaskStep::Done
);
let Some(jit) = JitProgram::compile(program) else {
assert!(
!available(),
"task JIT refused an array substrate program on a native target"
);
return result_words(&interp.result);
};
let mut task = JitTask::spawn(&jit, FnId(0));
assert_eq!(
task.run_hosted_with_value_memories(&jit, &mut [], &[], &mut [], memories),
TaskStep::Done
);
assert_eq!(task.result, interp.result);
assert_eq!(task.trace, interp.trace);
result_words(&interp.result)
}
#[test]
fn local_molten_handles_do_not_shadow_lent_molten_payloads() {
const SCHEMA: i64 = 0x55aa;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(10),
code: vec![
Op::ConstI64 { dst: 8, value: 1 },
Op::ArrayNew {
dst: 0,
status: 16,
count_slot: 8,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 24, value: -1 },
Op::ConstI64 { dst: 32, value: 0 },
Op::LoadArrayWord {
dst: 40,
present: 48,
array: 24,
index: 32,
elem_schema_ref: SCHEMA,
},
Op::LoadArrayWord {
dst: 56,
present: 64,
array: 0,
index: 32,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 16, size: 56 },
],
}],
};
let lent = array_words_payload(SCHEMA, &[99]);
let molten = [ValueMemory::from_slice(&lent)];
let memories = ValueMemories {
store: &[],
molten: &molten,
};
assert_eq!(
run_array_program_with_memories(&program, memories),
vec![ArrayOpStatus::Ok as i64, -1, 0, 99, 1, 0, 0,]
);
}
#[test]
fn dynamic_count_and_checked_oversized_allocation_match_between_lanes() {
const SCHEMA: i64 = 0x7777;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(10),
code: vec![
Op::ConstI64 { dst: 0, value: 2 },
Op::ArrayNew {
dst: 8,
status: 16,
count_slot: 0,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::LoadArrayLen {
dst: 24,
status: 32,
array: 8,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 {
dst: 64,
value: i64::MAX,
},
Op::ArrayNew {
dst: 48,
status: 56,
count_slot: 64,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 16, size: 48 },
],
}],
};
assert_eq!(
run_array_program_with_memories(&program, ValueMemories::empty()),
vec![
ArrayOpStatus::Ok as i64,
2,
ArrayOpStatus::Ok as i64,
0,
ARRAY_POISON_HANDLE,
ArrayOpStatus::Overflow as i64,
]
);
}
#[test]
fn failed_array_allocations_leave_poison_in_both_lanes() {
const SCHEMA: i64 = 0x7778;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(6),
code: vec![
Op::ConstI64 { dst: 0, value: -1 },
Op::ArrayNew {
dst: 8,
status: 16,
count_slot: 0,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 {
dst: 40,
value: isize::MAX as i64,
},
Op::ArrayNew {
dst: 24,
status: 32,
count_slot: 40,
elem_width: 1,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 8, size: 32 },
],
}],
};
assert_eq!(
run_array_program_with_memories(&program, ValueMemories::empty()),
vec![
ARRAY_POISON_HANDLE,
ArrayOpStatus::Overflow as i64,
ARRAY_POISON_HANDLE,
ArrayOpStatus::Overflow as i64,
]
);
}
#[test]
fn schema_mismatch_and_local_out_of_range_stores_report_status() {
const SCHEMA: i64 = 0x4444;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(7),
code: vec![
Op::ConstI64 { dst: 0, value: 1 },
Op::ArrayNew {
dst: 8,
status: 16,
count_slot: 0,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 24, value: 0 },
Op::ConstI64 { dst: 32, value: 77 },
Op::ArrayStore {
status: 40,
array: 8,
index: 24,
src: 32,
elem_width: 8,
elem_schema_ref: SCHEMA ^ 1,
},
Op::ConstI64 { dst: 24, value: 1 },
Op::ArrayStore {
status: 48,
array: 8,
index: 24,
src: 32,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 16, size: 40 },
],
}],
};
assert_eq!(
run_array_program_with_memories(&program, ValueMemories::empty()),
vec![
ArrayOpStatus::Ok as i64,
1,
77,
ArrayOpStatus::SchemaMismatch as i64,
ArrayOpStatus::OutOfRange as i64,
]
);
}
#[test]
fn multiword_elements_construct_fill_and_read_in_both_lanes() {
const SCHEMA: i64 = 0x2222;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(15),
code: vec![
Op::ConstI64 { dst: 0, value: 2 },
Op::ArrayNew {
dst: 8,
status: 16,
count_slot: 0,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 24, value: 0 },
Op::ConstI64 { dst: 32, value: 11 },
Op::ConstI64 { dst: 40, value: 12 },
Op::ArrayStore {
status: 48,
array: 8,
index: 24,
src: 32,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 24, value: 1 },
Op::ConstI64 { dst: 32, value: 21 },
Op::ConstI64 { dst: 40, value: 22 },
Op::ArrayStore {
status: 56,
array: 8,
index: 24,
src: 32,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::LoadArray {
dst: 64,
status: 80,
array: 8,
index: 24,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 48, size: 48 },
],
}],
};
assert_eq!(
run_array_program_with_memories(&program, ValueMemories::empty()),
vec![
ArrayOpStatus::Ok as i64,
ArrayOpStatus::Ok as i64,
21,
22,
ArrayOpStatus::Ok as i64,
0,
]
);
}
#[test]
fn task_local_reads_require_whole_element_initialization() {
const SCHEMA: i64 = 0x2223;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(13),
code: vec![
Op::ConstI64 { dst: 0, value: 2 },
Op::ArrayNew {
dst: 8,
status: 16,
count_slot: 0,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 24, value: 0 },
Op::LoadArray {
dst: 48,
status: 64,
array: 8,
index: 24,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 {
dst: 32,
value: 0x1111,
},
Op::ConstI64 {
dst: 40,
value: 0x2222,
},
Op::ArrayStore {
status: 72,
array: 8,
index: 24,
src: 32,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::LoadArray {
dst: 48,
status: 80,
array: 8,
index: 24,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 24, value: 1 },
Op::LoadArray {
dst: 88,
status: 96,
array: 8,
index: 24,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 16, size: 88 },
],
}],
};
assert_eq!(
run_array_program_with_memories(&program, ValueMemories::empty()),
vec![
ArrayOpStatus::Ok as i64,
1,
0x1111,
0x2222,
0x1111,
0x2222,
ArrayOpStatus::Uninitialized as i64,
ArrayOpStatus::Ok as i64,
ArrayOpStatus::Ok as i64,
0,
ArrayOpStatus::Uninitialized as i64,
]
);
}
#[test]
fn malformed_invalid_width_mismatch_and_out_of_range_status_are_distinct() {
const SCHEMA: i64 = 0x3333;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(24),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 1 },
Op::LoadArray {
dst: 80,
status: 96,
array: 0,
index: 8,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 48, value: 1 },
Op::ConstI64 { dst: 56, value: 10 },
Op::LoadArray {
dst: 104,
status: 120,
array: 48,
index: 8,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 184, value: 2 },
Op::LoadArray {
dst: 128,
status: 144,
array: 0,
index: 56,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::LoadArray {
dst: 152,
status: 168,
array: 184,
index: 0,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::ArrayStore {
status: 176,
array: 0,
index: 8,
src: 80,
elem_width: 16,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 80, size: 104 },
],
}],
};
let first = [1i64.to_le_bytes(), 2i64.to_le_bytes()].concat();
let second = [3i64.to_le_bytes(), 4i64.to_le_bytes()].concat();
let valid = array_elements_payload(SCHEMA, 16, &[&first, &second]);
let malformed = [1u8, 2, 3];
let width_mismatch = array_elements_payload(SCHEMA, 16, &[&first]);
let store = [
ValueMemory::from_slice(&valid),
ValueMemory::from_slice(&malformed),
ValueMemory::from_slice(&width_mismatch),
];
let memories = ValueMemories {
store: &store,
molten: &[],
};
assert_eq!(
run_array_program_with_memories(&program, memories),
vec![
3,
4,
ArrayOpStatus::Ok as i64,
0,
0,
ArrayOpStatus::MalformedPayload as i64,
0,
0,
ArrayOpStatus::OutOfRange as i64,
0,
0,
ArrayOpStatus::WidthMismatch as i64,
ArrayOpStatus::InvalidHandle as i64,
]
);
}
#[test]
fn wider_advertised_element_width_is_width_mismatch_and_zeroes_destination() {
const SCHEMA: i64 = 0x5150;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(6),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 0 },
Op::ConstI64 {
dst: 24,
value: 0x7fff,
},
Op::LoadArray {
dst: 24,
status: 32,
array: 0,
index: 8,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 24, size: 16 },
],
}],
};
let elem = [1i64.to_le_bytes(), 2i64.to_le_bytes()].concat();
let wide = array_elements_payload(SCHEMA, 16, &[&elem]);
let store = [ValueMemory::from_slice(&wide)];
let memories = ValueMemories {
store: &store,
molten: &[],
};
assert_eq!(
run_array_program_with_memories(&program, memories),
vec![0, ArrayOpStatus::WidthMismatch as i64],
);
}
#[test]
fn invalid_tag_with_other_schema_is_malformed_not_schema_mismatch() {
const SCHEMA: i64 = 0x6161;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(5),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 0 },
Op::LoadArray {
dst: 16,
status: 24,
array: 0,
index: 8,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 24, size: 8 },
],
}],
};
let mut invalid = Vec::new();
invalid.extend_from_slice(&0x0badi64.to_le_bytes());
invalid.extend_from_slice(&(SCHEMA ^ 0x1).to_le_bytes());
invalid.extend_from_slice(&0i64.to_le_bytes());
assert_eq!(invalid.len(), 24);
let store = [ValueMemory::from_slice(&invalid)];
let memories = ValueMemories {
store: &store,
molten: &[],
};
assert_eq!(
run_array_program_with_memories(&program, memories),
vec![ArrayOpStatus::MalformedPayload as i64],
);
}
#[test]
fn nonresident_sentinel_is_invalid_handle_not_resident_empty_slice() {
const SCHEMA: i64 = 0x7777;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(7),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 1 },
Op::ConstI64 { dst: 16, value: 0 },
Op::LoadArray {
dst: 24,
status: 32,
array: 0,
index: 16,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::LoadArray {
dst: 40,
status: 48,
array: 8,
index: 16,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 24, size: 32 },
],
}],
};
let store = [ValueMemory::empty(), ValueMemory::from_slice(&[])];
let memories = ValueMemories {
store: &store,
molten: &[],
};
assert_eq!(
run_array_program_with_memories(&program, memories),
vec![
0,
ArrayOpStatus::InvalidHandle as i64,
0,
ArrayOpStatus::MalformedPayload as i64,
]
);
}
#[test]
fn store_backed_array_reads_match_the_interpreter() {
const SCHEMA: i64 = 0x5eed_1234_abcd_0001u64 as i64;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(10),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 2 },
Op::LoadArrayWord {
dst: 16,
present: 24,
array: 0,
index: 8,
elem_schema_ref: SCHEMA,
},
Op::LoadArrayLen {
dst: 32,
status: 40,
array: 0,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 8, value: 9 },
Op::LoadArrayWord {
dst: 48,
present: 56,
array: 0,
index: 8,
elem_schema_ref: SCHEMA,
},
Op::LoadArrayLen {
dst: 64,
status: 72,
array: 0,
elem_schema_ref: SCHEMA ^ 1,
},
Op::Ret { src: 16, size: 64 },
],
}],
};
let payload = array_words_payload(SCHEMA, &[10, 20, 30]);
let store = [ValueMemory::from_slice(&payload)];
let memories = ValueMemories {
store: &store,
molten: &[],
};
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(
interp.run_hosted_with_value_memories(&program, &mut [], &[], &mut [], memories),
TaskStep::Done
);
let words = interp
.result
.chunks_exact(8)
.map(|word| i64::from_le_bytes(word.try_into().expect("one result word")))
.collect::<Vec<_>>();
assert_eq!(
words,
[
30,
1,
3,
ArrayOpStatus::Ok as i64,
0,
0,
0,
ArrayOpStatus::SchemaMismatch as i64,
]
);
let Some(jit) = JitProgram::compile(&program) else {
assert!(
!available(),
"task JIT refused store-backed array reads on a native target"
);
return;
};
let mut task = JitTask::spawn(&jit, FnId(0));
assert_eq!(
task.run_hosted_with_value_memories(&jit, &mut [], &[], &mut [], memories),
TaskStep::Done
);
assert_eq!(task.result, interp.result);
assert_eq!(task.trace, interp.trace);
}
#[test]
fn load_array_word_decodes_canonical_little_endian_payloads() {
const SCHEMA: i64 = 0x5eed_4321_abcd_0001u64 as i64;
let element = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
let mut payload = Vec::new();
payload.extend_from_slice(&0i64.to_le_bytes());
payload.extend_from_slice(&SCHEMA.to_le_bytes());
payload.extend_from_slice(&1i64.to_le_bytes());
payload.extend_from_slice(&element);
let expected = i64::from_le_bytes(element);
let store = [ValueMemory::from_slice(&payload)];
let memories = ValueMemories {
store: &store,
molten: &[],
};
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(4),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::ConstI64 { dst: 8, value: 0 },
Op::LoadArrayWord {
dst: 16,
present: 24,
array: 0,
index: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 16, size: 16 },
],
}],
};
assert_eq!(
run_array_program_with_memories(&program, memories),
vec![expected, 1]
);
}
#[test]
fn molten_array_construction_matches_the_interpreter() {
const SCHEMA: i64 = 7;
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(10),
code: vec![
Op::ConstI64 { dst: 72, value: 3 },
Op::ArrayNew {
dst: 0,
status: 8,
count_slot: 72,
elem_width: 8,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 8, value: 0 },
Op::ConstI64 { dst: 16, value: 10 },
Op::ArrayStoreWord {
status: 72,
array: 0,
index: 8,
src: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 8, value: 1 },
Op::ConstI64 { dst: 16, value: 20 },
Op::ArrayStoreWord {
status: 72,
array: 0,
index: 8,
src: 16,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 8, value: 2 },
Op::ConstI64 { dst: 16, value: 30 },
Op::ArrayStoreWord {
status: 72,
array: 0,
index: 8,
src: 16,
elem_schema_ref: SCHEMA,
},
Op::LoadArrayWord {
dst: 24,
present: 32,
array: 0,
index: 8,
elem_schema_ref: SCHEMA,
},
Op::LoadArrayLen {
dst: 40,
status: 48,
array: 0,
elem_schema_ref: SCHEMA,
},
Op::ConstI64 { dst: 8, value: 3 },
Op::LoadArrayWord {
dst: 56,
present: 64,
array: 0,
index: 8,
elem_schema_ref: SCHEMA,
},
Op::Ret { src: 24, size: 48 },
],
}],
};
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(interp.run(&program, &mut [], &[]), TaskStep::Done);
let words = interp
.result
.chunks_exact(8)
.map(|word| i64::from_le_bytes(word.try_into().expect("one result word")))
.collect::<Vec<_>>();
assert_eq!(words, [30, 1, 3, ArrayOpStatus::Ok as i64, 0, 0]);
let Some(jit) = JitProgram::compile(&program) else {
assert!(
!available(),
"task JIT refused molten array construction on a native target"
);
return;
};
let mut task = JitTask::spawn(&jit, FnId(0));
assert_eq!(task.run(&jit, &mut [], &[]), TaskStep::Done);
assert_eq!(task.result, interp.result);
assert_eq!(task.trace, interp.trace);
}
#[test]
fn forward_jump_matches_the_interpreter() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(1),
code: vec![
Op::ConstI64 { dst: 0, value: 1 },
Op::Jump { target: 3 },
Op::ConstI64 { dst: 0, value: 99 },
Op::ConstI64 { dst: 0, value: 41 },
Op::Ret { src: 0, size: 8 },
],
}],
};
differential(&program, FnId(0), &[(&[], &[])]);
}
#[test]
fn backward_jump_loop_matches_the_interpreter() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(5),
code: vec![
Op::ConstI64 { dst: 0, value: 5 },
Op::ConstI64 { dst: 8, value: 0 },
Op::ConstI64 { dst: 16, value: 0 },
Op::ConstI64 { dst: 24, value: 1 },
Op::EqI64 {
dst: 32,
a: 0,
b: 16,
},
Op::JumpIfZero {
value: 32,
target: 7,
},
Op::Ret { src: 8, size: 8 },
Op::AddI64 { dst: 8, a: 8, b: 0 },
Op::SubI64 {
dst: 0,
a: 0,
b: 24,
},
Op::Jump { target: 4 },
],
}],
};
differential(&program, FnId(0), &[(&[], &[])]);
let mut interp = Task::spawn(&program, FnId(0));
assert_eq!(interp.run(&program, &mut [], &[]), TaskStep::Done);
assert_eq!(interp.result_i64(), 15);
}
#[test]
fn jump_if_zero_taken_and_not_taken_match_the_interpreter() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(3),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::JumpIfZero {
value: 0,
target: 4,
},
Op::ConstI64 { dst: 8, value: 99 },
Op::Jump { target: 5 },
Op::ConstI64 { dst: 8, value: 10 },
Op::ConstI64 { dst: 0, value: 1 },
Op::JumpIfZero {
value: 0,
target: 9,
},
Op::ConstI64 { dst: 16, value: 5 },
Op::AddI64 {
dst: 8,
a: 8,
b: 16,
},
Op::Ret { src: 8, size: 8 },
],
}],
};
differential(&program, FnId(0), &[(&[], &[])]);
}
#[test]
fn match_shaped_eq_jump_chain_matches_the_interpreter() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(6),
code: vec![
Op::ConstI64 { dst: 0, value: 2 },
Op::ConstI64 { dst: 8, value: 1 },
Op::EqI64 {
dst: 16,
a: 0,
b: 8,
},
Op::JumpIfZero {
value: 16,
target: 7,
},
Op::ConstI64 { dst: 24, value: 10 },
Op::CopyI64 { dst: 40, src: 24 },
Op::Jump { target: 15 },
Op::ConstI64 { dst: 8, value: 2 },
Op::EqI64 {
dst: 16,
a: 0,
b: 8,
},
Op::JumpIfZero {
value: 16,
target: 13,
},
Op::ConstI64 { dst: 24, value: 20 },
Op::CopyI64 { dst: 40, src: 24 },
Op::Jump { target: 15 },
Op::ConstI64 { dst: 24, value: 30 },
Op::CopyI64 { dst: 40, src: 24 },
Op::Ret { src: 40, size: 8 },
],
}],
};
differential(&program, FnId(0), &[(&[], &[])]);
}
#[test]
fn production_branch_target_into_trace_lands_on_next_emitted_stencil() {
let program = Program {
fns: vec![TaskFn {
frame: frame_of_i64s(2),
code: vec![
Op::ConstI64 { dst: 0, value: 0 },
Op::JumpIfZero {
value: 0,
target: 3,
},
Op::ConstI64 { dst: 8, value: 99 },
Op::Trace { id: 42 },
Op::ConstI64 { dst: 8, value: 77 },
Op::Ret { src: 8, size: 8 },
],
}],
};
differential_with_mode(&program, FnId(0), &[(&[], &[])], TraceMode::Production);
}
fn frame_of_i64s(n: usize) -> Layout {
Layout {
size: n * 8,
align: 8,
}
}
#[test]
fn straight_line_calls_match_the_interpreter() {
let program = Program {
fns: vec![
TaskFn {
frame: frame_of_i64s(3),
code: vec![
Op::ConstI64 { dst: 0, value: 6 },
Op::ConstI64 { dst: 8, value: 7 },
Op::Call {
callee: FnId(1),
args: vec![
ArgCopy {
src: 0,
dst: 0,
size: 8,
},
ArgCopy {
src: 8,
dst: 8,
size: 8,
},
],
ret: 16,
},
Op::AddI64 {
dst: 16,
a: 16,
b: 0,
},
Op::Ret { src: 16, size: 8 },
],
},
TaskFn {
frame: frame_of_i64s(3),
code: vec![
Op::MulI64 {
dst: 16,
a: 0,
b: 8,
},
Op::AddI64 {
dst: 16,
a: 16,
b: 0,
},
Op::Ret { src: 16, size: 8 },
],
},
],
};
differential(&program, FnId(0), &[(&[], &[])]);
}
#[test]
fn parking_two_frames_deep_matches_the_interpreter() {
let program = Program {
fns: vec![
TaskFn {
frame: frame_of_i64s(2),
code: vec![
Op::ConstI64 { dst: 0, value: 100 },
Op::Call {
callee: FnId(1),
args: vec![],
ret: 8,
},
Op::AddI64 { dst: 8, a: 8, b: 0 },
Op::Ret { src: 8, size: 8 },
],
},
TaskFn {
frame: frame_of_i64s(1),
code: vec![
Op::Await { dst: 0, input: 0 },
Op::AddI64 { dst: 0, a: 0, b: 0 },
Op::Ret { src: 0, size: 8 },
],
},
],
};
differential(&program, FnId(0), &[(&[false], &[0]), (&[true], &[21])]);
}
#[test]
fn inline_composites_match_the_interpreter() {
let mut caller_code = vec![Op::ConstI64 { dst: 0, value: 7 }];
for k in 0..6i64 {
caller_code.push(Op::ConstI64 { dst: 64, value: k });
caller_code.push(Op::ConstI64 {
dst: 72,
value: 10 * (k + 1),
});
caller_code.push(Op::StoreIndexedI64 {
base: 8,
index: 64,
stride: 8,
src: 72,
});
}
caller_code.push(Op::Call {
callee: FnId(1),
args: vec![ArgCopy {
src: 8,
dst: 0,
size: 48,
}],
ret: 56,
});
caller_code.push(Op::ConstI64 { dst: 64, value: 2 });
caller_code.push(Op::LoadIndexedI64 {
dst: 72,
base: 8,
index: 64,
stride: 8,
});
caller_code.push(Op::AddI64 {
dst: 56,
a: 56,
b: 72,
});
caller_code.push(Op::Ret { src: 56, size: 8 });
let callee_code = vec![
Op::Await { dst: 48, input: 0 },
Op::LoadIndexedI64 {
dst: 56,
base: 0,
index: 48,
stride: 8,
},
Op::ConstI64 { dst: 72, value: 1 },
Op::AddI64 {
dst: 48,
a: 48,
b: 72,
},
Op::LoadIndexedI64 {
dst: 64,
base: 0,
index: 48,
stride: 8,
},
Op::AddI64 {
dst: 72,
a: 56,
b: 64,
},
Op::ConstI64 {
dst: 56,
value: 999,
},
Op::StoreIndexedI64 {
base: 0,
index: 48,
stride: 8,
src: 56,
},
Op::Ret { src: 72, size: 8 },
];
let program = Program {
fns: vec![
TaskFn {
frame: frame_of_i64s(10),
code: caller_code,
},
TaskFn {
frame: frame_of_i64s(10),
code: callee_code,
},
],
};
differential(&program, FnId(0), &[(&[false], &[0]), (&[true], &[2])]);
}
#[test]
fn composite_returns_match_the_interpreter() {
let program = Program {
fns: vec![
TaskFn {
frame: Layout { size: 40, align: 8 },
code: vec![
Op::Call {
callee: FnId(1),
args: vec![],
ret: 0,
},
Op::ConstI64 { dst: 24, value: 1 },
Op::LoadIndexedI64 {
dst: 32,
base: 0,
index: 24,
stride: 8,
},
Op::Ret { src: 32, size: 8 },
],
},
TaskFn {
frame: Layout { size: 40, align: 8 },
code: vec![
Op::ConstI64 { dst: 24, value: 0 },
Op::ConstI64 { dst: 32, value: 5 },
Op::StoreIndexedI64 {
base: 0,
index: 24,
stride: 8,
src: 32,
},
Op::ConstI64 { dst: 24, value: 1 },
Op::ConstI64 { dst: 32, value: 6 },
Op::StoreIndexedI64 {
base: 0,
index: 24,
stride: 8,
src: 32,
},
Op::ConstI64 { dst: 24, value: 2 },
Op::ConstI64 { dst: 32, value: 7 },
Op::StoreIndexedI64 {
base: 0,
index: 24,
stride: 8,
src: 32,
},
Op::Ret { src: 0, size: 24 },
],
},
],
};
differential(&program, FnId(0), &[(&[], &[])]);
}
}