use super::setup::JITCompiler;
use crate::context::JittedStrategyFn;
use shape_vm::bytecode::{BuiltinFunction, BytecodeProgram, Instruction, OpCode, Operand};
impl JITCompiler {
#[inline(always)]
pub fn get_function_table(&self) -> &[*const u8] {
&self.function_table
}
#[inline(always)]
pub fn get_function_by_index(&self, idx: usize) -> Option<JittedStrategyFn> {
self.function_table.get(idx).and_then(|&ptr| {
if ptr.is_null() {
None
} else {
Some(unsafe { std::mem::transmute(ptr) })
}
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct JitPreflightReport {
pub vm_only_opcodes: Vec<OpCode>,
pub unsupported_builtins: Vec<BuiltinFunction>,
}
impl JitPreflightReport {
pub fn can_jit(&self) -> bool {
self.vm_only_opcodes.is_empty() && self.unsupported_builtins.is_empty()
}
pub fn blockers_summary(&self) -> String {
let mut parts = Vec::new();
if !self.vm_only_opcodes.is_empty() {
let opcodes = self
.vm_only_opcodes
.iter()
.map(|op| format!("{op:?}"))
.collect::<Vec<_>>()
.join(", ");
parts.push(format!("opcodes=[{opcodes}]"));
}
if !self.unsupported_builtins.is_empty() {
let builtins = self
.unsupported_builtins
.iter()
.map(|builtin| format!("{builtin:?}"))
.collect::<Vec<_>>()
.join(", ");
parts.push(format!("builtins=[{builtins}]"));
}
if parts.is_empty() {
"none".to_string()
} else {
parts.join("; ")
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JitParityEntry {
pub target: JitParityTarget,
pub jit_supported: bool,
pub reason: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JitParityTarget {
Opcode(OpCode),
Builtin(BuiltinFunction),
}
fn push_unique_opcode(out: &mut Vec<OpCode>, opcode: OpCode) {
if !out.contains(&opcode) {
out.push(opcode);
}
}
fn push_unique_builtin(out: &mut Vec<BuiltinFunction>, builtin: BuiltinFunction) {
if !out.contains(&builtin) {
out.push(builtin);
}
}
fn sort_opcodes(opcodes: &mut [OpCode]) {
opcodes.sort_by_key(|op| format!("{op:?}"));
}
fn sort_builtins(builtins: &mut [BuiltinFunction]) {
builtins.sort_by_key(|builtin| format!("{builtin:?}"));
}
const ALL_OPCODES: &[OpCode] = &[
OpCode::PushConst,
OpCode::PushNull,
OpCode::Pop,
OpCode::Dup,
OpCode::Swap,
OpCode::BitAnd,
OpCode::BitOr,
OpCode::BitShl,
OpCode::BitShr,
OpCode::BitNot,
OpCode::BitXor,
OpCode::GtInt,
OpCode::GtNumber,
OpCode::GtDecimal,
OpCode::LtInt,
OpCode::LtNumber,
OpCode::LtDecimal,
OpCode::GteInt,
OpCode::GteNumber,
OpCode::GteDecimal,
OpCode::LteInt,
OpCode::EqInt,
OpCode::EqNumber,
OpCode::NeqInt,
OpCode::NeqNumber,
OpCode::EqString,
OpCode::GtString,
OpCode::LtString,
OpCode::GteString,
OpCode::LteString,
OpCode::EqDecimal,
OpCode::IsNull,
OpCode::And,
OpCode::Or,
OpCode::Not,
OpCode::AddInt,
OpCode::AddNumber,
OpCode::AddDecimal,
OpCode::SubInt,
OpCode::SubNumber,
OpCode::SubDecimal,
OpCode::MulInt,
OpCode::MulNumber,
OpCode::MulDecimal,
OpCode::DivInt,
OpCode::DivNumber,
OpCode::DivDecimal,
OpCode::ModInt,
OpCode::NegInt,
OpCode::NegNumber,
OpCode::Jump,
OpCode::JumpIfFalse,
OpCode::JumpIfFalseTrusted,
OpCode::JumpIfTrue,
OpCode::Call,
OpCode::Return,
OpCode::ReturnValue,
OpCode::CallValue,
OpCode::CallClosure,
OpCode::CallFunctionIndirect,
OpCode::LoadLocal,
OpCode::LoadLocalTrusted,
OpCode::StoreLocal,
OpCode::LoadModuleBinding,
OpCode::StoreModuleBinding,
OpCode::LoadClosure,
OpCode::StoreClosure,
OpCode::MakeClosure,
OpCode::CloseUpvalue,
OpCode::MakeRef,
OpCode::MakeFieldRef,
OpCode::MakeIndexRef,
OpCode::DerefLoad,
OpCode::DerefStore,
OpCode::SetIndexRef,
OpCode::NewArray,
OpCode::NewTypedArray,
OpCode::NewObject,
OpCode::GetProp,
OpCode::SetProp,
OpCode::Length,
OpCode::ArrayPush,
OpCode::ArrayPop,
OpCode::MergeObject,
OpCode::SetLocalIndex,
OpCode::SetModuleBindingIndex,
OpCode::ArrayPushLocal,
OpCode::LoopStart,
OpCode::LoopEnd,
OpCode::Break,
OpCode::Continue,
OpCode::IterNext,
OpCode::IterDone,
OpCode::CallMethod,
OpCode::PushTimeframe,
OpCode::PopTimeframe,
OpCode::BuiltinCall,
OpCode::TypeCheck,
OpCode::Convert,
OpCode::ModNumber,
OpCode::ModDecimal,
OpCode::PowInt,
OpCode::PowNumber,
OpCode::PowDecimal,
OpCode::LteNumber,
OpCode::LteDecimal,
OpCode::SetupTry,
OpCode::PopHandler,
OpCode::Throw,
OpCode::TryUnwrap,
OpCode::UnwrapOption,
OpCode::ErrorContext,
OpCode::IsOk,
OpCode::IsErr,
OpCode::UnwrapOk,
OpCode::UnwrapErr,
OpCode::SliceAccess,
OpCode::NullCoalesce,
OpCode::MakeRange,
OpCode::GetDataField,
OpCode::GetDataRow,
OpCode::GetFieldTyped,
OpCode::SetFieldTyped,
OpCode::NewTypedObject,
OpCode::TypedMergeObject,
OpCode::WrapTypeAnnotation,
OpCode::Yield,
OpCode::Suspend,
OpCode::Resume,
OpCode::Poll,
OpCode::AwaitBar,
OpCode::AwaitTick,
OpCode::Await,
OpCode::SpawnTask,
OpCode::EmitAlert,
OpCode::EmitEvent,
OpCode::JoinInit,
OpCode::JoinAwait,
OpCode::CancelTask,
OpCode::AsyncScopeEnter,
OpCode::AsyncScopeExit,
OpCode::LoadColF64,
OpCode::LoadColI64,
OpCode::LoadColBool,
OpCode::LoadColStr,
OpCode::BindSchema,
OpCode::BoxTraitObject,
OpCode::DynMethodCall,
OpCode::Nop,
OpCode::Halt,
OpCode::IntToNumber,
OpCode::NumberToInt,
OpCode::CallForeign,
OpCode::AddTyped,
OpCode::SubTyped,
OpCode::MulTyped,
OpCode::DivTyped,
OpCode::ModTyped,
OpCode::CmpTyped,
OpCode::StoreLocalTyped,
OpCode::StoreModuleBindingTyped,
OpCode::CastWidth,
OpCode::NewTypedArrayF64,
OpCode::NewTypedArrayI64,
OpCode::NewTypedArrayI32,
OpCode::NewTypedArrayBool,
OpCode::TypedArrayGetF64,
OpCode::TypedArrayGetI64,
OpCode::TypedArrayGetI32,
OpCode::TypedArrayGetBool,
OpCode::TypedArraySetF64,
OpCode::TypedArraySetI64,
OpCode::TypedArraySetI32,
OpCode::TypedArraySetBool,
OpCode::TypedArrayPushF64,
OpCode::TypedArrayPushI64,
OpCode::TypedArrayPushI32,
OpCode::TypedArrayPushBool,
OpCode::TypedArrayLen,
OpCode::NewTypedArrayI8,
OpCode::TypedArrayGetI8,
OpCode::TypedArrayPushI8,
OpCode::TypedArraySetI8,
OpCode::NewTypedArrayU8,
OpCode::TypedArrayGetU8,
OpCode::TypedArrayPushU8,
OpCode::TypedArraySetU8,
OpCode::NewTypedArrayI16,
OpCode::TypedArrayGetI16,
OpCode::TypedArrayPushI16,
OpCode::TypedArraySetI16,
OpCode::NewTypedArrayU16,
OpCode::TypedArrayGetU16,
OpCode::TypedArrayPushU16,
OpCode::TypedArraySetU16,
OpCode::NewTypedArrayU32,
OpCode::TypedArrayGetU32,
OpCode::TypedArrayPushU32,
OpCode::TypedArraySetU32,
OpCode::NewTypedArrayF32,
OpCode::TypedArrayGetF32,
OpCode::TypedArrayPushF32,
OpCode::TypedArraySetF32,
OpCode::NewTypedArrayChar,
OpCode::TypedArrayGetChar,
OpCode::TypedArrayPushChar,
OpCode::TypedArraySetChar,
];
const ALL_BUILTINS: &[BuiltinFunction] = &[
BuiltinFunction::Abs,
BuiltinFunction::Sqrt,
BuiltinFunction::Ln,
BuiltinFunction::Pow,
BuiltinFunction::Exp,
BuiltinFunction::Log,
BuiltinFunction::Min,
BuiltinFunction::Max,
BuiltinFunction::Floor,
BuiltinFunction::Ceil,
BuiltinFunction::Round,
BuiltinFunction::Sin,
BuiltinFunction::Cos,
BuiltinFunction::Tan,
BuiltinFunction::Asin,
BuiltinFunction::Acos,
BuiltinFunction::Atan,
BuiltinFunction::StdDev,
BuiltinFunction::Range,
BuiltinFunction::Slice,
BuiltinFunction::Push,
BuiltinFunction::Pop,
BuiltinFunction::First,
BuiltinFunction::Last,
BuiltinFunction::Zip,
BuiltinFunction::Filled,
BuiltinFunction::Map,
BuiltinFunction::Filter,
BuiltinFunction::Reduce,
BuiltinFunction::ForEach,
BuiltinFunction::Find,
BuiltinFunction::FindIndex,
BuiltinFunction::Some,
BuiltinFunction::Every,
BuiltinFunction::Print,
BuiltinFunction::Format,
BuiltinFunction::Snapshot,
BuiltinFunction::Exit,
BuiltinFunction::ObjectRest,
BuiltinFunction::ControlFold,
BuiltinFunction::TypeOf,
BuiltinFunction::IsNumber,
BuiltinFunction::IsString,
BuiltinFunction::IsBool,
BuiltinFunction::IsArray,
BuiltinFunction::IsObject,
BuiltinFunction::IsDataRow,
BuiltinFunction::ToString,
BuiltinFunction::ToNumber,
BuiltinFunction::ToBool,
BuiltinFunction::NativePtrSize,
BuiltinFunction::NativePtrNewCell,
BuiltinFunction::NativePtrFreeCell,
BuiltinFunction::NativePtrReadPtr,
BuiltinFunction::NativePtrWritePtr,
BuiltinFunction::NativeTableFromArrowC,
BuiltinFunction::NativeTableFromArrowCTyped,
BuiltinFunction::NativeTableBindType,
BuiltinFunction::FormatValueWithMeta,
BuiltinFunction::FormatValueWithSpec,
BuiltinFunction::FStringContentText,
BuiltinFunction::FStringContentStyledText,
BuiltinFunction::FStringContentFragment,
BuiltinFunction::IntrinsicMean,
BuiltinFunction::IntrinsicMin,
BuiltinFunction::IntrinsicMax,
BuiltinFunction::IntrinsicStd,
BuiltinFunction::IntrinsicVariance,
BuiltinFunction::IntrinsicRandom,
BuiltinFunction::IntrinsicRandomInt,
BuiltinFunction::IntrinsicRandomSeed,
BuiltinFunction::IntrinsicRandomNormal,
BuiltinFunction::IntrinsicRandomArray,
BuiltinFunction::IntrinsicDistUniform,
BuiltinFunction::IntrinsicDistLognormal,
BuiltinFunction::IntrinsicDistExponential,
BuiltinFunction::IntrinsicDistPoisson,
BuiltinFunction::IntrinsicDistSampleN,
BuiltinFunction::IntrinsicBrownianMotion,
BuiltinFunction::IntrinsicGbm,
BuiltinFunction::IntrinsicOuProcess,
BuiltinFunction::IntrinsicRandomWalk,
BuiltinFunction::IntrinsicRollingSum,
BuiltinFunction::IntrinsicRollingMean,
BuiltinFunction::IntrinsicRollingStd,
BuiltinFunction::IntrinsicRollingMin,
BuiltinFunction::IntrinsicRollingMax,
BuiltinFunction::IntrinsicEma,
BuiltinFunction::IntrinsicLinearRecurrence,
BuiltinFunction::IntrinsicShift,
BuiltinFunction::IntrinsicDiff,
BuiltinFunction::IntrinsicPctChange,
BuiltinFunction::IntrinsicFillna,
BuiltinFunction::IntrinsicCumsum,
BuiltinFunction::IntrinsicCumprod,
BuiltinFunction::IntrinsicClip,
BuiltinFunction::IntrinsicCorrelation,
BuiltinFunction::IntrinsicCovariance,
BuiltinFunction::IntrinsicPercentile,
BuiltinFunction::IntrinsicMedian,
BuiltinFunction::IntrinsicAtan2,
BuiltinFunction::IntrinsicSinh,
BuiltinFunction::IntrinsicCosh,
BuiltinFunction::IntrinsicTanh,
BuiltinFunction::IntrinsicCharCode,
BuiltinFunction::IntrinsicFromCharCode,
BuiltinFunction::IntrinsicSeries,
BuiltinFunction::IntrinsicVecAbs,
BuiltinFunction::IntrinsicVecSqrt,
BuiltinFunction::IntrinsicVecLn,
BuiltinFunction::IntrinsicVecExp,
BuiltinFunction::IntrinsicVecAdd,
BuiltinFunction::IntrinsicVecSub,
BuiltinFunction::IntrinsicVecMul,
BuiltinFunction::IntrinsicVecDiv,
BuiltinFunction::IntrinsicVecMax,
BuiltinFunction::IntrinsicVecMin,
BuiltinFunction::IntrinsicVecSelect,
BuiltinFunction::IntrinsicVecAddI64,
BuiltinFunction::IntrinsicMatMulVec,
BuiltinFunction::IntrinsicMatMulMat,
BuiltinFunction::IntrinsicMatAdd,
BuiltinFunction::IntrinsicMatSub,
BuiltinFunction::EvalTimeRef,
BuiltinFunction::EvalDateTimeExpr,
BuiltinFunction::EvalDataDateTimeRef,
BuiltinFunction::EvalDataSet,
BuiltinFunction::EvalDataRelative,
BuiltinFunction::EvalDataRelativeRange,
BuiltinFunction::SomeCtor,
BuiltinFunction::OkCtor,
BuiltinFunction::ErrCtor,
BuiltinFunction::HashMapCtor,
BuiltinFunction::SetCtor,
BuiltinFunction::DequeCtor,
BuiltinFunction::PriorityQueueCtor,
BuiltinFunction::JsonObjectGet,
BuiltinFunction::JsonArrayAt,
BuiltinFunction::JsonObjectKeys,
BuiltinFunction::JsonArrayLen,
BuiltinFunction::JsonObjectLen,
BuiltinFunction::WindowRowNumber,
BuiltinFunction::WindowRank,
BuiltinFunction::WindowDenseRank,
BuiltinFunction::WindowNtile,
BuiltinFunction::WindowLag,
BuiltinFunction::WindowLead,
BuiltinFunction::WindowFirstValue,
BuiltinFunction::WindowLastValue,
BuiltinFunction::WindowNthValue,
BuiltinFunction::WindowSum,
BuiltinFunction::WindowAvg,
BuiltinFunction::WindowMin,
BuiltinFunction::WindowMax,
BuiltinFunction::WindowCount,
BuiltinFunction::JoinExecute,
BuiltinFunction::Reflect,
BuiltinFunction::ContentChart,
BuiltinFunction::ContentTextCtor,
BuiltinFunction::ContentTableCtor,
BuiltinFunction::ContentCodeCtor,
BuiltinFunction::ContentKvCtor,
BuiltinFunction::ContentFragmentCtor,
BuiltinFunction::DateTimeNow,
BuiltinFunction::DateTimeUtc,
BuiltinFunction::DateTimeParse,
BuiltinFunction::DateTimeFromEpoch,
BuiltinFunction::DateTimeFromParts,
BuiltinFunction::DateTimeFromUnixSecs,
BuiltinFunction::MutexCtor,
BuiltinFunction::AtomicCtor,
BuiltinFunction::LazyCtor,
BuiltinFunction::ChannelCtor,
BuiltinFunction::Sign,
BuiltinFunction::Gcd,
BuiltinFunction::Lcm,
BuiltinFunction::Hypot,
BuiltinFunction::Clamp,
BuiltinFunction::IsNaN,
BuiltinFunction::IsFinite,
BuiltinFunction::MakeTableFromRows,
BuiltinFunction::TableBuilderNew,
BuiltinFunction::CodeBuilderNew,
BuiltinFunction::KeyValueBuilderNew,
];
fn vm_only_opcode_reason(opcode: OpCode) -> Option<&'static str> {
match opcode {
OpCode::AllocSharedModuleBinding
| OpCode::LoadSharedModuleBinding
| OpCode::StoreSharedModuleBinding => Some(
"A.1C.3 outer-scope Shared module-binding opcode; Cranelift lowering pending",
),
OpCode::ConvertToInt
| OpCode::ConvertToNumber
| OpCode::ConvertToString
| OpCode::ConvertToBool
| OpCode::ConvertToDecimal
| OpCode::ConvertToChar
| OpCode::TryConvertToInt
| OpCode::TryConvertToNumber
| OpCode::TryConvertToString
| OpCode::TryConvertToBool
| OpCode::TryConvertToDecimal
| OpCode::TryConvertToChar => Some(
"WS-12: ConvertTo*/TryConvertTo* (`as` cast) not lowered by the \
JIT translator; VM-only until per-kind typed convert bodies land",
),
_ => None,
}
}
fn is_supported_builtin(_builtin: BuiltinFunction) -> bool {
true
}
pub fn preflight_instructions(instructions: &[Instruction]) -> JitPreflightReport {
let mut report = JitPreflightReport::default();
for instr in instructions {
if vm_only_opcode_reason(instr.opcode).is_some() {
push_unique_opcode(&mut report.vm_only_opcodes, instr.opcode);
}
if instr.opcode == OpCode::BuiltinCall {
if let Some(Operand::Builtin(builtin)) = instr.operand {
if !is_supported_builtin(builtin) {
push_unique_builtin(&mut report.unsupported_builtins, builtin);
}
}
}
}
sort_opcodes(&mut report.vm_only_opcodes);
sort_builtins(&mut report.unsupported_builtins);
report
}
pub fn preflight_blob_jit_compatibility(
blob: &shape_vm::bytecode::FunctionBlob,
) -> JitPreflightReport {
preflight_instructions(&blob.instructions)
}
pub fn preflight_jit_compatibility(program: &BytecodeProgram) -> JitPreflightReport {
let mut report = JitPreflightReport::default();
for instr in &program.instructions {
if vm_only_opcode_reason(instr.opcode).is_some() {
push_unique_opcode(&mut report.vm_only_opcodes, instr.opcode);
}
if instr.opcode == OpCode::BuiltinCall {
if let Some(Operand::Builtin(builtin)) = instr.operand {
if !is_supported_builtin(builtin) {
push_unique_builtin(&mut report.unsupported_builtins, builtin);
}
}
}
}
sort_opcodes(&mut report.vm_only_opcodes);
sort_builtins(&mut report.unsupported_builtins);
report
}
pub fn build_program_parity_matrix(program: &BytecodeProgram) -> Vec<JitParityEntry> {
let mut opcodes = Vec::new();
let mut builtins = Vec::new();
for instr in &program.instructions {
push_unique_opcode(&mut opcodes, instr.opcode);
if instr.opcode == OpCode::BuiltinCall {
if let Some(Operand::Builtin(builtin)) = instr.operand {
push_unique_builtin(&mut builtins, builtin);
}
}
}
sort_opcodes(&mut opcodes);
sort_builtins(&mut builtins);
let mut matrix = Vec::with_capacity(opcodes.len() + builtins.len());
for opcode in opcodes {
if let Some(reason) = vm_only_opcode_reason(opcode) {
matrix.push(JitParityEntry {
target: JitParityTarget::Opcode(opcode),
jit_supported: false,
reason,
});
} else {
matrix.push(JitParityEntry {
target: JitParityTarget::Opcode(opcode),
jit_supported: true,
reason: "Opcode is lowered by the JIT translator.",
});
}
}
for builtin in builtins {
if is_supported_builtin(builtin) {
matrix.push(JitParityEntry {
target: JitParityTarget::Builtin(builtin),
jit_supported: true,
reason: "Builtin is lowered by JIT builtin handlers.",
});
} else {
matrix.push(JitParityEntry {
target: JitParityTarget::Builtin(builtin),
jit_supported: false,
reason: "Builtin is not lowered by JIT and must run on VM.",
});
}
}
matrix.sort_by_key(|entry| format!("{:?}", entry.target));
matrix
}
pub fn build_full_opcode_parity_matrix() -> Vec<JitParityEntry> {
let mut matrix = Vec::with_capacity(ALL_OPCODES.len());
for &opcode in ALL_OPCODES {
if let Some(reason) = vm_only_opcode_reason(opcode) {
matrix.push(JitParityEntry {
target: JitParityTarget::Opcode(opcode),
jit_supported: false,
reason,
});
} else {
matrix.push(JitParityEntry {
target: JitParityTarget::Opcode(opcode),
jit_supported: true,
reason: "Opcode is lowered by the JIT translator.",
});
}
}
matrix.sort_by_key(|entry| format!("{:?}", entry.target));
matrix
}
pub fn build_full_builtin_parity_matrix() -> Vec<JitParityEntry> {
let mut matrix = Vec::with_capacity(ALL_BUILTINS.len());
for &builtin in ALL_BUILTINS {
if is_supported_builtin(builtin) {
matrix.push(JitParityEntry {
target: JitParityTarget::Builtin(builtin),
jit_supported: true,
reason: "Builtin is lowered by JIT builtin handlers.",
});
} else {
matrix.push(JitParityEntry {
target: JitParityTarget::Builtin(builtin),
jit_supported: false,
reason: "Builtin is not lowered by JIT and must run on VM.",
});
}
}
matrix.sort_by_key(|entry| format!("{:?}", entry.target));
matrix
}
#[inline(always)]
pub fn can_jit_compile(program: &BytecodeProgram) -> bool {
preflight_jit_compatibility(program).can_jit()
}
#[inline(always)]
pub fn get_unsupported_opcodes(program: &BytecodeProgram) -> Vec<OpCode> {
let report = preflight_jit_compatibility(program);
let mut unsupported = report.vm_only_opcodes;
if !report.unsupported_builtins.is_empty() && !unsupported.contains(&OpCode::BuiltinCall) {
unsupported.push(OpCode::BuiltinCall);
}
sort_opcodes(&mut unsupported);
unsupported
}
pub fn get_incomplete_opcodes(_program: &BytecodeProgram) -> Vec<OpCode> {
Vec::new()
}
#[cfg(test)]
mod tests {
use super::*;
use shape_vm::bytecode::{Instruction, Operand};
#[test]
fn preflight_accepts_all_opcodes() {
let program = BytecodeProgram {
instructions: vec![Instruction::simple(OpCode::Await)],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(report.can_jit());
}
#[test]
fn preflight_accepts_all_builtins() {
let program = BytecodeProgram {
instructions: vec![Instruction::new(
OpCode::BuiltinCall,
Some(Operand::Builtin(BuiltinFunction::Snapshot)),
)],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(report.can_jit());
}
#[test]
fn parity_matrix_marks_all_builtins_supported() {
let program = BytecodeProgram {
instructions: vec![Instruction::new(
OpCode::BuiltinCall,
Some(Operand::Builtin(BuiltinFunction::Snapshot)),
)],
..Default::default()
};
let matrix = build_program_parity_matrix(&program);
assert!(matrix.iter().all(|row| row.jit_supported));
}
#[test]
fn preflight_instructions_compatible_slice() {
let instructions = vec![
Instruction::simple(OpCode::PushConst),
Instruction::simple(OpCode::AddInt),
Instruction::simple(OpCode::ReturnValue),
];
let report = preflight_instructions(&instructions);
assert!(report.can_jit());
}
#[test]
fn preflight_instructions_all_opcodes_pass() {
let instructions = vec![
Instruction::simple(OpCode::PushConst),
Instruction::simple(OpCode::Await),
Instruction::simple(OpCode::ReturnValue),
];
let report = preflight_instructions(&instructions);
assert!(report.can_jit());
}
#[test]
fn preflight_blob_passes_with_spawn_task() {
use shape_vm::bytecode::FunctionBlob;
let blob = FunctionBlob {
content_hash: shape_vm::bytecode::FunctionHash::ZERO,
name: "test_fn".to_string(),
arity: 0,
param_names: vec![],
locals_count: 0,
is_closure: false,
captures_count: 0,
is_async: false,
ref_params: vec![],
ref_mutates: vec![],
mutable_captures: vec![],
instructions: vec![
Instruction::simple(OpCode::PushConst),
Instruction::simple(OpCode::SpawnTask),
Instruction::simple(OpCode::ReturnValue),
],
constants: vec![],
strings: vec![],
required_permissions: Default::default(),
dependencies: vec![],
callee_names: vec![],
type_schemas: vec![],
source_map: vec![],
foreign_dependencies: vec![],
frame_descriptor: None,
};
let report = preflight_blob_jit_compatibility(&blob);
assert!(report.can_jit());
}
#[test]
fn all_opcodes_pass_preflight() {
for &opcode in ALL_OPCODES {
assert!(
vm_only_opcode_reason(opcode).is_none(),
"Opcode {:?} should pass preflight",
opcode
);
}
}
#[test]
fn all_builtins_pass_preflight() {
for &builtin in ALL_BUILTINS {
assert!(
is_supported_builtin(builtin),
"Builtin {:?} should be supported",
builtin
);
}
}
#[test]
fn a1d2_preflight_accepts_load_owned_mutable_capture() {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(OpCode::LoadOwnedMutableCapture, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
report.can_jit(),
"A.1D.2: LoadOwnedMutableCapture is now lowered by MirToIR; \
preflight must accept it"
);
assert!(
!report
.vm_only_opcodes
.contains(&OpCode::LoadOwnedMutableCapture),
"A.1D.2 removes LoadOwnedMutableCapture from vm_only_opcode_reason"
);
}
#[test]
fn a1d2_preflight_accepts_store_owned_mutable_capture() {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(OpCode::StoreOwnedMutableCapture, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
report.can_jit(),
"A.1D.2: StoreOwnedMutableCapture is now lowered by MirToIR; \
preflight must accept it"
);
assert!(
!report
.vm_only_opcodes
.contains(&OpCode::StoreOwnedMutableCapture),
"A.1D.2 removes StoreOwnedMutableCapture from vm_only_opcode_reason"
);
}
#[test]
fn a1e_preflight_accepts_load_shared_capture() {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(OpCode::LoadSharedCapture, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
report.can_jit(),
"A.1E: LoadSharedCapture is now lowered by MirToIR; \
preflight must accept it"
);
assert!(
!report.vm_only_opcodes.contains(&OpCode::LoadSharedCapture),
"A.1E removes LoadSharedCapture from vm_only_opcode_reason"
);
}
#[test]
fn a1e_preflight_accepts_store_shared_capture() {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(OpCode::StoreSharedCapture, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
report.can_jit(),
"A.1E: StoreSharedCapture is now lowered by MirToIR; \
preflight must accept it"
);
assert!(
!report.vm_only_opcodes.contains(&OpCode::StoreSharedCapture),
"A.1E removes StoreSharedCapture from vm_only_opcode_reason"
);
}
#[test]
fn a1c_preflight_immutable_closure_body_still_jits() {
let program = BytecodeProgram {
instructions: vec![
Instruction::simple(OpCode::PushConst),
Instruction::simple(OpCode::AddInt),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(report.can_jit());
}
#[test]
fn cell_identity_preflight_accepts_outer_shared_local_opcodes() {
for op in [
OpCode::AllocSharedLocal,
OpCode::LoadSharedLocal,
OpCode::StoreSharedLocal,
OpCode::DropSharedLocal,
] {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(op, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
report.can_jit(),
"cell-identity: outer-scope Shared local opcode {:?} \
must pass preflight after the handshake fix",
op
);
assert!(
!report.vm_only_opcodes.contains(&op),
"cell-identity: {:?} must be removed from vm_only_opcode_reason",
op
);
}
}
#[test]
fn session1_preflight_still_rejects_outer_shared_module_binding_opcodes() {
for op in [
OpCode::AllocSharedModuleBinding,
OpCode::LoadSharedModuleBinding,
OpCode::StoreSharedModuleBinding,
] {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(op, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
!report.can_jit(),
"Outer-scope Shared module-binding opcode {:?} must \
remain preflight-rejected pending A.1C.3 JIT lowering",
op
);
}
}
#[test]
fn a1e_preflight_accepts_capture_side_shared_opcodes() {
for op in [OpCode::LoadSharedCapture, OpCode::StoreSharedCapture] {
let program = BytecodeProgram {
instructions: vec![
Instruction::new(op, Some(Operand::Local(0))),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
report.can_jit(),
"Capture-side Shared opcode {:?} must pass preflight after A.1E",
op
);
}
}
#[test]
fn ws12_preflight_rejects_convert_cast_opcodes() {
for op in [
OpCode::ConvertToInt,
OpCode::ConvertToNumber,
OpCode::ConvertToString,
OpCode::ConvertToBool,
OpCode::ConvertToDecimal,
OpCode::ConvertToChar,
OpCode::TryConvertToInt,
OpCode::TryConvertToNumber,
OpCode::TryConvertToString,
OpCode::TryConvertToBool,
OpCode::TryConvertToDecimal,
OpCode::TryConvertToChar,
] {
let program = BytecodeProgram {
instructions: vec![
Instruction::simple(op),
Instruction::simple(OpCode::ReturnValue),
],
..Default::default()
};
let report = preflight_jit_compatibility(&program);
assert!(
!report.can_jit(),
"WS-12: cast opcode {:?} must be preflight-rejected \
(JIT translator does not lower it)",
op
);
assert!(
report.vm_only_opcodes.contains(&op),
"WS-12: {:?} must appear in vm_only_opcodes",
op
);
}
}
}