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#[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
55fn 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 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#[derive(Clone, Debug)]
261pub struct CodeUnitMetadata {
262 pub name: String, pub qualname: Option<String>, pub consts: ConstantPool, pub names: IndexSet<String>, pub varnames: IndexSet<String>, pub cellvars: IndexSet<String>, pub freevars: IndexSet<String>, pub fast_hidden: IndexMap<String, bool>, pub fast_hidden_final: IndexSet<String>, pub argcount: u32, pub posonlyargcount: u32, pub kwonlyargcount: u32, pub firstlineno: OneIndexed, }
276#[derive(Copy, Clone, PartialEq, Eq, Debug)]
279pub struct BlockIdx(u32);
280
281impl BlockIdx {
282 pub const NULL: Self = Self::new(u32::MAX);
283
284 #[must_use]
286 pub const fn new(value: u32) -> Self {
287 Self(value)
288 }
289
290 #[must_use]
292 pub const fn as_u32(self) -> u32 {
293 self.0
294 }
295
296 #[must_use]
298 pub const fn as_usize(self) -> usize {
299 self.0 as usize
300 }
301
302 #[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 pub lineno_override: Option<i32>,
331}
332
333impl InstructionInfo {
334 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 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 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 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 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 const fn loads_const(&self) -> bool {
458 self.instr.has_const() || matches!(self.instr.real_opcode(), Some(Opcode::LoadSmallInt))
459 }
460
461 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 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 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 fn is_jump(&self) -> bool {
510 self.instr.has_jump()
511 }
512
513 fn is_block_push(&self) -> bool {
515 self.instr.is_block_push()
516 }
517}
518
519#[derive(Clone, Copy, Debug, PartialEq, Eq)]
521pub struct ExceptHandlerInfo {
522 pub handler_block: BlockIdx,
524 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
536fn 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
578fn same_label(a: InstructionSequenceLabel, b: InstructionSequenceLabel) -> bool {
580 a == b
581}
582
583fn 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 instrs: Vec<InstructionSequenceEntry>,
647 instr_allocation: usize,
649 instr_used: usize,
651 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#[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 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
768fn 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
842fn 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
881fn instruction_sequence_new_label(seq: &mut InstructionSequence) -> InstructionSequenceLabel {
883 seq.next_free_label += 1;
884 InstructionSequenceLabel(seq.next_free_label)
885}
886
887fn 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
896fn 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
936fn 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
958fn 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
984fn 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
1013const 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}
1044fn 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
1084fn resolve_jump_offsets(instr_sequence: &mut InstructionSequence) {
1086 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 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
1161fn 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
1166fn 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
1204fn 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
1221fn 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
1269fn 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
1298fn 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 allocation_next: BlockIdx,
1398 cpython_label: InstructionSequenceLabel,
1400 instruction_allocation: usize,
1402 except_stack: Option<CfgExceptStack>,
1404 pub instructions: Vec<InstructionInfo>,
1406 pub next: BlockIdx,
1407 instruction_used: usize,
1409 unsafe_locals_mask: u64,
1411 predecessors: i32,
1413 pub start_depth: i32,
1415 pub preserve_lasti: bool,
1417 visited: bool,
1419 pub except_handler: bool,
1421 pub cold: bool,
1423 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 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 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 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 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 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 fn basicblock_clear(&mut self) {
1543 self.instruction_used = 0;
1544 }
1545
1546 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 fn bb_no_fallthrough(&self) -> bool {
1556 self.basicblock_nofallthrough()
1557 }
1558
1559 fn bb_has_fallthrough(&self) -> bool {
1561 !self.bb_no_fallthrough()
1562 }
1563
1564 #[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 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 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 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 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 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 fn nop_out(&mut self, instrs: &[usize]) {
1622 for &i in instrs {
1623 self.instructions[i].nop_out_no_location();
1624 }
1625 }
1626
1627 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 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 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 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 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
1846impl 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
1860impl Blocks {
1863 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 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 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 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 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 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 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 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 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 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 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 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 index_map[0] = 0;
2769
2770 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 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 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 consts.constants.truncate(n_used_consts);
2812
2813 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[cfg(debug_assertions)]
3473 fn no_redundant_nops(&mut self) -> bool {
3474 self.remove_redundant_nops() == 0
3475 }
3476
3477 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 #[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 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#[derive(Clone, Debug)]
3613struct CfgExceptStack {
3614 handlers: [BlockIdx; CO_MAXBLOCKS + 2],
3615 depth: usize,
3616}
3617
3618#[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 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>, 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 pub static_attributes: Option<IndexSet<String>>,
3845
3846 pub in_inlined_comp: bool,
3848
3849 pub fblock: Vec<crate::compile::FBlockInfo>,
3851
3852 pub symbol_table_index: usize,
3854 pub nparams: usize,
3857
3858 pub in_conditional_block: u32,
3861
3862 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 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 *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 blocks.insert_superinstructions();
4063 blocks.push_cold_blocks_to_end()?;
4064 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 blocks.check_cfg()?;
4079 blocks.inline_small_or_no_lineno_blocks()?;
4080 blocks.remove_unreachable()?;
4084 blocks.resolve_line_numbers(firstlineno)?;
4088 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 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 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 convert_pseudo_ops(blocks)?;
4121 blocks.normalize_jumps()?;
4122 #[cfg(debug_assertions)]
4123 assert!(blocks.no_redundant_jumps());
4124 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
4132pub 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
4163pub 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: _, 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 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
4328fn is_generator(flags: CodeFlags) -> bool {
4330 flags.intersects(CodeFlags::GENERATOR | CodeFlags::COROUTINE | CodeFlags::ASYNC_GENERATOR)
4331}
4332
4333fn 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
4427fn 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 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
4453fn 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
4524fn 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
4546fn 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
4587fn 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
4627fn 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
4644fn 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
4675fn 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
4742fn 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
4783fn 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
4799fn 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
4853fn 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
4858fn 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 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
4919fn 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
4934fn 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
4942fn 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
5000fn 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
5015fn 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
5093fn 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
5102fn 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 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 (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 (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
5316fn 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
5434fn 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
5525const VISITED: i32 = -1;
5527
5528fn 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
5537fn 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 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
5687fn 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#[derive(Clone, Copy, Eq, PartialEq)]
5899#[repr(u8)]
5900enum LoadFastInstrFlag {
5901 SupportKilled = 1,
5902 StoredAsLocal = 2,
5903 RefUnconsumed = 4,
5904}
5905
5906#[derive(Clone, Copy)]
5908struct Ref {
5909 instr: isize,
5910 local: isize,
5911}
5912
5913struct RefStack {
5915 refs: Vec<Ref>,
5916 size: usize,
5917 capacity: usize,
5918}
5919
5920fn 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
5938fn ref_stack_pop(stack: &mut RefStack) -> Ref {
5940 assert!(stack.size > 0);
5941 stack.size -= 1;
5942 stack.refs[stack.size]
5943}
5944
5945fn 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
5953fn ref_stack_at(stack: &RefStack, idx: usize) -> Ref {
5955 assert!(idx < stack.size);
5956 stack.refs[idx]
5957}
5958
5959fn ref_stack_clear(stack: &mut RefStack) {
5961 stack.size = 0;
5962}
5963
5964fn push_ref(stack: &mut RefStack, instr: isize, local: isize) -> crate::InternalResult<()> {
5966 ref_stack_push(stack, Ref { instr, local })
5967}
5968
5969fn 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
5981fn 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
5993fn 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#[derive(Clone, Copy, Eq, PartialEq)]
6029struct StackEffects {
6030 net: i32,
6031}
6032
6033fn 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
6077fn write_location_first_byte(linetable: &mut Vec<u8>, code: u8, length: usize) {
6079 linetable.extend(write_location_entry_start(code, length));
6080}
6081
6082fn 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
6089fn write_location_byte(linetable: &mut Vec<u8>, value: u8) {
6091 linetable.push(value);
6092}
6093
6094fn write_location_varint(linetable: &mut Vec<u8>, value: u32) {
6096 write_varint(linetable, value);
6097}
6098
6099fn write_location_signed_varint(linetable: &mut Vec<u8>, value: i32) {
6101 write_signed_varint(linetable, value);
6102}
6103
6104fn 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
6125fn 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
6143fn 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
6169fn write_location_info_none(linetable: &mut Vec<u8>, length: usize) {
6171 write_location_first_byte(linetable, PyCodeLocationInfoKind::None as u8, length);
6172}
6173
6174fn 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
6180fn 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
6231fn 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
6267fn 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
6292fn 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
6325fn 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
6366fn 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
6379struct CfgBuilder {
6381 blocks: Blocks,
6382 entry: BlockIdx,
6383 block_list: BlockIdx,
6384 current: BlockIdx,
6385 current_label: InstructionSequenceLabel,
6386}
6387
6388fn 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
6397fn 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
6405fn 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
6415fn 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
6428fn 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
6445fn 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
6456fn 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
6465fn 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
6471fn 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
6496fn 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
6515fn 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
6557fn 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
6640fn 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
6659fn 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 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
6729fn 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
6776fn 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
6811fn 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
6819const LOCAL_UNSAFE_MASK_BITS: usize = 64;
6821
6822const MAX_COPY_SIZE: usize = 4;
6824
6825fn 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
6837fn 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
6844fn 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
6853fn 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
6866fn 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
6889fn 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 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
7006pub(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 blocks.remove_redundant_nops_and_jumps()
7042}
7043
7044pub(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
7073pub(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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}