Auto merge of #121421 - saethlin:smarter-mono, r=oli-obk · rust-lang/rust@5a6c1aa (original) (raw)

`@@ -10,7 +10,7 @@ use crate::ty::print::{pretty_print_const, with_no_trimmed_paths};

`

10

10

`use crate::ty::print::{FmtPrinter, Printer};

`

11

11

`use crate::ty::visit::TypeVisitableExt;

`

12

12

`use crate::ty::{self, List, Ty, TyCtxt};

`

13

``

`-

use crate::ty::{AdtDef, InstanceDef, UserTypeAnnotationIndex};

`

``

13

`+

use crate::ty::{AdtDef, Instance, InstanceDef, UserTypeAnnotationIndex};

`

14

14

`use crate::ty::{GenericArg, GenericArgsRef};

`

15

15

``

16

16

`use rustc_data_structures::captures::Captures;

`

`@@ -27,6 +27,8 @@ pub use rustc_ast::Mutability;

`

27

27

`use rustc_data_structures::fx::FxHashMap;

`

28

28

`use rustc_data_structures::fx::FxHashSet;

`

29

29

`use rustc_data_structures::graph::dominators::Dominators;

`

``

30

`+

use rustc_data_structures::stack::ensure_sufficient_stack;

`

``

31

`+

use rustc_index::bit_set::BitSet;

`

30

32

`use rustc_index::{Idx, IndexSlice, IndexVec};

`

31

33

`use rustc_serialize::{Decodable, Encodable};

`

32

34

`use rustc_span::symbol::Symbol;

`

`@@ -640,6 +642,129 @@ impl<'tcx> Body<'tcx> {

`

640

642

`self.injection_phase.is_some()

`

641

643

`}

`

642

644

``

``

645

`+

/// Finds which basic blocks are actually reachable for a specific

`

``

646

`+

/// monomorphization of this body.

`

``

647

`+

///

`

``

648

`+

/// This is allowed to have false positives; just because this says a block

`

``

649

`+

/// is reachable doesn't mean that's necessarily true. It's thus always

`

``

650

`+

/// legal for this to return a filled set.

`

``

651

`+

///

`

``

652

`` +

/// Regardless, the [BitSet::domain_size] of the returned set will always

``

``

653

`` +

/// exactly match the number of blocks in the body so that contains

``

``

654

`+

/// checks can be done without worrying about panicking.

`

``

655

`+

///

`

``

656

`` +

/// This is mostly useful because it lets us skip lowering the false side

``

``

657

`` +

/// of if <T as Trait>::CONST, as well as intrinsics::debug_assertions.

``

``

658

`+

pub fn reachable_blocks_in_mono(

`

``

659

`+

&self,

`

``

660

`+

tcx: TyCtxt<'tcx>,

`

``

661

`+

instance: Instance<'tcx>,

`

``

662

`+

) -> BitSet {

`

``

663

`+

let mut set = BitSet::new_empty(self.basic_blocks.len());

`

``

664

`+

self.reachable_blocks_in_mono_from(tcx, instance, &mut set, START_BLOCK);

`

``

665

`+

set

`

``

666

`+

}

`

``

667

+

``

668

`+

fn reachable_blocks_in_mono_from(

`

``

669

`+

&self,

`

``

670

`+

tcx: TyCtxt<'tcx>,

`

``

671

`+

instance: Instance<'tcx>,

`

``

672

`+

set: &mut BitSet,

`

``

673

`+

bb: BasicBlock,

`

``

674

`+

) {

`

``

675

`+

if !set.insert(bb) {

`

``

676

`+

return;

`

``

677

`+

}

`

``

678

+

``

679

`+

let data = &self.basic_blocks[bb];

`

``

680

+

``

681

`+

if let Some((bits, targets)) = Self::try_const_mono_switchint(tcx, instance, data) {

`

``

682

`+

let target = targets.target_for_value(bits);

`

``

683

`+

ensure_sufficient_stack(|| {

`

``

684

`+

self.reachable_blocks_in_mono_from(tcx, instance, set, target)

`

``

685

`+

});

`

``

686

`+

return;

`

``

687

`+

}

`

``

688

+

``

689

`+

for target in data.terminator().successors() {

`

``

690

`+

ensure_sufficient_stack(|| {

`

``

691

`+

self.reachable_blocks_in_mono_from(tcx, instance, set, target)

`

``

692

`+

});

`

``

693

