Auto merge of #108815 - the8472:process-obligations-fast-skip, r=nnethercote
fast path for process_obligations Speeds up `keccak` and `cranelift-codegen` in perf.rlo.
This commit is contained in:
commit
df61fcaec1
@ -1365,9 +1365,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ena"
|
name = "ena"
|
||||||
version = "0.14.1"
|
version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2e5d13ca2353ab7d0230988629def93914a8c4015f621f9b13ed2955614731d"
|
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
@ -9,7 +9,7 @@ edition = "2021"
|
|||||||
arrayvec = { version = "0.7", default-features = false }
|
arrayvec = { version = "0.7", default-features = false }
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
ena = "0.14.1"
|
ena = "0.14.2"
|
||||||
indexmap = { version = "1.9.1" }
|
indexmap = { version = "1.9.1" }
|
||||||
jobserver_crate = { version = "0.1.13", package = "jobserver" }
|
jobserver_crate = { version = "0.1.13", package = "jobserver" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
@ -97,7 +97,17 @@ pub trait ObligationProcessor {
|
|||||||
type Error: Debug;
|
type Error: Debug;
|
||||||
type OUT: OutcomeTrait<Obligation = Self::Obligation, Error = Error<Self::Obligation, Self::Error>>;
|
type OUT: OutcomeTrait<Obligation = Self::Obligation, Error = Error<Self::Obligation, Self::Error>>;
|
||||||
|
|
||||||
fn needs_process_obligation(&self, obligation: &Self::Obligation) -> bool;
|
/// Implementations can provide a fast-path to obligation-processing
|
||||||
|
/// by counting the prefix of the passed iterator for which
|
||||||
|
/// `needs_process_obligation` would return false.
|
||||||
|
fn skippable_obligations<'a>(
|
||||||
|
&'a self,
|
||||||
|
_it: impl Iterator<Item = &'a Self::Obligation>,
|
||||||
|
) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_process_obligation(&self, _obligation: &Self::Obligation) -> bool;
|
||||||
|
|
||||||
fn process_obligation(
|
fn process_obligation(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -416,6 +426,10 @@ pub fn process_obligations<P>(&mut self, processor: &mut P) -> P::OUT
|
|||||||
loop {
|
loop {
|
||||||
let mut has_changed = false;
|
let mut has_changed = false;
|
||||||
|
|
||||||
|
// This is the super fast path for cheap-to-check conditions.
|
||||||
|
let mut index =
|
||||||
|
processor.skippable_obligations(self.nodes.iter().map(|n| &n.obligation));
|
||||||
|
|
||||||
// Note that the loop body can append new nodes, and those new nodes
|
// Note that the loop body can append new nodes, and those new nodes
|
||||||
// will then be processed by subsequent iterations of the loop.
|
// will then be processed by subsequent iterations of the loop.
|
||||||
//
|
//
|
||||||
@ -424,9 +438,8 @@ pub fn process_obligations<P>(&mut self, processor: &mut P) -> P::OUT
|
|||||||
// `for index in 0..self.nodes.len() { ... }` because the range would
|
// `for index in 0..self.nodes.len() { ... }` because the range would
|
||||||
// be computed with the initial length, and we would miss the appended
|
// be computed with the initial length, and we would miss the appended
|
||||||
// nodes. Therefore we use a `while` loop.
|
// nodes. Therefore we use a `while` loop.
|
||||||
let mut index = 0;
|
|
||||||
while let Some(node) = self.nodes.get_mut(index) {
|
while let Some(node) = self.nodes.get_mut(index) {
|
||||||
// This test is extremely hot.
|
// This is the moderately fast path when the prefix skipping above didn't work out.
|
||||||
if node.state.get() != NodeState::Pending
|
if node.state.get() != NodeState::Pending
|
||||||
|| !processor.needs_process_obligation(&node.obligation)
|
|| !processor.needs_process_obligation(&node.obligation)
|
||||||
{
|
{
|
||||||
|
@ -187,6 +187,16 @@ pub fn projection_cache(&mut self) -> traits::ProjectionCache<'_, 'tcx> {
|
|||||||
self.projection_cache.with_log(&mut self.undo_log)
|
self.projection_cache.with_log(&mut self.undo_log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_type_variables_probe_ref(
|
||||||
|
&self,
|
||||||
|
vid: ty::TyVid,
|
||||||
|
) -> Option<&type_variable::TypeVariableValue<'tcx>> {
|
||||||
|
// Uses a read-only view of the unification table, this way we don't
|
||||||
|
// need an undo log.
|
||||||
|
self.type_variable_storage.eq_relations_ref().try_probe_value(vid)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn type_variables(&mut self) -> type_variable::TypeVariableTable<'_, 'tcx> {
|
fn type_variables(&mut self) -> type_variable::TypeVariableTable<'_, 'tcx> {
|
||||||
self.type_variable_storage.with_log(&mut self.undo_log)
|
self.type_variable_storage.with_log(&mut self.undo_log)
|
||||||
@ -1646,6 +1656,28 @@ pub fn const_eval_resolve(
|
|||||||
tcx.const_eval_resolve_for_typeck(param_env_erased, unevaluated, span)
|
tcx.const_eval_resolve_for_typeck(param_env_erased, unevaluated, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The returned function is used in a fast path. If it returns `true` the variable is
|
||||||
|
/// unchanged, `false` indicates that the status is unknown.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_ty_infer_var_definitely_unchanged<'a>(
|
||||||
|
&'a self,
|
||||||
|
) -> (impl Fn(TyOrConstInferVar<'tcx>) -> bool + 'a) {
|
||||||
|
// This hoists the borrow/release out of the loop body.
|
||||||
|
let inner = self.inner.try_borrow();
|
||||||
|
|
||||||
|
return move |infer_var: TyOrConstInferVar<'tcx>| match (infer_var, &inner) {
|
||||||
|
(TyOrConstInferVar::Ty(ty_var), Ok(inner)) => {
|
||||||
|
use self::type_variable::TypeVariableValue;
|
||||||
|
|
||||||
|
match inner.try_type_variables_probe_ref(ty_var) {
|
||||||
|
Some(TypeVariableValue::Unknown { .. }) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// `ty_or_const_infer_var_changed` is equivalent to one of these two:
|
/// `ty_or_const_infer_var_changed` is equivalent to one of these two:
|
||||||
/// * `shallow_resolve(ty) != ty` (where `ty.kind = ty::Infer(_)`)
|
/// * `shallow_resolve(ty) != ty` (where `ty.kind = ty::Infer(_)`)
|
||||||
/// * `shallow_resolve(ct) != ct` (where `ct.kind = ty::ConstKind::Infer(_)`)
|
/// * `shallow_resolve(ct) != ct` (where `ct.kind = ty::ConstKind::Infer(_)`)
|
||||||
|
@ -190,6 +190,11 @@ pub(crate) fn with_log<'a>(
|
|||||||
) -> TypeVariableTable<'a, 'tcx> {
|
) -> TypeVariableTable<'a, 'tcx> {
|
||||||
TypeVariableTable { storage: self, undo_log }
|
TypeVariableTable { storage: self, undo_log }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn eq_relations_ref(&self) -> &ut::UnificationTableStorage<TyVidEqKey<'tcx>> {
|
||||||
|
&self.eq_relations
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> TypeVariableTable<'_, 'tcx> {
|
impl<'tcx> TypeVariableTable<'_, 'tcx> {
|
||||||
|
@ -211,6 +211,29 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
|
|||||||
type Error = FulfillmentErrorCode<'tcx>;
|
type Error = FulfillmentErrorCode<'tcx>;
|
||||||
type OUT = Outcome<Self::Obligation, Self::Error>;
|
type OUT = Outcome<Self::Obligation, Self::Error>;
|
||||||
|
|
||||||
|
/// Compared to `needs_process_obligation` this and its callees
|
||||||
|
/// contain some optimizations that come at the price of false negatives.
|
||||||
|
///
|
||||||
|
/// They
|
||||||
|
/// - reduce branching by covering only the most common case
|
||||||
|
/// - take a read-only view of the unification tables which allows skipping undo_log
|
||||||
|
/// construction.
|
||||||
|
/// - bail out on value-cache misses in ena to avoid pointer chasing
|
||||||
|
/// - hoist RefCell locking out of the loop
|
||||||
|
#[inline]
|
||||||
|
fn skippable_obligations<'b>(
|
||||||
|
&'b self,
|
||||||
|
it: impl Iterator<Item = &'b Self::Obligation>,
|
||||||
|
) -> usize {
|
||||||
|
let is_unchanged = self.selcx.infcx.is_ty_infer_var_definitely_unchanged();
|
||||||
|
|
||||||
|
it.take_while(|o| match o.stalled_on.as_slice() {
|
||||||
|
[o] => is_unchanged(*o),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifies whether a predicate obligation needs processing.
|
/// Identifies whether a predicate obligation needs processing.
|
||||||
///
|
///
|
||||||
/// This is always inlined because it has a single callsite and it is
|
/// This is always inlined because it has a single callsite and it is
|
||||||
|
Loading…
Reference in New Issue
Block a user