Skip to content

Commit fc403ad

Browse files
committed
Auto merge of #53255 - orium:fix-bug-overflow-send, r=arielb1
Add a per-tree error cache to the obligation forest This implements part of what @nikomatsakis mentioned in #30533 (comment): > 1. If you find that a new obligation is a duplicate of one already in the tree, the proper processing is: > * if that other location is your parent, you should abort with a cycle error (or accept it, if coinductive) > * if that other location is not an ancestor, you can safely ignore the new obligation In particular it implements the "if that other location is your parent accept it, if coinductive" part. This fixes #40827. I have to say that I'm not 100% confident that this is rock solid. This is my first pull request 🎉, and I didn't know anything about the trait resolver before this. In particular I'm not totally sure that comparing predicates is enough (for instance, do we need to compare `param_env` as well?). Also, I'm not sure what @nikomatsakis mentions [here](#30977 (comment)), but it might be something that affects this PR: > In particular, I am wary of getting things wrong around inference variables! We can always add things to the set in their current state, and if unifications occur then the obligation is just kind of out-of-date, but I want to be sure we don't accidentally fail to notice that something is our ancestor. I decided this was subtle enough to merit its own PR. Anyway, go ahead and review 🙂. Ref #30977. # Performance We are now copying vectors around, so I decided to do some benchmarking. A simple benchmark shows that this does not seem to affect performance in a measurable way: I ran `cargo clean && cargo build` 20 times on actix-web (84b27db) and these are the results: ```text rustc master: Mean Std.Dev. Min Median Max real 66.637 2.996 57.220 67.714 69.314 user 307.293 14.741 258.093 312.209 320.702 sys 12.524 0.653 10.499 12.726 13.193 rustc fix-bug-overflow-send: Mean Std.Dev. Min Median Max real 66.297 4.310 53.532 67.516 70.348 user 306.812 22.371 236.917 314.748 326.229 sys 12.757 0.952 9.671 13.125 13.544 ``` I will do a more comprehensive benchmark (compiling rustc stage1) and post the results. r? @nikomatsakis, @nnethercote PS: It is better to review this commit-by-commit.
2 parents 3905409 + 6bfa6aa commit fc403ad

File tree

10 files changed

+183
-49
lines changed

10 files changed

+183
-49
lines changed

