Skip to main content

rustpython_compiler_core/
bytecode.rs

1//! Implement python as a virtual machine with bytecode. This module
2//! implements bytecode structure.
3
4use crate::{
5    marshal::MarshalError,
6    varint::{read_varint, read_varint_with_start, write_varint_be, write_varint_with_start},
7    {OneIndexed, SourceLocation},
8};
9use alloc::{borrow::ToOwned, boxed::Box, collections::BTreeSet, fmt, string::String, vec::Vec};
10use bitflags::bitflags;
11use core::{
12    cell::UnsafeCell,
13    hash, mem,
14    ops::{Deref, Index, IndexMut},
15    sync::atomic::{AtomicU8, AtomicU16, AtomicUsize, Ordering},
16};
17use itertools::Itertools;
18use malachite_bigint::BigInt;
19use num_complex::Complex64;
20use rustpython_wtf8::{Wtf8, Wtf8Buf};
21
22pub use crate::bytecode::{
23    instruction::{
24        AnyInstruction, Arg, Instruction, InstructionMetadata, PseudoInstruction, StackEffect,
25    },
26    oparg::{
27        BinaryOperator, BuildSliceArgCount, CommonConstant, ComparisonOperator, ConvertValueOparg,
28        IntrinsicFunction1, IntrinsicFunction2, Invert, Label, LoadAttr, LoadSuperAttr,
29        MakeFunctionFlag, MakeFunctionFlags, NameIdx, OpArg, OpArgByte, OpArgState, OpArgType,
30        RaiseKind, SpecialMethod, UnpackExArgs,
31    },
32};
33
34mod instruction;
35pub mod oparg;
36
37/// Exception table entry for zero-cost exception handling
38/// Format: (start, size, target, depth<<1|lasti)
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct ExceptionTableEntry {
41    /// Start instruction offset (inclusive)
42    pub start: u32,
43    /// End instruction offset (exclusive)
44    pub end: u32,
45    /// Handler target offset
46    pub target: u32,
47    /// Stack depth at handler entry
48    pub depth: u16,
49    /// Whether to push lasti before exception
50    pub push_lasti: bool,
51}
52
53impl ExceptionTableEntry {
54    pub const fn new(start: u32, end: u32, target: u32, depth: u16, push_lasti: bool) -> Self {
55        Self {
56            start,
57            end,
58            target,
59            depth,
60            push_lasti,
61        }
62    }
63}
64
65/// Encode exception table entries.
66/// Uses 6-bit varint encoding with start marker (MSB) and continuation bit.
67pub fn encode_exception_table(entries: &[ExceptionTableEntry]) -> alloc::boxed::Box<[u8]> {
68    let mut data = Vec::new();
69    for entry in entries {
70        let size = entry.end.saturating_sub(entry.start);
71        let depth_lasti = ((entry.depth as u32) << 1) | (entry.push_lasti as u32);
72
73        write_varint_with_start(&mut data, entry.start);
74        write_varint_be(&mut data, size);
75        write_varint_be(&mut data, entry.target);
76        write_varint_be(&mut data, depth_lasti);
77    }
78    data.into_boxed_slice()
79}
80
81/// Find exception handler for given instruction offset.
82pub fn find_exception_handler(table: &[u8], offset: u32) -> Option<ExceptionTableEntry> {
83    let mut pos = 0;
84    while pos < table.len() {
85        let start = read_varint_with_start(table, &mut pos)?;
86        let size = read_varint(table, &mut pos)?;
87        let target = read_varint(table, &mut pos)?;
88        let depth_lasti = read_varint(table, &mut pos)?;
89
90        let end = start + size;
91        let depth = (depth_lasti >> 1) as u16;
92        let push_lasti = (depth_lasti & 1) != 0;
93
94        if offset >= start && offset < end {
95            return Some(ExceptionTableEntry {
96                start,
97                end,
98                target,
99                depth,
100                push_lasti,
101            });
102        }
103    }
104    None
105}
106
107/// Decode all exception table entries.
108pub fn decode_exception_table(table: &[u8]) -> Vec<ExceptionTableEntry> {
109    let mut entries = Vec::new();
110    let mut pos = 0;
111    while pos < table.len() {
112        let Some(start) = read_varint_with_start(table, &mut pos) else {
113            break;
114        };
115        let Some(size) = read_varint(table, &mut pos) else {
116            break;
117        };
118        let Some(target) = read_varint(table, &mut pos) else {
119            break;
120        };
121        let Some(depth_lasti) = read_varint(table, &mut pos) else {
122            break;
123        };
124        let Some(end) = start.checked_add(size) else {
125            break;
126        };
127        entries.push(ExceptionTableEntry {
128            start,
129            end,
130            target,
131            depth: (depth_lasti >> 1) as u16,
132            push_lasti: (depth_lasti & 1) != 0,
133        });
134    }
135    entries
136}
137
138/// Parse linetable to build a boolean mask indicating which code units
139/// have NO_LOCATION (line == -1). Returns a Vec<bool> of length `num_units`.
140pub fn build_no_location_mask(linetable: &[u8], num_units: usize) -> Vec<bool> {
141    let mut mask = Vec::new();
142    mask.resize(num_units, false);
143    let mut pos = 0;
144    let mut unit_idx = 0;
145
146    while pos < linetable.len() && unit_idx < num_units {
147        let header = linetable[pos];
148        pos += 1;
149        let code = (header >> 3) & 0xf;
150        let length = ((header & 7) + 1) as usize;
151
152        let is_no_location = code == PyCodeLocationInfoKind::None as u8;
153
154        // Skip payload bytes based on location kind
155        match code {
156            0..=9 => pos += 1,   // Short forms: 1 byte payload
157            10..=12 => pos += 2, // OneLine forms: 2 bytes payload
158            13 => {
159                // NoColumns: signed varint (line delta)
160                while pos < linetable.len() {
161                    let b = linetable[pos];
162                    pos += 1;
163                    if b & 0x40 == 0 {
164                        break;
165                    }
166                }
167            }
168            14 => {
169                // Long form: signed varint (line delta) + 3 unsigned varints
170                // line_delta
171                while pos < linetable.len() {
172                    let b = linetable[pos];
173                    pos += 1;
174                    if b & 0x40 == 0 {
175                        break;
176                    }
177                }
178                // end_line_delta, col+1, end_col+1
179                for _ in 0..3 {
180                    while pos < linetable.len() {
181                        let b = linetable[pos];
182                        pos += 1;
183                        if b & 0x40 == 0 {
184                            break;
185                        }
186                    }
187                }
188            }
189            15 => {} // None: no payload
190            _ => {}
191        }
192
193        for _ in 0..length {
194            if unit_idx < num_units {
195                mask[unit_idx] = is_no_location;
196                unit_idx += 1;
197            }
198        }
199    }
200
201    mask
202}
203
204/// CPython 3.11+ linetable location info codes
205#[derive(Copy, Clone, Debug, PartialEq, Eq)]
206#[repr(u8)]
207pub enum PyCodeLocationInfoKind {
208    // Short forms are 0 to 9
209    Short0 = 0,
210    Short1 = 1,
211    Short2 = 2,
212    Short3 = 3,
213    Short4 = 4,
214    Short5 = 5,
215    Short6 = 6,
216    Short7 = 7,
217    Short8 = 8,
218    Short9 = 9,
219    // One line forms are 10 to 12
220    OneLine0 = 10,
221    OneLine1 = 11,
222    OneLine2 = 12,
223    NoColumns = 13,
224    Long = 14,
225    None = 15,
226}
227
228impl PyCodeLocationInfoKind {
229    pub fn from_code(code: u8) -> Option<Self> {
230        match code {
231            0 => Some(Self::Short0),
232            1 => Some(Self::Short1),
233            2 => Some(Self::Short2),
234            3 => Some(Self::Short3),
235            4 => Some(Self::Short4),
236            5 => Some(Self::Short5),
237            6 => Some(Self::Short6),
238            7 => Some(Self::Short7),
239            8 => Some(Self::Short8),
240            9 => Some(Self::Short9),
241            10 => Some(Self::OneLine0),
242            11 => Some(Self::OneLine1),
243            12 => Some(Self::OneLine2),
244            13 => Some(Self::NoColumns),
245            14 => Some(Self::Long),
246            15 => Some(Self::None),
247            _ => Option::None,
248        }
249    }
250
251    pub fn is_short(&self) -> bool {
252        (*self as u8) <= 9
253    }
254
255    pub fn short_column_group(&self) -> Option<u8> {
256        if self.is_short() {
257            Some(*self as u8)
258        } else {
259            Option::None
260        }
261    }
262
263    pub fn one_line_delta(&self) -> Option<i32> {
264        match self {
265            Self::OneLine0 => Some(0),
266            Self::OneLine1 => Some(1),
267            Self::OneLine2 => Some(2),
268            _ => Option::None,
269        }
270    }
271}
272
273pub trait Constant: Sized + Clone {
274    type Name: AsRef<str>;
275
276    /// Transforms the given Constant to a BorrowedConstant
277    fn borrow_constant(&self) -> BorrowedConstant<'_, Self>;
278}
279
280impl Constant for ConstantData {
281    type Name = String;
282
283    fn borrow_constant(&self) -> BorrowedConstant<'_, Self> {
284        use BorrowedConstant::*;
285
286        match self {
287            Self::Integer { value } => Integer { value },
288            Self::Float { value } => Float { value: *value },
289            Self::Complex { value } => Complex { value: *value },
290            Self::Boolean { value } => Boolean { value: *value },
291            Self::Str { value } => Str { value },
292            Self::Bytes { value } => Bytes { value },
293            Self::Code { code } => Code { code },
294            Self::Tuple { elements } => Tuple { elements },
295            Self::Slice { elements } => Slice { elements },
296            Self::Frozenset { elements } => Frozenset { elements },
297            Self::None => None,
298            Self::Ellipsis => Ellipsis,
299        }
300    }
301}
302
303/// A Constant Bag
304pub trait ConstantBag: Sized + Copy {
305    type Constant: Constant;
306
307    fn make_constant<C: Constant>(&self, constant: BorrowedConstant<'_, C>) -> Self::Constant;
308
309    fn make_int(&self, value: BigInt) -> Self::Constant;
310
311    fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant;
312
313    fn make_code(&self, code: CodeObject<Self::Constant>) -> Self::Constant;
314
315    fn make_name(&self, name: &str) -> <Self::Constant as Constant>::Name;
316}
317
318pub trait AsBag {
319    type Bag: ConstantBag;
320
321    #[allow(clippy::wrong_self_convention)]
322    fn as_bag(self) -> Self::Bag;
323}
324
325impl<Bag: ConstantBag> AsBag for Bag {
326    type Bag = Self;
327
328    fn as_bag(self) -> Self {
329        self
330    }
331}
332
333#[derive(Clone, Copy)]
334pub struct BasicBag;
335
336impl ConstantBag for BasicBag {
337    type Constant = ConstantData;
338
339    fn make_constant<C: Constant>(&self, constant: BorrowedConstant<'_, C>) -> Self::Constant {
340        constant.to_owned()
341    }
342
343    fn make_int(&self, value: BigInt) -> Self::Constant {
344        ConstantData::Integer { value }
345    }
346
347    fn make_tuple(&self, elements: impl Iterator<Item = Self::Constant>) -> Self::Constant {
348        ConstantData::Tuple {
349            elements: elements.collect(),
350        }
351    }
352
353    fn make_code(&self, code: CodeObject<Self::Constant>) -> Self::Constant {
354        ConstantData::Code {
355            code: Box::new(code),
356        }
357    }
358
359    fn make_name(&self, name: &str) -> <Self::Constant as Constant>::Name {
360        name.to_owned()
361    }
362}
363
364#[derive(Clone)]
365pub struct Constants<C: Constant>(Box<[C]>);
366
367impl<C: Constant> Deref for Constants<C> {
368    type Target = [C];
369
370    fn deref(&self) -> &Self::Target {
371        &self.0
372    }
373}
374
375impl<C: Constant> Index<oparg::ConstIdx> for Constants<C> {
376    type Output = C;
377
378    fn index(&self, consti: oparg::ConstIdx) -> &Self::Output {
379        &self.0[consti.as_usize()]
380    }
381}
382
383impl<C: Constant> FromIterator<C> for Constants<C> {
384    fn from_iter<T: IntoIterator<Item = C>>(iter: T) -> Self {
385        Self(iter.into_iter().collect())
386    }
387}
388
389// TODO: Newtype "CodeObject.varnames". Make sure only `oparg:VarNum` can be used as index
390impl<T> Index<oparg::VarNum> for [T] {
391    type Output = T;
392
393    fn index(&self, var_num: oparg::VarNum) -> &Self::Output {
394        &self[var_num.as_usize()]
395    }
396}
397
398// TODO: Newtype "CodeObject.varnames". Make sure only `oparg:VarNum` can be used as index
399impl<T> IndexMut<oparg::VarNum> for [T] {
400    fn index_mut(&mut self, var_num: oparg::VarNum) -> &mut Self::Output {
401        &mut self[var_num.as_usize()]
402    }
403}
404
405/// Per-slot kind flags for localsplus (co_localspluskinds).
406pub const CO_FAST_HIDDEN: u8 = 0x10;
407pub const CO_FAST_LOCAL: u8 = 0x20;
408pub const CO_FAST_CELL: u8 = 0x40;
409pub const CO_FAST_FREE: u8 = 0x80;
410
411/// Primary container of a single code object. Each python function has
412/// a code object. Also a module has a code object.
413#[derive(Clone)]
414pub struct CodeObject<C: Constant = ConstantData> {
415    pub instructions: CodeUnits,
416    pub locations: Box<[(SourceLocation, SourceLocation)]>,
417    pub flags: CodeFlags,
418    /// Number of positional-only arguments
419    pub posonlyarg_count: u32,
420    pub arg_count: u32,
421    pub kwonlyarg_count: u32,
422    pub source_path: C::Name,
423    pub first_line_number: Option<OneIndexed>,
424    pub max_stackdepth: u32,
425    /// Name of the object that created this code object
426    pub obj_name: C::Name,
427    /// Qualified name of the object (like CPython's co_qualname)
428    pub qualname: C::Name,
429    pub constants: Constants<C>,
430    pub names: Box<[C::Name]>,
431    pub varnames: Box<[C::Name]>,
432    pub cellvars: Box<[C::Name]>,
433    pub freevars: Box<[C::Name]>,
434    /// Per-slot kind flags: CO_FAST_LOCAL, CO_FAST_CELL, CO_FAST_FREE, CO_FAST_HIDDEN.
435    /// Length = nlocalsplus (nlocals + ncells + nfrees).
436    pub localspluskinds: Box<[u8]>,
437    /// Line number table (CPython 3.11+ format)
438    pub linetable: Box<[u8]>,
439    /// Exception handling table
440    pub exceptiontable: Box<[u8]>,
441}
442
443bitflags! {
444    #[derive(Copy, Clone, Debug, PartialEq)]
445    pub struct CodeFlags: u32 {
446        const OPTIMIZED = 0x0001;
447        const NEWLOCALS = 0x0002;
448        const VARARGS = 0x0004;
449        const VARKEYWORDS = 0x0008;
450        const NESTED = 0x0010;
451        const GENERATOR = 0x0020;
452        const COROUTINE = 0x0080;
453        const ITERABLE_COROUTINE = 0x0100;
454        /// If a code object represents a function and has a docstring,
455        /// this bit is set and the first item in co_consts is the docstring.
456        const HAS_DOCSTRING = 0x4000000;
457    }
458}
459
460#[repr(C)]
461#[derive(Copy, Clone, Debug)]
462pub struct CodeUnit {
463    pub op: Instruction,
464    pub arg: OpArgByte,
465}
466
467const _: () = assert!(mem::size_of::<CodeUnit>() == 2);
468
469/// Adaptive specialization: number of executions before attempting specialization.
470///
471/// Matches CPython's `_Py_BackoffCounter` encoding.
472pub const ADAPTIVE_WARMUP_VALUE: u16 = adaptive_counter_bits(1, 1);
473/// Adaptive specialization: cooldown counter after a successful specialization.
474///
475/// Value/backoff = (52, 0), matching CPython's ADAPTIVE_COOLDOWN bits.
476pub const ADAPTIVE_COOLDOWN_VALUE: u16 = adaptive_counter_bits(52, 0);
477/// Initial JUMP_BACKWARD counter bits (value/backoff = 4095/12).
478pub const JUMP_BACKWARD_INITIAL_VALUE: u16 = adaptive_counter_bits(4095, 12);
479
480const BACKOFF_BITS: u16 = 4;
481const MAX_BACKOFF: u16 = 12;
482const UNREACHABLE_BACKOFF: u16 = 15;
483
484/// Encode an adaptive counter as `(value << 4) | backoff`.
485pub const fn adaptive_counter_bits(value: u16, backoff: u16) -> u16 {
486    (value << BACKOFF_BITS) | backoff
487}
488
489/// True when the adaptive counter should trigger specialization.
490#[inline]
491pub const fn adaptive_counter_triggers(counter: u16) -> bool {
492    counter < UNREACHABLE_BACKOFF
493}
494
495/// Decrement adaptive counter by one countdown step.
496#[inline]
497pub const fn advance_adaptive_counter(counter: u16) -> u16 {
498    counter.wrapping_sub(1 << BACKOFF_BITS)
499}
500
501/// Reset adaptive counter with exponential backoff.
502#[inline]
503pub const fn adaptive_counter_backoff(counter: u16) -> u16 {
504    let backoff = counter & ((1 << BACKOFF_BITS) - 1);
505    if backoff < MAX_BACKOFF {
506        adaptive_counter_bits((1 << (backoff + 1)) - 1, backoff + 1)
507    } else {
508        adaptive_counter_bits((1 << MAX_BACKOFF) - 1, MAX_BACKOFF)
509    }
510}
511
512impl CodeUnit {
513    pub const fn new(op: Instruction, arg: OpArgByte) -> Self {
514        Self { op, arg }
515    }
516}
517
518impl TryFrom<&[u8]> for CodeUnit {
519    type Error = MarshalError;
520
521    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
522        match value.len() {
523            2 => Ok(Self::new(value[0].try_into()?, value[1].into())),
524            _ => Err(Self::Error::InvalidBytecode),
525        }
526    }
527}
528
529pub struct CodeUnits {
530    units: UnsafeCell<Box<[CodeUnit]>>,
531    adaptive_counters: Box<[AtomicU16]>,
532    /// Pointer-sized cache entries for descriptor pointers.
533    /// Single atomic load/store prevents torn reads when multiple threads
534    /// specialize the same instruction concurrently.
535    pointer_cache: Box<[AtomicUsize]>,
536}
537
538// SAFETY: All cache operations use atomic read/write instructions.
539// - replace_op / compare_exchange_op: AtomicU8 store/CAS (Release)
540// - cache read/write: AtomicU16 load/store (Relaxed)
541// - adaptive counter: AtomicU16 load/store (Relaxed)
542// Ordering is established by:
543// - replace_op (Release) ↔ dispatch loop read_op (Acquire) for cache data visibility
544// - tp_version_tag (Acquire) for descriptor pointer validity
545unsafe impl Sync for CodeUnits {}
546
547impl Clone for CodeUnits {
548    fn clone(&self) -> Self {
549        // SAFETY: No concurrent mutation during clone — cloning is only done
550        // during code object construction or marshaling, not while instrumented.
551        let units = unsafe { &*self.units.get() }.clone();
552        let adaptive_counters = self
553            .adaptive_counters
554            .iter()
555            .map(|c| AtomicU16::new(c.load(Ordering::Relaxed)))
556            .collect();
557        let pointer_cache = self
558            .pointer_cache
559            .iter()
560            .map(|c| AtomicUsize::new(c.load(Ordering::Relaxed)))
561            .collect();
562        Self {
563            units: UnsafeCell::new(units),
564            adaptive_counters,
565            pointer_cache,
566        }
567    }
568}
569
570impl fmt::Debug for CodeUnits {
571    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572        // SAFETY: Debug formatting doesn't race with replace_op
573        let inner = unsafe { &*self.units.get() };
574        f.debug_tuple("CodeUnits").field(inner).finish()
575    }
576}
577
578impl TryFrom<&[u8]> for CodeUnits {
579    type Error = MarshalError;
580
581    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
582        if !value.len().is_multiple_of(2) {
583            return Err(Self::Error::InvalidBytecode);
584        }
585
586        let units = value
587            .chunks_exact(2)
588            .map(CodeUnit::try_from)
589            .collect::<Result<Vec<_>, _>>()?;
590        Ok(units.into())
591    }
592}
593
594impl<const N: usize> From<[CodeUnit; N]> for CodeUnits {
595    fn from(value: [CodeUnit; N]) -> Self {
596        Self::from(Vec::from(value))
597    }
598}
599
600impl From<Vec<CodeUnit>> for CodeUnits {
601    fn from(value: Vec<CodeUnit>) -> Self {
602        let units = value.into_boxed_slice();
603        let len = units.len();
604        let adaptive_counters = (0..len)
605            .map(|_| AtomicU16::new(0))
606            .collect::<Vec<_>>()
607            .into_boxed_slice();
608        let pointer_cache = (0..len)
609            .map(|_| AtomicUsize::new(0))
610            .collect::<Vec<_>>()
611            .into_boxed_slice();
612        Self {
613            units: UnsafeCell::new(units),
614            adaptive_counters,
615            pointer_cache,
616        }
617    }
618}
619
620impl FromIterator<CodeUnit> for CodeUnits {
621    fn from_iter<T: IntoIterator<Item = CodeUnit>>(iter: T) -> Self {
622        Self::from(iter.into_iter().collect::<Vec<_>>())
623    }
624}
625
626impl Deref for CodeUnits {
627    type Target = [CodeUnit];
628
629    fn deref(&self) -> &Self::Target {
630        // SAFETY: Shared references to the slice are valid even while replace_op
631        // may update individual opcode bytes — readers tolerate stale opcodes
632        // (they will re-read on the next iteration).
633        unsafe { &*self.units.get() }
634    }
635}
636
637impl CodeUnits {
638    /// Disable adaptive specialization by setting all counters to unreachable.
639    /// Used for CPython-compiled bytecode where specialization may not be safe.
640    pub fn disable_specialization(&self) {
641        for counter in self.adaptive_counters.iter() {
642            counter.store(UNREACHABLE_BACKOFF, Ordering::Relaxed);
643        }
644    }
645
646    /// Replace the opcode at `index` in-place without changing the arg byte.
647    /// Uses atomic Release store to ensure prior cache writes are visible
648    /// to threads that subsequently read the new opcode with Acquire.
649    ///
650    /// # Safety
651    /// - `index` must be in bounds.
652    /// - `new_op` must have the same arg semantics as the original opcode.
653    pub unsafe fn replace_op(&self, index: usize, new_op: Instruction) {
654        let units = unsafe { &*self.units.get() };
655        let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU8;
656        unsafe { &*ptr }.store(new_op.into(), Ordering::Release);
657    }
658
659    /// Atomically replace opcode only if it still matches `expected`.
660    /// Returns true on success. Uses Release ordering on success.
661    ///
662    /// # Safety
663    /// - `index` must be in bounds.
664    pub unsafe fn compare_exchange_op(
665        &self,
666        index: usize,
667        expected: Instruction,
668        new_op: Instruction,
669    ) -> bool {
670        let units = unsafe { &*self.units.get() };
671        let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU8;
672        unsafe { &*ptr }
673            .compare_exchange(
674                expected.into(),
675                new_op.into(),
676                Ordering::Release,
677                Ordering::Relaxed,
678            )
679            .is_ok()
680    }
681
682    /// Atomically read the opcode at `index` with Acquire ordering.
683    /// Pairs with `replace_op` (Release) to ensure cache data visibility.
684    pub fn read_op(&self, index: usize) -> Instruction {
685        let units = unsafe { &*self.units.get() };
686        let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU8;
687        let byte = unsafe { &*ptr }.load(Ordering::Acquire);
688        // SAFETY: Only valid Instruction values are stored via replace_op/compare_exchange_op.
689        unsafe { mem::transmute::<u8, Instruction>(byte) }
690    }
691
692    /// Atomically read the arg byte at `index` with Relaxed ordering.
693    pub fn read_arg(&self, index: usize) -> OpArgByte {
694        let units = unsafe { &*self.units.get() };
695        let ptr = units.as_ptr().wrapping_add(index) as *const u8;
696        let arg_ptr = unsafe { ptr.add(1) } as *const AtomicU8;
697        OpArgByte::from(unsafe { &*arg_ptr }.load(Ordering::Relaxed))
698    }
699
700    /// Write a u16 value into a CACHE code unit at `index`.
701    /// Each CodeUnit is 2 bytes (#[repr(C)]: op u8 + arg u8), so one u16 fits exactly.
702    /// Uses Relaxed atomic store; ordering is provided by replace_op (Release).
703    ///
704    /// # Safety
705    /// - `index` must be in bounds and point to a CACHE entry.
706    pub unsafe fn write_cache_u16(&self, index: usize, value: u16) {
707        let units = unsafe { &*self.units.get() };
708        let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU16;
709        unsafe { &*ptr }.store(value, Ordering::Relaxed);
710    }
711
712    /// Read a u16 value from a CACHE code unit at `index`.
713    /// Uses Relaxed atomic load; ordering is provided by read_op (Acquire).
714    ///
715    /// # Panics
716    /// Panics if `index` is out of bounds.
717    pub fn read_cache_u16(&self, index: usize) -> u16 {
718        let units = unsafe { &*self.units.get() };
719        assert!(index < units.len(), "read_cache_u16: index out of bounds");
720        let ptr = units.as_ptr().wrapping_add(index) as *const AtomicU16;
721        unsafe { &*ptr }.load(Ordering::Relaxed)
722    }
723
724    /// Write a u32 value across two consecutive CACHE code units starting at `index`.
725    ///
726    /// # Safety
727    /// Same requirements as `write_cache_u16`.
728    pub unsafe fn write_cache_u32(&self, index: usize, value: u32) {
729        unsafe {
730            self.write_cache_u16(index, value as u16);
731            self.write_cache_u16(index + 1, (value >> 16) as u16);
732        }
733    }
734
735    /// Read a u32 value from two consecutive CACHE code units starting at `index`.
736    ///
737    /// # Panics
738    /// Panics if `index + 1` is out of bounds.
739    pub fn read_cache_u32(&self, index: usize) -> u32 {
740        let lo = self.read_cache_u16(index) as u32;
741        let hi = self.read_cache_u16(index + 1) as u32;
742        lo | (hi << 16)
743    }
744
745    /// Store a pointer-sized value atomically in the pointer cache at `index`.
746    ///
747    /// Uses a single `AtomicUsize` store to prevent torn writes when
748    /// multiple threads specialize the same instruction concurrently.
749    ///
750    /// # Safety
751    /// - `index` must be in bounds.
752    /// - `value` must be `0` or a valid `*const PyObject` encoded as `usize`.
753    /// - Callers must follow the cache invalidation/upgrade protocol:
754    ///   invalidate the version guard before writing and publish the new
755    ///   version after writing.
756    pub unsafe fn write_cache_ptr(&self, index: usize, value: usize) {
757        self.pointer_cache[index].store(value, Ordering::Relaxed);
758    }
759
760    /// Load a pointer-sized value atomically from the pointer cache at `index`.
761    ///
762    /// Uses a single `AtomicUsize` load to prevent torn reads.
763    ///
764    /// # Panics
765    /// Panics if `index` is out of bounds.
766    pub fn read_cache_ptr(&self, index: usize) -> usize {
767        self.pointer_cache[index].load(Ordering::Relaxed)
768    }
769
770    /// Read adaptive counter bits for instruction at `index`.
771    /// Uses Relaxed atomic load.
772    pub fn read_adaptive_counter(&self, index: usize) -> u16 {
773        self.adaptive_counters[index].load(Ordering::Relaxed)
774    }
775
776    /// Write adaptive counter bits for instruction at `index`.
777    /// Uses Relaxed atomic store.
778    ///
779    /// # Safety
780    /// - `index` must be in bounds.
781    pub unsafe fn write_adaptive_counter(&self, index: usize, value: u16) {
782        self.adaptive_counters[index].store(value, Ordering::Relaxed);
783    }
784
785    /// Produce a clean copy of the bytecode suitable for serialization
786    /// (marshal) and `co_code`. Specialized opcodes are mapped back to their
787    /// base variants via `deoptimize()` and all CACHE entries are zeroed.
788    pub fn original_bytes(&self) -> Vec<u8> {
789        let len = self.len();
790        let mut out = Vec::with_capacity(len * 2);
791        let mut i = 0;
792        while i < len {
793            let op = self.read_op(i).deoptimize();
794            let arg = self.read_arg(i);
795            let caches = op.cache_entries();
796            out.push(u8::from(op));
797            out.push(u8::from(arg));
798            // Zero-fill all CACHE entries (counter + cached data)
799            for _ in 0..caches {
800                i += 1;
801                out.push(0); // op = Cache = 0
802                out.push(0); // arg = 0
803            }
804            i += 1;
805        }
806        out
807    }
808
809    /// Initialize adaptive warmup counters for all cacheable instructions.
810    /// Called lazily at RESUME (first execution of a code object).
811    /// Counters are stored out-of-line to preserve `op = Instruction::Cache`.
812    /// All writes are atomic (Relaxed) to avoid data races with concurrent readers.
813    pub fn quicken(&self) {
814        let len = self.len();
815        let mut i = 0;
816        while i < len {
817            let op = self.read_op(i);
818            let caches = op.cache_entries();
819            if caches > 0 {
820                // Don't write adaptive counter for instrumented opcodes;
821                // specialization is skipped while monitoring is active.
822                if !op.is_instrumented() {
823                    let cache_base = i + 1;
824                    if cache_base < len {
825                        let initial_counter = if matches!(op, Instruction::JumpBackward { .. }) {
826                            JUMP_BACKWARD_INITIAL_VALUE
827                        } else {
828                            ADAPTIVE_WARMUP_VALUE
829                        };
830                        unsafe {
831                            self.write_adaptive_counter(cache_base, initial_counter);
832                        }
833                    }
834                }
835                i += 1 + caches;
836            } else {
837                i += 1;
838            }
839        }
840    }
841}
842
843/// A Constant (which usually encapsulates data within it)
844///
845/// # Examples
846/// ```
847/// use rustpython_compiler_core::bytecode::ConstantData;
848/// let a = ConstantData::Float {value: 120f64};
849/// let b = ConstantData::Boolean {value: false};
850/// assert_ne!(a, b);
851/// ```
852#[derive(Debug, Clone)]
853pub enum ConstantData {
854    Tuple {
855        elements: Vec<ConstantData>,
856    },
857    Integer {
858        value: BigInt,
859    },
860    Float {
861        value: f64,
862    },
863    Complex {
864        value: Complex64,
865    },
866    Boolean {
867        value: bool,
868    },
869    Str {
870        value: Wtf8Buf,
871    },
872    Bytes {
873        value: Vec<u8>,
874    },
875    Code {
876        code: Box<CodeObject>,
877    },
878    /// Constant slice(start, stop, step)
879    Slice {
880        elements: Box<[ConstantData; 3]>,
881    },
882    Frozenset {
883        elements: Vec<ConstantData>,
884    },
885    None,
886    Ellipsis,
887}
888
889impl PartialEq for ConstantData {
890    fn eq(&self, other: &Self) -> bool {
891        use ConstantData::*;
892
893        match (self, other) {
894            (Integer { value: a }, Integer { value: b }) => a == b,
895            // we want to compare floats *by actual value* - if we have the *exact same* float
896            // already in a constant cache, we want to use that
897            (Float { value: a }, Float { value: b }) => a.to_bits() == b.to_bits(),
898            (Complex { value: a }, Complex { value: b }) => {
899                a.re.to_bits() == b.re.to_bits() && a.im.to_bits() == b.im.to_bits()
900            }
901            (Boolean { value: a }, Boolean { value: b }) => a == b,
902            (Str { value: a }, Str { value: b }) => a == b,
903            (Bytes { value: a }, Bytes { value: b }) => a == b,
904            (Code { code: a }, Code { code: b }) => core::ptr::eq(a.as_ref(), b.as_ref()),
905            (Tuple { elements: a }, Tuple { elements: b }) => a == b,
906            (Slice { elements: a }, Slice { elements: b }) => a == b,
907            (Frozenset { elements: a }, Frozenset { elements: b }) => a == b,
908            (None, None) => true,
909            (Ellipsis, Ellipsis) => true,
910            _ => false,
911        }
912    }
913}
914
915impl Eq for ConstantData {}
916
917impl hash::Hash for ConstantData {
918    fn hash<H: hash::Hasher>(&self, state: &mut H) {
919        use ConstantData::*;
920
921        mem::discriminant(self).hash(state);
922        match self {
923            Integer { value } => value.hash(state),
924            Float { value } => value.to_bits().hash(state),
925            Complex { value } => {
926                value.re.to_bits().hash(state);
927                value.im.to_bits().hash(state);
928            }
929            Boolean { value } => value.hash(state),
930            Str { value } => value.hash(state),
931            Bytes { value } => value.hash(state),
932            Code { code } => core::ptr::hash(code.as_ref(), state),
933            Tuple { elements } => elements.hash(state),
934            Slice { elements } => elements.hash(state),
935            Frozenset { elements } => elements.hash(state),
936            None => {}
937            Ellipsis => {}
938        }
939    }
940}
941
942/// A borrowed Constant
943pub enum BorrowedConstant<'a, C: Constant> {
944    Integer { value: &'a BigInt },
945    Float { value: f64 },
946    Complex { value: Complex64 },
947    Boolean { value: bool },
948    Str { value: &'a Wtf8 },
949    Bytes { value: &'a [u8] },
950    Code { code: &'a CodeObject<C> },
951    Tuple { elements: &'a [C] },
952    Slice { elements: &'a [C; 3] },
953    Frozenset { elements: &'a [C] },
954    None,
955    Ellipsis,
956}
957
958impl<C: Constant> Copy for BorrowedConstant<'_, C> {}
959
960impl<C: Constant> Clone for BorrowedConstant<'_, C> {
961    fn clone(&self) -> Self {
962        *self
963    }
964}
965
966impl<C: Constant> BorrowedConstant<'_, C> {
967    pub fn fmt_display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
968        match self {
969            BorrowedConstant::Integer { value } => write!(f, "{value}"),
970            BorrowedConstant::Float { value } => write!(f, "{value}"),
971            BorrowedConstant::Complex { value } => write!(f, "{value}"),
972            BorrowedConstant::Boolean { value } => {
973                write!(f, "{}", if *value { "True" } else { "False" })
974            }
975            BorrowedConstant::Str { value } => write!(f, "{value:?}"),
976            BorrowedConstant::Bytes { value } => write!(f, r#"b"{}""#, value.escape_ascii()),
977            BorrowedConstant::Code { code } => write!(f, "{code:?}"),
978            BorrowedConstant::Tuple { elements } => {
979                write!(f, "(")?;
980                let mut first = true;
981                for c in *elements {
982                    if first {
983                        first = false
984                    } else {
985                        write!(f, ", ")?;
986                    }
987                    c.borrow_constant().fmt_display(f)?;
988                }
989                write!(f, ")")
990            }
991            BorrowedConstant::Slice { elements } => {
992                write!(f, "slice(")?;
993                elements[0].borrow_constant().fmt_display(f)?;
994                write!(f, ", ")?;
995                elements[1].borrow_constant().fmt_display(f)?;
996                write!(f, ", ")?;
997                elements[2].borrow_constant().fmt_display(f)?;
998                write!(f, ")")
999            }
1000            BorrowedConstant::Frozenset { elements } => {
1001                write!(f, "frozenset({{")?;
1002                let mut first = true;
1003                for c in *elements {
1004                    if first {
1005                        first = false
1006                    } else {
1007                        write!(f, ", ")?;
1008                    }
1009                    c.borrow_constant().fmt_display(f)?;
1010                }
1011                write!(f, "}})")
1012            }
1013            BorrowedConstant::None => write!(f, "None"),
1014            BorrowedConstant::Ellipsis => write!(f, "..."),
1015        }
1016    }
1017
1018    pub fn to_owned(self) -> ConstantData {
1019        use ConstantData::*;
1020
1021        match self {
1022            BorrowedConstant::Integer { value } => Integer {
1023                value: value.clone(),
1024            },
1025            BorrowedConstant::Float { value } => Float { value },
1026            BorrowedConstant::Complex { value } => Complex { value },
1027            BorrowedConstant::Boolean { value } => Boolean { value },
1028            BorrowedConstant::Str { value } => Str {
1029                value: value.to_owned(),
1030            },
1031            BorrowedConstant::Bytes { value } => Bytes {
1032                value: value.to_owned(),
1033            },
1034            BorrowedConstant::Code { code } => Code {
1035                code: Box::new(code.map_clone_bag(&BasicBag)),
1036            },
1037            BorrowedConstant::Tuple { elements } => Tuple {
1038                elements: elements
1039                    .iter()
1040                    .map(|c| c.borrow_constant().to_owned())
1041                    .collect(),
1042            },
1043            BorrowedConstant::Slice { elements } => Slice {
1044                elements: Box::new(elements.each_ref().map(|c| c.borrow_constant().to_owned())),
1045            },
1046            BorrowedConstant::Frozenset { elements } => Frozenset {
1047                elements: elements
1048                    .iter()
1049                    .map(|c| c.borrow_constant().to_owned())
1050                    .collect(),
1051            },
1052            BorrowedConstant::None => None,
1053            BorrowedConstant::Ellipsis => Ellipsis,
1054        }
1055    }
1056}
1057
1058/*
1059Maintain a stack of blocks on the VM.
1060pub enum BlockType {
1061    Loop,
1062    Except,
1063}
1064*/
1065
1066/// Argument structure
1067pub struct Arguments<'a, N: AsRef<str>> {
1068    pub posonlyargs: &'a [N],
1069    pub args: &'a [N],
1070    pub vararg: Option<&'a N>,
1071    pub kwonlyargs: &'a [N],
1072    pub varkwarg: Option<&'a N>,
1073}
1074
1075impl<N: AsRef<str>> fmt::Debug for Arguments<'_, N> {
1076    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1077        macro_rules! fmt_slice {
1078            ($x:expr) => {
1079                format_args!("[{}]", $x.iter().map(AsRef::as_ref).format(", "))
1080            };
1081        }
1082        f.debug_struct("Arguments")
1083            .field("posonlyargs", &fmt_slice!(self.posonlyargs))
1084            .field("args", &fmt_slice!(self.posonlyargs))
1085            .field("vararg", &self.vararg.map(N::as_ref))
1086            .field("kwonlyargs", &fmt_slice!(self.kwonlyargs))
1087            .field("varkwarg", &self.varkwarg.map(N::as_ref))
1088            .finish()
1089    }
1090}
1091
1092impl<C: Constant> CodeObject<C> {
1093    /// Get all arguments of the code object
1094    /// like inspect.getargs
1095    pub fn arg_names(&self) -> Arguments<'_, C::Name> {
1096        let nargs = self.arg_count as usize;
1097        let nkwargs = self.kwonlyarg_count as usize;
1098        let mut varargs_pos = nargs + nkwargs;
1099        let posonlyargs = &self.varnames[..self.posonlyarg_count as usize];
1100        let args = &self.varnames[..nargs];
1101        let kwonlyargs = &self.varnames[nargs..varargs_pos];
1102
1103        let vararg = if self.flags.contains(CodeFlags::VARARGS) {
1104            let vararg = &self.varnames[varargs_pos];
1105            varargs_pos += 1;
1106            Some(vararg)
1107        } else {
1108            None
1109        };
1110        let varkwarg = if self.flags.contains(CodeFlags::VARKEYWORDS) {
1111            Some(&self.varnames[varargs_pos])
1112        } else {
1113            None
1114        };
1115
1116        Arguments {
1117            posonlyargs,
1118            args,
1119            vararg,
1120            kwonlyargs,
1121            varkwarg,
1122        }
1123    }
1124
1125    /// Return the labels targeted by the instructions of this CodeObject
1126    pub fn label_targets(&self) -> BTreeSet<Label> {
1127        let mut label_targets = BTreeSet::new();
1128        let mut arg_state = OpArgState::default();
1129        for instruction in &*self.instructions {
1130            let (instruction, arg) = arg_state.get(*instruction);
1131            if let Some(l) = instruction.label_arg() {
1132                label_targets.insert(l.get(arg));
1133            }
1134        }
1135        label_targets
1136    }
1137
1138    fn display_inner(
1139        &self,
1140        f: &mut fmt::Formatter<'_>,
1141        expand_code_objects: bool,
1142        level: usize,
1143    ) -> fmt::Result {
1144        let label_targets = self.label_targets();
1145        let line_digits = (3).max(self.locations.last().unwrap().0.line.digits().get());
1146        let offset_digits = (4).max(1 + self.instructions.len().ilog10() as usize);
1147        let mut last_line = OneIndexed::MAX;
1148        let mut arg_state = OpArgState::default();
1149        for (offset, &instruction) in self.instructions.iter().enumerate() {
1150            let (instruction, arg) = arg_state.get(instruction);
1151            // optional line number
1152            let line = self.locations[offset].0.line;
1153            if line != last_line {
1154                if last_line != OneIndexed::MAX {
1155                    writeln!(f)?;
1156                }
1157                last_line = line;
1158                write!(f, "{line:line_digits$}")?;
1159            } else {
1160                for _ in 0..line_digits {
1161                    write!(f, " ")?;
1162                }
1163            }
1164            write!(f, " ")?;
1165
1166            // level indent
1167            for _ in 0..level {
1168                write!(f, "    ")?;
1169            }
1170
1171            // arrow and offset
1172            let arrow = if label_targets.contains(&Label::from_u32(offset as u32)) {
1173                ">>"
1174            } else {
1175                "  "
1176            };
1177            write!(f, "{arrow} {offset:offset_digits$} ")?;
1178
1179            // instruction
1180            instruction.fmt_dis(arg, f, self, expand_code_objects, 21, level)?;
1181            writeln!(f)?;
1182        }
1183        Ok(())
1184    }
1185
1186    /// Recursively display this CodeObject
1187    pub fn display_expand_code_objects(&self) -> impl fmt::Display + '_ {
1188        struct Display<'a, C: Constant>(&'a CodeObject<C>);
1189        impl<C: Constant> fmt::Display for Display<'_, C> {
1190            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1191                self.0.display_inner(f, true, 1)
1192            }
1193        }
1194        Display(self)
1195    }
1196
1197    /// Map this CodeObject to one that holds a Bag::Constant
1198    pub fn map_bag<Bag: ConstantBag>(self, bag: Bag) -> CodeObject<Bag::Constant> {
1199        let map_names = |names: Box<[C::Name]>| {
1200            names
1201                .iter()
1202                .map(|x| bag.make_name(x.as_ref()))
1203                .collect::<Box<[_]>>()
1204        };
1205        CodeObject {
1206            constants: self
1207                .constants
1208                .iter()
1209                .map(|x| bag.make_constant(x.borrow_constant()))
1210                .collect(),
1211            names: map_names(self.names),
1212            varnames: map_names(self.varnames),
1213            cellvars: map_names(self.cellvars),
1214            freevars: map_names(self.freevars),
1215            source_path: bag.make_name(self.source_path.as_ref()),
1216            obj_name: bag.make_name(self.obj_name.as_ref()),
1217            qualname: bag.make_name(self.qualname.as_ref()),
1218
1219            instructions: self.instructions,
1220            locations: self.locations,
1221            flags: self.flags,
1222            posonlyarg_count: self.posonlyarg_count,
1223            arg_count: self.arg_count,
1224            kwonlyarg_count: self.kwonlyarg_count,
1225            first_line_number: self.first_line_number,
1226            max_stackdepth: self.max_stackdepth,
1227            localspluskinds: self.localspluskinds,
1228            linetable: self.linetable,
1229            exceptiontable: self.exceptiontable,
1230        }
1231    }
1232
1233    /// Same as `map_bag` but clones `self`
1234    pub fn map_clone_bag<Bag: ConstantBag>(&self, bag: &Bag) -> CodeObject<Bag::Constant> {
1235        let map_names =
1236            |names: &[C::Name]| names.iter().map(|x| bag.make_name(x.as_ref())).collect();
1237        CodeObject {
1238            constants: self
1239                .constants
1240                .iter()
1241                .map(|x| bag.make_constant(x.borrow_constant()))
1242                .collect(),
1243            names: map_names(&self.names),
1244            varnames: map_names(&self.varnames),
1245            cellvars: map_names(&self.cellvars),
1246            freevars: map_names(&self.freevars),
1247            source_path: bag.make_name(self.source_path.as_ref()),
1248            obj_name: bag.make_name(self.obj_name.as_ref()),
1249            qualname: bag.make_name(self.qualname.as_ref()),
1250
1251            instructions: self.instructions.clone(),
1252            locations: self.locations.clone(),
1253            flags: self.flags,
1254            posonlyarg_count: self.posonlyarg_count,
1255            arg_count: self.arg_count,
1256            kwonlyarg_count: self.kwonlyarg_count,
1257            first_line_number: self.first_line_number,
1258            max_stackdepth: self.max_stackdepth,
1259            localspluskinds: self.localspluskinds.clone(),
1260            linetable: self.linetable.clone(),
1261            exceptiontable: self.exceptiontable.clone(),
1262        }
1263    }
1264}
1265
1266impl<C: Constant> fmt::Display for CodeObject<C> {
1267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1268        self.display_inner(f, false, 1)?;
1269        for constant in &*self.constants {
1270            if let BorrowedConstant::Code { code } = constant.borrow_constant() {
1271                writeln!(f, "\nDisassembly of {code:?}")?;
1272                code.fmt(f)?;
1273            }
1274        }
1275        Ok(())
1276    }
1277}
1278
1279pub trait InstrDisplayContext {
1280    type Constant: Constant;
1281
1282    fn get_constant(&self, consti: oparg::ConstIdx) -> &Self::Constant;
1283
1284    fn get_name(&self, i: usize) -> &str;
1285
1286    fn get_varname(&self, var_num: oparg::VarNum) -> &str;
1287
1288    /// Get name for a localsplus index (used by DEREF instructions).
1289    fn get_localsplus_name(&self, var_num: oparg::VarNum) -> &str;
1290}
1291
1292impl<C: Constant> InstrDisplayContext for CodeObject<C> {
1293    type Constant = C;
1294
1295    fn get_constant(&self, consti: oparg::ConstIdx) -> &C {
1296        &self.constants[consti]
1297    }
1298
1299    fn get_name(&self, i: usize) -> &str {
1300        self.names[i].as_ref()
1301    }
1302
1303    fn get_varname(&self, var_num: oparg::VarNum) -> &str {
1304        self.varnames[var_num].as_ref()
1305    }
1306
1307    fn get_localsplus_name(&self, var_num: oparg::VarNum) -> &str {
1308        let idx = var_num.as_usize();
1309        let nlocals = self.varnames.len();
1310        if idx < nlocals {
1311            self.varnames[idx].as_ref()
1312        } else {
1313            let cell_idx = idx - nlocals;
1314            self.cellvars
1315                .get(cell_idx)
1316                .unwrap_or_else(|| &self.freevars[cell_idx - self.cellvars.len()])
1317                .as_ref()
1318        }
1319    }
1320}
1321
1322impl fmt::Display for ConstantData {
1323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1324        self.borrow_constant().fmt_display(f)
1325    }
1326}
1327
1328impl<C: Constant> fmt::Debug for CodeObject<C> {
1329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1330        write!(
1331            f,
1332            "<code object {} at ??? file {:?}, line {}>",
1333            self.obj_name.as_ref(),
1334            self.source_path.as_ref(),
1335            self.first_line_number.map_or(-1, |x| x.get() as i32)
1336        )
1337    }
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342    use super::*;
1343    use alloc::{vec, vec::Vec};
1344
1345    #[test]
1346    fn test_exception_table_encode_decode() {
1347        let entries = vec![
1348            ExceptionTableEntry::new(0, 10, 20, 2, false),
1349            ExceptionTableEntry::new(15, 25, 30, 1, true),
1350        ];
1351
1352        let encoded = encode_exception_table(&entries);
1353
1354        // Find handler at offset 5 (in range [0, 10))
1355        let handler = find_exception_handler(&encoded, 5);
1356        assert!(handler.is_some());
1357        let handler = handler.unwrap();
1358        assert_eq!(handler.start, 0);
1359        assert_eq!(handler.end, 10);
1360        assert_eq!(handler.target, 20);
1361        assert_eq!(handler.depth, 2);
1362        assert!(!handler.push_lasti);
1363
1364        // Find handler at offset 20 (in range [15, 25))
1365        let handler = find_exception_handler(&encoded, 20);
1366        assert!(handler.is_some());
1367        let handler = handler.unwrap();
1368        assert_eq!(handler.start, 15);
1369        assert_eq!(handler.end, 25);
1370        assert_eq!(handler.target, 30);
1371        assert_eq!(handler.depth, 1);
1372        assert!(handler.push_lasti);
1373
1374        // No handler at offset 12 (not in any range)
1375        let handler = find_exception_handler(&encoded, 12);
1376        assert!(handler.is_none());
1377
1378        // No handler at offset 30 (past all ranges)
1379        let handler = find_exception_handler(&encoded, 30);
1380        assert!(handler.is_none());
1381    }
1382
1383    #[test]
1384    fn test_exception_table_empty() {
1385        let entries: Vec<ExceptionTableEntry> = vec![];
1386        let encoded = encode_exception_table(&entries);
1387        assert!(encoded.is_empty());
1388        assert!(find_exception_handler(&encoded, 0).is_none());
1389    }
1390
1391    #[test]
1392    fn test_exception_table_single_entry() {
1393        let entries = vec![ExceptionTableEntry::new(5, 15, 100, 3, true)];
1394        let encoded = encode_exception_table(&entries);
1395
1396        // Inside range
1397        let handler = find_exception_handler(&encoded, 10);
1398        assert!(handler.is_some());
1399        let handler = handler.unwrap();
1400        assert_eq!(handler.target, 100);
1401        assert_eq!(handler.depth, 3);
1402        assert!(handler.push_lasti);
1403
1404        // At start boundary (inclusive)
1405        assert!(find_exception_handler(&encoded, 5).is_some());
1406
1407        // At end boundary (exclusive)
1408        assert!(find_exception_handler(&encoded, 15).is_none());
1409    }
1410}