Skip to main content

rustpython_compiler_core/bytecode/
instruction.rs

1use core::{fmt, marker::PhantomData};
2
3use crate::marshal::MarshalError;
4
5use super::{OpArg, OpArgByte, OpArgType, oparg};
6
7macro_rules! define_opcodes {
8    (
9        #[repr($typ:ident)]
10        $opcode_vis:vis enum $opcode_name:ident;
11
12        $(#[$instr_meta:meta])*
13        $instr_vis:vis enum $instr_name:ident {
14            $(
15                $(#[$op_meta:meta])*
16                    $op_name:ident $({ $arg_name:ident: Arg<$arg_type:ty> $(,)? })? = $op_id:expr
17            ),* $(,)?
18        }
19    ) => {
20        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
21        #[repr($typ)]
22        $opcode_vis enum $opcode_name {
23            $($op_name = $op_id),*
24        }
25
26        impl $opcode_name {
27            #[doc = concat!("Converts this opcode to [`", stringify!($instr_name), "`].")]
28            #[must_use]
29            #[inline]
30            $opcode_vis const fn as_instruction(&self) -> $instr_name {
31                // SAFETY: `$opcode_name` and `$instr_name` are both `#[repr($typ)]`
32                // enums sharing identical explicit discriminants, and every
33                // `$instr_name` payload field is the zero-sized `Arg<T>` marker
34                // (see the `size_of` assertion near its definition), so both
35                // enums have the same one-`$typ`-wide representation: just the
36                // discriminant. Converting a live `$opcode_name` value therefore
37                // yields the `$instr_name` variant with the matching discriminant.
38                unsafe { core::mem::transmute(*self) }
39            }
40
41            /// Map a specialized or instrumented opcode back to its adaptive (base) variant.
42            #[must_use]
43            #[inline]
44            $opcode_vis const fn deoptimize(self) -> Self {
45                match self.deopt() {
46                    Some(v) => v,
47                    None => {
48                        // Instrumented opcodes map back to their base
49                        match self.to_base() {
50                            Some(v) => v,
51                            None => self,
52                        }
53                    }
54                }
55            }
56
57            // NOTE: Keep private. Will be exposed under `try_from_u8/try_from_u16`.
58            // Kept as a match rather than a range check + transmute: `$op_id`
59            // values are not contiguous (specialized/instrumented opcodes leave
60            // gaps), so validity can't be expressed as a simple bound.
61            pub(super) const fn try_from_numeric(value: $typ) -> Result<Self, $crate::marshal::MarshalError> {
62                match value {
63                    $($op_id => Ok(Self::$op_name),)*
64                    _ => Err($crate::marshal::MarshalError::InvalidBytecode),
65                }
66            }
67
68            // NOTE: Keep private. Will be exposed under `as_u8/as_u16`.
69            #[must_use]
70            #[inline]
71            pub(super) const fn as_numeric(self) -> $typ {
72                // `$opcode_name` is `#[repr($typ)]` with an explicit `$op_id`
73                // discriminant on every variant, so this is a plain identity cast.
74                self as $typ
75            }
76        }
77
78        impl From<$opcode_name> for $instr_name {
79            fn from(opcode: $opcode_name) -> Self {
80                opcode.as_instruction()
81            }
82        }
83
84
85        impl TryFrom<$typ> for $opcode_name {
86            type Error = $crate::marshal::MarshalError;
87
88            fn try_from(value: $typ) -> Result<Self, Self::Error> {
89                Self::try_from_numeric(value)
90            }
91        }
92
93        impl From<$opcode_name> for $typ {
94            fn from(opcode: $opcode_name) -> Self {
95                opcode.as_numeric()
96            }
97        }
98
99        #[derive(Clone, Copy, Debug)]
100        #[repr($typ)] // TODO: Remove this repr
101        $instr_vis enum $instr_name {
102            $(
103                $(#[$op_meta])*
104                $op_name $({ $arg_name: Arg<$arg_type> })? = $op_id // TODO: Don't assign value
105            ),*
106        }
107
108        // Every `$instr_name` payload field is the zero-sized `Arg<T>` marker, so
109        // (combined with the `#[repr($typ)]` above) each variant's representation
110        // is exactly its `$typ` discriminant with no padding. `as_opcode` and
111        // `$opcode_name::as_instruction` rely on this to convert via
112        // `mem::transmute` instead of a per-variant match.
113        const _: () = assert!(core::mem::size_of::<$instr_name>() == core::mem::size_of::<$typ>());
114
115        impl $instr_name {
116            #[doc = concat!("Get the corresponding [`", stringify!($opcode_name), "`].")]
117            #[must_use]
118            #[inline]
119            $instr_vis const fn as_opcode(&self) -> $opcode_name {
120                // SAFETY: symmetric to `$opcode_name::as_instruction` above:
121                // `*self`'s representation is exactly its `$typ` discriminant
122                // (checked by the `size_of` assertion above this impl), and that
123                // discriminant is always a valid `$opcode_name` discriminant
124                // because both enums share the same explicit `$op_id` list.
125                unsafe { core::mem::transmute(*self) }
126            }
127
128            #[must_use]
129            $instr_vis const fn label_arg(&self) -> Option<Arg<oparg::Label>> {
130                //define_opcodes!(@label_arm Self::$op_name $({ $arg_name } : $arg_type)?)
131                define_opcodes!(@match self, Self, [$($op_name $({ $arg_name : $arg_type })?),*])
132            }
133
134            #[must_use]
135            pub const fn to_base(self) -> Option<Self> {
136                if let Some(op) = self.as_opcode().to_base() {
137                    Some(op.as_instruction())
138                } else {
139                    None
140                }
141            }
142
143            #[must_use]
144            pub const fn to_instrumented(self) -> Option<Self> {
145                if let Some(op) = self.as_opcode().to_instrumented() {
146                    Some(op.as_instruction())
147                } else {
148                    None
149                }
150            }
151
152            /// Returns `true` if this is any instrumented opcode.
153            #[must_use]
154            $instr_vis const fn is_instrumented(&self) -> bool {
155                self.as_opcode().is_instrumented()
156            }
157
158            #[must_use]
159            $instr_vis const fn is_unconditional_jump(&self) -> bool {
160                self.as_opcode().is_unconditional_jump()
161            }
162
163            #[must_use]
164            $instr_vis const fn is_block_push(&self) -> bool {
165                self.as_opcode().is_block_push()
166            }
167
168            #[must_use]
169            $instr_vis const fn is_scope_exit(&self) -> bool {
170                self.as_opcode().is_scope_exit()
171            }
172
173            #[must_use]
174            $instr_vis const fn is_terminator(&self) -> bool {
175                self.as_opcode().is_terminator()
176            }
177
178            #[must_use]
179            $instr_vis const fn is_no_fallthrough(&self) -> bool {
180                self.as_opcode().is_no_fallthrough()
181            }
182
183            #[must_use]
184            $instr_vis const fn has_target(&self) -> bool {
185                self.as_opcode().has_target()
186            }
187
188            #[must_use]
189            $instr_vis const fn has_jump(&self) -> bool {
190                self.as_opcode().has_jump()
191            }
192
193            #[must_use]
194            $instr_vis const fn has_arg(&self) -> bool {
195                self.as_opcode().has_arg()
196            }
197
198            #[must_use]
199            $instr_vis const fn has_const(&self) -> bool {
200                self.as_opcode().has_const()
201            }
202
203            #[must_use]
204            $instr_vis const fn has_eval_break(&self) -> bool {
205                self.as_opcode().has_eval_break()
206            }
207
208            #[must_use]
209            $instr_vis const fn is_assembler(&self) -> bool {
210                self.as_opcode().is_assembler()
211            }
212
213            #[must_use]
214            $instr_vis const fn cache_entries(&self) -> usize{
215                self.as_opcode().cache_entries()
216            }
217
218            /// Map a specialized or instrumented opcode back to its adaptive (base) variant.
219            #[must_use]
220            $instr_vis const fn deoptimize(&self) -> Self {
221                self.as_opcode().deoptimize().as_instruction()
222            }
223
224            #[must_use]
225            $instr_vis fn stack_effect_jump(&self, oparg: u32) -> i32 {
226                self.as_opcode().stack_effect_jump(oparg)
227            }
228
229            #[must_use]
230            $instr_vis fn stack_effect_info(&self, oparg: u32) -> StackEffect {
231                self.as_opcode().stack_effect_info(oparg)
232            }
233
234            #[must_use]
235            $instr_vis fn stack_effect(&self, oparg: u32) -> i32 {
236                self.as_opcode().stack_effect(oparg)
237            }
238        }
239
240        impl From<$instr_name> for $opcode_name {
241            fn from(instr: $instr_name) -> Self {
242                instr.as_opcode()
243            }
244        }
245
246        impl TryFrom<$typ> for $instr_name {
247            type Error = $crate::marshal::MarshalError;
248
249            fn try_from(value: $typ) -> Result<Self, Self::Error> {
250                $opcode_name::try_from_numeric(value).map(Into::into)
251            }
252        }
253
254        impl From<$instr_name> for $typ {
255            fn from(instr: $instr_name) -> Self {
256                instr.as_opcode().into()
257            }
258        }
259    };
260
261    // Base case: empty list
262    (@match $self:expr, $name:ident, []) => {
263        None
264    };
265
266    // Label field variant (with trailing variants)
267    (@match $self:expr, $name:ident, [$variant:ident { $field:ident : Label } , $($rest:tt)*]) => {
268        match $self {
269            $name::$variant { $field } => Some(*$field),
270            other => define_opcodes!(@match other, $name, [$($rest)*]),
271        }
272    };
273
274    // Label field variant (last in list)
275    (@match $self:expr, $name:ident, [$variant:ident { $field:ident : Label }]) => {
276        match $self {
277            $name::$variant { $field } => Some(*$field),
278            other => define_opcodes!(@match other, $name, []),
279        }
280    };
281
282    // Non-Label field variant (with trailing variants)
283    (@match $self:expr, $name:ident, [$variant:ident { $field:ident : $type:ty } , $($rest:tt)*]) => {
284        match $self {
285            $name::$variant { .. } => None,
286            other => define_opcodes!(@match other, $name, [$($rest)*]),
287        }
288    };
289
290    // Non-Label field variant (last in list)
291    (@match $self:expr, $name:ident, [$variant:ident { $field:ident : $type:ty }]) => {
292        match $self {
293            $name::$variant { .. } => None,
294            _ => define_opcodes!(@match _, $name, []),
295        }
296    };
297
298    // Unit variant (with trailing variants)
299    (@match $self:expr, $name:ident, [$variant:ident , $($rest:tt)*]) => {
300        match $self {
301            $name::$variant => None,
302            other => define_opcodes!(@match other, $name, [$($rest)*]),
303        }
304    };
305
306    // Unit variant (last in list)
307    (@match $self:expr, $name:ident, [$variant:ident]) => {
308        match $self {
309            $name::$variant => None,
310            _ => define_opcodes!(@match _, $name, []),
311        }
312    };
313}
314
315define_opcodes!(
316    #[repr(u8)]
317    pub enum Opcode;
318
319    pub enum Instruction {
320        Cache = 0,
321        BinarySlice = 1,
322        BuildTemplate = 2,
323        BinaryOpInplaceAddUnicode = 3,
324        CallFunctionEx = 4,
325        CheckEgMatch = 5,
326        CheckExcMatch = 6,
327        CleanupThrow = 7,
328        DeleteSubscr = 8,
329        EndFor = 9,
330        EndSend = 10,
331        ExitInitCheck = 11,
332        FormatSimple = 12,
333        FormatWithSpec = 13,
334        GetAiter = 14,
335        GetAnext = 15,
336        GetIter = 16,
337        Reserved = 17,
338        GetLen = 18,
339        GetYieldFromIter = 19,
340        InterpreterExit = 20,
341        LoadBuildClass = 21,
342        LoadLocals = 22,
343        MakeFunction = 23,
344        MatchKeys = 24,
345        MatchMapping = 25,
346        MatchSequence = 26,
347        Nop = 27,
348        NotTaken = 28,
349        PopExcept = 29,
350        PopIter = 30,
351        PopTop = 31,
352        PushExcInfo = 32,
353        PushNull = 33,
354        ReturnGenerator = 34,
355        ReturnValue = 35,
356        SetupAnnotations = 36,
357        StoreSlice = 37,
358        StoreSubscr = 38,
359        ToBool = 39,
360        UnaryInvert = 40,
361        UnaryNegative = 41,
362        UnaryNot = 42,
363        WithExceptStart = 43,
364        BinaryOp {
365            op: Arg<oparg::BinaryOperator>,
366        } = 44,
367        BuildInterpolation {
368            format: Arg<u32>,
369        } = 45,
370        BuildList {
371            count: Arg<u32>,
372        } = 46,
373        BuildMap {
374            count: Arg<u32>,
375        } = 47,
376        BuildSet {
377            count: Arg<u32>,
378        } = 48,
379        BuildSlice {
380            argc: Arg<oparg::BuildSliceArgCount>,
381        } = 49,
382        BuildString {
383            count: Arg<u32>,
384        } = 50,
385        BuildTuple {
386            count: Arg<u32>,
387        } = 51,
388        Call {
389            argc: Arg<u32>,
390        } = 52,
391        CallIntrinsic1 {
392            func: Arg<oparg::IntrinsicFunction1>,
393        } = 53,
394        CallIntrinsic2 {
395            func: Arg<oparg::IntrinsicFunction2>,
396        } = 54,
397        CallKw {
398            argc: Arg<u32>,
399        } = 55,
400        CompareOp {
401            opname: Arg<oparg::ComparisonOperator>,
402        } = 56,
403        ContainsOp {
404            invert: Arg<oparg::Invert>,
405        } = 57,
406        ConvertValue {
407            oparg: Arg<oparg::ConvertValueOparg>,
408        } = 58,
409        Copy {
410            i: Arg<u32>,
411        } = 59,
412        CopyFreeVars {
413            n: Arg<u32>,
414        } = 60,
415        DeleteAttr {
416            namei: Arg<oparg::NameIdx>,
417        } = 61,
418        DeleteDeref {
419            i: Arg<oparg::VarNum>,
420        } = 62,
421        DeleteFast {
422            var_num: Arg<oparg::VarNum>,
423        } = 63,
424        DeleteGlobal {
425            namei: Arg<oparg::NameIdx>,
426        } = 64,
427        DeleteName {
428            namei: Arg<oparg::NameIdx>,
429        } = 65,
430        DictMerge {
431            i: Arg<u32>,
432        } = 66,
433        DictUpdate {
434            i: Arg<u32>,
435        } = 67,
436        EndAsyncFor = 68,
437        ExtendedArg = 69,
438        ForIter {
439            delta: Arg<oparg::Label>,
440        } = 70,
441        GetAwaitable {
442            r#where: Arg<u32>,
443        } = 71,
444        ImportFrom {
445            namei: Arg<oparg::NameIdx>,
446        } = 72,
447        ImportName {
448            namei: Arg<oparg::NameIdx>,
449        } = 73,
450        IsOp {
451            invert: Arg<oparg::Invert>,
452        } = 74,
453        JumpBackward {
454            delta: Arg<oparg::Label>,
455        } = 75,
456        JumpBackwardNoInterrupt {
457            delta: Arg<oparg::Label>,
458        } = 76,
459        JumpForward {
460            delta: Arg<oparg::Label>,
461        } = 77,
462        ListAppend {
463            i: Arg<u32>,
464        } = 78,
465        ListExtend {
466            i: Arg<u32>,
467        } = 79,
468        LoadAttr {
469            namei: Arg<oparg::LoadAttr>,
470        } = 80,
471        LoadCommonConstant {
472            idx: Arg<oparg::CommonConstant>,
473        } = 81,
474        LoadConst {
475            consti: Arg<oparg::ConstIdx>,
476        } = 82,
477        LoadDeref {
478            i: Arg<oparg::VarNum>,
479        } = 83,
480        LoadFast {
481            var_num: Arg<oparg::VarNum>,
482        } = 84,
483        LoadFastAndClear {
484            var_num: Arg<oparg::VarNum>,
485        } = 85,
486        LoadFastBorrow {
487            var_num: Arg<oparg::VarNum>,
488        } = 86,
489        LoadFastBorrowLoadFastBorrow {
490            var_nums: Arg<oparg::VarNums>,
491        } = 87,
492        LoadFastCheck {
493            var_num: Arg<oparg::VarNum>,
494        } = 88,
495        LoadFastLoadFast {
496            var_nums: Arg<oparg::VarNums>,
497        } = 89,
498        LoadFromDictOrDeref {
499            i: Arg<oparg::VarNum>,
500        } = 90,
501        LoadFromDictOrGlobals {
502            i: Arg<oparg::NameIdx>,
503        } = 91,
504        LoadGlobal {
505            namei: Arg<oparg::NameIdx>,
506        } = 92,
507        LoadName {
508            namei: Arg<oparg::NameIdx>,
509        } = 93,
510        LoadSmallInt {
511            i: Arg<u32>,
512        } = 94,
513        LoadSpecial {
514            method: Arg<oparg::SpecialMethod>,
515        } = 95,
516        LoadSuperAttr {
517            namei: Arg<oparg::LoadSuperAttr>,
518        } = 96,
519        MakeCell {
520            i: Arg<oparg::VarNum>,
521        } = 97,
522        MapAdd {
523            i: Arg<u32>,
524        } = 98,
525        MatchClass {
526            count: Arg<u32>,
527        } = 99,
528        PopJumpIfFalse {
529            delta: Arg<oparg::Label>,
530        } = 100,
531        PopJumpIfNone {
532            delta: Arg<oparg::Label>,
533        } = 101,
534        PopJumpIfNotNone {
535            delta: Arg<oparg::Label>,
536        } = 102,
537        PopJumpIfTrue {
538            delta: Arg<oparg::Label>,
539        } = 103,
540        RaiseVarargs {
541            argc: Arg<oparg::RaiseKind>,
542        } = 104,
543        Reraise {
544            depth: Arg<u32>,
545        } = 105,
546        Send {
547            delta: Arg<oparg::Label>,
548        } = 106,
549        SetAdd {
550            i: Arg<u32>,
551        } = 107,
552        SetFunctionAttribute {
553            flag: Arg<oparg::MakeFunctionFlag>,
554        } = 108,
555        SetUpdate {
556            i: Arg<u32>,
557        } = 109,
558        StoreAttr {
559            namei: Arg<oparg::NameIdx>,
560        } = 110,
561        StoreDeref {
562            i: Arg<oparg::VarNum>,
563        } = 111,
564        StoreFast {
565            var_num: Arg<oparg::VarNum>,
566        } = 112,
567        StoreFastLoadFast {
568            var_nums: Arg<oparg::VarNums>,
569        } = 113,
570        StoreFastStoreFast {
571            var_nums: Arg<oparg::VarNums>,
572        } = 114,
573        StoreGlobal {
574            namei: Arg<oparg::NameIdx>,
575        } = 115,
576        StoreName {
577            namei: Arg<oparg::NameIdx>,
578        } = 116,
579        Swap {
580            i: Arg<u32>,
581        } = 117,
582        UnpackEx {
583            counts: Arg<oparg::UnpackExArgs>,
584        } = 118,
585        UnpackSequence {
586            count: Arg<u32>,
587        } = 119,
588        YieldValue {
589            arg: Arg<u32>,
590        } = 120,
591        Resume {
592            context: Arg<oparg::ResumeContext>,
593        } = 128,
594        BinaryOpAddFloat = 129,
595        BinaryOpAddInt = 130,
596        BinaryOpAddUnicode = 131,
597        BinaryOpExtend = 132,
598        BinaryOpMultiplyFloat = 133,
599        BinaryOpMultiplyInt = 134,
600        BinaryOpSubscrDict = 135,
601        BinaryOpSubscrGetitem = 136,
602        BinaryOpSubscrListInt = 137,
603        BinaryOpSubscrListSlice = 138,
604        BinaryOpSubscrStrInt = 139,
605        BinaryOpSubscrTupleInt = 140,
606        BinaryOpSubtractFloat = 141,
607        BinaryOpSubtractInt = 142,
608        CallAllocAndEnterInit = 143,
609        CallBoundMethodExactArgs = 144,
610        CallBoundMethodGeneral = 145,
611        CallBuiltinClass = 146,
612        CallBuiltinFast = 147,
613        CallBuiltinFastWithKeywords = 148,
614        CallBuiltinO = 149,
615        CallIsinstance = 150,
616        CallKwBoundMethod = 151,
617        CallKwNonPy = 152,
618        CallKwPy = 153,
619        CallLen = 154,
620        CallListAppend = 155,
621        CallMethodDescriptorFast = 156,
622        CallMethodDescriptorFastWithKeywords = 157,
623        CallMethodDescriptorNoargs = 158,
624        CallMethodDescriptorO = 159,
625        CallNonPyGeneral = 160,
626        CallPyExactArgs = 161,
627        CallPyGeneral = 162,
628        CallStr1 = 163,
629        CallTuple1 = 164,
630        CallType1 = 165,
631        CompareOpFloat = 166,
632        CompareOpInt = 167,
633        CompareOpStr = 168,
634        ContainsOpDict = 169,
635        ContainsOpSet = 170,
636        ForIterGen = 171,
637        ForIterList = 172,
638        ForIterRange = 173,
639        ForIterTuple = 174,
640        JumpBackwardJit = 175,
641        JumpBackwardNoJit = 176,
642        LoadAttrClass = 177,
643        LoadAttrClassWithMetaclassCheck = 178,
644        LoadAttrGetattributeOverridden = 179,
645        LoadAttrInstanceValue = 180,
646        LoadAttrMethodLazyDict = 181,
647        LoadAttrMethodNoDict = 182,
648        LoadAttrMethodWithValues = 183,
649        LoadAttrModule = 184,
650        LoadAttrNondescriptorNoDict = 185,
651        LoadAttrNondescriptorWithValues = 186,
652        LoadAttrProperty = 187,
653        LoadAttrSlot = 188,
654        LoadAttrWithHint = 189,
655        LoadConstImmortal = 190,
656        LoadConstMortal = 191,
657        LoadGlobalBuiltin = 192,
658        LoadGlobalModule = 193,
659        LoadSuperAttrAttr = 194,
660        LoadSuperAttrMethod = 195,
661        ResumeCheck = 196,
662        SendGen = 197,
663        StoreAttrInstanceValue = 198,
664        StoreAttrSlot = 199,
665        StoreAttrWithHint = 200,
666        StoreSubscrDict = 201,
667        StoreSubscrListInt = 202,
668        ToBoolAlwaysTrue = 203,
669        ToBoolBool = 204,
670        ToBoolInt = 205,
671        ToBoolList = 206,
672        ToBoolNone = 207,
673        ToBoolStr = 208,
674        UnpackSequenceList = 209,
675        UnpackSequenceTuple = 210,
676        UnpackSequenceTwoTuple = 211,
677        InstrumentedEndFor = 234,
678        InstrumentedPopIter = 235,
679        InstrumentedEndSend = 236,
680        InstrumentedForIter = 237,
681        InstrumentedInstruction = 238,
682        InstrumentedJumpForward = 239,
683        InstrumentedNotTaken = 240,
684        InstrumentedPopJumpIfTrue = 241,
685        InstrumentedPopJumpIfFalse = 242,
686        InstrumentedPopJumpIfNone = 243,
687        InstrumentedPopJumpIfNotNone = 244,
688        InstrumentedResume = 245,
689        InstrumentedReturnValue = 246,
690        InstrumentedYieldValue = 247,
691        InstrumentedEndAsyncFor = 248,
692        InstrumentedLoadSuperAttr = 249,
693        InstrumentedCall = 250,
694        InstrumentedCallKw = 251,
695        InstrumentedCallFunctionEx = 252,
696        InstrumentedJumpBackward = 253,
697        InstrumentedLine = 254,
698        EnterExecutor = 255,
699    }
700);
701
702define_opcodes!(
703    #[repr(u16)]
704    pub enum PseudoOpcode;
705
706    pub enum PseudoInstruction {
707        AnnotationsPlaceholder = 256,
708        Jump { delta: Arg<oparg::Label> } = 257,
709        JumpIfFalse { delta: Arg<oparg::Label> } = 258,
710        JumpIfTrue { delta: Arg<oparg::Label> } = 259,
711        JumpNoInterrupt { delta: Arg<oparg::Label> } = 260,
712        LoadClosure { i: Arg<oparg::NameIdx> } = 261,
713        PopBlock = 262,
714        SetupCleanup { delta: Arg<oparg::Label> } = 263,
715        SetupFinally { delta: Arg<oparg::Label> } = 264,
716        SetupWith { delta: Arg<oparg::Label> } = 265,
717        StoreFastMaybeNull { var_num: Arg<oparg::NameIdx> } = 266,
718    }
719);
720
721impl Opcode {
722    #[must_use]
723    pub const fn is_unconditional_jump(&self) -> bool {
724        matches!(
725            self,
726            Self::JumpForward | Self::JumpBackward | Self::JumpBackwardNoInterrupt
727        )
728    }
729
730    /// CPython's `IS_ASSEMBLER_OPCODE`.
731    #[must_use]
732    pub const fn is_assembler(&self) -> bool {
733        matches!(
734            self,
735            Self::JumpForward | Self::JumpBackward | Self::JumpBackwardNoInterrupt
736        )
737    }
738
739    #[must_use]
740    pub const fn is_scope_exit(&self) -> bool {
741        matches!(self, Self::ReturnValue | Self::RaiseVarargs | Self::Reraise)
742    }
743
744    /// CPython's `IS_TERMINATOR_OPCODE`.
745    #[must_use]
746    pub const fn is_terminator(&self) -> bool {
747        self.has_jump() || self.is_scope_exit()
748    }
749
750    /// CPython's `IS_SCOPE_EXIT_OPCODE || IS_UNCONDITIONAL_JUMP_OPCODE`.
751    #[must_use]
752    pub const fn is_no_fallthrough(&self) -> bool {
753        self.is_scope_exit() || self.is_unconditional_jump()
754    }
755
756    /// CPython's `HAS_TARGET`.
757    #[must_use]
758    pub const fn has_target(&self) -> bool {
759        self.has_jump() || self.is_block_push()
760    }
761
762    #[must_use]
763    pub const fn is_block_push(&self) -> bool {
764        false
765    }
766
767    /// Stack effect of [`Self::stack_effect_info`].
768    #[must_use]
769    pub fn stack_effect(&self, oparg: u32) -> i32 {
770        self.stack_effect_info(oparg).effect()
771    }
772
773    /// Stack effect when the instruction takes its branch (jump=true).
774    ///
775    /// CPython equivalent: `stack_effect(opcode, oparg, jump=True)`.
776    /// Current opcode metadata has the same real-opcode stack effect
777    /// for jump and fallthrough stack-depth calculation.
778    #[must_use]
779    pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
780        self.stack_effect(oparg)
781    }
782}
783
784impl PseudoOpcode {
785    #[must_use]
786    pub const fn is_block_push(&self) -> bool {
787        matches!(
788            self,
789            Self::SetupCleanup | Self::SetupFinally | Self::SetupWith
790        )
791    }
792
793    #[must_use]
794    pub const fn is_scope_exit(&self) -> bool {
795        false
796    }
797
798    #[must_use]
799    pub const fn is_unconditional_jump(&self) -> bool {
800        matches!(self, Self::Jump | Self::JumpNoInterrupt)
801    }
802
803    #[must_use]
804    pub const fn is_assembler(&self) -> bool {
805        false
806    }
807
808    /// CPython's `IS_TERMINATOR_OPCODE`.
809    #[must_use]
810    pub const fn is_terminator(&self) -> bool {
811        self.has_jump()
812    }
813
814    /// CPython's `IS_SCOPE_EXIT_OPCODE || IS_UNCONDITIONAL_JUMP_OPCODE`.
815    #[must_use]
816    pub const fn is_no_fallthrough(&self) -> bool {
817        self.is_unconditional_jump()
818    }
819
820    /// CPython's `HAS_TARGET`.
821    #[must_use]
822    pub const fn has_target(&self) -> bool {
823        self.has_jump() || self.is_block_push()
824    }
825
826    /// flowgraph.c get_stack_effects block-push non-jump case.
827    #[must_use]
828    pub fn stack_effect(&self, oparg: u32) -> i32 {
829        if self.is_block_push() {
830            0
831        } else {
832            self.stack_effect_info(oparg).effect()
833        }
834    }
835
836    /// Handler entry effect for SETUP_* pseudo ops.
837    ///
838    /// Fallthrough effect is 0 (NOPs), but when the branch is taken the
839    /// handler block starts with extra values on the stack:
840    ///   SETUP_FINALLY:  +1  (exc)
841    ///   SETUP_CLEANUP:  +2  (lasti + exc)
842    ///   SETUP_WITH:     +1  (pops __enter__ result, pushes lasti + exc)
843    #[must_use]
844    pub fn stack_effect_jump(&self, oparg: u32) -> i32 {
845        match self {
846            Self::SetupFinally | Self::SetupWith => 1,
847            Self::SetupCleanup => 2,
848            _ => self.stack_effect(oparg),
849        }
850    }
851}
852
853macro_rules! either_real_pseudo {
854    // Const
855    (
856        $(#[$meta:meta])*
857        $vis:vis const fn $name:ident(&self $(, $arg:ident : $arg_ty:ty)*) -> $ret:ty
858    ) => {
859        $(#[$meta])*
860        $vis const fn $name(&self $(, $arg: $arg_ty)*) -> $ret {
861            match self {
862                Self::Real(v) => v.$name($($arg),*),
863                Self::Pseudo(v) => v.$name($($arg),*),
864            }
865        }
866    };
867
868    // Not const
869    (
870        $(#[$meta:meta])*
871        $vis:vis fn $name:ident(&self $(, $arg:ident : $arg_ty:ty)*) -> $ret:ty
872    ) => {
873        $(#[$meta])*
874        $vis fn $name(&self $(, $arg: $arg_ty)*) -> $ret {
875            match self {
876                Self::Real(v) => v.$name($($arg),*),
877                Self::Pseudo(v) => v.$name($($arg),*),
878            }
879        }
880    };
881}
882
883#[derive(Clone, Copy, Debug)]
884pub enum AnyInstruction {
885    Real(Instruction),
886    Pseudo(PseudoInstruction),
887}
888
889impl AnyInstruction {
890    either_real_pseudo!(
891        #[must_use]
892        pub const fn is_unconditional_jump(&self) -> bool
893    );
894
895    either_real_pseudo!(
896        #[must_use]
897        pub const fn is_scope_exit(&self) -> bool
898    );
899
900    either_real_pseudo!(
901        #[must_use]
902        pub const fn is_terminator(&self) -> bool
903    );
904
905    either_real_pseudo!(
906        #[must_use]
907        pub const fn is_no_fallthrough(&self) -> bool
908    );
909
910    either_real_pseudo!(
911        #[must_use]
912        pub const fn has_target(&self) -> bool
913    );
914
915    either_real_pseudo!(
916        #[must_use]
917        pub const fn has_jump(&self) -> bool
918    );
919
920    either_real_pseudo!(
921        #[must_use]
922        pub const fn has_arg(&self) -> bool
923    );
924
925    either_real_pseudo!(
926        #[must_use]
927        pub const fn has_const(&self) -> bool
928    );
929
930    either_real_pseudo!(
931        #[must_use]
932        pub const fn has_eval_break(&self) -> bool
933    );
934
935    either_real_pseudo!(
936        #[must_use]
937        pub const fn is_assembler(&self) -> bool
938    );
939
940    either_real_pseudo!(
941        #[must_use]
942        pub fn stack_effect(&self, oparg: u32) -> i32
943    );
944
945    either_real_pseudo!(
946        #[must_use]
947        pub fn stack_effect_jump(&self, oparg: u32) -> i32
948    );
949
950    either_real_pseudo!(
951        #[must_use]
952        pub fn stack_effect_info(&self, oparg: u32) -> StackEffect
953    );
954}
955
956impl From<Instruction> for AnyInstruction {
957    fn from(value: Instruction) -> Self {
958        Self::Real(value)
959    }
960}
961
962impl From<PseudoInstruction> for AnyInstruction {
963    fn from(value: PseudoInstruction) -> Self {
964        Self::Pseudo(value)
965    }
966}
967
968impl TryFrom<u8> for AnyInstruction {
969    type Error = MarshalError;
970
971    fn try_from(value: u8) -> Result<Self, Self::Error> {
972        Ok(Instruction::try_from(value)?.into())
973    }
974}
975
976impl TryFrom<u16> for AnyInstruction {
977    type Error = MarshalError;
978
979    fn try_from(value: u16) -> Result<Self, Self::Error> {
980        match u8::try_from(value) {
981            Ok(v) => v.try_into(),
982            Err(_) => Ok(PseudoInstruction::try_from(value)?.into()),
983        }
984    }
985}
986
987impl From<Opcode> for AnyInstruction {
988    fn from(value: Opcode) -> Self {
989        Self::Real(value.into())
990    }
991}
992
993impl From<PseudoOpcode> for AnyInstruction {
994    fn from(value: PseudoOpcode) -> Self {
995        Self::Pseudo(value.into())
996    }
997}
998
999impl From<AnyOpcode> for AnyInstruction {
1000    fn from(value: AnyOpcode) -> Self {
1001        match value {
1002            AnyOpcode::Real(op) => op.into(),
1003            AnyOpcode::Pseudo(op) => op.into(),
1004        }
1005    }
1006}
1007
1008impl AnyInstruction {
1009    /// Inner value of [`Self::Real`].
1010    #[must_use]
1011    pub const fn real(self) -> Option<Instruction> {
1012        match self {
1013            Self::Real(ins) => Some(ins),
1014            _ => None,
1015        }
1016    }
1017
1018    /// Inner value of [`Self::Pseudo`].
1019    #[must_use]
1020    pub const fn pseudo(self) -> Option<PseudoInstruction> {
1021        match self {
1022            Self::Pseudo(ins) => Some(ins),
1023            _ => None,
1024        }
1025    }
1026
1027    /// Get [`Self::Real`] as [`Opcode`].
1028    #[must_use]
1029    pub const fn real_opcode(self) -> Option<Opcode> {
1030        match self.real() {
1031            Some(ins) => Some(ins.as_opcode()),
1032            _ => None,
1033        }
1034    }
1035
1036    /// Get [`Self::Pseudo`] as [`PseudoOpcode`].
1037    #[must_use]
1038    pub const fn pseudo_opcode(self) -> Option<PseudoOpcode> {
1039        match self.pseudo() {
1040            Some(ins) => Some(ins.as_opcode()),
1041            _ => None,
1042        }
1043    }
1044
1045    /// Same as [`Self::real`] but panics if wasn't called on [`Self::Real`].
1046    ///
1047    /// # Panics
1048    ///
1049    /// If was called on something else other than [`Self::Real`].
1050    #[must_use]
1051    pub const fn expect_real(self) -> Instruction {
1052        self.real()
1053            .expect("Expected AnyInstruction::Real, found AnyInstruction::Pseudo")
1054    }
1055
1056    /// Same as [`Self::pseudo`] but panics if wasn't called on [`Self::Pseudo`].
1057    ///
1058    /// # Panics
1059    ///
1060    /// If was called on something else other than [`Self::Pseudo`].
1061    #[must_use]
1062    pub const fn expect_pseudo(self) -> PseudoInstruction {
1063        self.pseudo()
1064            .expect("Expected AnyInstruction::Pseudo, found AnyInstruction::Real")
1065    }
1066
1067    /// Returns true if this is a [`PseudoInstruction::PopBlock`].
1068    #[must_use]
1069    pub const fn is_pop_block(self) -> bool {
1070        matches!(self, Self::Pseudo(PseudoInstruction::PopBlock))
1071    }
1072
1073    /// See [`PseudoInstruction::is_block_push`].
1074    #[must_use]
1075    pub const fn is_block_push(self) -> bool {
1076        matches!(self, Self::Pseudo(p) if p.is_block_push())
1077    }
1078}
1079
1080#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1081pub enum AnyOpcode {
1082    Real(Opcode),
1083    Pseudo(PseudoOpcode),
1084}
1085
1086impl From<Opcode> for AnyOpcode {
1087    fn from(value: Opcode) -> Self {
1088        Self::Real(value)
1089    }
1090}
1091
1092impl From<PseudoOpcode> for AnyOpcode {
1093    fn from(value: PseudoOpcode) -> Self {
1094        Self::Pseudo(value)
1095    }
1096}
1097
1098impl TryFrom<u8> for AnyOpcode {
1099    type Error = MarshalError;
1100
1101    fn try_from(value: u8) -> Result<Self, Self::Error> {
1102        Ok(Opcode::try_from(value)?.into())
1103    }
1104}
1105
1106impl TryFrom<u16> for AnyOpcode {
1107    type Error = MarshalError;
1108
1109    fn try_from(value: u16) -> Result<Self, Self::Error> {
1110        match u8::try_from(value) {
1111            Ok(v) => v.try_into(),
1112            Err(_) => Ok(PseudoOpcode::try_from(value)?.into()),
1113        }
1114    }
1115}
1116
1117impl From<AnyInstruction> for AnyOpcode {
1118    fn from(value: AnyInstruction) -> Self {
1119        match value {
1120            AnyInstruction::Real(instr) => Self::Real(instr.into()),
1121            AnyInstruction::Pseudo(instr) => Self::Pseudo(instr.into()),
1122        }
1123    }
1124}
1125
1126impl AnyOpcode {
1127    /// Gets the inner value of [`Self::Real`].
1128    #[must_use]
1129    pub const fn real(self) -> Option<Opcode> {
1130        match self {
1131            Self::Real(op) => Some(op),
1132            _ => None,
1133        }
1134    }
1135
1136    /// Gets the inner value of [`Self::Pseudo`].
1137    #[must_use]
1138    pub const fn pseudo(self) -> Option<PseudoOpcode> {
1139        match self {
1140            Self::Pseudo(op) => Some(op),
1141            _ => None,
1142        }
1143    }
1144
1145    /// Same as [`Self::real`] but panics if wasn't called on [`Self::Real`].
1146    ///
1147    /// # Panics
1148    ///
1149    /// If was called on something else other than [`Self::Real`].
1150    #[must_use]
1151    pub const fn expect_real(self) -> Opcode {
1152        self.real()
1153            .expect("Expected AnyOpcode::Real, found AnyOpcode::Pseudo")
1154    }
1155
1156    /// Same as [`Self::pseudo`] but panics if wasn't called on [`Self::Pseudo`].
1157    ///
1158    /// # Panics
1159    ///
1160    /// If was called on something else other than [`Self::Pseudo`].
1161    #[must_use]
1162    pub const fn expect_pseudo(self) -> PseudoOpcode {
1163        self.pseudo()
1164            .expect("Expected AnyOpcode::Pseudo, found AnyOpcode::Real")
1165    }
1166
1167    either_real_pseudo!(
1168        #[must_use]
1169        pub const fn has_arg(&self) -> bool
1170    );
1171
1172    either_real_pseudo!(
1173        #[must_use]
1174        pub const fn has_jump(&self) -> bool
1175    );
1176
1177    either_real_pseudo!(
1178        #[must_use]
1179        pub const fn has_free(&self) -> bool
1180    );
1181
1182    either_real_pseudo!(
1183        #[must_use]
1184        pub const fn has_local(&self) -> bool
1185    );
1186
1187    either_real_pseudo!(
1188        #[must_use]
1189        pub const fn has_name(&self) -> bool
1190    );
1191
1192    either_real_pseudo!(
1193        #[must_use]
1194        pub const fn has_const(&self) -> bool
1195    );
1196
1197    either_real_pseudo!(
1198        #[must_use]
1199        pub const fn is_instrumented(&self) -> bool
1200    );
1201
1202    either_real_pseudo!(
1203        #[must_use]
1204        pub const fn is_block_push(&self) -> bool
1205    );
1206
1207    either_real_pseudo!(
1208        #[must_use]
1209        pub fn stack_effect_jump(&self, oparg: u32) -> i32
1210    );
1211
1212    either_real_pseudo!(
1213        #[must_use]
1214        pub fn stack_effect(&self, oparg: u32) -> i32
1215    );
1216
1217    #[must_use]
1218    pub const fn deopt(&self) -> Option<Self> {
1219        match self {
1220            Self::Real(opcode) => {
1221                if let Some(op) = opcode.deopt() {
1222                    Some(Self::Real(op))
1223                } else {
1224                    None
1225                }
1226            }
1227            Self::Pseudo(opcode) => {
1228                if let Some(op) = opcode.deopt() {
1229                    Some(Self::Pseudo(op))
1230                } else {
1231                    None
1232                }
1233            }
1234        }
1235    }
1236}
1237
1238/// What effect the instruction has on the stack.
1239#[derive(Clone, Copy)]
1240pub struct StackEffect {
1241    /// How many items the instruction is pushing on the stack.
1242    pushed: u32,
1243    /// How many items the instruction is popping from the stack.
1244    popped: u32,
1245}
1246
1247impl StackEffect {
1248    /// Creates a new [`Self`].
1249    #[must_use]
1250    pub const fn new(pushed: u32, popped: u32) -> Self {
1251        Self { pushed, popped }
1252    }
1253
1254    /// Get the calculated stack effect as [`i32`].
1255    #[must_use]
1256    pub fn effect(self) -> i32 {
1257        self.into()
1258    }
1259
1260    /// Get the pushed count.
1261    #[must_use]
1262    pub const fn pushed(self) -> u32 {
1263        self.pushed
1264    }
1265
1266    /// Get the popped count.
1267    #[must_use]
1268    pub const fn popped(self) -> u32 {
1269        self.popped
1270    }
1271}
1272
1273impl From<StackEffect> for i32 {
1274    fn from(effect: StackEffect) -> Self {
1275        (effect.pushed() as Self) - (effect.popped() as Self)
1276    }
1277}
1278
1279#[derive(Copy, Clone)]
1280pub struct Arg<T: OpArgType>(PhantomData<T>);
1281
1282impl<T: OpArgType> Arg<T> {
1283    #[inline]
1284    #[must_use]
1285    pub const fn marker() -> Self {
1286        Self(PhantomData)
1287    }
1288
1289    #[inline]
1290    pub fn new(arg: T) -> (Self, OpArg) {
1291        (Self(PhantomData), OpArg::new(arg.into()))
1292    }
1293
1294    #[inline]
1295    pub fn new_single(arg: T) -> (Self, OpArgByte)
1296    where
1297        T: Into<u8>,
1298    {
1299        (Self(PhantomData), OpArgByte::new(arg.into()))
1300    }
1301
1302    #[inline(always)]
1303    #[must_use]
1304    pub fn get(self, arg: OpArg) -> T {
1305        self.try_get(arg).unwrap()
1306    }
1307
1308    #[inline(always)]
1309    pub fn try_get(self, arg: OpArg) -> Result<T, MarshalError> {
1310        T::try_from(u32::from(arg)).map_err(|_| MarshalError::InvalidBytecode)
1311    }
1312
1313    /// # Safety
1314    /// T::from_op_arg(self) must succeed
1315    #[inline(always)]
1316    #[must_use]
1317    pub unsafe fn get_unchecked(self, arg: OpArg) -> T {
1318        // SAFETY: requirements forwarded from caller
1319        unsafe { T::try_from(u32::from(arg)).unwrap_unchecked() }
1320    }
1321}
1322
1323impl<T: OpArgType> PartialEq for Arg<T> {
1324    fn eq(&self, _: &Self) -> bool {
1325        true
1326    }
1327}
1328
1329impl<T: OpArgType> Eq for Arg<T> {}
1330
1331impl<T: OpArgType> fmt::Debug for Arg<T> {
1332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1333        write!(f, "Arg<{}>", core::any::type_name::<T>())
1334    }
1335}
1336
1337// TODO: Can probably remove these asserts and remove the `repr($typ)` from the macro. but this
1338// breaks the VM:/
1339const _: () = assert!(core::mem::size_of::<Instruction>() == 1);
1340const _: () = assert!(core::mem::size_of::<PseudoInstruction>() == 2);
1341
1342#[cfg(test)]
1343mod tests {
1344    use super::*;
1345
1346    #[test]
1347    fn eval_break_flags_match_cpython_jump_metadata() {
1348        assert!(Opcode::JumpBackward.has_eval_break());
1349        assert!(!Opcode::JumpBackwardNoInterrupt.has_eval_break());
1350        assert!(!Opcode::JumpForward.has_eval_break());
1351
1352        assert!(PseudoOpcode::Jump.has_eval_break());
1353        assert!(!PseudoOpcode::JumpIfFalse.has_eval_break());
1354        assert!(!PseudoOpcode::JumpIfTrue.has_eval_break());
1355        assert!(!PseudoOpcode::JumpNoInterrupt.has_eval_break());
1356
1357        assert!(AnyInstruction::from(PseudoOpcode::Jump).has_eval_break());
1358    }
1359
1360    #[test]
1361    fn terminator_flags_match_cpython_opcode_utils() {
1362        assert!(Opcode::JumpForward.is_terminator());
1363        assert!(Opcode::PopJumpIfFalse.is_terminator());
1364        assert!(Opcode::ForIter.is_terminator());
1365        assert!(Opcode::ReturnValue.is_terminator());
1366        assert!(!Opcode::Nop.is_terminator());
1367
1368        assert!(PseudoOpcode::JumpIfTrue.is_terminator());
1369        assert!(PseudoOpcode::JumpNoInterrupt.is_terminator());
1370        assert!(!PseudoOpcode::SetupFinally.is_terminator());
1371        assert!(!PseudoOpcode::SetupWith.is_terminator());
1372        assert!(!PseudoOpcode::SetupCleanup.is_terminator());
1373        assert!(!PseudoOpcode::PopBlock.is_terminator());
1374
1375        assert!(AnyInstruction::from(PseudoOpcode::JumpIfFalse).is_terminator());
1376    }
1377
1378    #[test]
1379    fn assembler_flags_match_cpython_opcode_utils() {
1380        assert!(Opcode::JumpForward.is_assembler());
1381        assert!(Opcode::JumpBackward.is_assembler());
1382        assert!(Opcode::JumpBackwardNoInterrupt.is_assembler());
1383        assert!(!Opcode::PopJumpIfFalse.is_assembler());
1384        assert!(!Opcode::Nop.is_assembler());
1385
1386        assert!(!PseudoOpcode::Jump.is_assembler());
1387        assert!(!PseudoOpcode::JumpNoInterrupt.is_assembler());
1388        assert!(!AnyInstruction::from(PseudoOpcode::Jump).is_assembler());
1389    }
1390
1391    #[test]
1392    fn target_flags_match_cpython_opcode_utils() {
1393        assert!(Opcode::JumpForward.has_target());
1394        assert!(Opcode::ForIter.has_target());
1395        assert!(!Opcode::ReturnValue.has_target());
1396        assert!(!Opcode::Nop.has_target());
1397
1398        assert!(PseudoOpcode::Jump.has_target());
1399        assert!(PseudoOpcode::SetupFinally.has_target());
1400        assert!(PseudoOpcode::SetupWith.has_target());
1401        assert!(PseudoOpcode::SetupCleanup.has_target());
1402        assert!(!PseudoOpcode::PopBlock.has_target());
1403
1404        assert!(AnyInstruction::from(PseudoOpcode::SetupFinally).has_target());
1405    }
1406
1407    #[test]
1408    fn arg_flags_match_cpython_opcode_metadata() {
1409        assert!(Opcode::LoadConst.has_arg());
1410        assert!(Opcode::YieldValue.has_arg());
1411        assert!(!Opcode::Nop.has_arg());
1412        assert!(!Opcode::ReturnValue.has_arg());
1413
1414        assert!(PseudoOpcode::Jump.has_arg());
1415        assert!(PseudoOpcode::JumpIfFalse.has_arg());
1416        assert!(PseudoOpcode::JumpIfTrue.has_arg());
1417        assert!(PseudoOpcode::JumpNoInterrupt.has_arg());
1418        assert!(PseudoOpcode::LoadClosure.has_arg());
1419        assert!(PseudoOpcode::SetupCleanup.has_arg());
1420        assert!(PseudoOpcode::SetupFinally.has_arg());
1421        assert!(PseudoOpcode::SetupWith.has_arg());
1422        assert!(PseudoOpcode::StoreFastMaybeNull.has_arg());
1423        assert!(!PseudoOpcode::AnnotationsPlaceholder.has_arg());
1424        assert!(!PseudoOpcode::PopBlock.has_arg());
1425    }
1426
1427    #[test]
1428    fn const_flags_match_cpython_opcode_metadata() {
1429        assert!(Opcode::LoadConst.has_const());
1430        assert!(Opcode::LoadConstImmortal.has_const());
1431        assert!(Opcode::LoadConstMortal.has_const());
1432        assert!(!Opcode::LoadSmallInt.has_const());
1433        assert!(!Opcode::Nop.has_const());
1434
1435        assert!(!PseudoOpcode::LoadClosure.has_const());
1436        assert!(!AnyInstruction::from(PseudoOpcode::Jump).has_const());
1437    }
1438
1439    #[test]
1440    fn stack_effects_match_cpython_opcode_metadata() {
1441        assert_eq!(Opcode::ForIter.stack_effect_info(0).popped(), 1);
1442        assert_eq!(Opcode::ForIter.stack_effect_info(0).pushed(), 2);
1443        assert_eq!(Opcode::ForIter.stack_effect(0), 1);
1444        assert_eq!(Opcode::ForIter.stack_effect_jump(0), 1);
1445
1446        assert_eq!(Opcode::EndAsyncFor.stack_effect_info(0).popped(), 2);
1447        assert_eq!(Opcode::EndAsyncFor.stack_effect_info(0).pushed(), 0);
1448        assert_eq!(Opcode::PopJumpIfFalse.stack_effect(0), -1);
1449        assert_eq!(Opcode::PopJumpIfFalse.stack_effect_jump(0), -1);
1450
1451        assert_eq!(PseudoOpcode::SetupFinally.stack_effect_info(0).pushed(), 1);
1452        assert_eq!(PseudoOpcode::SetupFinally.stack_effect(0), 0);
1453        assert_eq!(PseudoOpcode::SetupFinally.stack_effect_jump(0), 1);
1454        assert_eq!(PseudoOpcode::SetupCleanup.stack_effect_info(0).pushed(), 2);
1455        assert_eq!(PseudoOpcode::SetupCleanup.stack_effect(0), 0);
1456        assert_eq!(PseudoOpcode::SetupCleanup.stack_effect_jump(0), 2);
1457    }
1458
1459    #[test]
1460    fn no_fallthrough_flags_match_cpython_basicblock_nofallthrough() {
1461        assert!(Opcode::JumpForward.is_no_fallthrough());
1462        assert!(Opcode::ReturnValue.is_no_fallthrough());
1463        assert!(!Opcode::PopJumpIfFalse.is_no_fallthrough());
1464        assert!(!Opcode::ForIter.is_no_fallthrough());
1465        assert!(!Opcode::Nop.is_no_fallthrough());
1466
1467        assert!(PseudoOpcode::Jump.is_no_fallthrough());
1468        assert!(PseudoOpcode::JumpNoInterrupt.is_no_fallthrough());
1469        assert!(!PseudoOpcode::JumpIfFalse.is_no_fallthrough());
1470        assert!(!PseudoOpcode::SetupFinally.is_no_fallthrough());
1471        assert!(!PseudoOpcode::SetupWith.is_no_fallthrough());
1472
1473        assert!(AnyInstruction::from(PseudoOpcode::Jump).is_no_fallthrough());
1474    }
1475
1476    /// Snapshot of the chained `match` implementations that `Opcode::deopt`
1477    /// and `Opcode::cache_entries` used before they were rewritten as table
1478    /// lookups. Exists only to pin the observable behavior of the table
1479    /// lookups against the logic they replaced.
1480    mod reference {
1481        use super::Opcode;
1482
1483        pub(super) const fn deopt(op: Opcode) -> Option<Opcode> {
1484            Some(match op {
1485                Opcode::ResumeCheck => Opcode::Resume,
1486                Opcode::LoadConstMortal | Opcode::LoadConstImmortal => Opcode::LoadConst,
1487                Opcode::ToBoolAlwaysTrue
1488                | Opcode::ToBoolBool
1489                | Opcode::ToBoolInt
1490                | Opcode::ToBoolList
1491                | Opcode::ToBoolNone
1492                | Opcode::ToBoolStr => Opcode::ToBool,
1493                Opcode::BinaryOpMultiplyInt
1494                | Opcode::BinaryOpAddInt
1495                | Opcode::BinaryOpSubtractInt
1496                | Opcode::BinaryOpMultiplyFloat
1497                | Opcode::BinaryOpAddFloat
1498                | Opcode::BinaryOpSubtractFloat
1499                | Opcode::BinaryOpAddUnicode
1500                | Opcode::BinaryOpSubscrListInt
1501                | Opcode::BinaryOpSubscrListSlice
1502                | Opcode::BinaryOpSubscrTupleInt
1503                | Opcode::BinaryOpSubscrStrInt
1504                | Opcode::BinaryOpSubscrDict
1505                | Opcode::BinaryOpSubscrGetitem
1506                | Opcode::BinaryOpExtend
1507                | Opcode::BinaryOpInplaceAddUnicode => Opcode::BinaryOp,
1508                Opcode::StoreSubscrDict | Opcode::StoreSubscrListInt => Opcode::StoreSubscr,
1509                Opcode::SendGen => Opcode::Send,
1510                Opcode::UnpackSequenceTwoTuple
1511                | Opcode::UnpackSequenceTuple
1512                | Opcode::UnpackSequenceList => Opcode::UnpackSequence,
1513                Opcode::StoreAttrInstanceValue
1514                | Opcode::StoreAttrSlot
1515                | Opcode::StoreAttrWithHint => Opcode::StoreAttr,
1516                Opcode::LoadGlobalModule | Opcode::LoadGlobalBuiltin => Opcode::LoadGlobal,
1517                Opcode::LoadSuperAttrAttr | Opcode::LoadSuperAttrMethod => Opcode::LoadSuperAttr,
1518                Opcode::LoadAttrInstanceValue
1519                | Opcode::LoadAttrModule
1520                | Opcode::LoadAttrWithHint
1521                | Opcode::LoadAttrSlot
1522                | Opcode::LoadAttrClass
1523                | Opcode::LoadAttrClassWithMetaclassCheck
1524                | Opcode::LoadAttrProperty
1525                | Opcode::LoadAttrGetattributeOverridden
1526                | Opcode::LoadAttrMethodWithValues
1527                | Opcode::LoadAttrMethodNoDict
1528                | Opcode::LoadAttrMethodLazyDict
1529                | Opcode::LoadAttrNondescriptorWithValues
1530                | Opcode::LoadAttrNondescriptorNoDict => Opcode::LoadAttr,
1531                Opcode::CompareOpFloat | Opcode::CompareOpInt | Opcode::CompareOpStr => {
1532                    Opcode::CompareOp
1533                }
1534                Opcode::ContainsOpSet | Opcode::ContainsOpDict => Opcode::ContainsOp,
1535                Opcode::JumpBackwardNoJit | Opcode::JumpBackwardJit => Opcode::JumpBackward,
1536                Opcode::ForIterList
1537                | Opcode::ForIterTuple
1538                | Opcode::ForIterRange
1539                | Opcode::ForIterGen => Opcode::ForIter,
1540                Opcode::CallBoundMethodExactArgs
1541                | Opcode::CallPyExactArgs
1542                | Opcode::CallType1
1543                | Opcode::CallStr1
1544                | Opcode::CallTuple1
1545                | Opcode::CallBuiltinClass
1546                | Opcode::CallBuiltinO
1547                | Opcode::CallBuiltinFast
1548                | Opcode::CallBuiltinFastWithKeywords
1549                | Opcode::CallLen
1550                | Opcode::CallIsinstance
1551                | Opcode::CallListAppend
1552                | Opcode::CallMethodDescriptorO
1553                | Opcode::CallMethodDescriptorFastWithKeywords
1554                | Opcode::CallMethodDescriptorNoargs
1555                | Opcode::CallMethodDescriptorFast
1556                | Opcode::CallAllocAndEnterInit
1557                | Opcode::CallPyGeneral
1558                | Opcode::CallBoundMethodGeneral
1559                | Opcode::CallNonPyGeneral => Opcode::Call,
1560                Opcode::CallKwBoundMethod | Opcode::CallKwPy | Opcode::CallKwNonPy => {
1561                    Opcode::CallKw
1562                }
1563                _ => return None,
1564            })
1565        }
1566
1567        pub(super) const fn deoptimize(op: Opcode) -> Opcode {
1568            match deopt(op) {
1569                Some(v) => v,
1570                None => match op.to_base() {
1571                    Some(v) => v,
1572                    None => op,
1573                },
1574            }
1575        }
1576
1577        pub(super) const fn cache_entries(op: Opcode) -> usize {
1578            match deoptimize(op) {
1579                Opcode::StoreSubscr => 1,
1580                Opcode::ToBool => 3,
1581                Opcode::BinaryOp => 5,
1582                Opcode::Call => 3,
1583                Opcode::CallKw => 3,
1584                Opcode::CompareOp => 1,
1585                Opcode::ContainsOp => 1,
1586                Opcode::ForIter => 1,
1587                Opcode::JumpBackward => 1,
1588                Opcode::LoadAttr => 9,
1589                Opcode::LoadGlobal => 4,
1590                Opcode::LoadSuperAttr => 1,
1591                Opcode::PopJumpIfFalse => 1,
1592                Opcode::PopJumpIfNone => 1,
1593                Opcode::PopJumpIfNotNone => 1,
1594                Opcode::PopJumpIfTrue => 1,
1595                Opcode::Send => 1,
1596                Opcode::StoreAttr => 4,
1597                Opcode::UnpackSequence => 1,
1598                _ => 0,
1599            }
1600        }
1601    }
1602
1603    #[test]
1604    fn cache_entries_and_deopt_tables_match_reference_impl() {
1605        let mut checked = 0;
1606        for byte in 0u8..=255 {
1607            let Ok(op) = Opcode::try_from_u8(byte) else {
1608                continue;
1609            };
1610
1611            assert_eq!(
1612                op.deopt(),
1613                reference::deopt(op),
1614                "deopt() mismatch for {op:?}"
1615            );
1616            assert_eq!(
1617                op.cache_entries(),
1618                reference::cache_entries(op),
1619                "cache_entries() mismatch for {op:?}"
1620            );
1621            checked += 1;
1622        }
1623
1624        // Sanity check that the loop actually exercised opcodes rather than
1625        // silently skipping all of them.
1626        assert!(checked > 200);
1627    }
1628
1629    /// `Opcode::as_numeric`, `Opcode::as_instruction` and
1630    /// `Instruction::as_opcode` used to be per-variant matches; they are now
1631    /// an identity cast and two `mem::transmute`s respectively. `byte` (an
1632    /// input independent of any of those three functions) together with the
1633    /// untouched `try_from_u8`/`TryFrom<u8>` conversions serve as the
1634    /// reference: every opcode reachable from a byte must convert back to
1635    /// that exact byte and round-trip through `Instruction`.
1636    #[test]
1637    fn opcode_instruction_numeric_conversions_match_try_from_numeric() {
1638        let mut checked = 0;
1639        for byte in 0u8..=255 {
1640            let Ok(op) = Opcode::try_from_u8(byte) else {
1641                continue;
1642            };
1643
1644            assert_eq!(op.as_numeric(), byte, "as_numeric() mismatch for {op:?}");
1645
1646            let instr = op.as_instruction();
1647            assert_eq!(
1648                instr.as_opcode(),
1649                op,
1650                "as_instruction()/as_opcode() round trip mismatch for {op:?}"
1651            );
1652
1653            let instr_via_try_from = Instruction::try_from(byte).unwrap();
1654            assert_eq!(
1655                instr_via_try_from.as_opcode(),
1656                op,
1657                "Instruction::try_from({byte}) mismatch"
1658            );
1659
1660            checked += 1;
1661        }
1662
1663        assert!(checked > 200);
1664    }
1665
1666    /// Same as [`opcode_instruction_numeric_conversions_match_try_from_numeric`]
1667    /// but for the `u16`-discriminant pseudo-opcode instantiation of
1668    /// `define_opcodes!`.
1669    #[test]
1670    fn pseudo_opcode_instruction_numeric_conversions_match_try_from_numeric() {
1671        let mut checked = 0;
1672        for value in 0u16..=u16::MAX {
1673            let Ok(op) = PseudoOpcode::try_from_u16(value) else {
1674                continue;
1675            };
1676
1677            assert_eq!(op.as_numeric(), value, "as_numeric() mismatch for {op:?}");
1678
1679            let instr = op.as_instruction();
1680            assert_eq!(
1681                instr.as_opcode(),
1682                op,
1683                "as_instruction()/as_opcode() round trip mismatch for {op:?}"
1684            );
1685
1686            let instr_via_try_from = PseudoInstruction::try_from(value).unwrap();
1687            assert_eq!(
1688                instr_via_try_from.as_opcode(),
1689                op,
1690                "PseudoInstruction::try_from({value}) mismatch"
1691            );
1692
1693            checked += 1;
1694        }
1695
1696        // All 11 `PseudoInstruction` variants should have been exercised.
1697        assert_eq!(checked, 11);
1698    }
1699}