| 1071 | |
| 1072 | #[pymethod] |
| 1073 | pub fn co_branches(&self, vm: &VirtualMachine) -> PyResult<PyObjectRef> { |
| 1074 | let instructions = &self.code.instructions; |
| 1075 | let mut branches = Vec::new(); |
| 1076 | let mut extended_arg: u32 = 0; |
| 1077 | |
| 1078 | for (i, unit) in instructions.iter().enumerate() { |
| 1079 | // De-instrument: use base opcode for instrumented variants |
| 1080 | let op = unit.op.to_base().unwrap_or(unit.op); |
| 1081 | let raw_arg = u32::from(u8::from(unit.arg)); |
| 1082 | |
| 1083 | if matches!(op, Instruction::ExtendedArg) { |
| 1084 | extended_arg = (extended_arg | raw_arg) << 8; |
| 1085 | continue; |
| 1086 | } |
| 1087 | |
| 1088 | let oparg = extended_arg | raw_arg; |
| 1089 | extended_arg = 0; |
| 1090 | |
| 1091 | let caches = op.cache_entries(); |
| 1092 | let (src, left, right) = match op { |
| 1093 | Instruction::ForIter { .. } => { |
| 1094 | // left = fall-through past CACHE entries (continue iteration) |
| 1095 | // right = past END_FOR (iterator exhausted, skip cleanup) |
| 1096 | // arg is relative forward from after instruction+caches |
| 1097 | let after_cache = i + 1 + caches; |
| 1098 | let target = after_cache + oparg as usize; |
| 1099 | let right = if matches!( |
| 1100 | instructions.get(target).map(|u| u.op), |
| 1101 | Some(Instruction::EndFor) | Some(Instruction::InstrumentedEndFor) |
| 1102 | ) { |
| 1103 | (target + 1) * 2 |
| 1104 | } else { |
| 1105 | target * 2 |
| 1106 | }; |
| 1107 | (i * 2, after_cache * 2, right) |
| 1108 | } |
| 1109 | Instruction::PopJumpIfFalse { .. } |
| 1110 | | Instruction::PopJumpIfTrue { .. } |
| 1111 | | Instruction::PopJumpIfNone { .. } |
| 1112 | | Instruction::PopJumpIfNotNone { .. } => { |
| 1113 | // left = fall-through past CACHE entries (skip NOT_TAKEN if present) |
| 1114 | // right = jump target (relative forward from after instruction+caches) |
| 1115 | let after_cache = i + 1 + caches; |
| 1116 | let next_op = instructions |
| 1117 | .get(after_cache) |
| 1118 | .map(|u| u.op.to_base().unwrap_or(u.op)); |
| 1119 | let fallthrough = if matches!(next_op, Some(Instruction::NotTaken)) { |
| 1120 | (after_cache + 1) * 2 |
| 1121 | } else { |
| 1122 | after_cache * 2 |
| 1123 | }; |
| 1124 | let right_target = after_cache + oparg as usize; |
| 1125 | (i * 2, fallthrough, right_target * 2) |
| 1126 | } |
| 1127 | Instruction::EndAsyncFor => { |
| 1128 | // src = END_SEND position (next_i - oparg) |
| 1129 | let next_i = i + 1; |
| 1130 | let Some(src_i) = next_i.checked_sub(oparg as usize) else { |