Skip to main content

rustpython_codegen/
ir.rs

1use core::ops::{Deref, DerefMut, Index, IndexMut};
2
3use crate::{IndexMap, IndexSet, error::InternalError};
4use malachite_bigint::BigInt;
5use num_complex::Complex;
6use num_traits::{ToPrimitive, Zero};
7use rustpython_wtf8::Wtf8Buf;
8
9use rustpython_compiler_core::{
10    OneIndexed, SourceLocation,
11    bytecode::{
12        AnyInstruction, AnyOpcode, CO_FAST_ARG_KW, CO_FAST_ARG_POS, CO_FAST_ARG_VAR, CO_FAST_CELL,
13        CO_FAST_FREE, CO_FAST_HIDDEN, CO_FAST_LOCAL, CodeFlags, CodeObject, CodeUnit, CodeUnits,
14        ConstantData, InstrDisplayContext, Instruction, IntrinsicFunction1, OpArg, OpArgByte,
15        Opcode, PseudoInstruction, PseudoOpcode, PyCodeLocationInfoKind, oparg,
16    },
17    varint::{write_signed_varint, write_varint},
18};
19
20/// Location info for linetable generation (allows line 0 for RESUME)
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22struct LineTableLocation {
23    line: i32,
24    end_line: i32,
25    col: i32,
26    end_col: i32,
27}
28
29#[derive(Clone, Copy)]
30struct InstructionLocation {
31    location: SourceLocation,
32    end_location: SourceLocation,
33    lineno_override: Option<i32>,
34}
35
36pub(crate) const LINE_ONLY_LOCATION_OVERRIDE: i32 = -4;
37pub(crate) const NEXT_LOCATION_OVERRIDE: i32 = -2;
38pub(crate) const NO_LOCATION_OVERRIDE: i32 = -1;
39
40const MAX_INT_SIZE: u64 = 128;
41const MAX_COLLECTION_SIZE: usize = 256;
42const DEFAULT_CODE_SIZE: usize = 128;
43const DEFAULT_LNOTAB_SIZE: usize = 16;
44const DEFAULT_CNOTAB_SIZE: usize = 32;
45const DEFAULT_BLOCK_SIZE: usize = 16;
46const INITIAL_INSTR_SEQUENCE_SIZE: usize = 100;
47const INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE: usize = 10;
48const MAX_REAL_OPCODE: u16 = 254;
49const MAX_OPCODE: u16 = 511;
50const MAX_TOTAL_ITEMS: isize = 1024;
51const MAX_STR_SIZE: usize = 4096;
52const MIN_CONST_SEQUENCE_SIZE: usize = 3;
53const STACK_USE_GUIDELINE: usize = 30;
54
55/// pycore_opcode_utils.h IS_WITHIN_OPCODE_RANGE
56fn is_within_opcode_range(opcode: AnyOpcode) -> bool {
57    match opcode {
58        AnyOpcode::Real(opcode) => u16::from(opcode.as_u8()) <= MAX_REAL_OPCODE,
59        AnyOpcode::Pseudo(opcode) => opcode.as_u16() <= MAX_OPCODE,
60    }
61}
62
63#[derive(Clone, Debug, Default)]
64pub struct ConstantPool {
65    constants: Vec<ConstantData>,
66}
67
68impl ConstantPool {
69    fn constant_contains_nan(constant: &ConstantData) -> bool {
70        match constant {
71            ConstantData::Float { value } => value.is_nan(),
72            ConstantData::Complex { value } => value.re.is_nan() || value.im.is_nan(),
73            ConstantData::Tuple { elements } | ConstantData::Frozenset { elements } => {
74                elements.iter().any(Self::constant_contains_nan)
75            }
76            ConstantData::Slice { elements } => elements.iter().any(Self::constant_contains_nan),
77            _ => false,
78        }
79    }
80
81    fn frozenset_key_contains(elements: &[ConstantData], needle: &ConstantData) -> bool {
82        if Self::constant_contains_nan(needle) {
83            return false;
84        }
85        elements.iter().any(|element| {
86            !Self::constant_contains_nan(element) && Self::constant_key_eq(element, needle)
87        })
88    }
89
90    fn frozenset_key_eq(left: &[ConstantData], right: &[ConstantData]) -> bool {
91        left.iter()
92            .all(|element| Self::frozenset_key_contains(right, element))
93            && right
94                .iter()
95                .all(|element| Self::frozenset_key_contains(left, element))
96    }
97
98    fn constant_key_eq(left: &ConstantData, right: &ConstantData) -> bool {
99        match (left, right) {
100            (ConstantData::Tuple { elements: left }, ConstantData::Tuple { elements: right }) => {
101                left.len() == right.len()
102                    && left
103                        .iter()
104                        .zip(right.iter())
105                        .all(|(left, right)| Self::constant_key_eq(left, right))
106            }
107            (
108                ConstantData::Frozenset { elements: left },
109                ConstantData::Frozenset { elements: right },
110            ) => Self::frozenset_key_eq(left, right),
111            (ConstantData::Slice { elements: left }, ConstantData::Slice { elements: right }) => {
112                left.iter()
113                    .zip(right.iter())
114                    .all(|(left, right)| Self::constant_key_eq(left, right))
115            }
116            _ => left == right,
117        }
118    }
119
120    fn canonicalize_constant_key(constant: ConstantData) -> crate::InternalResult<ConstantData> {
121        match constant {
122            ConstantData::Tuple { elements } => {
123                let mut canonical = Vec::new();
124                canonical
125                    .try_reserve_exact(elements.len())
126                    .map_err(|_| InternalError::MalformedControlFlowGraph)?;
127                for element in elements {
128                    canonical.push(Self::canonicalize_constant_key(element)?);
129                }
130                Ok(ConstantData::Tuple {
131                    elements: canonical,
132                })
133            }
134            ConstantData::Slice { elements } => {
135                let [start, stop, step] = *elements;
136                Ok(ConstantData::Slice {
137                    elements: Box::new([
138                        Self::canonicalize_constant_key(start)?,
139                        Self::canonicalize_constant_key(stop)?,
140                        Self::canonicalize_constant_key(step)?,
141                    ]),
142                })
143            }
144            ConstantData::Frozenset { elements } => {
145                let mut canonical = Vec::new();
146                canonical
147                    .try_reserve_exact(elements.len())
148                    .map_err(|_| InternalError::MalformedControlFlowGraph)?;
149                for element in elements {
150                    let element = Self::canonicalize_constant_key(element)?;
151                    if !Self::frozenset_key_contains(&canonical, &element) {
152                        canonical.push(element);
153                    }
154                }
155                Ok(ConstantData::Frozenset {
156                    elements: canonical,
157                })
158            }
159            other => Ok(other),
160        }
161    }
162
163    fn canonicalize_constant_key_infallible(constant: ConstantData) -> ConstantData {
164        Self::canonicalize_constant_key(constant)
165            .expect("constant key canonicalization only fails on allocation error")
166    }
167
168    /// Index of an already-stored constant equal to `constant`, if any.
169    /// _PyCode_ConstantKey() keeps NaN-bearing constants distinct because
170    /// Python-level NaN keys do not compare equal.
171    fn find_existing(&self, constant: &ConstantData) -> Option<usize> {
172        if Self::constant_contains_nan(constant) {
173            return None;
174        }
175        self.constants
176            .iter()
177            .position(|existing| Self::constant_key_eq(existing, constant))
178    }
179
180    pub fn insert_full(&mut self, constant: ConstantData) -> (usize, bool) {
181        let constant = Self::canonicalize_constant_key_infallible(constant);
182        if let Some(idx) = self.find_existing(&constant) {
183            return (idx, false);
184        }
185        let idx = self.constants.len();
186        self.constants.push(constant);
187        (idx, true)
188    }
189
190    fn try_insert_full(&mut self, constant: ConstantData) -> crate::InternalResult<(usize, bool)> {
191        let constant = Self::canonicalize_constant_key(constant)?;
192        if let Some(idx) = self.find_existing(&constant) {
193            return Ok((idx, false));
194        }
195        self.constants
196            .try_reserve_exact(1)
197            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
198        let idx = self.constants.len();
199        self.constants.push(constant);
200        Ok((idx, true))
201    }
202
203    pub fn insert(&mut self, constant: ConstantData) -> bool {
204        self.insert_full(constant).1
205    }
206
207    #[must_use]
208    pub fn from_ordered(constants: Vec<ConstantData>) -> Self {
209        Self { constants }
210    }
211
212    #[must_use]
213    pub fn into_vec(self) -> Vec<ConstantData> {
214        self.constants
215    }
216
217    #[must_use]
218    pub fn get_index(&self, idx: usize) -> Option<&ConstantData> {
219        self.constants.get(idx)
220    }
221
222    pub fn iter(&self) -> core::slice::Iter<'_, ConstantData> {
223        self.constants.iter()
224    }
225
226    #[must_use]
227    pub fn len(&self) -> usize {
228        self.constants.len()
229    }
230
231    #[must_use]
232    pub fn is_empty(&self) -> bool {
233        self.constants.is_empty()
234    }
235
236    pub fn clear(&mut self) {
237        self.constants.clear();
238    }
239}
240
241impl Index<usize> for ConstantPool {
242    type Output = ConstantData;
243
244    fn index(&self, idx: usize) -> &Self::Output {
245        &self.constants[idx]
246    }
247}
248
249impl IntoIterator for ConstantPool {
250    type Item = ConstantData;
251    type IntoIter = alloc::vec::IntoIter<ConstantData>;
252
253    fn into_iter(self) -> Self::IntoIter {
254        self.constants.into_iter()
255    }
256}
257
258/// Metadata for a code unit
259// = _PyCompile_CodeUnitMetadata
260#[derive(Clone, Debug)]
261pub struct CodeUnitMetadata {
262    pub name: String,                        // u_name (obj_name)
263    pub qualname: Option<String>,            // u_qualname
264    pub consts: ConstantPool,                // u_consts
265    pub names: IndexSet<String>,             // u_names
266    pub varnames: IndexSet<String>,          // u_varnames
267    pub cellvars: IndexSet<String>,          // u_cellvars
268    pub freevars: IndexSet<String>,          // u_freevars
269    pub fast_hidden: IndexMap<String, bool>, // u_fast_hidden
270    pub fast_hidden_final: IndexSet<String>, // final CO_FAST_HIDDEN names
271    pub argcount: u32,                       // u_argcount
272    pub posonlyargcount: u32,                // u_posonlyargcount
273    pub kwonlyargcount: u32,                 // u_kwonlyargcount
274    pub firstlineno: OneIndexed,             // u_firstlineno
275}
276// use rustpython_parser_core::source_code::{LineNumber, SourceLocation};
277
278#[derive(Copy, Clone, PartialEq, Eq, Debug)]
279pub struct BlockIdx(u32);
280
281impl BlockIdx {
282    pub const NULL: Self = Self::new(u32::MAX);
283
284    /// Creates a new instance of [`BlockIdx`] from a [`u32`].
285    #[must_use]
286    pub const fn new(value: u32) -> Self {
287        Self(value)
288    }
289
290    /// Returns the inner [`u32`] value.
291    #[must_use]
292    pub const fn as_u32(self) -> u32 {
293        self.0
294    }
295
296    /// Returns the inner value as a [`usize`].
297    #[must_use]
298    pub const fn as_usize(self) -> usize {
299        self.0 as usize
300    }
301
302    /// Returns the inner value as a [`usize`].
303    #[must_use]
304    pub const fn idx(self) -> usize {
305        self.as_usize()
306    }
307}
308
309impl From<BlockIdx> for u32 {
310    fn from(block_idx: BlockIdx) -> Self {
311        block_idx.as_u32()
312    }
313}
314
315impl From<BlockIdx> for usize {
316    fn from(block_idx: BlockIdx) -> Self {
317        block_idx.as_usize()
318    }
319}
320
321#[derive(Clone, Copy, Debug)]
322pub struct InstructionInfo {
323    pub instr: AnyInstruction,
324    pub arg: OpArg,
325    pub target: BlockIdx,
326    pub location: SourceLocation,
327    pub end_location: SourceLocation,
328    pub except_handler: Option<ExceptHandlerInfo>,
329    /// Override line number for linetable (e.g., line 0 for module RESUME)
330    pub lineno_override: Option<i32>,
331}
332
333impl InstructionInfo {
334    /// flowgraph.c INSTR_SET_OP0
335    fn instr_set_op0(&mut self, instr: AnyInstruction) {
336        debug_assert!(!AnyOpcode::from(instr).has_arg());
337        self.instr = instr;
338        self.arg = OpArg::new(0);
339    }
340
341    /// flowgraph.c INSTR_SET_OP1
342    fn instr_set_op1(&mut self, instr: AnyInstruction, arg: OpArg) {
343        debug_assert!(AnyOpcode::from(instr).has_arg());
344        self.instr = instr;
345        self.arg = arg;
346    }
347
348    /// flowgraph.c INSTR_SET_LOC
349    fn instr_set_loc(
350        &mut self,
351        location: SourceLocation,
352        end_location: SourceLocation,
353        lineno_override: Option<i32>,
354    ) {
355        self.location = location;
356        self.end_location = end_location;
357        self.lineno_override = lineno_override;
358    }
359
360    fn instr_location(&self) -> InstructionLocation {
361        InstructionLocation {
362            location: self.location,
363            end_location: self.end_location,
364            lineno_override: self.lineno_override,
365        }
366    }
367
368    fn instr_set_location(&mut self, loc: InstructionLocation) {
369        self.instr_set_loc(loc.location, loc.end_location, loc.lineno_override);
370    }
371
372    fn set_to_nop(&mut self) {
373        self.instr_set_op0(Instruction::Nop.into());
374    }
375
376    fn nop_out_no_location(&mut self) {
377        self.set_to_nop();
378        self.instr_set_loc(
379            SourceLocation::default(),
380            SourceLocation::default(),
381            Some(NO_LOCATION_OVERRIDE),
382        );
383    }
384
385    #[must_use]
386    fn empty() -> Self {
387        Self {
388            instr: Instruction::Nop.into(),
389            arg: OpArg::new(0),
390            target: BlockIdx::NULL,
391            location: SourceLocation::default(),
392            end_location: SourceLocation::default(),
393            except_handler: None,
394            lineno_override: None,
395        }
396    }
397
398    /// instruction_sequence.c _PyInstructionSequence_Addop asserts.
399    fn instruction_sequence_debug_check_addop(&self) {
400        let opcode = AnyOpcode::from(self.instr);
401        debug_assert!(is_within_opcode_range(opcode));
402        debug_assert!(
403            opcode.has_arg() || self.instr.has_target() || u32::from(self.arg) == 0,
404            "CPython _PyInstructionSequence_Addop requires either OPCODE_HAS_ARG, HAS_TARGET, or oparg == 0"
405        );
406        debug_assert!(
407            u32::from(self.arg) < (1 << 30),
408            "CPython _PyInstructionSequence_Addop requires 0 <= oparg < (1 << 30)"
409        );
410    }
411
412    /// assemble.c instr_size
413    fn instr_size(&self) -> usize {
414        let opcode = self.instr.expect_real();
415        let oparg = u32::from(self.arg) as i32;
416        debug_assert!(
417            self.instr.has_arg() || oparg == 0,
418            "CPython assemble.c instr_size requires OPCODE_HAS_ARG or oparg == 0"
419        );
420        let extended_args =
421            (0xFF_FFFF < oparg) as usize + (0xFF_FF < oparg) as usize + (0xFF < oparg) as usize;
422        let caches = opcode.cache_entries();
423        extended_args + 1 + caches
424    }
425
426    fn instruction_linetable_location(&self) -> LineTableLocation {
427        match self.lineno_override {
428            Some(NO_LOCATION_OVERRIDE) => LineTableLocation {
429                line: NO_LOCATION_OVERRIDE,
430                end_line: NO_LOCATION_OVERRIDE,
431                col: NO_LOCATION_OVERRIDE,
432                end_col: NO_LOCATION_OVERRIDE,
433            },
434            Some(LINE_ONLY_LOCATION_OVERRIDE) => LineTableLocation {
435                line: self.location.line.get() as i32,
436                end_line: self.end_location.line.get() as i32,
437                col: -1,
438                end_col: -1,
439            },
440            Some(NEXT_LOCATION_OVERRIDE) => next_linetable_location(),
441            Some(lineno) => LineTableLocation {
442                line: lineno,
443                end_line: self.end_location.line.get() as i32,
444                col: self.location.character_offset.to_zero_indexed() as i32,
445                end_col: self.end_location.character_offset.to_zero_indexed() as i32,
446            },
447            None => LineTableLocation {
448                line: self.location.line.get() as i32,
449                end_line: self.end_location.line.get() as i32,
450                col: self.location.character_offset.to_zero_indexed() as i32,
451                end_col: self.end_location.character_offset.to_zero_indexed() as i32,
452            },
453        }
454    }
455
456    /// flowgraph.c loads_const
457    const fn loads_const(&self) -> bool {
458        self.instr.has_const() || matches!(self.instr.real_opcode(), Some(Opcode::LoadSmallInt))
459    }
460
461    /// flowgraph.c STORES_TO
462    fn stores_to(&self) -> i32 {
463        match self.instr.into() {
464            AnyOpcode::Real(Opcode::StoreFast)
465            | AnyOpcode::Pseudo(PseudoOpcode::StoreFastMaybeNull) => u32::from(self.arg) as i32,
466            _ => -1,
467        }
468    }
469
470    /// flowgraph.c maybe_instr_make_load_smallint
471    fn maybe_instr_make_load_smallint(&mut self, constant: &ConstantData) -> bool {
472        if let ConstantData::Integer { value } = constant
473            && let Some(small) = value.to_i32().filter(|v| (0..=255).contains(v))
474        {
475            self.instr_set_op1(Opcode::LoadSmallInt.into(), OpArg::new(small as u32));
476            return true;
477        }
478        false
479    }
480
481    /// flowgraph.c make_super_instruction
482    fn make_super_instruction(inst1: &mut Self, inst2: &mut Self, super_op: AnyInstruction) {
483        let line1 = inst1.instruction_lineno();
484        let line2 = inst2.instruction_lineno();
485        if line1 >= 0 && line2 >= 0 && line1 != line2 {
486            return;
487        }
488        let arg1 = u32::from(inst1.arg);
489        let arg2 = u32::from(inst2.arg);
490        if arg1 >= 16 || arg2 >= 16 {
491            return;
492        }
493        inst1.instr_set_op1(super_op, OpArg::new((arg1 << 4) | arg2));
494        inst2.set_to_nop();
495    }
496
497    fn instruction_lineno(&self) -> i32 {
498        match self.lineno_override {
499            Some(LINE_ONLY_LOCATION_OVERRIDE) | None => self.location.line.get() as i32,
500            Some(lineno) => lineno,
501        }
502    }
503
504    fn instruction_is_no_location(&self) -> bool {
505        self.instruction_lineno() == NO_LOCATION_OVERRIDE
506    }
507
508    /// flowgraph.c is_jump
509    fn is_jump(&self) -> bool {
510        self.instr.has_jump()
511    }
512
513    /// flowgraph.c is_block_push
514    fn is_block_push(&self) -> bool {
515        self.instr.is_block_push()
516    }
517}
518
519/// Exception handler information for an instruction.
520#[derive(Clone, Copy, Debug, PartialEq, Eq)]
521pub struct ExceptHandlerInfo {
522    /// Block to jump to when exception occurs
523    pub handler_block: BlockIdx,
524    /// Whether to push lasti before exception
525    pub preserve_lasti: bool,
526}
527
528fn no_instruction_location() -> InstructionLocation {
529    InstructionLocation {
530        location: SourceLocation::default(),
531        end_location: SourceLocation::default(),
532        lineno_override: Some(NO_LOCATION_OVERRIDE),
533    }
534}
535
536/// codegen.c _Py_CArray_EnsureCapacity
537fn c_array_ensure_capacity<T>(
538    allocated_entries: usize,
539    idx: usize,
540    initial_num_entries: usize,
541) -> crate::InternalResult<usize> {
542    if allocated_entries == 0 {
543        let new_alloc = if idx >= initial_num_entries {
544            idx.checked_add(initial_num_entries)
545                .ok_or(InternalError::MalformedControlFlowGraph)?
546        } else {
547            initial_num_entries
548        };
549        Ok(new_alloc)
550    } else if idx >= allocated_entries {
551        let oldsize = allocated_entries
552            .checked_mul(core::mem::size_of::<T>())
553            .ok_or(InternalError::MalformedControlFlowGraph)?;
554        let doubled = allocated_entries
555            .checked_mul(2)
556            .ok_or(InternalError::MalformedControlFlowGraph)?;
557        let new_alloc = if idx >= doubled {
558            idx.checked_add(initial_num_entries)
559                .ok_or(InternalError::MalformedControlFlowGraph)?
560        } else {
561            doubled
562        };
563        let newsize = new_alloc
564            .checked_mul(core::mem::size_of::<T>())
565            .ok_or(InternalError::MalformedControlFlowGraph)?;
566        if oldsize > usize::MAX >> 1 || newsize == 0 {
567            return Err(InternalError::MalformedControlFlowGraph);
568        }
569        Ok(new_alloc)
570    } else {
571        Ok(allocated_entries)
572    }
573}
574
575#[derive(Clone, Copy, Debug, Eq, PartialEq)]
576pub(crate) struct InstructionSequenceLabel(i32);
577
578/// flowgraph.c SAME_LABEL
579fn same_label(a: InstructionSequenceLabel, b: InstructionSequenceLabel) -> bool {
580    a == b
581}
582
583/// flowgraph.c IS_LABEL
584fn is_label(label: InstructionSequenceLabel) -> bool {
585    !same_label(label, InstructionSequenceLabel::NO_LABEL)
586}
587
588impl InstructionSequenceLabel {
589    pub(crate) const NO_LABEL: Self = Self(-1);
590
591    pub(crate) fn from_index(index: i32) -> Self {
592        Self(index)
593    }
594
595    pub(crate) fn is_jump_target_label(self) -> bool {
596        is_label(self)
597    }
598
599    pub(crate) fn idx(self) -> usize {
600        debug_assert!(self.0 >= 0);
601        self.0 as usize
602    }
603}
604
605#[derive(Clone, Copy)]
606struct InstructionSequenceExceptHandlerInfo {
607    h_label: i32,
608    start_depth: i32,
609    preserve_lasti: i32,
610}
611
612const NO_EXCEPTION_HANDLER_LABEL: i32 = -1;
613const ZERO_EXCEPTION_HANDLER_INFO: InstructionSequenceExceptHandlerInfo =
614    InstructionSequenceExceptHandlerInfo {
615        h_label: 0,
616        start_depth: 0,
617        preserve_lasti: 0,
618    };
619
620#[derive(Clone, Copy)]
621struct InstructionSequenceEntry {
622    info: InstructionInfo,
623    except_handler: InstructionSequenceExceptHandlerInfo,
624    i_target: i32,
625    i_offset: i32,
626    python_loc: Option<[i32; 4]>,
627}
628
629impl InstructionSequenceEntry {
630    fn new(info: InstructionInfo, except_handler: InstructionSequenceExceptHandlerInfo) -> Self {
631        Self {
632            info,
633            except_handler,
634            i_target: 0,
635            i_offset: 0,
636            python_loc: None,
637        }
638    }
639}
640
641const INSTRUCTION_SEQUENCE_UNSET_LABEL: i32 = -111;
642
643#[derive(Clone, Default)]
644pub struct InstructionSequence {
645    /// CPython `instr_sequence.s_instrs`, including allocated slots beyond `s_used`.
646    instrs: Vec<InstructionSequenceEntry>,
647    /// CPython `instr_sequence.s_allocated`, the allocated size of `s_instrs`.
648    instr_allocation: usize,
649    /// CPython `instr_sequence.s_used`, the number of used entries in `s_instrs`.
650    instr_used: usize,
651    /// CPython `instr_sequence.s_next_free_label`.
652    next_free_label: i32,
653    label_map: Option<Vec<i32>>,
654    label_map_allocation: usize,
655    annotations_code: Option<Box<Self>>,
656    nested: Vec<Self>,
657}
658
659/// One instruction as returned by InstructionSequence.get_instructions.
660#[derive(Clone, Copy, Debug)]
661pub struct PythonInstruction {
662    pub opcode: i32,
663    pub oparg: Option<i32>,
664    pub lineno: i32,
665    pub end_lineno: i32,
666    pub col_offset: i32,
667    pub end_col_offset: i32,
668}
669
670impl InstructionSequence {
671    #[must_use]
672    pub fn new() -> Self {
673        instruction_sequence_new()
674    }
675
676    pub fn addop(
677        &mut self,
678        opcode: i32,
679        oparg: i32,
680        lineno: i32,
681        col_offset: i32,
682        end_lineno: i32,
683        end_col_offset: i32,
684    ) -> crate::InternalResult<()> {
685        let opcode = u16::try_from(opcode).map_err(|_| InternalError::MalformedControlFlowGraph)?;
686        if opcode > MAX_OPCODE {
687            return Err(InternalError::MalformedControlFlowGraph);
688        }
689        let opcode =
690            AnyOpcode::try_from(opcode).map_err(|_| InternalError::MalformedControlFlowGraph)?;
691        let instr: AnyInstruction = opcode.into();
692        let oparg = u32::try_from(oparg).map_err(|_| InternalError::MalformedControlFlowGraph)?;
693        if oparg >= (1 << 30) || !(opcode.has_arg() || instr.has_target() || oparg == 0) {
694            return Err(InternalError::MalformedControlFlowGraph);
695        }
696        let loc = [lineno, col_offset, end_lineno, end_col_offset];
697        let entry =
698            instruction_sequence_addop(self, instruction_info_from_python(instr, oparg, loc))?;
699        // addop arguments are (lineno, col_offset, end_lineno, end_col_offset);
700        // get_instructions reports (lineno, end_lineno, col_offset, end_col_offset)
701        // after storing those four values in struct order.
702        entry.python_loc = Some([lineno, col_offset, end_lineno, end_col_offset]);
703        Ok(())
704    }
705
706    pub fn new_label(&mut self) -> i32 {
707        instruction_sequence_new_label(self).0
708    }
709
710    pub fn use_label(&mut self, label: i32) -> crate::InternalResult<()> {
711        instruction_sequence_use_label(self, InstructionSequenceLabel(label))
712    }
713
714    pub fn apply_label_map(&mut self) {
715        instruction_sequence_apply_label_map(self);
716    }
717
718    pub fn add_nested(&mut self, nested: Self) {
719        self.nested.push(nested);
720    }
721
722    #[must_use]
723    pub fn nested(&self) -> &[Self] {
724        &self.nested
725    }
726
727    pub fn nested_mut(&mut self) -> &mut Vec<Self> {
728        &mut self.nested
729    }
730
731    #[must_use]
732    pub fn python_instructions(&self) -> Vec<PythonInstruction> {
733        let mut out = Vec::with_capacity(self.instr_used);
734        for entry in &self.instrs[..self.instr_used] {
735            let opcode = any_opcode_as_i32(entry.info.instr.into());
736            let has_arg = AnyOpcode::from(entry.info.instr).has_arg();
737            let [lineno, end_lineno, col_offset, end_col_offset] = entry
738                .python_loc
739                .unwrap_or_else(|| python_location_of(&entry.info));
740            out.push(PythonInstruction {
741                opcode,
742                oparg: has_arg.then_some(u32::from(entry.info.arg) as i32),
743                lineno,
744                end_lineno,
745                col_offset,
746                end_col_offset,
747            });
748        }
749        out
750    }
751
752    fn check_load_const_indices(&self, nconsts: usize) -> crate::InternalResult<()> {
753        for entry in &self.instrs[..self.instr_used] {
754            if matches!(entry.info.instr.real(), Some(Instruction::LoadConst { .. })) {
755                let index = u32::from(entry.info.arg) as usize;
756                if index >= nconsts {
757                    return Err(InternalError::ConstIndexOutOfRange {
758                        index,
759                        len: nconsts,
760                    });
761                }
762            }
763        }
764        Ok(())
765    }
766}
767
768/// instruction_sequence.c _PyInstructionSequence_New / inst_seq_create
769fn instruction_sequence_new() -> InstructionSequence {
770    InstructionSequence {
771        instrs: Vec::new(),
772        instr_allocation: 0,
773        instr_used: 0,
774        next_free_label: 0,
775        label_map: None,
776        label_map_allocation: 0,
777        annotations_code: None,
778        nested: Vec::new(),
779    }
780}
781
782fn any_opcode_as_i32(opcode: AnyOpcode) -> i32 {
783    match opcode {
784        AnyOpcode::Real(op) => i32::from(u8::from(op)),
785        AnyOpcode::Pseudo(op) => i32::from(u16::from(op)),
786    }
787}
788
789fn python_location_of(info: &InstructionInfo) -> [i32; 4] {
790    let loc = info.instruction_linetable_location();
791    [loc.line, loc.end_line, loc.col, loc.end_col]
792}
793
794fn instruction_info_from_python(
795    instr: AnyInstruction,
796    oparg: u32,
797    loc: [i32; 4],
798) -> InstructionInfo {
799    let [lineno, col_offset, end_lineno, end_col_offset] = loc;
800    let lineno_override = match lineno.cmp(&0) {
801        core::cmp::Ordering::Less => Some(NO_LOCATION_OVERRIDE),
802        core::cmp::Ordering::Equal => Some(0),
803        core::cmp::Ordering::Greater => None,
804    };
805    let line = if lineno > 0 {
806        OneIndexed::new(lineno as usize).unwrap_or(OneIndexed::MIN)
807    } else {
808        OneIndexed::MIN
809    };
810    let end_line = if end_lineno > 0 {
811        OneIndexed::new(end_lineno as usize).unwrap_or(line)
812    } else {
813        line
814    };
815    let col = if col_offset >= 0 {
816        OneIndexed::from_zero_indexed(col_offset as usize)
817    } else {
818        OneIndexed::MIN
819    };
820    let end_col = if end_col_offset >= 0 {
821        OneIndexed::from_zero_indexed(end_col_offset as usize)
822    } else {
823        OneIndexed::MIN
824    };
825    InstructionInfo {
826        instr,
827        arg: OpArg::new(oparg),
828        target: BlockIdx::NULL,
829        location: SourceLocation {
830            line,
831            character_offset: col,
832        },
833        end_location: SourceLocation {
834            line: end_line,
835            character_offset: end_col,
836        },
837        except_handler: None,
838        lineno_override,
839    }
840}
841
842/// instruction_sequence.c instr_sequence_next_inst
843fn instruction_sequence_next_inst(seq: &mut InstructionSequence) -> crate::InternalResult<usize> {
844    debug_assert!(!seq.instrs.is_empty() || seq.instr_used == 0);
845    let idx = seq.instr_used;
846    let new_allocation = c_array_ensure_capacity::<InstructionSequenceEntry>(
847        seq.instr_allocation,
848        idx + 1,
849        INITIAL_INSTR_SEQUENCE_SIZE,
850    )?;
851    if new_allocation > seq.instr_allocation {
852        if new_allocation > seq.instrs.capacity() {
853            seq.instrs
854                .try_reserve_exact(new_allocation - seq.instrs.capacity())
855                .map_err(|_| InternalError::MalformedControlFlowGraph)?;
856        }
857        if new_allocation > seq.instrs.len() {
858            seq.instrs.resize(
859                new_allocation,
860                InstructionSequenceEntry::new(
861                    InstructionInfo {
862                        instr: Instruction::Cache.into(),
863                        arg: OpArg::new(0),
864                        target: BlockIdx::NULL,
865                        location: SourceLocation::default(),
866                        end_location: SourceLocation::default(),
867                        except_handler: None,
868                        lineno_override: None,
869                    },
870                    ZERO_EXCEPTION_HANDLER_INFO,
871                ),
872            );
873        }
874        seq.instr_allocation = new_allocation;
875    }
876    debug_assert!(seq.instr_allocation > idx);
877    seq.instr_used += 1;
878    Ok(idx)
879}
880
881/// instruction_sequence.c _PyInstructionSequence_NewLabel
882fn instruction_sequence_new_label(seq: &mut InstructionSequence) -> InstructionSequenceLabel {
883    seq.next_free_label += 1;
884    InstructionSequenceLabel(seq.next_free_label)
885}
886
887/// instruction_sequence.c _PyInstructionSequence_SetAnnotationsCode
888fn instruction_sequence_set_annotations_code(
889    seq: &mut InstructionSequence,
890    annotations_code: Option<Box<InstructionSequence>>,
891) {
892    debug_assert!(seq.annotations_code.is_none());
893    seq.annotations_code = annotations_code;
894}
895
896/// instruction_sequence.c _PyInstructionSequence_UseLabel
897fn instruction_sequence_use_label(
898    seq: &mut InstructionSequence,
899    label: InstructionSequenceLabel,
900) -> crate::InternalResult<()> {
901    let old_size = seq.label_map_allocation;
902    let new_allocation = c_array_ensure_capacity::<i32>(
903        seq.label_map_allocation,
904        label.idx(),
905        INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE,
906    )?;
907    if new_allocation > seq.label_map_allocation {
908        if let Some(label_map) = &mut seq.label_map {
909            if new_allocation > label_map.capacity() {
910                label_map
911                    .try_reserve_exact(new_allocation - label_map.capacity())
912                    .map_err(|_| InternalError::MalformedControlFlowGraph)?;
913            }
914        } else {
915            let mut label_map = Vec::new();
916            label_map
917                .try_reserve_exact(new_allocation)
918                .map_err(|_| InternalError::MalformedControlFlowGraph)?;
919            seq.label_map = Some(label_map);
920        }
921        seq.label_map_allocation = new_allocation;
922    }
923    let label_map = seq
924        .label_map
925        .as_mut()
926        .ok_or(InternalError::MalformedControlFlowGraph)?;
927    if label_map.len() < seq.label_map_allocation {
928        label_map.resize(seq.label_map_allocation, INSTRUCTION_SEQUENCE_UNSET_LABEL);
929    }
930
931    label_map[old_size..seq.label_map_allocation].fill(INSTRUCTION_SEQUENCE_UNSET_LABEL);
932    label_map[label.idx()] = seq.instr_used as i32;
933    Ok(())
934}
935
936/// instruction_sequence.c _PyInstructionSequence_Addop
937fn instruction_sequence_addop(
938    seq: &mut InstructionSequence,
939    info: InstructionInfo,
940) -> crate::InternalResult<&mut InstructionSequenceEntry> {
941    info.instruction_sequence_debug_check_addop();
942    let idx = instruction_sequence_next_inst(seq)?;
943    let entry = &mut seq.instrs[idx];
944    entry.info = info;
945    Ok(entry)
946}
947
948fn instruction_sequence_last_info_mut(
949    seq: &mut InstructionSequence,
950) -> Option<&mut InstructionInfo> {
951    if seq.instr_used == 0 {
952        None
953    } else {
954        Some(&mut seq.instrs[seq.instr_used - 1].info)
955    }
956}
957
958/// instruction_sequence.c _PyInstructionSequence_InsertInstruction
959fn instruction_sequence_insert_instruction(
960    seq: &mut InstructionSequence,
961    pos: usize,
962    info: InstructionInfo,
963) -> crate::InternalResult<()> {
964    debug_assert!(pos <= seq.instr_used);
965    let last_idx = instruction_sequence_next_inst(seq)?;
966    for i in (pos..last_idx).rev() {
967        seq.instrs[i + 1] = seq.instrs[i];
968    }
969
970    seq.instrs[pos].info = info;
971    if let Some(label_map) = &mut seq.label_map {
972        let pos = pos as i32;
973
974        for lbl in label_map.iter_mut().take(seq.label_map_allocation) {
975            if *lbl >= pos {
976                *lbl += 1;
977            }
978        }
979    }
980
981    Ok(())
982}
983
984/// instruction_sequence.c _PyInstructionSequence_ApplyLabelMap
985fn instruction_sequence_apply_label_map(instrs: &mut InstructionSequence) {
986    {
987        let Some(label_map) = instrs.label_map.as_ref() else {
988            return;
989        };
990
991        for i in 0..instrs.instr_used {
992            let entry = &mut instrs.instrs[i];
993            if entry.info.instr.has_target() {
994                let label = u32::from(entry.info.arg) as usize;
995                debug_assert!(label < instrs.label_map_allocation);
996                let target = label_map[label];
997                debug_assert!(target >= 0);
998                entry.info.arg = OpArg::new(target as u32);
999            }
1000            let handler = &mut entry.except_handler;
1001            if handler.h_label >= 0 {
1002                let label = handler.h_label as usize;
1003                debug_assert!(label < instrs.label_map_allocation);
1004                handler.h_label = label_map[label];
1005            }
1006        }
1007    }
1008
1009    instrs.label_map = None;
1010    instrs.label_map_allocation = 0;
1011}
1012
1013/// pycore_opcode_metadata.h is_pseudo_target
1014const fn is_pseudo_target(pseudo: PseudoOpcode, target: Opcode) -> bool {
1015    match pseudo {
1016        PseudoOpcode::LoadClosure => matches!(target, Opcode::LoadFast),
1017        PseudoOpcode::StoreFastMaybeNull => matches!(target, Opcode::StoreFast),
1018        PseudoOpcode::AnnotationsPlaceholder
1019        | PseudoOpcode::SetupFinally
1020        | PseudoOpcode::SetupCleanup
1021        | PseudoOpcode::SetupWith
1022        | PseudoOpcode::PopBlock => matches!(target, Opcode::Nop),
1023        PseudoOpcode::Jump => matches!(target, Opcode::JumpForward | Opcode::JumpBackward),
1024        PseudoOpcode::JumpNoInterrupt => {
1025            matches!(
1026                target,
1027                Opcode::JumpForward | Opcode::JumpBackwardNoInterrupt
1028            )
1029        }
1030        PseudoOpcode::JumpIfFalse => {
1031            matches!(
1032                target,
1033                Opcode::Copy | Opcode::ToBool | Opcode::PopJumpIfFalse
1034            )
1035        }
1036        PseudoOpcode::JumpIfTrue => {
1037            matches!(
1038                target,
1039                Opcode::Copy | Opcode::ToBool | Opcode::PopJumpIfTrue
1040            )
1041        }
1042    }
1043}
1044/// assemble.c resolve_unconditional_jumps
1045fn resolve_unconditional_jumps(instr_sequence: &mut InstructionSequence) {
1046    for i in 0..instr_sequence.instr_used {
1047        let instr = &mut instr_sequence.instrs[i].info;
1048        let is_forward = (u32::from(instr.arg) as i32) > i as i32;
1049        match instr.instr {
1050            AnyInstruction::Pseudo(PseudoInstruction::Jump { .. }) => {
1051                debug_assert!(is_pseudo_target(PseudoOpcode::Jump, Opcode::JumpForward));
1052                debug_assert!(is_pseudo_target(PseudoOpcode::Jump, Opcode::JumpBackward));
1053
1054                if is_forward {
1055                    instr.instr = Opcode::JumpForward.into();
1056                } else {
1057                    instr.instr = Opcode::JumpBackward.into();
1058                }
1059            }
1060            AnyInstruction::Pseudo(PseudoInstruction::JumpNoInterrupt { .. }) => {
1061                debug_assert!(is_pseudo_target(
1062                    PseudoOpcode::JumpNoInterrupt,
1063                    Opcode::JumpForward
1064                ));
1065                debug_assert!(is_pseudo_target(
1066                    PseudoOpcode::JumpNoInterrupt,
1067                    Opcode::JumpBackwardNoInterrupt
1068                ));
1069                if is_forward {
1070                    instr.instr = Opcode::JumpForward.into();
1071                } else {
1072                    instr.instr = Opcode::JumpBackwardNoInterrupt.into();
1073                }
1074            }
1075            _ => {
1076                if instr.instr.has_jump() && matches!(instr.instr, AnyInstruction::Pseudo(_)) {
1077                    unreachable!("remaining pseudo jump in resolve_unconditional_jumps");
1078                }
1079            }
1080        }
1081    }
1082}
1083
1084/// assemble.c resolve_jump_offsets
1085fn resolve_jump_offsets(instr_sequence: &mut InstructionSequence) {
1086    // The offset (in code units) of END_SEND from SEND in the yield-from sequence.
1087    const END_SEND_OFFSET: i32 = 5;
1088    for i in 0..instr_sequence.instr_used {
1089        let instr = &mut instr_sequence.instrs[i];
1090        let opcode = instr.info.instr.expect_real();
1091        if opcode.has_jump() {
1092            instr.i_target = u32::from(instr.info.arg) as i32;
1093        }
1094    }
1095
1096    let mut extended_arg_recompile;
1097    loop {
1098        let mut totsize = 0i32;
1099        for i in 0..instr_sequence.instr_used {
1100            let instr = &mut instr_sequence.instrs[i];
1101            instr.i_offset = totsize;
1102            let instr_size = instr.info.instr_size();
1103            totsize += instr_size as i32;
1104        }
1105
1106        extended_arg_recompile = false;
1107        let mut offset = 0i32;
1108        for i in 0..instr_sequence.instr_used {
1109            let i_size = instr_sequence.instrs[i].info.instr_size();
1110            // Jump offsets are computed relative to the instruction pointer
1111            // after fetching the jump instruction.
1112            offset += i_size as i32;
1113
1114            let opcode = instr_sequence.instrs[i].info.instr.expect_real();
1115            if opcode.has_jump() {
1116                let target = instr_sequence.instrs[i].i_target;
1117                let target_offset = instr_sequence.instrs[target as usize].i_offset;
1118                let info = &mut instr_sequence.instrs[i].info;
1119                let op = opcode;
1120                let mut oparg = target_offset;
1121                info.arg = OpArg::new(oparg as u32);
1122                if matches!(op, Instruction::EndAsyncFor) {
1123                    oparg = offset - oparg - END_SEND_OFFSET;
1124                } else if oparg < offset {
1125                    debug_assert!(matches!(
1126                        op.into(),
1127                        Opcode::JumpBackward | Opcode::JumpBackwardNoInterrupt
1128                    ));
1129                    oparg = offset - oparg;
1130                } else {
1131                    debug_assert!(!matches!(
1132                        op.into(),
1133                        Opcode::JumpBackward | Opcode::JumpBackwardNoInterrupt
1134                    ));
1135                    oparg -= offset;
1136                }
1137                info.arg = OpArg::new(oparg as u32);
1138                if info.instr_size() != i_size {
1139                    extended_arg_recompile = true;
1140                }
1141            }
1142        }
1143
1144        if !extended_arg_recompile {
1145            break;
1146        }
1147    }
1148}
1149
1150struct AssembledCode {
1151    instructions: Vec<CodeUnit>,
1152    linetable: Box<[u8]>,
1153    exceptiontable: Box<[u8]>,
1154}
1155
1156struct LocalsPlusInfo {
1157    cellvars: Box<[String]>,
1158    kinds: Box<[u8]>,
1159}
1160
1161/// assemble.c same_location
1162fn same_location(a: LineTableLocation, b: LineTableLocation) -> bool {
1163    a.line == b.line && a.end_line == b.end_line && a.col == b.col && a.end_col == b.end_col
1164}
1165
1166/// assemble.c write_instr
1167fn write_instr(instructions: &mut Vec<CodeUnit>, info: &InstructionInfo, ilen: usize) {
1168    let opcode = info.instr.expect_real();
1169    let oparg = u32::from(info.arg) as i32;
1170    debug_assert!(
1171        info.instr.has_arg() || oparg == 0,
1172        "CPython assemble.c write_instr requires OPCODE_HAS_ARG or oparg == 0"
1173    );
1174    let caches = opcode.cache_entries();
1175    let non_cache_units = ilen - caches;
1176    match non_cache_units {
1177        1..=4 => {}
1178        _ => unreachable!("CPython write_instr expects 1 to 4 non-cache code units"),
1179    }
1180    if non_cache_units >= 4 {
1181        instructions.push(CodeUnit::new(
1182            Instruction::ExtendedArg,
1183            OpArgByte::new(((oparg >> 24) & 0xff) as u8),
1184        ));
1185    }
1186    if non_cache_units >= 3 {
1187        instructions.push(CodeUnit::new(
1188            Instruction::ExtendedArg,
1189            OpArgByte::new(((oparg >> 16) & 0xff) as u8),
1190        ));
1191    }
1192    if non_cache_units >= 2 {
1193        instructions.push(CodeUnit::new(
1194            Instruction::ExtendedArg,
1195            OpArgByte::new(((oparg >> 8) & 0xff) as u8),
1196        ));
1197    }
1198    instructions.push(CodeUnit::new(opcode, OpArgByte::new((oparg & 0xff) as u8)));
1199    for _ in 0..caches {
1200        instructions.push(CodeUnit::new(Instruction::Cache, OpArgByte::new(0)));
1201    }
1202}
1203
1204/// assemble.c assemble_emit_instr
1205fn assemble_emit_instr(
1206    instructions: &mut Vec<CodeUnit>,
1207    info: &mut InstructionInfo,
1208) -> crate::InternalResult<()> {
1209    let size = info.instr_size();
1210    let required = instructions
1211        .len()
1212        .checked_add(size)
1213        .ok_or(InternalError::MalformedControlFlowGraph)?;
1214    if required >= instructions.capacity() {
1215        vec_try_resize_to_double_capacity(instructions)?;
1216    }
1217    write_instr(instructions, info, size);
1218    Ok(())
1219}
1220
1221/// assemble.c assemble_location_info
1222fn assemble_location_info(
1223    instr_sequence: &mut InstructionSequence,
1224    first_line: i32,
1225    debug_ranges: bool,
1226) -> crate::InternalResult<Box<[u8]>> {
1227    for i in (0..instr_sequence.instr_used).rev() {
1228        let loc = instr_sequence.instrs[i]
1229            .info
1230            .instruction_linetable_location();
1231        if same_location(loc, next_linetable_location()) {
1232            if instr_sequence.instrs[i]
1233                .info
1234                .instr
1235                .expect_real()
1236                .is_terminator()
1237            {
1238                instr_sequence.instrs[i].info.lineno_override = Some(NO_LOCATION_OVERRIDE);
1239            } else {
1240                debug_assert!(i < instr_sequence.instr_used - 1);
1241                let next = instr_sequence.instrs[i + 1].info;
1242                instr_sequence.instrs[i].info.instr_set_loc(
1243                    next.location,
1244                    next.end_location,
1245                    next.lineno_override,
1246                );
1247            }
1248        }
1249    }
1250
1251    let mut linetable = Vec::new();
1252    vec_try_reserve_exact(&mut linetable, DEFAULT_CNOTAB_SIZE)?;
1253    let mut prev_line = first_line;
1254    let mut loc = no_linetable_location();
1255    let mut size = 0;
1256    for entry in instr_sequence.instrs.iter().take(instr_sequence.instr_used) {
1257        let instr_loc = entry.info.instruction_linetable_location();
1258        if !same_location(loc, instr_loc) {
1259            assemble_emit_location(&mut linetable, loc, size, &mut prev_line, debug_ranges)?;
1260            loc = instr_loc;
1261            size = 0;
1262        }
1263        size += entry.info.instr_size();
1264    }
1265    assemble_emit_location(&mut linetable, loc, size, &mut prev_line, debug_ranges)?;
1266    Ok(linetable.into_boxed_slice())
1267}
1268
1269/// assemble.c assemble_emit
1270fn assemble_emit(
1271    instr_sequence: &mut InstructionSequence,
1272    first_line: i32,
1273    debug_ranges: bool,
1274) -> crate::InternalResult<AssembledCode> {
1275    let mut instructions = Vec::new();
1276    vec_try_reserve_exact(
1277        &mut instructions,
1278        DEFAULT_CODE_SIZE / core::mem::size_of::<CodeUnit>(),
1279    )?;
1280
1281    for i in 0..instr_sequence.instr_used {
1282        let instr = &mut instr_sequence.instrs[i].info;
1283        assemble_emit_instr(&mut instructions, instr)?;
1284    }
1285
1286    let linetable = assemble_location_info(instr_sequence, first_line, debug_ranges)?;
1287
1288    let exceptiontable =
1289        assemble_exception_table(&instr_sequence.instrs[..instr_sequence.instr_used])?;
1290
1291    Ok(AssembledCode {
1292        instructions,
1293        linetable,
1294        exceptiontable,
1295    })
1296}
1297
1298/// assemble.c compute_localsplus_info
1299fn compute_localsplus_info(
1300    umd: &CodeUnitMetadata,
1301    nlocalsplus: usize,
1302    flags: CodeFlags,
1303) -> crate::InternalResult<LocalsPlusInfo> {
1304    let nlocals = umd.varnames.len();
1305    let ncells = umd.cellvars.len();
1306    let nfrees = umd.freevars.len();
1307    let mut localspluskinds = Vec::new();
1308    vec_try_reserve_exact(&mut localspluskinds, nlocalsplus)?;
1309    localspluskinds.resize(nlocalsplus, 0);
1310    let mut cellvars = Vec::new();
1311    vec_try_reserve_exact(&mut cellvars, ncells)?;
1312
1313    let argvarkinds = [
1314        (umd.posonlyargcount as usize, CO_FAST_ARG_POS),
1315        (umd.argcount as usize, CO_FAST_ARG_POS | CO_FAST_ARG_KW),
1316        (umd.kwonlyargcount as usize, CO_FAST_ARG_KW),
1317        (
1318            usize::from(flags.contains(CodeFlags::VARARGS)),
1319            CO_FAST_ARG_VAR | CO_FAST_ARG_POS,
1320        ),
1321        (
1322            usize::from(flags.contains(CodeFlags::VARKEYWORDS)),
1323            CO_FAST_ARG_VAR | CO_FAST_ARG_KW,
1324        ),
1325        (usize::MAX, 0),
1326    ];
1327    let mut pos = 0usize;
1328    let mut max = 0usize;
1329    for (count, argkind) in argvarkinds {
1330        max = if count == usize::MAX {
1331            usize::MAX
1332        } else {
1333            max + count
1334        };
1335        while pos < max && pos < nlocals {
1336            let name = umd
1337                .varnames
1338                .get_index(pos)
1339                .expect("varname index is in range")
1340                .as_str();
1341            let mut kind = CO_FAST_LOCAL | argkind;
1342            if umd.fast_hidden.get(name).copied().unwrap_or(false)
1343                || umd.fast_hidden_final.contains(name)
1344            {
1345                kind |= CO_FAST_HIDDEN;
1346            }
1347            if umd.cellvars.contains(name) {
1348                kind |= CO_FAST_CELL;
1349                cellvars.push(name.to_owned());
1350            }
1351            localspluskinds[pos] = kind;
1352            pos += 1;
1353        }
1354    }
1355
1356    let mut numdropped = 0usize;
1357    let mut cellvar_offset = -1i32;
1358    for i in 0..ncells {
1359        let name = umd
1360            .cellvars
1361            .get_index(i)
1362            .expect("cellvar index is in range")
1363            .as_str();
1364        if umd.varnames.contains(name) {
1365            numdropped += 1;
1366            continue;
1367        }
1368        let offset = i + nlocals - numdropped;
1369        debug_assert!(offset < nlocalsplus);
1370        cellvars.push(name.to_owned());
1371        localspluskinds[offset] = CO_FAST_CELL;
1372        cellvar_offset = offset as i32;
1373    }
1374
1375    for i in 0..nfrees {
1376        let offset = ncells + i + nlocals - numdropped;
1377        debug_assert!(offset < nlocalsplus);
1378        debug_assert!((offset as i32) > cellvar_offset);
1379        localspluskinds[offset] = CO_FAST_FREE;
1380    }
1381
1382    debug_assert_eq!(
1383        nlocalsplus,
1384        nlocals + ncells - numdropped + nfrees,
1385        "CPython prepare_localsplus() result must match assemble.c localsplus sizing"
1386    );
1387    debug_assert_eq!(cellvars.len(), ncells);
1388    Ok(LocalsPlusInfo {
1389        cellvars: cellvars.into_boxed_slice(),
1390        kinds: localspluskinds.into_boxed_slice(),
1391    })
1392}
1393
1394#[derive(Debug, Clone)]
1395pub struct Block {
1396    /// CPython `basicblock.b_list`, allocation-order list distinct from CFG `b_next`.
1397    allocation_next: BlockIdx,
1398    /// CPython `basicblock.b_label` used by translate_jump_labels_to_targets.
1399    cpython_label: InstructionSequenceLabel,
1400    /// CPython `basicblock.b_ialloc`, the allocated size of `b_instr`.
1401    instruction_allocation: usize,
1402    /// Exception stack at start of block, used by label_exception_targets (b_exceptstack)
1403    except_stack: Option<CfgExceptStack>,
1404    /// CPython `basicblock.b_instr`, including allocated slots beyond `b_iused`.
1405    pub instructions: Vec<InstructionInfo>,
1406    pub next: BlockIdx,
1407    /// CPython `basicblock.b_iused`, the number of used entries in `b_instr`.
1408    instruction_used: usize,
1409    /// Potentially uninitialized locals mask for local-check analysis (b_unsafe_locals_mask)
1410    unsafe_locals_mask: u64,
1411    /// Number of incoming CFG edges from reachable blocks (b_predecessors)
1412    predecessors: i32,
1413    /// Stack depth at block entry, set by stack depth analysis
1414    pub start_depth: i32,
1415    /// Whether to preserve lasti for this handler block (b_preserve_lasti)
1416    pub preserve_lasti: bool,
1417    /// Temporary traversal mark used by CFG passes (b_visited)
1418    visited: bool,
1419    /// Whether this block is an exception handler target (b_except_handler)
1420    pub except_handler: bool,
1421    /// Whether this block is only reachable via exception table (b_cold)
1422    pub cold: bool,
1423    /// Definitely reachable outside exception-only paths (b_warm)
1424    warm: bool,
1425}
1426
1427impl Default for Block {
1428    fn default() -> Self {
1429        Self {
1430            allocation_next: BlockIdx::NULL,
1431            cpython_label: InstructionSequenceLabel::NO_LABEL,
1432            instruction_allocation: 0,
1433            except_stack: None,
1434            instructions: Vec::new(),
1435            next: BlockIdx::NULL,
1436            instruction_used: 0,
1437            unsafe_locals_mask: 0,
1438            predecessors: 0,
1439            start_depth: START_DEPTH_UNSET,
1440            preserve_lasti: false,
1441            visited: false,
1442            except_handler: false,
1443            cold: false,
1444            warm: false,
1445        }
1446    }
1447}
1448
1449impl Block {
1450    pub(crate) fn used_instructions(&self) -> &[InstructionInfo] {
1451        &self.instructions[..self.instruction_used]
1452    }
1453
1454    #[must_use]
1455    pub(crate) const fn is_empty(&self) -> bool {
1456        self.instruction_used == 0
1457    }
1458
1459    /// flowgraph.c basicblock_next_instr
1460    fn basicblock_next_instr(&mut self) -> crate::InternalResult<usize> {
1461        let off = self.instruction_used;
1462        let new_allocation = c_array_ensure_capacity::<InstructionInfo>(
1463            self.instruction_allocation,
1464            off + 1,
1465            DEFAULT_BLOCK_SIZE,
1466        )?;
1467        if new_allocation > self.instruction_allocation {
1468            if new_allocation > self.instructions.len() {
1469                self.instructions
1470                    .try_reserve_exact(new_allocation - self.instructions.len())
1471                    .map_err(|_| InternalError::MalformedControlFlowGraph)?;
1472                self.instructions
1473                    .resize_with(new_allocation, InstructionInfo::empty);
1474            }
1475            self.instruction_allocation = new_allocation;
1476        }
1477        debug_assert!(self.instruction_allocation > off);
1478        self.instruction_used += 1;
1479        Ok(off)
1480    }
1481
1482    /// flowgraph.c basicblock_last_instr
1483    fn basicblock_last_instr(&self) -> Option<&InstructionInfo> {
1484        debug_assert!(self.instruction_allocation >= self.instruction_used);
1485        if self.instruction_used > 0 {
1486            debug_assert!(!self.instructions.is_empty());
1487            Some(&self.instructions[self.instruction_used - 1])
1488        } else {
1489            None
1490        }
1491    }
1492
1493    /// flowgraph.c basicblock_last_instr
1494    fn basicblock_last_instr_mut(&mut self) -> Option<&mut InstructionInfo> {
1495        debug_assert!(self.instruction_allocation >= self.instruction_used);
1496        if self.instruction_used > 0 {
1497            debug_assert!(!self.instructions.is_empty());
1498            Some(&mut self.instructions[self.instruction_used - 1])
1499        } else {
1500            None
1501        }
1502    }
1503
1504    /// flowgraph.c basicblock_addop
1505    fn basicblock_addop(&mut self, mut info: InstructionInfo) -> crate::InternalResult<()> {
1506        let opcode = AnyOpcode::from(info.instr);
1507        debug_assert!(is_within_opcode_range(opcode));
1508        debug_assert!(!info.instr.is_assembler());
1509        debug_assert!(
1510            info.instr.has_arg() || info.instr.has_target() || u32::from(info.arg) == 0,
1511            "CPython basicblock_addop requires OPCODE_HAS_ARG, HAS_TARGET, or oparg == 0"
1512        );
1513        debug_assert!(
1514            u32::from(info.arg) < (1 << 30),
1515            "CPython basicblock_addop requires 0 <= oparg < (1 << 30)"
1516        );
1517        let off = self.basicblock_next_instr()?;
1518        let except_handler = self.instructions[off].except_handler;
1519        info.target = BlockIdx::NULL;
1520        info.except_handler = except_handler;
1521        self.instructions[off] = info;
1522        Ok(())
1523    }
1524
1525    /// flowgraph.c basicblock_insert_instruction
1526    fn basicblock_insert_instruction(
1527        &mut self,
1528        pos: usize,
1529        info: InstructionInfo,
1530    ) -> crate::InternalResult<()> {
1531        let old_len = self.instruction_used;
1532        debug_assert!(pos <= old_len);
1533        self.basicblock_next_instr()?;
1534        for i in (pos + 1..=old_len).rev() {
1535            self.instructions[i] = self.instructions[i - 1];
1536        }
1537        self.instructions[pos] = info;
1538        Ok(())
1539    }
1540
1541    /// flowgraph.c direct `b_iused = 0`
1542    fn basicblock_clear(&mut self) {
1543        self.instruction_used = 0;
1544    }
1545
1546    /// CPython direct `b_instr[0]` access. Some passes set `b_iused = 0`
1547    /// without clearing the backing array, so an empty basic block can still have
1548    /// a first raw instruction slot.
1549    fn basicblock_raw_first_instr_mut(&mut self) -> &mut InstructionInfo {
1550        debug_assert!(self.instruction_allocation > 0);
1551        &mut self.instructions[0]
1552    }
1553
1554    /// flowgraph.c BB_NO_FALLTHROUGH
1555    fn bb_no_fallthrough(&self) -> bool {
1556        self.basicblock_nofallthrough()
1557    }
1558
1559    /// flowgraph.c BB_HAS_FALLTHROUGH
1560    fn bb_has_fallthrough(&self) -> bool {
1561        !self.bb_no_fallthrough()
1562    }
1563
1564    /// flowgraph.c basicblock_returns
1565    #[cfg(test)]
1566    fn basicblock_returns(&self) -> bool {
1567        let last = self.basicblock_last_instr();
1568        if let Some(last) = last {
1569            matches!(last.instr.real(), Some(Instruction::ReturnValue))
1570        } else {
1571            false
1572        }
1573    }
1574
1575    /// flowgraph.c basicblock_exits_scope
1576    fn basicblock_exits_scope(&self) -> bool {
1577        let last = self.basicblock_last_instr();
1578        last.is_some_and(|last| last.instr.is_scope_exit())
1579    }
1580
1581    /// flowgraph.c is_exit_or_eval_check_without_lineno
1582    fn is_exit_or_eval_check_without_lineno(&self) -> bool {
1583        if self.basicblock_exits_scope() || self.basicblock_has_eval_break() {
1584            self.basicblock_has_no_lineno()
1585        } else {
1586            false
1587        }
1588    }
1589
1590    /// flowgraph.c basicblock_has_eval_break
1591    fn basicblock_has_eval_break(&self) -> bool {
1592        let mut i = 0;
1593        while i < self.instruction_used {
1594            if self.instructions[i].instr.has_eval_break() {
1595                return true;
1596            }
1597            i += 1;
1598        }
1599        false
1600    }
1601
1602    /// flowgraph.c basicblock_has_no_lineno
1603    fn basicblock_has_no_lineno(&self) -> bool {
1604        let mut i = 0;
1605        while i < self.instruction_used {
1606            if self.instructions[i].instruction_lineno() >= 0 {
1607                return false;
1608            }
1609            i += 1;
1610        }
1611        true
1612    }
1613
1614    /// flowgraph.c basicblock_nofallthrough
1615    fn basicblock_nofallthrough(&self) -> bool {
1616        let last = self.basicblock_last_instr();
1617        last.is_some_and(|last| last.instr.is_scope_exit() || last.instr.is_unconditional_jump())
1618    }
1619
1620    /// flowgraph.c nop_out
1621    fn nop_out(&mut self, instrs: &[usize]) {
1622        for &i in instrs {
1623            self.instructions[i].nop_out_no_location();
1624        }
1625    }
1626
1627    /// flowgraph.c get_const_loading_instrs
1628    fn get_const_loading_instrs(
1629        &self,
1630        mut start: usize,
1631        size: usize,
1632    ) -> crate::InternalResult<Option<Vec<usize>>> {
1633        let mut indices = Vec::new();
1634        indices
1635            .try_reserve_exact(size)
1636            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
1637        loop {
1638            if start >= self.instruction_used {
1639                return Ok(None);
1640            }
1641
1642            let instr = &self.instructions[start];
1643            if !matches!(instr.instr.real(), Some(Instruction::Nop)) {
1644                if !instr.loads_const() {
1645                    return Ok(None);
1646                }
1647
1648                indices.push(start);
1649                if indices.len() == size {
1650                    break;
1651                }
1652            }
1653
1654            let Some(prev) = start.checked_sub(1) else {
1655                return Ok(None);
1656            };
1657
1658            start = prev;
1659        }
1660
1661        indices.reverse();
1662        Ok(Some(indices))
1663    }
1664
1665    /// flowgraph.c next_swappable_instruction
1666    fn next_swappable_instruction(&self, mut i: usize, lineno: i32) -> Option<usize> {
1667        loop {
1668            i += 1;
1669            if i >= self.instruction_used {
1670                return None;
1671            }
1672
1673            let info = &self.instructions[i];
1674            let info_lineno = info.instruction_lineno();
1675
1676            if lineno >= 0 && info_lineno != lineno {
1677                return None;
1678            }
1679
1680            if matches!(info.instr, AnyInstruction::Real(Instruction::Nop)) {
1681                continue;
1682            }
1683
1684            if is_swappable(info.instr) {
1685                return Some(i);
1686            }
1687
1688            return None;
1689        }
1690    }
1691
1692    /// flowgraph.c swaptimize
1693    fn swaptimize(&mut self, ix: &mut usize) -> crate::InternalResult<()> {
1694        debug_assert!(matches!(
1695            self.instructions[*ix].instr.real_opcode(),
1696            Some(Opcode::Swap)
1697        ));
1698        let mut depth = u32::from(self.instructions[*ix].arg) as usize;
1699        let mut len = 1usize;
1700        let mut more = false;
1701        let limit = self.instruction_used - *ix;
1702        while len < limit {
1703            match self.instructions[*ix + len].instr.real_opcode() {
1704                Some(Opcode::Swap) => {
1705                    depth = depth.max(u32::from(self.instructions[*ix + len].arg) as usize);
1706                    more = true;
1707                    len += 1;
1708                }
1709                Some(Opcode::Nop) => {
1710                    len += 1;
1711                }
1712                _ => break,
1713            }
1714        }
1715
1716        if !more {
1717            return Ok(());
1718        }
1719
1720        let mut stack = Vec::new();
1721        stack
1722            .try_reserve_exact(depth)
1723            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
1724        stack.resize(depth, 0);
1725        let mut i = 0;
1726        while i < depth {
1727            stack[i] = i as i32;
1728            i += 1;
1729        }
1730
1731        i = 0;
1732        while i < len {
1733            let info = &self.instructions[*ix + i];
1734            if matches!(info.instr.real_opcode(), Some(Opcode::Swap)) {
1735                let oparg = u32::from(info.arg) as usize;
1736                stack.swap(0, oparg - 1);
1737            }
1738            i += 1;
1739        }
1740
1741        let mut current = len as isize - 1;
1742        for i in 0..depth {
1743            if stack[i] == VISITED || stack[i] == i as i32 {
1744                continue;
1745            }
1746            let mut j = i;
1747            loop {
1748                if j != 0 {
1749                    debug_assert!(current >= 0);
1750                    let out = &mut self.instructions[*ix + current as usize];
1751                    out.instr = Opcode::Swap.into();
1752                    out.arg = OpArg::new((j + 1) as u32);
1753                    current -= 1;
1754                }
1755                if stack[j] == VISITED {
1756                    debug_assert_eq!(j, i);
1757                    break;
1758                }
1759                let next_j = stack[j] as usize;
1760                stack[j] = VISITED;
1761                j = next_j;
1762            }
1763        }
1764
1765        while current >= 0 {
1766            self.instructions[*ix + current as usize].set_to_nop();
1767            current -= 1;
1768        }
1769        *ix += len - 1;
1770        Ok(())
1771    }
1772
1773    /// flowgraph.c apply_static_swaps
1774    fn apply_static_swaps(&mut self, mut i: isize) {
1775        while i >= 0 {
1776            let idx = i as usize;
1777            debug_assert!(idx < self.instruction_used);
1778            let swap_arg = match self.instructions[idx].instr.real_opcode() {
1779                Some(Opcode::Swap) => u32::from(self.instructions[idx].arg),
1780                Some(Opcode::Nop | Opcode::PopTop | Opcode::StoreFast) => {
1781                    i -= 1;
1782                    continue;
1783                }
1784                _ if matches!(
1785                    self.instructions[idx].instr.pseudo_opcode(),
1786                    Some(PseudoOpcode::StoreFastMaybeNull)
1787                ) =>
1788                {
1789                    i -= 1;
1790                    continue;
1791                }
1792                _ => return,
1793            };
1794
1795            let Some(j) = self.next_swappable_instruction(idx, -1) else {
1796                return;
1797            };
1798            let lineno = self.instructions[j].instruction_lineno();
1799            let mut k = j;
1800            for _ in 1..swap_arg {
1801                let Some(next) = self.next_swappable_instruction(k, lineno) else {
1802                    return;
1803                };
1804                k = next;
1805            }
1806
1807            let store_j = self.instructions[j].stores_to();
1808            let store_k = self.instructions[k].stores_to();
1809            if store_j >= 0 || store_k >= 0 {
1810                if store_j == store_k {
1811                    return;
1812                }
1813                let mut idx = j + 1;
1814                while idx < k {
1815                    let store_idx = self.instructions[idx].stores_to();
1816                    if store_idx >= 0 && (store_idx == store_j || store_idx == store_k) {
1817                        return;
1818                    }
1819                    idx += 1;
1820                }
1821            }
1822
1823            self.instructions[idx].set_to_nop();
1824            self.instructions.swap(j, k);
1825            i -= 1;
1826        }
1827    }
1828
1829    /// flowgraph.c optimize_basic_block swap pass
1830    fn apply_static_swaps_block(&mut self) -> crate::InternalResult<()> {
1831        let mut i = 0;
1832        while i < self.instruction_used {
1833            if matches!(self.instructions[i].instr.real_opcode(), Some(Opcode::Swap)) {
1834                self.swaptimize(&mut i)?;
1835                self.apply_static_swaps(i as isize);
1836            }
1837            i += 1;
1838        }
1839        Ok(())
1840    }
1841}
1842
1843#[derive(Clone, Debug, Default)]
1844pub struct Blocks(Vec<Block>);
1845
1846// Vec like methods
1847impl Blocks {
1848    pub fn try_reserve(
1849        &mut self,
1850        additional: usize,
1851    ) -> Result<(), alloc::collections::TryReserveError> {
1852        self.0.try_reserve(additional)
1853    }
1854
1855    pub fn push(&mut self, value: Block) {
1856        self.0.push(value)
1857    }
1858}
1859
1860// CPython functions
1861
1862impl Blocks {
1863    /// # See also
1864    /// [CPython's remove_unreachable](https://github.com/python/cpython/blob/v3.14.6/Python/flowgraph.c#L995-L1041)
1865    pub fn remove_unreachable(&mut self) -> crate::InternalResult<()> {
1866        let mut block_idx = BlockIdx(0);
1867        while block_idx != BlockIdx::NULL {
1868            self[block_idx].predecessors = 0;
1869            block_idx = self[block_idx].next;
1870        }
1871
1872        let mut stack = self.make_cfg_traversal_stack()?;
1873        self[0].predecessors = 1;
1874        stack.push(BlockIdx(0));
1875        self[0].visited = true;
1876        while let Some(current) = stack.pop() {
1877            let idx = current.idx();
1878            let next = self[idx].next;
1879            if next != BlockIdx::NULL && self[idx].bb_has_fallthrough() {
1880                if !self[next].visited {
1881                    debug_assert_eq!(self[next].predecessors, 0);
1882                    stack.push(next);
1883                    self[next].visited = true;
1884                }
1885                self[next].predecessors += 1;
1886            }
1887
1888            let instr_count = self[idx].instruction_used;
1889            for i in 0..instr_count {
1890                let instr = self[idx].instructions[i];
1891                if instr.is_jump() || instr.is_block_push() {
1892                    let target = instr.target;
1893                    debug_assert!(target != BlockIdx::NULL);
1894                    let target_idx = target.idx();
1895                    if !self[target_idx].visited {
1896                        stack.push(target);
1897                        self[target_idx].visited = true;
1898                    }
1899                    self[target_idx].predecessors += 1;
1900                }
1901            }
1902        }
1903
1904        block_idx = BlockIdx(0);
1905        while block_idx != BlockIdx::NULL {
1906            let next = self[block_idx].next;
1907            if self[block_idx].predecessors == 0 {
1908                let block = &mut self[block_idx];
1909                block.basicblock_clear();
1910                block.except_handler = false;
1911            }
1912            block_idx = next;
1913        }
1914        Ok(())
1915    }
1916
1917    /// flowgraph.c basicblock_append_instructions
1918    fn basicblock_append_block_instructions(
1919        &mut self,
1920        to: BlockIdx,
1921        from: BlockIdx,
1922    ) -> crate::InternalResult<()> {
1923        debug_assert_ne!(to, from);
1924
1925        let from_len = self[from].instruction_used;
1926        for i in 0..from_len {
1927            let info = self[from].instructions[i];
1928            let off = self[to].basicblock_next_instr()?;
1929            self[to].instructions[off] = info;
1930        }
1931
1932        Ok(())
1933    }
1934
1935    /// flowgraph.c copy_basicblock
1936    fn copy_basicblock(&mut self, block_idx: BlockIdx) -> crate::InternalResult<BlockIdx> {
1937        debug_assert!(self[block_idx].bb_no_fallthrough());
1938
1939        let result = self.blocks_new_block()?;
1940        self.basicblock_append_block_instructions(result, block_idx)?;
1941        Ok(result)
1942    }
1943
1944    fn duplicate_exits_without_lineno(&mut self) -> crate::InternalResult<()> {
1945        let mut next_lbl = get_max_label(self) + 1;
1946
1947        let entryblock = BlockIdx(0);
1948        let mut b = entryblock;
1949        while b != BlockIdx::NULL {
1950            let Some(last) = self[b].basicblock_last_instr().copied() else {
1951                b = self[b].next;
1952                continue;
1953            };
1954
1955            if last.is_jump() {
1956                debug_assert!(last.target != BlockIdx::NULL);
1957
1958                let target = next_nonempty_block(self, last.target);
1959
1960                debug_assert!(target != BlockIdx::NULL);
1961
1962                if self[target].is_exit_or_eval_check_without_lineno()
1963                    && self[target].predecessors > 1
1964                {
1965                    let new_target = self.copy_basicblock(target)?;
1966                    self[new_target].instructions[0].instr_set_location(last.instr_location());
1967                    let last_mut = self[b].basicblock_last_instr_mut().unwrap();
1968                    last_mut.target = new_target;
1969                    self[target].predecessors -= 1;
1970                    self[new_target].predecessors = 1;
1971                    self[new_target].next = self[target].next;
1972                    self[new_target].cpython_label = InstructionSequenceLabel(next_lbl);
1973                    next_lbl += 1;
1974                    self[target].next = new_target;
1975                }
1976            }
1977            b = self[b].next;
1978        }
1979
1980        b = entryblock;
1981        while b != BlockIdx::NULL {
1982            let next = self[b].next;
1983            if self[b].bb_has_fallthrough()
1984                && next != BlockIdx::NULL
1985                && self[b].instruction_used != 0
1986                && self[next].is_exit_or_eval_check_without_lineno()
1987            {
1988                let last = *self[b]
1989                    .basicblock_last_instr()
1990                    .expect("block has instructions");
1991                self[next].instructions[0].instr_set_location(last.instr_location());
1992            }
1993            b = self[b].next;
1994        }
1995
1996        Ok(())
1997    }
1998
1999    fn resolve_line_numbers(&mut self, _firstlineno: OneIndexed) -> crate::InternalResult<()> {
2000        self.duplicate_exits_without_lineno()?;
2001        self.propagate_line_numbers();
2002        Ok(())
2003    }
2004
2005    /// flowgraph.c optimize_basic_block
2006    fn optimize_basic_block(
2007        &mut self,
2008        metadata: &mut CodeUnitMetadata,
2009        block_idx: BlockIdx,
2010    ) -> crate::InternalResult<()> {
2011        let mut nop = InstructionInfo {
2012            instr: Instruction::Nop.into(),
2013            arg: OpArg::NULL,
2014            target: BlockIdx::NULL,
2015            location: SourceLocation::default(),
2016            end_location: SourceLocation::default(),
2017            except_handler: None,
2018            lineno_override: None,
2019        };
2020        nop.instr_set_op0(Instruction::Nop.into());
2021        let mut i = 0;
2022        while i < self[block_idx].instruction_used {
2023            let inst = self[block_idx].instructions[i];
2024            debug_assert!(!inst.instr.is_assembler());
2025            let target = if inst.instr.has_target() {
2026                let target = inst.target;
2027                debug_assert!(target != BlockIdx::NULL);
2028                debug_assert!(self[target.idx()].instruction_used != 0);
2029                debug_assert!(!self[target.idx()].instructions[0].instr.is_assembler());
2030                self[target.idx()].instructions[0]
2031            } else {
2032                nop
2033            };
2034
2035            let nextop = self[block_idx]
2036                .instructions
2037                .get(i + 1)
2038                .and_then(|next| next.instr.real());
2039
2040            match inst.instr {
2041                AnyInstruction::Real(Instruction::BuildTuple { .. }) => {
2042                    let oparg = u32::from(inst.arg);
2043                    if matches!(nextop, Some(Instruction::UnpackSequence { .. }))
2044                        && u32::from(self[block_idx].instructions[i + 1].arg) == oparg
2045                    {
2046                        match oparg {
2047                            1 => {
2048                                self[block_idx].instructions[i].set_to_nop();
2049                                self[block_idx].instructions[i + 1].set_to_nop();
2050                                i += 1;
2051                                continue;
2052                            }
2053                            2 | 3 => {
2054                                self[block_idx].instructions[i].set_to_nop();
2055                                self[block_idx].instructions[i + 1].instr = Opcode::Swap.into();
2056                                i += 1;
2057                                continue;
2058                            }
2059                            _ => {}
2060                        }
2061                    }
2062                    fold_tuple_of_constants(metadata, &mut self[block_idx], i)?;
2063                }
2064                AnyInstruction::Real(
2065                    Instruction::BuildList { .. } | Instruction::BuildSet { .. },
2066                ) => {
2067                    optimize_lists_and_sets(metadata, &mut self[block_idx], i, nextop)?;
2068                }
2069                AnyInstruction::Real(
2070                    Instruction::PopJumpIfNotNone { .. } | Instruction::PopJumpIfNone { .. },
2071                ) if matches!(target.instr.into(), AnyOpcode::Pseudo(PseudoOpcode::Jump))
2072                    && self.jump_thread(block_idx, i, &target, inst.instr)? =>
2073                {
2074                    continue;
2075                }
2076                AnyInstruction::Real(Instruction::PopJumpIfFalse { .. })
2077                    if matches!(target.instr.into(), AnyOpcode::Pseudo(PseudoOpcode::Jump))
2078                        && self.jump_thread(block_idx, i, &target, inst.instr)? =>
2079                {
2080                    continue;
2081                }
2082                AnyInstruction::Real(Instruction::PopJumpIfTrue { .. })
2083                    if matches!(target.instr.into(), AnyOpcode::Pseudo(PseudoOpcode::Jump))
2084                        && self.jump_thread(block_idx, i, &target, inst.instr)? =>
2085                {
2086                    continue;
2087                }
2088                AnyInstruction::Pseudo(
2089                    pseudo @ (PseudoInstruction::JumpIfFalse { .. }
2090                    | PseudoInstruction::JumpIfTrue { .. }),
2091                ) => {
2092                    let opcode = pseudo.into();
2093                    let opcode_is_false = matches!(pseudo, PseudoInstruction::JumpIfFalse { .. });
2094                    match target.instr.pseudo().map(Into::into) {
2095                        Some(PseudoOpcode::Jump)
2096                            if self.jump_thread(block_idx, i, &target, opcode)? =>
2097                        {
2098                            continue;
2099                        }
2100                        Some(PseudoOpcode::JumpIfFalse)
2101                            if opcode_is_false
2102                                && self.jump_thread(block_idx, i, &target, opcode)? =>
2103                        {
2104                            continue;
2105                        }
2106                        Some(PseudoOpcode::JumpIfTrue)
2107                            if !opcode_is_false
2108                                && self.jump_thread(block_idx, i, &target, opcode)? =>
2109                        {
2110                            continue;
2111                        }
2112                        Some(PseudoOpcode::JumpIfTrue) if opcode_is_false => {
2113                            let next = self[inst.target].next;
2114                            debug_assert!(next != BlockIdx::NULL);
2115                            debug_assert!(next != inst.target);
2116                            self[block_idx].instructions[i].target = next;
2117                            continue;
2118                        }
2119                        Some(PseudoOpcode::JumpIfFalse) if !opcode_is_false => {
2120                            let next = self[inst.target].next;
2121                            debug_assert!(next != BlockIdx::NULL);
2122                            debug_assert!(next != inst.target);
2123                            self[block_idx].instructions[i].target = next;
2124                            continue;
2125                        }
2126                        _ => {}
2127                    }
2128                }
2129                AnyInstruction::Pseudo(
2130                    PseudoInstruction::Jump { .. } | PseudoInstruction::JumpNoInterrupt { .. },
2131                ) => match target.instr.into() {
2132                    AnyOpcode::Pseudo(PseudoOpcode::Jump)
2133                        if self.jump_thread(
2134                            block_idx,
2135                            i,
2136                            &target,
2137                            PseudoOpcode::Jump.into(),
2138                        )? =>
2139                    {
2140                        continue;
2141                    }
2142                    AnyOpcode::Pseudo(PseudoOpcode::JumpNoInterrupt)
2143                        if self.jump_thread(block_idx, i, &target, inst.instr)? =>
2144                    {
2145                        continue;
2146                    }
2147                    _ => {}
2148                },
2149                // CPython leaves FOR_ITER jump threading disabled.
2150                AnyInstruction::Real(Instruction::ForIter { .. }) => {}
2151                AnyInstruction::Real(Instruction::StoreFast { .. })
2152                    if matches!(nextop, Some(Instruction::StoreFast { .. }))
2153                        && u32::from(inst.arg)
2154                            == u32::from(self[block_idx].instructions[i + 1].arg)
2155                        && self[block_idx].instructions[i].instruction_lineno()
2156                            == self[block_idx].instructions[i + 1].instruction_lineno() =>
2157                {
2158                    self[block_idx].instructions[i].instr = Instruction::PopTop.into();
2159                    self[block_idx].instructions[i].arg = OpArg::NULL;
2160                }
2161                AnyInstruction::Real(Instruction::Swap { .. }) if u32::from(inst.arg) == 1 => {
2162                    self[block_idx].instructions[i].set_to_nop();
2163                }
2164                AnyInstruction::Real(Instruction::LoadGlobal { .. })
2165                    if matches!(nextop, Some(Instruction::PushNull))
2166                        && (u32::from(inst.arg) & 1) == 0 =>
2167                {
2168                    self[block_idx].instructions[i]
2169                        .instr_set_op1(inst.instr, OpArg::new(u32::from(inst.arg) | 1));
2170                    self[block_idx].instructions[i + 1].set_to_nop();
2171                }
2172                AnyInstruction::Real(Instruction::CompareOp { .. })
2173                    if matches!(nextop, Some(Instruction::ToBool)) =>
2174                {
2175                    self[block_idx].instructions[i].set_to_nop();
2176                    self[block_idx].instructions[i + 1].instr_set_op1(
2177                        inst.instr,
2178                        OpArg::new(u32::from(inst.arg) | oparg::COMPARE_OP_BOOL_MASK),
2179                    );
2180                    i += 1;
2181                    continue;
2182                }
2183                AnyInstruction::Real(Instruction::ContainsOp { .. } | Instruction::IsOp { .. })
2184                    if matches!(nextop, Some(Instruction::ToBool)) =>
2185                {
2186                    self[block_idx].instructions[i].set_to_nop();
2187                    self[block_idx].instructions[i + 1].instr_set_op1(inst.instr, inst.arg);
2188                    i += 1;
2189                    continue;
2190                }
2191                AnyInstruction::Real(Instruction::ContainsOp { .. } | Instruction::IsOp { .. })
2192                    if matches!(nextop, Some(Instruction::UnaryNot)) =>
2193                {
2194                    self[block_idx].instructions[i].set_to_nop();
2195                    let inverted = u32::from(inst.arg) ^ 1;
2196                    debug_assert!(inverted == 0 || inverted == 1);
2197                    self[block_idx].instructions[i + 1]
2198                        .instr_set_op1(inst.instr, OpArg::new(inverted));
2199                    i += 1;
2200                    continue;
2201                }
2202                AnyInstruction::Real(Instruction::ToBool)
2203                    if matches!(nextop, Some(Instruction::ToBool)) =>
2204                {
2205                    self[block_idx].instructions[i].set_to_nop();
2206                    i += 1;
2207                    continue;
2208                }
2209                AnyInstruction::Real(Instruction::UnaryNot) => {
2210                    if matches!(nextop, Some(Instruction::ToBool)) {
2211                        self[block_idx].instructions[i].set_to_nop();
2212                        self[block_idx].instructions[i + 1].instr_set_op0(inst.instr);
2213                        i += 1;
2214                        continue;
2215                    }
2216                    if matches!(nextop, Some(Instruction::UnaryNot)) {
2217                        self[block_idx].instructions[i].set_to_nop();
2218                        self[block_idx].instructions[i + 1].set_to_nop();
2219                        i += 1;
2220                        continue;
2221                    }
2222                    fold_const_unaryop(metadata, &mut self[block_idx], i)?;
2223                }
2224                AnyInstruction::Real(Instruction::UnaryInvert | Instruction::UnaryNegative) => {
2225                    fold_const_unaryop(metadata, &mut self[block_idx], i)?;
2226                }
2227                AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => {
2228                    match func.get(inst.arg) {
2229                        IntrinsicFunction1::ListToTuple => {
2230                            if matches!(nextop, Some(Instruction::GetIter)) {
2231                                self[block_idx].instructions[i].set_to_nop();
2232                            } else {
2233                                fold_constant_intrinsic_list_to_tuple(
2234                                    metadata,
2235                                    &mut self[block_idx],
2236                                    i,
2237                                )?;
2238                            }
2239                        }
2240                        IntrinsicFunction1::UnaryPositive => {
2241                            fold_const_unaryop(metadata, &mut self[block_idx], i)?;
2242                        }
2243                        _ => {}
2244                    }
2245                }
2246                AnyInstruction::Real(Instruction::BinaryOp { .. }) => {
2247                    fold_const_binop(metadata, &mut self[block_idx], i)?;
2248                }
2249                _ => {}
2250            }
2251
2252            i += 1;
2253        }
2254        self[block_idx].apply_static_swaps_block()?;
2255        Ok(())
2256    }
2257
2258    /// flowgraph.c _PyCfg_ToInstructionSequence
2259    fn cfg_to_instruction_sequence(
2260        &mut self,
2261        instr_sequence: &mut InstructionSequence,
2262    ) -> crate::InternalResult<()> {
2263        let mut label_id = 0;
2264        let mut block_idx = BlockIdx(0);
2265        while block_idx != BlockIdx::NULL {
2266            self[block_idx].cpython_label = InstructionSequenceLabel::from_index(label_id);
2267            label_id += 1;
2268            block_idx = self[block_idx].next;
2269        }
2270
2271        block_idx = BlockIdx(0);
2272        while block_idx != BlockIdx::NULL {
2273            let block_label = self[block_idx].cpython_label;
2274            debug_assert!(is_label(block_label));
2275            instruction_sequence_use_label(instr_sequence, block_label)?;
2276
2277            let instr_count = self[block_idx].instruction_used;
2278            for i in 0..instr_count {
2279                if self[block_idx].instructions[i].instr.has_target() {
2280                    let target_block = self[block_idx].instructions[i].target;
2281                    debug_assert!(target_block != BlockIdx::NULL);
2282                    let lbl = self[target_block].cpython_label;
2283                    debug_assert!(is_label(lbl));
2284                    self[block_idx].instructions[i].arg = OpArg::new(lbl.0 as u32);
2285                }
2286
2287                let mut info = self[block_idx].instructions[i];
2288                info.target = BlockIdx::NULL;
2289                let except_handler = info.except_handler.take();
2290                let entry = instruction_sequence_addop(instr_sequence, info)?;
2291                let hi = &mut entry.except_handler;
2292                if let Some(handler) = except_handler {
2293                    debug_assert!(handler.handler_block != BlockIdx::NULL);
2294                    let lbl = self[handler.handler_block].cpython_label;
2295                    debug_assert!(is_label(lbl));
2296                    let start_depth = self[handler.handler_block].start_depth;
2297                    debug_assert!(start_depth >= 0);
2298                    hi.h_label = lbl.0;
2299                    hi.start_depth = start_depth;
2300                    hi.preserve_lasti = i32::from(handler.preserve_lasti);
2301                } else {
2302                    hi.h_label = NO_EXCEPTION_HANDLER_LABEL;
2303                }
2304            }
2305            block_idx = self[block_idx].next;
2306        }
2307
2308        instruction_sequence_apply_label_map(instr_sequence);
2309        Ok(())
2310    }
2311
2312    fn optimize_load_fast(&mut self) -> crate::InternalResult<()> {
2313        let mut max_instrs = 0;
2314        let mut current = BlockIdx(0);
2315        while current != BlockIdx::NULL {
2316            max_instrs = max_instrs.max(self[current].instruction_used);
2317            current = self[current].next;
2318        }
2319
2320        let mut instr_flags = Vec::new();
2321        instr_flags
2322            .try_reserve_exact(max_instrs)
2323            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
2324        instr_flags.resize(max_instrs, 0u8);
2325        let mut refs = RefStack {
2326            refs: Vec::new(),
2327            size: 0,
2328            capacity: 0,
2329        };
2330        let mut worklist = self.make_cfg_traversal_stack()?;
2331        worklist.push(BlockIdx(0));
2332        self[0].start_depth = 0;
2333        self[0].visited = true;
2334        while let Some(block_idx) = worklist.pop() {
2335            let instr_count = self[block_idx].instruction_used;
2336            instr_flags[..instr_count].fill(0);
2337            debug_assert!(self[block_idx].start_depth >= 0);
2338            let start_depth = self[block_idx].start_depth as usize;
2339            ref_stack_clear(&mut refs);
2340            for _ in 0..start_depth {
2341                push_ref(&mut refs, DUMMY_INSTR, NOT_LOCAL)?;
2342            }
2343
2344            for i in 0..instr_count {
2345                let info = self[block_idx].instructions[i];
2346                let instr = info.instr;
2347                let arg_u32 = u32::from(info.arg);
2348                debug_assert!(!matches!(instr.real(), Some(Instruction::ExtendedArg)));
2349
2350                match instr {
2351                    AnyInstruction::Real(Instruction::DeleteFast { var_num }) => {
2352                        kill_local(
2353                            &mut instr_flags,
2354                            &refs,
2355                            local_as_ref_local(usize::from(var_num.get(info.arg))),
2356                        );
2357                    }
2358                    AnyInstruction::Real(Instruction::LoadFast { var_num }) => {
2359                        push_ref(
2360                            &mut refs,
2361                            i as isize,
2362                            local_as_ref_local(usize::from(var_num.get(info.arg))),
2363                        )?;
2364                    }
2365                    AnyInstruction::Real(Instruction::LoadFastAndClear { var_num }) => {
2366                        let local = local_as_ref_local(usize::from(var_num.get(info.arg)));
2367                        kill_local(&mut instr_flags, &refs, local);
2368                        push_ref(&mut refs, i as isize, local)?;
2369                    }
2370                    AnyInstruction::Real(Instruction::LoadFastLoadFast { .. }) => {
2371                        let local1 = (arg_u32 >> 4) as isize;
2372                        let local2 = (arg_u32 & 15) as isize;
2373                        push_ref(&mut refs, i as isize, local1)?;
2374                        push_ref(&mut refs, i as isize, local2)?;
2375                    }
2376                    AnyInstruction::Real(Instruction::StoreFast { var_num }) => {
2377                        let r = ref_stack_pop(&mut refs);
2378                        store_local(
2379                            &mut instr_flags,
2380                            &refs,
2381                            local_as_ref_local(usize::from(var_num.get(info.arg))),
2382                            r,
2383                        );
2384                    }
2385                    AnyInstruction::Real(Instruction::StoreFastLoadFast { .. }) => {
2386                        let r = ref_stack_pop(&mut refs);
2387                        store_local(&mut instr_flags, &refs, (arg_u32 >> 4) as isize, r);
2388                        push_ref(&mut refs, i as isize, (arg_u32 & 15) as isize)?;
2389                    }
2390                    AnyInstruction::Real(Instruction::StoreFastStoreFast { .. }) => {
2391                        let r1 = ref_stack_pop(&mut refs);
2392                        store_local(&mut instr_flags, &refs, (arg_u32 >> 4) as isize, r1);
2393                        let r2 = ref_stack_pop(&mut refs);
2394                        store_local(&mut instr_flags, &refs, (arg_u32 & 15) as isize, r2);
2395                    }
2396                    AnyInstruction::Real(Instruction::Copy { i: _ }) => {
2397                        let depth = arg_u32 as usize;
2398                        assert!(depth > 0);
2399                        assert!(refs.size >= depth);
2400                        let r = ref_stack_at(&refs, refs.size - depth);
2401                        push_ref(&mut refs, r.instr, r.local)?;
2402                    }
2403                    AnyInstruction::Real(Instruction::Swap { i: _ }) => {
2404                        let depth = arg_u32 as usize;
2405                        assert!(depth >= 2);
2406                        assert!(refs.size >= depth);
2407                        ref_stack_swap_top(&mut refs, depth);
2408                    }
2409                    AnyInstruction::Real(
2410                        Instruction::FormatSimple
2411                        | Instruction::GetAnext
2412                        | Instruction::GetLen
2413                        | Instruction::GetYieldFromIter
2414                        | Instruction::ImportFrom { .. }
2415                        | Instruction::MatchKeys
2416                        | Instruction::MatchMapping
2417                        | Instruction::MatchSequence
2418                        | Instruction::WithExceptStart,
2419                    ) => {
2420                        let effect = instr.stack_effect_info(arg_u32);
2421                        let net_pushed = effect.pushed() as isize - effect.popped() as isize;
2422                        debug_assert!(net_pushed >= 0);
2423                        // CPython optimize_load_fast() shadows the outer
2424                        // instruction index in this produced-value loop.
2425                        for produced in 0..net_pushed {
2426                            push_ref(&mut refs, produced, NOT_LOCAL)?;
2427                        }
2428                    }
2429                    AnyInstruction::Real(
2430                        Instruction::DictMerge { .. }
2431                        | Instruction::DictUpdate { .. }
2432                        | Instruction::ListAppend { .. }
2433                        | Instruction::ListExtend { .. }
2434                        | Instruction::MapAdd { .. }
2435                        | Instruction::Reraise { .. }
2436                        | Instruction::SetAdd { .. }
2437                        | Instruction::SetUpdate { .. },
2438                    ) => {
2439                        let effect = instr.stack_effect_info(arg_u32);
2440                        let net_popped = effect.popped() as isize - effect.pushed() as isize;
2441                        debug_assert!(net_popped > 0);
2442                        for _ in 0..net_popped {
2443                            let _ = ref_stack_pop(&mut refs);
2444                        }
2445                    }
2446                    AnyInstruction::Real(
2447                        Instruction::EndSend | Instruction::SetFunctionAttribute { .. },
2448                    ) => {
2449                        let effect = instr.stack_effect_info(arg_u32);
2450                        debug_assert_eq!(effect.popped(), 2);
2451                        debug_assert_eq!(effect.pushed(), 1);
2452                        let tos = ref_stack_pop(&mut refs);
2453                        let _ = ref_stack_pop(&mut refs);
2454                        push_ref(&mut refs, tos.instr, tos.local)?;
2455                    }
2456                    AnyInstruction::Real(Instruction::CheckExcMatch) => {
2457                        let _ = ref_stack_pop(&mut refs);
2458                        push_ref(&mut refs, i as isize, NOT_LOCAL)?;
2459                    }
2460                    AnyInstruction::Real(Instruction::ForIter { .. }) => {
2461                        let target = info.target;
2462                        debug_assert!(target != BlockIdx::NULL);
2463                        load_fast_push_block(&mut worklist, self, target, refs.size + 1);
2464                        push_ref(&mut refs, i as isize, NOT_LOCAL)?;
2465                    }
2466                    AnyInstruction::Real(
2467                        Instruction::LoadAttr { .. } | Instruction::LoadSuperAttr { .. },
2468                    ) => {
2469                        let self_ref = ref_stack_pop(&mut refs);
2470                        if matches!(instr.real(), Some(Instruction::LoadSuperAttr { .. })) {
2471                            let _ = ref_stack_pop(&mut refs);
2472                            let _ = ref_stack_pop(&mut refs);
2473                        }
2474                        push_ref(&mut refs, i as isize, NOT_LOCAL)?;
2475                        if arg_u32 & 1 != 0 {
2476                            push_ref(&mut refs, self_ref.instr, self_ref.local)?;
2477                        }
2478                    }
2479                    AnyInstruction::Real(
2480                        Instruction::LoadSpecial { .. } | Instruction::PushExcInfo,
2481                    ) => {
2482                        let tos = ref_stack_pop(&mut refs);
2483                        push_ref(&mut refs, i as isize, NOT_LOCAL)?;
2484                        push_ref(&mut refs, tos.instr, tos.local)?;
2485                    }
2486                    AnyInstruction::Real(Instruction::Send { .. }) => {
2487                        let target = info.target;
2488                        debug_assert!(target != BlockIdx::NULL);
2489                        load_fast_push_block(&mut worklist, self, target, refs.size);
2490                        let _ = ref_stack_pop(&mut refs);
2491                        push_ref(&mut refs, i as isize, NOT_LOCAL)?;
2492                    }
2493                    _ => {
2494                        let effect = instr.stack_effect_info(arg_u32);
2495                        let num_popped = effect.popped() as usize;
2496                        let num_pushed = effect.pushed() as usize;
2497                        let target = info.target;
2498                        if instr.has_target() {
2499                            debug_assert!(target != BlockIdx::NULL);
2500                            debug_assert!(refs.size >= num_popped);
2501                            let target_depth = refs.size - num_popped + num_pushed;
2502                            load_fast_push_block(&mut worklist, self, target, target_depth);
2503                        }
2504                        if !info.is_block_push() {
2505                            for _ in 0..num_popped {
2506                                let _ = ref_stack_pop(&mut refs);
2507                            }
2508                            for _ in 0..num_pushed {
2509                                push_ref(&mut refs, i as isize, NOT_LOCAL)?;
2510                            }
2511                        }
2512                    }
2513                }
2514            }
2515
2516            let fallthrough = self[block_idx].next;
2517            let term = self[block_idx].basicblock_last_instr().copied();
2518            if let Some(term) = term
2519                && fallthrough != BlockIdx::NULL
2520                && !term.instr.is_unconditional_jump()
2521                && !term.instr.is_scope_exit()
2522            {
2523                debug_assert!(self[block_idx].bb_has_fallthrough());
2524                load_fast_push_block(&mut worklist, self, fallthrough, refs.size);
2525            }
2526
2527            for i in 0..refs.size {
2528                let r = ref_stack_at(&refs, i);
2529                if r.instr != DUMMY_INSTR {
2530                    instr_flags[r.instr as usize] |= LoadFastInstrFlag::RefUnconsumed as u8;
2531                }
2532            }
2533
2534            let block = &mut self[block_idx];
2535            let iused = block.instruction_used;
2536            let mut i = 0;
2537            while i < iused {
2538                let info = &mut block.instructions[i];
2539                if instr_flags[i] != 0 {
2540                    i += 1;
2541                    continue;
2542                }
2543
2544                match info.instr.real_opcode() {
2545                    Some(Opcode::LoadFast) => {
2546                        info.instr = Opcode::LoadFastBorrow.into();
2547                    }
2548                    Some(Opcode::LoadFastLoadFast) => {
2549                        info.instr = Opcode::LoadFastBorrowLoadFastBorrow.into();
2550                    }
2551                    _ => {}
2552                }
2553                i += 1;
2554            }
2555        }
2556
2557        Ok(())
2558    }
2559
2560    fn propagate_line_numbers(&mut self) {
2561        let mut current = BlockIdx(0);
2562        while current != BlockIdx::NULL {
2563            let Some(last) = self[current].basicblock_last_instr().copied() else {
2564                current = self[current].next;
2565                continue;
2566            };
2567
2568            let mut prev_location = no_instruction_location();
2569            for i in 0..self[current].instruction_used {
2570                if self[current].instructions[i].instruction_is_no_location() {
2571                    self[current].instructions[i].instr_set_location(prev_location);
2572                } else {
2573                    prev_location = self[current].instructions[i].instr_location();
2574                }
2575            }
2576
2577            let next = self[current].next;
2578            if self[current].bb_has_fallthrough() {
2579                debug_assert!(next != BlockIdx::NULL);
2580                if next != BlockIdx::NULL
2581                    && self[next].predecessors == 1
2582                    && self[next].instruction_used != 0
2583                    && self[next].instructions[0].instruction_is_no_location()
2584                {
2585                    self[next].instructions[0].instr_set_location(prev_location);
2586                }
2587            }
2588
2589            if last.is_jump() {
2590                let target = last.target;
2591                debug_assert!(target != BlockIdx::NULL);
2592                if self[target].predecessors == 1 {
2593                    let instr = self[target].basicblock_raw_first_instr_mut();
2594                    if instr.instruction_is_no_location() {
2595                        instr.instr_set_location(prev_location);
2596                    }
2597                }
2598            }
2599            current = self[current].next;
2600        }
2601    }
2602
2603    /// flowgraph.c remove_redundant_nops_and_pairs
2604    fn remove_redundant_nops_and_pairs(&mut self) {
2605        let mut done = false;
2606
2607        while !done {
2608            done = true;
2609            let mut instr: Option<(BlockIdx, usize)> = None;
2610            let mut block_idx = BlockIdx::new(0);
2611
2612            while block_idx != BlockIdx::NULL {
2613                self.basicblock_remove_redundant_nops(block_idx);
2614                if is_label(self[block_idx].cpython_label) {
2615                    instr = None;
2616                }
2617
2618                let len = self[block_idx].instruction_used;
2619                for instr_idx in 0..len {
2620                    let prev_instr = instr;
2621                    instr = Some((block_idx, instr_idx));
2622                    let instr_info = self[block_idx].instructions[instr_idx];
2623                    let mut prev_opcode = None;
2624                    let prev_oparg = if let Some((prev_block, prev_instr_idx)) = prev_instr {
2625                        let prev_info = self[prev_block].instructions[prev_instr_idx];
2626                        prev_opcode = prev_info.instr.real_opcode();
2627                        match prev_info.instr.real() {
2628                            Some(Instruction::Copy { i }) => i.get(prev_info.arg),
2629                            _ => u32::from(prev_info.arg),
2630                        }
2631                    } else {
2632                        0
2633                    };
2634
2635                    let opcode = instr_info.instr.real_opcode();
2636                    let is_redundant_pair = matches!(opcode, Some(Opcode::PopTop))
2637                        && (matches!(prev_opcode, Some(Opcode::LoadConst | Opcode::LoadSmallInt))
2638                            || (prev_oparg == 1 && matches!(prev_opcode, Some(Opcode::Copy))));
2639
2640                    if is_redundant_pair {
2641                        let (prev_block, prev_instr_idx) =
2642                            prev_instr.expect("redundant pair has previous");
2643                        self[prev_block].instructions[prev_instr_idx].set_to_nop();
2644                        self[block_idx].instructions[instr_idx].set_to_nop();
2645                        done = false;
2646                    }
2647                }
2648
2649                let instr_is_jump = instr.is_some_and(|(instr_block, instr_idx)| {
2650                    self[instr_block].instructions[instr_idx].is_jump()
2651                });
2652
2653                let block = &self[block_idx];
2654                if instr_is_jump || !block.bb_has_fallthrough() {
2655                    instr = None;
2656                }
2657                block_idx = block.next;
2658            }
2659        }
2660    }
2661
2662    /// flowgraph.c calculate_stackdepth
2663    fn calculate_stackdepth(&mut self) -> crate::InternalResult<u32> {
2664        let mut current = BlockIdx(0);
2665        while current != BlockIdx::NULL {
2666            self[current.idx()].start_depth = START_DEPTH_UNSET;
2667            current = self[current.idx()].next;
2668        }
2669        let mut stack = self.make_cfg_traversal_stack()?;
2670        let mut maxdepth = 0i32;
2671        stackdepth_push(&mut stack, self, BlockIdx(0), 0)?;
2672        while let Some(block_idx) = stack.pop() {
2673            let mut depth = self[block_idx].start_depth;
2674            debug_assert!(depth >= 0);
2675            let mut next = self[block_idx].next;
2676            let instr_count = self[block_idx].instruction_used;
2677            for i in 0..instr_count {
2678                let ins = self[block_idx].instructions[i];
2679                let instr = &ins.instr;
2680                let effects = get_stack_effects(*instr, ins.arg, 0)?;
2681                let new_depth = depth + effects.net;
2682                if new_depth < 0 {
2683                    return Err(InternalError::StackUnderflow);
2684                }
2685                maxdepth = maxdepth.max(depth);
2686                if instr.has_target() && !matches!(instr.real(), Some(Instruction::EndAsyncFor)) {
2687                    debug_assert!(ins.target != BlockIdx::NULL);
2688                    let effects = get_stack_effects(*instr, ins.arg, 1)?;
2689                    let target_depth = depth + effects.net;
2690                    debug_assert!(target_depth >= 0);
2691                    maxdepth = maxdepth.max(depth);
2692                    stackdepth_push(&mut stack, self, ins.target, target_depth)?;
2693                }
2694                depth = new_depth;
2695                debug_assert!(!instr.is_assembler());
2696                if instr.is_unconditional_jump() || instr.is_scope_exit() {
2697                    next = BlockIdx::NULL;
2698                    break;
2699                }
2700            }
2701
2702            if next != BlockIdx::NULL {
2703                debug_assert!(self[block_idx].bb_has_fallthrough());
2704                stackdepth_push(&mut stack, self, next, depth)?;
2705            }
2706        }
2707
2708        let stackdepth = maxdepth;
2709        Ok(stackdepth as u32)
2710    }
2711
2712    /// flowgraph.c make_cfg_traversal_stack
2713    fn make_cfg_traversal_stack(&mut self) -> crate::InternalResult<CfgTraversalStack> {
2714        debug_assert!(!self.is_empty());
2715
2716        let mut nblocks = 0;
2717        let mut current = BlockIdx(0);
2718        while current != BlockIdx::NULL {
2719            self[current].visited = false;
2720            nblocks += 1;
2721            current = self[current].next;
2722        }
2723        debug_assert!(nblocks > 0);
2724        let mut stack = Vec::new();
2725        stack
2726            .try_reserve_exact(nblocks)
2727            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
2728        stack.resize(nblocks, BlockIdx::NULL);
2729        let stack = CfgTraversalStack { stack, sp: 0 };
2730        debug_assert_eq!(stack.capacity(), nblocks);
2731        Ok(stack)
2732    }
2733
2734    /// flowgraph.c normalize_jumps
2735    fn normalize_jumps(&mut self) -> crate::InternalResult<()> {
2736        let mut current = BlockIdx(0);
2737        while current != BlockIdx::NULL {
2738            self[current].visited = false;
2739            current = self[current].next;
2740        }
2741
2742        let mut current = BlockIdx(0);
2743        while current != BlockIdx::NULL {
2744            self[current].visited = true;
2745            self.normalize_jumps_in_block(current)?;
2746            current = self[current].next;
2747        }
2748
2749        Ok(())
2750    }
2751
2752    /// flowgraph.c remove_unused_consts
2753    fn remove_unused_consts(&mut self, consts: &mut ConstantPool) -> crate::InternalResult<()> {
2754        let nconsts = consts.len();
2755        if nconsts == 0 {
2756            return Ok(());
2757        }
2758
2759        let mut index_map = Vec::new();
2760        index_map
2761            .try_reserve_exact(nconsts)
2762            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
2763        index_map.resize(nconsts, 0isize);
2764
2765        index_map[1..nconsts].fill(-1);
2766
2767        // The first constant may be docstring; keep it always.
2768        index_map[0] = 0;
2769
2770        // Mark used consts.
2771        let mut block_idx = BlockIdx(0);
2772        while block_idx != BlockIdx::NULL {
2773            let block = &self[block_idx];
2774            for instr in block.instructions.iter().take(block.instruction_used) {
2775                if instr.instr.has_const() {
2776                    let index = u32::from(instr.arg) as usize;
2777                    debug_assert!(index < nconsts);
2778                    index_map[index] = index as isize;
2779                }
2780            }
2781            block_idx = block.next;
2782        }
2783
2784        // Now index_map[i] == i if consts[i] is used, -1 otherwise.
2785        // Condense consts.
2786        let mut n_used_consts = 0;
2787        for i in 0..nconsts {
2788            if index_map[i] != -1 {
2789                debug_assert_eq!(index_map[i], i as isize);
2790                index_map[n_used_consts] = index_map[i];
2791                n_used_consts += 1;
2792            }
2793        }
2794
2795        if n_used_consts == nconsts {
2796            return Ok(());
2797        }
2798
2799        // Move all used consts to the beginning of the consts list.
2800        debug_assert!(n_used_consts < nconsts);
2801        for (i, item) in index_map.iter().enumerate().take(n_used_consts) {
2802            let old_index = *item as usize;
2803            debug_assert!(i <= old_index && old_index < nconsts);
2804            if i != old_index {
2805                let value = consts.constants[old_index].clone();
2806                consts.constants[i] = value;
2807            }
2808        }
2809
2810        // Truncate the consts list at its new size.
2811        consts.constants.truncate(n_used_consts);
2812
2813        // Adjust const indices in the bytecode.
2814        let mut reverse_index_map = Vec::new();
2815        reverse_index_map
2816            .try_reserve_exact(nconsts)
2817            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
2818        reverse_index_map.resize(nconsts, 0isize);
2819
2820        reverse_index_map[..nconsts].fill(-1);
2821        for (i, old_index) in index_map.iter().enumerate().take(n_used_consts) {
2822            debug_assert!(*old_index != -1);
2823            let old_index = *old_index as usize;
2824            debug_assert_eq!(reverse_index_map[old_index], -1);
2825            reverse_index_map[old_index] = i as isize;
2826        }
2827
2828        block_idx = BlockIdx(0);
2829        while block_idx != BlockIdx::NULL {
2830            let next_block = self[block_idx].next;
2831            let block = &mut self[block_idx];
2832            for i in 0..block.instruction_used {
2833                let instr = &mut block.instructions[i];
2834                if instr.instr.has_const() {
2835                    let index = u32::from(instr.arg) as usize;
2836                    debug_assert!(reverse_index_map[index] >= 0);
2837                    debug_assert!(reverse_index_map[index] < n_used_consts as isize);
2838                    instr.arg = OpArg::new(reverse_index_map[index] as u32);
2839                }
2840            }
2841            block_idx = next_block;
2842        }
2843        Ok(())
2844    }
2845
2846    /// flowgraph.c insert_superinstructions
2847    fn insert_superinstructions(&mut self) -> usize {
2848        let mut block_idx = BlockIdx(0);
2849        while block_idx != BlockIdx::NULL {
2850            let next_block = self[block_idx].next;
2851            let block = &mut self[block_idx];
2852            for i in 0..block.instruction_used {
2853                let nextop = (i + 1 < block.instruction_used)
2854                    .then(|| block.instructions[i + 1].instr.real_opcode())
2855                    .flatten();
2856
2857                let super_op = match (block.instructions[i].instr.real_opcode(), nextop) {
2858                    (Some(Opcode::LoadFast), Some(Opcode::LoadFast)) => {
2859                        Some(Opcode::LoadFastLoadFast)
2860                    }
2861
2862                    (Some(Opcode::StoreFast), Some(Opcode::LoadFast)) => {
2863                        Some(Opcode::StoreFastLoadFast)
2864                    }
2865
2866                    (Some(Opcode::StoreFast), Some(Opcode::StoreFast)) => {
2867                        Some(Opcode::StoreFastStoreFast)
2868                    }
2869
2870                    (_, _) => None,
2871                };
2872
2873                if let Some(super_op) = super_op {
2874                    let (inst1, rest) = block.instructions[i..].split_at_mut(1);
2875
2876                    InstructionInfo::make_super_instruction(
2877                        &mut inst1[0],
2878                        &mut rest[0],
2879                        super_op.into(),
2880                    );
2881                }
2882            }
2883
2884            block_idx = next_block;
2885        }
2886
2887        let res = self.remove_redundant_nops();
2888
2889        #[cfg(debug_assertions)]
2890        assert!(self.no_redundant_nops());
2891
2892        res
2893    }
2894
2895    /// Mark exception handler target blocks.
2896    /// flowgraph.c mark_except_handlers
2897    pub(crate) fn mark_except_handlers(&mut self) {
2898        #[cfg(debug_assertions)]
2899        {
2900            let mut block_idx = BlockIdx(0);
2901            while block_idx != BlockIdx::NULL {
2902                assert!(!self[block_idx].except_handler);
2903                block_idx = self[block_idx].next;
2904            }
2905        }
2906
2907        let mut block_idx = BlockIdx(0);
2908        while block_idx != BlockIdx::NULL {
2909            let next = self[block_idx].next;
2910            let instr_count = self[block_idx].instruction_used;
2911            for i in 0..instr_count {
2912                let instr = self[block_idx].instructions[i];
2913                if instr.is_block_push() {
2914                    debug_assert!(instr.target != BlockIdx::NULL);
2915                    self[instr.target].except_handler = true;
2916                }
2917            }
2918            block_idx = next;
2919        }
2920    }
2921
2922    /// flowgraph.c mark_cold (two-pass).
2923    ///
2924    /// Phase 1 (mark_warm): propagate "warm" from entry via fall-through and
2925    /// jump targets. The pass asserts while visiting warm blocks that they are not
2926    /// exception handlers.
2927    ///
2928    /// Phase 2 (mark_cold): propagate "cold" from except_handler blocks via
2929    /// forward edges. Blocks reached only via runtime exception dispatch are
2930    /// marked cold and pushed to the end by push_cold_blocks_to_end.
2931    ///
2932    /// Blocks reached by neither phase remain `cold=false`. They are typically
2933    /// empty unreachable placeholders left by remove_unreachable; they stay in
2934    /// their original chain position (e.g. between entry and the post-try
2935    /// continuation for a nested try/except whose inner_end was emptied by
2936    /// optimize_cfg). This is necessary for
2937    /// optimize_load_fast to terminate fall-through at those placeholders.
2938    /// flowgraph.c mark_warm
2939    fn mark_warm(&mut self) -> crate::InternalResult<()> {
2940        let mut stack = self.make_cfg_traversal_stack()?;
2941        stack.push(BlockIdx(0));
2942        self[0].visited = true;
2943        while let Some(block_idx) = stack.pop() {
2944            debug_assert!(!self[block_idx].except_handler);
2945            self[block_idx].warm = true;
2946
2947            let next = self[block_idx].next;
2948            if next != BlockIdx::NULL && self[block_idx].bb_has_fallthrough() && !self[next].visited
2949            {
2950                stack.push(next);
2951                self[next].visited = true;
2952            }
2953
2954            let instr_count = self[block_idx].instruction_used;
2955            for i in 0..instr_count {
2956                let instr = self[block_idx].instructions[i];
2957                if instr.is_jump() {
2958                    let target = instr.target;
2959                    debug_assert!(target != BlockIdx::NULL);
2960                    if !self[target].visited {
2961                        stack.push(target);
2962                        self[target].visited = true;
2963                    }
2964                }
2965            }
2966        }
2967        Ok(())
2968    }
2969
2970    fn mark_cold(&mut self) -> crate::InternalResult<()> {
2971        let mut block_idx = BlockIdx(0);
2972        while block_idx != BlockIdx::NULL {
2973            let block = &mut self[block_idx];
2974            debug_assert!(!block.cold);
2975            debug_assert!(!block.warm);
2976            block_idx = block.next;
2977        }
2978
2979        self.mark_warm()?;
2980
2981        let mut cold_stack = self.make_cfg_traversal_stack()?;
2982        block_idx = BlockIdx(0);
2983        while block_idx != BlockIdx::NULL {
2984            let next = self[block_idx].next;
2985            let block = &self[block_idx];
2986            if block.except_handler {
2987                debug_assert!(!block.warm);
2988                cold_stack.push(block_idx);
2989                self[block_idx].visited = true;
2990            }
2991            block_idx = next;
2992        }
2993
2994        while let Some(block_idx) = cold_stack.pop() {
2995            self[block_idx].cold = true;
2996            let next = self[block_idx].next;
2997            if next != BlockIdx::NULL
2998                && self[block_idx].bb_has_fallthrough()
2999                && !self[next].warm
3000                && !self[next].visited
3001            {
3002                cold_stack.push(next);
3003                self[next].visited = true;
3004            }
3005
3006            let instr_count = self[block_idx].instruction_used;
3007            for i in 0..instr_count {
3008                let instr = self[block_idx].instructions[i];
3009                if instr.is_jump() {
3010                    debug_assert_eq!(i, instr_count - 1);
3011                    let target = instr.target;
3012                    debug_assert!(target != BlockIdx::NULL);
3013                    if !self[target].warm && !self[target].visited {
3014                        cold_stack.push(target);
3015                        self[target].visited = true;
3016                    }
3017                }
3018            }
3019        }
3020        Ok(())
3021    }
3022
3023    /// flowgraph.c push_cold_blocks_to_end
3024    fn push_cold_blocks_to_end(&mut self) -> crate::InternalResult<()> {
3025        if self[0].next == BlockIdx::NULL {
3026            return Ok(());
3027        }
3028
3029        self.mark_cold()?;
3030        let mut next_label = get_max_label(self) + 1;
3031
3032        // If a cold block falls through to a warm block, add an explicit jump
3033        let mut block_idx = BlockIdx(0);
3034        while block_idx != BlockIdx::NULL {
3035            let next = self[block_idx].next;
3036            if self[block_idx].cold
3037                && self[block_idx].bb_has_fallthrough()
3038                && next != BlockIdx::NULL
3039                && self[next].warm
3040            {
3041                let explicit_jump = self.blocks_new_block()?;
3042                if !is_label(self[next].cpython_label) {
3043                    self[next].cpython_label = InstructionSequenceLabel::from_index(next_label);
3044                    next_label += 1;
3045                }
3046                let jump_label = self[next].cpython_label;
3047                debug_assert!(is_label(jump_label));
3048                self[explicit_jump].basicblock_addop(InstructionInfo {
3049                    instr: PseudoOpcode::JumpNoInterrupt.into(),
3050                    arg: instruction_sequence_label_oparg(jump_label),
3051                    target: BlockIdx::NULL,
3052                    location: SourceLocation::default(),
3053                    end_location: SourceLocation::default(),
3054                    except_handler: None,
3055                    lineno_override: Some(NO_LOCATION_OVERRIDE),
3056                })?;
3057                self[explicit_jump].cold = true;
3058                self[explicit_jump].next = next;
3059                self[explicit_jump].predecessors = 1;
3060                self[block_idx].next = explicit_jump;
3061                let target = self[explicit_jump].next;
3062                let last = self[explicit_jump]
3063                    .basicblock_last_instr_mut()
3064                    .expect("missing explicit jump");
3065                last.target = target;
3066            }
3067            block_idx = self[block_idx].next;
3068        }
3069
3070        assert!(!self[0].cold);
3071        let mut cold_blocks: BlockIdx = BlockIdx::NULL;
3072        let mut cold_blocks_tail: BlockIdx = BlockIdx::NULL;
3073        let mut block_idx = BlockIdx(0);
3074
3075        while self[block_idx].next != BlockIdx::NULL {
3076            debug_assert!(!self[block_idx].cold);
3077            while self[block_idx].next != BlockIdx::NULL && !self[self[block_idx].next].cold {
3078                block_idx = self[block_idx].next;
3079            }
3080
3081            if self[block_idx].next == BlockIdx::NULL {
3082                break;
3083            }
3084
3085            debug_assert!(!self[block_idx].cold);
3086            debug_assert!(self[self[block_idx].next].cold);
3087
3088            let mut block_end = self[block_idx].next;
3089            while self[block_end].next != BlockIdx::NULL && self[self[block_end].next].cold {
3090                block_end = self[block_end].next;
3091            }
3092
3093            debug_assert!(self[block_end].cold);
3094            debug_assert!(
3095                self[block_end].next == BlockIdx::NULL || !self[self[block_end].next].cold
3096            );
3097
3098            if cold_blocks == BlockIdx::NULL {
3099                cold_blocks = self[block_idx].next;
3100            } else {
3101                self[cold_blocks_tail].next = self[block_idx].next;
3102            }
3103
3104            cold_blocks_tail = block_end;
3105            self[block_idx].next = self[block_end].next;
3106            self[block_end].next = BlockIdx::NULL;
3107        }
3108
3109        debug_assert!(self[block_idx].next == BlockIdx::NULL);
3110        self[block_idx].next = cold_blocks;
3111
3112        if cold_blocks != BlockIdx::NULL {
3113            self.remove_redundant_nops_and_jumps()?;
3114        }
3115        Ok(())
3116    }
3117
3118    /// flowgraph.c check_cfg
3119    fn check_cfg(&self) -> crate::InternalResult<()> {
3120        let mut block_idx = BlockIdx(0);
3121        while block_idx != BlockIdx::NULL {
3122            let block = &self[block_idx];
3123            for i in 0..block.instruction_used {
3124                let opcode = block.instructions[i].instr;
3125                debug_assert!(!opcode.is_assembler());
3126                if opcode.is_terminator() && i != block.instruction_used - 1 {
3127                    return Err(InternalError::MalformedControlFlowGraph);
3128                }
3129            }
3130            block_idx = block.next;
3131        }
3132        Ok(())
3133    }
3134
3135    /// flowgraph.c jump_thread
3136    fn jump_thread(
3137        &mut self,
3138        block_idx: BlockIdx,
3139        instr_idx: usize,
3140        target: &InstructionInfo,
3141        opcode: AnyInstruction,
3142    ) -> crate::InternalResult<bool> {
3143        debug_assert!(self[block_idx].instructions[instr_idx].is_jump());
3144        debug_assert!(target.is_jump());
3145        debug_assert_eq!(instr_idx + 1, self[block_idx].instruction_used);
3146        debug_assert!(target.target != BlockIdx::NULL);
3147
3148        if self[block_idx].instructions[instr_idx].target != target.target {
3149            self[block_idx].instructions[instr_idx].set_to_nop();
3150            self.basicblock_add_jump(block_idx, opcode, target.target, target)?;
3151            return Ok(true);
3152        }
3153
3154        Ok(false)
3155    }
3156
3157    /// flowgraph.c basicblock_add_jump
3158    fn basicblock_add_jump(
3159        &mut self,
3160        block_idx: BlockIdx,
3161        instr: AnyInstruction,
3162        target: BlockIdx,
3163        loc_source: &InstructionInfo,
3164    ) -> crate::InternalResult<()> {
3165        let last = self[block_idx].basicblock_last_instr();
3166        if last.is_some_and(|l| l.is_jump()) {
3167            return Err(InternalError::MalformedControlFlowGraph);
3168        }
3169        debug_assert!(target != BlockIdx::NULL);
3170        let label = self[target].cpython_label;
3171        debug_assert!(is_label(label));
3172        let arg = instruction_sequence_label_oparg(label);
3173        let block = &mut self[block_idx];
3174        block.basicblock_addop(InstructionInfo {
3175            instr,
3176            arg,
3177            target: BlockIdx::NULL,
3178            location: loc_source.location,
3179            end_location: loc_source.end_location,
3180            except_handler: None,
3181            lineno_override: loc_source.lineno_override,
3182        })?;
3183        let last = block.basicblock_last_instr_mut().expect("missing jump");
3184        debug_assert!(match (last.instr, instr) {
3185            (AnyInstruction::Real(last), AnyInstruction::Real(opcode)) =>
3186                last.as_opcode() == opcode.as_opcode(),
3187            (AnyInstruction::Pseudo(last), AnyInstruction::Pseudo(opcode)) =>
3188                last.as_opcode() == opcode.as_opcode(),
3189            _ => false,
3190        });
3191        last.target = target;
3192        Ok(())
3193    }
3194
3195    /// flowgraph.c convert_pseudo_conditional_jumps
3196    fn convert_pseudo_conditional_jumps(&mut self) -> crate::InternalResult<()> {
3197        let mut block_idx = BlockIdx(0);
3198        while block_idx != BlockIdx::NULL {
3199            let next = self[block_idx].next;
3200            let block = &mut self[block_idx];
3201            let mut i = 0;
3202            while i < block.instruction_used {
3203                let instr = block.instructions[i];
3204                let opcode = instr.instr;
3205                if matches!(
3206                    opcode.pseudo_opcode(),
3207                    Some(PseudoOpcode::JumpIfFalse | PseudoOpcode::JumpIfTrue)
3208                ) {
3209                    debug_assert_eq!(i, block.instruction_used - 1);
3210                    block.instructions[i].instr =
3211                        if matches!(opcode.pseudo_opcode(), Some(PseudoOpcode::JumpIfFalse)) {
3212                            Opcode::PopJumpIfFalse
3213                        } else {
3214                            Opcode::PopJumpIfTrue
3215                        }
3216                        .into();
3217
3218                    let location = instr.location;
3219                    let end_location = instr.end_location;
3220                    let except_handler = instr.except_handler;
3221                    let lineno_override = instr.lineno_override;
3222                    let copy = InstructionInfo {
3223                        instr: Opcode::Copy.into(),
3224                        arg: OpArg::new(1),
3225                        target: BlockIdx::NULL,
3226                        location,
3227                        end_location,
3228                        except_handler,
3229                        lineno_override,
3230                    };
3231                    block.basicblock_insert_instruction(i, copy)?;
3232                    i += 1;
3233
3234                    let to_bool = InstructionInfo {
3235                        instr: Opcode::ToBool.into(),
3236                        arg: OpArg::new(0),
3237                        target: BlockIdx::NULL,
3238                        location,
3239                        end_location,
3240                        except_handler,
3241                        lineno_override,
3242                    };
3243                    block.basicblock_insert_instruction(i, to_bool)?;
3244                    i += 1;
3245                }
3246                i += 1;
3247            }
3248            block_idx = next;
3249        }
3250        Ok(())
3251    }
3252
3253    /// flowgraph.c normalize_jumps_in_block
3254    fn normalize_jumps_in_block(&mut self, block_idx: BlockIdx) -> crate::InternalResult<()> {
3255        let Some(last_ins) = self[block_idx].basicblock_last_instr().copied() else {
3256            return Ok(());
3257        };
3258        if !is_conditional_jump_opcode(last_ins.instr) {
3259            return Ok(());
3260        }
3261        debug_assert!(!last_ins.instr.is_assembler());
3262
3263        debug_assert!(last_ins.target != BlockIdx::NULL);
3264        let is_forward = !self[last_ins.target].visited;
3265
3266        if is_forward {
3267            // Insert NOT_TAKEN after forward conditional jump.
3268            let not_taken = InstructionInfo {
3269                instr: Opcode::NotTaken.into(),
3270                arg: OpArg::new(0),
3271                target: BlockIdx::NULL,
3272                location: last_ins.location,
3273                end_location: last_ins.end_location,
3274                except_handler: None,
3275                lineno_override: last_ins.lineno_override,
3276            };
3277
3278            self[block_idx].basicblock_addop(not_taken)?;
3279            return Ok(());
3280        }
3281
3282        let reversed_opcode = match last_ins.instr.real_opcode() {
3283            Some(Opcode::PopJumpIfNotNone) => Opcode::PopJumpIfNone.into(),
3284            Some(Opcode::PopJumpIfNone) => Opcode::PopJumpIfNotNone.into(),
3285            Some(Opcode::PopJumpIfFalse) => Opcode::PopJumpIfTrue.into(),
3286            Some(Opcode::PopJumpIfTrue) => Opcode::PopJumpIfFalse.into(),
3287            _ => unreachable!("conditional jump has reverse opcode"),
3288        };
3289
3290        // Transform 'conditional jump T' to 'reversed_jump b_next' followed by
3291        // 'jump_backwards T'.
3292        let loc = last_ins.location;
3293        let end_loc = last_ins.end_location;
3294
3295        let target = last_ins.target;
3296        let backwards_jump_idx = self.blocks_new_block()?;
3297
3298        self[backwards_jump_idx].basicblock_addop(InstructionInfo {
3299            instr: Opcode::NotTaken.into(),
3300            arg: OpArg::new(0),
3301            target: BlockIdx::NULL,
3302            location: loc,
3303            end_location: end_loc,
3304            except_handler: None,
3305            lineno_override: last_ins.lineno_override,
3306        })?;
3307        self.basicblock_add_jump(
3308            backwards_jump_idx,
3309            PseudoOpcode::Jump.into(),
3310            target,
3311            &last_ins,
3312        )?;
3313        self[backwards_jump_idx].start_depth = self[target].start_depth;
3314
3315        let old_next = self[block_idx].next;
3316        debug_assert!(old_next != BlockIdx::NULL);
3317
3318        let last_mut = self[block_idx].basicblock_last_instr_mut().unwrap();
3319        last_mut.instr = reversed_opcode;
3320        last_mut.target = old_next;
3321
3322        self[backwards_jump_idx].cold = self[block_idx].cold;
3323        self[backwards_jump_idx].next = old_next;
3324        self[block_idx].next = backwards_jump_idx;
3325        Ok(())
3326    }
3327
3328    /// flowgraph.c basicblock_inline_small_or_no_lineno_blocks
3329    fn basicblock_inline_small_or_no_lineno_blocks(
3330        &mut self,
3331        block_idx: BlockIdx,
3332    ) -> crate::InternalResult<bool> {
3333        let Some(last) = self[block_idx].basicblock_last_instr().copied() else {
3334            return Ok(false);
3335        };
3336
3337        if !last.instr.is_unconditional_jump() {
3338            return Ok(false);
3339        }
3340
3341        let target = last.target;
3342        debug_assert!(target != BlockIdx::NULL);
3343        let small_exit_block =
3344            self[target].basicblock_exits_scope() && self[target].instruction_used <= MAX_COPY_SIZE;
3345        let no_lineno_no_fallthrough =
3346            self[target].basicblock_has_no_lineno() && !self[target].bb_has_fallthrough();
3347        if small_exit_block || no_lineno_no_fallthrough {
3348            debug_assert!(last.is_jump());
3349            let removed_jump_opcode = last.instr;
3350            let last = self[block_idx]
3351                .basicblock_last_instr_mut()
3352                .expect("non-empty block has last instruction");
3353            last.set_to_nop();
3354            self.basicblock_append_block_instructions(block_idx, target)?;
3355            if no_lineno_no_fallthrough {
3356                let last = self[block_idx].basicblock_last_instr_mut().unwrap();
3357                if last.instr.is_unconditional_jump()
3358                    && matches!(
3359                        removed_jump_opcode.into(),
3360                        AnyOpcode::Pseudo(PseudoOpcode::Jump)
3361                    )
3362                {
3363                    last.instr = PseudoOpcode::Jump.into();
3364                }
3365            }
3366            self[target].predecessors -= 1;
3367            return Ok(true);
3368        }
3369        Ok(false)
3370    }
3371
3372    /// flowgraph.c inline_small_or_no_lineno_blocks
3373    fn inline_small_or_no_lineno_blocks(&mut self) -> crate::InternalResult<bool> {
3374        loop {
3375            let mut changes = false;
3376            let mut current = BlockIdx(0);
3377            while current != BlockIdx::NULL {
3378                let next = self[current].next;
3379                let res = self.basicblock_inline_small_or_no_lineno_blocks(current)?;
3380                if res {
3381                    changes = true;
3382                }
3383
3384                current = next;
3385            }
3386            if !changes {
3387                return Ok(changes);
3388            }
3389        }
3390    }
3391
3392    /// flowgraph.c basicblock_remove_redundant_nops
3393    fn basicblock_remove_redundant_nops(&mut self, block_idx: BlockIdx) -> usize {
3394        let mut dest = 0;
3395        let mut prev_lineno = -1i32;
3396        let instr_count = self[block_idx].instruction_used;
3397
3398        for src in 0..instr_count {
3399            let instr = self[block_idx].instructions[src];
3400            let lineno = instr.instruction_lineno();
3401
3402            if matches!(instr.instr.real(), Some(Instruction::Nop)) {
3403                if lineno < 0 {
3404                    continue;
3405                }
3406                if prev_lineno == lineno {
3407                    continue;
3408                }
3409                if src < instr_count - 1 {
3410                    let next_lineno = self[block_idx].instructions[src + 1].instruction_lineno();
3411                    if next_lineno == lineno {
3412                        continue;
3413                    }
3414                    if next_lineno < 0 {
3415                        self[block_idx].instructions[src + 1].instr_set_loc(
3416                            instr.location,
3417                            instr.end_location,
3418                            instr.lineno_override,
3419                        );
3420                        continue;
3421                    }
3422                } else {
3423                    let next = next_nonempty_block(self, self[block_idx].next);
3424                    if next != BlockIdx::NULL {
3425                        let mut next_loc = no_linetable_location();
3426                        let mut next_i = 0;
3427                        while next_i < self[next].instruction_used {
3428                            let instr = self[next].instructions[next_i];
3429                            if matches!(instr.instr.real(), Some(Instruction::Nop))
3430                                && instr.instruction_lineno() < 0
3431                            {
3432                                next_i += 1;
3433                                continue;
3434                            }
3435                            next_loc = instr.instruction_linetable_location();
3436                            break;
3437                        }
3438                        if lineno == next_loc.line {
3439                            continue;
3440                        }
3441                    }
3442                }
3443            }
3444
3445            if dest != src {
3446                self[block_idx].instructions[dest] = self[block_idx].instructions[src];
3447            }
3448            dest += 1;
3449            prev_lineno = lineno;
3450        }
3451
3452        debug_assert!(dest <= instr_count);
3453        let num_removed = instr_count - dest;
3454        self[block_idx].instruction_used = dest;
3455        num_removed
3456    }
3457
3458    /// flowgraph.c remove_redundant_nops
3459    fn remove_redundant_nops(&mut self) -> usize {
3460        let mut changes = 0;
3461        let mut current = BlockIdx(0);
3462        while current != BlockIdx::NULL {
3463            let next = self[current].next;
3464            let change = self.basicblock_remove_redundant_nops(current);
3465            changes += change;
3466            current = next;
3467        }
3468        changes
3469    }
3470
3471    /// flowgraph.c no_redundant_nops
3472    #[cfg(debug_assertions)]
3473    fn no_redundant_nops(&mut self) -> bool {
3474        self.remove_redundant_nops() == 0
3475    }
3476
3477    /// flowgraph.c remove_redundant_jumps
3478    fn remove_redundant_jumps(&mut self) -> crate::InternalResult<usize> {
3479        let mut changes = 0;
3480        let mut current = BlockIdx(0);
3481        while current != BlockIdx::NULL {
3482            let Some(last) = self[current].basicblock_last_instr().copied() else {
3483                current = self[current].next;
3484                continue;
3485            };
3486
3487            debug_assert!(!last.instr.is_assembler());
3488            if last.instr.is_unconditional_jump() {
3489                let jump_target = next_nonempty_block(self, last.target);
3490                if jump_target == BlockIdx::NULL {
3491                    return Err(InternalError::MalformedControlFlowGraph);
3492                }
3493                let next = next_nonempty_block(self, self[current].next);
3494                if jump_target == next {
3495                    changes += 1;
3496                    let last = self[current].basicblock_last_instr_mut().unwrap();
3497                    last.set_to_nop();
3498                }
3499            }
3500            current = self[current].next;
3501        }
3502        Ok(changes)
3503    }
3504
3505    /// flowgraph.c no_redundant_jumps
3506    #[cfg(debug_assertions)]
3507    fn no_redundant_jumps(&self) -> bool {
3508        let mut current = BlockIdx(0);
3509        while current != BlockIdx::NULL {
3510            let block = &self[current];
3511            if let Some(last) = block.basicblock_last_instr()
3512                && last.instr.is_unconditional_jump()
3513            {
3514                let next = next_nonempty_block(self, block.next);
3515                let jump_target = next_nonempty_block(self, last.target);
3516                if jump_target == next {
3517                    assert!(next != BlockIdx::NULL);
3518                    if last.instruction_lineno() == self[next].instructions[0].instruction_lineno()
3519                    {
3520                        assert_ne!(
3521                            last.instruction_lineno(),
3522                            self[next].instructions[0].instruction_lineno(),
3523                            "redundant jump has same line as fallthrough target"
3524                        );
3525                        return false;
3526                    }
3527                }
3528            }
3529            current = block.next;
3530        }
3531        true
3532    }
3533
3534    fn remove_redundant_nops_and_jumps(&mut self) -> crate::InternalResult<()> {
3535        loop {
3536            // Convergence is guaranteed because the number of redundant jumps and
3537            // nops only decreases.
3538            let removed_nops = self.remove_redundant_nops();
3539            let removed_jumps = self.remove_redundant_jumps()?;
3540            if removed_nops + removed_jumps == 0 {
3541                break;
3542            }
3543        }
3544        Ok(())
3545    }
3546
3547    fn blocks_new_block(&mut self) -> crate::InternalResult<BlockIdx> {
3548        self.try_reserve(1)
3549            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
3550        let block_idx = BlockIdx(
3551            self.len()
3552                .to_u32()
3553                .ok_or(InternalError::MalformedControlFlowGraph)?,
3554        );
3555        self.push(Block::default());
3556        Ok(block_idx)
3557    }
3558}
3559
3560impl<const N: usize> From<[Block; N]> for Blocks {
3561    fn from(value: [Block; N]) -> Self {
3562        Self(value.into())
3563    }
3564}
3565
3566impl Deref for Blocks {
3567    type Target = [Block];
3568
3569    fn deref(&self) -> &Self::Target {
3570        &self.0
3571    }
3572}
3573
3574impl DerefMut for Blocks {
3575    fn deref_mut(&mut self) -> &mut Self::Target {
3576        &mut self.0
3577    }
3578}
3579
3580impl Index<usize> for Blocks {
3581    type Output = Block;
3582
3583    fn index(&self, idx: usize) -> &Self::Output {
3584        &self.0[idx]
3585    }
3586}
3587
3588impl IndexMut<usize> for Blocks {
3589    fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
3590        &mut self.0[idx]
3591    }
3592}
3593
3594impl Index<BlockIdx> for Blocks {
3595    type Output = Block;
3596
3597    fn index(&self, block_idx: BlockIdx) -> &Self::Output {
3598        &self.0[block_idx.as_usize()]
3599    }
3600}
3601
3602impl IndexMut<BlockIdx> for Blocks {
3603    fn index_mut(&mut self, block_idx: BlockIdx) -> &mut Self::Output {
3604        &mut self.0[block_idx.as_usize()]
3605    }
3606}
3607
3608pub(crate) const START_DEPTH_UNSET: i32 = i32::MIN;
3609const CO_MAXBLOCKS: usize = 21;
3610
3611/// flowgraph.c struct _PyCfgExceptStack
3612#[derive(Clone, Debug)]
3613struct CfgExceptStack {
3614    handlers: [BlockIdx; CO_MAXBLOCKS + 2],
3615    depth: usize,
3616}
3617
3618/// flowgraph.c `basicblock **stack`
3619#[derive(Clone, Debug)]
3620struct CfgTraversalStack {
3621    stack: Vec<BlockIdx>,
3622    sp: usize,
3623}
3624
3625impl CfgTraversalStack {
3626    fn push(&mut self, block: BlockIdx) {
3627        debug_assert!(self.sp < self.stack.len());
3628        self.stack[self.sp] = block;
3629        self.sp += 1;
3630    }
3631
3632    fn pop(&mut self) -> Option<BlockIdx> {
3633        if self.sp == 0 {
3634            return None;
3635        }
3636        self.sp -= 1;
3637        Some(self.stack[self.sp])
3638    }
3639
3640    fn capacity(&self) -> usize {
3641        self.stack.len()
3642    }
3643}
3644
3645#[derive(Clone, Debug)]
3646pub(crate) struct InstructionSequenceLabelMap {
3647    block_labels: Vec<InstructionSequenceLabel>,
3648    /// Codegen-side shadow of the instruction-sequence label map.
3649    ///
3650    /// `_PyInstructionSequence_UseLabel()` can map multiple labels to the same
3651    /// instruction offset before `_PyCfg_FromInstructionSequence()` materializes
3652    /// CFG blocks. The codegen CFG path keeps the same aliasing by resolving
3653    /// those labels to the block that owns the shared offset.
3654    cpython_block_by_label: Vec<BlockIdx>,
3655}
3656
3657fn instruction_sequence_label_map_register_label(
3658    map: &mut InstructionSequenceLabelMap,
3659    label: InstructionSequenceLabel,
3660) -> crate::InternalResult<()> {
3661    debug_assert!(is_label(label));
3662    let old_size = map.cpython_block_by_label.len();
3663    let new_allocation = c_array_ensure_capacity::<i32>(
3664        old_size,
3665        label.idx(),
3666        INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE,
3667    )?;
3668    if new_allocation > old_size {
3669        if new_allocation > map.cpython_block_by_label.capacity() {
3670            map.cpython_block_by_label
3671                .try_reserve_exact(new_allocation - map.cpython_block_by_label.capacity())
3672                .map_err(|_| InternalError::MalformedControlFlowGraph)?;
3673        }
3674        map.cpython_block_by_label
3675            .resize(new_allocation, BlockIdx::NULL);
3676        for i in old_size..map.cpython_block_by_label.len() {
3677            map.cpython_block_by_label[i] = BlockIdx::NULL;
3678        }
3679    }
3680    debug_assert!(map.cpython_block_by_label.len() > label.idx());
3681    Ok(())
3682}
3683
3684fn instruction_sequence_label_map_ensure_label_for_block(
3685    map: &mut InstructionSequenceLabelMap,
3686    seq: &mut InstructionSequence,
3687    block: BlockIdx,
3688) -> crate::InternalResult<InstructionSequenceLabel> {
3689    debug_assert_ne!(block, BlockIdx::NULL);
3690    let block_label = map.block_labels[block.idx()];
3691    if is_label(block_label) {
3692        return Ok(block_label);
3693    }
3694    let label = instruction_sequence_new_label(seq);
3695    debug_assert_eq!(label.0, seq.next_free_label);
3696    instruction_sequence_label_map_register_label(map, label)?;
3697    map.cpython_block_by_label[label.idx()] = block;
3698    map.block_labels[block.idx()] = label;
3699    Ok(label)
3700}
3701
3702fn instruction_sequence_label_map_label_for_block(
3703    map: &InstructionSequenceLabelMap,
3704    block: BlockIdx,
3705) -> InstructionSequenceLabel {
3706    debug_assert_ne!(block, BlockIdx::NULL);
3707    map.block_labels
3708        .get(block.idx())
3709        .copied()
3710        .unwrap_or(InstructionSequenceLabel::NO_LABEL)
3711}
3712
3713fn instruction_sequence_label_map_block_for_label(
3714    map: &InstructionSequenceLabelMap,
3715    label: InstructionSequenceLabel,
3716) -> Option<BlockIdx> {
3717    if !is_label(label) {
3718        return None;
3719    }
3720    map.cpython_block_by_label
3721        .get(label.idx())
3722        .copied()
3723        .filter(|&block| block != BlockIdx::NULL)
3724}
3725
3726fn instruction_sequence_label_map_resolve_label(
3727    map: &InstructionSequenceLabelMap,
3728    block: BlockIdx,
3729) -> BlockIdx {
3730    if block == BlockIdx::NULL {
3731        return BlockIdx::NULL;
3732    }
3733    let label = instruction_sequence_label_map_label_for_block(map, block);
3734    if !is_label(label) {
3735        return block;
3736    }
3737    instruction_sequence_label_map_block_for_label(map, label).unwrap_or_else(|| {
3738        debug_assert!(
3739            false,
3740            "CPython instruction-sequence label must map to a codegen CFG block"
3741        );
3742        BlockIdx::NULL
3743    })
3744}
3745
3746fn instruction_sequence_label_map_resolve_label_to_block(
3747    map: &InstructionSequenceLabelMap,
3748    label: InstructionSequenceLabel,
3749) -> BlockIdx {
3750    if !is_label(label) {
3751        return BlockIdx::NULL;
3752    }
3753    instruction_sequence_label_map_block_for_label(map, label).unwrap_or_else(|| {
3754        debug_assert!(
3755            false,
3756            "CPython instruction-sequence label must map to a codegen CFG block"
3757        );
3758        BlockIdx::NULL
3759    })
3760}
3761
3762fn instruction_sequence_label_oparg(label: InstructionSequenceLabel) -> OpArg {
3763    debug_assert!(is_label(label));
3764    OpArg::new(label.idx() as u32)
3765}
3766
3767fn instruction_sequence_label_map_use_label_at_block(
3768    map: &mut InstructionSequenceLabelMap,
3769    seq: &mut InstructionSequence,
3770    from: BlockIdx,
3771    to: BlockIdx,
3772) -> crate::InternalResult<()> {
3773    if from == BlockIdx::NULL || from == to {
3774        return Ok(());
3775    }
3776    let from_label = instruction_sequence_label_map_ensure_label_for_block(map, seq, from)?;
3777    debug_assert!(map.cpython_block_by_label.len() > from_label.idx());
3778    let to_block = instruction_sequence_label_map_resolve_label(map, to);
3779    if to_block == BlockIdx::NULL {
3780        debug_assert!(
3781            false,
3782            "CPython label target must map to a codegen CFG block"
3783        );
3784        return Ok(());
3785    }
3786    map.cpython_block_by_label[from_label.idx()] = to_block;
3787    Ok(())
3788}
3789
3790fn instruction_sequence_label_map_push_unlabeled_block(
3791    map: &mut InstructionSequenceLabelMap,
3792) -> crate::InternalResult<()> {
3793    map.block_labels
3794        .try_reserve(1)
3795        .map_err(|_| InternalError::MalformedControlFlowGraph)?;
3796    map.block_labels.push(InstructionSequenceLabel::NO_LABEL);
3797    Ok(())
3798}
3799
3800fn instruction_sequence_label_map_push_unmapped_label(
3801    map: &mut InstructionSequenceLabelMap,
3802    seq: &mut InstructionSequence,
3803) -> crate::InternalResult<()> {
3804    let label = instruction_sequence_new_label(seq);
3805    debug_assert_eq!(label.0, seq.next_free_label);
3806    instruction_sequence_label_map_register_label(map, label)?;
3807    let block = BlockIdx(
3808        map.block_labels
3809            .len()
3810            .to_u32()
3811            .ok_or(InternalError::MalformedControlFlowGraph)?,
3812    );
3813    map.cpython_block_by_label[label.idx()] = block;
3814    map.block_labels
3815        .try_reserve(1)
3816        .map_err(|_| InternalError::MalformedControlFlowGraph)?;
3817    map.block_labels.push(label);
3818    Ok(())
3819}
3820
3821impl InstructionSequenceLabelMap {
3822    pub(crate) fn new() -> Self {
3823        Self {
3824            block_labels: vec![InstructionSequenceLabel::NO_LABEL],
3825            cpython_block_by_label: Vec::new(),
3826        }
3827    }
3828}
3829
3830pub struct CodeInfo {
3831    pub flags: CodeFlags,
3832    pub source_path: String,
3833    pub private: Option<String>, // For private name mangling, mostly for class
3834
3835    pub blocks: Blocks,
3836    pub current_block: BlockIdx,
3837    pub(crate) instr_sequence: InstructionSequence,
3838    pub(crate) instr_sequence_label_map: InstructionSequenceLabelMap,
3839    pub(crate) annotations_instr_sequence: Option<InstructionSequence>,
3840
3841    pub metadata: CodeUnitMetadata,
3842
3843    // For class scopes: attributes accessed via self.X
3844    pub static_attributes: Option<IndexSet<String>>,
3845
3846    // True if compiling an inlined comprehension
3847    pub in_inlined_comp: bool,
3848
3849    // Block stack for tracking nested control structures
3850    pub fblock: Vec<crate::compile::FBlockInfo>,
3851
3852    // Reference to the symbol table for this scope
3853    pub symbol_table_index: usize,
3854    // compile.c uses PyList_GET_SIZE(u->u_ste->ste_varnames)
3855    // when calling flowgraph.c _PyCfg_OptimizeCodeUnit().
3856    pub nparams: usize,
3857
3858    // PEP 649: Track nesting depth inside conditional blocks (if/for/while/etc.)
3859    // u_in_conditional_block
3860    pub in_conditional_block: u32,
3861
3862    // PEP 649: Next index for conditional annotation tracking
3863    // u_next_conditional_annotation_index
3864    pub next_conditional_annotation_index: u32,
3865}
3866
3867impl CodeInfo {
3868    pub(crate) fn addop_to_instr_sequence(
3869        &mut self,
3870        mut info: InstructionInfo,
3871    ) -> crate::InternalResult<()> {
3872        if info.instr.has_target() && info.target != BlockIdx::NULL {
3873            let label = instruction_sequence_label_map_ensure_label_for_block(
3874                &mut self.instr_sequence_label_map,
3875                &mut self.instr_sequence,
3876                info.target,
3877            )?;
3878            info.arg = instruction_sequence_label_oparg(label);
3879            info.target = BlockIdx::NULL;
3880        }
3881        instruction_sequence_addop(&mut self.instr_sequence, info)?;
3882        Ok(())
3883    }
3884
3885    pub(crate) fn addop_to_instr_sequence_with_target_label(
3886        &mut self,
3887        mut info: InstructionInfo,
3888        target_label: InstructionSequenceLabel,
3889    ) -> crate::InternalResult<()> {
3890        if !info.instr.has_target() {
3891            return Err(InternalError::MalformedControlFlowGraph);
3892        }
3893        info.arg = instruction_sequence_label_oparg(target_label);
3894        info.target = BlockIdx::NULL;
3895        instruction_sequence_addop(&mut self.instr_sequence, info)?;
3896        Ok(())
3897    }
3898
3899    pub(crate) fn addop_to_current_block(
3900        &mut self,
3901        info: InstructionInfo,
3902    ) -> crate::InternalResult<()> {
3903        self.blocks[self.current_block].basicblock_addop(info)
3904    }
3905
3906    pub(crate) fn last_current_block_instr_mut(&mut self) -> Option<&mut InstructionInfo> {
3907        self.blocks[self.current_block].basicblock_last_instr_mut()
3908    }
3909
3910    pub(crate) fn set_last_instr_sequence_lineno_override(&mut self, lineno_override: i32) {
3911        if let Some(last) = instruction_sequence_last_info_mut(&mut self.instr_sequence) {
3912            last.lineno_override = Some(lineno_override);
3913        }
3914    }
3915
3916    pub(crate) fn use_instr_sequence_label(
3917        &mut self,
3918        block: BlockIdx,
3919    ) -> crate::InternalResult<()> {
3920        let label = instruction_sequence_label_map_ensure_label_for_block(
3921            &mut self.instr_sequence_label_map,
3922            &mut self.instr_sequence,
3923            block,
3924        )?;
3925        instruction_sequence_use_label(&mut self.instr_sequence, label)
3926    }
3927
3928    pub(crate) fn new_instr_sequence_label(&mut self) -> InstructionSequenceLabel {
3929        instruction_sequence_new_label(&mut self.instr_sequence)
3930    }
3931
3932    pub(crate) fn use_raw_instr_sequence_label(
3933        &mut self,
3934        label: InstructionSequenceLabel,
3935    ) -> crate::InternalResult<()> {
3936        instruction_sequence_use_label(&mut self.instr_sequence, label)
3937    }
3938
3939    pub(crate) fn mark_cpython_cfg_label(&mut self, block: BlockIdx) -> crate::InternalResult<()> {
3940        let label = instruction_sequence_label_map_ensure_label_for_block(
3941            &mut self.instr_sequence_label_map,
3942            &mut self.instr_sequence,
3943            block,
3944        )?;
3945        self.blocks[block].cpython_label = label;
3946        Ok(())
3947    }
3948
3949    pub(crate) fn resolve_instr_sequence_label(&self, block: BlockIdx) -> BlockIdx {
3950        instruction_sequence_label_map_resolve_label(&self.instr_sequence_label_map, block)
3951    }
3952
3953    pub(crate) fn block_for_instr_sequence_label(
3954        &self,
3955        label: InstructionSequenceLabel,
3956    ) -> BlockIdx {
3957        instruction_sequence_label_map_resolve_label_to_block(&self.instr_sequence_label_map, label)
3958    }
3959
3960    pub(crate) fn use_instr_sequence_label_at_block(
3961        &mut self,
3962        from: BlockIdx,
3963        to: BlockIdx,
3964    ) -> crate::InternalResult<()> {
3965        instruction_sequence_label_map_use_label_at_block(
3966            &mut self.instr_sequence_label_map,
3967            &mut self.instr_sequence,
3968            from,
3969            to,
3970        )
3971    }
3972
3973    pub(crate) fn instr_sequence_label_for_block(
3974        &mut self,
3975        block: BlockIdx,
3976    ) -> crate::InternalResult<InstructionSequenceLabel> {
3977        if block == BlockIdx::NULL {
3978            Ok(InstructionSequenceLabel::NO_LABEL)
3979        } else {
3980            instruction_sequence_label_map_ensure_label_for_block(
3981                &mut self.instr_sequence_label_map,
3982                &mut self.instr_sequence,
3983                block,
3984            )
3985        }
3986    }
3987
3988    pub(crate) fn insert_start_setup_cleanup(
3989        &mut self,
3990        handler_block: BlockIdx,
3991    ) -> crate::InternalResult<()> {
3992        let handler_label = instruction_sequence_label_map_ensure_label_for_block(
3993            &mut self.instr_sequence_label_map,
3994            &mut self.instr_sequence,
3995            handler_block,
3996        )?;
3997        instruction_sequence_insert_instruction(
3998            &mut self.instr_sequence,
3999            0,
4000            InstructionInfo {
4001                instr: PseudoOpcode::SetupCleanup.into(),
4002                arg: instruction_sequence_label_oparg(handler_label),
4003                target: BlockIdx::NULL,
4004                location: SourceLocation::default(),
4005                end_location: SourceLocation::default(),
4006                except_handler: None,
4007                lineno_override: Some(NO_LOCATION_OVERRIDE),
4008            },
4009        )
4010    }
4011
4012    pub(crate) fn push_unmapped_instr_sequence_label(&mut self) -> crate::InternalResult<()> {
4013        instruction_sequence_label_map_push_unmapped_label(
4014            &mut self.instr_sequence_label_map,
4015            &mut self.instr_sequence,
4016        )
4017    }
4018
4019    pub(crate) fn push_unlabeled_instr_sequence_block(&mut self) -> crate::InternalResult<()> {
4020        instruction_sequence_label_map_push_unlabeled_block(&mut self.instr_sequence_label_map)
4021    }
4022
4023    fn take_recorded_instr_sequence(&mut self) -> InstructionSequence {
4024        let mut instr_sequence =
4025            core::mem::replace(&mut self.instr_sequence, instruction_sequence_new());
4026        if let Some(mut annotations_instr_sequence) = self.annotations_instr_sequence.take() {
4027            instruction_sequence_apply_label_map(&mut annotations_instr_sequence);
4028            instruction_sequence_set_annotations_code(
4029                &mut instr_sequence,
4030                Some(Box::new(annotations_instr_sequence)),
4031            );
4032        }
4033
4034        instr_sequence
4035    }
4036
4037    fn prepare_cfg_from_codegen(&mut self) -> InstructionSequence {
4038        // compile.c optimize_and_assemble_code_unit passes
4039        // u_instr_sequence directly into flowgraph.c _PyCfg_FromInstructionSequence().
4040        self.take_recorded_instr_sequence()
4041    }
4042}
4043
4044fn optimize_code_unit(
4045    metadata: &mut CodeUnitMetadata,
4046    blocks: &mut Blocks,
4047    instr_sequence: InstructionSequence,
4048    nlocals: usize,
4049    nparams: usize,
4050) -> crate::InternalResult<()> {
4051    // Phase 1: _PyCfg_OptimizeCodeUnit (flowgraph.c)
4052    *blocks = cfg_from_instruction_sequence(instr_sequence)?;
4053    translate_jump_labels_to_targets(blocks)?;
4054    blocks.mark_except_handlers();
4055    label_exception_targets(blocks)?;
4056    optimize_cfg(metadata, blocks, metadata.firstlineno)?;
4057    blocks.remove_unused_consts(&mut metadata.consts)?;
4058    add_checks_for_loads_of_uninitialized_variables(blocks, nlocals, nparams)?;
4059    // Superinstructions are inserted in _PyCfg_OptimizeCodeUnit, before
4060    // later jump normalization / block reordering can create adjacencies
4061    // that never exist at this stage in flowgraph.c.
4062    blocks.insert_superinstructions();
4063    blocks.push_cold_blocks_to_end()?;
4064    // Line numbers are resolved again after cold-block extraction.
4065    blocks.resolve_line_numbers(metadata.firstlineno)?;
4066    Ok(())
4067}
4068
4069fn optimize_cfg(
4070    metadata: &mut CodeUnitMetadata,
4071    blocks: &mut Blocks,
4072    firstlineno: OneIndexed,
4073) -> crate::InternalResult<()> {
4074    // flowgraph.c optimize_cfg
4075    // optimize_cfg() starts with check_cfg() and raises
4076    // SystemError if a jump or scope exit is not the last instruction in
4077    // its block.
4078    blocks.check_cfg()?;
4079    blocks.inline_small_or_no_lineno_blocks()?;
4080    // The instruction-sequence label-map/CFG conversion is not re-run
4081    // after this point. Unreferenced label blocks left by jump inlining
4082    // remain block boundaries and can preserve line-marker NOPs.
4083    blocks.remove_unreachable()?;
4084    // optimize_cfg resolves line numbers before local checks and
4085    // superinstruction insertion, so fusion decisions see propagated
4086    // source locations.
4087    blocks.resolve_line_numbers(firstlineno)?;
4088    // optimize_cfg() runs optimize_load_const() and then
4089    // optimize_basic_block() after line numbers are resolved.
4090    optimize_load_const(metadata, blocks)?;
4091    let mut block_idx = BlockIdx(0);
4092    while block_idx != BlockIdx::NULL {
4093        let next_block = blocks[block_idx].next;
4094        blocks.optimize_basic_block(metadata, block_idx)?;
4095        block_idx = next_block;
4096    }
4097    blocks.remove_redundant_nops_and_pairs();
4098    // optimize_cfg() removes newly-unreachable blocks and
4099    // redundant NOP/jump chains before _PyCfg_OptimizeCodeUnit() prunes
4100    // unused constants.
4101    blocks.remove_unreachable()?;
4102    blocks.remove_redundant_nops_and_jumps()?;
4103    #[cfg(debug_assertions)]
4104    assert!(blocks.no_redundant_jumps());
4105    Ok(())
4106}
4107
4108fn optimized_cfg_to_instruction_sequence(
4109    metadata: &CodeUnitMetadata,
4110    flags: CodeFlags,
4111    blocks: &mut Blocks,
4112) -> crate::InternalResult<(u32, usize, InstructionSequence)> {
4113    // Phase 2: _PyCfg_OptimizedCfgToInstructionSequence (flowgraph.c)
4114    blocks.convert_pseudo_conditional_jumps()?;
4115    let max_stackdepth = blocks.calculate_stackdepth()?;
4116    debug_assert!(!is_generator(flags) || max_stackdepth != 0);
4117    let nlocalsplus = prepare_localsplus(metadata, blocks, flags)?;
4118    // Pseudo ops are lowered after stackdepth and
4119    // localsplus preparation, before normalize_jumps.
4120    convert_pseudo_ops(blocks)?;
4121    blocks.normalize_jumps()?;
4122    #[cfg(debug_assertions)]
4123    assert!(blocks.no_redundant_jumps());
4124    // optimize_load_fast: after normalize_jumps
4125    blocks.optimize_load_fast()?;
4126
4127    let mut instr_sequence = instruction_sequence_new();
4128    blocks.cfg_to_instruction_sequence(&mut instr_sequence)?;
4129    Ok((max_stackdepth, nlocalsplus, instr_sequence))
4130}
4131
4132/// flowgraph.c _PyCompile_OptimizeCfg
4133pub fn optimize_cfg_for_tests(
4134    seq: InstructionSequence,
4135    consts: Vec<ConstantData>,
4136    nlocals: usize,
4137) -> crate::InternalResult<(InstructionSequence, Vec<ConstantData>)> {
4138    seq.check_load_const_indices(consts.len())?;
4139    let mut metadata = CodeUnitMetadata {
4140        name: String::new(),
4141        qualname: None,
4142        consts: ConstantPool::from_ordered(consts),
4143        names: IndexSet::default(),
4144        varnames: IndexSet::default(),
4145        cellvars: IndexSet::default(),
4146        freevars: IndexSet::default(),
4147        fast_hidden: IndexMap::default(),
4148        fast_hidden_final: IndexSet::default(),
4149        argcount: 0,
4150        posonlyargcount: 0,
4151        kwonlyargcount: 0,
4152        firstlineno: OneIndexed::MIN,
4153    };
4154    let mut blocks = Blocks::from([Block::default()]);
4155    optimize_code_unit(&mut metadata, &mut blocks, seq, nlocals, 0)?;
4156    let _ = blocks.calculate_stackdepth()?;
4157    blocks.optimize_load_fast()?;
4158    let mut out = instruction_sequence_new();
4159    blocks.cfg_to_instruction_sequence(&mut out)?;
4160    Ok((out, metadata.consts.into_vec()))
4161}
4162
4163/// compile.c _PyCompile_Assemble
4164pub fn assemble_for_tests(
4165    filename: String,
4166    seq: InstructionSequence,
4167    metadata: CodeUnitMetadata,
4168    debug_ranges: bool,
4169) -> crate::InternalResult<CodeObject> {
4170    let mut blocks = cfg_from_instruction_sequence(seq)?;
4171    translate_jump_labels_to_targets(&mut blocks)?;
4172    blocks.mark_except_handlers();
4173    label_exception_targets(&mut blocks)?;
4174    let flags = CodeFlags::empty();
4175    let (max_stackdepth, nlocalsplus, mut instr_sequence) =
4176        optimized_cfg_to_instruction_sequence(&metadata, flags, &mut blocks)?;
4177    let localsplusinfo = compute_localsplus_info(&metadata, nlocalsplus, flags)?;
4178    let CodeUnitMetadata {
4179        name: obj_name,
4180        qualname,
4181        consts: constants,
4182        names: name_cache,
4183        varnames: varname_cache,
4184        cellvars: _,
4185        freevars: freevar_cache,
4186        fast_hidden: _,
4187        fast_hidden_final: _,
4188        argcount: arg_count,
4189        posonlyargcount: posonlyarg_count,
4190        kwonlyargcount: kwonlyarg_count,
4191        firstlineno: first_line_number,
4192    } = metadata;
4193    let code_arg_count = posonlyarg_count
4194        .checked_add(arg_count)
4195        .ok_or(InternalError::MalformedControlFlowGraph)?;
4196    resolve_unconditional_jumps(&mut instr_sequence);
4197    resolve_jump_offsets(&mut instr_sequence);
4198    let assembled = assemble_emit(
4199        &mut instr_sequence,
4200        first_line_number.get() as i32,
4201        debug_ranges,
4202    )?;
4203    let locations = rustpython_compiler_core::marshal::linetable_to_locations(
4204        &assembled.linetable,
4205        first_line_number.get() as i32,
4206        assembled.instructions.len(),
4207    );
4208    Ok(CodeObject {
4209        flags,
4210        posonlyarg_count,
4211        arg_count: code_arg_count,
4212        kwonlyarg_count,
4213        source_path: filename,
4214        first_line_number: Some(first_line_number),
4215        obj_name: obj_name.clone(),
4216        qualname: qualname.unwrap_or(obj_name),
4217        max_stackdepth: max_stackdepth.max(1),
4218        instructions: CodeUnits::from(assembled.instructions),
4219        locations,
4220        constants: constants.into_iter().collect(),
4221        names: name_cache.into_iter().collect(),
4222        varnames: varname_cache.into_iter().collect(),
4223        cellvars: localsplusinfo.cellvars,
4224        freevars: freevar_cache.into_iter().collect(),
4225        localspluskinds: localsplusinfo.kinds,
4226        linetable: assembled.linetable,
4227        exceptiontable: assembled.exceptiontable,
4228    })
4229}
4230
4231impl CodeInfo {
4232    pub fn finalize_code(
4233        mut self,
4234        opts: &crate::compile::CompileOpts,
4235    ) -> crate::InternalResult<CodeObject> {
4236        let instr_sequence = self.prepare_cfg_from_codegen();
4237        let nlocals = self.metadata.varnames.len();
4238        let nparams = self.nparams;
4239        optimize_code_unit(
4240            &mut self.metadata,
4241            &mut self.blocks,
4242            instr_sequence,
4243            nlocals,
4244            nparams,
4245        )?;
4246        let (max_stackdepth, nlocalsplus, mut instr_sequence) =
4247            optimized_cfg_to_instruction_sequence(&self.metadata, self.flags, &mut self.blocks)?;
4248        let localsplusinfo = compute_localsplus_info(&self.metadata, nlocalsplus, self.flags)?;
4249
4250        let Self {
4251            flags,
4252            source_path,
4253            private: _, // private is only used during compilation
4254
4255            blocks: _,
4256            current_block: _,
4257            instr_sequence: _,
4258            instr_sequence_label_map: _,
4259            annotations_instr_sequence: _,
4260            metadata,
4261            static_attributes: _,
4262            in_inlined_comp: _,
4263            fblock: _,
4264            symbol_table_index: _,
4265            nparams: _,
4266            in_conditional_block: _,
4267            next_conditional_annotation_index: _,
4268        } = self;
4269
4270        let CodeUnitMetadata {
4271            name: obj_name,
4272            qualname,
4273            consts: constants,
4274            names: name_cache,
4275            varnames: varname_cache,
4276            cellvars: _,
4277            freevars: freevar_cache,
4278            fast_hidden: _,
4279            fast_hidden_final: _,
4280            argcount: arg_count,
4281            posonlyargcount: posonlyarg_count,
4282            kwonlyargcount: kwonlyarg_count,
4283            firstlineno: first_line_number,
4284        } = metadata;
4285        let code_arg_count = posonlyarg_count
4286            .checked_add(arg_count)
4287            .ok_or(InternalError::MalformedControlFlowGraph)?;
4288
4289        resolve_unconditional_jumps(&mut instr_sequence);
4290        resolve_jump_offsets(&mut instr_sequence);
4291        let assembled = assemble_emit(
4292            &mut instr_sequence,
4293            first_line_number.get() as i32,
4294            opts.debug_ranges,
4295        )?;
4296        let locations = rustpython_compiler_core::marshal::linetable_to_locations(
4297            &assembled.linetable,
4298            first_line_number.get() as i32,
4299            assembled.instructions.len(),
4300        );
4301
4302        Ok(CodeObject {
4303            flags,
4304            posonlyarg_count,
4305            arg_count: code_arg_count,
4306            kwonlyarg_count,
4307            source_path,
4308            first_line_number: Some(first_line_number),
4309            obj_name: obj_name.clone(),
4310            qualname: qualname.unwrap_or(obj_name),
4311
4312            // Room for one value is always reserved, even where nothing is pushed.
4313            max_stackdepth: max_stackdepth.max(1),
4314            instructions: CodeUnits::from(assembled.instructions),
4315            locations,
4316            constants: constants.into_iter().collect(),
4317            names: name_cache.into_iter().collect(),
4318            varnames: varname_cache.into_iter().collect(),
4319            cellvars: localsplusinfo.cellvars,
4320            freevars: freevar_cache.into_iter().collect(),
4321            localspluskinds: localsplusinfo.kinds,
4322            linetable: assembled.linetable,
4323            exceptiontable: assembled.exceptiontable,
4324        })
4325    }
4326}
4327
4328/// flowgraph.c IS_GENERATOR
4329fn is_generator(flags: CodeFlags) -> bool {
4330    flags.intersects(CodeFlags::GENERATOR | CodeFlags::COROUTINE | CodeFlags::ASYNC_GENERATOR)
4331}
4332
4333/// flowgraph.c insert_prefix_instructions
4334fn insert_prefix_instructions(
4335    metadata: &CodeUnitMetadata,
4336    blocks: &mut Blocks,
4337    cellfixedoffsets: &[i32],
4338    nfreevars: usize,
4339    flags: CodeFlags,
4340) -> crate::InternalResult<()> {
4341    debug_assert!(!blocks.is_empty());
4342    let entry = &mut blocks[0];
4343    let ncellvars = metadata.cellvars.len();
4344    let firstlineno = metadata.firstlineno;
4345    debug_assert!(firstlineno.get() > 0);
4346
4347    if is_generator(flags) {
4348        let location = SourceLocation {
4349            line: firstlineno,
4350            character_offset: OneIndexed::MIN,
4351        };
4352        entry.basicblock_insert_instruction(
4353            0,
4354            InstructionInfo {
4355                instr: Instruction::ReturnGenerator.into(),
4356                arg: OpArg::new(0),
4357                target: BlockIdx::NULL,
4358                location,
4359                end_location: location,
4360                except_handler: None,
4361                lineno_override: Some(LINE_ONLY_LOCATION_OVERRIDE),
4362            },
4363        )?;
4364        entry.basicblock_insert_instruction(
4365            1,
4366            InstructionInfo {
4367                instr: Instruction::PopTop.into(),
4368                arg: OpArg::new(0),
4369                target: BlockIdx::NULL,
4370                location,
4371                end_location: location,
4372                except_handler: None,
4373                lineno_override: Some(LINE_ONLY_LOCATION_OVERRIDE),
4374            },
4375        )?;
4376    }
4377
4378    if ncellvars > 0 {
4379        let nvars = metadata.varnames.len() + ncellvars;
4380        let mut sorted = Vec::new();
4381        vec_try_reserve_exact(&mut sorted, nvars)?;
4382        sorted.resize(nvars, 0i32);
4383        for i in 0..ncellvars {
4384            sorted[cellfixedoffsets[i] as usize] = i as i32 + 1;
4385        }
4386        let mut ncellsused = 0;
4387        let mut i = 0;
4388        while ncellsused < ncellvars {
4389            let oldindex = sorted[i] - 1;
4390            i += 1;
4391            if oldindex == -1 {
4392                continue;
4393            }
4394            entry.basicblock_insert_instruction(
4395                ncellsused,
4396                InstructionInfo {
4397                    instr: Opcode::MakeCell.into(),
4398                    arg: OpArg::new(oldindex as u32),
4399                    target: BlockIdx::NULL,
4400                    location: SourceLocation::default(),
4401                    end_location: SourceLocation::default(),
4402                    except_handler: None,
4403                    lineno_override: Some(NO_LOCATION_OVERRIDE),
4404                },
4405            )?;
4406            ncellsused += 1;
4407        }
4408    }
4409
4410    if nfreevars > 0 {
4411        entry.basicblock_insert_instruction(
4412            0,
4413            InstructionInfo {
4414                instr: Opcode::CopyFreeVars.into(),
4415                arg: OpArg::new(nfreevars as u32),
4416                target: BlockIdx::NULL,
4417                location: SourceLocation::default(),
4418                end_location: SourceLocation::default(),
4419                except_handler: None,
4420                lineno_override: Some(NO_LOCATION_OVERRIDE),
4421            },
4422        )?;
4423    }
4424    Ok(())
4425}
4426
4427/// flowgraph.c prepare_localsplus
4428fn prepare_localsplus(
4429    metadata: &CodeUnitMetadata,
4430    blocks: &mut Blocks,
4431    flags: CodeFlags,
4432) -> crate::InternalResult<usize> {
4433    let nlocals = metadata.varnames.len();
4434    let ncellvars = metadata.cellvars.len();
4435    let nfreevars = metadata.freevars.len();
4436    let int_max = i32::MAX as usize;
4437    debug_assert!(nlocals < int_max);
4438    debug_assert!(ncellvars < int_max);
4439    debug_assert!(nfreevars < int_max);
4440    debug_assert!(int_max - nlocals - ncellvars > 0);
4441    debug_assert!(int_max - nlocals - ncellvars - nfreevars > 0);
4442    let mut nlocalsplus = nlocals + ncellvars + nfreevars;
4443    let mut cellfixedoffsets = build_cellfixedoffsets(metadata)?;
4444
4445    // This must be called before fix_cell_offsets().
4446    insert_prefix_instructions(metadata, blocks, &cellfixedoffsets, nfreevars, flags)?;
4447
4448    let numdropped = fix_cell_offsets(metadata, blocks, &mut cellfixedoffsets);
4449    nlocalsplus -= numdropped;
4450    Ok(nlocalsplus)
4451}
4452
4453/// flowgraph.c eval_const_unaryop
4454fn eval_const_unaryop(
4455    operand: &ConstantData,
4456    op: Instruction,
4457    intrinsic: Option<oparg::IntrinsicFunction1>,
4458) -> Option<ConstantData> {
4459    match (operand, op, intrinsic) {
4460        (ConstantData::Integer { value }, Instruction::UnaryNegative, None) => {
4461            Some(ConstantData::Integer { value: -value })
4462        }
4463        (ConstantData::Float { value }, Instruction::UnaryNegative, None) => {
4464            Some(ConstantData::Float { value: -value })
4465        }
4466        (ConstantData::Complex { value }, Instruction::UnaryNegative, None) => {
4467            Some(ConstantData::Complex { value: -value })
4468        }
4469        (ConstantData::Boolean { value }, Instruction::UnaryNegative, None) => {
4470            Some(ConstantData::Integer {
4471                value: BigInt::from(-i32::from(*value)),
4472            })
4473        }
4474        (ConstantData::Integer { value }, Instruction::UnaryInvert, None) => {
4475            Some(ConstantData::Integer { value: !value })
4476        }
4477        (ConstantData::Boolean { .. }, Instruction::UnaryInvert, None) => None,
4478        (_, Instruction::UnaryNot, None) => Some(ConstantData::Boolean {
4479            value: !operand.truthiness(),
4480        }),
4481        (
4482            ConstantData::Integer { value },
4483            Instruction::CallIntrinsic1 { .. },
4484            Some(oparg::IntrinsicFunction1::UnaryPositive),
4485        ) => Some(ConstantData::Integer {
4486            value: value.clone(),
4487        }),
4488        (
4489            ConstantData::Float { value },
4490            Instruction::CallIntrinsic1 { .. },
4491            Some(oparg::IntrinsicFunction1::UnaryPositive),
4492        ) => Some(ConstantData::Float { value: *value }),
4493        (
4494            ConstantData::Boolean { value },
4495            Instruction::CallIntrinsic1 { .. },
4496            Some(oparg::IntrinsicFunction1::UnaryPositive),
4497        ) => Some(ConstantData::Integer {
4498            value: BigInt::from(i32::from(*value)),
4499        }),
4500        (
4501            ConstantData::Complex { value },
4502            Instruction::CallIntrinsic1 { .. },
4503            Some(oparg::IntrinsicFunction1::UnaryPositive),
4504        ) => Some(ConstantData::Complex { value: *value }),
4505        _ => None,
4506    }
4507}
4508
4509fn load_const_truthiness(
4510    instr: Instruction,
4511    arg: OpArg,
4512    metadata: &CodeUnitMetadata,
4513) -> Option<bool> {
4514    match instr {
4515        Instruction::LoadConst { consti } => {
4516            let constant = &metadata.consts[consti.get(arg).as_usize()];
4517            Some(constant.truthiness())
4518        }
4519        Instruction::LoadSmallInt { i } => Some(i.get(arg) != 0),
4520        _ => None,
4521    }
4522}
4523
4524/// flowgraph.c add_const
4525fn add_const(
4526    metadata: &mut CodeUnitMetadata,
4527    constant: ConstantData,
4528) -> crate::InternalResult<usize> {
4529    Ok(metadata.consts.try_insert_full(constant)?.0)
4530}
4531
4532fn instr_make_load_const(
4533    metadata: &mut CodeUnitMetadata,
4534    instr: &mut InstructionInfo,
4535    constant: ConstantData,
4536) -> crate::InternalResult<()> {
4537    if instr.maybe_instr_make_load_smallint(&constant) {
4538        return Ok(());
4539    }
4540
4541    let const_idx = add_const(metadata, constant)?;
4542    instr.instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32));
4543    Ok(())
4544}
4545
4546/// flowgraph.c fold_const_unaryop
4547fn fold_const_unaryop(
4548    metadata: &mut CodeUnitMetadata,
4549    block: &mut Block,
4550    i: usize,
4551) -> crate::InternalResult<bool> {
4552    let instr = &block.instructions[i];
4553    let (op, intrinsic) = match instr.instr.real() {
4554        Some(Instruction::UnaryNegative) => (Instruction::UnaryNegative, None),
4555        Some(Instruction::UnaryInvert) => (Instruction::UnaryInvert, None),
4556        Some(Instruction::UnaryNot) => (Instruction::UnaryNot, None),
4557        Some(Instruction::CallIntrinsic1 { func })
4558            if matches!(
4559                func.get(instr.arg),
4560                oparg::IntrinsicFunction1::UnaryPositive
4561            ) =>
4562        {
4563            (Opcode::CallIntrinsic1.into(), Some(func.get(instr.arg)))
4564        }
4565        _ => return Ok(false),
4566    };
4567    let Some(operand_index) = (if let Some(start) = i.checked_sub(1) {
4568        block.get_const_loading_instrs(start, 1)?
4569    } else {
4570        None
4571    })
4572    .and_then(|indices| indices.into_iter().next()) else {
4573        return Ok(false);
4574    };
4575    let operand = get_const_value(metadata, &block.instructions[operand_index]);
4576    let Some(operand) = operand else {
4577        return Ok(false);
4578    };
4579    let Some(folded_const) = eval_const_unaryop(&operand, op, intrinsic) else {
4580        return Ok(false);
4581    };
4582    block.nop_out(&[operand_index]);
4583    instr_make_load_const(metadata, &mut block.instructions[i], folded_const)?;
4584    Ok(true)
4585}
4586
4587/// flowgraph.c fold_const_binop
4588fn fold_const_binop(
4589    metadata: &mut CodeUnitMetadata,
4590    block: &mut Block,
4591    i: usize,
4592) -> crate::InternalResult<bool> {
4593    use oparg::BinaryOperator as BinOp;
4594
4595    let Some(Opcode::BinaryOp) = block.instructions[i].instr.real_opcode() else {
4596        return Ok(false);
4597    };
4598
4599    let Some(operand_indices) = (if let Some(start) = i.checked_sub(1) {
4600        block.get_const_loading_instrs(start, 2)?
4601    } else {
4602        None
4603    }) else {
4604        return Ok(false);
4605    };
4606
4607    let op_raw = u32::from(block.instructions[i].arg);
4608    let Ok(op) = BinOp::try_from(op_raw) else {
4609        return Ok(false);
4610    };
4611
4612    let left = get_const_value(metadata, &block.instructions[operand_indices[0]]);
4613    let right = get_const_value(metadata, &block.instructions[operand_indices[1]]);
4614    let (Some(left_val), Some(right_val)) = (left, right) else {
4615        return Ok(false);
4616    };
4617
4618    let Some(result_const) = eval_const_binop(&left_val, &right_val, op) else {
4619        return Ok(false);
4620    };
4621
4622    block.nop_out(&operand_indices);
4623    instr_make_load_const(metadata, &mut block.instructions[i], result_const)?;
4624    Ok(true)
4625}
4626
4627/// flowgraph.c get_const_value
4628fn get_const_value(metadata: &CodeUnitMetadata, info: &InstructionInfo) -> Option<ConstantData> {
4629    match info.instr.real_opcode() {
4630        Some(Opcode::LoadSmallInt) => {
4631            let v = u32::from(info.arg) as i32;
4632            Some(ConstantData::Integer {
4633                value: BigInt::from(v),
4634            })
4635        }
4636        _ if info.instr.has_const() => {
4637            let idx = u32::from(info.arg) as usize;
4638            metadata.consts.get_index(idx).cloned()
4639        }
4640        _ => None,
4641    }
4642}
4643
4644/// flowgraph.c const_folding_check_complexity
4645fn const_folding_check_complexity(obj: &ConstantData, mut limit: isize) -> Option<isize> {
4646    if let ConstantData::Tuple { elements } = obj {
4647        limit -= isize::try_from(elements.len()).ok()?;
4648        if limit < 0 {
4649            return None;
4650        }
4651        for element in elements {
4652            limit = const_folding_check_complexity(element, limit)?;
4653        }
4654    }
4655    Some(limit)
4656}
4657
4658fn repeat_wtf8(value: &Wtf8Buf, n: usize) -> Option<Wtf8Buf> {
4659    let mut result = Wtf8Buf::new();
4660    result.try_reserve_exact(value.len().checked_mul(n)?).ok()?;
4661    for _ in 0..n {
4662        result.push_wtf8(value);
4663    }
4664    Some(result)
4665}
4666
4667fn checked_repeat_count(n: &BigInt, item_size: usize) -> Option<usize> {
4668    let n = n.to_isize()?;
4669    if item_size != 0 && (n < 0 || n as usize > MAX_STR_SIZE / item_size) {
4670        return None;
4671    }
4672    Some(n.max(0) as usize)
4673}
4674
4675/// flowgraph.c const_folding_safe_multiply
4676fn const_folding_safe_multiply(left: &ConstantData, right: &ConstantData) -> Option<ConstantData> {
4677    match (left, right) {
4678        (ConstantData::Integer { value: l }, ConstantData::Integer { value: r }) => {
4679            if !l.is_zero() && !r.is_zero() && l.bits() + r.bits() > MAX_INT_SIZE {
4680                return None;
4681            }
4682            Some(ConstantData::Integer { value: l * r })
4683        }
4684        (ConstantData::Float { value: l }, ConstantData::Float { value: r }) => {
4685            Some(ConstantData::Float { value: l * r })
4686        }
4687        (ConstantData::Str { value: s }, ConstantData::Integer { value: n }) => {
4688            let n = checked_repeat_count(n, s.code_points().count())?;
4689            Some(ConstantData::Str {
4690                value: repeat_wtf8(s, n)?,
4691            })
4692        }
4693        (ConstantData::Integer { .. }, ConstantData::Str { .. }) => {
4694            const_folding_safe_multiply(right, left)
4695        }
4696        (ConstantData::Bytes { value: b }, ConstantData::Integer { value: n }) => {
4697            let n = checked_repeat_count(n, b.len())?;
4698            let mut value = Vec::new();
4699            value.try_reserve_exact(b.len().checked_mul(n)?).ok()?;
4700            for _ in 0..n {
4701                value.extend_from_slice(b);
4702            }
4703            Some(ConstantData::Bytes { value })
4704        }
4705        (ConstantData::Integer { .. }, ConstantData::Bytes { .. }) => {
4706            const_folding_safe_multiply(right, left)
4707        }
4708        (ConstantData::Tuple { elements }, ConstantData::Integer { value: n }) => {
4709            if elements.is_empty() {
4710                return Some(ConstantData::Tuple {
4711                    elements: Vec::new(),
4712                });
4713            }
4714            let n = n.to_usize()?;
4715            if n != 0 {
4716                if n > MAX_COLLECTION_SIZE / elements.len() {
4717                    return None;
4718                }
4719                const_folding_check_complexity(
4720                    &ConstantData::Tuple {
4721                        elements: elements.clone(),
4722                    },
4723                    MAX_TOTAL_ITEMS / isize::try_from(n).ok()?,
4724                )?;
4725            }
4726            let mut result = Vec::new();
4727            result
4728                .try_reserve_exact(elements.len().checked_mul(n)?)
4729                .ok()?;
4730            for _ in 0..n {
4731                result.extend(elements.iter().cloned());
4732            }
4733            Some(ConstantData::Tuple { elements: result })
4734        }
4735        (ConstantData::Integer { .. }, ConstantData::Tuple { .. }) => {
4736            const_folding_safe_multiply(right, left)
4737        }
4738        _ => None,
4739    }
4740}
4741
4742/// flowgraph.c const_folding_safe_power
4743fn const_folding_safe_power(left: &ConstantData, right: &ConstantData) -> Option<ConstantData> {
4744    match (left, right) {
4745        (ConstantData::Integer { value: l }, ConstantData::Integer { value: r }) => {
4746            if r < &BigInt::from(0) {
4747                if l.is_zero() {
4748                    return None;
4749                }
4750                let base = l.to_f64()?;
4751                if !base.is_finite() {
4752                    return None;
4753                }
4754                let result = if let Some(exp) = r.to_i32() {
4755                    base.powi(exp)
4756                } else {
4757                    base.powf(r.to_f64()?)
4758                };
4759                if !result.is_finite() {
4760                    return None;
4761                }
4762                return Some(ConstantData::Float { value: result });
4763            }
4764            let exp: u64 = r.try_into().ok()?;
4765            let exp_usize = usize::try_from(exp).ok()?;
4766            if !l.is_zero() && exp > 0 && l.bits() > MAX_INT_SIZE / exp {
4767                return None;
4768            }
4769            Some(ConstantData::Integer {
4770                value: num_traits::pow::pow(l.clone(), exp_usize),
4771            })
4772        }
4773        (ConstantData::Float { value: l }, ConstantData::Float { value: r }) => {
4774            let result = l.powf(*r);
4775            result
4776                .is_finite()
4777                .then_some(ConstantData::Float { value: result })
4778        }
4779        _ => None,
4780    }
4781}
4782
4783/// flowgraph.c const_folding_safe_lshift
4784fn const_folding_safe_lshift(left: &ConstantData, right: &ConstantData) -> Option<ConstantData> {
4785    let (ConstantData::Integer { value: l }, ConstantData::Integer { value: r }) = (left, right)
4786    else {
4787        return None;
4788    };
4789    let shift: u64 = r.try_into().ok()?;
4790    let shift_usize = usize::try_from(shift).ok()?;
4791    if shift > MAX_INT_SIZE || (!l.is_zero() && l.bits() > MAX_INT_SIZE - shift) {
4792        return None;
4793    }
4794    Some(ConstantData::Integer {
4795        value: l << shift_usize,
4796    })
4797}
4798
4799/// flowgraph.c const_folding_safe_mod
4800fn const_folding_safe_mod(left: &ConstantData, right: &ConstantData) -> Option<ConstantData> {
4801    if matches!(left, ConstantData::Str { .. } | ConstantData::Bytes { .. }) {
4802        return None;
4803    }
4804
4805    match (left, right) {
4806        (ConstantData::Integer { value: l }, ConstantData::Integer { value: r }) => {
4807            if r.is_zero() {
4808                return None;
4809            }
4810            let rem = l.clone() % r.clone();
4811            let value = if !rem.is_zero() && (rem < BigInt::from(0)) != (*r < BigInt::from(0)) {
4812                rem + r
4813            } else {
4814                rem
4815            };
4816            Some(ConstantData::Integer { value })
4817        }
4818        (ConstantData::Float { value: l }, ConstantData::Float { value: r }) => {
4819            let (_, modulo) = float_div_mod(*l, *r)?;
4820            Some(ConstantData::Float { value: modulo })
4821        }
4822        _ => None,
4823    }
4824}
4825
4826fn float_div_mod(left: f64, right: f64) -> Option<(f64, f64)> {
4827    if right == 0.0 {
4828        return None;
4829    }
4830
4831    let mut modulo = left % right;
4832    let div = (left - modulo) / right;
4833    let floordiv = if modulo != 0.0 {
4834        let div = if (right < 0.0) != (modulo < 0.0) {
4835            modulo += right;
4836            div - 1.0
4837        } else {
4838            div
4839        };
4840        let mut floordiv = div.floor();
4841        if div - floordiv > 0.5 {
4842            floordiv += 1.0;
4843        }
4844        floordiv
4845    } else {
4846        modulo = 0.0f64.copysign(right);
4847        0.0f64.copysign(left / right)
4848    };
4849
4850    Some((floordiv, modulo))
4851}
4852
4853/// flowgraph.c eval_const_binop complex result construction
4854fn eval_const_complex_const(value: Complex<f64>) -> Option<ConstantData> {
4855    (value.re.is_finite() && value.im.is_finite()).then_some(ConstantData::Complex { value })
4856}
4857
4858/// flowgraph.c eval_const_binop complex operations
4859fn eval_const_complex_binop(
4860    left: Complex<f64>,
4861    right: Complex<f64>,
4862    op: oparg::BinaryOperator,
4863) -> Option<ConstantData> {
4864    use oparg::BinaryOperator as BinOp;
4865
4866    let value = match op {
4867        BinOp::Add => left + right,
4868        BinOp::Subtract => {
4869            let re = left.re - right.re;
4870            // Preserve signed-zero behavior for real-zero
4871            // minus zero-complex expressions such as `0 - 0j`.
4872            let im = if left.re == 0.0
4873                && left.im == 0.0
4874                && right.re == 0.0
4875                && right.im == 0.0
4876                && !right.im.is_sign_negative()
4877            {
4878                -0.0
4879            } else {
4880                left.im - right.im
4881            };
4882            Complex::new(re, im)
4883        }
4884        BinOp::Multiply => left * right,
4885        BinOp::TrueDivide => {
4886            if right == Complex::new(0.0, 0.0) {
4887                return None;
4888            }
4889            left / right
4890        }
4891        BinOp::Power => {
4892            if left == Complex::new(0.0, 0.0) {
4893                if right.im != 0.0 || right.re < 0.0 {
4894                    return None;
4895                }
4896
4897                return eval_const_complex_const(if right.re == 0.0 {
4898                    Complex::new(1.0, 0.0)
4899                } else {
4900                    Complex::new(0.0, 0.0)
4901                });
4902            }
4903
4904            if right.im == 0.0
4905                && right.re.fract() == 0.0
4906                && right.re >= f64::from(i32::MIN)
4907                && right.re <= f64::from(i32::MAX)
4908            {
4909                left.powi(right.re as i32)
4910            } else {
4911                left.powc(right)
4912            }
4913        }
4914        _ => return None,
4915    };
4916    eval_const_complex_const(value)
4917}
4918
4919/// flowgraph.c eval_const_binop subscript index conversion
4920fn constant_as_index(value: &ConstantData) -> Option<i64> {
4921    match value {
4922        ConstantData::Integer { value } => value.to_i64().or_else(|| {
4923            if value < &BigInt::from(0) {
4924                Some(i64::MIN)
4925            } else {
4926                Some(i64::MAX)
4927            }
4928        }),
4929        ConstantData::Boolean { value } => Some(i64::from(*value)),
4930        _ => None,
4931    }
4932}
4933
4934/// flowgraph.c eval_const_binop subscript slice bound conversion
4935fn slice_bound(value: &ConstantData) -> Option<Option<i64>> {
4936    match value {
4937        ConstantData::None => Some(None),
4938        _ => constant_as_index(value).map(Some),
4939    }
4940}
4941
4942/// flowgraph.c eval_const_binop subscript slice index adjustment
4943fn adjusted_slice_indices(len: usize, slice: &[ConstantData; 3]) -> Option<Vec<usize>> {
4944    let len = i64::try_from(len).ok()?;
4945    let start = slice_bound(&slice[0])?;
4946    let stop = slice_bound(&slice[1])?;
4947    let step = slice_bound(&slice[2])?.unwrap_or(1);
4948    if step == 0 || step == i64::MIN {
4949        return None;
4950    }
4951
4952    let step_is_negative = step < 0;
4953    let lower = if step_is_negative { -1 } else { 0 };
4954    let upper = if step_is_negative { len - 1 } else { len };
4955    let adjust = |value: Option<i64>, default: i64| {
4956        let mut value = value.unwrap_or(default);
4957        if value < 0 {
4958            value = value.saturating_add(len);
4959            if value < 0 {
4960                value = lower;
4961            }
4962        } else if value >= len {
4963            value = upper;
4964        }
4965        value
4966    };
4967    let start = adjust(start, if step_is_negative { upper } else { lower });
4968    let stop = adjust(stop, if step_is_negative { lower } else { upper });
4969
4970    let mut index = i128::from(start);
4971    let stop = i128::from(stop);
4972    let step = i128::from(step);
4973    let slice_len = if step > 0 {
4974        if index < stop {
4975            usize::try_from((stop - index - 1) / step + 1).ok()?
4976        } else {
4977            0
4978        }
4979    } else if index > stop {
4980        usize::try_from((index - stop - 1) / -step + 1).ok()?
4981    } else {
4982        0
4983    };
4984    let mut indices = Vec::new();
4985    indices.try_reserve_exact(slice_len).ok()?;
4986    if step > 0 {
4987        while index < stop {
4988            indices.push(usize::try_from(index).ok()?);
4989            index += step;
4990        }
4991    } else {
4992        while index > stop {
4993            indices.push(usize::try_from(index).ok()?);
4994            index += step;
4995        }
4996    }
4997    Some(indices)
4998}
4999
5000/// flowgraph.c eval_const_binop subscript index adjustment
5001fn adjusted_const_index(len: usize, index: &ConstantData) -> Option<usize> {
5002    let len = i64::try_from(len).ok()?;
5003    let index = constant_as_index(index)?;
5004    let index = if index < 0 {
5005        index.saturating_add(len)
5006    } else {
5007        index
5008    };
5009    if index < 0 || index >= len {
5010        return None;
5011    }
5012    usize::try_from(index).ok()
5013}
5014
5015/// flowgraph.c eval_const_binop NB_SUBSCR
5016fn eval_const_subscript(container: &ConstantData, index: &ConstantData) -> Option<ConstantData> {
5017    match (container, index) {
5018        (
5019            ConstantData::Str { value },
5020            ConstantData::Integer { .. } | ConstantData::Boolean { .. },
5021        ) => {
5022            let string = value.to_string();
5023            if string.contains(char::REPLACEMENT_CHARACTER) {
5024                return None;
5025            }
5026            let mut chars = Vec::new();
5027            chars.try_reserve_exact(string.chars().count()).ok()?;
5028            chars.extend(string.chars());
5029            let index = adjusted_const_index(chars.len(), index)?;
5030            Some(ConstantData::Str {
5031                value: chars[index].to_string().into(),
5032            })
5033        }
5034        (ConstantData::Str { value }, ConstantData::Slice { elements }) => {
5035            let string = value.to_string();
5036            if string.contains(char::REPLACEMENT_CHARACTER) {
5037                return None;
5038            }
5039            let mut chars = Vec::new();
5040            chars.try_reserve_exact(string.chars().count()).ok()?;
5041            chars.extend(string.chars());
5042            let indices = adjusted_slice_indices(chars.len(), elements)?;
5043            let capacity = indices.iter().try_fold(0usize, |capacity, &index| {
5044                capacity.checked_add(chars[index].len_utf8())
5045            })?;
5046            let mut result = String::new();
5047            result.try_reserve_exact(capacity).ok()?;
5048            for index in indices {
5049                result.push(chars[index]);
5050            }
5051            Some(ConstantData::Str {
5052                value: result.into(),
5053            })
5054        }
5055        (
5056            ConstantData::Bytes { value },
5057            ConstantData::Integer { .. } | ConstantData::Boolean { .. },
5058        ) => {
5059            let index = adjusted_const_index(value.len(), index)?;
5060            Some(ConstantData::Integer {
5061                value: BigInt::from(value[index]),
5062            })
5063        }
5064        (ConstantData::Bytes { value }, ConstantData::Slice { elements }) => {
5065            let indices = adjusted_slice_indices(value.len(), elements)?;
5066            let mut result = Vec::new();
5067            result.try_reserve_exact(indices.len()).ok()?;
5068            for index in indices {
5069                result.push(value[index]);
5070            }
5071            Some(ConstantData::Bytes { value: result })
5072        }
5073        (
5074            ConstantData::Tuple { elements },
5075            ConstantData::Integer { .. } | ConstantData::Boolean { .. },
5076        ) => {
5077            let index = adjusted_const_index(elements.len(), index)?;
5078            Some(elements[index].clone())
5079        }
5080        (ConstantData::Tuple { elements }, ConstantData::Slice { elements: slice }) => {
5081            let indices = adjusted_slice_indices(elements.len(), slice)?;
5082            let mut result = Vec::new();
5083            result.try_reserve_exact(indices.len()).ok()?;
5084            for index in indices {
5085                result.push(elements[index].clone());
5086            }
5087            Some(ConstantData::Tuple { elements: result })
5088        }
5089        _ => None,
5090    }
5091}
5092
5093/// flowgraph.c eval_const_binop bool/int coercion
5094fn constant_as_int(value: &ConstantData) -> Option<(BigInt, bool)> {
5095    match value {
5096        ConstantData::Boolean { value } => Some((BigInt::from(u8::from(*value)), true)),
5097        ConstantData::Integer { value } => Some((value.clone(), false)),
5098        _ => None,
5099    }
5100}
5101
5102/// flowgraph.c eval_const_binop
5103fn eval_const_binop(
5104    left: &ConstantData,
5105    right: &ConstantData,
5106    op: oparg::BinaryOperator,
5107) -> Option<ConstantData> {
5108    use oparg::BinaryOperator as BinOp;
5109
5110    if matches!(op, BinOp::Subscr) {
5111        return eval_const_subscript(left, right);
5112    }
5113
5114    if let (Some((left_int, left_is_bool)), Some((right_int, right_is_bool))) =
5115        (constant_as_int(left), constant_as_int(right))
5116        && (left_is_bool || right_is_bool)
5117    {
5118        if left_is_bool && right_is_bool {
5119            match op {
5120                BinOp::And => {
5121                    return Some(ConstantData::Boolean {
5122                        value: !left_int.is_zero() & !right_int.is_zero(),
5123                    });
5124                }
5125                BinOp::Or => {
5126                    return Some(ConstantData::Boolean {
5127                        value: !left_int.is_zero() | !right_int.is_zero(),
5128                    });
5129                }
5130                BinOp::Xor => {
5131                    return Some(ConstantData::Boolean {
5132                        value: !left_int.is_zero() ^ !right_int.is_zero(),
5133                    });
5134                }
5135                _ => {}
5136            }
5137        }
5138
5139        return eval_const_binop(
5140            &ConstantData::Integer { value: left_int },
5141            &ConstantData::Integer { value: right_int },
5142            op,
5143        );
5144    }
5145
5146    match (left, right) {
5147        (ConstantData::Integer { value: l }, ConstantData::Integer { value: r }) => {
5148            let result = match op {
5149                BinOp::Add => l + r,
5150                BinOp::Subtract => l - r,
5151                BinOp::Multiply => {
5152                    return const_folding_safe_multiply(left, right);
5153                }
5154                BinOp::TrueDivide => {
5155                    if r.is_zero() {
5156                        return None;
5157                    }
5158                    let l_f = l.to_f64()?;
5159                    let r_f = r.to_f64()?;
5160                    let result = l_f / r_f;
5161                    if !result.is_finite() {
5162                        return None;
5163                    }
5164                    return Some(ConstantData::Float { value: result });
5165                }
5166                BinOp::FloorDivide => {
5167                    if r.is_zero() {
5168                        return None;
5169                    }
5170                    // Python floor division: round towards negative infinity
5171                    let (q, rem) = (l.clone() / r.clone(), l.clone() % r.clone());
5172                    if !rem.is_zero() && (rem < BigInt::from(0)) != (*r < BigInt::from(0)) {
5173                        q - 1
5174                    } else {
5175                        q
5176                    }
5177                }
5178                BinOp::Remainder => return const_folding_safe_mod(left, right),
5179                BinOp::Power => return const_folding_safe_power(left, right),
5180                BinOp::Lshift => return const_folding_safe_lshift(left, right),
5181                BinOp::Rshift => {
5182                    let shift: u32 = r.try_into().ok()?;
5183                    l >> (shift as usize)
5184                }
5185                BinOp::And => l & r,
5186                BinOp::Or => l | r,
5187                BinOp::Xor => l ^ r,
5188                _ => return None,
5189            };
5190            Some(ConstantData::Integer { value: result })
5191        }
5192        (ConstantData::Float { value: l }, ConstantData::Float { value: r }) => {
5193            let result = match op {
5194                BinOp::Add => l + r,
5195                BinOp::Subtract => l - r,
5196                BinOp::Multiply => return const_folding_safe_multiply(left, right),
5197                BinOp::TrueDivide => {
5198                    if *r == 0.0 {
5199                        return None;
5200                    }
5201                    l / r
5202                }
5203                BinOp::FloorDivide => {
5204                    let (floordiv, _) = float_div_mod(*l, *r)?;
5205                    floordiv
5206                }
5207                BinOp::Remainder => return const_folding_safe_mod(left, right),
5208                BinOp::Power => return const_folding_safe_power(left, right),
5209                _ => return None,
5210            };
5211            if matches!(op, BinOp::Power) && !result.is_finite() {
5212                return None;
5213            }
5214            Some(ConstantData::Float { value: result })
5215        }
5216        // Int op Float or Float op Int → Float
5217        (ConstantData::Integer { value: l }, ConstantData::Float { value: r }) => {
5218            let l_f = l.to_f64()?;
5219            eval_const_binop(
5220                &ConstantData::Float { value: l_f },
5221                &ConstantData::Float { value: *r },
5222                op,
5223            )
5224        }
5225        (ConstantData::Float { value: l }, ConstantData::Integer { value: r }) => {
5226            let r_f = r.to_f64()?;
5227            eval_const_binop(
5228                &ConstantData::Float { value: *l },
5229                &ConstantData::Float { value: r_f },
5230                op,
5231            )
5232        }
5233        (ConstantData::Integer { value: l }, ConstantData::Complex { value: r }) => {
5234            eval_const_complex_binop(Complex::new(l.to_f64()?, 0.0), *r, op)
5235        }
5236        (ConstantData::Complex { value: l }, ConstantData::Integer { value: r }) => {
5237            eval_const_complex_binop(*l, Complex::new(r.to_f64()?, 0.0), op)
5238        }
5239        (ConstantData::Float { value: l }, ConstantData::Complex { value: r }) => {
5240            eval_const_complex_binop(Complex::new(*l, 0.0), *r, op)
5241        }
5242        (ConstantData::Complex { value: l }, ConstantData::Float { value: r }) => {
5243            eval_const_complex_binop(*l, Complex::new(*r, 0.0), op)
5244        }
5245        (ConstantData::Complex { value: l }, ConstantData::Complex { value: r }) => {
5246            eval_const_complex_binop(*l, *r, op)
5247        }
5248        // String concatenation and repetition
5249        (ConstantData::Str { value: l }, ConstantData::Str { value: r })
5250            if matches!(op, BinOp::Add) =>
5251        {
5252            let mut result = Wtf8Buf::new();
5253            result
5254                .try_reserve_exact(l.len().checked_add(r.len())?)
5255                .ok()?;
5256            result.push_wtf8(l);
5257            result.push_wtf8(r);
5258            Some(ConstantData::Str { value: result })
5259        }
5260        (ConstantData::Str { .. }, ConstantData::Integer { .. })
5261            if matches!(op, BinOp::Multiply) =>
5262        {
5263            const_folding_safe_multiply(left, right)
5264        }
5265        (ConstantData::Tuple { elements: l }, ConstantData::Tuple { elements: r })
5266            if matches!(op, BinOp::Add) =>
5267        {
5268            let mut result = Vec::new();
5269            result
5270                .try_reserve_exact(l.len().checked_add(r.len())?)
5271                .ok()?;
5272            result.extend(l.iter().cloned());
5273            result.extend(r.iter().cloned());
5274            Some(ConstantData::Tuple { elements: result })
5275        }
5276        (ConstantData::Tuple { .. }, ConstantData::Integer { .. })
5277            if matches!(op, BinOp::Multiply) =>
5278        {
5279            const_folding_safe_multiply(left, right)
5280        }
5281        (ConstantData::Integer { .. }, ConstantData::Tuple { .. })
5282            if matches!(op, BinOp::Multiply) =>
5283        {
5284            const_folding_safe_multiply(left, right)
5285        }
5286        (ConstantData::Integer { .. }, ConstantData::Str { .. })
5287            if matches!(op, BinOp::Multiply) =>
5288        {
5289            const_folding_safe_multiply(left, right)
5290        }
5291        (ConstantData::Bytes { value: l }, ConstantData::Bytes { value: r })
5292            if matches!(op, BinOp::Add) =>
5293        {
5294            let mut result = Vec::new();
5295            result
5296                .try_reserve_exact(l.len().checked_add(r.len())?)
5297                .ok()?;
5298            result.extend_from_slice(l);
5299            result.extend_from_slice(r);
5300            Some(ConstantData::Bytes { value: result })
5301        }
5302        (ConstantData::Bytes { .. }, ConstantData::Integer { .. })
5303            if matches!(op, BinOp::Multiply) =>
5304        {
5305            const_folding_safe_multiply(left, right)
5306        }
5307        (ConstantData::Integer { .. }, ConstantData::Bytes { .. })
5308            if matches!(op, BinOp::Multiply) =>
5309        {
5310            const_folding_safe_multiply(left, right)
5311        }
5312        _ => None,
5313    }
5314}
5315
5316/// flowgraph.c fold_tuple_of_constants
5317fn fold_tuple_of_constants(
5318    metadata: &mut CodeUnitMetadata,
5319    block: &mut Block,
5320    i: usize,
5321) -> crate::InternalResult<bool> {
5322    let Some(Opcode::BuildTuple) = block.instructions[i].instr.real_opcode() else {
5323        return Ok(false);
5324    };
5325
5326    let tuple_size = u32::from(block.instructions[i].arg) as usize;
5327    if tuple_size > STACK_USE_GUIDELINE {
5328        return Ok(false);
5329    }
5330
5331    let Some(operand_indices) = (if tuple_size == 0 {
5332        Some(Vec::new())
5333    } else if let Some(start) = i.checked_sub(1) {
5334        block.get_const_loading_instrs(start, tuple_size)?
5335    } else {
5336        None
5337    }) else {
5338        return Ok(false);
5339    };
5340
5341    let mut elements = Vec::new();
5342    elements
5343        .try_reserve_exact(tuple_size)
5344        .map_err(|_| InternalError::MalformedControlFlowGraph)?;
5345    for &j in &operand_indices {
5346        let Some(element) = get_const_value(metadata, &block.instructions[j]) else {
5347            return Ok(false);
5348        };
5349        elements.push(element);
5350    }
5351
5352    block.nop_out(&operand_indices);
5353    instr_make_load_const(
5354        metadata,
5355        &mut block.instructions[i],
5356        ConstantData::Tuple { elements },
5357    )?;
5358    Ok(true)
5359}
5360
5361fn fold_constant_intrinsic_list_to_tuple(
5362    metadata: &mut CodeUnitMetadata,
5363    block: &mut Block,
5364    i: usize,
5365) -> crate::InternalResult<bool> {
5366    let Some(Instruction::CallIntrinsic1 { func }) = block.instructions[i].instr.real() else {
5367        return Ok(false);
5368    };
5369    if func.get(block.instructions[i].arg) != IntrinsicFunction1::ListToTuple {
5370        return Ok(false);
5371    }
5372
5373    let mut consts_found = 0usize;
5374    let mut expect_append = true;
5375    let mut pos = i;
5376    while let Some(prev) = pos.checked_sub(1) {
5377        pos = prev;
5378        let instr = &block.instructions[pos];
5379        if matches!(instr.instr.real(), Some(Instruction::Nop)) {
5380            continue;
5381        }
5382
5383        if matches!(instr.instr.real(), Some(Instruction::BuildList { .. }))
5384            && u32::from(instr.arg) == 0
5385        {
5386            if !expect_append {
5387                return Ok(false);
5388            }
5389
5390            let mut elements = Vec::new();
5391            elements
5392                .try_reserve_exact(consts_found)
5393                .map_err(|_| InternalError::MalformedControlFlowGraph)?;
5394            for idx in (pos..i).rev() {
5395                if matches!(block.instructions[idx].instr.real(), Some(Instruction::Nop)) {
5396                    continue;
5397                }
5398                if block.instructions[idx].loads_const() {
5399                    let Some(value) = get_const_value(metadata, &block.instructions[idx]) else {
5400                        return Ok(false);
5401                    };
5402                    elements.push(value);
5403                }
5404                block.instructions[idx].nop_out_no_location();
5405            }
5406            debug_assert_eq!(elements.len(), consts_found);
5407            elements.reverse();
5408            instr_make_load_const(
5409                metadata,
5410                &mut block.instructions[i],
5411                ConstantData::Tuple { elements },
5412            )?;
5413            return Ok(true);
5414        }
5415
5416        if expect_append {
5417            if !matches!(instr.instr.real(), Some(Instruction::ListAppend { .. }))
5418                || u32::from(instr.arg) != 1
5419            {
5420                return Ok(false);
5421            }
5422        } else {
5423            if !instr.loads_const() {
5424                return Ok(false);
5425            }
5426            consts_found += 1;
5427        }
5428        expect_append = !expect_append;
5429    }
5430
5431    Ok(false)
5432}
5433
5434/// Port of flowgraph.c optimize_lists_and_sets().
5435fn optimize_lists_and_sets(
5436    metadata: &mut CodeUnitMetadata,
5437    block: &mut Block,
5438    i: usize,
5439    nextop: Option<Instruction>,
5440) -> crate::InternalResult<bool> {
5441    let Some(instr) = block.instructions[i].instr.real() else {
5442        return Ok(false);
5443    };
5444    let is_list = matches!(instr, Instruction::BuildList { .. });
5445    let is_set = matches!(instr, Instruction::BuildSet { .. });
5446    if !is_list && !is_set {
5447        return Ok(false);
5448    }
5449
5450    let contains_or_iter = matches!(
5451        nextop,
5452        Some(Instruction::GetIter | Instruction::ContainsOp { .. })
5453    );
5454    let seq_size = u32::from(block.instructions[i].arg) as usize;
5455    if seq_size > STACK_USE_GUIDELINE || (seq_size < MIN_CONST_SEQUENCE_SIZE && !contains_or_iter) {
5456        return Ok(false);
5457    }
5458
5459    let Some(operand_indices) = (if seq_size == 0 {
5460        Some(Vec::new())
5461    } else if let Some(start) = i.checked_sub(1) {
5462        block.get_const_loading_instrs(start, seq_size)?
5463    } else {
5464        None
5465    }) else {
5466        if contains_or_iter && is_list {
5467            let arg = block.instructions[i].arg;
5468            block.instructions[i].instr_set_op1(Opcode::BuildTuple.into(), arg);
5469            return Ok(true);
5470        }
5471        return Ok(false);
5472    };
5473
5474    let mut elements = Vec::new();
5475    elements
5476        .try_reserve_exact(seq_size)
5477        .map_err(|_| InternalError::MalformedControlFlowGraph)?;
5478    for &j in &operand_indices {
5479        let Some(element) = get_const_value(metadata, &block.instructions[j]) else {
5480            return Ok(false);
5481        };
5482        elements.push(element);
5483    }
5484
5485    let const_data = if is_list {
5486        ConstantData::Tuple { elements }
5487    } else {
5488        ConstantData::Frozenset { elements }
5489    };
5490    let const_idx = add_const(metadata, const_data)?;
5491
5492    if !contains_or_iter {
5493        debug_assert!(i >= 2);
5494        let folded_loc = block.instructions[i].instr_location();
5495
5496        block.nop_out(&operand_indices);
5497
5498        let build_instr = if is_list {
5499            Opcode::BuildList
5500        } else {
5501            Opcode::BuildSet
5502        }
5503        .into();
5504        block.instructions[i - 2].instr_set_op1(build_instr, OpArg::new(0));
5505        block.instructions[i - 2].instr_set_location(folded_loc);
5506
5507        block.instructions[i - 1]
5508            .instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32));
5509
5510        let extend_instr = if is_list {
5511            Opcode::ListExtend
5512        } else {
5513            Opcode::SetUpdate
5514        };
5515        block.instructions[i].instr_set_op1(extend_instr.into(), OpArg::new(1));
5516        return Ok(true);
5517    }
5518
5519    block.nop_out(&operand_indices);
5520
5521    block.instructions[i].instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32));
5522    Ok(true)
5523}
5524
5525/// flowgraph.c VISITED
5526const VISITED: i32 = -1;
5527
5528/// flowgraph.c SWAPPABLE
5529fn is_swappable(instr: AnyInstruction) -> bool {
5530    matches!(
5531        instr.into(),
5532        AnyOpcode::Real(Opcode::StoreFast | Opcode::PopTop)
5533            | AnyOpcode::Pseudo(PseudoOpcode::StoreFastMaybeNull)
5534    )
5535}
5536
5537/// flowgraph.c basicblock_optimize_load_const
5538fn basicblock_optimize_load_const(
5539    metadata: &mut CodeUnitMetadata,
5540    block: &mut Block,
5541) -> crate::InternalResult<()> {
5542    let mut i = 0;
5543    let mut effective_opcode = Instruction::Nop.into();
5544    let mut effective_oparg = OpArg::new(0);
5545    while i < block.instruction_used {
5546        if matches!(
5547            block.instructions[i].instr.real(),
5548            Some(Instruction::LoadConst { .. })
5549        ) && let Some(constant) = get_const_value(metadata, &block.instructions[i])
5550        {
5551            block.instructions[i].maybe_instr_make_load_smallint(&constant);
5552        }
5553
5554        let curr = block.instructions[i];
5555        let curr_arg = curr.arg;
5556
5557        let is_copy_of_load_const = matches!(
5558            (effective_opcode, curr.instr.real()),
5559            (AnyInstruction::Real(Instruction::LoadConst { .. }), Some(Instruction::Copy { i }))
5560                if i.get(curr_arg) == 1
5561        );
5562        if !is_copy_of_load_const {
5563            effective_opcode = curr.instr;
5564            effective_oparg = curr_arg;
5565        }
5566        debug_assert!(!effective_opcode.is_assembler());
5567        let Some(const_instr @ (Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. })) =
5568            effective_opcode.real()
5569        else {
5570            i += 1;
5571            continue;
5572        };
5573        let const_arg = effective_oparg;
5574
5575        if i + 1 >= block.instruction_used {
5576            i += 1;
5577            continue;
5578        }
5579
5580        let next = block.instructions[i + 1];
5581        let next_arg = next.arg;
5582
5583        if let Some(is_true) = load_const_truthiness(const_instr, const_arg, metadata) {
5584            let const_jump = match (next.instr.real_opcode(), next.instr.pseudo_opcode()) {
5585                (_, Some(PseudoOpcode::JumpIfTrue)) => Some((true, false)),
5586                (_, Some(PseudoOpcode::JumpIfFalse)) => Some((false, false)),
5587                (Some(Opcode::PopJumpIfTrue), _) => Some((true, true)),
5588                (Some(Opcode::PopJumpIfFalse), _) => Some((false, true)),
5589                _ => None,
5590            };
5591            if let Some((jump_if_true, pops_condition)) = const_jump {
5592                if pops_condition {
5593                    block.instructions[i].set_to_nop();
5594                }
5595                if is_true == jump_if_true {
5596                    block.instructions[i + 1].instr = PseudoOpcode::Jump.into();
5597                } else {
5598                    block.instructions[i + 1].set_to_nop();
5599                }
5600                i += 1;
5601                continue;
5602            }
5603        }
5604
5605        // The remaining combinations require both instructions to be real.
5606        let Some(next_instr) = next.instr.real() else {
5607            i += 1;
5608            continue;
5609        };
5610
5611        if let Instruction::LoadConst { consti } = const_instr {
5612            let constant = &metadata.consts[consti.get(const_arg).as_usize()];
5613            if matches!(constant, ConstantData::None)
5614                && let Instruction::IsOp { invert } = next_instr
5615            {
5616                let mut jump_idx = i + 2;
5617                if jump_idx >= block.instruction_used {
5618                    i += 1;
5619                    continue;
5620                }
5621
5622                if matches!(
5623                    block.instructions[jump_idx].instr.real(),
5624                    Some(Instruction::ToBool)
5625                ) {
5626                    block.instructions[jump_idx].set_to_nop();
5627                    jump_idx += 1;
5628                    if jump_idx >= block.instruction_used {
5629                        i += 1;
5630                        continue;
5631                    }
5632                }
5633
5634                let Some(jump_instr) = block.instructions[jump_idx].instr.real() else {
5635                    i += 1;
5636                    continue;
5637                };
5638
5639                let mut invert = matches!(
5640                    invert.get(next_arg),
5641                    rustpython_compiler_core::bytecode::Invert::Yes
5642                );
5643                match jump_instr {
5644                    Instruction::PopJumpIfFalse { .. } => {
5645                        invert = !invert;
5646                    }
5647                    Instruction::PopJumpIfTrue { .. } => {}
5648                    _ => {
5649                        i += 1;
5650                        continue;
5651                    }
5652                };
5653
5654                block.instructions[i].set_to_nop();
5655                block.instructions[i + 1].set_to_nop();
5656                block.instructions[jump_idx].instr = if invert {
5657                    Opcode::PopJumpIfNotNone
5658                } else {
5659                    Opcode::PopJumpIfNone
5660                }
5661                .into();
5662                i += 1;
5663                continue;
5664            }
5665        }
5666
5667        if matches!(
5668            const_instr,
5669            Instruction::LoadConst { .. } | Instruction::LoadSmallInt { .. }
5670        ) && matches!(next_instr, Instruction::ToBool)
5671            && let Some(value) = load_const_truthiness(const_instr, const_arg, metadata)
5672        {
5673            let const_idx = add_const(metadata, ConstantData::Boolean { value })?;
5674            block.instructions[i].set_to_nop();
5675
5676            block.instructions[i + 1]
5677                .instr_set_op1(Opcode::LoadConst.into(), OpArg::new(const_idx as u32));
5678            i += 1;
5679            continue;
5680        }
5681
5682        i += 1;
5683    }
5684    Ok(())
5685}
5686
5687/// flowgraph.c optimize_load_const
5688fn optimize_load_const(
5689    metadata: &mut CodeUnitMetadata,
5690    blocks: &mut Blocks,
5691) -> crate::InternalResult<()> {
5692    let mut block_idx = BlockIdx(0);
5693    while block_idx != BlockIdx::NULL {
5694        let next_block = blocks[block_idx].next;
5695        let block = &mut blocks[block_idx];
5696        basicblock_optimize_load_const(metadata, block)?;
5697        block_idx = next_block;
5698    }
5699    Ok(())
5700}
5701
5702#[cfg(test)]
5703impl CodeInfo {
5704    fn debug_block_dump(&self) -> String {
5705        let mut out = String::new();
5706        let mut block_idx = BlockIdx(0);
5707        while block_idx != BlockIdx::NULL {
5708            use core::fmt::Write;
5709            let block = &self.blocks[block_idx];
5710            let block_return = if block.basicblock_returns() {
5711                " return"
5712            } else {
5713                ""
5714            };
5715            let _ = writeln!(
5716                out,
5717                "block {} next={} cold={} except={} preserve_lasti={} start_depth={}{}",
5718                u32::from(block_idx),
5719                if block.next == BlockIdx::NULL {
5720                    String::from("NULL")
5721                } else {
5722                    u32::from(block.next).to_string()
5723                },
5724                block.cold,
5725                block.except_handler,
5726                block.preserve_lasti,
5727                if block.start_depth < 0 {
5728                    String::from("None")
5729                } else {
5730                    block.start_depth.to_string()
5731                },
5732                block_return,
5733            );
5734
5735            for info in &block.instructions[..block.instruction_used] {
5736                let lineno = info.instruction_lineno();
5737                let _ = writeln!(
5738                    out,
5739                    "  [disp={}:{} raw={}:{}-{}:{} override={:?}] {:?} arg={} target={}",
5740                    lineno,
5741                    info.location.character_offset.get(),
5742                    info.location.line.get(),
5743                    info.location.character_offset.get(),
5744                    info.end_location.line.get(),
5745                    info.end_location.character_offset.get(),
5746                    info.lineno_override,
5747                    info.instr,
5748                    u32::from(info.arg),
5749                    if info.target == BlockIdx::NULL {
5750                        String::from("NULL")
5751                    } else {
5752                        u32::from(info.target).to_string()
5753                    }
5754                );
5755            }
5756            block_idx = block.next;
5757        }
5758        out
5759    }
5760
5761    pub(crate) fn debug_late_cfg_trace(mut self) -> crate::InternalResult<Vec<(String, String)>> {
5762        let mut trace = Vec::new();
5763        trace.push(("initial".to_owned(), self.debug_block_dump()));
5764
5765        let instr_sequence = self.prepare_cfg_from_codegen();
5766        self.blocks = cfg_from_instruction_sequence(instr_sequence)?;
5767        trace.push((
5768            "after_cfg_from_instruction_sequence".to_owned(),
5769            self.debug_block_dump(),
5770        ));
5771        translate_jump_labels_to_targets(&mut self.blocks)?;
5772        self.blocks.mark_except_handlers();
5773        label_exception_targets(&mut self.blocks)?;
5774        self.blocks.check_cfg()?;
5775        self.blocks.inline_small_or_no_lineno_blocks()?;
5776        trace.push((
5777            "after_inline_small_or_no_lineno_blocks".to_owned(),
5778            self.debug_block_dump(),
5779        ));
5780        self.blocks.remove_unreachable()?;
5781        self.blocks
5782            .resolve_line_numbers(self.metadata.firstlineno)?;
5783        optimize_load_const(&mut self.metadata, &mut self.blocks)?;
5784        trace.push((
5785            "after_optimize_load_const".to_owned(),
5786            self.debug_block_dump(),
5787        ));
5788        let mut block_idx = BlockIdx(0);
5789        while block_idx != BlockIdx::NULL {
5790            let next_block = self.blocks[block_idx].next;
5791            self.blocks
5792                .optimize_basic_block(&mut self.metadata, block_idx)?;
5793            block_idx = next_block;
5794        }
5795        trace.push((
5796            "after_optimize_basic_block".to_owned(),
5797            self.debug_block_dump(),
5798        ));
5799        self.blocks.remove_redundant_nops_and_pairs();
5800        self.blocks.remove_unreachable()?;
5801        self.blocks.remove_redundant_nops_and_jumps()?;
5802
5803        #[cfg(debug_assertions)]
5804        assert!(self.blocks.no_redundant_jumps());
5805
5806        self.blocks
5807            .remove_unused_consts(&mut self.metadata.consts)?;
5808        trace.push((
5809            "after_optimize_cfg_cleanup".to_owned(),
5810            self.debug_block_dump(),
5811        ));
5812        let nlocals = self.metadata.varnames.len();
5813        let nparams = self.nparams;
5814        add_checks_for_loads_of_uninitialized_variables(&mut self.blocks, nlocals, nparams)?;
5815        self.blocks.insert_superinstructions();
5816        self.blocks.push_cold_blocks_to_end()?;
5817        trace.push((
5818            "after_push_cold_before_chain_reorder".to_owned(),
5819            self.debug_block_dump(),
5820        ));
5821        self.blocks
5822            .resolve_line_numbers(self.metadata.firstlineno)?;
5823        trace.push((
5824            "after_push_cold_resolve_line_numbers".to_owned(),
5825            self.debug_block_dump(),
5826        ));
5827
5828        trace.push((
5829            "after_push_cold_blocks_to_end".to_owned(),
5830            self.debug_block_dump(),
5831        ));
5832
5833        self.blocks.convert_pseudo_conditional_jumps()?;
5834        trace.push((
5835            "after_convert_pseudo_conditional_jumps".to_owned(),
5836            self.debug_block_dump(),
5837        ));
5838
5839        let _max_stackdepth = self.blocks.calculate_stackdepth()?;
5840        let _nlocalsplus = prepare_localsplus(&self.metadata, &mut self.blocks, self.flags)?;
5841        convert_pseudo_ops(&mut self.blocks)?;
5842        trace.push((
5843            "after_convert_pseudo_ops".to_owned(),
5844            self.debug_block_dump(),
5845        ));
5846
5847        self.blocks.normalize_jumps()?;
5848
5849        #[cfg(debug_assertions)]
5850        assert!(self.blocks.no_redundant_jumps());
5851
5852        trace.push(("after_normalize_jumps".to_owned(), self.debug_block_dump()));
5853        self.blocks.optimize_load_fast()?;
5854        trace.push((
5855            "after_optimize_load_fast".to_owned(),
5856            self.debug_block_dump(),
5857        ));
5858
5859        Ok(trace)
5860    }
5861}
5862
5863impl InstrDisplayContext for CodeInfo {
5864    type Constant = ConstantData;
5865
5866    fn get_constant(&self, consti: oparg::ConstIdx) -> &ConstantData {
5867        &self.metadata.consts[consti.as_usize()]
5868    }
5869
5870    fn get_name(&self, i: usize) -> &str {
5871        self.metadata.names[i].as_ref()
5872    }
5873
5874    fn get_varname(&self, var_num: oparg::VarNum) -> &str {
5875        self.metadata.varnames[var_num.as_usize()].as_ref()
5876    }
5877
5878    fn get_localsplus_name(&self, var_num: oparg::VarNum) -> &str {
5879        let idx = var_num.as_usize();
5880        let nlocals = self.metadata.varnames.len();
5881        if idx < nlocals {
5882            self.metadata.varnames[idx].as_ref()
5883        } else {
5884            let cell_idx = idx - nlocals;
5885            self.metadata
5886                .cellvars
5887                .get_index(cell_idx)
5888                .unwrap_or_else(|| &self.metadata.freevars[cell_idx - self.metadata.cellvars.len()])
5889                .as_ref()
5890        }
5891    }
5892}
5893
5894const NOT_LOCAL: isize = -1;
5895const DUMMY_INSTR: isize = -1;
5896
5897/// flowgraph.c LoadFastInstrFlag
5898#[derive(Clone, Copy, Eq, PartialEq)]
5899#[repr(u8)]
5900enum LoadFastInstrFlag {
5901    SupportKilled = 1,
5902    StoredAsLocal = 2,
5903    RefUnconsumed = 4,
5904}
5905
5906/// flowgraph.c ref
5907#[derive(Clone, Copy)]
5908struct Ref {
5909    instr: isize,
5910    local: isize,
5911}
5912
5913/// flowgraph.c ref_stack
5914struct RefStack {
5915    refs: Vec<Ref>,
5916    size: usize,
5917    capacity: usize,
5918}
5919
5920/// flowgraph.c ref_stack_push
5921fn ref_stack_push(stack: &mut RefStack, r: Ref) -> crate::InternalResult<()> {
5922    debug_assert_eq!(stack.refs.len(), stack.capacity);
5923    if stack.size == stack.capacity {
5924        let doubled = stack.capacity * 2;
5925        let new_cap = 32.max(doubled);
5926        stack
5927            .refs
5928            .try_reserve_exact(new_cap - stack.capacity)
5929            .map_err(|_| InternalError::MalformedControlFlowGraph)?;
5930        stack.refs.resize(new_cap, Ref { instr: 0, local: 0 });
5931        stack.capacity = new_cap;
5932    }
5933    stack.refs[stack.size] = r;
5934    stack.size += 1;
5935    Ok(())
5936}
5937
5938/// flowgraph.c ref_stack_pop
5939fn ref_stack_pop(stack: &mut RefStack) -> Ref {
5940    assert!(stack.size > 0);
5941    stack.size -= 1;
5942    stack.refs[stack.size]
5943}
5944
5945/// flowgraph.c ref_stack_swap_top
5946fn ref_stack_swap_top(stack: &mut RefStack, off: usize) {
5947    assert!(off >= 2 && stack.size >= off);
5948    let top = stack.size - 1;
5949    let other = stack.size - off;
5950    stack.refs.swap(top, other);
5951}
5952
5953/// flowgraph.c ref_stack_at
5954fn ref_stack_at(stack: &RefStack, idx: usize) -> Ref {
5955    assert!(idx < stack.size);
5956    stack.refs[idx]
5957}
5958
5959/// flowgraph.c ref_stack_clear
5960fn ref_stack_clear(stack: &mut RefStack) {
5961    stack.size = 0;
5962}
5963
5964/// flowgraph.c optimize_load_fast PUSH_REF
5965fn push_ref(stack: &mut RefStack, instr: isize, local: isize) -> crate::InternalResult<()> {
5966    ref_stack_push(stack, Ref { instr, local })
5967}
5968
5969/// flowgraph.c kill_local
5970fn kill_local(instr_flags: &mut [u8], refs: &RefStack, local: isize) {
5971    for i in 0..refs.size {
5972        let r = ref_stack_at(refs, i);
5973        if r.local != local {
5974            continue;
5975        }
5976        debug_assert!(r.instr >= 0);
5977        instr_flags[r.instr as usize] |= LoadFastInstrFlag::SupportKilled as u8;
5978    }
5979}
5980
5981/// flowgraph.c store_local
5982fn store_local(instr_flags: &mut [u8], refs: &RefStack, local: isize, r: Ref) {
5983    kill_local(instr_flags, refs, local);
5984    if r.instr != DUMMY_INSTR {
5985        instr_flags[r.instr as usize] |= LoadFastInstrFlag::StoredAsLocal as u8;
5986    }
5987}
5988
5989fn local_as_ref_local(local: usize) -> isize {
5990    local as isize
5991}
5992
5993/// flowgraph.c load_fast_push_block
5994fn load_fast_push_block(
5995    worklist: &mut CfgTraversalStack,
5996    blocks: &mut Blocks,
5997    target: BlockIdx,
5998    start_depth: usize,
5999) {
6000    debug_assert!(target != BlockIdx::NULL);
6001    debug_assert!(blocks[target].start_depth >= 0);
6002    debug_assert_eq!(blocks[target].start_depth as usize, start_depth,);
6003    if !blocks[target].visited {
6004        blocks[target].visited = true;
6005        worklist.push(target);
6006    }
6007}
6008
6009fn stackdepth_push(
6010    stack: &mut CfgTraversalStack,
6011    blocks: &mut Blocks,
6012    target: BlockIdx,
6013    depth: i32,
6014) -> crate::InternalResult<()> {
6015    let block_depth = &mut blocks[target].start_depth;
6016    if !(*block_depth < 0 || *block_depth == depth) {
6017        return Err(InternalError::InconsistentStackDepth);
6018    }
6019    if *block_depth < depth && *block_depth < 100 {
6020        debug_assert!(*block_depth < 0);
6021        *block_depth = depth;
6022        stack.push(target);
6023    }
6024    Ok(())
6025}
6026
6027/// flowgraph.c stack_effects
6028#[derive(Clone, Copy, Eq, PartialEq)]
6029struct StackEffects {
6030    net: i32,
6031}
6032
6033/// flowgraph.c get_stack_effects
6034fn get_stack_effects(
6035    instr: AnyInstruction,
6036    oparg: OpArg,
6037    jump: i32,
6038) -> crate::InternalResult<StackEffects> {
6039    if instr
6040        .real()
6041        .is_some_and(|op| op.as_opcode().deopt().is_some())
6042    {
6043        return Err(InternalError::InvalidStackEffect);
6044    }
6045    let oparg = u32::from(oparg);
6046    let net = if instr.is_block_push() && jump == 0 {
6047        0
6048    } else if jump != 0 {
6049        instr.stack_effect_jump(oparg)
6050    } else {
6051        instr.stack_effect(oparg)
6052    };
6053    Ok(StackEffects { net })
6054}
6055
6056fn vec_try_reserve_exact<T>(vec: &mut Vec<T>, additional: usize) -> crate::InternalResult<()> {
6057    vec.try_reserve_exact(additional)
6058        .map_err(|_| InternalError::MalformedControlFlowGraph)
6059}
6060
6061fn vec_try_resize_to_double_capacity<T>(vec: &mut Vec<T>) -> crate::InternalResult<()> {
6062    let capacity = vec.capacity();
6063    debug_assert!(capacity > 0);
6064    let len = capacity
6065        .checked_mul(core::mem::size_of::<T>())
6066        .ok_or(InternalError::MalformedControlFlowGraph)?;
6067    if capacity == 0 || len > usize::MAX / 2 {
6068        return Err(InternalError::MalformedControlFlowGraph);
6069    }
6070    let new_capacity = capacity * 2;
6071    let additional = new_capacity
6072        .checked_sub(vec.len())
6073        .ok_or(InternalError::MalformedControlFlowGraph)?;
6074    vec_try_reserve_exact(vec, additional)
6075}
6076
6077/// assemble.c write_location_first_byte
6078fn write_location_first_byte(linetable: &mut Vec<u8>, code: u8, length: usize) {
6079    linetable.extend(write_location_entry_start(code, length));
6080}
6081
6082/// pycore_code.h write_location_entry_start
6083fn write_location_entry_start(code: u8, length: usize) -> [u8; 1] {
6084    debug_assert!(length > 0 && length <= 8);
6085    debug_assert_eq!(code & 15, code);
6086    [0x80 | (code << 3) | ((length - 1) as u8)]
6087}
6088
6089/// assemble.c write_location_byte
6090fn write_location_byte(linetable: &mut Vec<u8>, value: u8) {
6091    linetable.push(value);
6092}
6093
6094/// assemble.c write_location_varint
6095fn write_location_varint(linetable: &mut Vec<u8>, value: u32) {
6096    write_varint(linetable, value);
6097}
6098
6099/// assemble.c write_location_signed_varint
6100fn write_location_signed_varint(linetable: &mut Vec<u8>, value: i32) {
6101    write_signed_varint(linetable, value);
6102}
6103
6104/// assemble.c write_location_info_short_form
6105fn write_location_info_short_form(
6106    linetable: &mut Vec<u8>,
6107    length: usize,
6108    column: i32,
6109    end_column: i32,
6110) {
6111    debug_assert!(length > 0 && length <= 8);
6112    debug_assert!(column < 80);
6113    debug_assert!(end_column >= column);
6114    debug_assert!(end_column - column < 16);
6115    let column_low_bits = column & 7;
6116    let column_group = column >> 3;
6117    let code = PyCodeLocationInfoKind::Short0 as u8 + column_group as u8;
6118    write_location_first_byte(linetable, code, length);
6119    write_location_byte(
6120        linetable,
6121        ((column_low_bits as u8) << 4) | ((end_column - column) as u8),
6122    );
6123}
6124
6125/// assemble.c write_location_info_oneline_form
6126fn write_location_info_oneline_form(
6127    linetable: &mut Vec<u8>,
6128    length: usize,
6129    line_delta: i32,
6130    column: i32,
6131    end_column: i32,
6132) {
6133    debug_assert!(length > 0 && length <= 8);
6134    debug_assert!((0..3).contains(&line_delta));
6135    debug_assert!(column < 128);
6136    debug_assert!(end_column < 128);
6137    let code = PyCodeLocationInfoKind::OneLine0 as u8 + line_delta as u8;
6138    write_location_first_byte(linetable, code, length);
6139    write_location_byte(linetable, column as u8);
6140    write_location_byte(linetable, end_column as u8);
6141}
6142
6143/// assemble.c write_location_info_long_form
6144fn write_location_info_long_form(
6145    linetable: &mut Vec<u8>,
6146    loc: LineTableLocation,
6147    length: usize,
6148    line_delta: i32,
6149) {
6150    debug_assert!(length > 0 && length <= 8);
6151    write_location_first_byte(linetable, PyCodeLocationInfoKind::Long as u8, length);
6152    write_location_signed_varint(linetable, line_delta);
6153    debug_assert!(loc.end_line >= loc.line);
6154    write_location_varint(linetable, (loc.end_line - loc.line) as u32);
6155    write_location_varint(
6156        linetable,
6157        if loc.col < 0 { 0 } else { (loc.col as u32) + 1 },
6158    );
6159    write_location_varint(
6160        linetable,
6161        if loc.end_col < 0 {
6162            0
6163        } else {
6164            (loc.end_col as u32) + 1
6165        },
6166    );
6167}
6168
6169/// assemble.c write_location_info_none
6170fn write_location_info_none(linetable: &mut Vec<u8>, length: usize) {
6171    write_location_first_byte(linetable, PyCodeLocationInfoKind::None as u8, length);
6172}
6173
6174/// assemble.c write_location_info_no_column
6175fn write_location_info_no_column(linetable: &mut Vec<u8>, length: usize, line_delta: i32) {
6176    write_location_first_byte(linetable, PyCodeLocationInfoKind::NoColumns as u8, length);
6177    write_location_signed_varint(linetable, line_delta);
6178}
6179
6180/// assemble.c write_location_info_entry
6181fn write_location_info_entry(
6182    linetable: &mut Vec<u8>,
6183    loc: LineTableLocation,
6184    length: usize,
6185    prev_line: &mut i32,
6186    debug_ranges: bool,
6187) -> crate::InternalResult<()> {
6188    const THEORETICAL_MAX_ENTRY_SIZE: usize = 25;
6189    if linetable
6190        .len()
6191        .checked_add(THEORETICAL_MAX_ENTRY_SIZE)
6192        .ok_or(InternalError::MalformedControlFlowGraph)?
6193        >= linetable.capacity()
6194    {
6195        debug_assert!(linetable.capacity() > THEORETICAL_MAX_ENTRY_SIZE);
6196        vec_try_resize_to_double_capacity(linetable)?;
6197    }
6198    if loc.line == NO_LOCATION_OVERRIDE {
6199        write_location_info_none(linetable, length);
6200        return Ok(());
6201    }
6202
6203    let line_delta = loc.line - *prev_line;
6204    let column = loc.col;
6205    let end_column = loc.end_col;
6206    if !debug_ranges
6207        || ((column < 0 || end_column < 0) && (loc.end_line == loc.line || loc.end_line < 0))
6208    {
6209        write_location_info_no_column(linetable, length, line_delta);
6210        *prev_line = loc.line;
6211        return Ok(());
6212    }
6213
6214    if loc.end_line == loc.line {
6215        if line_delta == 0 && column < 80 && end_column - column < 16 && end_column >= column {
6216            write_location_info_short_form(linetable, length, column, end_column);
6217            return Ok(());
6218        }
6219        if (0..3).contains(&line_delta) && column < 128 && end_column < 128 {
6220            write_location_info_oneline_form(linetable, length, line_delta, column, end_column);
6221            *prev_line = loc.line;
6222            return Ok(());
6223        }
6224    }
6225
6226    write_location_info_long_form(linetable, loc, length, line_delta);
6227    *prev_line = loc.line;
6228    Ok(())
6229}
6230
6231/// assemble.c assemble_emit_location
6232fn assemble_emit_location(
6233    linetable: &mut Vec<u8>,
6234    loc: LineTableLocation,
6235    mut size: usize,
6236    prev_line: &mut i32,
6237    debug_ranges: bool,
6238) -> crate::InternalResult<()> {
6239    if size == 0 {
6240        return Ok(());
6241    }
6242    while size > 8 {
6243        write_location_info_entry(linetable, loc, 8, prev_line, debug_ranges)?;
6244        size -= 8;
6245    }
6246    write_location_info_entry(linetable, loc, size, prev_line, debug_ranges)
6247}
6248
6249fn no_linetable_location() -> LineTableLocation {
6250    LineTableLocation {
6251        line: NO_LOCATION_OVERRIDE,
6252        end_line: NO_LOCATION_OVERRIDE,
6253        col: NO_LOCATION_OVERRIDE,
6254        end_col: NO_LOCATION_OVERRIDE,
6255    }
6256}
6257
6258fn next_linetable_location() -> LineTableLocation {
6259    LineTableLocation {
6260        line: NEXT_LOCATION_OVERRIDE,
6261        end_line: NEXT_LOCATION_OVERRIDE,
6262        col: NEXT_LOCATION_OVERRIDE,
6263        end_col: NEXT_LOCATION_OVERRIDE,
6264    }
6265}
6266
6267/// assemble.c assemble_emit_exception_table_item
6268fn assemble_emit_exception_table_item(table: &mut Vec<u8>, value: i32, mut msb: u8) {
6269    debug_assert!((msb | 128) == 128);
6270    debug_assert!((0..(1 << 30)).contains(&value));
6271    let value = value as u32;
6272    const CONTINUATION_BIT: u8 = 64;
6273    if value >= 1 << 24 {
6274        table.push(((value >> 24) as u8) | CONTINUATION_BIT | msb);
6275        msb = 0;
6276    }
6277    if value >= 1 << 18 {
6278        table.push((((value >> 18) & 0x3f) as u8) | CONTINUATION_BIT | msb);
6279        msb = 0;
6280    }
6281    if value >= 1 << 12 {
6282        table.push((((value >> 12) & 0x3f) as u8) | CONTINUATION_BIT | msb);
6283        msb = 0;
6284    }
6285    if value >= 1 << 6 {
6286        table.push((((value >> 6) & 0x3f) as u8) | CONTINUATION_BIT | msb);
6287        msb = 0;
6288    }
6289    table.push(((value & 0x3f) as u8) | msb);
6290}
6291
6292/// assemble.c assemble_emit_exception_table_entry
6293fn assemble_emit_exception_table_entry(
6294    table: &mut Vec<u8>,
6295    start: i32,
6296    end: i32,
6297    handler_offset: i32,
6298    handler: InstructionSequenceExceptHandlerInfo,
6299) -> crate::InternalResult<()> {
6300    const MAX_SIZE_OF_ENTRY: usize = 20;
6301    if table
6302        .len()
6303        .checked_add(MAX_SIZE_OF_ENTRY)
6304        .ok_or(InternalError::MalformedControlFlowGraph)?
6305        >= table.capacity()
6306    {
6307        vec_try_resize_to_double_capacity(table)?;
6308    }
6309    let size = end - start;
6310    debug_assert!(end > start);
6311    let target = handler_offset;
6312    let mut depth = handler.start_depth - 1;
6313    if handler.preserve_lasti > 0 {
6314        depth -= 1;
6315    }
6316    debug_assert!(depth >= 0);
6317    let depth_lasti = (depth << 1) | handler.preserve_lasti;
6318    assemble_emit_exception_table_item(table, start, 1 << 7);
6319    assemble_emit_exception_table_item(table, size, 0);
6320    assemble_emit_exception_table_item(table, target, 0);
6321    assemble_emit_exception_table_item(table, depth_lasti, 0);
6322    Ok(())
6323}
6324
6325/// assemble.c assemble_exception_table
6326fn assemble_exception_table(
6327    instrs: &[InstructionSequenceEntry],
6328) -> crate::InternalResult<Box<[u8]>> {
6329    let mut table = Vec::new();
6330    vec_try_reserve_exact(&mut table, DEFAULT_LNOTAB_SIZE)?;
6331    let mut handler = InstructionSequenceExceptHandlerInfo {
6332        h_label: NO_EXCEPTION_HANDLER_LABEL,
6333        start_depth: -1,
6334        preserve_lasti: -1,
6335    };
6336    let mut start = -1;
6337    let mut ioffset = 0i32;
6338
6339    for i in 0..instrs.len() {
6340        let instr = &instrs[i];
6341        if instr.except_handler.h_label != handler.h_label {
6342            if handler.h_label >= 0 {
6343                let handler_offset = instrs[handler.h_label as usize].i_offset;
6344                assemble_emit_exception_table_entry(
6345                    &mut table,
6346                    start,
6347                    ioffset,
6348                    handler_offset,
6349                    handler,
6350                )?;
6351            }
6352            start = ioffset;
6353            handler = instr.except_handler;
6354        }
6355        ioffset += instr.info.instr_size() as i32;
6356    }
6357
6358    if handler.h_label >= 0 {
6359        let handler_offset = instrs[handler.h_label as usize].i_offset;
6360        assemble_emit_exception_table_entry(&mut table, start, ioffset, handler_offset, handler)?;
6361    }
6362
6363    Ok(table.into_boxed_slice())
6364}
6365
6366/// pycore_opcode_utils.h IS_CONDITIONAL_JUMP_OPCODE
6367fn is_conditional_jump_opcode(instr: AnyInstruction) -> bool {
6368    matches!(
6369        instr.real().map(Into::into),
6370        Some(
6371            Opcode::PopJumpIfFalse
6372                | Opcode::PopJumpIfTrue
6373                | Opcode::PopJumpIfNone
6374                | Opcode::PopJumpIfNotNone
6375        )
6376    )
6377}
6378
6379/// flowgraph.c struct _PyCfgBuilder
6380struct CfgBuilder {
6381    blocks: Blocks,
6382    entry: BlockIdx,
6383    block_list: BlockIdx,
6384    current: BlockIdx,
6385    current_label: InstructionSequenceLabel,
6386}
6387
6388/// flowgraph.c cfg_builder_new_block
6389fn cfg_builder_new_block(g: &mut CfgBuilder) -> crate::InternalResult<BlockIdx> {
6390    let block = g.blocks.blocks_new_block()?;
6391    g.blocks[block].allocation_next = g.block_list;
6392    g.blocks[block].cpython_label = InstructionSequenceLabel::NO_LABEL;
6393    g.block_list = block;
6394    Ok(block)
6395}
6396
6397/// flowgraph.c cfg_builder_use_next_block
6398fn cfg_builder_use_next_block(g: &mut CfgBuilder, block: BlockIdx) -> BlockIdx {
6399    debug_assert!(block != BlockIdx::NULL);
6400    g.blocks[g.current].next = block;
6401    g.current = block;
6402    block
6403}
6404
6405/// flowgraph.c init_cfg_builder
6406fn init_cfg_builder(g: &mut CfgBuilder) -> crate::InternalResult<()> {
6407    g.block_list = BlockIdx::NULL;
6408    let block = cfg_builder_new_block(g)?;
6409    g.entry = block;
6410    g.current = block;
6411    g.current_label = InstructionSequenceLabel::NO_LABEL;
6412    Ok(())
6413}
6414
6415/// flowgraph.c _PyCfgBuilder_New
6416fn cfg_builder_new() -> crate::InternalResult<CfgBuilder> {
6417    let mut builder = CfgBuilder {
6418        blocks: Blocks::default(),
6419        entry: BlockIdx::NULL,
6420        block_list: BlockIdx::NULL,
6421        current: BlockIdx::NULL,
6422        current_label: InstructionSequenceLabel::NO_LABEL,
6423    };
6424    init_cfg_builder(&mut builder)?;
6425    Ok(builder)
6426}
6427
6428/// flowgraph.c cfg_builder_current_block_is_terminated
6429fn cfg_builder_current_block_is_terminated(g: &mut CfgBuilder) -> bool {
6430    let block = &mut g.blocks[g.current];
6431    let last = block.basicblock_last_instr().copied();
6432    if last.is_some_and(|last| last.instr.is_terminator()) {
6433        return true;
6434    }
6435    if is_label(g.current_label) {
6436        if last.is_some() || is_label(block.cpython_label) {
6437            return true;
6438        }
6439        block.cpython_label = g.current_label;
6440        g.current_label = InstructionSequenceLabel::NO_LABEL;
6441    }
6442    false
6443}
6444
6445/// flowgraph.c cfg_builder_maybe_start_new_block
6446fn cfg_builder_maybe_start_new_block(g: &mut CfgBuilder) -> crate::InternalResult<()> {
6447    if cfg_builder_current_block_is_terminated(g) {
6448        let block = cfg_builder_new_block(g)?;
6449        g.blocks[block].cpython_label = g.current_label;
6450        g.current_label = InstructionSequenceLabel::NO_LABEL;
6451        cfg_builder_use_next_block(g, block);
6452    }
6453    Ok(())
6454}
6455
6456/// flowgraph.c _PyCfgBuilder_UseLabel
6457fn cfg_builder_use_label(
6458    g: &mut CfgBuilder,
6459    label_id: InstructionSequenceLabel,
6460) -> crate::InternalResult<()> {
6461    g.current_label = label_id;
6462    cfg_builder_maybe_start_new_block(g)
6463}
6464
6465/// flowgraph.c _PyCfgBuilder_Addop
6466fn cfg_builder_addop(g: &mut CfgBuilder, info: InstructionInfo) -> crate::InternalResult<()> {
6467    cfg_builder_maybe_start_new_block(g)?;
6468    g.blocks[g.current].basicblock_addop(info)
6469}
6470
6471/// flowgraph.c cfg_builder_check
6472fn cfg_builder_check(g: &CfgBuilder) -> bool {
6473    debug_assert!(g.entry != BlockIdx::NULL);
6474    debug_assert!(g.blocks[g.entry].instruction_used != 0);
6475    let mut block = g.block_list;
6476    while block != BlockIdx::NULL {
6477        debug_assert!(block.idx() < g.blocks.len());
6478        let block_ref = &g.blocks[block];
6479        let has_instr_array = block_ref.instruction_allocation > 0;
6480        if has_instr_array {
6481            debug_assert!(block_ref.instruction_allocation > 0);
6482            debug_assert_eq!(
6483                block_ref.instructions.len(),
6484                block_ref.instruction_allocation
6485            );
6486            debug_assert!(block_ref.instruction_allocation >= block_ref.instruction_used);
6487        } else {
6488            debug_assert_eq!(block_ref.instruction_used, 0);
6489            debug_assert_eq!(block_ref.instruction_allocation, 0);
6490        }
6491        block = block_ref.allocation_next;
6492    }
6493    true
6494}
6495
6496/// flowgraph.c _PyCfgBuilder_CheckSize
6497fn cfg_builder_check_size(g: &CfgBuilder) -> crate::InternalResult<()> {
6498    debug_assert!(g.entry != BlockIdx::NULL);
6499    debug_assert!(g.block_list != BlockIdx::NULL);
6500    debug_assert!(g.current != BlockIdx::NULL);
6501    let mut nblocks = 0usize;
6502    let mut block = g.block_list;
6503    while block != BlockIdx::NULL {
6504        debug_assert!(block.idx() < g.blocks.len());
6505        nblocks += 1;
6506        block = g.blocks[block].allocation_next;
6507    }
6508    debug_assert_eq!(nblocks, g.blocks.len());
6509    if nblocks > usize::MAX / core::mem::size_of::<usize>() {
6510        return Err(InternalError::MalformedControlFlowGraph);
6511    }
6512    Ok(())
6513}
6514
6515/// flowgraph.c translate_jump_labels_to_targets
6516fn translate_jump_labels_to_targets(blocks: &mut Blocks) -> crate::InternalResult<()> {
6517    let max_label = get_max_label(blocks);
6518    let label_count = (max_label + 1) as usize;
6519    if label_count > usize::MAX / core::mem::size_of::<usize>() {
6520        return Err(InternalError::MalformedControlFlowGraph);
6521    }
6522    let mut label_to_block = Vec::new();
6523    vec_try_reserve_exact(&mut label_to_block, label_count)?;
6524    label_to_block.resize(label_count, BlockIdx::NULL);
6525
6526    let mut block_idx = BlockIdx(0);
6527    while block_idx != BlockIdx::NULL {
6528        let block = &blocks[block_idx];
6529        if is_label(block.cpython_label) {
6530            let label_id = block.cpython_label;
6531            debug_assert!(label_id.0 <= max_label);
6532            label_to_block[label_id.idx()] = block_idx;
6533        }
6534        block_idx = block.next;
6535    }
6536
6537    block_idx = BlockIdx(0);
6538    while block_idx != BlockIdx::NULL {
6539        let next = blocks[block_idx].next;
6540        for i in 0..blocks[block_idx].instruction_used {
6541            let info = &mut blocks[block_idx].instructions[i];
6542            debug_assert_eq!(info.target, BlockIdx::NULL);
6543            if info.instr.has_target() {
6544                let lbl = u32::from(info.arg) as i32;
6545                debug_assert!(lbl >= 0 && lbl <= max_label);
6546                let target = label_to_block[lbl as usize];
6547                debug_assert!(target != BlockIdx::NULL);
6548                info.target = target;
6549                debug_assert_eq!(blocks[target].cpython_label, InstructionSequenceLabel(lbl));
6550            }
6551        }
6552        block_idx = next;
6553    }
6554    Ok(())
6555}
6556
6557/// flowgraph.c _PyCfg_FromInstructionSequence
6558fn cfg_from_instruction_sequence(
6559    mut instr_sequence: InstructionSequence,
6560) -> crate::InternalResult<Blocks> {
6561    instruction_sequence_apply_label_map(&mut instr_sequence);
6562    let mut builder = cfg_builder_new()?;
6563
6564    for i in 0..instr_sequence.instr_used {
6565        instr_sequence.instrs[i].i_target = 0;
6566    }
6567    for i in 0..instr_sequence.instr_used {
6568        if instr_sequence.instrs[i].info.instr.has_target() {
6569            let target_offset = u32::from(instr_sequence.instrs[i].info.arg) as usize;
6570            debug_assert!(target_offset < instr_sequence.instr_used);
6571            instr_sequence.instrs[target_offset].i_target = 1;
6572        }
6573    }
6574    let InstructionSequence {
6575        instrs,
6576        instr_used,
6577        label_map,
6578        label_map_allocation,
6579        annotations_code,
6580        ..
6581    } = instr_sequence;
6582    debug_assert!(label_map.is_none());
6583    debug_assert_eq!(label_map_allocation, 0);
6584
6585    let mut offset = 0i32;
6586
6587    let mut i = 0;
6588    while i < instr_used {
6589        let mut entry = instrs[i];
6590        if matches!(
6591            entry.info.instr.pseudo(),
6592            Some(PseudoInstruction::AnnotationsPlaceholder)
6593        ) {
6594            if let Some(annotations_code) = &annotations_code {
6595                debug_assert!(annotations_code.label_map.is_none());
6596                debug_assert_eq!(annotations_code.label_map_allocation, 0);
6597                for ann_entry in annotations_code
6598                    .instrs
6599                    .iter()
6600                    .take(annotations_code.instr_used)
6601                {
6602                    debug_assert!(!ann_entry.info.instr.has_target());
6603                    let mut info = ann_entry.info;
6604                    info.target = BlockIdx::NULL;
6605                    cfg_builder_addop(&mut builder, info)?;
6606                }
6607                offset += annotations_code.instr_used as i32 - 1;
6608            } else {
6609                offset -= 1;
6610            }
6611            i += 1;
6612            continue;
6613        }
6614
6615        if entry.i_target != 0 {
6616            let label_id = i as i32 + offset;
6617            let label = InstructionSequenceLabel(label_id);
6618            cfg_builder_use_label(&mut builder, label)?;
6619        }
6620
6621        let opcode = entry.info.instr;
6622        let mut oparg = entry.info.arg;
6623        if opcode.has_target() {
6624            let target_offset = u32::from(oparg) as i32 + offset;
6625            debug_assert!(target_offset >= 0);
6626            oparg = OpArg::new(target_offset as u32);
6627        }
6628        entry.info.instr = opcode;
6629        entry.info.arg = oparg;
6630        entry.info.target = BlockIdx::NULL;
6631        cfg_builder_addop(&mut builder, entry.info)?;
6632        i += 1;
6633    }
6634
6635    cfg_builder_check_size(&builder)?;
6636    debug_assert!(cfg_builder_check(&builder));
6637    Ok(builder.blocks)
6638}
6639
6640/// flowgraph.c maybe_push
6641fn maybe_push(
6642    blocks: &mut Blocks,
6643    worklist: &mut CfgTraversalStack,
6644    block: BlockIdx,
6645    unsafe_mask: u64,
6646) {
6647    debug_assert!(block != BlockIdx::NULL);
6648
6649    let both = blocks[block].unsafe_locals_mask | unsafe_mask;
6650    if blocks[block].unsafe_locals_mask != both {
6651        blocks[block].unsafe_locals_mask = both;
6652        if !blocks[block].visited {
6653            worklist.push(block);
6654            blocks[block].visited = true;
6655        }
6656    }
6657}
6658
6659/// flowgraph.c scan_block_for_locals
6660fn scan_block_for_locals(
6661    blocks: &mut Blocks,
6662    block_idx: BlockIdx,
6663    worklist: &mut CfgTraversalStack,
6664) {
6665    let idx = block_idx.idx();
6666    let mut unsafe_mask = blocks[idx].unsafe_locals_mask;
6667    let instr_count = blocks[idx].instruction_used;
6668
6669    for i in 0..instr_count {
6670        let (instr, arg, except_handler) = {
6671            let info = &blocks[idx].instructions[i];
6672            (
6673                info.instr,
6674                info.arg,
6675                info.except_handler.map(|eh| eh.handler_block),
6676            )
6677        };
6678        debug_assert!(!matches!(instr.real(), Some(Instruction::ExtendedArg)));
6679
6680        if let Some(handler_block) = except_handler {
6681            maybe_push(blocks, worklist, handler_block, unsafe_mask);
6682        }
6683
6684        let oparg = u32::from(arg) as usize;
6685        if oparg >= LOCAL_UNSAFE_MASK_BITS {
6686            continue;
6687        }
6688
6689        let bit = 1u64 << oparg;
6690        match instr {
6691            AnyInstruction::Real(
6692                Instruction::DeleteFast { .. } | Instruction::LoadFastAndClear { .. },
6693            )
6694            | AnyInstruction::Pseudo(PseudoInstruction::StoreFastMaybeNull { .. }) => {
6695                unsafe_mask |= bit;
6696            }
6697            AnyInstruction::Real(Instruction::StoreFast { .. }) => {
6698                unsafe_mask &= !bit;
6699            }
6700            AnyInstruction::Real(Instruction::LoadFastCheck { .. }) => {
6701                // If this doesn't raise, then the local is defined.
6702                unsafe_mask &= !bit;
6703            }
6704            AnyInstruction::Real(Instruction::LoadFast { .. }) => {
6705                if unsafe_mask & bit != 0 {
6706                    blocks[idx].instructions[i].instr = Opcode::LoadFastCheck.into();
6707                }
6708                unsafe_mask &= !bit;
6709            }
6710            _ => {}
6711        }
6712    }
6713
6714    let next = blocks[idx].next;
6715    if next != BlockIdx::NULL && blocks[idx].bb_has_fallthrough() {
6716        maybe_push(blocks, worklist, next, unsafe_mask);
6717    }
6718
6719    let last = blocks[idx].basicblock_last_instr().copied();
6720    if let Some(last) = last
6721        && last.is_jump()
6722    {
6723        let target = last.target;
6724        debug_assert!(target != BlockIdx::NULL);
6725        maybe_push(blocks, worklist, target, unsafe_mask);
6726    }
6727}
6728
6729/// flowgraph.c fast_scan_many_locals
6730fn fast_scan_many_locals(blocks: &mut Blocks, nlocals: usize) -> crate::InternalResult<()> {
6731    debug_assert!(nlocals > LOCAL_UNSAFE_MASK_BITS);
6732    let mut states = Vec::new();
6733    states
6734        .try_reserve_exact(nlocals - LOCAL_UNSAFE_MASK_BITS)
6735        .map_err(|_| InternalError::MalformedControlFlowGraph)?;
6736    states.resize(nlocals - LOCAL_UNSAFE_MASK_BITS, 0usize);
6737    let mut blocknum = 0usize;
6738    let mut current = BlockIdx(0);
6739    while current != BlockIdx::NULL {
6740        blocknum += 1;
6741        for i in 0..blocks[current].instruction_used {
6742            let info = &mut blocks[current].instructions[i];
6743            debug_assert!(!matches!(info.instr.real(), Some(Instruction::ExtendedArg)));
6744            let arg = u32::from(info.arg) as usize;
6745            if arg < LOCAL_UNSAFE_MASK_BITS {
6746                continue;
6747            }
6748            debug_assert!(arg >= LOCAL_UNSAFE_MASK_BITS);
6749            match info.instr {
6750                AnyInstruction::Real(
6751                    Instruction::DeleteFast { .. } | Instruction::LoadFastAndClear { .. },
6752                )
6753                | AnyInstruction::Pseudo(PseudoInstruction::StoreFastMaybeNull { .. }) => {
6754                    debug_assert!(arg < nlocals);
6755                    states[arg - LOCAL_UNSAFE_MASK_BITS] = blocknum - 1;
6756                }
6757                AnyInstruction::Real(Instruction::StoreFast { .. }) => {
6758                    debug_assert!(arg < nlocals);
6759                    states[arg - LOCAL_UNSAFE_MASK_BITS] = blocknum;
6760                }
6761                AnyInstruction::Real(Instruction::LoadFast { .. }) => {
6762                    debug_assert!(arg < nlocals);
6763                    if states[arg - LOCAL_UNSAFE_MASK_BITS] != blocknum {
6764                        info.instr = Opcode::LoadFastCheck.into();
6765                    }
6766                    states[arg - LOCAL_UNSAFE_MASK_BITS] = blocknum;
6767                }
6768                _ => {}
6769            }
6770        }
6771        current = blocks[current].next;
6772    }
6773    Ok(())
6774}
6775
6776/// flowgraph.c add_checks_for_loads_of_uninitialized_variables
6777fn add_checks_for_loads_of_uninitialized_variables(
6778    blocks: &mut Blocks,
6779    mut nlocals: usize,
6780    nparams: usize,
6781) -> crate::InternalResult<()> {
6782    if nlocals == 0 {
6783        return Ok(());
6784    }
6785
6786    if nlocals > LOCAL_UNSAFE_MASK_BITS {
6787        fast_scan_many_locals(blocks, nlocals)?;
6788        nlocals = LOCAL_UNSAFE_MASK_BITS;
6789    }
6790
6791    let mut worklist = blocks.make_cfg_traversal_stack()?;
6792    let mut start_mask = 0u64;
6793    for i in nparams..nlocals {
6794        start_mask |= 1u64 << i;
6795    }
6796    maybe_push(blocks, &mut worklist, BlockIdx(0), start_mask);
6797
6798    let mut current = BlockIdx(0);
6799    while current != BlockIdx::NULL {
6800        scan_block_for_locals(blocks, current, &mut worklist);
6801        current = blocks[current].next;
6802    }
6803
6804    while let Some(block_idx) = worklist.pop() {
6805        blocks[block_idx].visited = false;
6806        scan_block_for_locals(blocks, block_idx, &mut worklist);
6807    }
6808    Ok(())
6809}
6810
6811/// Follow chain of empty blocks to find first non-empty block.
6812fn next_nonempty_block(blocks: &Blocks, mut idx: BlockIdx) -> BlockIdx {
6813    while idx != BlockIdx::NULL && blocks[idx].instruction_used == 0 {
6814        idx = blocks[idx].next;
6815    }
6816    idx
6817}
6818
6819/// flowgraph.c add_checks_for_loads_of_uninitialized_variables uses uint64_t masks.
6820const LOCAL_UNSAFE_MASK_BITS: usize = 64;
6821
6822/// flowgraph.c MAX_COPY_SIZE
6823const MAX_COPY_SIZE: usize = 4;
6824
6825/// flowgraph.c get_max_label
6826fn get_max_label(blocks: &Blocks) -> i32 {
6827    let mut lbl = -1;
6828    let mut current = BlockIdx(0);
6829    while current != BlockIdx::NULL {
6830        let cpython_label = blocks[current].cpython_label;
6831        lbl = lbl.max(cpython_label.0);
6832        current = blocks[current].next;
6833    }
6834    lbl
6835}
6836
6837/// flowgraph.c make_except_stack
6838fn make_except_stack() -> CfgExceptStack {
6839    let handlers = [BlockIdx::NULL; CO_MAXBLOCKS + 2];
6840    debug_assert_eq!(handlers[0], BlockIdx::NULL);
6841    CfgExceptStack { handlers, depth: 0 }
6842}
6843
6844/// flowgraph.c copy_except_stack
6845fn copy_except_stack(stack: &CfgExceptStack) -> CfgExceptStack {
6846    debug_assert!(stack.depth <= CO_MAXBLOCKS + 1);
6847    CfgExceptStack {
6848        handlers: stack.handlers,
6849        depth: stack.depth,
6850    }
6851}
6852
6853/// flowgraph.c except_stack_top
6854fn except_stack_top(stack: &CfgExceptStack, blocks: &Blocks) -> Option<ExceptHandlerInfo> {
6855    debug_assert!(stack.depth <= CO_MAXBLOCKS + 1);
6856    let handler_block = stack.handlers[stack.depth];
6857    if handler_block == BlockIdx::NULL {
6858        return None;
6859    }
6860    Some(ExceptHandlerInfo {
6861        handler_block,
6862        preserve_lasti: blocks[handler_block].preserve_lasti,
6863    })
6864}
6865
6866/// flowgraph.c push_except_block
6867fn push_except_block(
6868    stack: &mut CfgExceptStack,
6869    setup: InstructionInfo,
6870    blocks: &mut Blocks,
6871) -> Option<ExceptHandlerInfo> {
6872    debug_assert!(setup.is_block_push());
6873    let instr = setup.instr;
6874    let target = setup.target;
6875    debug_assert!(target != BlockIdx::NULL);
6876    if matches!(
6877        instr.pseudo(),
6878        Some(PseudoInstruction::SetupWith { .. } | PseudoInstruction::SetupCleanup { .. })
6879    ) {
6880        blocks[target].preserve_lasti = true;
6881    }
6882    debug_assert!(stack.depth <= CO_MAXBLOCKS);
6883    stack.depth += 1;
6884    stack.handlers[stack.depth] = target;
6885    debug_assert!(stack.depth <= CO_MAXBLOCKS + 1);
6886    except_stack_top(stack, blocks)
6887}
6888
6889/// flowgraph.c pop_except_block
6890fn pop_except_block(stack: &mut CfgExceptStack, blocks: &Blocks) -> Option<ExceptHandlerInfo> {
6891    debug_assert!(stack.depth > 0);
6892    stack.depth -= 1;
6893    debug_assert!(stack.depth <= CO_MAXBLOCKS);
6894    except_stack_top(stack, blocks)
6895}
6896
6897pub(crate) fn label_exception_targets(blocks: &mut Blocks) -> crate::InternalResult<()> {
6898    let mut todo = blocks.make_cfg_traversal_stack()?;
6899
6900    todo.push(BlockIdx(0));
6901    blocks[0].visited = true;
6902    blocks[0].except_stack = Some(make_except_stack());
6903
6904    while let Some(block_idx) = todo.pop() {
6905        let bi = block_idx.idx();
6906        debug_assert!(blocks[bi].visited);
6907        let mut stack = Some(
6908            blocks[bi]
6909                .except_stack
6910                .take()
6911                .expect("visited exception block has an except stack"),
6912        );
6913        let mut handler = except_stack_top(stack.as_ref().expect("active exception stack"), blocks);
6914        let mut last_yield_except_depth: i32 = -1;
6915        let mut stack_transferred = false;
6916
6917        let instr_count = blocks[bi].instruction_used;
6918        for i in 0..instr_count {
6919            let info = blocks[bi].instructions[i];
6920            let instr = info.instr;
6921            let target = info.target;
6922            let arg = info.arg;
6923
6924            if info.is_block_push() {
6925                debug_assert!(target != BlockIdx::NULL);
6926                if !blocks[target].visited {
6927                    blocks[target].except_stack = Some(copy_except_stack(
6928                        stack.as_ref().expect("active exception stack"),
6929                    ));
6930                    todo.push(target);
6931                    blocks[target].visited = true;
6932                }
6933                handler = push_except_block(
6934                    stack.as_mut().expect("active exception stack"),
6935                    info,
6936                    blocks,
6937                );
6938            } else if instr.is_pop_block() {
6939                handler = pop_except_block(stack.as_mut().expect("active exception stack"), blocks);
6940                blocks[bi].instructions[i].set_to_nop();
6941            } else if blocks[bi].instructions[i].is_jump() {
6942                blocks[bi].instructions[i].except_handler = handler;
6943                debug_assert_eq!(i, instr_count - 1);
6944
6945                // CPython label_exception_targets(): copy the except stack
6946                // when this block can also fall through, otherwise transfer it
6947                // to the jump target.
6948                debug_assert!(target != BlockIdx::NULL);
6949                if !blocks[target].visited {
6950                    if blocks[bi].bb_has_fallthrough() {
6951                        blocks[target].except_stack = Some(copy_except_stack(
6952                            stack.as_ref().expect("active exception stack"),
6953                        ));
6954                    } else {
6955                        blocks[target].except_stack = stack.take();
6956                        stack_transferred = true;
6957                        todo.push(target);
6958                        blocks[target].visited = true;
6959                        break;
6960                    }
6961                    todo.push(target);
6962                    blocks[target].visited = true;
6963                }
6964            } else if matches!(instr.real(), Some(Instruction::YieldValue { .. })) {
6965                blocks[bi].instructions[i].except_handler = handler;
6966                last_yield_except_depth =
6967                    stack.as_ref().expect("active exception stack").depth as i32;
6968            } else if let Some(Instruction::Resume { context: _ }) = instr.real() {
6969                blocks[bi].instructions[i].except_handler = handler;
6970                let resume_arg = u32::from(arg);
6971                if resume_arg != u32::from(oparg::ResumeLocation::AtFuncStart) {
6972                    debug_assert!(last_yield_except_depth >= 0);
6973                    if last_yield_except_depth == 1 {
6974                        blocks[bi].instructions[i].arg =
6975                            OpArg::new(resume_arg | oparg::ResumeContext::DEPTH1_MASK);
6976                    }
6977                    last_yield_except_depth = -1;
6978                }
6979            } else {
6980                blocks[bi].instructions[i].except_handler = handler;
6981            }
6982        }
6983
6984        let next = blocks[bi].next;
6985        if !stack_transferred && blocks[bi].bb_has_fallthrough() {
6986            debug_assert!(next != BlockIdx::NULL);
6987            if next != BlockIdx::NULL && !blocks[next].visited {
6988                blocks[next].except_stack = stack.take();
6989                todo.push(next);
6990                blocks[next].visited = true;
6991            }
6992        }
6993    }
6994    #[cfg(debug_assertions)]
6995    {
6996        let mut block_idx = BlockIdx(0);
6997        while block_idx != BlockIdx::NULL {
6998            let block = &blocks[block_idx];
6999            debug_assert!(block.except_stack.is_none());
7000            block_idx = block.next;
7001        }
7002    }
7003    Ok(())
7004}
7005
7006/// Convert remaining pseudo ops to real instructions or NOP.
7007/// flowgraph.c convert_pseudo_ops
7008pub(crate) fn convert_pseudo_ops(blocks: &mut Blocks) -> crate::InternalResult<()> {
7009    let mut block_idx = BlockIdx(0);
7010    while block_idx != BlockIdx::NULL {
7011        let next = blocks[block_idx].next;
7012        let block = &mut blocks[block_idx];
7013        for i in 0..block.instruction_used {
7014            let info = &mut block.instructions[i];
7015            if info.is_block_push() {
7016                info.set_to_nop();
7017            } else if matches!(
7018                info.instr.pseudo(),
7019                Some(PseudoInstruction::LoadClosure { .. })
7020            ) {
7021                debug_assert!(is_pseudo_target(
7022                    PseudoOpcode::LoadClosure,
7023                    Opcode::LoadFast
7024                ));
7025                info.instr = Opcode::LoadFast.into();
7026            } else if matches!(
7027                info.instr.pseudo(),
7028                Some(PseudoInstruction::StoreFastMaybeNull { .. })
7029            ) {
7030                debug_assert!(is_pseudo_target(
7031                    PseudoOpcode::StoreFastMaybeNull,
7032                    Opcode::StoreFast
7033                ));
7034                info.instr = Opcode::StoreFast.into();
7035            }
7036        }
7037        block_idx = next;
7038    }
7039    // CPython flowgraph.c::convert_pseudo_ops() finishes by calling
7040    // remove_redundant_nops_and_jumps().
7041    blocks.remove_redundant_nops_and_jumps()
7042}
7043
7044/// flowgraph.c build_cellfixedoffsets
7045pub(crate) fn build_cellfixedoffsets(
7046    metadata: &CodeUnitMetadata,
7047) -> crate::InternalResult<Vec<i32>> {
7048    let nlocals = metadata.varnames.len();
7049    let ncellvars = metadata.cellvars.len();
7050    let nfreevars = metadata.freevars.len();
7051    let noffsets = ncellvars + nfreevars;
7052    let mut fixed = Vec::new();
7053    vec_try_reserve_exact(&mut fixed, noffsets)?;
7054    fixed.resize(noffsets, 0);
7055
7056    for (i, item) in fixed.iter_mut().enumerate().take(noffsets) {
7057        *item = (nlocals + i) as i32;
7058    }
7059
7060    for (oldindex, cell) in fixed.iter_mut().enumerate().take(ncellvars) {
7061        let varname = metadata
7062            .cellvars
7063            .get_index(oldindex)
7064            .expect("cellvar index is in range");
7065        if let Some(varindex) = metadata.varnames.get_index_of(varname) {
7066            let argoffset = varindex as i32;
7067            *cell = argoffset;
7068        }
7069    }
7070    Ok(fixed)
7071}
7072
7073/// flowgraph.c fix_cell_offsets
7074pub(crate) fn fix_cell_offsets(
7075    metadata: &CodeUnitMetadata,
7076    blocks: &mut Blocks,
7077    cellfixedoffsets: &mut [i32],
7078) -> usize {
7079    let nlocals = metadata.varnames.len();
7080    let ncellvars = metadata.cellvars.len();
7081    let nfreevars = metadata.freevars.len();
7082    let noffsets = ncellvars + nfreevars;
7083    debug_assert_eq!(cellfixedoffsets.len(), noffsets);
7084
7085    let mut numdropped = 0usize;
7086    for (i, cell) in cellfixedoffsets.iter_mut().enumerate().take(noffsets) {
7087        if *cell == (i + nlocals) as i32 {
7088            *cell -= numdropped as i32;
7089        } else {
7090            numdropped += 1;
7091        }
7092    }
7093
7094    let mut block_idx = BlockIdx(0);
7095    while block_idx != BlockIdx::NULL {
7096        let next = blocks[block_idx].next;
7097        let block = &mut blocks[block_idx];
7098        for i in 0..block.instruction_used {
7099            let inst = &mut block.instructions[i];
7100            debug_assert!(
7101                !matches!(inst.instr.real(), Some(Instruction::ExtendedArg)),
7102                "fix_cell_offsets is called before extended args are generated"
7103            );
7104            let oldoffset = u32::from(inst.arg) as i32;
7105            match inst.instr {
7106                AnyInstruction::Real(
7107                    Instruction::MakeCell { .. }
7108                    | Instruction::LoadDeref { .. }
7109                    | Instruction::StoreDeref { .. }
7110                    | Instruction::DeleteDeref { .. }
7111                    | Instruction::LoadFromDictOrDeref { .. },
7112                )
7113                | AnyInstruction::Pseudo(PseudoInstruction::LoadClosure { .. }) => {
7114                    debug_assert!(oldoffset >= 0);
7115                    debug_assert!(oldoffset < noffsets as i32);
7116                    let fixed_offset = cellfixedoffsets[oldoffset as usize];
7117                    debug_assert!(fixed_offset >= 0);
7118                    inst.arg = OpArg::new(fixed_offset as u32);
7119                }
7120                _ => {}
7121            }
7122        }
7123        block_idx = next;
7124    }
7125    numdropped
7126}
7127
7128#[cfg(test)]
7129mod tests {
7130    use super::*;
7131    use rustpython_compiler_core::bytecode::Arg;
7132
7133    fn int_const(value: i32) -> ConstantData {
7134        ConstantData::Integer {
7135            value: BigInt::from(value),
7136        }
7137    }
7138
7139    fn nan_const() -> ConstantData {
7140        ConstantData::Float { value: f64::NAN }
7141    }
7142
7143    #[test]
7144    fn constant_pool_frozenset_key_ignores_order_and_duplicates_like_cpython() {
7145        let mut pool = ConstantPool::default();
7146        let (first, inserted) = pool.insert_full(ConstantData::Frozenset {
7147            elements: vec![int_const(1), int_const(2)],
7148        });
7149        assert_eq!(first, 0);
7150        assert!(inserted);
7151
7152        let (second, inserted) = pool.insert_full(ConstantData::Frozenset {
7153            elements: vec![int_const(2), int_const(1), int_const(1)],
7154        });
7155        assert_eq!(
7156            second, first,
7157            "CPython _PyCode_ConstantKey uses frozenset item keys, not insertion order"
7158        );
7159        assert!(!inserted);
7160        assert!(matches!(
7161            &pool.constants[first],
7162            ConstantData::Frozenset { elements } if elements.len() == 2
7163        ));
7164    }
7165
7166    #[test]
7167    fn constant_pool_frozenset_key_preserves_nan_duplicates_like_cpython() {
7168        let mut pool = ConstantPool::default();
7169        let (idx, inserted) = pool.insert_full(ConstantData::Frozenset {
7170            elements: vec![nan_const(), nan_const()],
7171        });
7172
7173        assert_eq!(idx, 0);
7174        assert!(inserted);
7175        assert!(matches!(
7176            &pool.constants[idx],
7177            ConstantData::Frozenset { elements }
7178                if elements.iter().filter(|constant| {
7179                    matches!(constant, ConstantData::Float { value } if value.is_nan())
7180                }).count() == 2
7181        ));
7182    }
7183
7184    fn test_location(line: u32) -> SourceLocation {
7185        SourceLocation {
7186            line: OneIndexed::new(line as usize).expect("valid line number"),
7187            character_offset: OneIndexed::MIN,
7188        }
7189    }
7190
7191    fn test_instr(instr: Instruction, line: u32) -> InstructionInfo {
7192        InstructionInfo {
7193            instr: instr.into(),
7194            arg: OpArg::new(0),
7195            target: BlockIdx::NULL,
7196            location: test_location(line),
7197            end_location: test_location(line),
7198            except_handler: None,
7199            lineno_override: None,
7200        }
7201    }
7202
7203    fn test_jump(target: BlockIdx, line: u32) -> InstructionInfo {
7204        let mut instr = test_instr(Instruction::Nop, line);
7205        instr.instr = PseudoOpcode::Jump.into();
7206        instr.target = target;
7207        instr
7208    }
7209
7210    fn test_cond_jump(target: BlockIdx, line: u32) -> InstructionInfo {
7211        let mut instr = test_instr(Instruction::Nop, line);
7212        instr.instr = PseudoOpcode::JumpIfFalse.into();
7213        instr.target = target;
7214        instr
7215    }
7216
7217    fn test_true_cond_jump(target: BlockIdx, line: u32) -> InstructionInfo {
7218        let mut instr = test_instr(Instruction::Nop, line);
7219        instr.instr = PseudoOpcode::JumpIfTrue.into();
7220        instr.target = target;
7221        instr
7222    }
7223
7224    fn test_block_push(block: &mut Block, info: InstructionInfo) {
7225        let off = block
7226            .basicblock_next_instr()
7227            .expect("test block instruction slot");
7228        block.instructions[off] = info;
7229    }
7230
7231    fn test_code_info(block: Block) -> CodeInfo {
7232        CodeInfo {
7233            flags: CodeFlags::empty(),
7234            source_path: "source_path".to_owned(),
7235            private: None,
7236            blocks: Blocks::from([block]),
7237            current_block: BlockIdx::new(0),
7238            instr_sequence: instruction_sequence_new(),
7239            instr_sequence_label_map: InstructionSequenceLabelMap::new(),
7240            annotations_instr_sequence: None,
7241            metadata: CodeUnitMetadata {
7242                name: "<module>".to_owned(),
7243                qualname: Some("<module>".to_owned()),
7244                consts: Default::default(),
7245                names: IndexSet::default(),
7246                varnames: IndexSet::default(),
7247                cellvars: IndexSet::default(),
7248                freevars: IndexSet::default(),
7249                fast_hidden: IndexMap::default(),
7250                fast_hidden_final: IndexSet::default(),
7251                argcount: 0,
7252                posonlyargcount: 0,
7253                kwonlyargcount: 0,
7254                firstlineno: OneIndexed::MIN,
7255            },
7256            static_attributes: None,
7257            in_inlined_comp: false,
7258            fblock: Vec::new(),
7259            symbol_table_index: 0,
7260            nparams: 0,
7261            in_conditional_block: 0,
7262            next_conditional_annotation_index: 0,
7263        }
7264    }
7265
7266    #[test]
7267    fn get_stack_effects_rejects_cpython_deopt_opcodes() {
7268        match get_stack_effects(Instruction::BinaryOpAddInt.into(), OpArg::new(0), 0) {
7269            Err(InternalError::InvalidStackEffect) => {}
7270            Err(err) => panic!("unexpected stack-effect error: {err}"),
7271            Ok(_) => panic!("CPython get_stack_effects rejects specialized deopt opcodes"),
7272        }
7273    }
7274
7275    #[test]
7276    fn instruction_sequence_label_shadow_preserves_cpython_offset_aliases() {
7277        let mut seq = instruction_sequence_new();
7278        let mut labels = InstructionSequenceLabelMap::new();
7279        instruction_sequence_label_map_push_unmapped_label(&mut labels, &mut seq).unwrap();
7280        instruction_sequence_label_map_push_unmapped_label(&mut labels, &mut seq).unwrap();
7281        assert_eq!(
7282            labels.cpython_block_by_label.len(),
7283            INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE
7284        );
7285
7286        let first = BlockIdx::new(1);
7287        let second = BlockIdx::new(2);
7288        assert_ne!(
7289            instruction_sequence_label_map_label_for_block(&labels, first),
7290            instruction_sequence_label_map_label_for_block(&labels, second)
7291        );
7292
7293        // CPython `_PyInstructionSequence_UseLabel()` can map consecutive
7294        // labels to the same instruction offset. The codegen CFG shadow must
7295        // resolve the later block label to the block owning that shared offset.
7296        instruction_sequence_label_map_use_label_at_block(&mut labels, &mut seq, second, first)
7297            .unwrap();
7298        assert_eq!(
7299            instruction_sequence_label_map_resolve_label(&labels, first),
7300            first
7301        );
7302        assert_eq!(
7303            instruction_sequence_label_map_resolve_label(&labels, second),
7304            first
7305        );
7306    }
7307
7308    #[test]
7309    fn except_stack_tracks_cpython_depth_and_handler_slots() {
7310        let mut stack = make_except_stack();
7311        assert_eq!(stack.depth, 0);
7312        assert_eq!(stack.handlers.len(), CO_MAXBLOCKS + 2);
7313        assert_eq!(stack.handlers[0], BlockIdx::NULL);
7314
7315        let mut blocks = Blocks::from([Block::default(), Block::default()]);
7316        assert!(except_stack_top(&stack, &blocks).is_none());
7317
7318        let setup = InstructionInfo {
7319            instr: PseudoOpcode::SetupWith.into(),
7320            arg: OpArg::new(0),
7321            target: BlockIdx::new(1),
7322            location: SourceLocation::default(),
7323            end_location: SourceLocation::default(),
7324            except_handler: None,
7325            lineno_override: None,
7326        };
7327        let handler = push_except_block(&mut stack, setup, &mut blocks).unwrap();
7328        assert_eq!(stack.depth, 1);
7329        assert_eq!(stack.handlers[1], BlockIdx::new(1));
7330        assert_eq!(handler.handler_block, BlockIdx::new(1));
7331        assert!(handler.preserve_lasti);
7332        assert!(blocks[1].preserve_lasti);
7333
7334        let copy = copy_except_stack(&stack);
7335        assert_eq!(copy.depth, stack.depth);
7336        assert_eq!(copy.handlers, stack.handlers);
7337
7338        assert!(pop_except_block(&mut stack, &blocks).is_none());
7339        assert_eq!(stack.depth, 0);
7340    }
7341
7342    #[test]
7343    fn ref_stack_tracks_cpython_size_and_allocated_refs() {
7344        let mut stack = RefStack {
7345            refs: Vec::new(),
7346            size: 0,
7347            capacity: 0,
7348        };
7349        ref_stack_push(&mut stack, Ref { instr: 7, local: 3 }).unwrap();
7350        assert_eq!(stack.size, 1);
7351        assert_eq!(stack.capacity, 32);
7352        assert_eq!(stack.refs.len(), 32);
7353        assert_eq!(ref_stack_at(&stack, 0).instr, 7);
7354        assert_eq!(ref_stack_at(&stack, 0).local, 3);
7355
7356        ref_stack_clear(&mut stack);
7357        assert_eq!(stack.size, 0);
7358        assert_eq!(stack.capacity, 32);
7359        assert_eq!(stack.refs.len(), 32);
7360
7361        ref_stack_push(
7362            &mut stack,
7363            Ref {
7364                instr: DUMMY_INSTR,
7365                local: NOT_LOCAL,
7366            },
7367        )
7368        .unwrap();
7369        assert_eq!(stack.size, 1);
7370        assert_eq!(ref_stack_pop(&mut stack).instr, DUMMY_INSTR);
7371        assert_eq!(stack.size, 0);
7372    }
7373
7374    #[test]
7375    fn cfg_traversal_stack_resets_visited_and_allocates_for_blocks() {
7376        let mut blocks = Blocks::from([Block::default(), Block::default()]);
7377        blocks[0].next = BlockIdx::new(1);
7378        blocks[0].visited = true;
7379        blocks[1].visited = true;
7380
7381        let mut stack = blocks.make_cfg_traversal_stack().unwrap();
7382        assert!(!blocks[0].visited);
7383        assert!(!blocks[1].visited);
7384        assert!(stack.capacity() >= 2);
7385        assert_eq!(stack.pop(), None);
7386
7387        stack.push(BlockIdx::new(1));
7388        stack.push(BlockIdx::new(0));
7389        assert_eq!(stack.pop(), Some(BlockIdx::new(0)));
7390        assert_eq!(stack.pop(), Some(BlockIdx::new(1)));
7391        assert_eq!(stack.pop(), None);
7392    }
7393
7394    #[test]
7395    fn instruction_sequence_insert_preserves_cpython_slot_metadata() {
7396        let handler = InstructionSequenceExceptHandlerInfo {
7397            h_label: 7,
7398            start_depth: 3,
7399            preserve_lasti: 1,
7400        };
7401        let mut seq = instruction_sequence_new();
7402        let entry = instruction_sequence_addop(&mut seq, test_instr(Instruction::Nop, 11)).unwrap();
7403        entry.except_handler = handler;
7404        entry.i_target = 1;
7405        entry.i_offset = 42;
7406
7407        instruction_sequence_insert_instruction(&mut seq, 0, test_instr(Instruction::PopTop, 12))
7408            .unwrap();
7409
7410        // CPython `_PyInstructionSequence_InsertInstruction()` shifts the
7411        // backing instruction slots, then overwrites only opcode/oparg/loc.
7412        let inserted = &seq.instrs[0];
7413        assert!(matches!(
7414            inserted.info.instr.real(),
7415            Some(Instruction::PopTop)
7416        ));
7417        assert_eq!(inserted.except_handler.h_label, handler.h_label);
7418        assert_eq!(inserted.except_handler.start_depth, handler.start_depth);
7419        assert_eq!(
7420            inserted.except_handler.preserve_lasti,
7421            handler.preserve_lasti
7422        );
7423        assert_eq!(inserted.i_target, 1);
7424        assert_eq!(inserted.i_offset, 42);
7425    }
7426
7427    #[test]
7428    fn instruction_sequence_tracks_cpython_c_array_allocation() {
7429        let mut seq = instruction_sequence_new();
7430        for i in 0..99 {
7431            instruction_sequence_addop(&mut seq, test_instr(Instruction::Nop, 10 + i)).unwrap();
7432        }
7433        assert_eq!(seq.instr_allocation, INITIAL_INSTR_SEQUENCE_SIZE);
7434        assert_eq!(seq.instrs.len(), seq.instr_allocation);
7435        assert_eq!(seq.instr_used, 99);
7436
7437        // CPython calls `_Py_CArray_EnsureCapacity(s_used + 1)`, so the 100th
7438        // instruction expands a 100-slot array to 200 before returning offset 99.
7439        instruction_sequence_addop(&mut seq, test_instr(Instruction::Nop, 109)).unwrap();
7440        assert_eq!(seq.instr_allocation, INITIAL_INSTR_SEQUENCE_SIZE * 2);
7441        assert_eq!(seq.instrs.len(), seq.instr_allocation);
7442        assert_eq!(seq.instr_used, 100);
7443    }
7444
7445    #[test]
7446    fn instruction_sequence_label_map_tracks_cpython_c_array_allocation() {
7447        let mut seq = instruction_sequence_new();
7448        instruction_sequence_use_label(&mut seq, InstructionSequenceLabel::from_index(1)).unwrap();
7449        assert_eq!(
7450            seq.label_map_allocation,
7451            INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE
7452        );
7453        assert_eq!(
7454            seq.label_map.as_ref().expect("label map allocated").len(),
7455            INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE
7456        );
7457
7458        // CPython passes the label id itself to `_Py_CArray_EnsureCapacity()`.
7459        // Label 10 therefore expands the initial 10-slot map to 20.
7460        instruction_sequence_use_label(&mut seq, InstructionSequenceLabel::from_index(10)).unwrap();
7461        assert_eq!(
7462            seq.label_map_allocation,
7463            INITIAL_INSTR_SEQUENCE_LABELS_MAP_SIZE * 2
7464        );
7465    }
7466
7467    #[test]
7468    fn basicblock_addop_reuses_cpython_spare_except_handler_slot() {
7469        let handler = ExceptHandlerInfo {
7470            handler_block: BlockIdx::new(7),
7471            preserve_lasti: true,
7472        };
7473        let mut block = Block::default();
7474        let mut stale = test_instr(Instruction::Nop, 11);
7475        stale.except_handler = Some(handler);
7476        test_block_push(&mut block, stale);
7477        block.basicblock_clear();
7478
7479        block
7480            .basicblock_addop(test_instr(Instruction::PopTop, 12))
7481            .expect("basicblock_addop succeeds");
7482
7483        // CPython `basicblock_addop()` writes opcode/oparg/target/location into
7484        // the reused `b_instr[b_iused]` slot, but does not clear `i_except`.
7485        assert_eq!(block.instruction_used, 1);
7486        assert_eq!(block.instructions[0].except_handler, Some(handler));
7487        assert_eq!(block.instructions[0].target, BlockIdx::NULL);
7488    }
7489
7490    #[test]
7491    fn basicblock_next_instr_tracks_cpython_c_array_allocation() {
7492        let mut block = Block::default();
7493        for i in 0..15 {
7494            block
7495                .basicblock_addop(test_instr(Instruction::PopTop, 10 + i))
7496                .expect("basicblock_addop succeeds");
7497        }
7498        assert_eq!(block.instruction_allocation, DEFAULT_BLOCK_SIZE);
7499
7500        // CPython calls `_Py_CArray_EnsureCapacity(b_iused + 1)`, so the 16th
7501        // instruction expands a 16-slot array to 32 before returning offset 15.
7502        block
7503            .basicblock_addop(test_instr(Instruction::PopTop, 25))
7504            .expect("basicblock_addop succeeds");
7505        assert_eq!(block.instruction_allocation, DEFAULT_BLOCK_SIZE * 2);
7506    }
7507
7508    #[test]
7509    fn basicblock_insert_instruction_consumes_spare_without_inheriting_except_handler() {
7510        let handler = ExceptHandlerInfo {
7511            handler_block: BlockIdx::new(9),
7512            preserve_lasti: false,
7513        };
7514        let mut block = Block::default();
7515        test_block_push(&mut block, test_instr(Instruction::Nop, 21));
7516        let mut stale = test_instr(Instruction::Nop, 22);
7517        stale.except_handler = Some(handler);
7518        test_block_push(&mut block, stale);
7519        block.instruction_used = 1;
7520
7521        block
7522            .basicblock_insert_instruction(0, test_instr(Instruction::PopTop, 23))
7523            .expect("basicblock_insert_instruction succeeds");
7524
7525        // CPython `basicblock_insert_instruction()` also obtains a slot with
7526        // `basicblock_next_instr()`, then overwrites the inserted position with
7527        // the provided instruction copy, including its `i_except` value.
7528        assert_eq!(block.instruction_used, 2);
7529        assert_eq!(block.instructions[0].except_handler, None);
7530    }
7531
7532    #[test]
7533    fn basicblock_clear_preserves_cpython_spare_slots() {
7534        let handler = ExceptHandlerInfo {
7535            handler_block: BlockIdx::new(3),
7536            preserve_lasti: true,
7537        };
7538        let mut block = Block::default();
7539        let mut stale = test_instr(Instruction::PopTop, 31);
7540        stale.except_handler = Some(handler);
7541        test_block_push(&mut block, stale);
7542
7543        block.basicblock_clear();
7544        block
7545            .basicblock_addop(test_instr(Instruction::Nop, 32))
7546            .expect("basicblock_addop succeeds");
7547
7548        // CPython `remove_unreachable()` sets `b_iused = 0` without clearing the
7549        // backing `b_instr` slot. A later `basicblock_addop()` reuses that slot
7550        // and does not overwrite `i_except`.
7551        assert_eq!(block.instruction_used, 1);
7552        assert_eq!(block.instructions[0].except_handler, Some(handler));
7553    }
7554
7555    #[test]
7556    fn basicblock_clear_reuses_cpython_spare_slots_in_offset_order() {
7557        let mut block = Block::default();
7558        for i in 0..3 {
7559            let mut stale = test_instr(Instruction::Nop, 35 + i);
7560            stale.except_handler = Some(ExceptHandlerInfo {
7561                handler_block: BlockIdx::new(i + 1),
7562                preserve_lasti: false,
7563            });
7564            test_block_push(&mut block, stale);
7565        }
7566
7567        block.basicblock_clear();
7568        for i in 0..3 {
7569            block
7570                .basicblock_addop(test_instr(Instruction::PopTop, 38 + i))
7571                .expect("basicblock_addop succeeds");
7572        }
7573
7574        let handlers = block
7575            .used_instructions()
7576            .iter()
7577            .map(|instr| {
7578                instr
7579                    .except_handler
7580                    .expect("reused CPython slot")
7581                    .handler_block
7582            })
7583            .collect::<Vec<_>>();
7584        assert_eq!(
7585            handlers,
7586            [BlockIdx::new(1), BlockIdx::new(2), BlockIdx::new(3)]
7587        );
7588    }
7589
7590    #[test]
7591    fn basicblock_append_instructions_overwrites_cpython_spare_slot() {
7592        let handler = ExceptHandlerInfo {
7593            handler_block: BlockIdx::new(5),
7594            preserve_lasti: false,
7595        };
7596        let mut blocks = Blocks::from([Block::default(), Block::default()]);
7597        let mut stale = test_instr(Instruction::Nop, 41);
7598        stale.except_handler = Some(handler);
7599        test_block_push(&mut blocks[0], stale);
7600        blocks[0].basicblock_clear();
7601
7602        test_block_push(&mut blocks[1], test_instr(Instruction::PopTop, 42));
7603        blocks
7604            .basicblock_append_block_instructions(BlockIdx::new(0), BlockIdx::new(1))
7605            .expect("basicblock_append_block_instructions succeeds");
7606
7607        // CPython `basicblock_append_instructions()` obtains a slot with
7608        // `basicblock_next_instr()`, then overwrites it with the copied
7609        // instruction, including `i_except`.
7610        assert_eq!(blocks[0].instruction_used, 1);
7611        assert_eq!(blocks[0].instructions[0].except_handler, None);
7612    }
7613
7614    #[test]
7615    fn instr_set_op0_nop_preserves_cpython_stale_target() {
7616        let mut info = test_jump(BlockIdx::new(1), 50);
7617        info.set_to_nop();
7618
7619        assert_eq!(info.target, BlockIdx::new(1));
7620
7621        let mut blocks = Blocks::from([Block::default(), Block::default()]);
7622        test_block_push(&mut blocks[0], info);
7623        blocks[0].next = BlockIdx::new(1);
7624
7625        let mut instr_sequence = instruction_sequence_new();
7626        blocks
7627            .cfg_to_instruction_sequence(&mut instr_sequence)
7628            .expect("non-target NOP should ignore stale CPython i_target");
7629    }
7630
7631    #[test]
7632    #[cfg(debug_assertions)]
7633    #[should_panic(expected = "target_block != BlockIdx::NULL")]
7634    fn cfg_to_instruction_sequence_requires_target_for_target_opcodes() {
7635        let mut block = Block::default();
7636        test_block_push(&mut block, test_jump(BlockIdx::NULL, 51));
7637        let mut blocks = Blocks::from([block]);
7638
7639        let mut instr_sequence = instruction_sequence_new();
7640        let _ = blocks.cfg_to_instruction_sequence(&mut instr_sequence);
7641    }
7642
7643    #[test]
7644    fn static_swaps_respect_cpython_no_location_line_boundary() {
7645        let mut block = Block::default();
7646        let mut swap = test_instr(Opcode::Swap.into(), 60);
7647        swap.arg = OpArg::new(2);
7648        let mut store = test_instr(Opcode::StoreFast.into(), 60);
7649        store.arg = OpArg::new(0);
7650        let mut pop = test_instr(Instruction::PopTop, 60);
7651        pop.lineno_override = Some(NO_LOCATION_OVERRIDE);
7652        for info in [swap, store, pop] {
7653            test_block_push(&mut block, info);
7654        }
7655
7656        block
7657            .apply_static_swaps_block()
7658            .expect("apply_static_swaps_block succeeds");
7659
7660        // CPython `next_swappable_instruction()` compares `i_loc.lineno`
7661        // directly, so a following NO_LOCATION swaperand does not match the
7662        // first swaperand's positive line number.
7663        assert!(matches!(
7664            block.instructions[0].instr.real(),
7665            Some(Instruction::Swap { .. })
7666        ));
7667        assert!(matches!(
7668            block.instructions[1].instr.real(),
7669            Some(Instruction::StoreFast { .. })
7670        ));
7671        assert!(matches!(
7672            block.instructions[2].instr.real(),
7673            Some(Instruction::PopTop)
7674        ));
7675
7676        let mut block = Block::default();
7677        let mut swap = test_instr(Opcode::Swap.into(), 70);
7678        swap.arg = OpArg::new(2);
7679        let mut store = test_instr(Opcode::StoreFast.into(), 70);
7680        store.arg = OpArg::new(0);
7681        store.lineno_override = Some(NO_LOCATION_OVERRIDE);
7682        let pop = test_instr(Instruction::PopTop, 71);
7683        for info in [swap, store, pop] {
7684            test_block_push(&mut block, info);
7685        }
7686
7687        block
7688            .apply_static_swaps_block()
7689            .expect("apply_static_swaps_block succeeds");
7690
7691        // Conversely, when the first swaperand has NO_LOCATION, CPython passes
7692        // `-1` as the line filter and does not enforce a boundary.
7693        assert!(matches!(
7694            block.instructions[0].instr.real_opcode(),
7695            Some(Opcode::Nop)
7696        ));
7697        assert!(matches!(
7698            block.instructions[1].instr.real_opcode(),
7699            Some(Opcode::PopTop)
7700        ));
7701        assert!(matches!(
7702            block.instructions[2].instr.real_opcode(),
7703            Some(Opcode::StoreFast)
7704        ));
7705    }
7706
7707    #[test]
7708    fn optimize_load_const_tracks_cpython_copy_of_load_const() {
7709        let mut block = Block::default();
7710        test_block_push(&mut block, test_instr(Opcode::LoadConst.into(), 80));
7711        let mut copy = test_instr(Opcode::Copy.into(), 80);
7712        copy.arg = OpArg::new(1);
7713        test_block_push(&mut block, copy);
7714        test_block_push(&mut block, test_instr(Instruction::ToBool, 80));
7715
7716        let mut code = test_code_info(block);
7717        let (const_idx, _) = code.metadata.consts.insert_full(ConstantData::Tuple {
7718            elements: vec![ConstantData::Integer {
7719                value: BigInt::from(1),
7720            }],
7721        });
7722        code.blocks[0].instructions[0].arg = OpArg::new(const_idx as u32);
7723
7724        optimize_load_const(&mut code.metadata, &mut code.blocks)
7725            .expect("optimize_load_const succeeds");
7726
7727        // CPython `basicblock_optimize_load_const()` keeps the previous
7728        // LOAD_CONST as the effective opcode for a following `COPY 1`, so the
7729        // COPY is NOPed and TO_BOOL becomes LOAD_CONST True.
7730        assert!(matches!(
7731            code.blocks[0].instructions[0].instr.real(),
7732            Some(Instruction::LoadConst { .. })
7733        ));
7734        assert!(matches!(
7735            code.blocks[0].instructions[1].instr.real(),
7736            Some(Instruction::Nop)
7737        ));
7738        let load_bool = &code.blocks[0].instructions[2];
7739        assert!(matches!(
7740            load_bool.instr.real(),
7741            Some(Instruction::LoadConst { .. })
7742        ));
7743        assert_eq!(
7744            code.metadata.consts[u32::from(load_bool.arg) as usize],
7745            ConstantData::Boolean { value: true }
7746        );
7747    }
7748
7749    #[test]
7750    fn optimize_load_const_pseudo_opcode_breaks_effective_load_const() {
7751        let mut block = Block::default();
7752        test_block_push(
7753            &mut block,
7754            test_instr(
7755                Instruction::LoadConst {
7756                    consti: Arg::marker(),
7757                },
7758                90,
7759            ),
7760        );
7761        test_block_push(&mut block, test_true_cond_jump(BlockIdx::new(0), 90));
7762        let mut copy = test_instr(Instruction::Copy { i: Arg::marker() }, 90);
7763        copy.arg = OpArg::new(1);
7764        test_block_push(&mut block, copy);
7765        test_block_push(&mut block, test_instr(Instruction::ToBool, 90));
7766
7767        let mut code = test_code_info(block);
7768        let (const_idx, _) = code.metadata.consts.insert_full(ConstantData::Tuple {
7769            elements: vec![ConstantData::Integer {
7770                value: BigInt::from(1),
7771            }],
7772        });
7773        code.blocks[0].instructions[0].arg = OpArg::new(const_idx as u32);
7774
7775        optimize_load_const(&mut code.metadata, &mut code.blocks)
7776            .expect("optimize_load_const succeeds");
7777
7778        // `basicblock_optimize_load_const()` assigns the current
7779        // pseudo opcode to its effective opcode slot, so the following COPY 1
7780        // is not treated as a copy of the earlier LOAD_CONST.
7781        assert!(matches!(
7782            code.blocks[0].instructions[1].instr.pseudo(),
7783            Some(PseudoInstruction::Jump { .. })
7784        ));
7785        assert!(matches!(
7786            code.blocks[0].instructions[2].instr.real(),
7787            Some(Instruction::Copy { .. })
7788        ));
7789        assert!(matches!(
7790            code.blocks[0].instructions[3].instr.real(),
7791            Some(Instruction::ToBool)
7792        ));
7793    }
7794
7795    #[test]
7796    fn optimize_load_fast_records_no_input_opcode_ref_at_cpython_produced_index() {
7797        let mut block = Block::default();
7798        test_block_push(&mut block, test_instr(Opcode::LoadFast.into(), 10));
7799        test_block_push(&mut block, test_instr(Instruction::GetLen, 10));
7800        let mut swap = test_instr(Opcode::Swap.into(), 10);
7801        swap.arg = OpArg::new(2);
7802        test_block_push(&mut block, swap);
7803        test_block_push(&mut block, test_instr(Instruction::PopTop, 10));
7804
7805        let mut code = test_code_info(block);
7806        code.blocks
7807            .optimize_load_fast()
7808            .expect("optimize_load_fast succeeds");
7809
7810        // CPython `optimize_load_fast()` shadows the outer instruction index in
7811        // the produced-value loop for GET_LEN, so the produced ref is recorded
7812        // with index 0 here. The original LOAD_FAST is therefore not considered
7813        // the consumed producer.
7814        assert!(matches!(
7815            code.blocks[0].instructions[0].instr.real(),
7816            Some(Instruction::LoadFast { .. })
7817        ));
7818    }
7819
7820    #[test]
7821    fn constant_sequence_loads_use_cpython_opcode_has_const_metadata() {
7822        let mut metadata = CodeUnitMetadata {
7823            name: "<module>".to_owned(),
7824            qualname: Some("<module>".to_owned()),
7825            consts: Default::default(),
7826            names: IndexSet::default(),
7827            varnames: IndexSet::default(),
7828            cellvars: IndexSet::default(),
7829            freevars: IndexSet::default(),
7830            fast_hidden: IndexMap::default(),
7831            fast_hidden_final: IndexSet::default(),
7832            argcount: 0,
7833            posonlyargcount: 0,
7834            kwonlyargcount: 0,
7835            firstlineno: OneIndexed::MIN,
7836        };
7837        let (left, _) = metadata
7838            .consts
7839            .insert_full(ConstantData::Str { value: "a".into() });
7840        let (right, _) = metadata
7841            .consts
7842            .insert_full(ConstantData::Str { value: "b".into() });
7843
7844        let mut immortal = test_instr(Instruction::Nop, 90);
7845        immortal.instr = Opcode::LoadConstImmortal.into();
7846        immortal.arg = OpArg::new(left as u32);
7847        let mut mortal = test_instr(Instruction::Nop, 90);
7848        mortal.instr = Opcode::LoadConstMortal.into();
7849        mortal.arg = OpArg::new(right as u32);
7850        let mut build = test_instr(Opcode::BuildTuple.into(), 90);
7851        build.arg = OpArg::new(2);
7852        let mut block = Block::default();
7853        for info in [immortal, mortal, build] {
7854            test_block_push(&mut block, info);
7855        }
7856
7857        assert!(
7858            fold_tuple_of_constants(&mut metadata, &mut block, 2)
7859                .expect("fold_tuple_of_constants succeeds")
7860        );
7861
7862        // CPython `loads_const()` accepts every `OPCODE_HAS_CONST` opcode, not
7863        // just canonical LOAD_CONST, so LOAD_CONST_IMMORTAL/MORTAL participate
7864        // in constant-sequence folding.
7865        assert!(matches!(
7866            block.instructions[0].instr.real(),
7867            Some(Instruction::Nop)
7868        ));
7869        assert!(matches!(
7870            block.instructions[1].instr.real(),
7871            Some(Instruction::Nop)
7872        ));
7873        let folded = &block.instructions[2];
7874        assert!(matches!(
7875            folded.instr.real(),
7876            Some(Instruction::LoadConst { .. })
7877        ));
7878        assert!(matches!(
7879            &metadata.consts[u32::from(folded.arg) as usize],
7880            ConstantData::Tuple { elements } if elements.len() == 2
7881        ));
7882    }
7883
7884    #[test]
7885    fn empty_tuple_repeat_folds_negative_count_like_cpython() {
7886        let folded = const_folding_safe_multiply(
7887            &ConstantData::Tuple {
7888                elements: Vec::new(),
7889            },
7890            &ConstantData::Integer {
7891                value: BigInt::from(-1),
7892            },
7893        )
7894        .expect("CPython skips repeat-count checks for empty tuples");
7895
7896        assert!(matches!(
7897            folded,
7898            ConstantData::Tuple { elements } if elements.is_empty()
7899        ));
7900    }
7901
7902    #[test]
7903    fn resolve_line_numbers_duplicates_exit_blocks_like_cpython() {
7904        let exit = BlockIdx::new(2);
7905        let mut blocks = Blocks::from([Block::default(), Block::default(), Block::default()]);
7906        blocks[0].cpython_label = InstructionSequenceLabel::from_index(0);
7907        blocks[1].cpython_label = InstructionSequenceLabel::from_index(1);
7908        blocks[2].cpython_label = InstructionSequenceLabel::from_index(2);
7909        blocks[0].next = BlockIdx::new(1);
7910        test_block_push(&mut blocks[0], test_cond_jump(exit, 10));
7911        blocks[1].next = exit;
7912        test_block_push(&mut blocks[1], test_jump(exit, 20));
7913        test_block_push(&mut blocks[2], test_instr(Instruction::ReturnValue, 30));
7914        blocks[2].instructions[0].lineno_override = Some(NO_LOCATION_OVERRIDE);
7915
7916        blocks
7917            .remove_unreachable()
7918            .expect("remove_unreachable succeeds");
7919        blocks
7920            .resolve_line_numbers(OneIndexed::MIN)
7921            .expect("resolve_line_numbers succeeds");
7922
7923        // CPython `duplicate_exits_without_lineno()` copies a shared exit block
7924        // reached by jumps so each copy can inherit its sole predecessor's line.
7925        let duplicate = blocks[0].instructions[0].target;
7926        assert_ne!(duplicate, exit);
7927        assert_eq!(
7928            blocks[duplicate].cpython_label,
7929            InstructionSequenceLabel::from_index(3)
7930        );
7931        assert_eq!(blocks[duplicate].instructions[0].instruction_lineno(), 10);
7932        assert_eq!(blocks[1].instructions[0].target, exit);
7933        assert_eq!(blocks[exit].instructions[0].instruction_lineno(), 20);
7934    }
7935
7936    #[test]
7937    fn propagate_line_numbers_treats_next_location_like_cpython() {
7938        let mut block = Block::default();
7939        test_block_push(&mut block, test_instr(Instruction::Nop, 10));
7940        test_block_push(&mut block, test_instr(Instruction::PopTop, 20));
7941        block.instructions[1].lineno_override = Some(NEXT_LOCATION_OVERRIDE);
7942        test_block_push(&mut block, test_instr(Instruction::ReturnValue, 30));
7943        block.instructions[2].lineno_override = Some(NO_LOCATION_OVERRIDE);
7944        let mut blocks = Blocks::from([block]);
7945
7946        blocks
7947            .remove_unreachable()
7948            .expect("remove_unreachable succeeds");
7949        blocks.propagate_line_numbers();
7950
7951        // CPython `propagate_line_numbers()` only copies over NO_LOCATION
7952        // (`lineno == NO_LOCATION`). `NEXT_LOCATION` (`lineno == -2`) becomes the
7953        // current previous location and is copied to following NO_LOCATION
7954        // instructions for assemble.c to resolve later.
7955        assert_eq!(
7956            blocks[0].instructions[1].lineno_override,
7957            Some(NEXT_LOCATION_OVERRIDE)
7958        );
7959        assert_eq!(
7960            blocks[0].instructions[2].lineno_override,
7961            Some(NEXT_LOCATION_OVERRIDE)
7962        );
7963    }
7964
7965    #[test]
7966    fn propagate_line_numbers_updates_empty_jump_target_raw_slot_like_cpython() {
7967        let mut blocks = Blocks::from([Block::default(), Block::default(), Block::default()]);
7968        blocks[0].next = BlockIdx::new(2);
7969        test_block_push(&mut blocks[0], test_cond_jump(BlockIdx::new(1), 10));
7970        test_block_push(&mut blocks[1], test_instr(Instruction::Nop, 20));
7971        blocks[1].instructions[0].lineno_override = Some(NO_LOCATION_OVERRIDE);
7972        blocks[1].basicblock_clear();
7973        test_block_push(&mut blocks[2], test_instr(Instruction::ReturnValue, 30));
7974
7975        blocks
7976            .remove_unreachable()
7977            .expect("remove_unreachable succeeds");
7978        blocks.propagate_line_numbers();
7979
7980        // CPython `propagate_line_numbers()` directly reads `target->b_instr[0]`
7981        // for jump targets without checking `b_iused`. If
7982        // `remove_redundant_nops()` emptied the target, that writes the stale
7983        // backing slot rather than an active instruction.
7984        assert_eq!(blocks[1].instructions[0].instruction_lineno(), 10);
7985    }
7986
7987    #[test]
7988    fn basicblock_has_no_lineno_treats_next_location_like_cpython() {
7989        let mut block = Block::default();
7990        test_block_push(&mut block, test_instr(Instruction::Nop, 10));
7991        block.instructions[0].lineno_override = Some(NEXT_LOCATION_OVERRIDE);
7992
7993        // CPython `basicblock_has_no_lineno()` treats every negative lineno as
7994        // no line number, including `NEXT_LOCATION` (`lineno == -2`).
7995        assert!(block.basicblock_has_no_lineno());
7996
7997        test_block_push(&mut block, test_instr(Instruction::PopTop, 11));
7998        assert!(!block.basicblock_has_no_lineno());
7999    }
8000
8001    #[test]
8002    fn jump_threading_rechecks_new_jump_like_cpython() {
8003        let mut blocks = Blocks::from([
8004            Block::default(),
8005            Block::default(),
8006            Block::default(),
8007            Block::default(),
8008        ]);
8009        for (i, block) in blocks.iter_mut().enumerate() {
8010            block.cpython_label = InstructionSequenceLabel::from_index(i as i32);
8011        }
8012        blocks[0].next = BlockIdx::new(1);
8013        blocks[1].next = BlockIdx::new(2);
8014        blocks[2].next = BlockIdx::new(3);
8015        test_block_push(&mut blocks[0], test_jump(BlockIdx::new(1), 10));
8016        test_block_push(&mut blocks[1], test_jump(BlockIdx::new(2), 20));
8017        test_block_push(&mut blocks[2], test_jump(BlockIdx::new(3), 30));
8018        test_block_push(&mut blocks[3], test_instr(Instruction::ReturnValue, 40));
8019
8020        let mut metadata = test_code_info(Block::default()).metadata;
8021        blocks
8022            .optimize_basic_block(&mut metadata, BlockIdx::new(0))
8023            .expect("valid jump chain");
8024
8025        // CPython `optimize_basic_block()` continues after `jump_thread()`, so
8026        // the appended jump is immediately checked against the next jump target.
8027        let threaded = blocks[0].basicblock_last_instr().expect("threaded jump");
8028        assert!(matches!(
8029            threaded.instr.pseudo(),
8030            Some(PseudoInstruction::Jump { .. })
8031        ));
8032        assert_eq!(threaded.target, BlockIdx::new(3));
8033        assert_eq!(u32::from(threaded.arg), 3);
8034    }
8035
8036    #[test]
8037    fn same_direction_pseudo_conditional_jump_thread_false_keeps_target() {
8038        let mut blocks = Blocks::from([Block::default(), Block::default(), Block::default()]);
8039        for (i, block) in blocks.iter_mut().enumerate() {
8040            block.cpython_label = InstructionSequenceLabel::from_index(i as i32);
8041        }
8042        blocks[0].next = BlockIdx::new(1);
8043        blocks[1].next = BlockIdx::new(2);
8044        test_block_push(&mut blocks[0], test_cond_jump(BlockIdx::new(1), 10));
8045        test_block_push(&mut blocks[1], test_cond_jump(BlockIdx::new(1), 20));
8046        test_block_push(&mut blocks[2], test_instr(Instruction::ReturnValue, 30));
8047
8048        let mut metadata = test_code_info(Block::default()).metadata;
8049        blocks
8050            .optimize_basic_block(&mut metadata, BlockIdx::new(0))
8051            .expect("valid conditional jump chain");
8052
8053        // Only rewrite JUMP_IF_FALSE -> JUMP_IF_TRUE through
8054        // target->b_next. For same-direction jumps, a failed jump_thread()
8055        // leaves the original target unchanged.
8056        assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(1));
8057        assert!(matches!(
8058            blocks[0].instructions[0].instr.pseudo(),
8059            Some(PseudoInstruction::JumpIfFalse { .. })
8060        ));
8061    }
8062
8063    #[test]
8064    fn opposite_direction_pseudo_conditional_uses_target_fallthrough() {
8065        let mut blocks = Blocks::from([Block::default(), Block::default(), Block::default()]);
8066        for (i, block) in blocks.iter_mut().enumerate() {
8067            block.cpython_label = InstructionSequenceLabel::from_index(i as i32);
8068        }
8069        blocks[0].next = BlockIdx::new(1);
8070        blocks[1].next = BlockIdx::new(2);
8071        test_block_push(&mut blocks[0], test_cond_jump(BlockIdx::new(1), 10));
8072        test_block_push(&mut blocks[1], test_true_cond_jump(BlockIdx::new(2), 20));
8073        test_block_push(&mut blocks[2], test_instr(Instruction::ReturnValue, 30));
8074
8075        let mut metadata = test_code_info(Block::default()).metadata;
8076        blocks
8077            .optimize_basic_block(&mut metadata, BlockIdx::new(0))
8078            .expect("valid conditional jump chain");
8079
8080        assert_eq!(blocks[0].instructions[0].target, BlockIdx::new(2));
8081    }
8082}