Skip to content

Commit

Permalink
implement a performant and fuzzed solver cache
Browse files Browse the repository at this point in the history
  • Loading branch information
lcnr committed Aug 13, 2024
1 parent f860873 commit a634209
Show file tree
Hide file tree
Showing 4 changed files with 736 additions and 359 deletions.
52 changes: 31 additions & 21 deletions compiler/rustc_next_trait_solver/src/solve/search_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::convert::Infallible;
use std::marker::PhantomData;

use rustc_type_ir::inherent::*;
use rustc_type_ir::search_graph::{self, CycleKind, UsageKind};
use rustc_type_ir::search_graph::{self, PathKind};
use rustc_type_ir::solve::{CanonicalInput, Certainty, QueryResult};
use rustc_type_ir::Interner;

use super::inspect::ProofTreeBuilder;
use super::FIXPOINT_STEP_LIMIT;
use super::{has_no_inference_or_external_constraints, FIXPOINT_STEP_LIMIT};
use crate::delegate::SolverDelegate;

/// This type is never constructed. We only use it to implement `search_graph::Delegate`
Expand All @@ -23,10 +23,11 @@ where
{
type Cx = D::Interner;

const ENABLE_PROVISIONAL_CACHE: bool = true;
type ValidationScope = Infallible;
fn enter_validation_scope(
_cx: Self::Cx,
_input: <Self::Cx as search_graph::Cx>::Input,
_input: CanonicalInput<I>,
) -> Option<Self::ValidationScope> {
None
}
Expand All @@ -38,39 +39,32 @@ where
inspect.is_noop()
}

const DIVIDE_AVAILABLE_DEPTH_ON_OVERFLOW: usize = 4;
fn recursion_limit(cx: I) -> usize {
cx.recursion_limit()
}

fn initial_provisional_result(
cx: I,
kind: CycleKind,
kind: PathKind,
input: CanonicalInput<I>,
) -> QueryResult<I> {
match kind {
CycleKind::Coinductive => response_no_constraints(cx, input, Certainty::Yes),
CycleKind::Inductive => response_no_constraints(cx, input, Certainty::overflow(false)),
PathKind::Coinductive => response_no_constraints(cx, input, Certainty::Yes),
PathKind::Inductive => response_no_constraints(cx, input, Certainty::overflow(false)),
}
}

