From 1f34e11d1b7708700e696e9950ac503be594c264 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 28 Oct 2022 23:27:28 +0400 Subject: [PATCH 01/14] Lift `T: Sized` bounds from some `strict_provenance` pointer methods --- library/core/src/ptr/const_ptr.rs | 24 ++++++------------------ library/core/src/ptr/mut_ptr.rs | 24 ++++++------------------ 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index ed16c5f051f..49831dbe44b 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -178,14 +178,11 @@ impl *const T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn addr(self) -> usize - where - T: Sized, - { + pub fn addr(self) -> usize { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. // SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the // provenance). - unsafe { mem::transmute(self) } + unsafe { mem::transmute(self.cast::<()>()) } } /// Gets the "address" portion of the pointer, and 'exposes' the "provenance" part for future @@ -215,12 +212,9 @@ impl *const T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn expose_addr(self) -> usize - where - T: Sized, - { + pub fn expose_addr(self) -> usize { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. - self as usize + self.cast::<()>() as usize } /// Creates a new pointer with the given address. @@ -238,10 +232,7 @@ impl *const T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn with_addr(self, addr: usize) -> Self - where - T: Sized, - { + pub fn with_addr(self, addr: usize) -> Self { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. // // In the mean-time, this operation is defined to be "as if" it was @@ -264,10 +255,7 @@ impl *const T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn map_addr(self, f: impl FnOnce(usize) -> usize) -> Self - where - T: Sized, - { + pub fn map_addr(self, f: impl FnOnce(usize) -> usize) -> Self { self.with_addr(f(self.addr())) } diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 6764002bcd4..578e27fec7a 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -188,14 +188,11 @@ impl *mut T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn addr(self) -> usize - where - T: Sized, - { + pub fn addr(self) -> usize { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. // SAFETY: Pointer-to-integer transmutes are valid (if you are okay with losing the // provenance). - unsafe { mem::transmute(self) } + unsafe { mem::transmute(self.cast::<()>()) } } /// Gets the "address" portion of the pointer, and 'exposes' the "provenance" part for future @@ -225,12 +222,9 @@ impl *mut T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn expose_addr(self) -> usize - where - T: Sized, - { + pub fn expose_addr(self) -> usize { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. - self as usize + self.cast::<()>() as usize } /// Creates a new pointer with the given address. @@ -248,10 +242,7 @@ impl *mut T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn with_addr(self, addr: usize) -> Self - where - T: Sized, - { + pub fn with_addr(self, addr: usize) -> Self { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. // // In the mean-time, this operation is defined to be "as if" it was @@ -274,10 +265,7 @@ impl *mut T { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn map_addr(self, f: impl FnOnce(usize) -> usize) -> Self - where - T: Sized, - { + pub fn map_addr(self, f: impl FnOnce(usize) -> usize) -> Self { self.with_addr(f(self.addr())) } From 662f1f20e4497cdd1459d15686706fe27d56d335 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 7 Nov 2022 11:58:58 +0000 Subject: [PATCH 02/14] Lift `T: Sized` bounds from some `strict_provenance` `NonNull` methods --- library/core/src/ptr/non_null.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index 7264d57ba6a..f728c0e2233 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -268,10 +268,7 @@ impl NonNull { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn addr(self) -> NonZeroUsize - where - T: Sized, - { + pub fn addr(self) -> NonZeroUsize { // SAFETY: The pointer is guaranteed by the type to be non-null, // meaning that the address will be non-zero. unsafe { NonZeroUsize::new_unchecked(self.pointer.addr()) } @@ -286,10 +283,7 @@ impl NonNull { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn with_addr(self, addr: NonZeroUsize) -> Self - where - T: Sized, - { + pub fn with_addr(self, addr: NonZeroUsize) -> Self { // SAFETY: The result of `ptr::from::with_addr` is non-null because `addr` is guaranteed to be non-zero. unsafe { NonNull::new_unchecked(self.pointer.with_addr(addr.get()) as *mut _) } } @@ -303,10 +297,7 @@ impl NonNull { #[must_use] #[inline] #[unstable(feature = "strict_provenance", issue = "95228")] - pub fn map_addr(self, f: impl FnOnce(NonZeroUsize) -> NonZeroUsize) -> Self - where - T: Sized, - { + pub fn map_addr(self, f: impl FnOnce(NonZeroUsize) -> NonZeroUsize) -> Self { self.with_addr(f(self.addr())) } From ee59533167706986ab6fe912a6fb246f7917022e Mon Sep 17 00:00:00 2001 From: mllken Date: Wed, 4 Jan 2023 12:47:43 +0700 Subject: [PATCH 03/14] relax reference requirement on from_abstract_name --- library/std/src/os/net/linux_ext/addr.rs | 2 +- library/std/src/os/unix/net/addr.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/os/net/linux_ext/addr.rs b/library/std/src/os/net/linux_ext/addr.rs index df3fc8e6a3b..85065984fbb 100644 --- a/library/std/src/os/net/linux_ext/addr.rs +++ b/library/std/src/os/net/linux_ext/addr.rs @@ -38,7 +38,7 @@ pub trait SocketAddrExt: Sealed { /// Ok(()) /// } /// ``` - fn from_abstract_name(name: &N) -> crate::io::Result + fn from_abstract_name(name: N) -> crate::io::Result where N: AsRef<[u8]>; diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index 81ac829d21b..ece2b33bddf 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -256,7 +256,7 @@ impl linux_ext::addr::SocketAddrExt for SocketAddr { if let AddressKind::Abstract(name) = self.address() { Some(name) } else { None } } - fn from_abstract_name(name: &N) -> crate::io::Result + fn from_abstract_name(name: N) -> crate::io::Result where N: AsRef<[u8]>, { From 47014b1bb968be08923a670b1ebfe4deb4256601 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Mon, 16 Jan 2023 21:39:36 +0100 Subject: [PATCH 04/14] Don't do pointer arithmetic on pointers to deallocated memory vec::Splice can invalidate the slice::Iter inside vec::Drain. So we replace them with dangling pointers which, unlike ones to deallocated memory, are allowed. --- library/alloc/src/vec/drain.rs | 6 +++--- library/alloc/src/vec/splice.rs | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/library/alloc/src/vec/drain.rs b/library/alloc/src/vec/drain.rs index 541f99bcfab..2b1a787cc54 100644 --- a/library/alloc/src/vec/drain.rs +++ b/library/alloc/src/vec/drain.rs @@ -223,9 +223,9 @@ impl Drop for Drain<'_, T, A> { } // as_slice() must only be called when iter.len() is > 0 because - // vec::Splice modifies vec::Drain fields and may grow the vec which would invalidate - // the iterator's internal pointers. Creating a reference to deallocated memory - // is invalid even when it is zero-length + // it also gets touched by vec::Splice which may turn it into a dangling pointer + // which would make it and the vec pointer point to different allocations which would + // lead to invalid pointer arithmetic below. let drop_ptr = iter.as_slice().as_ptr(); unsafe { diff --git a/library/alloc/src/vec/splice.rs b/library/alloc/src/vec/splice.rs index bad765c7f51..1861147fe72 100644 --- a/library/alloc/src/vec/splice.rs +++ b/library/alloc/src/vec/splice.rs @@ -54,6 +54,12 @@ impl ExactSizeIterator for Splice<'_, I, A> {} impl Drop for Splice<'_, I, A> { fn drop(&mut self) { self.drain.by_ref().for_each(drop); + // At this point draining is done and the only remaining tasks are splicing + // and moving things into the final place. + // Which means we can replace the slice::Iter with pointers that won't point to deallocated + // memory, so that Drain::drop is still allowed to call iter.len(), otherwise it would break + // the ptr.sub_ptr contract. + self.drain.iter = (&[]).iter(); unsafe { if self.drain.tail_len == 0 { From 2d54b7ceb2a5c078f019fd2eacf70d59ea37555b Mon Sep 17 00:00:00 2001 From: The 8472 Date: Tue, 17 Jan 2023 20:30:47 +0100 Subject: [PATCH 05/14] add miri regression test --- src/tools/miri/tests/pass/vec.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs index 3a6655e2ba6..30a28bc5803 100644 --- a/src/tools/miri/tests/pass/vec.rs +++ b/src/tools/miri/tests/pass/vec.rs @@ -162,6 +162,11 @@ fn reverse() { assert!(v[0].0 == 49); } +fn miri_issue_2759() { + let mut input = "1".to_string(); + input.replace_range(0..0, "0"); +} + fn main() { assert_eq!(vec_reallocate().len(), 5); @@ -191,4 +196,5 @@ fn main() { swap(); swap_remove(); reverse(); + miri_issue_2759(); } From 6d7e2135f15eeb9a586ef97bb6a7744e5a936777 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 17 Jan 2023 17:22:05 -0700 Subject: [PATCH 06/14] rustdoc: remove function `handleClick` that's only used once --- src/librustdoc/html/static/js/main.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index f52229d8095..4d604a21496 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -803,15 +803,10 @@ function loadCss(cssUrl) { } }); - function handleClick(id, f) { - const elem = document.getElementById(id); - if (elem) { - elem.addEventListener("click", f); - } + const mainElem = document.getElementById(MAIN_ID); + if (mainElem) { + mainElem.addEventListener("click", hideSidebar); } - handleClick(MAIN_ID, () => { - hideSidebar(); - }); onEachLazy(document.querySelectorAll("a[href^='#']"), el => { // For clicks on internal links ( tags with a hash property), we expand the section we're From 708b529f314cc5b126adc2c131dc0d0231db41dc Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 17 Jan 2023 17:54:58 -0700 Subject: [PATCH 07/14] rustdoc: stop using deprecated `window.event` when there's an `ev` param --- src/librustdoc/html/static/js/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 4d604a21496..9ceeeb5ae8f 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -940,7 +940,7 @@ function loadCss(cssUrl) { return; } if (!this.NOTABLE_FORCE_VISIBLE && - !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) { + !elemIsInParent(ev.relatedTarget, window.CURRENT_NOTABLE_ELEMENT)) { hideNotable(true); } }; From b738b0616093dbe6ce14bd640d44cf4252981d56 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 11 Jan 2023 13:39:02 +0100 Subject: [PATCH 08/14] update cache --- Cargo.lock | 1 + compiler/rustc_trait_selection/Cargo.toml | 1 + compiler/rustc_trait_selection/src/lib.rs | 1 + .../rustc_trait_selection/src/solve/cache.rs | 200 ++++++++---------- 4 files changed, 91 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39fba4fa731..b3afaaa35c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4783,6 +4783,7 @@ dependencies = [ "rustc_middle", "rustc_parse_format", "rustc_query_system", + "rustc_serialize", "rustc_session", "rustc_span", "rustc_target", diff --git a/compiler/rustc_trait_selection/Cargo.toml b/compiler/rustc_trait_selection/Cargo.toml index 67613e1a4eb..90d879976c2 100644 --- a/compiler/rustc_trait_selection/Cargo.toml +++ b/compiler/rustc_trait_selection/Cargo.toml @@ -19,6 +19,7 @@ rustc_infer = { path = "../rustc_infer" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } rustc_query_system = { path = "../rustc_query_system" } +rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index 081ac966c69..6fa09410363 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -21,6 +21,7 @@ #![feature(never_type)] #![feature(result_option_inspect)] #![feature(type_alias_impl_trait)] +#![feature(min_specialization)] #![recursion_limit = "512"] // For rustdoc #[macro_use] diff --git a/compiler/rustc_trait_selection/src/solve/cache.rs b/compiler/rustc_trait_selection/src/solve/cache.rs index f1ee73a5b85..9ac629980eb 100644 --- a/compiler/rustc_trait_selection/src/solve/cache.rs +++ b/compiler/rustc_trait_selection/src/solve/cache.rs @@ -11,22 +11,37 @@ use super::overflow::OverflowData; use super::{CanonicalGoal, Certainty, MaybeCause, Response}; use super::{EvalCtxt, QueryResult}; - use rustc_data_structures::fx::FxHashMap; +use rustc_index::vec::IndexVec; use rustc_infer::infer::canonical::{Canonical, CanonicalVarKind, CanonicalVarValues}; use rustc_middle::ty::{self, TyCtxt}; -use std::{cmp::Ordering, collections::hash_map::Entry}; +use std::collections::hash_map::Entry; + +rustc_index::newtype_index! { + pub struct StackDepth {} +} +rustc_index::newtype_index! { + pub struct EntryIndex {} +} #[derive(Debug, Clone)] struct ProvisionalEntry<'tcx> { // In case we have a coinductive cycle, this is the // the currently least restrictive result of this goal. response: QueryResult<'tcx>, - // The lowest element on the stack on which this result - // relies on. Starts out as just being the depth at which - // we've proven this obligation, but gets lowered to the - // depth of another goal if we rely on it in a cycle. - depth: usize, + // In case of a cycle, the depth of lowest stack entry involved + // in that cycle. This is monotonically decreasing in the stack as all + // elements between the current stack element in the lowest stack entry + // involved have to also be involved in that cycle. + // + // We can only move entries to the global cache once we're complete done + // with the cycle. If this entry has not been involved in a cycle, + // this is just its own depth. + depth: StackDepth, + + // The goal for this entry. Should always be equal to the corresponding goal + // in the lookup table. + goal: CanonicalGoal<'tcx>, } struct StackElem<'tcx> { @@ -34,61 +49,22 @@ struct StackElem<'tcx> { has_been_used: bool, } -/// The cache used for goals which are currently in progress or which depend -/// on in progress results. -/// -/// Once we're done with a goal we can store it in the global trait solver -/// cache of the `TyCtxt`. For goals which we're currently proving, or which -/// have only been proven via a coinductive cycle using a goal still on our stack -/// we have to use this separate data structure. -/// -/// The current data structure is not perfect, so there may still be room for -/// improvement here. We have the following requirements: -/// -/// ## Is there is a provisional entry for the given goal: -/// -/// ```ignore (for syntax highlighting) -/// self.entries.get(goal) -/// ``` -/// -/// ## Get all goals on the stack involved in a cycle: -/// -/// ```ignore (for syntax highlighting) -/// let entry = self.entries.get(goal).unwrap(); -/// let involved_goals = self.stack.iter().skip(entry.depth); -/// ``` -/// -/// ## Capping the depth of all entries -/// -/// Needed whenever we encounter a cycle. The current implementation always -/// iterates over all entries instead of only the ones with a larger depth. -/// Changing this may result in notable performance improvements. -/// -/// ```ignore (for syntax highlighting) -/// let cycle_depth = self.entries.get(goal).unwrap().depth; -/// for e in &mut self.entries { -/// e.depth = e.depth.min(cycle_depth); -/// } -/// ``` -/// -/// ## Checking whether we have to rerun the current goal -/// -/// A goal has to be rerun if its provisional result was used in a cycle -/// and that result is different from its final result. We update -/// [StackElem::has_been_used] for the deepest stack element involved in a cycle. -/// -/// ## Moving all finished goals into the global cache -/// -/// If `stack_elem.has_been_used` is true, iterate over all entries, moving the ones -/// with equal depth. If not, simply move this single entry. pub(super) struct ProvisionalCache<'tcx> { - stack: Vec>, - entries: FxHashMap, ProvisionalEntry<'tcx>>, + stack: IndexVec>, + entries: IndexVec>, + // FIXME: This is only used to quickly check whether a given goal + // is in the cache. We should experiment with using something like + // `SsoHashSet` here because in most cases there are only a few entries. + lookup_table: FxHashMap, EntryIndex>, } impl<'tcx> ProvisionalCache<'tcx> { pub(super) fn empty() -> ProvisionalCache<'tcx> { - ProvisionalCache { stack: Vec::new(), entries: Default::default() } + ProvisionalCache { + stack: Default::default(), + entries: Default::default(), + lookup_table: Default::default(), + } } pub(super) fn current_depth(&self) -> usize { @@ -108,18 +84,17 @@ impl<'tcx> EvalCtxt<'tcx> { // Look at the provisional cache to check for cycles. let cache = &mut self.provisional_cache; - match cache.entries.entry(goal) { + match cache.lookup_table.entry(goal) { // No entry, simply push this goal on the stack after dealing with overflow. Entry::Vacant(v) => { if self.overflow_data.has_overflow(cache.stack.len()) { return Err(self.deal_with_overflow(goal)); } - v.insert(ProvisionalEntry { - response: response_no_constraints(self.tcx, goal, Certainty::Yes), - depth: cache.stack.len(), - }); - cache.stack.push(StackElem { goal, has_been_used: false }); + let depth = cache.stack.push(StackElem { goal, has_been_used: false }); + let response = response_no_constraints(self.tcx, goal, Certainty::Yes); + let entry_index = cache.entries.push(ProvisionalEntry { response, depth, goal }); + v.insert(entry_index); Ok(()) } // We have a nested goal which relies on a goal `root` deeper in the stack. @@ -131,11 +106,12 @@ impl<'tcx> EvalCtxt<'tcx> { // // Finally we can return either the provisional response for that goal if we have a // coinductive cycle or an ambiguous result if the cycle is inductive. - Entry::Occupied(entry) => { - // FIXME: `ProvisionalEntry` should be `Copy`. - let entry = entry.get().clone(); + Entry::Occupied(entry_index) => { + let entry_index = *entry_index.get(); + // FIXME `ProvisionalEntry` should be `Copy`. + let entry = cache.entries.get(entry_index).unwrap().clone(); cache.stack[entry.depth].has_been_used = true; - for provisional_entry in cache.entries.values_mut() { + for provisional_entry in cache.entries.iter_mut().skip(entry_index.index()) { provisional_entry.depth = provisional_entry.depth.min(entry.depth); } @@ -143,9 +119,9 @@ impl<'tcx> EvalCtxt<'tcx> { // We can also depend on goals which aren't part of the stack but coinductively // depend on the stack themselves. We already checked whether all the goals // between these goals and their root on the stack. This means that as long as - // each goal in a cycle is checked for coinductivity by itself simply checking + // each goal in a cycle is checked for coinductivity by itself, simply checking // the stack is enough. - if cache.stack[entry.depth..] + if cache.stack.raw[entry.depth.index()..] .iter() .all(|g| g.goal.value.predicate.is_coinductive(self.tcx)) { @@ -154,7 +130,7 @@ impl<'tcx> EvalCtxt<'tcx> { Err(response_no_constraints( self.tcx, goal, - Certainty::Maybe(MaybeCause::Ambiguity), + Certainty::Maybe(MaybeCause::Overflow), )) } } @@ -182,49 +158,49 @@ impl<'tcx> EvalCtxt<'tcx> { let StackElem { goal, has_been_used } = cache.stack.pop().unwrap(); assert_eq!(goal, actual_goal); - let provisional_entry = cache.entries.get_mut(&goal).unwrap(); - // Check whether the current stack entry is the root of a cycle. - // - // If so, we either move all participants of that cycle to the global cache - // or, in case the provisional response used in the cycle is not equal to the - // final response, have to recompute the goal after updating the provisional - // response to the final response of this iteration. - if has_been_used { - if provisional_entry.response == response { - // We simply drop all entries according to an immutable condition, so - // query instability is not a concern here. - #[allow(rustc::potential_query_instability)] - cache.entries.retain(|goal, entry| match entry.depth.cmp(&cache.stack.len()) { - Ordering::Less => true, - Ordering::Equal => { - Self::try_move_finished_goal_to_global_cache( - self.tcx, - &mut self.overflow_data, - &cache.stack, - // FIXME: these should be `Copy` :( - goal.clone(), - entry.response.clone(), - ); - false - } - Ordering::Greater => bug!("entry with greater depth than the current leaf"), - }); + let provisional_entry_index = *cache.lookup_table.get(&goal).unwrap(); + let provisional_entry = &mut cache.entries[provisional_entry_index]; + // Was the current goal the root of a cycle and was the provisional response + // different from the final one. + if has_been_used && provisional_entry.response != response { + // If so, update the provisional reponse for this goal... + provisional_entry.response = response; + // ...remove all entries whose result depends on this goal + // from the provisional cache... + // + // That's not completely correct, as a nested goal can also + // depend on a goal which is lower in the stack so it doesn't + // actually depend on the current goal. This should be fairly + // rare and is hopefully not relevant for performance. + #[allow(rustc::potential_query_instability)] + cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index); + cache.entries.truncate(provisional_entry_index.index() + 1); - true - } else { - provisional_entry.response = response; - cache.stack.push(StackElem { goal, has_been_used: false }); - false - } + // ...and finally push our goal back on the stack and reevaluate it. + cache.stack.push(StackElem { goal, has_been_used: false }); + false } else { - Self::try_move_finished_goal_to_global_cache( - self.tcx, - &mut self.overflow_data, - &cache.stack, - goal, - response, - ); - cache.entries.remove(&goal); + // If not, we're done with this goal. + // + // Check whether that this goal doesn't depend on a goal deeper on the stack + // and if so, move it and all nested goals to the global cache. + // + // Note that if any nested goal were to depend on something deeper on the stack, + // this would have also updated the depth of this goal. + if provisional_entry.depth == cache.stack.next_index() { + for (i, entry) in cache.entries.drain_enumerated(provisional_entry_index.index()..) + { + let actual_index = cache.lookup_table.remove(&entry.goal); + debug_assert_eq!(Some(i), actual_index); + Self::try_move_finished_goal_to_global_cache( + self.tcx, + &mut self.overflow_data, + &cache.stack, + entry.goal, + entry.response, + ); + } + } true } } @@ -232,7 +208,7 @@ impl<'tcx> EvalCtxt<'tcx> { fn try_move_finished_goal_to_global_cache( tcx: TyCtxt<'tcx>, overflow_data: &mut OverflowData, - stack: &[StackElem<'tcx>], + stack: &IndexVec>, goal: CanonicalGoal<'tcx>, response: QueryResult<'tcx>, ) { From bf7dbff9210497ca3db0b19b5ca0c6daed47e64e Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 17 Jan 2023 10:21:30 +0100 Subject: [PATCH 09/14] instantiate canonical vars eagerly --- compiler/rustc_middle/src/ty/sty.rs | 11 - .../src/solve/assembly.rs | 78 ++--- .../rustc_trait_selection/src/solve/cache.rs | 267 ------------------ .../src/solve/fulfill.rs | 26 +- .../src/solve/infcx_ext.rs | 37 --- .../rustc_trait_selection/src/solve/mod.rs | 212 ++++++++------ .../src/solve/project_goals.rs | 36 +-- .../src/solve/search_graph/cache.rs | 115 ++++++++ .../src/solve/search_graph/mod.rs | 172 +++++++++++ .../src/solve/{ => search_graph}/overflow.rs | 29 +- .../src/solve/trait_goals.rs | 22 +- 11 files changed, 509 insertions(+), 496 deletions(-) delete mode 100644 compiler/rustc_trait_selection/src/solve/cache.rs create mode 100644 compiler/rustc_trait_selection/src/solve/search_graph/cache.rs create mode 100644 compiler/rustc_trait_selection/src/solve/search_graph/mod.rs rename compiler/rustc_trait_selection/src/solve/{ => search_graph}/overflow.rs (74%) diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 3f8252aefdc..4166f79c96c 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1106,17 +1106,6 @@ impl<'tcx, T> Binder<'tcx, T> { if self.0.has_escaping_bound_vars() { None } else { Some(self.skip_binder()) } } - pub fn no_bound_vars_ignoring_escaping(self, tcx: TyCtxt<'tcx>) -> Option - where - T: TypeFoldable<'tcx>, - { - if !self.0.has_escaping_bound_vars() { - Some(self.skip_binder()) - } else { - self.0.try_fold_with(&mut SkipBindersAt { index: ty::INNERMOST, tcx }).ok() - } - } - /// Splits the contents into two things that share the same binder /// level as the original, returning two distinct binders. /// diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs index ba68da0686f..da6bb844a0f 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly.rs @@ -1,20 +1,11 @@ //! Code shared by trait and projection goals for candidate assembly. use super::infcx_ext::InferCtxtExt; -use super::{ - instantiate_canonical_query_response, CanonicalGoal, CanonicalResponse, Certainty, EvalCtxt, - Goal, -}; +use super::{CanonicalResponse, Certainty, EvalCtxt, Goal}; use rustc_hir::def_id::DefId; -use rustc_infer::infer::TyCtxtInferExt; -use rustc_infer::infer::{ - canonical::{CanonicalVarValues, OriginalQueryValues}, - InferCtxt, -}; use rustc_infer::traits::query::NoSolution; use rustc_middle::ty::TypeFoldable; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::DUMMY_SP; use std::fmt::Debug; /// A candidate is a possible way to prove a goal. @@ -40,7 +31,7 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy { fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId; fn consider_impl_candidate( - acx: &mut AssemblyCtxt<'_, 'tcx, Self>, + acx: &mut AssemblyCtxt<'_, '_, 'tcx, Self>, goal: Goal<'tcx, Self>, impl_def_id: DefId, ); @@ -49,21 +40,17 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy { /// An abstraction which correctly deals with the canonical results for candidates. /// /// It also deduplicates the behavior between trait and projection predicates. -pub(super) struct AssemblyCtxt<'a, 'tcx, G: GoalKind<'tcx>> { - pub(super) cx: &'a mut EvalCtxt<'tcx>, - pub(super) infcx: &'a InferCtxt<'tcx>, - var_values: CanonicalVarValues<'tcx>, +pub(super) struct AssemblyCtxt<'a, 'b, 'tcx, G: GoalKind<'tcx>> { + pub(super) cx: &'a mut EvalCtxt<'b, 'tcx>, candidates: Vec>, } -impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> { +impl<'a, 'b, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'b, 'tcx, G> { pub(super) fn assemble_and_evaluate_candidates( - cx: &'a mut EvalCtxt<'tcx>, - goal: CanonicalGoal<'tcx, G>, + cx: &'a mut EvalCtxt<'b, 'tcx>, + goal: Goal<'tcx, G>, ) -> Vec> { - let (ref infcx, goal, var_values) = - cx.tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &goal); - let mut acx = AssemblyCtxt { cx, infcx, var_values, candidates: Vec::new() }; + let mut acx = AssemblyCtxt { cx, candidates: Vec::new() }; acx.assemble_candidates_after_normalizing_self_ty(goal); @@ -77,7 +64,7 @@ impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> { source: G::CandidateSource, certainty: Certainty, ) { - match self.infcx.make_canonical_response(self.var_values.clone(), certainty) { + match self.cx.make_canonical_response(certainty) { Ok(result) => self.candidates.push(Candidate { source, result }), Err(NoSolution) => debug!(?source, ?certainty, "failed leakcheck"), } @@ -89,13 +76,14 @@ impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> { /// self type to the list of candidates in case that succeeds. Note that we can't just eagerly return in /// this case as projections as self types add ` fn assemble_candidates_after_normalizing_self_ty(&mut self, goal: Goal<'tcx, G>) { - let tcx = self.cx.tcx; + let tcx = self.cx.tcx(); + let infcx = self.cx.infcx; // FIXME: We also have to normalize opaque types, not sure where to best fit that in. let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else { return }; - self.infcx.probe(|_| { - let normalized_ty = self.infcx.next_ty_infer(); + infcx.probe(|_| { + let normalized_ty = infcx.next_ty_infer(); let normalizes_to_goal = goal.with( tcx, ty::Binder::dummy(ty::ProjectionPredicate { @@ -103,43 +91,31 @@ impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> { term: normalized_ty.into(), }), ); - let normalization_certainty = - match self.cx.evaluate_goal(&self.infcx, normalizes_to_goal) { - Ok((_, certainty)) => certainty, - Err(NoSolution) => return, - }; + let normalization_certainty = match self.cx.evaluate_goal(normalizes_to_goal) { + Ok((_, certainty)) => certainty, + Err(NoSolution) => return, + }; // NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate. // This doesn't work as long as we use `CandidateSource` in both winnowing and to resolve associated items. let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty)); - let mut orig_values = OriginalQueryValues::default(); - let goal = self.infcx.canonicalize_query(goal, &mut orig_values); let normalized_candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self.cx, goal); - - // Map each candidate from being canonical wrt the current inference context to being - // canonical wrt the caller. - for Candidate { source, result } in normalized_candidates { - self.infcx.probe(|_| { - let candidate_certainty = - instantiate_canonical_query_response(&self.infcx, &orig_values, result); - - // FIXME: This is a bit scary if the `normalizes_to_goal` overflows. - // - // If we have an ambiguous candidate it hides that normalization - // caused an overflow which may cause issues. - self.try_insert_candidate( - source, - normalization_certainty.unify_and(candidate_certainty), - ) - }) + for mut normalized_candidate in normalized_candidates { + normalized_candidate.result = + normalized_candidate.result.unchecked_map(|mut response| { + response.certainty = response.certainty.unify_and(normalization_certainty); + response + }); + self.candidates.push(normalized_candidate); } }) } fn assemble_impl_candidates(&mut self, goal: Goal<'tcx, G>) { - self.cx.tcx.for_each_relevant_impl( - goal.predicate.trait_def_id(self.cx.tcx), + let tcx = self.cx.tcx(); + tcx.for_each_relevant_impl( + goal.predicate.trait_def_id(tcx), goal.predicate.self_ty(), |impl_def_id| G::consider_impl_candidate(self, goal, impl_def_id), ); diff --git a/compiler/rustc_trait_selection/src/solve/cache.rs b/compiler/rustc_trait_selection/src/solve/cache.rs deleted file mode 100644 index 9ac629980eb..00000000000 --- a/compiler/rustc_trait_selection/src/solve/cache.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! This module both handles the global cache which stores "finished" goals, -//! and the provisional cache which contains partially computed goals. -//! -//! The provisional cache is necessary when dealing with coinductive cycles. -//! -//! For more information about the provisional cache and coinduction in general, -//! check out the relevant section of the rustc-dev-guide. -//! -//! FIXME(@lcnr): Write that section, feel free to ping me if you need help here -//! before then or if I still haven't done that before January 2023. -use super::overflow::OverflowData; -use super::{CanonicalGoal, Certainty, MaybeCause, Response}; -use super::{EvalCtxt, QueryResult}; -use rustc_data_structures::fx::FxHashMap; -use rustc_index::vec::IndexVec; -use rustc_infer::infer::canonical::{Canonical, CanonicalVarKind, CanonicalVarValues}; -use rustc_middle::ty::{self, TyCtxt}; -use std::collections::hash_map::Entry; - -rustc_index::newtype_index! { - pub struct StackDepth {} -} -rustc_index::newtype_index! { - pub struct EntryIndex {} -} - -#[derive(Debug, Clone)] -struct ProvisionalEntry<'tcx> { - // In case we have a coinductive cycle, this is the - // the currently least restrictive result of this goal. - response: QueryResult<'tcx>, - // In case of a cycle, the depth of lowest stack entry involved - // in that cycle. This is monotonically decreasing in the stack as all - // elements between the current stack element in the lowest stack entry - // involved have to also be involved in that cycle. - // - // We can only move entries to the global cache once we're complete done - // with the cycle. If this entry has not been involved in a cycle, - // this is just its own depth. - depth: StackDepth, - - // The goal for this entry. Should always be equal to the corresponding goal - // in the lookup table. - goal: CanonicalGoal<'tcx>, -} - -struct StackElem<'tcx> { - goal: CanonicalGoal<'tcx>, - has_been_used: bool, -} - -pub(super) struct ProvisionalCache<'tcx> { - stack: IndexVec>, - entries: IndexVec>, - // FIXME: This is only used to quickly check whether a given goal - // is in the cache. We should experiment with using something like - // `SsoHashSet` here because in most cases there are only a few entries. - lookup_table: FxHashMap, EntryIndex>, -} - -impl<'tcx> ProvisionalCache<'tcx> { - pub(super) fn empty() -> ProvisionalCache<'tcx> { - ProvisionalCache { - stack: Default::default(), - entries: Default::default(), - lookup_table: Default::default(), - } - } - - pub(super) fn current_depth(&self) -> usize { - self.stack.len() - } -} - -impl<'tcx> EvalCtxt<'tcx> { - /// Tries putting the new goal on the stack, returning an error if it is already cached. - /// - /// This correctly updates the provisional cache if there is a cycle. - pub(super) fn try_push_stack( - &mut self, - goal: CanonicalGoal<'tcx>, - ) -> Result<(), QueryResult<'tcx>> { - // FIXME: start by checking the global cache - - // Look at the provisional cache to check for cycles. - let cache = &mut self.provisional_cache; - match cache.lookup_table.entry(goal) { - // No entry, simply push this goal on the stack after dealing with overflow. - Entry::Vacant(v) => { - if self.overflow_data.has_overflow(cache.stack.len()) { - return Err(self.deal_with_overflow(goal)); - } - - let depth = cache.stack.push(StackElem { goal, has_been_used: false }); - let response = response_no_constraints(self.tcx, goal, Certainty::Yes); - let entry_index = cache.entries.push(ProvisionalEntry { response, depth, goal }); - v.insert(entry_index); - Ok(()) - } - // We have a nested goal which relies on a goal `root` deeper in the stack. - // - // We first store that we may have to rerun `evaluate_goal` for `root` in case the - // provisional response is not equal to the final response. We also update the depth - // of all goals which recursively depend on our current goal to depend on `root` - // instead. - // - // Finally we can return either the provisional response for that goal if we have a - // coinductive cycle or an ambiguous result if the cycle is inductive. - Entry::Occupied(entry_index) => { - let entry_index = *entry_index.get(); - // FIXME `ProvisionalEntry` should be `Copy`. - let entry = cache.entries.get(entry_index).unwrap().clone(); - cache.stack[entry.depth].has_been_used = true; - for provisional_entry in cache.entries.iter_mut().skip(entry_index.index()) { - provisional_entry.depth = provisional_entry.depth.min(entry.depth); - } - - // NOTE: The goals on the stack aren't the only goals involved in this cycle. - // We can also depend on goals which aren't part of the stack but coinductively - // depend on the stack themselves. We already checked whether all the goals - // between these goals and their root on the stack. This means that as long as - // each goal in a cycle is checked for coinductivity by itself, simply checking - // the stack is enough. - if cache.stack.raw[entry.depth.index()..] - .iter() - .all(|g| g.goal.value.predicate.is_coinductive(self.tcx)) - { - Err(entry.response) - } else { - Err(response_no_constraints( - self.tcx, - goal, - Certainty::Maybe(MaybeCause::Overflow), - )) - } - } - } - } - - /// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with - /// coinductive cycles. - /// - /// When we encounter a coinductive cycle, we have to prove the final result of that cycle - /// while we are still computing that result. Because of this we continously recompute the - /// cycle until the result of the previous iteration is equal to the final result, at which - /// point we are done. - /// - /// This function returns `true` if we were able to finalize the goal and `false` if it has - /// updated the provisional cache and we have to recompute the current goal. - /// - /// FIXME: Refer to the rustc-dev-guide entry once it exists. - pub(super) fn try_finalize_goal( - &mut self, - actual_goal: CanonicalGoal<'tcx>, - response: QueryResult<'tcx>, - ) -> bool { - let cache = &mut self.provisional_cache; - let StackElem { goal, has_been_used } = cache.stack.pop().unwrap(); - assert_eq!(goal, actual_goal); - - let provisional_entry_index = *cache.lookup_table.get(&goal).unwrap(); - let provisional_entry = &mut cache.entries[provisional_entry_index]; - // Was the current goal the root of a cycle and was the provisional response - // different from the final one. - if has_been_used && provisional_entry.response != response { - // If so, update the provisional reponse for this goal... - provisional_entry.response = response; - // ...remove all entries whose result depends on this goal - // from the provisional cache... - // - // That's not completely correct, as a nested goal can also - // depend on a goal which is lower in the stack so it doesn't - // actually depend on the current goal. This should be fairly - // rare and is hopefully not relevant for performance. - #[allow(rustc::potential_query_instability)] - cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index); - cache.entries.truncate(provisional_entry_index.index() + 1); - - // ...and finally push our goal back on the stack and reevaluate it. - cache.stack.push(StackElem { goal, has_been_used: false }); - false - } else { - // If not, we're done with this goal. - // - // Check whether that this goal doesn't depend on a goal deeper on the stack - // and if so, move it and all nested goals to the global cache. - // - // Note that if any nested goal were to depend on something deeper on the stack, - // this would have also updated the depth of this goal. - if provisional_entry.depth == cache.stack.next_index() { - for (i, entry) in cache.entries.drain_enumerated(provisional_entry_index.index()..) - { - let actual_index = cache.lookup_table.remove(&entry.goal); - debug_assert_eq!(Some(i), actual_index); - Self::try_move_finished_goal_to_global_cache( - self.tcx, - &mut self.overflow_data, - &cache.stack, - entry.goal, - entry.response, - ); - } - } - true - } - } - - fn try_move_finished_goal_to_global_cache( - tcx: TyCtxt<'tcx>, - overflow_data: &mut OverflowData, - stack: &IndexVec>, - goal: CanonicalGoal<'tcx>, - response: QueryResult<'tcx>, - ) { - // We move goals to the global cache if we either did not hit an overflow or if it's - // the root goal as that will now always hit the same overflow limit. - // - // NOTE: We cannot move any non-root goals to the global cache even if their final result - // isn't impacted by the overflow as that goal still has unstable query dependencies - // because it didn't go its full depth. - // - // FIXME(@lcnr): We could still cache subtrees which are not impacted by overflow though. - // Tracking that info correctly isn't trivial, so I haven't implemented it for now. - let should_cache_globally = !overflow_data.did_overflow() || stack.is_empty(); - if should_cache_globally { - // FIXME: move the provisional entry to the global cache. - let _ = (tcx, goal, response); - } - } -} - -pub(super) fn response_no_constraints<'tcx>( - tcx: TyCtxt<'tcx>, - goal: Canonical<'tcx, impl Sized>, - certainty: Certainty, -) -> QueryResult<'tcx> { - let var_values = goal - .variables - .iter() - .enumerate() - .map(|(i, info)| match info.kind { - CanonicalVarKind::Ty(_) | CanonicalVarKind::PlaceholderTy(_) => { - tcx.mk_ty(ty::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i).into())).into() - } - CanonicalVarKind::Region(_) | CanonicalVarKind::PlaceholderRegion(_) => { - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(i), - kind: ty::BrAnon(i as u32, None), - }; - tcx.mk_region(ty::ReLateBound(ty::INNERMOST, br)).into() - } - CanonicalVarKind::Const(_, ty) | CanonicalVarKind::PlaceholderConst(_, ty) => tcx - .mk_const(ty::ConstKind::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i)), ty) - .into(), - }) - .collect(); - - Ok(Canonical { - max_universe: goal.max_universe, - variables: goal.variables, - value: Response { - var_values: CanonicalVarValues { var_values }, - external_constraints: Default::default(), - certainty, - }, - }) -} diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index dfc2b5ed329..3146f468f7d 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -2,7 +2,7 @@ use std::mem; use rustc_data_structures::fx::FxHashMap; use rustc_infer::{ - infer::InferCtxt, + infer::{canonical::OriginalQueryValues, InferCtxt}, traits::{ query::NoSolution, FulfillmentError, FulfillmentErrorCode, PredicateObligation, SelectionError, TraitEngine, @@ -67,10 +67,26 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { let mut has_changed = false; for obligation in mem::take(&mut self.obligations) { - let mut cx = EvalCtxt::new(infcx.tcx); - let (changed, certainty) = match cx.evaluate_goal(infcx, obligation.clone().into()) - { - Ok(result) => result, + let goal = obligation.clone().into(); + + // FIXME: Add a better API for that '^^ + let mut orig_values = OriginalQueryValues::default(); + let canonical_goal = infcx.canonicalize_query(goal, &mut orig_values); + let (changed, certainty) = match EvalCtxt::evaluate_canonical_goal( + infcx.tcx, + &mut super::search_graph::SearchGraph::new(infcx.tcx), + canonical_goal, + ) { + Ok(canonical_response) => { + ( + true, // FIXME: check whether `var_values` are an identity substitution. + super::instantiate_canonical_query_response( + infcx, + &orig_values, + canonical_response, + ), + ) + } Err(NoSolution) => { errors.push(FulfillmentError { obligation: obligation.clone(), diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs index 436f4eea662..8a8c3091d54 100644 --- a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs +++ b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs @@ -1,23 +1,11 @@ -use rustc_infer::infer::canonical::CanonicalVarValues; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::InferCtxt; -use rustc_infer::traits::query::NoSolution; use rustc_middle::ty::Ty; use rustc_span::DUMMY_SP; -use crate::solve::ExternalConstraints; - -use super::{Certainty, QueryResult, Response}; - /// Methods used inside of the canonical queries of the solver. pub(super) trait InferCtxtExt<'tcx> { fn next_ty_infer(&self) -> Ty<'tcx>; - - fn make_canonical_response( - &self, - var_values: CanonicalVarValues<'tcx>, - certainty: Certainty, - ) -> QueryResult<'tcx>; } impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { @@ -27,29 +15,4 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { span: DUMMY_SP, }) } - - fn make_canonical_response( - &self, - var_values: CanonicalVarValues<'tcx>, - certainty: Certainty, - ) -> QueryResult<'tcx> { - let external_constraints = take_external_constraints(self)?; - - Ok(self.canonicalize_response(Response { var_values, external_constraints, certainty })) - } -} - -#[instrument(level = "debug", skip(infcx), ret)] -fn take_external_constraints<'tcx>( - infcx: &InferCtxt<'tcx>, -) -> Result, NoSolution> { - let region_obligations = infcx.take_registered_region_obligations(); - let opaque_types = infcx.take_opaque_types_for_query_response(); - Ok(ExternalConstraints { - // FIXME: Now that's definitely wrong :) - // - // Should also do the leak check here I think - regions: drop(region_obligations), - opaque_types, - }) } diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index 80775b7aaf2..0e629664b2b 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -19,27 +19,23 @@ use std::mem; +use rustc_infer::infer::canonical::{Canonical, CanonicalVarKind, CanonicalVarValues}; use rustc_infer::infer::canonical::{OriginalQueryValues, QueryRegionConstraints, QueryResponse}; use rustc_infer::infer::{InferCtxt, InferOk, TyCtxtInferExt}; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::Obligation; use rustc_middle::infer::canonical::Certainty as OldCertainty; -use rustc_middle::infer::canonical::{Canonical, CanonicalVarValues}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::ty::{RegionOutlivesPredicate, ToPredicate, TypeOutlivesPredicate}; use rustc_span::DUMMY_SP; use crate::traits::ObligationCause; -use self::cache::response_no_constraints; -use self::infcx_ext::InferCtxtExt; - mod assembly; -mod cache; mod fulfill; mod infcx_ext; -mod overflow; mod project_goals; +mod search_graph; mod trait_goals; pub use fulfill::FulfillmentCtxt; @@ -146,45 +142,25 @@ pub trait TyCtxtExt<'tcx> { impl<'tcx> TyCtxtExt<'tcx> for TyCtxt<'tcx> { fn evaluate_goal(self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> { - let mut cx = EvalCtxt::new(self); - cx.evaluate_canonical_goal(goal) + let mut search_graph = search_graph::SearchGraph::new(self); + EvalCtxt::evaluate_canonical_goal(self, &mut search_graph, goal) } } -struct EvalCtxt<'tcx> { - tcx: TyCtxt<'tcx>, +struct EvalCtxt<'a, 'tcx> { + infcx: &'a InferCtxt<'tcx>, + var_values: CanonicalVarValues<'tcx>, - provisional_cache: cache::ProvisionalCache<'tcx>, - overflow_data: overflow::OverflowData, + search_graph: &'a mut search_graph::SearchGraph<'tcx>, } -impl<'tcx> EvalCtxt<'tcx> { - fn new(tcx: TyCtxt<'tcx>) -> EvalCtxt<'tcx> { - EvalCtxt { - tcx, - provisional_cache: cache::ProvisionalCache::empty(), - overflow_data: overflow::OverflowData::new(tcx), - } - } - - /// Recursively evaluates `goal`, returning whether any inference vars have - /// been constrained and the certainty of the result. - fn evaluate_goal( - &mut self, - infcx: &InferCtxt<'tcx>, - goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> Result<(bool, Certainty), NoSolution> { - let mut orig_values = OriginalQueryValues::default(); - let canonical_goal = infcx.canonicalize_query(goal, &mut orig_values); - let canonical_response = self.evaluate_canonical_goal(canonical_goal)?; - Ok(( - !canonical_response.value.var_values.is_identity(), - instantiate_canonical_query_response(infcx, &orig_values, canonical_response), - )) - } - - fn evaluate_canonical_goal(&mut self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> { - match self.try_push_stack(goal) { +impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { + fn evaluate_canonical_goal( + tcx: TyCtxt<'tcx>, + search_graph: &'a mut search_graph::SearchGraph<'tcx>, + canonical_goal: CanonicalGoal<'tcx>, + ) -> QueryResult<'tcx> { + match search_graph.try_push_stack(tcx, canonical_goal) { Ok(()) => {} // Our goal is already on the stack, eager return. Err(response) => return response, @@ -195,41 +171,65 @@ impl<'tcx> EvalCtxt<'tcx> { // // FIXME: Similar to `evaluate_all`, this has to check for overflow. loop { - let result = self.compute_goal(goal); + let (ref infcx, goal, var_values) = + tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal); + let mut ecx = EvalCtxt { infcx, var_values, search_graph }; + let result = ecx.compute_goal(goal); // FIXME: `Response` should be `Copy` - if self.try_finalize_goal(goal, result.clone()) { + if search_graph.try_finalize_goal(tcx, canonical_goal, result.clone()) { return result; } } } - fn compute_goal(&mut self, canonical_goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> { - // WARNING: We're looking at a canonical value without instantiating it here. - // - // We have to be incredibly careful to not change the order of bound variables or - // remove any. As we go from `Goal<'tcx, Predicate>` to `Goal` with the variants - // of `PredicateKind` this is the case and it is and faster than instantiating and - // recanonicalizing. - let Goal { param_env, predicate } = canonical_goal.value; + fn tcx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } - if let Some(kind) = predicate.kind().no_bound_vars_ignoring_escaping(self.tcx) { + fn make_canonical_response(&self, certainty: Certainty) -> QueryResult<'tcx> { + let external_constraints = take_external_constraints(self.infcx)?; + + Ok(self.infcx.canonicalize_response(Response { + var_values: self.var_values.clone(), + external_constraints, + certainty, + })) + } + + /// Recursively evaluates `goal`, returning whether any inference vars have + /// been constrained and the certainty of the result. + fn evaluate_goal( + &mut self, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + ) -> Result<(bool, Certainty), NoSolution> { + let mut orig_values = OriginalQueryValues::default(); + let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values); + let canonical_response = + EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?; + Ok(( + !canonical_response.value.var_values.is_identity(), + instantiate_canonical_query_response(self.infcx, &orig_values, canonical_response), + )) + } + + fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> { + let Goal { param_env, predicate } = goal; + let kind = predicate.kind(); + if let Some(kind) = kind.no_bound_vars() { match kind { - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => self.compute_trait_goal( - canonical_goal.unchecked_rebind(Goal { param_env, predicate }), - ), - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => self - .compute_projection_goal( - canonical_goal.unchecked_rebind(Goal { param_env, predicate }), - ), - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => self - .compute_type_outlives_goal( - canonical_goal.unchecked_rebind(Goal { param_env, predicate }), - ), - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => self - .compute_region_outlives_goal( - canonical_goal.unchecked_rebind(Goal { param_env, predicate }), - ), + ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => { + self.compute_trait_goal(Goal { param_env, predicate }) + } + ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => { + self.compute_projection_goal(Goal { param_env, predicate }) + } + ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => { + self.compute_type_outlives_goal(Goal { param_env, predicate }) + } + ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => { + self.compute_region_outlives_goal(Goal { param_env, predicate }) + } // FIXME: implement these predicates :) ty::PredicateKind::WellFormed(_) | ty::PredicateKind::ObjectSafe(_) @@ -239,49 +239,41 @@ impl<'tcx> EvalCtxt<'tcx> { | ty::PredicateKind::ConstEvaluatable(_) | ty::PredicateKind::ConstEquate(_, _) | ty::PredicateKind::TypeWellFormedFromEnv(_) - | ty::PredicateKind::Ambiguous => { - // FIXME - response_no_constraints(self.tcx, canonical_goal, Certainty::Yes) - } + | ty::PredicateKind::Ambiguous => self.make_canonical_response(Certainty::Yes), } } else { - let (infcx, goal, var_values) = - self.tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal); - let kind = infcx.replace_bound_vars_with_placeholders(goal.predicate.kind()); - let goal = goal.with(self.tcx, ty::Binder::dummy(kind)); - let (_, certainty) = self.evaluate_goal(&infcx, goal)?; - infcx.make_canonical_response(var_values, certainty) + let kind = self.infcx.replace_bound_vars_with_placeholders(kind); + let goal = goal.with(self.tcx(), ty::Binder::dummy(kind)); + let (_, certainty) = self.evaluate_goal(goal)?; + self.make_canonical_response(certainty) } } fn compute_type_outlives_goal( &mut self, - goal: CanonicalGoal<'tcx, TypeOutlivesPredicate<'tcx>>, + _goal: Goal<'tcx, TypeOutlivesPredicate<'tcx>>, ) -> QueryResult<'tcx> { - // FIXME - response_no_constraints(self.tcx, goal, Certainty::Yes) + self.make_canonical_response(Certainty::Yes) } fn compute_region_outlives_goal( &mut self, - goal: CanonicalGoal<'tcx, RegionOutlivesPredicate<'tcx>>, + _goal: Goal<'tcx, RegionOutlivesPredicate<'tcx>>, ) -> QueryResult<'tcx> { - // FIXME - response_no_constraints(self.tcx, goal, Certainty::Yes) + self.make_canonical_response(Certainty::Yes) } } -impl<'tcx> EvalCtxt<'tcx> { +impl<'tcx> EvalCtxt<'_, 'tcx> { fn evaluate_all( &mut self, - infcx: &InferCtxt<'tcx>, mut goals: Vec>>, ) -> Result { let mut new_goals = Vec::new(); self.repeat_while_none(|this| { let mut has_changed = Err(Certainty::Yes); for goal in goals.drain(..) { - let (changed, certainty) = match this.evaluate_goal(infcx, goal) { + let (changed, certainty) = match this.evaluate_goal(goal) { Ok(result) => result, Err(NoSolution) => return Some(Err(NoSolution)), }; @@ -310,6 +302,21 @@ impl<'tcx> EvalCtxt<'tcx> { } } +#[instrument(level = "debug", skip(infcx), ret)] +fn take_external_constraints<'tcx>( + infcx: &InferCtxt<'tcx>, +) -> Result, NoSolution> { + let region_obligations = infcx.take_registered_region_obligations(); + let opaque_types = infcx.take_opaque_types_for_query_response(); + Ok(ExternalConstraints { + // FIXME: Now that's definitely wrong :) + // + // Should also do the leak check here I think + regions: drop(region_obligations), + opaque_types, + }) +} + fn instantiate_canonical_query_response<'tcx>( infcx: &InferCtxt<'tcx>, original_values: &OriginalQueryValues<'tcx>, @@ -334,3 +341,40 @@ fn instantiate_canonical_query_response<'tcx>( assert!(obligations.is_empty()); value } + +pub(super) fn response_no_constraints<'tcx>( + tcx: TyCtxt<'tcx>, + goal: Canonical<'tcx, impl Sized>, + certainty: Certainty, +) -> QueryResult<'tcx> { + let var_values = goal + .variables + .iter() + .enumerate() + .map(|(i, info)| match info.kind { + CanonicalVarKind::Ty(_) | CanonicalVarKind::PlaceholderTy(_) => { + tcx.mk_ty(ty::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i).into())).into() + } + CanonicalVarKind::Region(_) | CanonicalVarKind::PlaceholderRegion(_) => { + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(i), + kind: ty::BrAnon(i as u32, None), + }; + tcx.mk_region(ty::ReLateBound(ty::INNERMOST, br)).into() + } + CanonicalVarKind::Const(_, ty) | CanonicalVarKind::PlaceholderConst(_, ty) => tcx + .mk_const(ty::ConstKind::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i)), ty) + .into(), + }) + .collect(); + + Ok(Canonical { + max_universe: goal.max_universe, + variables: goal.variables, + value: Response { + var_values: CanonicalVarValues { var_values }, + external_constraints: Default::default(), + certainty, + }, + }) +} diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index d2f2e78f555..1120dfb8098 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -1,7 +1,7 @@ use crate::traits::{specialization_graph, translate_substs}; use super::assembly::{self, AssemblyCtxt}; -use super::{CanonicalGoal, EvalCtxt, Goal, QueryResult}; +use super::{EvalCtxt, Goal, QueryResult}; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -26,10 +26,10 @@ pub(super) enum CandidateSource { type Candidate<'tcx> = assembly::Candidate<'tcx, ProjectionPredicate<'tcx>>; -impl<'tcx> EvalCtxt<'tcx> { +impl<'tcx> EvalCtxt<'_, 'tcx> { pub(super) fn compute_projection_goal( &mut self, - goal: CanonicalGoal<'tcx, ProjectionPredicate<'tcx>>, + goal: Goal<'tcx, ProjectionPredicate<'tcx>>, ) -> QueryResult<'tcx> { let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal); self.merge_project_candidates(candidates) @@ -104,11 +104,13 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { } fn consider_impl_candidate( - acx: &mut AssemblyCtxt<'_, 'tcx, ProjectionPredicate<'tcx>>, + acx: &mut AssemblyCtxt<'_, '_, 'tcx, ProjectionPredicate<'tcx>>, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, impl_def_id: DefId, ) { - let tcx = acx.cx.tcx; + let tcx = acx.cx.tcx(); + let infcx = acx.cx.infcx; + let goal_trait_ref = goal.predicate.projection_ty.trait_ref(tcx); let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder }; @@ -118,12 +120,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { return; } - acx.infcx.probe(|_| { - let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + infcx.probe(|_| { + let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); - let Ok(InferOk { obligations, .. }) = acx - .infcx + let Ok(InferOk { obligations, .. }) = infcx .at(&ObligationCause::dummy(), goal.param_env) .define_opaque_types(false) .eq(goal_trait_ref, impl_trait_ref) @@ -138,11 +139,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { .into_iter() .map(|pred| goal.with(tcx, pred)); - let nested_goals = obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); - let Ok(trait_ref_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return }; + let nested_goals = + obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); + let Ok(trait_ref_certainty) = acx.cx.evaluate_all(nested_goals) else { return }; let Some(assoc_def) = fetch_eligible_assoc_item_def( - acx.infcx, + infcx, goal.param_env, goal_trait_ref, goal.predicate.def_id(), @@ -174,7 +176,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { impl_substs, ); let substs = translate_substs( - acx.infcx, + infcx, goal.param_env, impl_def_id, impl_substs_with_gat, @@ -185,7 +187,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { let is_const = matches!(tcx.def_kind(assoc_def.item.def_id), DefKind::AssocConst); let ty = tcx.bound_type_of(assoc_def.item.def_id); let term: ty::EarlyBinder> = if is_const { - let identity_substs = ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id); + let identity_substs = + ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id); let did = ty::WithOptConstParam::unknown(assoc_def.item.def_id); let kind = ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs)); @@ -194,8 +197,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ty.map_bound(|ty| ty.into()) }; - let Ok(InferOk { obligations, .. }) = acx - .infcx + let Ok(InferOk { obligations, .. }) = infcx .at(&ObligationCause::dummy(), goal.param_env) .define_opaque_types(false) .eq(goal.predicate.term, term.subst(tcx, substs)) @@ -205,7 +207,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { }; let nested_goals = obligations.into_iter().map(|o| o.into()).collect(); - let Ok(rhs_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return }; + let Ok(rhs_certainty) = acx.cx.evaluate_all(nested_goals) else { return }; let certainty = trait_ref_certainty.unify_and(rhs_certainty); acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty); diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs new file mode 100644 index 00000000000..435e46f211a --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs @@ -0,0 +1,115 @@ +//! This module both handles the global cache which stores "finished" goals, +//! and the provisional cache which contains partially computed goals. +//! +//! The provisional cache is necessary when dealing with coinductive cycles. +//! +//! For more information about the provisional cache and coinduction in general, +//! check out the relevant section of the rustc-dev-guide. +//! +//! FIXME(@lcnr): Write that section, feel free to ping me if you need help here +//! before then or if I still haven't done that before January 2023. +use super::overflow::OverflowData; +use super::StackDepth; +use crate::solve::{CanonicalGoal, QueryResult}; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::vec::IndexVec; +use rustc_middle::ty::TyCtxt; + +rustc_index::newtype_index! { + pub struct EntryIndex {} +} + +#[derive(Debug, Clone)] +pub(super) struct ProvisionalEntry<'tcx> { + // In case we have a coinductive cycle, this is the + // the currently least restrictive result of this goal. + pub(super) response: QueryResult<'tcx>, + // In case of a cycle, the position of deepest stack entry involved + // in that cycle. This is monotonically decreasing in the stack as all + // elements between the current stack element in the deepest stack entry + // involved have to also be involved in that cycle. + // + // We can only move entries to the global cache once we're complete done + // with the cycle. If this entry has not been involved in a cycle, + // this is just its own depth. + pub(super) depth: StackDepth, + + // The goal for this entry. Should always be equal to the corresponding goal + // in the lookup table. + pub(super) goal: CanonicalGoal<'tcx>, +} + +pub(super) struct ProvisionalCache<'tcx> { + pub(super) entries: IndexVec>, + // FIXME: This is only used to quickly check whether a given goal + // is in the cache. We should experiment with using something like + // `SsoHashSet` here because in most cases there are only a few entries. + pub(super) lookup_table: FxHashMap, EntryIndex>, +} + +impl<'tcx> ProvisionalCache<'tcx> { + pub(super) fn empty() -> ProvisionalCache<'tcx> { + ProvisionalCache { entries: Default::default(), lookup_table: Default::default() } + } + + /// Adds a dependency from the current leaf to `target` in the cache + /// to prevent us from moving any goals which depend on the current leaf + /// to the global cache while we're still computing `target`. + pub(super) fn add_dependency_of_leaf_on(&mut self, target: EntryIndex) { + let depth = self.entries[target].depth; + for provisional_entry in &mut self.entries.raw[target.index()..] { + // The depth of `target` is the position of the deepest goal in the stack + // on which `target` depends. That goal is the `root` of this cycle. + // + // Any entry which was added after `target` is either on the stack itself + // at which point its depth is definitely at least as high as the depth of + // `root`. If it's not on the stack itself it has to depend on a goal + // between `root` and `leaf`. If it were to depend on a goal deeper in the + // stack than `root`, then `root` would also depend on that goal, at which + // point `root` wouldn't be the root anymore. + debug_assert!(provisional_entry.depth >= depth); + provisional_entry.depth = depth; + } + + // We only update entries which were added after `target` as no other + // entry should have a higher depth. + // + // Any entry which previously had a higher depth than target has to + // be between `target` and `root`. Because of this we would have updated + // its depth when calling `add_dependency_of_leaf_on(root)` for `target`. + if cfg!(debug_assertions) { + self.entries.iter().all(|e| e.depth <= depth); + } + } + + pub(super) fn depth(&self, entry_index: EntryIndex) -> StackDepth { + self.entries[entry_index].depth + } + + pub(super) fn provisional_result(&self, entry_index: EntryIndex) -> QueryResult<'tcx> { + self.entries[entry_index].response.clone() + } +} + +pub(super) fn try_move_finished_goal_to_global_cache<'tcx>( + tcx: TyCtxt<'tcx>, + overflow_data: &mut OverflowData, + stack: &IndexVec>, + goal: CanonicalGoal<'tcx>, + response: QueryResult<'tcx>, +) { + // We move goals to the global cache if we either did not hit an overflow or if it's + // the root goal as that will now always hit the same overflow limit. + // + // NOTE: We cannot move any non-root goals to the global cache even if their final result + // isn't impacted by the overflow as that goal still has unstable query dependencies + // because it didn't go its full depth. + // + // FIXME(@lcnr): We could still cache subtrees which are not impacted by overflow though. + // Tracking that info correctly isn't trivial, so I haven't implemented it for now. + let should_cache_globally = !overflow_data.did_overflow() || stack.is_empty(); + if should_cache_globally { + // FIXME: move the provisional entry to the global cache. + let _ = (tcx, goal, response); + } +} diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs new file mode 100644 index 00000000000..8d2a3a9cd81 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs @@ -0,0 +1,172 @@ +mod cache; +mod overflow; + +use self::cache::ProvisionalEntry; +use super::{CanonicalGoal, Certainty, MaybeCause, QueryResult}; +use cache::ProvisionalCache; +use overflow::OverflowData; +use rustc_index::vec::IndexVec; +use rustc_middle::ty::TyCtxt; +use std::collections::hash_map::Entry; + +rustc_index::newtype_index! { + pub struct StackDepth {} +} + +struct StackElem<'tcx> { + goal: CanonicalGoal<'tcx>, + has_been_used: bool, +} + +pub(super) struct SearchGraph<'tcx> { + /// The stack of goals currently being computed. + /// + /// An element is *deeper* in the stack if its index is *lower*. + stack: IndexVec>, + overflow_data: OverflowData, + provisional_cache: ProvisionalCache<'tcx>, +} + +impl<'tcx> SearchGraph<'tcx> { + pub(super) fn new(tcx: TyCtxt<'tcx>) -> SearchGraph<'tcx> { + Self { + stack: Default::default(), + overflow_data: OverflowData::new(tcx), + provisional_cache: ProvisionalCache::empty(), + } + } + + /// Tries putting the new goal on the stack, returning an error if it is already cached. + /// + /// This correctly updates the provisional cache if there is a cycle. + pub(super) fn try_push_stack( + &mut self, + tcx: TyCtxt<'tcx>, + goal: CanonicalGoal<'tcx>, + ) -> Result<(), QueryResult<'tcx>> { + // FIXME: start by checking the global cache + + // Look at the provisional cache to check for cycles. + let cache = &mut self.provisional_cache; + match cache.lookup_table.entry(goal) { + // No entry, simply push this goal on the stack after dealing with overflow. + Entry::Vacant(v) => { + if self.overflow_data.has_overflow(self.stack.len()) { + return Err(self.deal_with_overflow(tcx, goal)); + } + + let depth = self.stack.push(StackElem { goal, has_been_used: false }); + let response = super::response_no_constraints(tcx, goal, Certainty::Yes); + let entry_index = cache.entries.push(ProvisionalEntry { response, depth, goal }); + v.insert(entry_index); + Ok(()) + } + // We have a nested goal which relies on a goal `root` deeper in the stack. + // + // We first store that we may have to rerun `evaluate_goal` for `root` in case the + // provisional response is not equal to the final response. We also update the depth + // of all goals which recursively depend on our current goal to depend on `root` + // instead. + // + // Finally we can return either the provisional response for that goal if we have a + // coinductive cycle or an ambiguous result if the cycle is inductive. + Entry::Occupied(entry_index) => { + let entry_index = *entry_index.get(); + + cache.add_dependency_of_leaf_on(entry_index); + let stack_depth = cache.depth(entry_index); + + self.stack[stack_depth].has_been_used = true; + // NOTE: The goals on the stack aren't the only goals involved in this cycle. + // We can also depend on goals which aren't part of the stack but coinductively + // depend on the stack themselves. We already checked whether all the goals + // between these goals and their root on the stack. This means that as long as + // each goal in a cycle is checked for coinductivity by itself, simply checking + // the stack is enough. + if self.stack.raw[stack_depth.index()..] + .iter() + .all(|g| g.goal.value.predicate.is_coinductive(tcx)) + { + Err(cache.provisional_result(entry_index)) + } else { + Err(super::response_no_constraints( + tcx, + goal, + Certainty::Maybe(MaybeCause::Overflow), + )) + } + } + } + } + + /// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with + /// coinductive cycles. + /// + /// When we encounter a coinductive cycle, we have to prove the final result of that cycle + /// while we are still computing that result. Because of this we continously recompute the + /// cycle until the result of the previous iteration is equal to the final result, at which + /// point we are done. + /// + /// This function returns `true` if we were able to finalize the goal and `false` if it has + /// updated the provisional cache and we have to recompute the current goal. + /// + /// FIXME: Refer to the rustc-dev-guide entry once it exists. + pub(super) fn try_finalize_goal( + &mut self, + tcx: TyCtxt<'tcx>, + actual_goal: CanonicalGoal<'tcx>, + response: QueryResult<'tcx>, + ) -> bool { + let StackElem { goal, has_been_used } = self.stack.pop().unwrap(); + assert_eq!(goal, actual_goal); + + let cache = &mut self.provisional_cache; + let provisional_entry_index = *cache.lookup_table.get(&goal).unwrap(); + let provisional_entry = &mut cache.entries[provisional_entry_index]; + let depth = provisional_entry.depth; + // Was the current goal the root of a cycle and was the provisional response + // different from the final one. + if has_been_used && provisional_entry.response != response { + // If so, update the provisional reponse for this goal... + provisional_entry.response = response; + // ...remove all entries whose result depends on this goal + // from the provisional cache... + // + // That's not completely correct, as a nested goal can also + // depend on a goal which is lower in the stack so it doesn't + // actually depend on the current goal. This should be fairly + // rare and is hopefully not relevant for performance. + #[allow(rustc::potential_query_instability)] + cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index); + cache.entries.truncate(provisional_entry_index.index() + 1); + + // ...and finally push our goal back on the stack and reevaluate it. + self.stack.push(StackElem { goal, has_been_used: false }); + false + } else { + // If not, we're done with this goal. + // + // Check whether that this goal doesn't depend on a goal deeper on the stack + // and if so, move it and all nested goals to the global cache. + // + // Note that if any nested goal were to depend on something deeper on the stack, + // this would have also updated the depth of the current goal. + if depth == self.stack.next_index() { + for (i, entry) in cache.entries.drain_enumerated(provisional_entry_index.index()..) + { + let actual_index = cache.lookup_table.remove(&entry.goal); + debug_assert_eq!(Some(i), actual_index); + debug_assert!(entry.depth == depth); + cache::try_move_finished_goal_to_global_cache( + tcx, + &mut self.overflow_data, + &self.stack, + entry.goal, + entry.response, + ); + } + } + true + } + } +} diff --git a/compiler/rustc_trait_selection/src/solve/overflow.rs b/compiler/rustc_trait_selection/src/solve/search_graph/overflow.rs similarity index 74% rename from compiler/rustc_trait_selection/src/solve/overflow.rs rename to compiler/rustc_trait_selection/src/solve/search_graph/overflow.rs index 8bbb9f63e78..1dd3894c91a 100644 --- a/compiler/rustc_trait_selection/src/solve/overflow.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/overflow.rs @@ -3,8 +3,8 @@ use rustc_infer::traits::query::NoSolution; use rustc_middle::ty::TyCtxt; use rustc_session::Limit; -use super::cache::response_no_constraints; -use super::{Certainty, EvalCtxt, MaybeCause, QueryResult}; +use super::SearchGraph; +use crate::solve::{response_no_constraints, Certainty, EvalCtxt, MaybeCause, QueryResult}; /// When detecting a solver overflow, we return ambiguity. Overflow can be /// *hidden* by either a fatal error in an **AND** or a trivial success in an **OR**. @@ -50,32 +50,35 @@ impl OverflowData { } } -impl<'tcx> EvalCtxt<'tcx> { - pub(super) fn deal_with_overflow( +impl<'tcx> SearchGraph<'tcx> { + pub fn deal_with_overflow( &mut self, + tcx: TyCtxt<'tcx>, goal: Canonical<'tcx, impl Sized>, ) -> QueryResult<'tcx> { self.overflow_data.deal_with_overflow(); - response_no_constraints(self.tcx, goal, Certainty::Maybe(MaybeCause::Overflow)) + response_no_constraints(tcx, goal, Certainty::Maybe(MaybeCause::Overflow)) } +} +impl<'tcx> EvalCtxt<'_, 'tcx> { /// A `while`-loop which tracks overflow. - pub(super) fn repeat_while_none( + pub fn repeat_while_none( &mut self, mut loop_body: impl FnMut(&mut Self) -> Option>, ) -> Result { - let start_depth = self.overflow_data.additional_depth; - let depth = self.provisional_cache.current_depth(); - while !self.overflow_data.has_overflow(depth) { + let start_depth = self.search_graph.overflow_data.additional_depth; + let depth = self.search_graph.stack.len(); + while !self.search_graph.overflow_data.has_overflow(depth) { if let Some(result) = loop_body(self) { - self.overflow_data.additional_depth = start_depth; + self.search_graph.overflow_data.additional_depth = start_depth; return result; } - self.overflow_data.additional_depth += 1; + self.search_graph.overflow_data.additional_depth += 1; } - self.overflow_data.additional_depth = start_depth; - self.overflow_data.deal_with_overflow(); + self.search_graph.overflow_data.additional_depth = start_depth; + self.search_graph.overflow_data.deal_with_overflow(); Ok(Certainty::Maybe(MaybeCause::Overflow)) } } diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index a43fef5cdb0..cc68cfeea1d 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -3,7 +3,7 @@ use std::iter; use super::assembly::{self, AssemblyCtxt}; -use super::{CanonicalGoal, EvalCtxt, Goal, QueryResult}; +use super::{EvalCtxt, Goal, QueryResult}; use rustc_hir::def_id::DefId; use rustc_infer::infer::InferOk; use rustc_infer::traits::query::NoSolution; @@ -67,11 +67,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { } fn consider_impl_candidate( - acx: &mut AssemblyCtxt<'_, 'tcx, Self>, + acx: &mut AssemblyCtxt<'_, '_, 'tcx, Self>, goal: Goal<'tcx, TraitPredicate<'tcx>>, impl_def_id: DefId, ) { - let tcx = acx.cx.tcx; + let tcx = acx.cx.tcx(); + let infcx = acx.cx.infcx; let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder }; @@ -81,12 +82,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { return; } - acx.infcx.probe(|_| { - let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + infcx.probe(|_| { + let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); - let Ok(InferOk { obligations, .. }) = acx - .infcx + let Ok(InferOk { obligations, .. }) = infcx .at(&ObligationCause::dummy(), goal.param_env) .define_opaque_types(false) .eq(goal.predicate.trait_ref, impl_trait_ref) @@ -104,16 +104,16 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { let nested_goals = obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); - let Ok(certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return }; + let Ok(certainty) = acx.cx.evaluate_all(nested_goals) else { return }; acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty); }) } } -impl<'tcx> EvalCtxt<'tcx> { +impl<'tcx> EvalCtxt<'_, 'tcx> { pub(super) fn compute_trait_goal( &mut self, - goal: CanonicalGoal<'tcx, TraitPredicate<'tcx>>, + goal: Goal<'tcx, TraitPredicate<'tcx>>, ) -> QueryResult<'tcx> { let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal); self.merge_trait_candidates_discard_reservation_impls(candidates) @@ -176,7 +176,7 @@ impl<'tcx> EvalCtxt<'tcx> { fn discard_reservation_impl(&self, candidate: Candidate<'tcx>) -> Candidate<'tcx> { if let CandidateSource::Impl(def_id) = candidate.source { - if let ty::ImplPolarity::Reservation = self.tcx.impl_polarity(def_id) { + if let ty::ImplPolarity::Reservation = self.tcx().impl_polarity(def_id) { debug!("Selected reservation impl"); // FIXME: reduce candidate to ambiguous // FIXME: replace `var_values` with identity, yeet external constraints. From 660c28391c79bd12e116724a8877a2148630dee5 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 17 Jan 2023 11:47:47 +0100 Subject: [PATCH 10/14] remove assembly context and impl a bit more --- compiler/rustc_middle/src/infer/canonical.rs | 6 + .../src/solve/assembly.rs | 264 ++++++++++++++---- .../src/solve/fulfill.rs | 27 +- .../rustc_trait_selection/src/solve/mod.rs | 21 +- .../src/solve/project_goals.rs | 74 ++--- .../src/solve/search_graph/cache.rs | 4 + .../src/solve/search_graph/mod.rs | 6 + .../src/solve/trait_goals.rs | 88 +++--- 8 files changed, 322 insertions(+), 168 deletions(-) diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs index 7f3567c08be..43583b5723e 100644 --- a/compiler/rustc_middle/src/infer/canonical.rs +++ b/compiler/rustc_middle/src/infer/canonical.rs @@ -339,6 +339,12 @@ TrivialTypeTraversalAndLiftImpls! { } impl<'tcx> CanonicalVarValues<'tcx> { + /// Creates dummy var values which should not be used in a + /// canonical response. + pub fn dummy() -> CanonicalVarValues<'tcx> { + CanonicalVarValues { var_values: Default::default() } + } + #[inline] pub fn len(&self) -> usize { self.var_values.len() diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs index da6bb844a0f..cd6e4d2bccd 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly.rs @@ -12,18 +12,73 @@ use std::fmt::Debug; /// /// It consists of both the `source`, which describes how that goal would be proven, /// and the `result` when using the given `source`. -/// -/// For the list of possible candidates, please look at the documentation of -/// [super::trait_goals::CandidateSource] and [super::project_goals::CandidateSource]. #[derive(Debug, Clone)] -pub(super) struct Candidate<'tcx, G: GoalKind<'tcx>> { - pub(super) source: G::CandidateSource, +pub(super) struct Candidate<'tcx> { + pub(super) source: CandidateSource, pub(super) result: CanonicalResponse<'tcx>, } -pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy { - type CandidateSource: Debug + Copy; +/// Possible ways the given goal can be proven. +#[derive(Debug, Clone, Copy)] +pub(super) enum CandidateSource { + /// A user written impl. + /// + /// ## Examples + /// + /// ```rust + /// fn main() { + /// let x: Vec = Vec::new(); + /// // This uses the impl from the standard library to prove `Vec: Clone`. + /// let y = x.clone(); + /// } + /// ``` + Impl(DefId), + /// A builtin impl generated by the compiler. When adding a new special + /// trait, try to use actual impls whenever possible. Builtin impls should + /// only be used in cases where the impl cannot be manually be written. + /// + /// Notable examples are auto traits, `Sized`, and `DiscriminantKind`. + /// For a list of all traits with builtin impls, check out the + /// [`EvalCtxt::assemble_builtin_impl_candidates`] method. Not + BuiltinImpl, + /// An assumption from the environment. + /// + /// More precicely we've used the `n-th` assumption in the `param_env`. + /// + /// ## Examples + /// + /// ```rust + /// fn is_clone(x: T) -> (T, T) { + /// // This uses the assumption `T: Clone` from the `where`-bounds + /// // to prove `T: Clone`. + /// (x.clone(), x) + /// } + /// ``` + ParamEnv(usize), + /// If the self type is an alias type, e.g. an opaque type or a projection, + /// we know the bounds on that alias to hold even without knowing its concrete + /// underlying type. + /// + /// More precisely this candidate is using the `n-th` bound in the `item_bounds` of + /// the self type. + /// + /// ## Examples + /// + /// ```rust + /// trait Trait { + /// type Assoc: Clone; + /// } + /// + /// fn foo(x: ::Assoc) { + /// // We prove `::Assoc` by looking at the bounds on `Assoc` in + /// // in the trait definition. + /// let _y = x.clone(); + /// } + /// ``` + AliasBound(usize), +} +pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy { fn self_ty(self) -> Ty<'tcx>; fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self; @@ -31,43 +86,40 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy { fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId; fn consider_impl_candidate( - acx: &mut AssemblyCtxt<'_, '_, 'tcx, Self>, + ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, impl_def_id: DefId, - ); + ) -> Result; + + fn consider_builtin_sized_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> Result; + + fn consider_assumption( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + assumption: ty::Predicate<'tcx>, + ) -> Result; } - -/// An abstraction which correctly deals with the canonical results for candidates. -/// -/// It also deduplicates the behavior between trait and projection predicates. -pub(super) struct AssemblyCtxt<'a, 'b, 'tcx, G: GoalKind<'tcx>> { - pub(super) cx: &'a mut EvalCtxt<'b, 'tcx>, - candidates: Vec>, -} - -impl<'a, 'b, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'b, 'tcx, G> { - pub(super) fn assemble_and_evaluate_candidates( - cx: &'a mut EvalCtxt<'b, 'tcx>, - goal: Goal<'tcx, G>, - ) -> Vec> { - let mut acx = AssemblyCtxt { cx, candidates: Vec::new() }; - - acx.assemble_candidates_after_normalizing_self_ty(goal); - - acx.assemble_impl_candidates(goal); - - acx.candidates - } - - pub(super) fn try_insert_candidate( +impl<'tcx> EvalCtxt<'_, 'tcx> { + pub(super) fn assemble_and_evaluate_candidates>( &mut self, - source: G::CandidateSource, - certainty: Certainty, - ) { - match self.cx.make_canonical_response(certainty) { - Ok(result) => self.candidates.push(Candidate { source, result }), - Err(NoSolution) => debug!(?source, ?certainty, "failed leakcheck"), - } + goal: Goal<'tcx, G>, + ) -> Vec> { + let mut candidates = Vec::new(); + + self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates); + + self.assemble_impl_candidates(goal, &mut candidates); + + self.assemble_builtin_impl_candidates(goal, &mut candidates); + + self.assemble_param_env_candidates(goal, &mut candidates); + + self.assemble_alias_bound_candidates(goal, &mut candidates); + + candidates } /// If the self type of a goal is a projection, computing the relevant candidates is difficult. @@ -75,15 +127,18 @@ impl<'a, 'b, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'b, 'tcx, G> { /// To deal with this, we first try to normalize the self type and add the candidates for the normalized /// self type to the list of candidates in case that succeeds. Note that we can't just eagerly return in /// this case as projections as self types add ` - fn assemble_candidates_after_normalizing_self_ty(&mut self, goal: Goal<'tcx, G>) { - let tcx = self.cx.tcx(); - let infcx = self.cx.infcx; + fn assemble_candidates_after_normalizing_self_ty>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + let tcx = self.tcx(); // FIXME: We also have to normalize opaque types, not sure where to best fit that in. let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else { return }; - infcx.probe(|_| { - let normalized_ty = infcx.next_ty_infer(); + self.infcx.probe(|_| { + let normalized_ty = self.infcx.next_ty_infer(); let normalizes_to_goal = goal.with( tcx, ty::Binder::dummy(ty::ProjectionPredicate { @@ -91,33 +146,136 @@ impl<'a, 'b, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'b, 'tcx, G> { term: normalized_ty.into(), }), ); - let normalization_certainty = match self.cx.evaluate_goal(normalizes_to_goal) { + let normalization_certainty = match self.evaluate_goal(normalizes_to_goal) { Ok((_, certainty)) => certainty, Err(NoSolution) => return, }; // NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate. - // This doesn't work as long as we use `CandidateSource` in both winnowing and to resolve associated items. + // This doesn't work as long as we use `CandidateSource` in winnowing. let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty)); - let normalized_candidates = - AssemblyCtxt::assemble_and_evaluate_candidates(self.cx, goal); + // FIXME: This is broken if we care about the `usize` of `AliasBound` because the self type + // could be normalized to yet another projection with different item bounds. + let normalized_candidates = self.assemble_and_evaluate_candidates(goal); for mut normalized_candidate in normalized_candidates { normalized_candidate.result = normalized_candidate.result.unchecked_map(|mut response| { + // FIXME: This currently hides overflow in the normalization step of the self type + // which is probably wrong. Maybe `unify_and` should actually keep overflow as + // we treat it as non-fatal anyways. response.certainty = response.certainty.unify_and(normalization_certainty); response }); - self.candidates.push(normalized_candidate); + candidates.push(normalized_candidate); } }) } - fn assemble_impl_candidates(&mut self, goal: Goal<'tcx, G>) { - let tcx = self.cx.tcx(); + fn assemble_impl_candidates>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + let tcx = self.tcx(); tcx.for_each_relevant_impl( goal.predicate.trait_def_id(tcx), goal.predicate.self_ty(), - |impl_def_id| G::consider_impl_candidate(self, goal, impl_def_id), + |impl_def_id| match G::consider_impl_candidate(self, goal, impl_def_id) + .and_then(|certainty| self.make_canonical_response(certainty)) + { + Ok(result) => candidates + .push(Candidate { source: CandidateSource::Impl(impl_def_id), result }), + Err(NoSolution) => (), + }, ); } + + fn assemble_builtin_impl_candidates>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + let lang_items = self.tcx().lang_items(); + let trait_def_id = goal.predicate.trait_def_id(self.tcx()); + let result = if lang_items.sized_trait() == Some(trait_def_id) { + G::consider_builtin_sized_candidate(self, goal) + } else { + Err(NoSolution) + }; + + match result.and_then(|certainty| self.make_canonical_response(certainty)) { + Ok(result) => { + candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }) + } + Err(NoSolution) => (), + } + } + + fn assemble_param_env_candidates>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + for (i, assumption) in goal.param_env.caller_bounds().iter().enumerate() { + match G::consider_assumption(self, goal, assumption) + .and_then(|certainty| self.make_canonical_response(certainty)) + { + Ok(result) => { + candidates.push(Candidate { source: CandidateSource::ParamEnv(i), result }) + } + Err(NoSolution) => (), + } + } + } + + fn assemble_alias_bound_candidates>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + let alias_ty = match goal.predicate.self_ty().kind() { + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Adt(_, _) + | ty::Foreign(_) + | ty::Str + | ty::Array(_, _) + | ty::Slice(_) + | ty::RawPtr(_) + | ty::Ref(_, _, _) + | ty::FnDef(_, _) + | ty::FnPtr(_) + | ty::Dynamic(..) + | ty::Closure(..) + | ty::Generator(..) + | ty::GeneratorWitness(_) + | ty::Never + | ty::Tuple(_) + | ty::Param(_) + | ty::Placeholder(..) + | ty::Infer(_) + | ty::Error(_) => return, + ty::Bound(..) => bug!("unexpected bound type: {goal:?}"), + ty::Alias(_, alias_ty) => alias_ty, + }; + + for (i, (assumption, _)) in self + .tcx() + .bound_explicit_item_bounds(alias_ty.def_id) + .subst_iter_copied(self.tcx(), alias_ty.substs) + .enumerate() + { + match G::consider_assumption(self, goal, assumption) + .and_then(|certainty| self.make_canonical_response(certainty)) + { + Ok(result) => { + candidates.push(Candidate { source: CandidateSource::AliasBound(i), result }) + } + Err(NoSolution) => (), + } + } + } } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 3146f468f7d..b086c0684d2 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -2,7 +2,7 @@ use std::mem; use rustc_data_structures::fx::FxHashMap; use rustc_infer::{ - infer::{canonical::OriginalQueryValues, InferCtxt}, + infer::InferCtxt, traits::{ query::NoSolution, FulfillmentError, FulfillmentErrorCode, PredicateObligation, SelectionError, TraitEngine, @@ -10,7 +10,7 @@ use rustc_infer::{ }; use rustc_middle::ty; -use super::{Certainty, EvalCtxt}; +use super::{search_graph, Certainty, EvalCtxt}; /// A trait engine using the new trait solver. /// @@ -68,25 +68,10 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { let mut has_changed = false; for obligation in mem::take(&mut self.obligations) { let goal = obligation.clone().into(); - - // FIXME: Add a better API for that '^^ - let mut orig_values = OriginalQueryValues::default(); - let canonical_goal = infcx.canonicalize_query(goal, &mut orig_values); - let (changed, certainty) = match EvalCtxt::evaluate_canonical_goal( - infcx.tcx, - &mut super::search_graph::SearchGraph::new(infcx.tcx), - canonical_goal, - ) { - Ok(canonical_response) => { - ( - true, // FIXME: check whether `var_values` are an identity substitution. - super::instantiate_canonical_query_response( - infcx, - &orig_values, - canonical_response, - ), - ) - } + let search_graph = &mut search_graph::SearchGraph::new(infcx.tcx); + let mut ecx = EvalCtxt::new_outside_solver(infcx, search_graph); + let (changed, certainty) = match ecx.evaluate_goal(goal) { + Ok(result) => result, Err(NoSolution) => { errors.push(FulfillmentError { obligation: obligation.clone(), diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index 0e629664b2b..579cd6a2d59 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -155,6 +155,23 @@ struct EvalCtxt<'a, 'tcx> { } impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + + /// Creates a new evaluation context outside of the trait solver. + /// + /// With this solver making a canonical response doesn't make much sense. + /// The `search_graph` for this solver has to be completely empty. + fn new_outside_solver( + infcx: &'a InferCtxt<'tcx>, + search_graph: &'a mut search_graph::SearchGraph<'tcx>, + ) -> EvalCtxt<'a, 'tcx> { + assert!(search_graph.is_empty()); + EvalCtxt { infcx, var_values: CanonicalVarValues::dummy(), search_graph } + } + + #[instrument(level = "debug", skip(tcx, search_graph), ret)] fn evaluate_canonical_goal( tcx: TyCtxt<'tcx>, search_graph: &'a mut search_graph::SearchGraph<'tcx>, @@ -183,10 +200,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { } } - fn tcx(&self) -> TyCtxt<'tcx> { - self.infcx.tcx - } - fn make_canonical_response(&self, certainty: Certainty) -> QueryResult<'tcx> { let external_constraints = take_external_constraints(self.infcx)?; diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 1120dfb8098..92c5d4e53f5 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -1,7 +1,7 @@ use crate::traits::{specialization_graph, translate_substs}; -use super::assembly::{self, AssemblyCtxt}; -use super::{EvalCtxt, Goal, QueryResult}; +use super::assembly::{self, Candidate, CandidateSource}; +use super::{Certainty, EvalCtxt, Goal, QueryResult}; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -16,22 +16,12 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::DUMMY_SP; use std::iter; -#[allow(dead_code)] // FIXME: implement and use all variants. -#[derive(Debug, Clone, Copy)] -pub(super) enum CandidateSource { - Impl(DefId), - ParamEnv(usize), - Builtin, -} - -type Candidate<'tcx> = assembly::Candidate<'tcx, ProjectionPredicate<'tcx>>; - impl<'tcx> EvalCtxt<'_, 'tcx> { pub(super) fn compute_projection_goal( &mut self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, ) -> QueryResult<'tcx> { - let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal); + let candidates = self.assemble_and_evaluate_candidates(goal); self.merge_project_candidates(candidates) } @@ -83,14 +73,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { match (candidate.source, other.source) { (CandidateSource::Impl(_), _) | (CandidateSource::ParamEnv(_), _) - | (CandidateSource::Builtin, _) => unimplemented!(), + | (CandidateSource::BuiltinImpl, _) + | (CandidateSource::AliasBound(_), _) => unimplemented!(), } } } impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { - type CandidateSource = CandidateSource; - fn self_ty(self) -> Ty<'tcx> { self.self_ty() } @@ -104,12 +93,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { } fn consider_impl_candidate( - acx: &mut AssemblyCtxt<'_, '_, 'tcx, ProjectionPredicate<'tcx>>, + ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, impl_def_id: DefId, - ) { - let tcx = acx.cx.tcx(); - let infcx = acx.cx.infcx; + ) -> Result { + let tcx = ecx.tcx(); let goal_trait_ref = goal.predicate.projection_ty.trait_ref(tcx); let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); @@ -117,20 +105,20 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { if iter::zip(goal_trait_ref.substs, impl_trait_ref.skip_binder().substs) .any(|(goal, imp)| !drcx.generic_args_may_unify(goal, imp)) { - return; + return Err(NoSolution); } - infcx.probe(|_| { - let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + ecx.infcx.probe(|_| { + let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); - let Ok(InferOk { obligations, .. }) = infcx + let Ok(InferOk { obligations, .. }) = ecx.infcx .at(&ObligationCause::dummy(), goal.param_env) .define_opaque_types(false) .eq(goal_trait_ref, impl_trait_ref) .map_err(|e| debug!("failed to equate trait refs: {e:?}")) else { - return + return Err(NoSolution) }; let where_clause_bounds = tcx .predicates_of(impl_def_id) @@ -141,16 +129,16 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { let nested_goals = obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); - let Ok(trait_ref_certainty) = acx.cx.evaluate_all(nested_goals) else { return }; + let trait_ref_certainty = ecx.evaluate_all(nested_goals)?; let Some(assoc_def) = fetch_eligible_assoc_item_def( - infcx, + ecx.infcx, goal.param_env, goal_trait_ref, goal.predicate.def_id(), impl_def_id ) else { - return + return Err(NoSolution); }; if !assoc_def.item.defaultness(tcx).has_value() { @@ -176,7 +164,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { impl_substs, ); let substs = translate_substs( - infcx, + ecx.infcx, goal.param_env, impl_def_id, impl_substs_with_gat, @@ -197,22 +185,40 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ty.map_bound(|ty| ty.into()) }; - let Ok(InferOk { obligations, .. }) = infcx + let Ok(InferOk { obligations, .. }) = ecx.infcx .at(&ObligationCause::dummy(), goal.param_env) .define_opaque_types(false) .eq(goal.predicate.term, term.subst(tcx, substs)) .map_err(|e| debug!("failed to equate trait refs: {e:?}")) else { - return + return Err(NoSolution); }; let nested_goals = obligations.into_iter().map(|o| o.into()).collect(); - let Ok(rhs_certainty) = acx.cx.evaluate_all(nested_goals) else { return }; + let rhs_certainty = ecx.evaluate_all(nested_goals)?; - let certainty = trait_ref_certainty.unify_and(rhs_certainty); - acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty); + Ok(trait_ref_certainty.unify_and(rhs_certainty)) }) } + + fn consider_builtin_sized_candidate( + _ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> Result { + bug!("`Sized` does not have an associated type: {:?}", goal); + } + + fn consider_assumption( + _ecx: &mut EvalCtxt<'_, 'tcx>, + _goal: Goal<'tcx, Self>, + assumption: ty::Predicate<'tcx>, + ) -> Result { + if let Some(_poly_projection_pred) = assumption.to_opt_poly_projection_pred() { + unimplemented!() + } else { + Err(NoSolution) + } + } } /// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code. diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs index 435e46f211a..cb00fe6bf14 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs @@ -52,6 +52,10 @@ impl<'tcx> ProvisionalCache<'tcx> { ProvisionalCache { entries: Default::default(), lookup_table: Default::default() } } + pub(super) fn is_empty(&self) -> bool { + self.entries.is_empty() && self.lookup_table.is_empty() + } + /// Adds a dependency from the current leaf to `target` in the cache /// to prevent us from moving any goals which depend on the current leaf /// to the global cache while we're still computing `target`. diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs index 8d2a3a9cd81..4f48389410b 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs @@ -36,6 +36,12 @@ impl<'tcx> SearchGraph<'tcx> { } } + pub(super) fn is_empty(&self) -> bool { + self.stack.is_empty() + && self.provisional_cache.is_empty() + && !self.overflow_data.did_overflow() + } + /// Tries putting the new goal on the stack, returning an error if it is already cached. /// /// This correctly updates the provisional cache if there is a cycle. diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index cc68cfeea1d..3c8314aa565 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -2,8 +2,8 @@ use std::iter; -use super::assembly::{self, AssemblyCtxt}; -use super::{EvalCtxt, Goal, QueryResult}; +use super::assembly::{self, Candidate, CandidateSource}; +use super::{Certainty, EvalCtxt, Goal, QueryResult}; use rustc_hir::def_id::DefId; use rustc_infer::infer::InferOk; use rustc_infer::traits::query::NoSolution; @@ -13,47 +13,7 @@ use rustc_middle::ty::TraitPredicate; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::DUMMY_SP; -#[allow(dead_code)] // FIXME: implement and use all variants. -#[derive(Debug, Clone, Copy)] -pub(super) enum CandidateSource { - /// Some user-defined impl with the given `DefId`. - Impl(DefId), - /// The n-th caller bound in the `param_env` of our goal. - /// - /// This is pretty much always a bound from the `where`-clauses of the - /// currently checked item. - ParamEnv(usize), - /// A bound on the `self_ty` in case it is a projection or an opaque type. - /// - /// # Examples - /// - /// ```ignore (for syntax highlighting) - /// trait Trait { - /// type Assoc: OtherTrait; - /// } - /// ``` - /// - /// We know that `::Assoc: OtherTrait` holds by looking at - /// the bounds on `Trait::Assoc`. - AliasBound(usize), - /// A builtin implementation for some specific traits, used in cases - /// where we cannot rely an ordinary library implementations. - /// - /// The most notable examples are `Sized`, `Copy` and `Clone`. This is also - /// used for the `DiscriminantKind` and `Pointee` trait, both of which have - /// an associated type. - Builtin, - /// An automatic impl for an auto trait, e.g. `Send`. These impls recursively look - /// at the constituent types of the `self_ty` to check whether the auto trait - /// is implemented for those. - AutoImpl, -} - -type Candidate<'tcx> = assembly::Candidate<'tcx, TraitPredicate<'tcx>>; - impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { - type CandidateSource = CandidateSource; - fn self_ty(self) -> Ty<'tcx> { self.self_ty() } @@ -67,32 +27,31 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { } fn consider_impl_candidate( - acx: &mut AssemblyCtxt<'_, '_, 'tcx, Self>, + ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, TraitPredicate<'tcx>>, impl_def_id: DefId, - ) { - let tcx = acx.cx.tcx(); - let infcx = acx.cx.infcx; + ) -> Result { + let tcx = ecx.tcx(); let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder }; if iter::zip(goal.predicate.trait_ref.substs, impl_trait_ref.skip_binder().substs) .any(|(goal, imp)| !drcx.generic_args_may_unify(goal, imp)) { - return; + return Err(NoSolution); } - infcx.probe(|_| { - let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + ecx.infcx.probe(|_| { + let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); - let Ok(InferOk { obligations, .. }) = infcx + let Ok(InferOk { obligations, .. }) = ecx.infcx .at(&ObligationCause::dummy(), goal.param_env) .define_opaque_types(false) .eq(goal.predicate.trait_ref, impl_trait_ref) .map_err(|e| debug!("failed to equate trait refs: {e:?}")) else { - return + return Err(NoSolution); }; let where_clause_bounds = tcx .predicates_of(impl_def_id) @@ -104,10 +63,28 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { let nested_goals = obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); - let Ok(certainty) = acx.cx.evaluate_all(nested_goals) else { return }; - acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty); + ecx.evaluate_all(nested_goals) }) } + + fn consider_builtin_sized_candidate( + _ecx: &mut EvalCtxt<'_, 'tcx>, + _goal: Goal<'tcx, Self>, + ) -> Result { + unimplemented!(); + } + + fn consider_assumption( + _ecx: &mut EvalCtxt<'_, 'tcx>, + _goal: Goal<'tcx, Self>, + assumption: ty::Predicate<'tcx>, + ) -> Result { + if let Some(_poly_trait_pred) = assumption.to_opt_poly_trait_pred() { + unimplemented!() + } else { + Err(NoSolution) + } + } } impl<'tcx> EvalCtxt<'_, 'tcx> { @@ -115,7 +92,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { &mut self, goal: Goal<'tcx, TraitPredicate<'tcx>>, ) -> QueryResult<'tcx> { - let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal); + let candidates = self.assemble_and_evaluate_candidates(goal); self.merge_trait_candidates_discard_reservation_impls(candidates) } @@ -169,8 +146,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { (CandidateSource::Impl(_), _) | (CandidateSource::ParamEnv(_), _) | (CandidateSource::AliasBound(_), _) - | (CandidateSource::Builtin, _) - | (CandidateSource::AutoImpl, _) => unimplemented!(), + | (CandidateSource::BuiltinImpl, _) => unimplemented!(), } } From 9a757d6ee4229bd7e022a2f3f26946cf05fddbcf Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 17 Jan 2023 12:26:28 +0100 Subject: [PATCH 11/14] add `eq` to `InferCtxtExt` --- .../src/solve/infcx_ext.rs | 40 ++++++++++++++++++- .../src/solve/project_goals.rs | 29 ++++---------- .../src/solve/trait_goals.rs | 18 ++------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs index 8a8c3091d54..f92d6463134 100644 --- a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs +++ b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs @@ -1,11 +1,28 @@ +use rustc_infer::infer::at::ToTrace; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; -use rustc_infer::infer::InferCtxt; -use rustc_middle::ty::Ty; +use rustc_infer::infer::{InferCtxt, InferOk}; +use rustc_infer::traits::query::NoSolution; +use rustc_infer::traits::ObligationCause; +use rustc_middle::ty::{self, Ty}; use rustc_span::DUMMY_SP; +use super::Goal; + /// Methods used inside of the canonical queries of the solver. +/// +/// Most notably these do not care about diagnostics information. +/// If you find this while looking for methods to use outside of the +/// solver, you may look at the implementation of these method for +/// help. pub(super) trait InferCtxtExt<'tcx> { fn next_ty_infer(&self) -> Ty<'tcx>; + + fn eq>( + &self, + param_env: ty::ParamEnv<'tcx>, + lhs: T, + rhs: T, + ) -> Result>>, NoSolution>; } impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { @@ -15,4 +32,23 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { span: DUMMY_SP, }) } + + #[instrument(level = "debug", skip(self, param_env), ret)] + fn eq>( + &self, + param_env: ty::ParamEnv<'tcx>, + lhs: T, + rhs: T, + ) -> Result>>, NoSolution> { + self.at(&ObligationCause::dummy(), param_env) + .define_opaque_types(false) + .eq(lhs, rhs) + .map(|InferOk { value: (), obligations }| { + obligations.into_iter().map(|o| o.into()).collect() + }) + .map_err(|e| { + debug!(?e, "failed to equate"); + NoSolution + }) + } } diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 92c5d4e53f5..cf07926f85a 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -1,14 +1,15 @@ use crate::traits::{specialization_graph, translate_substs}; use super::assembly::{self, Candidate, CandidateSource}; +use super::infcx_ext::InferCtxtExt; use super::{Certainty, EvalCtxt, Goal, QueryResult}; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; -use rustc_infer::infer::{InferCtxt, InferOk}; +use rustc_infer::infer::InferCtxt; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::specialization_graph::LeafDef; -use rustc_infer::traits::{ObligationCause, Reveal}; +use rustc_infer::traits::Reveal; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::ProjectionPredicate; use rustc_middle::ty::TypeVisitable; @@ -112,14 +113,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); - let Ok(InferOk { obligations, .. }) = ecx.infcx - .at(&ObligationCause::dummy(), goal.param_env) - .define_opaque_types(false) - .eq(goal_trait_ref, impl_trait_ref) - .map_err(|e| debug!("failed to equate trait refs: {e:?}")) - else { - return Err(NoSolution) - }; + let mut nested_goals = ecx.infcx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?; let where_clause_bounds = tcx .predicates_of(impl_def_id) .instantiate(tcx, impl_substs) @@ -127,8 +121,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { .into_iter() .map(|pred| goal.with(tcx, pred)); - let nested_goals = - obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); + nested_goals.extend(where_clause_bounds); let trait_ref_certainty = ecx.evaluate_all(nested_goals)?; let Some(assoc_def) = fetch_eligible_assoc_item_def( @@ -185,16 +178,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ty.map_bound(|ty| ty.into()) }; - let Ok(InferOk { obligations, .. }) = ecx.infcx - .at(&ObligationCause::dummy(), goal.param_env) - .define_opaque_types(false) - .eq(goal.predicate.term, term.subst(tcx, substs)) - .map_err(|e| debug!("failed to equate trait refs: {e:?}")) - else { - return Err(NoSolution); - }; - - let nested_goals = obligations.into_iter().map(|o| o.into()).collect(); + let nested_goals = + ecx.infcx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))?; let rhs_certainty = ecx.evaluate_all(nested_goals)?; Ok(trait_ref_certainty.unify_and(rhs_certainty)) diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index 3c8314aa565..bbe175d5cc8 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -3,11 +3,10 @@ use std::iter; use super::assembly::{self, Candidate, CandidateSource}; +use super::infcx_ext::InferCtxtExt; use super::{Certainty, EvalCtxt, Goal, QueryResult}; use rustc_hir::def_id::DefId; -use rustc_infer::infer::InferOk; use rustc_infer::traits::query::NoSolution; -use rustc_infer::traits::ObligationCause; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::TraitPredicate; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -45,24 +44,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { let impl_substs = ecx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); - let Ok(InferOk { obligations, .. }) = ecx.infcx - .at(&ObligationCause::dummy(), goal.param_env) - .define_opaque_types(false) - .eq(goal.predicate.trait_ref, impl_trait_ref) - .map_err(|e| debug!("failed to equate trait refs: {e:?}")) - else { - return Err(NoSolution); - }; + let mut nested_goals = + ecx.infcx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?; let where_clause_bounds = tcx .predicates_of(impl_def_id) .instantiate(tcx, impl_substs) .predicates .into_iter() .map(|pred| goal.with(tcx, pred)); - - let nested_goals = - obligations.into_iter().map(|o| o.into()).chain(where_clause_bounds).collect(); - + nested_goals.extend(where_clause_bounds); ecx.evaluate_all(nested_goals) }) } From 31ac29d9899c063fdee7a78988990f2b037bef7b Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 17 Jan 2023 13:41:12 +0100 Subject: [PATCH 12/14] update project to emulate a projection cache --- .../src/solve/infcx_ext.rs | 8 ++ .../src/solve/project_goals.rs | 128 ++++++++++++++++-- .../src/solve/search_graph/mod.rs | 2 +- 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs index f92d6463134..9b7feb50537 100644 --- a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs +++ b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs @@ -3,6 +3,7 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::{InferCtxt, InferOk}; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::ObligationCause; +use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; use rustc_middle::ty::{self, Ty}; use rustc_span::DUMMY_SP; @@ -16,6 +17,7 @@ use super::Goal; /// help. pub(super) trait InferCtxtExt<'tcx> { fn next_ty_infer(&self) -> Ty<'tcx>; + fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx>; fn eq>( &self, @@ -32,6 +34,12 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { span: DUMMY_SP, }) } + fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> { + self.next_const_var( + ty, + ConstVariableOrigin { kind: ConstVariableOriginKind::MiscVariable, span: DUMMY_SP }, + ) + } #[instrument(level = "debug", skip(self, param_env), ret)] fn eq>( diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index cf07926f85a..435f2877fb3 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -2,7 +2,7 @@ use crate::traits::{specialization_graph, translate_substs}; use super::assembly::{self, Candidate, CandidateSource}; use super::infcx_ext::InferCtxtExt; -use super::{Certainty, EvalCtxt, Goal, QueryResult}; +use super::{Certainty, EvalCtxt, Goal, MaybeCause, QueryResult}; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -11,19 +11,112 @@ use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::specialization_graph::LeafDef; use rustc_infer::traits::Reveal; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; -use rustc_middle::ty::ProjectionPredicate; use rustc_middle::ty::TypeVisitable; use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{ProjectionPredicate, TypeSuperVisitable, TypeVisitor}; use rustc_span::DUMMY_SP; use std::iter; +use std::ops::ControlFlow; impl<'tcx> EvalCtxt<'_, 'tcx> { pub(super) fn compute_projection_goal( &mut self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, ) -> QueryResult<'tcx> { - let candidates = self.assemble_and_evaluate_candidates(goal); - self.merge_project_candidates(candidates) + // To only compute normalization ones for each projection we only + // normalize if the expected term is an unconstrained inference variable. + // + // E.g. for `::Assoc = u32` we recursively compute the goal + // `exists ::Assoc = U` and then take the resulting type for + // `U` and equate it with `u32`. This means that we don't need a separate + // projection cache in the solver. + if self.term_is_fully_unconstrained(goal) { + let candidates = self.assemble_and_evaluate_candidates(goal); + self.merge_project_candidates(candidates) + } else { + let predicate = goal.predicate; + let unconstrained_rhs = match predicate.term.unpack() { + ty::TermKind::Ty(_) => self.infcx.next_ty_infer().into(), + ty::TermKind::Const(ct) => self.infcx.next_const_infer(ct.ty()).into(), + }; + let unconstrained_predicate = ty::Clause::Projection(ProjectionPredicate { + projection_ty: goal.predicate.projection_ty, + term: unconstrained_rhs, + }); + let (_has_changed, normalize_certainty) = + self.evaluate_goal(goal.with(self.tcx(), unconstrained_predicate))?; + + let nested_eq_goals = + self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?; + let eval_certainty = self.evaluate_all(nested_eq_goals)?; + self.make_canonical_response(normalize_certainty.unify_and(eval_certainty)) + } + } + + /// Is the projection predicate is of the form `exists ::Assoc = T`. + /// + /// This is the case if the `term` is an inference variable in the innermost universe + /// and does not occur in any other part of the predicate. + fn term_is_fully_unconstrained(&self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>) -> bool { + let infcx = self.infcx; + let term_is_infer = match goal.predicate.term.unpack() { + ty::TermKind::Ty(ty) => { + if let &ty::Infer(ty::TyVar(vid)) = ty.kind() { + match infcx.probe_ty_var(vid) { + Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"), + Err(universe) => universe == infcx.universe(), + } + } else { + false + } + } + ty::TermKind::Const(ct) => { + if let ty::ConstKind::Infer(ty::InferConst::Var(vid)) = ct.kind() { + match self.infcx.probe_const_var(vid) { + Ok(value) => bug!("resolved var in query: {goal:?} {value:?}"), + Err(universe) => universe == infcx.universe(), + } + } else { + false + } + } + }; + + struct ContainsTerm<'tcx> { + term: ty::Term<'tcx>, + } + impl<'tcx> TypeVisitor<'tcx> for ContainsTerm<'tcx> { + type BreakTy = (); + fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { + if t.needs_infer() { + if ty::Term::from(t) == self.term { + ControlFlow::BREAK + } else { + t.super_visit_with(self) + } + } else { + ControlFlow::CONTINUE + } + } + + fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow { + if c.needs_infer() { + if ty::Term::from(c) == self.term { + ControlFlow::BREAK + } else { + c.super_visit_with(self) + } + } else { + ControlFlow::CONTINUE + } + } + } + + let mut visitor = ContainsTerm { term: goal.predicate.term }; + + term_is_infer + && goal.predicate.projection_ty.visit_with(&mut visitor).is_continue() + && goal.param_env.visit_with(&mut visitor).is_continue() } fn merge_project_candidates( @@ -124,14 +217,18 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { nested_goals.extend(where_clause_bounds); let trait_ref_certainty = ecx.evaluate_all(nested_goals)?; + // In case the associated item is hidden due to specialization, we have to + // return ambiguity this would otherwise be incomplete, resulting in + // unsoundness during coherence (#105782). let Some(assoc_def) = fetch_eligible_assoc_item_def( ecx.infcx, goal.param_env, goal_trait_ref, goal.predicate.def_id(), impl_def_id - ) else { - return Err(NoSolution); + )? else { + let certainty = Certainty::Maybe(MaybeCause::Ambiguity); + return Ok(trait_ref_certainty.unify_and(certainty)); }; if !assoc_def.item.defaultness(tcx).has_value() { @@ -178,9 +275,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ty.map_bound(|ty| ty.into()) }; - let nested_goals = - ecx.infcx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))?; - let rhs_certainty = ecx.evaluate_all(nested_goals)?; + // The term of our goal should be fully unconstrained, so this should never fail. + // + // It can however be ambiguous when the resolved type is a projection. + let nested_goals = ecx + .infcx + .eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs)) + .expect("failed to unify with unconstrained term"); + let rhs_certainty = + ecx.evaluate_all(nested_goals).expect("failed to unify with unconstrained term"); Ok(trait_ref_certainty.unify_and(rhs_certainty)) }) @@ -217,10 +320,9 @@ fn fetch_eligible_assoc_item_def<'tcx>( goal_trait_ref: ty::TraitRef<'tcx>, trait_assoc_def_id: DefId, impl_def_id: DefId, -) -> Option { +) -> Result, NoSolution> { let node_item = specialization_graph::assoc_def(infcx.tcx, impl_def_id, trait_assoc_def_id) - .map_err(|ErrorGuaranteed { .. }| ()) - .ok()?; + .map_err(|ErrorGuaranteed { .. }| NoSolution)?; let eligible = if node_item.is_final() { // Non-specializable items are always projectable. @@ -239,5 +341,5 @@ fn fetch_eligible_assoc_item_def<'tcx>( } }; - if eligible { Some(node_item) } else { None } + if eligible { Ok(Some(node_item)) } else { Ok(None) } } diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs index 4f48389410b..0030e9aa3e5 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs @@ -105,7 +105,7 @@ impl<'tcx> SearchGraph<'tcx> { } } - /// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with + /// We cannot simply store the result of [super::EvalCtxt::compute_goal] as we have to deal with /// coinductive cycles. /// /// When we encounter a coinductive cycle, we have to prove the final result of that cycle From 415aa663a2cafd07d934dd683db6ab88e65f6c30 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 17 Jan 2023 16:00:00 +0100 Subject: [PATCH 13/14] add note about indirect cycles --- .../rustc_trait_selection/src/solve/search_graph/cache.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs index cb00fe6bf14..730a8e61258 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs @@ -59,6 +59,10 @@ impl<'tcx> ProvisionalCache<'tcx> { /// Adds a dependency from the current leaf to `target` in the cache /// to prevent us from moving any goals which depend on the current leaf /// to the global cache while we're still computing `target`. + /// + /// Its important to note that `target` may already be part of a different cycle. + /// In this case we have to ensure that we also depend on all other goals + /// in the existing cycle in addition to the potentially direct cycle with `target`. pub(super) fn add_dependency_of_leaf_on(&mut self, target: EntryIndex) { let depth = self.entries[target].depth; for provisional_entry in &mut self.entries.raw[target.index()..] { From 369f9aa0990bd90b5f193ce149e74170f276c0e4 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 18 Jan 2023 08:08:58 +0100 Subject: [PATCH 14/14] add comment --- compiler/rustc_trait_selection/src/solve/project_goals.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 435f2877fb3..0658836fb9c 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -82,6 +82,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } }; + // Guard against `>::Assoc = ?0>`. struct ContainsTerm<'tcx> { term: ty::Term<'tcx>, }