Skip to main content

shape_jit/compiler/
accessors.rs

1//! Accessor methods and utility functions
2//!
3//! This module contains simple accessor methods and helper functions
4//! for querying JIT compiler state.
5
6use super::setup::JITCompiler;
7use crate::context::JittedStrategyFn;
8use shape_vm::bytecode::{BuiltinFunction, BytecodeProgram, Instruction, OpCode, Operand};
9
10impl JITCompiler {
11    /// Get the function table for setting up JITContext
12    #[inline(always)]
13    pub fn get_function_table(&self) -> &[*const u8] {
14        &self.function_table
15    }
16
17    /// Get a compiled function pointer by function index
18    #[inline(always)]
19    pub fn get_function_by_index(&self, idx: usize) -> Option<JittedStrategyFn> {
20        self.function_table.get(idx).and_then(|&ptr| {
21            if ptr.is_null() {
22                None
23            } else {
24                Some(unsafe { std::mem::transmute(ptr) })
25            }
26        })
27    }
28}
29
30/// Program-level preflight report for JIT capability checks.
31#[derive(Debug, Clone, Default, PartialEq, Eq)]
32pub struct JitPreflightReport {
33    /// Opcodes that require VM execution for correct behavior.
34    pub vm_only_opcodes: Vec<OpCode>,
35    /// Builtins that are not lowered by the JIT translator.
36    pub unsupported_builtins: Vec<BuiltinFunction>,
37}
38
39impl JitPreflightReport {
40    /// True when the program can run safely in JIT without semantic downgrades.
41    pub fn can_jit(&self) -> bool {
42        self.vm_only_opcodes.is_empty() && self.unsupported_builtins.is_empty()
43    }
44
45    /// Human-readable blocker summary for diagnostics/logging.
46    pub fn blockers_summary(&self) -> String {
47        let mut parts = Vec::new();
48
49        if !self.vm_only_opcodes.is_empty() {
50            let opcodes = self
51                .vm_only_opcodes
52                .iter()
53                .map(|op| format!("{op:?}"))
54                .collect::<Vec<_>>()
55                .join(", ");
56            parts.push(format!("opcodes=[{opcodes}]"));
57        }
58
59        if !self.unsupported_builtins.is_empty() {
60            let builtins = self
61                .unsupported_builtins
62                .iter()
63                .map(|builtin| format!("{builtin:?}"))
64                .collect::<Vec<_>>()
65                .join(", ");
66            parts.push(format!("builtins=[{builtins}]"));
67        }
68
69        if parts.is_empty() {
70            "none".to_string()
71        } else {
72            parts.join("; ")
73        }
74    }
75}
76
77/// Program-level JIT parity entry (opcode/builtin support row).
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct JitParityEntry {
80    pub target: JitParityTarget,
81    pub jit_supported: bool,
82    pub reason: &'static str,
83}
84
85/// What the parity row describes.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum JitParityTarget {
88    Opcode(OpCode),
89    Builtin(BuiltinFunction),
90}
91
92fn push_unique_opcode(out: &mut Vec<OpCode>, opcode: OpCode) {
93    if !out.contains(&opcode) {
94        out.push(opcode);
95    }
96}
97
98fn push_unique_builtin(out: &mut Vec<BuiltinFunction>, builtin: BuiltinFunction) {
99    if !out.contains(&builtin) {
100        out.push(builtin);
101    }
102}
103
104fn sort_opcodes(opcodes: &mut [OpCode]) {
105    opcodes.sort_by_key(|op| format!("{op:?}"));
106}
107
108fn sort_builtins(builtins: &mut [BuiltinFunction]) {
109    builtins.sort_by_key(|builtin| format!("{builtin:?}"));
110}
111
112const ALL_OPCODES: &[OpCode] = &[
113    OpCode::PushConst,
114    OpCode::PushNull,
115    OpCode::Pop,
116    OpCode::Dup,
117    OpCode::Swap,
118    OpCode::BitAnd,
119    OpCode::BitOr,
120    OpCode::BitShl,
121    OpCode::BitShr,
122    OpCode::BitNot,
123    OpCode::BitXor,
124    OpCode::GtInt,
125    OpCode::GtNumber,
126    OpCode::GtDecimal,
127    OpCode::LtInt,
128    OpCode::LtNumber,
129    OpCode::LtDecimal,
130    OpCode::GteInt,
131    OpCode::GteNumber,
132    OpCode::GteDecimal,
133    OpCode::LteInt,
134    OpCode::EqInt,
135    OpCode::EqNumber,
136    OpCode::NeqInt,
137    OpCode::NeqNumber,
138    OpCode::EqString,
139    OpCode::GtString,
140    OpCode::LtString,
141    OpCode::GteString,
142    OpCode::LteString,
143    OpCode::EqDecimal,
144    OpCode::IsNull,
145    OpCode::And,
146    OpCode::Or,
147    OpCode::Not,
148    OpCode::AddInt,
149    OpCode::AddNumber,
150    OpCode::AddDecimal,
151    OpCode::SubInt,
152    OpCode::SubNumber,
153    OpCode::SubDecimal,
154    OpCode::MulInt,
155    OpCode::MulNumber,
156    OpCode::MulDecimal,
157    OpCode::DivInt,
158    OpCode::DivNumber,
159    OpCode::DivDecimal,
160    OpCode::ModInt,
161    OpCode::NegInt,
162    OpCode::NegNumber,
163    OpCode::Jump,
164    OpCode::JumpIfFalse,
165    OpCode::JumpIfFalseTrusted,
166    OpCode::JumpIfTrue,
167    OpCode::Call,
168    OpCode::Return,
169    OpCode::ReturnValue,
170    OpCode::CallValue,
171    OpCode::CallClosure,
172    OpCode::CallFunctionIndirect,
173    OpCode::LoadLocal,
174    OpCode::LoadLocalTrusted,
175    OpCode::StoreLocal,
176    OpCode::LoadModuleBinding,
177    OpCode::StoreModuleBinding,
178    OpCode::LoadClosure,
179    OpCode::StoreClosure,
180    OpCode::MakeClosure,
181    OpCode::CloseUpvalue,
182    OpCode::MakeRef,
183    // γ-CP4 jit-makefieldref: `MakeFieldRef` (`&`/`&mut` into a typed-
184    // object field) is JIT-compiled via the field-address path in
185    // `mir_compiler::rvalues::Rvalue::Borrow`; `MakeIndexRef` is out of
186    // the β1 `RefTarget` scope and clean-deopts to the interpreter. Both
187    // produce correct results, so the parity matrix lists them supported.
188    OpCode::MakeFieldRef,
189    OpCode::MakeIndexRef,
190    OpCode::DerefLoad,
191    OpCode::DerefStore,
192    OpCode::SetIndexRef,
193    OpCode::NewArray,
194    OpCode::NewTypedArray,
195    OpCode::NewObject,
196    OpCode::GetProp,
197    OpCode::SetProp,
198    OpCode::Length,
199    OpCode::ArrayPush,
200    OpCode::ArrayPop,
201    OpCode::MergeObject,
202    OpCode::SetLocalIndex,
203    OpCode::SetModuleBindingIndex,
204    OpCode::ArrayPushLocal,
205    OpCode::LoopStart,
206    OpCode::LoopEnd,
207    OpCode::Break,
208    OpCode::Continue,
209    OpCode::IterNext,
210    OpCode::IterDone,
211    OpCode::CallMethod,
212    OpCode::PushTimeframe,
213    OpCode::PopTimeframe,
214    OpCode::BuiltinCall,
215    OpCode::TypeCheck,
216    OpCode::Convert,
217    OpCode::ModNumber,
218    OpCode::ModDecimal,
219    OpCode::PowInt,
220    OpCode::PowNumber,
221    OpCode::PowDecimal,
222    OpCode::LteNumber,
223    OpCode::LteDecimal,
224    OpCode::SetupTry,
225    OpCode::PopHandler,
226    OpCode::Throw,
227    OpCode::TryUnwrap,
228    OpCode::UnwrapOption,
229    OpCode::ErrorContext,
230    OpCode::IsOk,
231    OpCode::IsErr,
232    OpCode::UnwrapOk,
233    OpCode::UnwrapErr,
234    OpCode::SliceAccess,
235    OpCode::NullCoalesce,
236    OpCode::MakeRange,
237    OpCode::GetDataField,
238    OpCode::GetDataRow,
239    OpCode::GetFieldTyped,
240    OpCode::SetFieldTyped,
241    OpCode::NewTypedObject,
242    OpCode::TypedMergeObject,
243    OpCode::WrapTypeAnnotation,
244    OpCode::Yield,
245    OpCode::Suspend,
246    OpCode::Resume,
247    OpCode::Poll,
248    OpCode::AwaitBar,
249    OpCode::AwaitTick,
250    OpCode::Await,
251    OpCode::SpawnTask,
252    OpCode::EmitAlert,
253    OpCode::EmitEvent,
254    OpCode::JoinInit,
255    OpCode::JoinAwait,
256    OpCode::CancelTask,
257    OpCode::AsyncScopeEnter,
258    OpCode::AsyncScopeExit,
259    OpCode::LoadColF64,
260    OpCode::LoadColI64,
261    OpCode::LoadColBool,
262    OpCode::LoadColStr,
263    OpCode::BindSchema,
264    OpCode::BoxTraitObject,
265    OpCode::DynMethodCall,
266    OpCode::Nop,
267    OpCode::Halt,
268    OpCode::IntToNumber,
269    OpCode::NumberToInt,
270    OpCode::CallForeign,
271    OpCode::AddTyped,
272    OpCode::SubTyped,
273    OpCode::MulTyped,
274    OpCode::DivTyped,
275    OpCode::ModTyped,
276    OpCode::CmpTyped,
277    OpCode::StoreLocalTyped,
278    OpCode::StoreModuleBindingTyped,
279    OpCode::CastWidth,
280    // v2 typed array opcodes
281    OpCode::NewTypedArrayF64,
282    OpCode::NewTypedArrayI64,
283    OpCode::NewTypedArrayI32,
284    OpCode::NewTypedArrayBool,
285    OpCode::TypedArrayGetF64,
286    OpCode::TypedArrayGetI64,
287    OpCode::TypedArrayGetI32,
288    OpCode::TypedArrayGetBool,
289    OpCode::TypedArraySetF64,
290    OpCode::TypedArraySetI64,
291    OpCode::TypedArraySetI32,
292    OpCode::TypedArraySetBool,
293    OpCode::TypedArrayPushF64,
294    OpCode::TypedArrayPushI64,
295    OpCode::TypedArrayPushI32,
296    OpCode::TypedArrayPushBool,
297    OpCode::TypedArrayLen,
298    // W12 S1 (2026-05-13) — sized-integer typed array opcodes (I8/U8/I16/U16/U32/U64).
299    // The opcodes themselves are VM-dispatched today (`exec_v2_typed_array`);
300    // the JIT registers them here to mark the parity matrix as supported
301    // and to keep `build_full_opcode_parity_matrix` exhaustive.
302    OpCode::NewTypedArrayI8,
303    OpCode::TypedArrayGetI8,
304    OpCode::TypedArrayPushI8,
305    OpCode::TypedArraySetI8,
306    OpCode::NewTypedArrayU8,
307    OpCode::TypedArrayGetU8,
308    OpCode::TypedArrayPushU8,
309    OpCode::TypedArraySetU8,
310    OpCode::NewTypedArrayI16,
311    OpCode::TypedArrayGetI16,
312    OpCode::TypedArrayPushI16,
313    OpCode::TypedArraySetI16,
314    OpCode::NewTypedArrayU16,
315    OpCode::TypedArrayGetU16,
316    OpCode::TypedArrayPushU16,
317    OpCode::TypedArraySetU16,
318    OpCode::NewTypedArrayU32,
319    OpCode::TypedArrayGetU32,
320    OpCode::TypedArrayPushU32,
321    OpCode::TypedArraySetU32,
322    // U64 typed-array opcodes intentionally NOT minted — deferred to
323    // S1.5 per supervisor's S1 reopen (2026-05-13). See
324    // crates/shape-vm/src/bytecode/opcode_defs.rs comment block.
325    // Wave 2 Agent A1 (2026-05-14) — F32 + Char monomorphizations.
326    // VM-dispatched today via `exec_v2_typed_array`; JIT registers them
327    // in the parity matrix to keep `build_full_opcode_parity_matrix`
328    // exhaustive. The JIT v2_array_new_func / v2_array_push_elem_size
329    // helpers return None for these element kinds, so the JIT codegen
330    // falls back to the legacy NaN-boxed path or the VM dispatcher.
331    OpCode::NewTypedArrayF32,
332    OpCode::TypedArrayGetF32,
333    OpCode::TypedArrayPushF32,
334    OpCode::TypedArraySetF32,
335    OpCode::NewTypedArrayChar,
336    OpCode::TypedArrayGetChar,
337    OpCode::TypedArrayPushChar,
338    OpCode::TypedArraySetChar,
339];
340
341const ALL_BUILTINS: &[BuiltinFunction] = &[
342    // Math (18)
343    BuiltinFunction::Abs,
344    BuiltinFunction::Sqrt,
345    BuiltinFunction::Ln,
346    BuiltinFunction::Pow,
347    BuiltinFunction::Exp,
348    BuiltinFunction::Log,
349    BuiltinFunction::Min,
350    BuiltinFunction::Max,
351    BuiltinFunction::Floor,
352    BuiltinFunction::Ceil,
353    BuiltinFunction::Round,
354    BuiltinFunction::Sin,
355    BuiltinFunction::Cos,
356    BuiltinFunction::Tan,
357    BuiltinFunction::Asin,
358    BuiltinFunction::Acos,
359    BuiltinFunction::Atan,
360    BuiltinFunction::StdDev,
361    // Array (8)
362    BuiltinFunction::Range,
363    BuiltinFunction::Slice,
364    BuiltinFunction::Push,
365    BuiltinFunction::Pop,
366    BuiltinFunction::First,
367    BuiltinFunction::Last,
368    BuiltinFunction::Zip,
369    BuiltinFunction::Filled,
370    // HOF (8)
371    BuiltinFunction::Map,
372    BuiltinFunction::Filter,
373    BuiltinFunction::Reduce,
374    BuiltinFunction::ForEach,
375    BuiltinFunction::Find,
376    BuiltinFunction::FindIndex,
377    BuiltinFunction::Some,
378    BuiltinFunction::Every,
379    // Utility (4)
380    BuiltinFunction::Print,
381    BuiltinFunction::Format,
382    BuiltinFunction::Snapshot,
383    BuiltinFunction::Exit,
384    // Object (1)
385    BuiltinFunction::ObjectRest,
386    // Control (1)
387    BuiltinFunction::ControlFold,
388    // Type (7)
389    BuiltinFunction::TypeOf,
390    BuiltinFunction::IsNumber,
391    BuiltinFunction::IsString,
392    BuiltinFunction::IsBool,
393    BuiltinFunction::IsArray,
394    BuiltinFunction::IsObject,
395    BuiltinFunction::IsDataRow,
396    // Conversion (3)
397    BuiltinFunction::ToString,
398    BuiltinFunction::ToNumber,
399    BuiltinFunction::ToBool,
400    // Native ptr (8)
401    BuiltinFunction::NativePtrSize,
402    BuiltinFunction::NativePtrNewCell,
403    BuiltinFunction::NativePtrFreeCell,
404    BuiltinFunction::NativePtrReadPtr,
405    BuiltinFunction::NativePtrWritePtr,
406    BuiltinFunction::NativeTableFromArrowC,
407    BuiltinFunction::NativeTableFromArrowCTyped,
408    BuiltinFunction::NativeTableBindType,
409    // Format (2)
410    BuiltinFunction::FormatValueWithMeta,
411    BuiltinFunction::FormatValueWithSpec,
412    // R8 W4 W18.4 (supervisor 2026-05-24 D1 + (a-modified)
413    // REVIVE-WITH-SHARED-MODULE): f-string content-lowering (3).
414    // VM-dispatched today; registered here to keep
415    // `build_full_opcode_parity_matrix` exhaustive.
416    BuiltinFunction::FStringContentText,
417    BuiltinFunction::FStringContentStyledText,
418    BuiltinFunction::FStringContentFragment,
419    // Math intrinsics (5)
420    // W12-stdlib-intrinsic-collapse (Wave-2-Agent-G, 2026-05-14):
421    // `IntrinsicSum` deleted — PHF `.sum()` is canonical (ADR-005 §1).
422    BuiltinFunction::IntrinsicMean,
423    BuiltinFunction::IntrinsicMin,
424    BuiltinFunction::IntrinsicMax,
425    BuiltinFunction::IntrinsicStd,
426    BuiltinFunction::IntrinsicVariance,
427    // Random (5)
428    BuiltinFunction::IntrinsicRandom,
429    BuiltinFunction::IntrinsicRandomInt,
430    BuiltinFunction::IntrinsicRandomSeed,
431    BuiltinFunction::IntrinsicRandomNormal,
432    BuiltinFunction::IntrinsicRandomArray,
433    // Distribution (5)
434    BuiltinFunction::IntrinsicDistUniform,
435    BuiltinFunction::IntrinsicDistLognormal,
436    BuiltinFunction::IntrinsicDistExponential,
437    BuiltinFunction::IntrinsicDistPoisson,
438    BuiltinFunction::IntrinsicDistSampleN,
439    // Stochastic (4)
440    BuiltinFunction::IntrinsicBrownianMotion,
441    BuiltinFunction::IntrinsicGbm,
442    BuiltinFunction::IntrinsicOuProcess,
443    BuiltinFunction::IntrinsicRandomWalk,
444    // Rolling window (7)
445    BuiltinFunction::IntrinsicRollingSum,
446    BuiltinFunction::IntrinsicRollingMean,
447    BuiltinFunction::IntrinsicRollingStd,
448    BuiltinFunction::IntrinsicRollingMin,
449    BuiltinFunction::IntrinsicRollingMax,
450    BuiltinFunction::IntrinsicEma,
451    BuiltinFunction::IntrinsicLinearRecurrence,
452    // Series transform (7)
453    BuiltinFunction::IntrinsicShift,
454    BuiltinFunction::IntrinsicDiff,
455    BuiltinFunction::IntrinsicPctChange,
456    BuiltinFunction::IntrinsicFillna,
457    BuiltinFunction::IntrinsicCumsum,
458    BuiltinFunction::IntrinsicCumprod,
459    BuiltinFunction::IntrinsicClip,
460    // Statistics (4)
461    BuiltinFunction::IntrinsicCorrelation,
462    BuiltinFunction::IntrinsicCovariance,
463    BuiltinFunction::IntrinsicPercentile,
464    BuiltinFunction::IntrinsicMedian,
465    // Trigonometric (4)
466    BuiltinFunction::IntrinsicAtan2,
467    BuiltinFunction::IntrinsicSinh,
468    BuiltinFunction::IntrinsicCosh,
469    BuiltinFunction::IntrinsicTanh,
470    // Char codes (2)
471    BuiltinFunction::IntrinsicCharCode,
472    BuiltinFunction::IntrinsicFromCharCode,
473    // Series (1)
474    BuiltinFunction::IntrinsicSeries,
475    // Vector intrinsics (12 — includes R5.4D IntrinsicVecAddI64)
476    BuiltinFunction::IntrinsicVecAbs,
477    BuiltinFunction::IntrinsicVecSqrt,
478    BuiltinFunction::IntrinsicVecLn,
479    BuiltinFunction::IntrinsicVecExp,
480    BuiltinFunction::IntrinsicVecAdd,
481    BuiltinFunction::IntrinsicVecSub,
482    BuiltinFunction::IntrinsicVecMul,
483    BuiltinFunction::IntrinsicVecDiv,
484    BuiltinFunction::IntrinsicVecMax,
485    BuiltinFunction::IntrinsicVecMin,
486    BuiltinFunction::IntrinsicVecSelect,
487    BuiltinFunction::IntrinsicVecAddI64,
488    // Matrix (4 — includes R5.4D IntrinsicMatAdd / IntrinsicMatSub)
489    BuiltinFunction::IntrinsicMatMulVec,
490    BuiltinFunction::IntrinsicMatMulMat,
491    BuiltinFunction::IntrinsicMatAdd,
492    BuiltinFunction::IntrinsicMatSub,
493    // Eval helpers (6)
494    BuiltinFunction::EvalTimeRef,
495    BuiltinFunction::EvalDateTimeExpr,
496    BuiltinFunction::EvalDataDateTimeRef,
497    BuiltinFunction::EvalDataSet,
498    BuiltinFunction::EvalDataRelative,
499    BuiltinFunction::EvalDataRelativeRange,
500    // Option/Result ctors (3)
501    BuiltinFunction::SomeCtor,
502    BuiltinFunction::OkCtor,
503    BuiltinFunction::ErrCtor,
504    // Collection ctors (4)
505    BuiltinFunction::HashMapCtor,
506    BuiltinFunction::SetCtor,
507    BuiltinFunction::DequeCtor,
508    BuiltinFunction::PriorityQueueCtor,
509    // JSON (5)
510    BuiltinFunction::JsonObjectGet,
511    BuiltinFunction::JsonArrayAt,
512    BuiltinFunction::JsonObjectKeys,
513    BuiltinFunction::JsonArrayLen,
514    BuiltinFunction::JsonObjectLen,
515    // Window functions (14)
516    BuiltinFunction::WindowRowNumber,
517    BuiltinFunction::WindowRank,
518    BuiltinFunction::WindowDenseRank,
519    BuiltinFunction::WindowNtile,
520    BuiltinFunction::WindowLag,
521    BuiltinFunction::WindowLead,
522    BuiltinFunction::WindowFirstValue,
523    BuiltinFunction::WindowLastValue,
524    BuiltinFunction::WindowNthValue,
525    BuiltinFunction::WindowSum,
526    BuiltinFunction::WindowAvg,
527    BuiltinFunction::WindowMin,
528    BuiltinFunction::WindowMax,
529    BuiltinFunction::WindowCount,
530    // Join (1)
531    BuiltinFunction::JoinExecute,
532    // Reflection (1)
533    BuiltinFunction::Reflect,
534    // Content (6 constructors)
535    BuiltinFunction::ContentChart,
536    BuiltinFunction::ContentTextCtor,
537    BuiltinFunction::ContentTableCtor,
538    BuiltinFunction::ContentCodeCtor,
539    BuiltinFunction::ContentKvCtor,
540    BuiltinFunction::ContentFragmentCtor,
541    // DateTime (6)
542    BuiltinFunction::DateTimeNow,
543    BuiltinFunction::DateTimeUtc,
544    BuiltinFunction::DateTimeParse,
545    BuiltinFunction::DateTimeFromEpoch,
546    BuiltinFunction::DateTimeFromParts,
547    BuiltinFunction::DateTimeFromUnixSecs,
548    // Concurrency (4)
549    BuiltinFunction::MutexCtor,
550    BuiltinFunction::AtomicCtor,
551    BuiltinFunction::LazyCtor,
552    BuiltinFunction::ChannelCtor,
553    // Math extras (7)
554    BuiltinFunction::Sign,
555    BuiltinFunction::Gcd,
556    BuiltinFunction::Lcm,
557    BuiltinFunction::Hypot,
558    BuiltinFunction::Clamp,
559    BuiltinFunction::IsNaN,
560    BuiltinFunction::IsFinite,
561    // Table construction
562    BuiltinFunction::MakeTableFromRows,
563    // W18.5 content builder ctors (3) — supervisor D4 (R8 W3, 2026-05-24).
564    // VM-dispatched via builtins.rs; listed here to keep
565    // `build_full_opcode_parity_matrix` exhaustive (the JIT trampoline
566    // routes BuiltinCall instructions to the VM dispatch for these
567    // identifiers).
568    BuiltinFunction::TableBuilderNew,
569    BuiltinFunction::CodeBuilderNew,
570    BuiltinFunction::KeyValueBuilderNew,
571];
572
573fn vm_only_opcode_reason(opcode: OpCode) -> Option<&'static str> {
574    // All opcodes are now compiled by the JIT translator — either natively
575    // or via FFI trampoline calls to the VM runtime.  No interpreter
576    // fallback is required.
577    //
578    // Track A.1D.2: `LoadOwnedMutableCapture` / `StoreOwnedMutableCapture`
579    // are now compiled by MirToIR via the A.1D.2 side-table (see
580    // `MirToIR::owned_mutable_capture_slots` and `register_owned_mutable_capture_slots`).
581    // The MIR's plain `LoadLocal` / `StoreLocal` on capture param slots
582    // are dispatched to pointer-deref `load.i64` / `store.i64` through
583    // the `*mut ValueWord` cell bits stored in the slot. These opcodes
584    // no longer force interpreter fallback.
585    //
586    // Track A.1E: `LoadSharedCapture` / `StoreSharedCapture` are now
587    // compiled by MirToIR via the Shared-capture side-table (see
588    // `MirToIR::shared_capture_slots`). The MIR's plain `LoadLocal` /
589    // `StoreLocal` on capture param slots are dispatched to lock-gated
590    // pointer-deref `load.i64` / `store.i64` through the
591    // `*const SharedCell` pointer bits stored in the slot. The lock
592    // fast path is a single CAS on the state byte at offset 0; slow
593    // paths call `jit_shared_lock_contended` /
594    // `jit_shared_unlock_contended`.
595    //
596    // Session 1 Commit 3: MirToIR now carries the infrastructure to
597    // lower the outer-scope `var` lifecycle opcodes
598    // (`AllocSharedLocal` / `LoadSharedLocal` / `StoreSharedLocal` /
599    // `DropSharedLocal`) — see the `MirToIR::shared_local_slots`
600    // side-table populated from `StoragePlan::slot_classes`, the
601    // `initialize_shared_local_slots` helper that eagerly allocates
602    // one `Arc<SharedCell>` per SharedCow slot at function entry,
603    // the lock-gated `read_place` / `write_place` branches that
604    // mirror the interpreter's `op_load_shared_local` /
605    // `op_store_shared_local`, and the `emit_drop` branch that calls
606    // `jit_arc_shared_release` on the slot's share.
607    //
608    // The full end-to-end lift of the preflight gate still exhibits
609    // a cell-identity mismatch under the current JIT closure
610    // dispatch pipeline — see `project_jit_closure_fix.md`'s
611    // follow-up note. To keep the a1e gated e2e tests green (which
612    // rely on the outer frame staying interpreted while the closure
613    // body JIT-runs against the interpreter-allocated cell) the gate
614    // remains in place for the four local opcodes.
615    //
616    // The infrastructure is wired and tested at the FFI + preflight
617    // level so a follow-up commit can flip this match arm to `None`
618    // once the outer-frame cell-identity handshake is resolved.
619    //
620    // The module-binding variants remain gated for the same reason
621    // plus the per-module side-table requirement — their storage
622    // lives outside the MIR slot space.
623    //
624    // WS-12 as-cast (v0.3 round-6, 2026-05-22): the `ConvertTo*` /
625    // `TryConvertTo*` opcode family (`expr as int/number/string/bool/
626    // decimal/char`) is NOT lowered by the JIT translator. The opcode-
627    // FFI trampoline (`ffi/generic_builtin.rs::dispatch_opcode`) is a
628    // deliberate pass-through stub — it returns the operand bits
629    // unchanged, so a JIT'd `x as int` silently yields the unconverted
630    // value (`true` instead of `1`, or a raw `Ptr(Option)` pointer
631    // printed as garbage). That is a real VM≠JIT correctness divergence.
632    // Until the per-kind typed convert opcode bodies land on the JIT
633    // side, these opcodes are VM-only: the preflight gate routes any
634    // program containing them to the bytecode interpreter via the
635    // documented `[jit-fallback]` path (NOT silent-wrong-output). This
636    // is surface-and-stop, not a dynamic-fallback shim — the JIT simply
637    // does not implement these opcodes yet.
638    match opcode {
639        OpCode::AllocSharedModuleBinding
640        | OpCode::LoadSharedModuleBinding
641        | OpCode::StoreSharedModuleBinding => Some(
642            "A.1C.3 outer-scope Shared module-binding opcode; Cranelift lowering pending",
643        ),
644        OpCode::ConvertToInt
645        | OpCode::ConvertToNumber
646        | OpCode::ConvertToString
647        | OpCode::ConvertToBool
648        | OpCode::ConvertToDecimal
649        | OpCode::ConvertToChar
650        | OpCode::TryConvertToInt
651        | OpCode::TryConvertToNumber
652        | OpCode::TryConvertToString
653        | OpCode::TryConvertToBool
654        | OpCode::TryConvertToDecimal
655        | OpCode::TryConvertToChar => Some(
656            "WS-12: ConvertTo*/TryConvertTo* (`as` cast) not lowered by the \
657             JIT translator; VM-only until per-kind typed convert bodies land",
658        ),
659        _ => None,
660    }
661}
662
663fn is_supported_builtin(_builtin: BuiltinFunction) -> bool {
664    // All builtins are now supported — either via dedicated JIT lowering
665    // or via the generic builtin FFI trampoline.
666    true
667}
668
669/// Run JIT compatibility preflight on a raw instruction slice.
670///
671/// This is the shared core used by both `preflight_blob_jit_compatibility`
672/// and `preflight_jit_compatibility`. It enables per-function JIT/interpreter
673/// decisions in the mixed function table.
674pub fn preflight_instructions(instructions: &[Instruction]) -> JitPreflightReport {
675    let mut report = JitPreflightReport::default();
676
677    for instr in instructions {
678        if vm_only_opcode_reason(instr.opcode).is_some() {
679            push_unique_opcode(&mut report.vm_only_opcodes, instr.opcode);
680        }
681
682        if instr.opcode == OpCode::BuiltinCall {
683            if let Some(Operand::Builtin(builtin)) = instr.operand {
684                if !is_supported_builtin(builtin) {
685                    push_unique_builtin(&mut report.unsupported_builtins, builtin);
686                }
687            }
688        }
689    }
690
691    sort_opcodes(&mut report.vm_only_opcodes);
692    sort_builtins(&mut report.unsupported_builtins);
693    report
694}
695
696/// Run JIT compatibility preflight on a single function blob.
697///
698/// Same logic as the whole-program preflight but operating on a single
699/// `FunctionBlob`'s instruction stream. This enables per-function
700/// JIT/interpreter decisions in the mixed function table.
701pub fn preflight_blob_jit_compatibility(
702    blob: &shape_vm::bytecode::FunctionBlob,
703) -> JitPreflightReport {
704    preflight_instructions(&blob.instructions)
705}
706
707/// Run JIT preflight and collect all constructs that require VM fallback.
708pub fn preflight_jit_compatibility(program: &BytecodeProgram) -> JitPreflightReport {
709    let mut report = JitPreflightReport::default();
710
711    for instr in &program.instructions {
712        if vm_only_opcode_reason(instr.opcode).is_some() {
713            push_unique_opcode(&mut report.vm_only_opcodes, instr.opcode);
714        }
715
716        if instr.opcode == OpCode::BuiltinCall {
717            if let Some(Operand::Builtin(builtin)) = instr.operand {
718                if !is_supported_builtin(builtin) {
719                    push_unique_builtin(&mut report.unsupported_builtins, builtin);
720                }
721            }
722        }
723    }
724
725    sort_opcodes(&mut report.vm_only_opcodes);
726    sort_builtins(&mut report.unsupported_builtins);
727    report
728}
729
730/// Build a program-specific JIT parity matrix.
731///
732/// This is intended for diagnostics/CI tooling that wants an automatic
733/// per-program support report.
734pub fn build_program_parity_matrix(program: &BytecodeProgram) -> Vec<JitParityEntry> {
735    let mut opcodes = Vec::new();
736    let mut builtins = Vec::new();
737
738    for instr in &program.instructions {
739        push_unique_opcode(&mut opcodes, instr.opcode);
740        if instr.opcode == OpCode::BuiltinCall {
741            if let Some(Operand::Builtin(builtin)) = instr.operand {
742                push_unique_builtin(&mut builtins, builtin);
743            }
744        }
745    }
746
747    sort_opcodes(&mut opcodes);
748    sort_builtins(&mut builtins);
749
750    let mut matrix = Vec::with_capacity(opcodes.len() + builtins.len());
751
752    for opcode in opcodes {
753        if let Some(reason) = vm_only_opcode_reason(opcode) {
754            matrix.push(JitParityEntry {
755                target: JitParityTarget::Opcode(opcode),
756                jit_supported: false,
757                reason,
758            });
759        } else {
760            matrix.push(JitParityEntry {
761                target: JitParityTarget::Opcode(opcode),
762                jit_supported: true,
763                reason: "Opcode is lowered by the JIT translator.",
764            });
765        }
766    }
767
768    for builtin in builtins {
769        if is_supported_builtin(builtin) {
770            matrix.push(JitParityEntry {
771                target: JitParityTarget::Builtin(builtin),
772                jit_supported: true,
773                reason: "Builtin is lowered by JIT builtin handlers.",
774            });
775        } else {
776            matrix.push(JitParityEntry {
777                target: JitParityTarget::Builtin(builtin),
778                jit_supported: false,
779                reason: "Builtin is not lowered by JIT and must run on VM.",
780            });
781        }
782    }
783
784    matrix.sort_by_key(|entry| format!("{:?}", entry.target));
785    matrix
786}
787
788/// Build a full opcode parity matrix across the entire VM opcode surface.
789pub fn build_full_opcode_parity_matrix() -> Vec<JitParityEntry> {
790    let mut matrix = Vec::with_capacity(ALL_OPCODES.len());
791    for &opcode in ALL_OPCODES {
792        if let Some(reason) = vm_only_opcode_reason(opcode) {
793            matrix.push(JitParityEntry {
794                target: JitParityTarget::Opcode(opcode),
795                jit_supported: false,
796                reason,
797            });
798        } else {
799            matrix.push(JitParityEntry {
800                target: JitParityTarget::Opcode(opcode),
801                jit_supported: true,
802                reason: "Opcode is lowered by the JIT translator.",
803            });
804        }
805    }
806    matrix.sort_by_key(|entry| format!("{:?}", entry.target));
807    matrix
808}
809
810/// Build a full builtin parity matrix across the entire BuiltinFunction surface.
811pub fn build_full_builtin_parity_matrix() -> Vec<JitParityEntry> {
812    let mut matrix = Vec::with_capacity(ALL_BUILTINS.len());
813    for &builtin in ALL_BUILTINS {
814        if is_supported_builtin(builtin) {
815            matrix.push(JitParityEntry {
816                target: JitParityTarget::Builtin(builtin),
817                jit_supported: true,
818                reason: "Builtin is lowered by JIT builtin handlers.",
819            });
820        } else {
821            matrix.push(JitParityEntry {
822                target: JitParityTarget::Builtin(builtin),
823                jit_supported: false,
824                reason: "Builtin is not lowered by JIT and must run on VM.",
825            });
826        }
827    }
828    matrix.sort_by_key(|entry| format!("{:?}", entry.target));
829    matrix
830}
831
832/// Check if a bytecode program can be fully JIT-compiled
833#[inline(always)]
834pub fn can_jit_compile(program: &BytecodeProgram) -> bool {
835    preflight_jit_compatibility(program).can_jit()
836}
837
838/// Get a list of unsupported opcodes in a program (for debugging)
839#[inline(always)]
840pub fn get_unsupported_opcodes(program: &BytecodeProgram) -> Vec<OpCode> {
841    let report = preflight_jit_compatibility(program);
842    let mut unsupported = report.vm_only_opcodes;
843
844    if !report.unsupported_builtins.is_empty() && !unsupported.contains(&OpCode::BuiltinCall) {
845        unsupported.push(OpCode::BuiltinCall);
846    }
847
848    sort_opcodes(&mut unsupported);
849    unsupported
850}
851
852/// Get a list of opcodes that have placeholder (incomplete) implementations
853pub fn get_incomplete_opcodes(_program: &BytecodeProgram) -> Vec<OpCode> {
854    // All opcodes now have full implementations (native or FFI).
855    Vec::new()
856}
857
858#[cfg(test)]
859mod tests {
860    use super::*;
861    use shape_vm::bytecode::{Instruction, Operand};
862
863    #[test]
864    fn preflight_accepts_all_opcodes() {
865        // All opcodes are now supported — no VM-only gates remain.
866        let program = BytecodeProgram {
867            instructions: vec![Instruction::simple(OpCode::Await)],
868            ..Default::default()
869        };
870        let report = preflight_jit_compatibility(&program);
871        assert!(report.can_jit());
872    }
873
874    #[test]
875    fn preflight_accepts_all_builtins() {
876        // All builtins are now supported — dedicated or generic trampoline.
877        let program = BytecodeProgram {
878            instructions: vec![Instruction::new(
879                OpCode::BuiltinCall,
880                Some(Operand::Builtin(BuiltinFunction::Snapshot)),
881            )],
882            ..Default::default()
883        };
884        let report = preflight_jit_compatibility(&program);
885        assert!(report.can_jit());
886    }
887
888    #[test]
889    fn parity_matrix_marks_all_builtins_supported() {
890        let program = BytecodeProgram {
891            instructions: vec![Instruction::new(
892                OpCode::BuiltinCall,
893                Some(Operand::Builtin(BuiltinFunction::Snapshot)),
894            )],
895            ..Default::default()
896        };
897        let matrix = build_program_parity_matrix(&program);
898        assert!(matrix.iter().all(|row| row.jit_supported));
899    }
900
901    #[test]
902    fn preflight_instructions_compatible_slice() {
903        let instructions = vec![
904            Instruction::simple(OpCode::PushConst),
905            Instruction::simple(OpCode::AddInt),
906            Instruction::simple(OpCode::ReturnValue),
907        ];
908        let report = preflight_instructions(&instructions);
909        assert!(report.can_jit());
910    }
911
912    #[test]
913    fn preflight_instructions_all_opcodes_pass() {
914        // Even async opcodes now pass preflight.
915        let instructions = vec![
916            Instruction::simple(OpCode::PushConst),
917            Instruction::simple(OpCode::Await),
918            Instruction::simple(OpCode::ReturnValue),
919        ];
920        let report = preflight_instructions(&instructions);
921        assert!(report.can_jit());
922    }
923
924    #[test]
925    fn preflight_blob_passes_with_spawn_task() {
926        use shape_vm::bytecode::FunctionBlob;
927
928        let blob = FunctionBlob {
929            content_hash: shape_vm::bytecode::FunctionHash::ZERO,
930            name: "test_fn".to_string(),
931            arity: 0,
932            param_names: vec![],
933            locals_count: 0,
934            is_closure: false,
935            captures_count: 0,
936            is_async: false,
937            ref_params: vec![],
938            ref_mutates: vec![],
939            mutable_captures: vec![],
940            instructions: vec![
941                Instruction::simple(OpCode::PushConst),
942                Instruction::simple(OpCode::SpawnTask),
943                Instruction::simple(OpCode::ReturnValue),
944            ],
945            constants: vec![],
946            strings: vec![],
947            required_permissions: Default::default(),
948            dependencies: vec![],
949            callee_names: vec![],
950            type_schemas: vec![],
951            source_map: vec![],
952            foreign_dependencies: vec![],
953            frame_descriptor: None,
954        };
955
956        let report = preflight_blob_jit_compatibility(&blob);
957        assert!(report.can_jit());
958    }
959
960    #[test]
961    fn all_opcodes_pass_preflight() {
962        // Exhaustive check: every opcode in ALL_OPCODES must pass preflight.
963        for &opcode in ALL_OPCODES {
964            assert!(
965                vm_only_opcode_reason(opcode).is_none(),
966                "Opcode {:?} should pass preflight",
967                opcode
968            );
969        }
970    }
971
972    #[test]
973    fn all_builtins_pass_preflight() {
974        // Exhaustive check: every builtin in ALL_BUILTINS must be supported.
975        for &builtin in ALL_BUILTINS {
976            assert!(
977                is_supported_builtin(builtin),
978                "Builtin {:?} should be supported",
979                builtin
980            );
981        }
982    }
983
984    // Track A.1D.2 — OwnedMutable opcodes are now JIT-compiled via the
985    // MIR-side `owned_mutable_capture_slots` side-table (see
986    // `MirToIR::register_owned_mutable_capture_slots` +
987    // `read_place`/`write_place` pointer-deref lowering). These tests
988    // pin that the preflight gate accepts OwnedMutable opcodes while
989    // keeping Shared opcodes gated.
990    //
991    // Previously (A.1C / A.1D partial) these tests asserted rejection of
992    // LoadOwnedMutableCapture / StoreOwnedMutableCapture; A.1D.2 flips
993    // them to assert acceptance, matching the lifted `vm_only_opcode_reason`
994    // entry.
995
996    #[test]
997    fn a1d2_preflight_accepts_load_owned_mutable_capture() {
998        let program = BytecodeProgram {
999            instructions: vec![
1000                Instruction::new(OpCode::LoadOwnedMutableCapture, Some(Operand::Local(0))),
1001                Instruction::simple(OpCode::ReturnValue),
1002            ],
1003            ..Default::default()
1004        };
1005        let report = preflight_jit_compatibility(&program);
1006        assert!(
1007            report.can_jit(),
1008            "A.1D.2: LoadOwnedMutableCapture is now lowered by MirToIR; \
1009             preflight must accept it"
1010        );
1011        assert!(
1012            !report
1013                .vm_only_opcodes
1014                .contains(&OpCode::LoadOwnedMutableCapture),
1015            "A.1D.2 removes LoadOwnedMutableCapture from vm_only_opcode_reason"
1016        );
1017    }
1018
1019    #[test]
1020    fn a1d2_preflight_accepts_store_owned_mutable_capture() {
1021        let program = BytecodeProgram {
1022            instructions: vec![
1023                Instruction::new(OpCode::StoreOwnedMutableCapture, Some(Operand::Local(0))),
1024                Instruction::simple(OpCode::ReturnValue),
1025            ],
1026            ..Default::default()
1027        };
1028        let report = preflight_jit_compatibility(&program);
1029        assert!(
1030            report.can_jit(),
1031            "A.1D.2: StoreOwnedMutableCapture is now lowered by MirToIR; \
1032             preflight must accept it"
1033        );
1034        assert!(
1035            !report
1036                .vm_only_opcodes
1037                .contains(&OpCode::StoreOwnedMutableCapture),
1038            "A.1D.2 removes StoreOwnedMutableCapture from vm_only_opcode_reason"
1039        );
1040    }
1041
1042    // Track A.1E replaces the A.1C rejection tests for
1043    // `LoadSharedCapture` / `StoreSharedCapture` with acceptance
1044    // assertions on the lifted gate. The Cranelift inline parking_lot
1045    // fast path is now live — see `MirToIR::shared_capture_slots` and
1046    // the lock-gated pointer-deref in `read_place` / `write_place`.
1047
1048    #[test]
1049    fn a1e_preflight_accepts_load_shared_capture() {
1050        let program = BytecodeProgram {
1051            instructions: vec![
1052                Instruction::new(OpCode::LoadSharedCapture, Some(Operand::Local(0))),
1053                Instruction::simple(OpCode::ReturnValue),
1054            ],
1055            ..Default::default()
1056        };
1057        let report = preflight_jit_compatibility(&program);
1058        assert!(
1059            report.can_jit(),
1060            "A.1E: LoadSharedCapture is now lowered by MirToIR; \
1061             preflight must accept it"
1062        );
1063        assert!(
1064            !report.vm_only_opcodes.contains(&OpCode::LoadSharedCapture),
1065            "A.1E removes LoadSharedCapture from vm_only_opcode_reason"
1066        );
1067    }
1068
1069    #[test]
1070    fn a1e_preflight_accepts_store_shared_capture() {
1071        let program = BytecodeProgram {
1072            instructions: vec![
1073                Instruction::new(OpCode::StoreSharedCapture, Some(Operand::Local(0))),
1074                Instruction::simple(OpCode::ReturnValue),
1075            ],
1076            ..Default::default()
1077        };
1078        let report = preflight_jit_compatibility(&program);
1079        assert!(
1080            report.can_jit(),
1081            "A.1E: StoreSharedCapture is now lowered by MirToIR; \
1082             preflight must accept it"
1083        );
1084        assert!(
1085            !report.vm_only_opcodes.contains(&OpCode::StoreSharedCapture),
1086            "A.1E removes StoreSharedCapture from vm_only_opcode_reason"
1087        );
1088    }
1089
1090    #[test]
1091    fn a1c_preflight_immutable_closure_body_still_jits() {
1092        // Closure bodies that DON'T contain the A.1B mutable-cell opcodes
1093        // should still JIT-compile — the A.1C gate is narrow.
1094        let program = BytecodeProgram {
1095            instructions: vec![
1096                Instruction::simple(OpCode::PushConst),
1097                Instruction::simple(OpCode::AddInt),
1098                Instruction::simple(OpCode::ReturnValue),
1099            ],
1100            ..Default::default()
1101        };
1102        let report = preflight_jit_compatibility(&program);
1103        assert!(report.can_jit());
1104    }
1105
1106    // Track A.1E — A.1E lifts the capture-side Shared opcodes
1107    // (`Load/StoreSharedCapture`). The outer-scope `var`-lifecycle
1108    // opcodes remain gated pending follow-up work. This guard pins
1109    // exactly which opcodes are still interpreter-only after A.1E.
1110
1111    #[test]
1112    fn cell_identity_preflight_accepts_outer_shared_local_opcodes() {
1113        // cell-identity #3: after the JIT/VM cell-identity handshake
1114        // fix, the outer-scope `var` lifecycle opcodes pass preflight.
1115        // MirToIR lowers them via `shared_local_slots` +
1116        // `initialize_shared_local_slots` + lock-gated
1117        // `read_place` / `write_place` / `emit_drop` (see
1118        // `mir_compiler/mod.rs`, `blocks.rs`, `places.rs`,
1119        // `ownership.rs`). The trampoline boundary preserves cell
1120        // identity by sharing the trampoline VM's stack slot through
1121        // `jit_shared_local_install` — see
1122        // `crates/shape-jit/src/ffi/object/closure.rs`.
1123        for op in [
1124            OpCode::AllocSharedLocal,
1125            OpCode::LoadSharedLocal,
1126            OpCode::StoreSharedLocal,
1127            OpCode::DropSharedLocal,
1128        ] {
1129            let program = BytecodeProgram {
1130                instructions: vec![
1131                    Instruction::new(op, Some(Operand::Local(0))),
1132                    Instruction::simple(OpCode::ReturnValue),
1133                ],
1134                ..Default::default()
1135            };
1136            let report = preflight_jit_compatibility(&program);
1137            assert!(
1138                report.can_jit(),
1139                "cell-identity: outer-scope Shared local opcode {:?} \
1140                 must pass preflight after the handshake fix",
1141                op
1142            );
1143            assert!(
1144                !report.vm_only_opcodes.contains(&op),
1145                "cell-identity: {:?} must be removed from vm_only_opcode_reason",
1146                op
1147            );
1148        }
1149    }
1150
1151    #[test]
1152    fn session1_preflight_still_rejects_outer_shared_module_binding_opcodes() {
1153        // The module-binding counterparts remain gated — their
1154        // storage lives on a per-module side-table, not on a MIR slot,
1155        // and needs its own lowering (A.1C.3 follow-up). This guard
1156        // pins the exact set still rejected after Session 1 Commit 3.
1157        for op in [
1158            OpCode::AllocSharedModuleBinding,
1159            OpCode::LoadSharedModuleBinding,
1160            OpCode::StoreSharedModuleBinding,
1161        ] {
1162            let program = BytecodeProgram {
1163                instructions: vec![
1164                    Instruction::new(op, Some(Operand::Local(0))),
1165                    Instruction::simple(OpCode::ReturnValue),
1166                ],
1167                ..Default::default()
1168            };
1169            let report = preflight_jit_compatibility(&program);
1170            assert!(
1171                !report.can_jit(),
1172                "Outer-scope Shared module-binding opcode {:?} must \
1173                 remain preflight-rejected pending A.1C.3 JIT lowering",
1174                op
1175            );
1176        }
1177    }
1178
1179    #[test]
1180    fn a1e_preflight_accepts_capture_side_shared_opcodes() {
1181        // Symmetric to the outer-scope rejection: the capture-side
1182        // opcodes `Load/StoreSharedCapture` DO pass preflight after
1183        // A.1E.
1184        for op in [OpCode::LoadSharedCapture, OpCode::StoreSharedCapture] {
1185            let program = BytecodeProgram {
1186                instructions: vec![
1187                    Instruction::new(op, Some(Operand::Local(0))),
1188                    Instruction::simple(OpCode::ReturnValue),
1189                ],
1190                ..Default::default()
1191            };
1192            let report = preflight_jit_compatibility(&program);
1193            assert!(
1194                report.can_jit(),
1195                "Capture-side Shared opcode {:?} must pass preflight after A.1E",
1196                op
1197            );
1198        }
1199    }
1200
1201    #[test]
1202    fn ws12_preflight_rejects_convert_cast_opcodes() {
1203        // WS-12: the `ConvertTo*` / `TryConvertTo*` (`as` cast) opcode
1204        // family is not lowered by the JIT translator — the opcode-FFI
1205        // trampoline `dispatch_opcode` is a pass-through stub that
1206        // returns the operand unchanged, silently yielding the
1207        // unconverted value (`true` instead of `1`, a raw Ptr printed
1208        // as garbage). Preflight must reject any program containing one
1209        // so `--mode jit` cleanly falls back to the bytecode
1210        // interpreter rather than producing wrong output. This guard
1211        // pins the exact gated set.
1212        for op in [
1213            OpCode::ConvertToInt,
1214            OpCode::ConvertToNumber,
1215            OpCode::ConvertToString,
1216            OpCode::ConvertToBool,
1217            OpCode::ConvertToDecimal,
1218            OpCode::ConvertToChar,
1219            OpCode::TryConvertToInt,
1220            OpCode::TryConvertToNumber,
1221            OpCode::TryConvertToString,
1222            OpCode::TryConvertToBool,
1223            OpCode::TryConvertToDecimal,
1224            OpCode::TryConvertToChar,
1225        ] {
1226            let program = BytecodeProgram {
1227                instructions: vec![
1228                    Instruction::simple(op),
1229                    Instruction::simple(OpCode::ReturnValue),
1230                ],
1231                ..Default::default()
1232            };
1233            let report = preflight_jit_compatibility(&program);
1234            assert!(
1235                !report.can_jit(),
1236                "WS-12: cast opcode {:?} must be preflight-rejected \
1237                 (JIT translator does not lower it)",
1238                op
1239            );
1240            assert!(
1241                report.vm_only_opcodes.contains(&op),
1242                "WS-12: {:?} must appear in vm_only_opcodes",
1243                op
1244            );
1245        }
1246    }
1247}