fn reached_fixpoint(
cx: I,
kind: UsageKind,
fn is_initial_provisional_result(
cx: Self::Cx,
kind: PathKind,
input: CanonicalInput<I>,
provisional_result: Option<QueryResult<I>>,
result: QueryResult<I>,
) -> bool {
if let Some(r) = provisional_result {
r == result
} else {
match kind {
UsageKind::Single(CycleKind::Coinductive) => {
response_no_constraints(cx, input, Certainty::Yes) == result
}
UsageKind::Single(CycleKind::Inductive) => {
response_no_constraints(cx, input, Certainty::overflow(false)) == result
}
UsageKind::Mixed => false,
match kind {
PathKind::Coinductive => response_no_constraints(cx, input, Certainty::Yes) == result,
PathKind::Inductive => {
response_no_constraints(cx, input, Certainty::overflow(false)) == result
}
}
}
Expand All @@ -88,6 +82,22 @@ where
response_no_constraints(cx, input, Certainty::overflow(false))
}

fn is_ambiguous_result(result: QueryResult<I>) -> bool {
result.is_ok_and(|response| {
has_no_inference_or_external_constraints(response)
&& matches!(response.value.certainty, Certainty::Maybe(_))
})
}

fn propagate_ambiguity(
cx: I,
for_input: CanonicalInput<I>,
from_result: QueryResult<I>,
) -> QueryResult<I> {
let certainty = from_result.unwrap().value.certainty;
response_no_constraints(cx, for_input, certainty)
}

fn step_is_coinductive(cx: I, input: CanonicalInput<I>) -> bool {
input.value.goal.predicate.is_coinductive(cx)
}
Expand Down
72 changes: 39 additions & 33 deletions compiler/rustc_type_ir/src/search_graph/global_cache.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use derive_where::derive_where;
use rustc_index::IndexVec;

use super::{AvailableDepth, Cx, StackDepth, StackEntry};
use crate::data_structures::{HashMap, HashSet};
use super::{AvailableDepth, Cx, NestedGoals};
use crate::data_structures::HashMap;

struct Success<X: Cx> {
result: X::Tracked<X::Result>,
additional_depth: usize,
nested_goals: NestedGoals<X>,
result: X::Tracked<X::Result>,
}

struct WithOverflow<X: Cx> {
nested_goals: NestedGoals<X>,
result: X::Tracked<X::Result>,
}

/// The cache entry for a given input.
Expand All @@ -17,23 +22,15 @@ struct Success<X: Cx> {
#[derive_where(Default; X: Cx)]
struct CacheEntry<X: Cx> {
success: Option<Success<X>>,
/// We have to be careful when caching roots of cycles.
///
/// See the doc comment of `StackEntry::cycle_participants` for more
/// details.
nested_goals: HashSet<X::Input>,
with_overflow: HashMap<usize, X::Tracked<X::Result>>,
with_overflow: HashMap<usize, WithOverflow<X>>,
}

#[derive_where(Debug; X: Cx)]
pub(super) struct CacheData<'a, X: Cx> {
pub(super) result: X::Result,
pub(super) additional_depth: usize,
pub(super) encountered_overflow: bool,
// FIXME: This is currently unused, but impacts the design
// by requiring a closure for `Cx::with_global_cache`.
#[allow(dead_code)]
pub(super) nested_goals: &'a HashSet<X::Input>,
pub(super) nested_goals: &'a NestedGoals<X>,
}
#[derive_where(Default; X: Cx)]
pub struct GlobalCache<X: Cx> {
Expand All @@ -52,15 +49,17 @@ impl<X: Cx> GlobalCache<X> {

additional_depth: usize,
encountered_overflow: bool,
nested_goals: &HashSet<X::Input>,
nested_goals: NestedGoals<X>,
) {
let result = cx.mk_tracked(result, dep_node);
let entry = self.map.entry(input).or_default();
entry.nested_goals.extend(nested_goals);
if encountered_overflow {
entry.with_overflow.insert(additional_depth, result);
let with_overflow = WithOverflow { nested_goals, result };
let prev = entry.with_overflow.insert(additional_depth, with_overflow);
assert!(prev.is_none());
} else {
entry.success = Some(Success { result, additional_depth });
let prev = entry.success.replace(Success { additional_depth, nested_goals, result });
assert!(prev.is_none());
}
}

Expand All @@ -72,30 +71,37 @@ impl<X: Cx> GlobalCache<X> {
&'a self,
cx: X,
input: X::Input,
stack: &IndexVec<StackDepth, StackEntry<X>>,
available_depth: AvailableDepth,
mut candidate_is_applicable: impl FnMut(&NestedGoals<X>) -> bool,
) -> Option<CacheData<'a, X>> {
let entry = self.map.get(&input)?;
if stack.iter().any(|e| entry.nested_goals.contains(&e.input)) {
return None;
if let Some(Success { additional_depth, ref nested_goals, ref result }) = entry.success {
if available_depth.cache_entry_is_applicable(additional_depth)
&& candidate_is_applicable(nested_goals)
{
return Some(CacheData {
result: cx.get_tracked(&result),
additional_depth,
encountered_overflow: false,
nested_goals,
});
}
}

if let Some(ref success) = entry.success {
if available_depth.cache_entry_is_applicable(success.additional_depth) {
let additional_depth = available_depth.0;
if let Some(WithOverflow { nested_goals, result }) =
entry.with_overflow.get(&additional_depth)
{
if candidate_is_applicable(nested_goals) {
return Some(CacheData {
result: cx.get_tracked(&success.result),
additional_depth: success.additional_depth,
encountered_overflow: false,
nested_goals: &entry.nested_goals,
result: cx.get_tracked(result),
additional_depth,
encountered_overflow: true,
nested_goals,
});
}
}

entry.with_overflow.get(&available_depth.0).map(|e| CacheData {
result: cx.get_tracked(e),
additional_depth: available_depth.0,
encountered_overflow: true,
nested_goals: &entry.nested_goals,
})
None
}
}
Loading

0 comments on commit a634209

Please sign in to comment.