src/librustc/traits/auto_trait.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,16 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
7373
/// ```
7474
/// struct Foo<T> { data: Box<T> }
7575
/// ```
76-
76+
///
7777
/// then this might return that Foo<T>: Send if T: Send (encoded in the AutoTraitResult type).
7878
/// The analysis attempts to account for custom impls as well as other complex cases. This
7979
/// result is intended for use by rustdoc and other such consumers.
80-
80+
///
8181
/// (Note that due to the coinductive nature of Send, the full and correct result is actually
8282
/// quite simple to generate. That is, when a type has no custom impl, it is Send iff its field
8383
/// types are all Send. So, in our example, we might have that Foo<T>: Send if Box<T>: Send.
8484
/// But this is often not the best way to present to the user.)
85-
85+
///
8686
/// Warning: The API should be considered highly unstable, and it may be refactored or removed
8787
/// in the future.
8888
pub fn find_auto_trait_generics<A>(
@@ -288,7 +288,7 @@ impl<'a, 'tcx> AutoTraitFinder<'a, 'tcx> {
288288
// hold.
289289
//
290290
// One additional consideration is supertrait bounds. Normally, a ParamEnv is only ever
291-
// consutrcted once for a given type. As part of the construction process, the ParamEnv will
291+
// constructed once for a given type. As part of the construction process, the ParamEnv will
292292
// have any supertrait bounds normalized - e.g. if we have a type 'struct Foo<T: Copy>', the
293293
// ParamEnv will contain 'T: Copy' and 'T: Clone', since 'Copy: Clone'. When we construct our
294294
// own ParamEnv, we need to do this ourselves, through traits::elaborate_predicates, or else

src/librustc/traits/fulfill.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> {
4545
/// along. Once all type inference constraints have been generated, the
4646
/// method `select_all_or_error` can be used to report any remaining
4747
/// ambiguous cases as errors.
48-
4948
pub struct FulfillmentContext<'tcx> {
5049
// A list of all obligations that have been registered with this
5150
// fulfillment context.
@@ -89,7 +88,7 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
8988

9089
/// Attempts to select obligations using `selcx`.
9190
fn select(&mut self, selcx: &mut SelectionContext<'a, 'gcx, 'tcx>)
92-
-> Result<(),Vec<FulfillmentError<'tcx>>> {
91+
-> Result<(), Vec<FulfillmentError<'tcx>>> {
9392
debug!("select(obligation-forest-size={})", self.predicates.len());
9493

9594
let mut errors = Vec::new();

src/librustc/traits/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ pub fn normalize_param_env_or_error<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
644644
// have the errors get reported at a defined place (e.g.,
645645
// during typeck). Instead I have all parameter
646646
// environments, in effect, going through this function
647-
// and hence potentially reporting errors. This ensurse of
647+
// and hence potentially reporting errors. This ensures of
648648
// course that we never forget to normalize (the
649649
// alternative seemed like it would involve a lot of
650650
// manual invocations of this fn -- and then we'd have to

src/librustc/traits/select.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
582582
match self.confirm_candidate(obligation, candidate) {
583583
Err(SelectionError::Overflow) => {
584584
assert!(self.query_mode == TraitQueryMode::Canonical);
585-
return Err(SelectionError::Overflow);
585+
Err(SelectionError::Overflow)
586586
},
587587
Err(e) => Err(e),
588588
Ok(candidate) => Ok(Some(candidate))
@@ -879,7 +879,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
879879
// must be met of course). One obvious case this comes up is
880880
// marker traits like `Send`. Think of a linked list:
881881
//
882-
// struct List<T> { data: T, next: Option<Box<List<T>>> {
882+
// struct List<T> { data: T, next: Option<Box<List<T>>> }
883883
//
884884
// `Box<List<T>>` will be `Send` if `T` is `Send` and
885885
// `Option<Box<List<T>>>` is `Send`, and in turn
@@ -1407,7 +1407,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
14071407
stack: &TraitObligationStack<'o, 'tcx>)
14081408
-> Result<SelectionCandidateSet<'tcx>, SelectionError<'tcx>>
14091409
{
1410-
let TraitObligationStack { obligation, .. } = *stack;
1410+
let obligation = stack.obligation;
14111411
let ref obligation = Obligation {
14121412
param_env: obligation.param_env,
14131413
cause: obligation.cause.clone(),
@@ -1788,9 +1788,9 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
17881788
}
17891789

17901790
fn assemble_candidates_from_auto_impls(&mut self,
1791-
obligation: &TraitObligation<'tcx>,
1792-
candidates: &mut SelectionCandidateSet<'tcx>)
1793-
-> Result<(), SelectionError<'tcx>>
1791+
obligation: &TraitObligation<'tcx>,
1792+
candidates: &mut SelectionCandidateSet<'tcx>)
1793+
-> Result<(), SelectionError<'tcx>>
17941794
{
17951795
// OK to skip binder here because the tests we do below do not involve bound regions
17961796
let self_ty = *obligation.self_ty().skip_binder();
@@ -2433,7 +2433,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
24332433
fn confirm_candidate(&mut self,
24342434
obligation: &TraitObligation<'tcx>,
24352435
candidate: SelectionCandidate<'tcx>)
2436-
-> Result<Selection<'tcx>,SelectionError<'tcx>>
2436+
-> Result<Selection<'tcx>, SelectionError<'tcx>>
24372437
{
24382438
debug!("confirm_candidate({:?}, {:?})",
24392439
obligation,
@@ -2612,11 +2612,11 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
26122612
let mut obligations = self.collect_predicates_for_types(
26132613
obligation.param_env,
26142614
cause,
2615-
obligation.recursion_depth+1,
2615+
obligation.recursion_depth + 1,
26162616
trait_def_id,
26172617
nested);
26182618

2619-
let trait_obligations = self.in_snapshot(|this, snapshot| {
2619+
let trait_obligations: Vec<PredicateObligation<'_>> = self.in_snapshot(|this, snapshot| {
26202620
let poly_trait_ref = obligation.predicate.to_poly_trait_ref();
26212621
let (trait_ref, skol_map) =
26222622
this.infcx().skolemize_late_bound_regions(&poly_trait_ref);
@@ -2630,6 +2630,8 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
26302630
snapshot)
26312631
});
26322632

2633+
// Adds the predicates from the trait. Note that this contains a `Self: Trait`
2634+
// predicate as usual. It won't have any effect since auto traits are coinductive.
26332635
obligations.extend(trait_obligations);
26342636

26352637
debug!("vtable_auto_impl: obligations={:?}", obligations);

src/librustc_data_structures/obligation_forest/mod.rs

+73-20
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ pub enum ProcessResult<O, E> {
6565
Error(E),
6666
}
6767

68+
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
69+
struct ObligationTreeId(usize);
70+
71+
type ObligationTreeIdGenerator =
72+
::std::iter::Map<::std::ops::RangeFrom<usize>, fn(usize) -> ObligationTreeId>;
73+
6874
pub struct ObligationForest<O: ForestObligation> {
6975
/// The list of obligations. In between calls to
7076
/// `process_obligations`, this list only contains nodes in the
@@ -79,11 +85,25 @@ pub struct ObligationForest<O: ForestObligation> {
7985
/// at a higher index than its parent. This is needed by the
8086
/// backtrace iterator (which uses `split_at`).
8187
nodes: Vec<Node<O>>,
88+
8289
/// A cache of predicates that have been successfully completed.
8390
done_cache: FxHashSet<O::Predicate>,
91+
8492
/// An cache of the nodes in `nodes`, indexed by predicate.
8593
waiting_cache: FxHashMap<O::Predicate, NodeIndex>,
94+
8695
scratch: Option<Vec<usize>>,
96+
97+
obligation_tree_id_generator: ObligationTreeIdGenerator,
98+
99+
/// Per tree error cache. This is used to deduplicate errors,
100+
/// which is necessary to avoid trait resolution overflow in
101+
/// some cases.
102+
///
103+
/// See [this][details] for details.
104+
///
105+
/// [details]: https://github.com/rust-lang/rust/pull/53255#issuecomment-421184780
106+
error_cache: FxHashMap<ObligationTreeId, FxHashSet<O::Predicate>>,
87107
}
88108

89109
#[derive(Debug)]
@@ -99,6 +119,9 @@ struct Node<O> {
99119
/// Obligations that depend on this obligation for their
100120
/// completion. They must all be in a non-pending state.
101121
dependents: Vec<NodeIndex>,
122+
123+
/// Identifier of the obligation tree to which this node belongs.
124+
obligation_tree_id: ObligationTreeId,
102125
}
103126

104127
/// The state of one node in some tree within the forest. This
@@ -165,6 +188,8 @@ impl<O: ForestObligation> ObligationForest<O> {
165188
done_cache: FxHashSet(),
166189
waiting_cache: FxHashMap(),
167190
scratch: Some(vec![]),
191+
obligation_tree_id_generator: (0..).map(|i| ObligationTreeId(i)),
192+
error_cache: FxHashMap(),
168193
}
169194
}
170195

@@ -187,7 +212,7 @@ impl<O: ForestObligation> ObligationForest<O> {
187212
-> Result<(), ()>
188213
{
189214
if self.done_cache.contains(obligation.as_predicate()) {
190-
return Ok(())
215+
return Ok(());
191216
}
192217

193218
match self.waiting_cache.entry(obligation.as_predicate().clone()) {
@@ -214,9 +239,29 @@ impl<O: ForestObligation> ObligationForest<O> {
214239
Entry::Vacant(v) => {
215240
debug!("register_obligation_at({:?}, {:?}) - ok, new index is {}",
216241
obligation, parent, self.nodes.len());
217-
v.insert(NodeIndex::new(self.nodes.len()));
218-
self.nodes.push(Node::new(parent, obligation));
219-
Ok(())
242+
243+
let obligation_tree_id = match parent {
244+
Some(p) => {
245+
let parent_node = &self.nodes[p.get()];
246+
parent_node.obligation_tree_id
247+
}
248+
None => self.obligation_tree_id_generator.next().unwrap()
249+
};
250+
251+
let already_failed =
252+
parent.is_some()
253+
&& self.error_cache
254+
.get(&obligation_tree_id)
255+
.map(|errors| errors.contains(obligation.as_predicate()))
256+
.unwrap_or(false);
257+
258+
if already_failed {
259+
Err(())
260+
} else {
261+
v.insert(NodeIndex::new(self.nodes.len()));
262+
self.nodes.push(Node::new(parent, obligation, obligation_tree_id));
263+
Ok(())
264+
}
220265
}
221266
}
222267
}
@@ -251,6 +296,15 @@ impl<O: ForestObligation> ObligationForest<O> {
251296
.collect()
252297
}
253298

299+
fn insert_into_error_cache(&mut self, node_index: usize) {
300+
let node = &self.nodes[node_index];
301+
302+
self.error_cache
303+
.entry(node.obligation_tree_id)
304+
.or_insert_with(|| FxHashSet())
305+
.insert(node.obligation.as_predicate().clone());
306+
}
307+
254308
/// Perform a pass through the obligation list. This must
255309
/// be called in a loop until `outcome.stalled` is false.
256310
///
@@ -264,22 +318,15 @@ impl<O: ForestObligation> ObligationForest<O> {
264318
let mut stalled = true;
265319

266320
for index in 0..self.nodes.len() {
267-
debug!("process_obligations: node {} == {:?}",
268-
index,
269-
self.nodes[index]);
321+
debug!("process_obligations: node {} == {:?}", index, self.nodes[index]);
270322

271323
let result = match self.nodes[index] {
272-
Node { state: ref _state, ref mut obligation, .. }
273-
if _state.get() == NodeState::Pending =>
274-
{
275-
processor.process_obligation(obligation)
276-
}
324+
Node { ref state, ref mut obligation, .. } if state.get() == NodeState::Pending =>
325+
processor.process_obligation(obligation),
277326
_ => continue
278327
};
279328

280-
debug!("process_obligations: node {} got result {:?}",
281-
index,
282-
result);
329+
debug!("process_obligations: node {} got result {:?}", index, result);
283330

284331
match result {
285332
ProcessResult::Unchanged => {
@@ -420,13 +467,13 @@ impl<O: ForestObligation> ObligationForest<O> {
420467
}
421468

422469
while let Some(i) = error_stack.pop() {
423-
let node = &self.nodes[i];
424-
425-
match node.state.get() {
470+
match self.nodes[i].state.get() {
426471
NodeState::Error => continue,
427-
_ => node.state.set(NodeState::Error)
472+
_ => self.nodes[i].state.set(NodeState::Error),
428473
}
429474

475+
let node = &self.nodes[i];
476+
430477
error_stack.extend(
431478
node.parent.iter().chain(node.dependents.iter()).map(|x| x.get())
432479
);
@@ -514,6 +561,7 @@ impl<O: ForestObligation> ObligationForest<O> {
514561
self.waiting_cache.remove(self.nodes[i].obligation.as_predicate());
515562
node_rewrites[i] = nodes_len;
516563
dead_nodes += 1;
564+
self.insert_into_error_cache(i);
517565
}
518566
NodeState::OnDfsStack | NodeState::Success => unreachable!()
519567
}
@@ -587,12 +635,17 @@ impl<O: ForestObligation> ObligationForest<O> {
587635
}
588636

589637
impl<O> Node<O> {
590-
fn new(parent: Option<NodeIndex>, obligation: O) -> Node<O> {
638+
fn new(
639+
parent: Option<NodeIndex>,
640+
obligation: O,
641+
obligation_tree_id: ObligationTreeId
642+
) -> Node<O> {
591643
Node {
592644
obligation,
593645
state: Cell::new(NodeState::Pending),
594646
parent,
595647
dependents: vec![],
648+
obligation_tree_id,
596649
}
597650
}
598651
}

src/librustc_data_structures/obligation_forest/test.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ impl<OF, BF, O, E> ObligationProcessor for ClosureObligationProcessor<OF, BF, O,
5959

6060
fn process_backedge<'c, I>(&mut self, _cycle: I,
6161
_marker: PhantomData<&'c Self::Obligation>)
62-
where I: Clone + Iterator<Item=&'c Self::Obligation> {
63-
}
62+
where I: Clone + Iterator<Item=&'c Self::Obligation>
63+
{
64+
}
6465
}
6566

6667

@@ -350,11 +351,8 @@ fn done_dependency() {
350351
}, |_|{}));
351352
assert_eq!(ok, vec!["(A,B,C): Sized"]);
352353
assert_eq!(err.len(), 0);
353-
354-
355354
}
356355

357-
358356
#[test]
359357
fn orphan() {
360358
// check that orphaned nodes are handled correctly

src/test/ui/issue-40827.rs

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::rc::Rc;
12+
use std::sync::Arc;
13+
14+
struct Foo(Arc<Bar>);
15+
16+
enum Bar {
17+
A(Rc<Foo>),
18+
B(Option<Foo>),
19+
}
20+
21+
fn f<T: Send>(_: T) {}
22+
23+
fn main() {
24+
f(Foo(Arc::new(Bar::B(None))));
25+
//~^ ERROR E0277
26+
//~| ERROR E0277
27+
}

0 commit comments

Comments
 (0)