diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 5334e7677664d..56fcfe8e798b1 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -181,6 +181,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { } } + fn evaluation_is_concurrent(&self) -> bool { + self.sess.threads() > 1 + } + fn expand_abstract_consts>>(self, t: T) -> T { self.expand_abstract_consts(t) } diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index f2492ede4f5ea..8dec2133a45dd 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -137,6 +137,8 @@ pub trait Interner: f: impl FnOnce(&mut search_graph::GlobalCache) -> R, ) -> R; + fn evaluation_is_concurrent(&self) -> bool; + fn expand_abstract_consts>(self, t: T) -> T; type GenericsOf: GenericsOf; @@ -404,4 +406,7 @@ impl search_graph::Cx for I { ) -> R { I::with_global_cache(self, mode, f) } + fn evaluation_is_concurrent(&self) -> bool { + self.evaluation_is_concurrent() + } } diff --git a/compiler/rustc_type_ir/src/search_graph/global_cache.rs b/compiler/rustc_type_ir/src/search_graph/global_cache.rs index 47f7cefac6ad1..0ce927b58bb38 100644 --- a/compiler/rustc_type_ir/src/search_graph/global_cache.rs +++ b/compiler/rustc_type_ir/src/search_graph/global_cache.rs @@ -44,22 +44,28 @@ impl GlobalCache { cx: X, input: X::Input, - result: X::Result, + origin_result: X::Result, dep_node: X::DepNodeIndex, additional_depth: usize, encountered_overflow: bool, nested_goals: NestedGoals, ) { - let result = cx.mk_tracked(result, dep_node); + let result = cx.mk_tracked(origin_result, dep_node); let entry = self.map.entry(input).or_default(); if encountered_overflow { let with_overflow = WithOverflow { nested_goals, result }; let prev = entry.with_overflow.insert(additional_depth, with_overflow); - assert!(prev.is_none()); + if let Some(prev) = &prev { + assert!(cx.evaluation_is_concurrent()); + assert_eq!(cx.get_tracked(&prev.result), origin_result); + } } else { let prev = entry.success.replace(Success { additional_depth, nested_goals, result }); - assert!(prev.is_none()); + if let Some(prev) = &prev { + assert!(cx.evaluation_is_concurrent()); + assert_eq!(cx.get_tracked(&prev.result), origin_result); + } } } diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 418139c3aadc0..ac4d0795a92e8 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -53,6 +53,8 @@ pub trait Cx: Copy { mode: SolverMode, f: impl FnOnce(&mut GlobalCache) -> R, ) -> R; + + fn evaluation_is_concurrent(&self) -> bool; } pub trait Delegate { diff --git a/tests/ui/traits/next-solver/global-cache-and-parallel-frontend.rs b/tests/ui/traits/next-solver/global-cache-and-parallel-frontend.rs new file mode 100644 index 0000000000000..2b4f7ba9fa29e --- /dev/null +++ b/tests/ui/traits/next-solver/global-cache-and-parallel-frontend.rs @@ -0,0 +1,27 @@ +//@ compile-flags: -Zthreads=16 + +// original issue: https://github.com/rust-lang/rust/issues/129112 +// Previously, the "next" solver asserted that each successful solution is only obtained once. +// This test exhibits a repro that, with next-solver + -Zthreads, triggered that old assert. +// In the presence of multithreaded solving, it's possible to concurrently evaluate things twice, +// which leads to replacing already-solved solutions in the global solution cache! +// We assume this is fine if we check to make sure they are solved the same way each time. + +// This test only nondeterministically fails but that's okay, as it will be rerun by CI many times, +// so it should almost always fail before anything is merged. As other thread tests already exist, +// we already face this difficulty, probably. If we need to fix this by reducing the error margin, +// we should improve compiletest. + +#[derive(Clone, Eq)] //~ ERROR [E0277] +pub struct Struct(T); + +impl PartialEq for Struct +where + U: Into> + Clone +{ + fn eq(&self, _other: &U) -> bool { + todo!() + } +} + +fn main() {} diff --git a/tests/ui/traits/next-solver/global-cache-and-parallel-frontend.stderr b/tests/ui/traits/next-solver/global-cache-and-parallel-frontend.stderr new file mode 100644 index 0000000000000..65e7dd2ab34bc --- /dev/null +++ b/tests/ui/traits/next-solver/global-cache-and-parallel-frontend.stderr @@ -0,0 +1,24 @@ +error[E0277]: the trait bound `T: Clone` is not satisfied + --> $DIR/global-cache-and-parallel-frontend.rs:15:17 + | +LL | #[derive(Clone, Eq)] + | ^^ the trait `Clone` is not implemented for `T`, which is required by `Struct: PartialEq` + | +note: required for `Struct` to implement `PartialEq` + --> $DIR/global-cache-and-parallel-frontend.rs:18:19 + | +LL | impl PartialEq for Struct + | ----- ^^^^^^^^^^^^ ^^^^^^^^^ + | | + | unsatisfied trait bound introduced here +note: required by a bound in `Eq` + --> $SRC_DIR/core/src/cmp.rs:LL:COL + = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider restricting type parameter `T` + | +LL | pub struct Struct(T); + | +++++++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`.