1use itertools::Itertools as _;
55use rustc_const_eval::const_eval::DummyMachine;
56use rustc_const_eval::interpret::{ImmTy, Immediate, InterpCx, OpTy, Projectable};
57use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
58use rustc_index::IndexVec;
59use rustc_index::bit_set::{DenseBitSet, GrowableBitSet};
60use rustc_middle::bug;
61use rustc_middle::mir::interpret::Scalar;
62use rustc_middle::mir::visit::Visitor;
63use rustc_middle::mir::*;
64use rustc_middle::ty::{self, ScalarInt, TyCtxt};
65use rustc_mir_dataflow::value_analysis::{
66 Map, PlaceCollectionMode, PlaceIndex, TrackElem, ValueIndex,
67};
68use rustc_span::DUMMY_SP;
69use tracing::{debug, instrument, trace};
70
71use crate::PassPolicy;
72use crate::cost_checker::CostChecker;
73
74pub(super) struct JumpThreading;
75
76const MAX_COST: u8 = 100;
77
78impl<'tcx> crate::MirPass<'tcx> for JumpThreading {
79 fn policy(&self, ctx: &crate::PassCtx<'_>) -> PassPolicy {
80 PassPolicy::optional(ctx.mir_opt_level() >= 2 && !ctx.target.is_like_gpu)
86 }
87
88 #[instrument(skip_all level = "debug")]
89 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
90 let def_id = body.source.def_id();
91 debug!(?def_id);
92
93 if tcx.is_coroutine(def_id) {
95 trace!("Skipped for coroutine {:?}", def_id);
96 return;
97 }
98
99 let typing_env = body.typing_env(tcx);
100 let mut finder = TOFinder {
101 tcx,
102 typing_env,
103 ecx: InterpCx::new(tcx, DUMMY_SP, typing_env, DummyMachine),
104 body,
105 map: Map::new(tcx, body, PlaceCollectionMode::OnDemand),
106 maybe_loop_headers: maybe_loop_headers(body),
107 entry_states: IndexVec::from_elem(ConditionSet::default(), &body.basic_blocks),
108 };
109
110 for (bb, bbdata) in traversal::postorder(body) {
111 if bbdata.is_cleanup {
112 continue;
113 }
114
115 let mut state = finder.populate_from_outgoing_edges(bb);
116 trace!("output_states[{bb:?}] = {state:?}");
117
118 finder.process_terminator(bb, &mut state);
119 trace!("pre_terminator_states[{bb:?}] = {state:?}");
120
121 for stmt in bbdata.statements.iter().rev() {
122 if state.is_empty() {
123 break;
124 }
125
126 finder.process_statement(stmt, &mut state);
127
128 if let Some((lhs, tail)) = finder.mutated_statement(stmt) {
133 finder.flood_state(lhs, tail, &mut state);
134 }
135 }
136
137 trace!("entry_states[{bb:?}] = {state:?}");
138 finder.entry_states[bb] = state;
139 }
140
141 let mut entry_states = finder.entry_states;
142 simplify_conditions(body, &mut entry_states);
143 remove_costly_conditions(tcx, typing_env, body, &mut entry_states);
144
145 if let Some(opportunities) = OpportunitySet::new(body, entry_states) {
146 opportunities.apply();
147 }
148 }
149}
150
151struct TOFinder<'a, 'tcx> {
152 tcx: TyCtxt<'tcx>,
153 typing_env: ty::TypingEnv<'tcx>,
154 ecx: InterpCx<'tcx, DummyMachine>,
155 body: &'a Body<'tcx>,
156 map: Map<'tcx>,
157 maybe_loop_headers: DenseBitSet<BasicBlock>,
158 entry_states: IndexVec<BasicBlock, ConditionSet>,
163}
164
165rustc_index::newtype_index! {
166 #[orderable]
167 #[debug_format = "_c{}"]
168 struct ConditionIndex {}
169}
170
171#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
174struct Condition {
175 place: ValueIndex,
176 value: ScalarInt,
177 polarity: Polarity,
178}
179
180#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
181enum Polarity {
182 Ne,
183 Eq,
184}
185
186impl Condition {
187 fn matches(&self, place: ValueIndex, value: ScalarInt) -> bool {
188 self.place == place && (self.value == value) == (self.polarity == Polarity::Eq)
189 }
190}
191
192#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
194enum EdgeEffect {
195 Goto { target: BasicBlock },
197 Chain { succ_block: BasicBlock, succ_condition: ConditionIndex },
199}
200
201impl EdgeEffect {
202 fn block(self) -> BasicBlock {
203 match self {
204 EdgeEffect::Goto { target: bb } | EdgeEffect::Chain { succ_block: bb, .. } => bb,
205 }
206 }
207
208 fn replace_block(&mut self, target: BasicBlock, new_target: BasicBlock) {
209 match self {
210 EdgeEffect::Goto { target: bb } | EdgeEffect::Chain { succ_block: bb, .. } => {
211 if *bb == target {
212 *bb = new_target
213 }
214 }
215 }
216 }
217}
218
219#[derive(Clone, Debug, Default)]
220struct ConditionSet {
221 active: Vec<(ConditionIndex, Condition)>,
222 fulfilled: Vec<ConditionIndex>,
223 targets: IndexVec<ConditionIndex, Vec<EdgeEffect>>,
224}
225
226impl ConditionSet {
227 fn is_empty(&self) -> bool {
228 self.active.is_empty()
229 }
230
231 #[tracing::instrument(level = "trace", skip(self))]
232 fn push_condition(&mut self, c: Condition, target: BasicBlock) {
233 let index = self.targets.push(vec![EdgeEffect::Goto { target }]);
234 self.active.push((index, c));
235 }
236
237 fn fulfill_if(&mut self, f: impl Fn(Condition, &Vec<EdgeEffect>) -> bool) {
239 self.active.retain(|&(index, condition)| {
240 let targets = &self.targets[index];
241 if f(condition, targets) {
242 trace!(?index, ?condition, "fulfill");
243 self.fulfilled.push(index);
244 false
245 } else {
246 true
247 }
248 })
249 }
250
251 fn fulfill_matches(&mut self, place: ValueIndex, value: ScalarInt) {
253 self.fulfill_if(|c, _| c.matches(place, value))
254 }
255
256 fn retain(&mut self, mut f: impl FnMut(Condition) -> bool) {
257 self.active.retain(|&(_, c)| f(c))
258 }
259
260 fn retain_mut(&mut self, mut f: impl FnMut(Condition) -> Option<Condition>) {
261 self.active.retain_mut(|(_, c)| {
262 if let Some(new) = f(*c) {
263 *c = new;
264 true
265 } else {
266 false
267 }
268 })
269 }
270
271 fn for_each_mut(&mut self, f: impl Fn(&mut Condition)) {
272 for (_, c) in &mut self.active {
273 f(c)
274 }
275 }
276}
277
278impl<'a, 'tcx> TOFinder<'a, 'tcx> {
279 fn place(&mut self, place: Place<'tcx>, tail: Option<TrackElem>) -> Option<PlaceIndex> {
280 self.map.register_place(self.tcx, self.body, place, tail)
281 }
282
283 fn value(&mut self, place: PlaceIndex) -> Option<ValueIndex> {
284 self.map.register_value(self.tcx, self.typing_env, place)
285 }
286
287 fn place_value(&mut self, place: Place<'tcx>, tail: Option<TrackElem>) -> Option<ValueIndex> {
288 let place = self.place(place, tail)?;
289 self.value(place)
290 }
291
292 #[instrument(level = "trace", skip(self))]
294 fn populate_from_outgoing_edges(&mut self, bb: BasicBlock) -> ConditionSet {
295 let bbdata = &self.body[bb];
296
297 debug_assert!(self.entry_states[bb].is_empty());
299
300 let state_len =
301 bbdata.terminator().successors().map(|succ| self.entry_states[succ].active.len()).sum();
302 let mut state = ConditionSet {
303 active: Vec::with_capacity(state_len),
304 targets: IndexVec::with_capacity(state_len),
305 fulfilled: Vec::new(),
306 };
307
308 let mut known_conditions =
310 FxIndexSet::with_capacity_and_hasher(state_len, Default::default());
311 let mut insert = |condition, succ_block, succ_condition| {
312 let (index, new) = known_conditions.insert_full(condition);
313 let index = ConditionIndex::from_usize(index);
314 if new {
315 state.active.push((index, condition));
316 let _index = state.targets.push(Vec::new());
317 debug_assert_eq!(_index, index);
318 }
319 let target = EdgeEffect::Chain { succ_block, succ_condition };
320 debug_assert!(
321 !state.targets[index].contains(&target),
322 "duplicate targets for index={index:?} as {target:?} targets={:#?}",
323 &state.targets[index],
324 );
325 state.targets[index].push(target);
326 };
327
328 let mut seen = FxHashSet::default();
330 for succ in bbdata.terminator().successors() {
331 if !seen.insert(succ) {
332 continue;
333 }
334
335 if self.maybe_loop_headers.contains(succ) {
337 continue;
338 }
339
340 for &(succ_index, cond) in self.entry_states[succ].active.iter() {
341 insert(cond, succ, succ_index);
342 }
343 }
344
345 let num_conditions = known_conditions.len();
346 debug_assert_eq!(num_conditions, state.active.len());
347 debug_assert_eq!(num_conditions, state.targets.len());
348 state.fulfilled.reserve(num_conditions);
349
350 state
351 }
352
353 fn flood_state(
355 &self,
356 place: Place<'tcx>,
357 extra_elem: Option<TrackElem>,
358 state: &mut ConditionSet,
359 ) {
360 if state.is_empty() {
361 return;
362 }
363 let mut places_to_exclude = FxHashSet::default();
364 self.map.for_each_aliasing_place(place.as_ref(), extra_elem, &mut |vi| {
365 places_to_exclude.insert(vi);
366 });
367 trace!(?places_to_exclude, "flood_state");
368 if places_to_exclude.is_empty() {
369 return;
370 }
371 state.retain(|c| !places_to_exclude.contains(&c.place));
372 }
373
374 #[instrument(level = "trace", skip(self), ret)]
388 fn mutated_statement(
389 &self,
390 stmt: &Statement<'tcx>,
391 ) -> Option<(Place<'tcx>, Option<TrackElem>)> {
392 match stmt.kind {
393 StatementKind::Assign((place, _)) => Some((place, None)),
394 StatementKind::SetDiscriminant { ref place, variant_index: _ } => {
395 Some((**place, Some(TrackElem::Discriminant)))
396 }
397 StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
398 Some((Place::from(local), None))
399 }
400 | StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(..))
401 | StatementKind::Intrinsic(NonDivergingIntrinsic::CopyNonOverlapping(..))
403 | StatementKind::AscribeUserType(..)
404 | StatementKind::Coverage(..)
405 | StatementKind::FakeRead(..)
406 | StatementKind::ConstEvalCounter
407 | StatementKind::PlaceMention(..)
408 | StatementKind::BackwardIncompatibleDropHint { .. }
409 | StatementKind::Nop => None,
410 }
411 }
412
413 #[instrument(level = "trace", skip(self, state))]
414 fn process_immediate(&mut self, lhs: PlaceIndex, rhs: ImmTy<'tcx>, state: &mut ConditionSet) {
415 if let Some(lhs) = self.value(lhs)
416 && let Immediate::Scalar(Scalar::Int(int)) = *rhs
417 {
418 state.fulfill_matches(lhs, int)
419 }
420 }
421
422 #[instrument(level = "trace", skip(self, state))]
424 fn process_constant(
425 &mut self,
426 lhs: PlaceIndex,
427 constant: OpTy<'tcx>,
428 state: &mut ConditionSet,
429 ) {
430 self.map.for_each_projection_value(
431 lhs,
432 constant,
433 &mut |elem, op| match elem {
434 TrackElem::Field(idx) => self.ecx.project_field(op, idx).discard_err(),
435 TrackElem::Variant(idx) => self.ecx.project_downcast(op, idx).discard_err(),
436 TrackElem::Discriminant => {
437 let variant = self.ecx.read_discriminant(op).discard_err()?;
438 let discr_value =
439 self.ecx.discriminant_for_variant(op.layout.ty, variant).discard_err()?;
440 Some(discr_value.into())
441 }
442 TrackElem::DerefLen => {
443 let op: OpTy<'_> = self.ecx.deref_pointer(op).discard_err()?.into();
444 let len_usize = op.len(&self.ecx).discard_err()?;
445 let layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
446 Some(ImmTy::from_uint(len_usize, layout).into())
447 }
448 },
449 &mut |place, op| {
450 if let Some(place) = self.map.value(place)
451 && let Some(imm) = self.ecx.read_immediate_raw(op).discard_err()
452 && let Some(imm) = imm.right()
453 && let Immediate::Scalar(Scalar::Int(int)) = *imm
454 {
455 state.fulfill_matches(place, int)
456 }
457 },
458 );
459 }
460
461 #[instrument(level = "trace", skip(self, state))]
462 fn process_copy(&mut self, lhs: PlaceIndex, rhs: PlaceIndex, state: &mut ConditionSet) {
463 let mut renames = FxHashMap::default();
464 self.map.register_copy_tree(
465 lhs, rhs, &mut |lhs, rhs| {
468 renames.insert(lhs, rhs);
469 },
470 );
471 state.for_each_mut(|c| {
472 if let Some(rhs) = renames.get(&c.place) {
473 c.place = *rhs
474 }
475 });
476 }
477
478 #[instrument(level = "trace", skip(self, state))]
479 fn process_operand(&mut self, lhs: PlaceIndex, rhs: &Operand<'tcx>, state: &mut ConditionSet) {
480 match rhs {
481 Operand::Constant(constant) => {
483 let Some(constant) =
484 self.ecx.eval_mir_constant(&constant.const_, constant.span, None).discard_err()
485 else {
486 return;
487 };
488 self.process_constant(lhs, constant, state);
489 }
490 Operand::Move(rhs) | Operand::Copy(rhs) => {
492 let Some(rhs) = self.place(*rhs, None) else { return };
493 self.process_copy(lhs, rhs, state)
494 }
495 Operand::RuntimeChecks(_) => {}
496 }
497 }
498
499 #[instrument(level = "trace", skip(self, state))]
500 fn process_assign(
501 &mut self,
502 lhs_place: &Place<'tcx>,
503 rvalue: &Rvalue<'tcx>,
504 state: &mut ConditionSet,
505 ) {
506 let Some(lhs) = self.place(*lhs_place, None) else { return };
507 match rvalue {
508 Rvalue::Use(operand, _) => self.process_operand(lhs, operand, state),
509 Rvalue::Discriminant(rhs) => {
511 let Some(rhs) = self.place(*rhs, Some(TrackElem::Discriminant)) else { return };
512 self.process_copy(lhs, rhs, state)
513 }
514 Rvalue::Aggregate(kind, operands) => {
516 let agg_ty = lhs_place.ty(self.body, self.tcx).ty;
517 let lhs = match kind {
518 AggregateKind::Adt(.., Some(_)) => return,
520 AggregateKind::Adt(_, variant_index, ..) if agg_ty.is_enum() => {
521 let discr_ty = agg_ty.discriminant_ty(self.tcx);
522 let discr_target =
523 self.map.register_place_index(discr_ty, lhs, TrackElem::Discriminant);
524 if let Some(discr_value) =
525 self.ecx.discriminant_for_variant(agg_ty, *variant_index).discard_err()
526 {
527 self.process_immediate(discr_target, discr_value, state);
528 }
529 self.map.register_place_index(
530 agg_ty,
531 lhs,
532 TrackElem::Variant(*variant_index),
533 )
534 }
535 _ => lhs,
536 };
537 for (field_index, operand) in operands.iter_enumerated() {
538 let operand_ty = operand.ty(self.body, self.tcx);
539 let field = self.map.register_place_index(
540 operand_ty,
541 lhs,
542 TrackElem::Field(field_index),
543 );
544 self.process_operand(field, operand, state);
545 }
546 }
547 Rvalue::UnaryOp(UnOp::Not, Operand::Move(operand) | Operand::Copy(operand)) => {
549 let layout = self.ecx.layout_of(operand.ty(self.body, self.tcx).ty).unwrap();
550 let Some(lhs) = self.value(lhs) else { return };
551 let Some(operand) = self.place_value(*operand, None) else { return };
552 state.retain_mut(|mut c| {
553 if c.place == lhs {
554 let value = self
555 .ecx
556 .unary_op(UnOp::Not, &ImmTy::from_scalar_int(c.value, layout))
557 .discard_err()?
558 .to_scalar_int()
559 .discard_err()?;
560 c.place = operand;
561 c.value = value;
562 }
563 Some(c)
564 });
565 }
566 Rvalue::BinaryOp(
569 op,
570 (Operand::Move(operand) | Operand::Copy(operand), Operand::Constant(value))
571 | (Operand::Constant(value), Operand::Move(operand) | Operand::Copy(operand)),
572 ) => {
573 let equals = match op {
574 BinOp::Eq => ScalarInt::TRUE,
575 BinOp::Ne => ScalarInt::FALSE,
576 _ => return,
577 };
578 if value.const_.ty().is_floating_point() {
579 return;
584 }
585 let Some(lhs) = self.value(lhs) else { return };
586 let Some(operand) = self.place_value(*operand, None) else { return };
587 let Some(value) = value.const_.try_eval_scalar_int(self.tcx, self.typing_env)
588 else {
589 return;
590 };
591 state.for_each_mut(|c| {
592 if c.place == lhs {
593 let polarity =
594 if c.matches(lhs, equals) { Polarity::Eq } else { Polarity::Ne };
595 c.place = operand;
596 c.value = value;
597 c.polarity = polarity;
598 }
599 });
600 }
601
602 _ => {}
603 }
604 }
605
606 #[instrument(level = "trace", skip(self, state))]
607 fn process_statement(&mut self, stmt: &Statement<'tcx>, state: &mut ConditionSet) {
608 match &stmt.kind {
612 StatementKind::SetDiscriminant { place, variant_index } => {
615 let Some(discr_target) = self.place(**place, Some(TrackElem::Discriminant)) else {
616 return;
617 };
618 let enum_ty = place.ty(self.body, self.tcx).ty;
619 let Some(discr) =
623 self.ecx.discriminant_for_variant(enum_ty, *variant_index).discard_err()
624 else {
625 return;
626 };
627 self.process_immediate(discr_target, discr, state)
628 }
629 StatementKind::Intrinsic(NonDivergingIntrinsic::Assume(
631 Operand::Copy(place) | Operand::Move(place),
632 )) => {
633 let Some(place) = self.place_value(*place, None) else { return };
634 state.fulfill_matches(place, ScalarInt::TRUE);
635 }
636 StatementKind::Assign((lhs_place, rhs)) => self.process_assign(lhs_place, rhs, state),
637 _ => {}
638 }
639 }
640
641 #[instrument(level = "trace", skip(self, state))]
643 fn process_terminator(&mut self, bb: BasicBlock, state: &mut ConditionSet) {
644 let term = self.body.basic_blocks[bb].terminator();
645 let place_to_flood = match term.kind {
646 TerminatorKind::FalseEdge { .. }
648 | TerminatorKind::FalseUnwind { .. }
649 | TerminatorKind::Yield { .. } => bug!("{term:?} invalid"),
650 TerminatorKind::InlineAsm { .. } => {
652 state.active.clear();
653 return;
654 }
655 TerminatorKind::SwitchInt { ref discr, ref targets } => {
657 return self.process_switch_int(discr, targets, state);
658 }
659 TerminatorKind::UnwindResume
661 | TerminatorKind::UnwindTerminate(_)
662 | TerminatorKind::Return
663 | TerminatorKind::Unreachable
664 | TerminatorKind::CoroutineDrop
665 | TerminatorKind::Assert { .. }
667 | TerminatorKind::Goto { .. } => None,
668 TerminatorKind::Drop { place: destination, .. }
670 | TerminatorKind::Call { destination, .. } => Some(destination),
671 TerminatorKind::TailCall { .. } => Some(RETURN_PLACE.into()),
672 };
673
674 if let Some(place_to_flood) = place_to_flood {
676 self.flood_state(place_to_flood, None, state);
677 }
678 }
679
680 #[instrument(level = "trace", skip(self))]
681 fn process_switch_int(
682 &mut self,
683 discr: &Operand<'tcx>,
684 targets: &SwitchTargets,
685 state: &mut ConditionSet,
686 ) {
687 let Some(discr) = discr.place() else { return };
688 let Some(discr_idx) = self.place_value(discr, None) else { return };
689
690 let discr_ty = discr.ty(self.body, self.tcx).ty;
691 let Ok(discr_layout) = self.ecx.layout_of(discr_ty) else { return };
692
693 if targets.is_distinct() {
696 for &(index, c) in state.active.iter() {
697 if c.place != discr_idx {
698 continue;
699 }
700
701 let mut edges_fulfilling_condition = FxHashSet::default();
703
704 for (branch, tgt) in targets.iter() {
706 if let Some(branch) = ScalarInt::try_from_uint(branch, discr_layout.size)
707 && c.matches(discr_idx, branch)
708 {
709 edges_fulfilling_condition.insert(tgt);
710 }
711 }
712
713 if c.polarity == Polarity::Ne
718 && let value = c.value.to_bits(discr_layout.size)
719 && targets.all_values().contains(&value.into())
720 {
721 edges_fulfilling_condition.insert(targets.otherwise());
722 }
723
724 let condition_targets = &state.targets[index];
728
729 let new_edges: Vec<_> = condition_targets
730 .iter()
731 .copied()
732 .filter(|&target| match target {
733 EdgeEffect::Goto { .. } => false,
734 EdgeEffect::Chain { succ_block, .. } => {
735 edges_fulfilling_condition.contains(&succ_block)
736 }
737 })
738 .collect();
739
740 if new_edges.len() == condition_targets.len() {
741 state.fulfilled.push(index);
744 } else {
745 let index = state.targets.push(new_edges);
748 state.fulfilled.push(index);
749 }
750 }
751 }
752
753 let mut mk_condition = |value, polarity, target| {
755 let c = Condition { place: discr_idx, value, polarity };
756 state.push_condition(c, target);
757 };
758 if let Some((value, then_, else_)) = targets.as_static_if() {
759 let Some(value) = ScalarInt::try_from_uint(value, discr_layout.size) else { return };
761 mk_condition(value, Polarity::Eq, then_);
762 mk_condition(value, Polarity::Ne, else_);
763 } else {
764 for (value, target) in targets.iter() {
767 if let Some(value) = ScalarInt::try_from_uint(value, discr_layout.size) {
768 mk_condition(value, Polarity::Eq, target);
769 }
770 }
771 }
772 }
773}
774
775#[instrument(level = "debug", skip(body, entry_states))]
777fn simplify_conditions(body: &Body<'_>, entry_states: &mut IndexVec<BasicBlock, ConditionSet>) {
778 let basic_blocks = &body.basic_blocks;
779 let reverse_postorder = basic_blocks.reverse_postorder();
780
781 let mut predecessors = IndexVec::from_elem(0, &entry_states);
784 predecessors[START_BLOCK] = 1; for &bb in reverse_postorder {
786 let term = basic_blocks[bb].terminator();
787 for s in term.successors() {
788 predecessors[s] += 1;
789 }
790 }
791
792 let mut fulfill_in_pred_count = IndexVec::from_fn_n(
794 |bb: BasicBlock| IndexVec::from_elem_n(0, entry_states[bb].targets.len()),
795 entry_states.len(),
796 );
797
798 for &bb in reverse_postorder {
800 let preds = predecessors[bb];
801 trace!(?bb, ?preds);
802
803 if preds == 0 {
805 continue;
806 }
807
808 let state = &mut entry_states[bb];
809 trace!(?state);
810
811 trace!(fulfilled_count = ?fulfill_in_pred_count[bb]);
813 for (condition, &cond_preds) in fulfill_in_pred_count[bb].iter_enumerated() {
814 if cond_preds == preds {
815 trace!(?condition);
816 state.fulfilled.push(condition);
817 }
818 }
819
820 let mut targets: Vec<_> = state
823 .fulfilled
824 .iter()
825 .flat_map(|&index| state.targets[index].iter().copied())
826 .collect();
827 targets.sort();
828 targets.dedup();
829 trace!(?targets);
830
831 let mut successors = basic_blocks[bb].terminator().successors().collect::<Vec<_>>();
833
834 targets.reverse();
835 while let Some(target) = targets.pop() {
836 match target {
837 EdgeEffect::Goto { target } => {
838 predecessors[target] += 1;
841 for &s in successors.iter() {
842 predecessors[s] -= 1;
843 }
844 targets.retain(|t| t.block() == target);
846 successors.clear();
847 successors.push(target);
848 }
849 EdgeEffect::Chain { succ_block, succ_condition } => {
850 let count = successors.iter().filter(|&&s| s == succ_block).count();
853 fulfill_in_pred_count[succ_block][succ_condition] += count;
854 }
855 }
856 }
857 }
858}
859
860#[instrument(level = "debug", skip(tcx, typing_env, body, entry_states))]
861fn remove_costly_conditions<'tcx>(
862 tcx: TyCtxt<'tcx>,
863 typing_env: ty::TypingEnv<'tcx>,
864 body: &Body<'tcx>,
865 entry_states: &mut IndexVec<BasicBlock, ConditionSet>,
866) {
867 let basic_blocks = &body.basic_blocks;
868
869 let mut costs = IndexVec::from_elem(None, basic_blocks);
870 let mut cost = |bb: BasicBlock| -> u8 {
871 let c = *costs[bb].get_or_insert_with(|| {
872 let bbdata = &basic_blocks[bb];
873 let mut cost = CostChecker::new(tcx, typing_env, None, body);
874 cost.visit_basic_block_data(bb, bbdata);
875 cost.cost().try_into().unwrap_or(MAX_COST)
876 });
877 trace!("cost[{bb:?}] = {c}");
878 c
879 };
880
881 let mut condition_cost = IndexVec::from_fn_n(
883 |bb: BasicBlock| IndexVec::from_elem_n(MAX_COST, entry_states[bb].targets.len()),
884 entry_states.len(),
885 );
886
887 let reverse_postorder = basic_blocks.reverse_postorder();
888
889 for &bb in reverse_postorder.iter().rev() {
890 let state = &entry_states[bb];
891 trace!(?bb, ?state);
892
893 let mut current_costs = IndexVec::from_elem(0u8, &state.targets);
894
895 for (condition, targets) in state.targets.iter_enumerated() {
896 for &target in targets {
897 match target {
898 EdgeEffect::Goto { .. } => {}
900 EdgeEffect::Chain { succ_block, succ_condition }
902 if entry_states[succ_block].fulfilled.contains(&succ_condition) => {}
903 EdgeEffect::Chain { succ_block, succ_condition } => {
905 let duplication_cost = cost(succ_block);
907 let target_cost =
909 *condition_cost[succ_block].get(succ_condition).unwrap_or(&MAX_COST);
910 let cost = current_costs[condition]
911 .saturating_add(duplication_cost)
912 .saturating_add(target_cost);
913 trace!(?condition, ?succ_block, ?duplication_cost, ?target_cost);
914 current_costs[condition] = cost;
915 }
916 }
917 }
918 }
919
920 trace!("condition_cost[{bb:?}] = {:?}", current_costs);
921 condition_cost[bb] = current_costs;
922 }
923
924 trace!(?condition_cost);
925
926 for &bb in reverse_postorder {
927 for (index, targets) in entry_states[bb].targets.iter_enumerated_mut() {
928 if condition_cost[bb][index] >= MAX_COST {
929 trace!(?bb, ?index, ?targets, c = ?condition_cost[bb][index], "remove");
930 targets.clear()
931 }
932 }
933 }
934}
935
936struct OpportunitySet<'a, 'tcx> {
937 basic_blocks: &'a mut IndexVec<BasicBlock, BasicBlockData<'tcx>>,
938 entry_states: IndexVec<BasicBlock, ConditionSet>,
939 duplicates: FxHashMap<(BasicBlock, ConditionIndex), BasicBlock>,
942}
943
944impl<'a, 'tcx> OpportunitySet<'a, 'tcx> {
945 fn new(
946 body: &'a mut Body<'tcx>,
947 mut entry_states: IndexVec<BasicBlock, ConditionSet>,
948 ) -> Option<OpportunitySet<'a, 'tcx>> {
949 trace!(def_id = ?body.source.def_id(), "apply");
950
951 if entry_states.iter().all(|state| state.fulfilled.is_empty()) {
952 return None;
953 }
954
955 for state in entry_states.iter_mut() {
957 state.active = Default::default();
958 }
959 let duplicates = Default::default();
960 let basic_blocks = body.basic_blocks.as_mut();
961 Some(OpportunitySet { basic_blocks, entry_states, duplicates })
962 }
963
964 #[instrument(level = "debug", skip(self))]
966 fn apply(mut self) {
967 let mut worklist = Vec::with_capacity(self.basic_blocks.len());
968 worklist.push(START_BLOCK);
969
970 let mut visited = GrowableBitSet::with_capacity(self.basic_blocks.len());
972
973 while let Some(bb) = worklist.pop() {
974 if !visited.insert(bb) {
975 continue;
976 }
977
978 self.apply_once(bb);
979
980 worklist.extend(self.basic_blocks[bb].terminator().successors());
983 }
984 }
985
986 #[instrument(level = "debug", skip(self))]
988 fn apply_once(&mut self, bb: BasicBlock) {
989 let state = &mut self.entry_states[bb];
990 trace!(?state);
991
992 let mut targets: Vec<_> = state
995 .fulfilled
996 .iter()
997 .flat_map(|&index| std::mem::take(&mut state.targets[index]))
998 .collect();
999 targets.sort();
1000 targets.dedup();
1001 trace!(?targets);
1002
1003 targets.reverse();
1005 while let Some(target) = targets.pop() {
1006 debug!(?target);
1007 trace!(term = ?self.basic_blocks[bb].terminator().kind);
1008
1009 debug_assert!(
1013 self.basic_blocks[bb].terminator().successors().contains(&target.block()),
1014 "missing {target:?} in successors for {bb:?}, term={:?}",
1015 self.basic_blocks[bb].terminator(),
1016 );
1017
1018 match target {
1019 EdgeEffect::Goto { target } => {
1020 self.apply_goto(bb, target);
1021
1022 targets.retain(|t| t.block() == target);
1024 for ts in self.entry_states[bb].targets.iter_mut() {
1026 ts.retain(|t| t.block() == target);
1027 }
1028 }
1029 EdgeEffect::Chain { succ_block, succ_condition } => {
1030 let new_succ_block = self.apply_chain(bb, succ_block, succ_condition);
1031
1032 if let Some(new_succ_block) = new_succ_block {
1034 for t in targets.iter_mut() {
1035 t.replace_block(succ_block, new_succ_block)
1036 }
1037 for t in
1039 self.entry_states[bb].targets.iter_mut().flat_map(|ts| ts.iter_mut())
1040 {
1041 t.replace_block(succ_block, new_succ_block)
1042 }
1043 }
1044 }
1045 }
1046
1047 trace!(post_term = ?self.basic_blocks[bb].terminator().kind);
1048 }
1049 }
1050
1051 #[instrument(level = "debug", skip(self))]
1052 fn apply_goto(&mut self, bb: BasicBlock, target: BasicBlock) {
1053 self.basic_blocks[bb].terminator_mut().kind = TerminatorKind::Goto { target };
1054 }
1055
1056 #[instrument(level = "debug", skip(self), ret)]
1057 fn apply_chain(
1058 &mut self,
1059 bb: BasicBlock,
1060 target: BasicBlock,
1061 condition: ConditionIndex,
1062 ) -> Option<BasicBlock> {
1063 if self.entry_states[target].fulfilled.contains(&condition) {
1064 trace!("fulfilled");
1066 return None;
1067 }
1068
1069 let new_target = *self.duplicates.entry((target, condition)).or_insert_with(|| {
1075 let new_target = self.basic_blocks.push(self.basic_blocks[target].clone());
1078 trace!(?target, ?new_target, ?condition, "clone");
1079
1080 let mut condition_set = self.entry_states[target].clone();
1083 condition_set.fulfilled.push(condition);
1084 let _new_target = self.entry_states.push(condition_set);
1085 debug_assert_eq!(new_target, _new_target);
1086
1087 new_target
1088 });
1089 trace!(?target, ?new_target, ?condition, "reuse");
1090
1091 self.basic_blocks[bb].terminator_mut().successors_mut(|s| {
1094 if *s == target {
1095 *s = new_target;
1096 }
1097 });
1098
1099 Some(new_target)
1100 }
1101}
1102
1103fn maybe_loop_headers(body: &Body<'_>) -> DenseBitSet<BasicBlock> {
1109 let mut maybe_loop_headers = DenseBitSet::new_empty(body.basic_blocks.len());
1110 let mut visited = DenseBitSet::new_empty(body.basic_blocks.len());
1111 for (bb, bbdata) in traversal::postorder(body) {
1112 for succ in bbdata.terminator().successors() {
1115 if !visited.contains(succ) {
1116 maybe_loop_headers.insert(succ);
1117 }
1118 }
1119
1120 let _new = visited.insert(bb);
1123 debug_assert!(_new);
1124 }
1125
1126 maybe_loop_headers
1127}