Skip to content

Commit c7bc0bf

Browse files
committed
Auto merge of #64627 - nnethercote:ObligForest-even-more, r=nikomatsakis
Even more `ObligationForest` improvements Following on from #64545, more speed and readability improvements. r? @nikomatsakis
2 parents b7820b2 + aa10abb commit c7bc0bf

File tree

6 files changed

+85
-60
lines changed

6 files changed

+85
-60
lines changed

Cargo.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -917,9 +917,9 @@ dependencies = [
917917

918918
[[package]]
919919
name = "ena"
920-
version = "0.13.0"
920+
version = "0.13.1"
921921
source = "registry+https://github.com/rust-lang/crates.io-index"
922-
checksum = "3dc01d68e08ca384955a3aeba9217102ca1aa85b6e168639bf27739f1d749d87"
922+
checksum = "8944dc8fa28ce4a38f778bd46bf7d923fe73eed5a439398507246c8e017e6f36"
923923
dependencies = [
924924
"log",
925925
]

src/librustc/infer/mod.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1600,29 +1600,30 @@ impl<'a, 'tcx> ShallowResolver<'a, 'tcx> {
16001600

16011601
// `resolver.shallow_resolve_changed(ty)` is equivalent to
16021602
// `resolver.shallow_resolve(ty) != ty`, but more efficient. It's always
1603-
// inlined, despite being large, because it has a single call site that is
1604-
// extremely hot.
1603+
// inlined, despite being large, because it has only two call sites that
1604+
// are extremely hot.
16051605
#[inline(always)]
16061606
pub fn shallow_resolve_changed(&mut self, typ: Ty<'tcx>) -> bool {
16071607
match typ.sty {
16081608
ty::Infer(ty::TyVar(v)) => {
16091609
use self::type_variable::TypeVariableValue;
16101610

16111611
// See the comment in `shallow_resolve()`.
1612-
match self.infcx.type_variables.borrow_mut().probe(v) {
1612+
match self.infcx.type_variables.borrow_mut().inlined_probe(v) {
16131613
TypeVariableValue::Known { value: t } => self.fold_ty(t) != typ,
16141614
TypeVariableValue::Unknown { .. } => false,
16151615
}
16161616
}
16171617

16181618
ty::Infer(ty::IntVar(v)) => {
1619-
match self.infcx.int_unification_table.borrow_mut().probe_value(v) {
1619+
match self.infcx.int_unification_table.borrow_mut().inlined_probe_value(v) {
16201620
Some(v) => v.to_type(self.infcx.tcx) != typ,
16211621
None => false,
16221622
}
16231623
}
16241624

16251625
ty::Infer(ty::FloatVar(v)) => {
1626+
// Not `inlined_probe_value(v)` because this call site is colder.
16261627
match self.infcx.float_unification_table.borrow_mut().probe_value(v) {
16271628
Some(v) => v.to_type(self.infcx.tcx) != typ,
16281629
None => false,

src/librustc/infer/type_variable.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,13 @@ impl<'tcx> TypeVariableTable<'tcx> {
234234
/// Retrieves the type to which `vid` has been instantiated, if
235235
/// any.
236236
pub fn probe(&mut self, vid: ty::TyVid) -> TypeVariableValue<'tcx> {
237-
self.eq_relations.probe_value(vid)
237+
self.inlined_probe(vid)
238+
}
239+
240+
/// An always-inlined variant of `probe`, for very hot call sites.
241+
#[inline(always)]
242+
pub fn inlined_probe(&mut self, vid: ty::TyVid) -> TypeVariableValue<'tcx> {
243+
self.eq_relations.inlined_probe_value(vid)
238244
}
239245

240246
/// If `t` is a type-inference variable, and it has been

src/librustc/traits/fulfill.rs

+36-19
Original file line numberDiff line numberDiff line change
@@ -256,29 +256,46 @@ impl<'a, 'b, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'b, 'tcx> {
256256
&mut self,
257257
pending_obligation: &mut Self::Obligation,
258258
) -> ProcessResult<Self::Obligation, Self::Error> {
259-
// If we were stalled on some unresolved variables, first check
260-
// whether any of them have been resolved; if not, don't bother
261-
// doing more work yet
262-
if !pending_obligation.stalled_on.is_empty() {
263-
let mut changed = false;
264-
// This `for` loop was once a call to `all()`, but this lower-level
265-
// form was a perf win. See #64545 for details.
266-
for &ty in &pending_obligation.stalled_on {
267-
if ShallowResolver::new(self.selcx.infcx()).shallow_resolve_changed(ty) {
268-
changed = true;
269-
break;
270-
}
259+
// If we were stalled on some unresolved variables, first check whether
260+
// any of them have been resolved; if not, don't bother doing more work
261+
// yet.
262+
let change = match pending_obligation.stalled_on.len() {
263+
// Match arms are in order of frequency, which matters because this
264+
// code is so hot. 1 and 0 dominate; 2+ is fairly rare.
265+
1 => {
266+
let ty = pending_obligation.stalled_on[0];
267+
ShallowResolver::new(self.selcx.infcx()).shallow_resolve_changed(ty)
268+
}
269+
0 => {
270+
// In this case we haven't changed, but wish to make a change.
271+
true
271272
}
272-
if !changed {
273-
debug!("process_predicate: pending obligation {:?} still stalled on {:?}",
274-
self.selcx.infcx()
275-
.resolve_vars_if_possible(&pending_obligation.obligation),
276-
pending_obligation.stalled_on);
277-
return ProcessResult::Unchanged;
273+
_ => {
274+
// This `for` loop was once a call to `all()`, but this lower-level
275+
// form was a perf win. See #64545 for details.
276+
(|| {
277+
for &ty in &pending_obligation.stalled_on {
278+
if ShallowResolver::new(self.selcx.infcx()).shallow_resolve_changed(ty) {
279+
return true;
280+
}
281+
}
282+
false
283+
})()
278284
}
279-
pending_obligation.stalled_on = vec![];
285+
};
286+
287+
if !change {
288+
debug!("process_predicate: pending obligation {:?} still stalled on {:?}",
289+
self.selcx.infcx()
290+
.resolve_vars_if_possible(&pending_obligation.obligation),
291+
pending_obligation.stalled_on);
292+
return ProcessResult::Unchanged;
280293
}
281294

295+
// This part of the code is much colder.
296+
297+
pending_obligation.stalled_on.truncate(0);
298+
282299
let obligation = &mut pending_obligation.obligation;
283300

284301
if obligation.predicate.has_infer_types() {

src/librustc_data_structures/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ path = "lib.rs"
1010
doctest = false
1111

1212
[dependencies]
13-
ena = "0.13"
13+
ena = "0.13.1"
1414
indexmap = "1"
1515
log = "0.4"
1616
jobserver_crate = { version = "0.1.13", package = "jobserver" }

src/librustc_data_structures/obligation_forest/mod.rs

+34-33
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ pub struct ObligationForest<O: ForestObligation> {
149149
/// A cache of the nodes in `nodes`, indexed by predicate. Unfortunately,
150150
/// its contents are not guaranteed to match those of `nodes`. See the
151151
/// comments in `process_obligation` for details.
152-
waiting_cache: FxHashMap<O::Predicate, usize>,
152+
active_cache: FxHashMap<O::Predicate, usize>,
153153

154154
/// A scratch vector reused in various operations, to avoid allocating new
155155
/// vectors.
@@ -278,7 +278,7 @@ impl<O: ForestObligation> ObligationForest<O> {
278278
ObligationForest {
279279
nodes: vec![],
280280
done_cache: Default::default(),
281-
waiting_cache: Default::default(),
281+
active_cache: Default::default(),
282282
scratch: RefCell::new(vec![]),
283283
obligation_tree_id_generator: (0..).map(ObligationTreeId),
284284
error_cache: Default::default(),
@@ -303,15 +303,15 @@ impl<O: ForestObligation> ObligationForest<O> {
303303
return Ok(());
304304
}
305305

306-
match self.waiting_cache.entry(obligation.as_predicate().clone()) {
306+
match self.active_cache.entry(obligation.as_predicate().clone()) {
307307
Entry::Occupied(o) => {
308308
debug!("register_obligation_at({:?}, {:?}) - duplicate of {:?}!",
309309
obligation, parent, o.get());
310310
let node = &mut self.nodes[*o.get()];
311311
if let Some(parent_index) = parent {
312-
// If the node is already in `waiting_cache`, it has
313-
// already had its chance to be marked with a parent. So if
314-
// it's not already present, just dump `parent` into the
312+
// If the node is already in `active_cache`, it has already
313+
// had its chance to be marked with a parent. So if it's
314+
// not already present, just dump `parent` into the
315315
// dependents as a non-parent.
316316
if !node.dependents.contains(&parent_index) {
317317
node.dependents.push(parent_index);
@@ -355,10 +355,9 @@ impl<O: ForestObligation> ObligationForest<O> {
355355
let mut errors = vec![];
356356
for (index, node) in self.nodes.iter().enumerate() {
357357
if let NodeState::Pending = node.state.get() {
358-
let backtrace = self.error_at(index);
359358
errors.push(Error {
360359
error: error.clone(),
361-
backtrace,
360+
backtrace: self.error_at(index),
362361
});
363362
}
364363
}
@@ -406,8 +405,8 @@ impl<O: ForestObligation> ObligationForest<O> {
406405

407406
// `processor.process_obligation` can modify the predicate within
408407
// `node.obligation`, and that predicate is the key used for
409-
// `self.waiting_cache`. This means that `self.waiting_cache` can
410-
// get out of sync with `nodes`. It's not very common, but it does
408+
// `self.active_cache`. This means that `self.active_cache` can get
409+
// out of sync with `nodes`. It's not very common, but it does
411410
// happen, and code in `compress` has to allow for it.
412411
let result = match node.state.get() {
413412
NodeState::Pending => processor.process_obligation(&mut node.obligation),
@@ -439,10 +438,9 @@ impl<O: ForestObligation> ObligationForest<O> {
439438
}
440439
ProcessResult::Error(err) => {
441440
stalled = false;
442-
let backtrace = self.error_at(index);
443441
errors.push(Error {
444442
error: err,
445-
backtrace,
443+
backtrace: self.error_at(index),
446444
});
447445
}
448446
}
@@ -484,13 +482,16 @@ impl<O: ForestObligation> ObligationForest<O> {
484482
debug!("process_cycles()");
485483

486484
for (index, node) in self.nodes.iter().enumerate() {
487-
// For rustc-benchmarks/inflate-0.1.0 this state test is extremely
488-
// hot and the state is almost always `Pending` or `Waiting`. It's
489-
// a win to handle the no-op cases immediately to avoid the cost of
490-
// the function call.
485+
// For some benchmarks this state test is extremely
486+
// hot. It's a win to handle the no-op cases immediately to avoid
487+
// the cost of the function call.
491488
match node.state.get() {
492-
NodeState::Waiting | NodeState::Pending | NodeState::Done | NodeState::Error => {},
493-
_ => self.find_cycles_from_node(&mut stack, processor, index),
489+
// Match arms are in order of frequency. Pending, Success and
490+
// Waiting dominate; the others are rare.
491+
NodeState::Pending => {},
492+
NodeState::Success => self.find_cycles_from_node(&mut stack, processor, index),
493+
NodeState::Waiting | NodeState::Done | NodeState::Error => {},
494+
NodeState::OnDfsStack => self.find_cycles_from_node(&mut stack, processor, index),
494495
}
495496
}
496497

@@ -506,8 +507,8 @@ impl<O: ForestObligation> ObligationForest<O> {
506507
let node = &self.nodes[index];
507508
match node.state.get() {
508509
NodeState::OnDfsStack => {
509-
let index = stack.iter().rposition(|&n| n == index).unwrap();
510-
processor.process_backedge(stack[index..].iter().map(GetObligation(&self.nodes)),
510+
let rpos = stack.iter().rposition(|&n| n == index).unwrap();
511+
processor.process_backedge(stack[rpos..].iter().map(GetObligation(&self.nodes)),
511512
PhantomData);
512513
}
513514
NodeState::Success => {
@@ -636,11 +637,11 @@ impl<O: ForestObligation> ObligationForest<O> {
636637
}
637638
NodeState::Done => {
638639
// This lookup can fail because the contents of
639-
// `self.waiting_cache` is not guaranteed to match those of
640+
// `self.active_cache` is not guaranteed to match those of
640641
// `self.nodes`. See the comment in `process_obligation`
641642
// for more details.
642-
if let Some((predicate, _)) = self.waiting_cache
643-
.remove_entry(node.obligation.as_predicate())
643+
if let Some((predicate, _)) =
644+
self.active_cache.remove_entry(node.obligation.as_predicate())
644645
{
645646
self.done_cache.insert(predicate);
646647
} else {
@@ -653,7 +654,7 @@ impl<O: ForestObligation> ObligationForest<O> {
653654
// We *intentionally* remove the node from the cache at this point. Otherwise
654655
// tests must come up with a different type on every type error they
655656
// check against.
656-
self.waiting_cache.remove(node.obligation.as_predicate());
657+
self.active_cache.remove(node.obligation.as_predicate());
657658
node_rewrites[index] = nodes_len;
658659
dead_nodes += 1;
659660
self.insert_into_error_cache(index);
@@ -697,25 +698,25 @@ impl<O: ForestObligation> ObligationForest<O> {
697698
let nodes_len = node_rewrites.len();
698699

699700
for node in &mut self.nodes {
700-
let mut index = 0;
701-
while index < node.dependents.len() {
702-
let new_index = node_rewrites[node.dependents[index]];
701+
let mut i = 0;
702+
while i < node.dependents.len() {
703+
let new_index = node_rewrites[node.dependents[i]];
703704
if new_index >= nodes_len {
704-
node.dependents.swap_remove(index);
705-
if index == 0 && node.has_parent {
705+
node.dependents.swap_remove(i);
706+
if i == 0 && node.has_parent {
706707
// We just removed the parent.
707708
node.has_parent = false;
708709
}
709710
} else {
710-
node.dependents[index] = new_index;
711-
index += 1;
711+
node.dependents[i] = new_index;
712+
i += 1;
712713
}
713714
}
714715
}
715716

716-
// This updating of `self.waiting_cache` is necessary because the
717+
// This updating of `self.active_cache` is necessary because the
717718
// removal of nodes within `compress` can fail. See above.
718-
self.waiting_cache.retain(|_predicate, index| {
719+
self.active_cache.retain(|_predicate, index| {
719720
let new_index = node_rewrites[*index];
720721
if new_index >= nodes_len {
721722
false

0 commit comments

Comments
 (0)