`+

}

`

``

694

`+

}

`

``

695

+

``

696

`` +

/// If this basic block ends with a [TerminatorKind::SwitchInt] for which we can evaluate the

``

``

697

`+

/// dimscriminant in monomorphization, we return the discriminant bits and the

`

``

698

`` +

/// [SwitchTargets], just so the caller doesn't also have to match on the terminator.

``

``

699

`+

fn try_const_mono_switchint<'a>(

`

``

700

`+

tcx: TyCtxt<'tcx>,

`

``

701

`+

instance: Instance<'tcx>,

`

``

702

`+

block: &'a BasicBlockData<'tcx>,

`

``

703

`+

) -> Option<(u128, &'a SwitchTargets)> {

`

``

704

`+

// There are two places here we need to evaluate a constant.

`

``

705

`+

let eval_mono_const = |constant: &ConstOperand<'tcx>| {

`

``

706

`+

let env = ty::ParamEnv::reveal_all();

`

``

707

`+

let mono_literal = instance.instantiate_mir_and_normalize_erasing_regions(

`

``

708

`+

tcx,

`

``

709

`+

env,

`

``

710

`+

crate::ty::EarlyBinder::bind(constant.const_),

`

``

711

`+

);

`

``

712

`+

let Some(bits) = mono_literal.try_eval_bits(tcx, env) else {

`

``

713

`+

bug!("Couldn't evaluate constant {:?} in mono {:?}", constant, instance);

`

``

714

`+

};

`

``

715

`+

bits

`

``

716

`+

};

`

``

717

+

``

718

`+

let TerminatorKind::SwitchInt { discr, targets } = &block.terminator().kind else {

`

``

719

`+

return None;

`

``

720

`+

};

`

``

721

+

``

722

`+

// If this is a SwitchInt(const _), then we can just evaluate the constant and return.

`

``

723

`+

let discr = match discr {

`

``

724

`+

Operand::Constant(constant) => {

`

``

725

`+

let bits = eval_mono_const(constant);

`

``

726

`+

return Some((bits, targets));

`

``

727

`+

}

`

``

728

`+

Operand::Move(place) | Operand::Copy(place) => place,

`

``

729

`+

};

`

``

730

+

``

731

`` +

// MIR for if false actually looks like this:

``

``

732

`+

// _1 = const _

`

``

733

`+

// SwitchInt(_1)

`

``

734

`+

//

`

``

735

`+

// And MIR for if intrinsics::debug_assertions() looks like this:

`

``

736

`+

// _1 = cfg!(debug_assertions)

`

``

737

`+

// SwitchInt(_1)

`

``

738

`+

//

`

``

739

`+

// So we're going to try to recognize this pattern.

`

``

740

`+

//

`

``

741

`+

// If we have a SwitchInt on a non-const place, we find the most recent statement that

`

``

742

`+

// isn't a storage marker. If that statement is an assignment of a const to our

`

``

743

`+

// discriminant place, we evaluate and return the const, as if we've const-propagated it

`

``

744

`+

// into the SwitchInt.

`

``

745

+

``

746

`+

let last_stmt = block.statements.iter().rev().find(|stmt| {

`

``

747

`+

!matches!(stmt.kind, StatementKind::StorageDead() | StatementKind::StorageLive())

`

``

748

`+

})?;

`

``

749

+

``

750

`+

let (place, rvalue) = last_stmt.kind.as_assign()?;

`

``

751

+

``

752

`+

if discr != place {

`

``

753

`+

return None;

`

``

754

`+

}

`

``

755

+

``

756

`+

match rvalue {

`

``

757

`+

Rvalue::NullaryOp(NullOp::UbCheck(_), _) => {

`

``

758

`+

Some((tcx.sess.opts.debug_assertions as u128, targets))

`

``

759

`+

}

`

``

760

`+

Rvalue::Use(Operand::Constant(constant)) => {

`

``

761

`+

let bits = eval_mono_const(constant);

`

``

762

`+

Some((bits, targets))

`

``

763

`+

}

`

``

764

`+

_ => None,

`

``

765

`+

}

`

``

766

`+

}

`

``

767

+

643

768

`` /// For a Location in this scope, determine what the "caller location" at that point is. This

``

644

769

`` /// is interesting because of inlining: the #[track_caller] attribute of inlined functions

``

645

770

`` /// must be honored. Falls back to the tracked_caller value for #[track_caller] functions,

``