Auto merge of #150748 - nnethercote:canonicalizer-cleanups, r=lcnr

Canonicalizer cleanups

Some cleanups in and around the canonicalizers, found while I was looking closely at this code.

r? @lcnr
This commit is contained in:
rust-bors[bot] 2026-01-11 22:58:38 +00:00 committed by GitHub
commit 44a5b55557
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 163 additions and 154 deletions

View file

@ -64,7 +64,7 @@ pub(super) struct Canonicalizer<'a, D: SolverDelegate<Interner = I>, I: Interner
canonicalize_mode: CanonicalizeMode,
// Mutable fields.
variables: &'a mut Vec<I::GenericArg>,
variables: Vec<I::GenericArg>,
var_kinds: Vec<CanonicalVarKind<I>>,
variable_lookup_table: HashMap<I::GenericArg, usize>,
/// Maps each `sub_unification_table_root_var` to the index of the first
@ -84,14 +84,13 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
pub(super) fn canonicalize_response<T: TypeFoldable<I>>(
delegate: &'a D,
max_input_universe: ty::UniverseIndex,
variables: &'a mut Vec<I::GenericArg>,
value: T,
) -> ty::Canonical<I, T> {
let mut canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Response { max_input_universe },
variables,
variables: Vec::new(),
variable_lookup_table: Default::default(),
sub_root_lookup_table: Default::default(),
var_kinds: Vec::new(),
@ -106,16 +105,17 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
};
debug_assert!(!value.has_infer(), "unexpected infer in {value:?}");
debug_assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}");
let (max_universe, variables) = canonicalizer.finalize();
Canonical { max_universe, variables, value }
let (max_universe, _variables, var_kinds) = canonicalizer.finalize();
Canonical { max_universe, var_kinds, value }
}
fn canonicalize_param_env(
delegate: &'a D,
variables: &'a mut Vec<I::GenericArg>,
param_env: I::ParamEnv,
) -> (I::ParamEnv, HashMap<I::GenericArg, usize>, Vec<CanonicalVarKind<I>>) {
) -> (I::ParamEnv, Vec<I::GenericArg>, Vec<CanonicalVarKind<I>>, HashMap<I::GenericArg, usize>)
{
if !param_env.has_type_flags(NEEDS_CANONICAL) {
return (param_env, Default::default(), Vec::new());
return (param_env, Vec::new(), Vec::new(), Default::default());
}
// Check whether we can use the global cache for this param_env. As we only use
@ -129,12 +129,11 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
delegate.cx().canonical_param_env_cache_get_or_insert(
param_env,
|| {
let mut variables = Vec::new();
let mut env_canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv),
variables: &mut variables,
variables: Vec::new(),
variable_lookup_table: Default::default(),
sub_root_lookup_table: Default::default(),
var_kinds: Vec::new(),
@ -147,7 +146,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
param_env,
variable_lookup_table: env_canonicalizer.variable_lookup_table,
var_kinds: env_canonicalizer.var_kinds,
variables,
variables: env_canonicalizer.variables,
}
},
|&CanonicalParamEnvCacheEntry {
@ -156,9 +155,12 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
ref variable_lookup_table,
ref var_kinds,
}| {
debug_assert!(variables.is_empty());
// FIXME(nnethercote): for reasons I don't understand, this `new`+`extend`
// combination is faster than `variables.clone()`, because it somehow avoids
// some allocations.
let mut variables = Vec::new();
variables.extend(cache_variables.iter().copied());
(param_env, variable_lookup_table.clone(), var_kinds.clone())
(param_env, variables, var_kinds.clone(), variable_lookup_table.clone())
},
)
} else {
@ -166,7 +168,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
delegate,
canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv),
variables,
variables: Vec::new(),
variable_lookup_table: Default::default(),
sub_root_lookup_table: Default::default(),
var_kinds: Vec::new(),
@ -175,7 +177,12 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
};
let param_env = param_env.fold_with(&mut env_canonicalizer);
debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty());
(param_env, env_canonicalizer.variable_lookup_table, env_canonicalizer.var_kinds)
(
param_env,
env_canonicalizer.variables,
env_canonicalizer.var_kinds,
env_canonicalizer.variable_lookup_table,
)
}
}
@ -189,12 +196,11 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
/// variable in the future by changing the way we detect global where-bounds.
pub(super) fn canonicalize_input<P: TypeFoldable<I>>(
delegate: &'a D,
variables: &'a mut Vec<I::GenericArg>,
input: QueryInput<I, P>,
) -> ty::Canonical<I, QueryInput<I, P>> {
) -> (Vec<I::GenericArg>, ty::Canonical<I, QueryInput<I, P>>) {
// First canonicalize the `param_env` while keeping `'static`
let (param_env, variable_lookup_table, var_kinds) =
Canonicalizer::canonicalize_param_env(delegate, variables, input.goal.param_env);
let (param_env, variables, var_kinds, variable_lookup_table) =
Canonicalizer::canonicalize_param_env(delegate, input.goal.param_env);
// Then canonicalize the rest of the input without keeping `'static`
// while *mostly* reusing the canonicalizer from above.
let mut rest_canonicalizer = Canonicalizer {
@ -234,8 +240,8 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
debug_assert!(!value.has_infer(), "unexpected infer in {value:?}");
debug_assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}");
let (max_universe, variables) = rest_canonicalizer.finalize();
Canonical { max_universe, variables, value }
let (max_universe, variables, var_kinds) = rest_canonicalizer.finalize();
(variables, Canonical { max_universe, var_kinds, value })
}
fn get_or_insert_bound_var(
@ -243,8 +249,9 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
arg: impl Into<I::GenericArg>,
kind: CanonicalVarKind<I>,
) -> ty::BoundVar {
// FIXME: 16 is made up and arbitrary. We should look at some
// perf data here.
// The exact value of 16 here doesn't matter that much (8 and 32 give extremely similar
// results). So long as we have protection against the rare cases where the length reaches
// 1000+ (e.g. `wg-grammar`).
let arg = arg.into();
let idx = if self.variables.len() > 16 {
if self.variable_lookup_table.is_empty() {
@ -276,19 +283,18 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
ty::BoundVar::from(idx)
}
fn finalize(self) -> (ty::UniverseIndex, I::CanonicalVarKinds) {
fn finalize(self) -> (ty::UniverseIndex, Vec<I::GenericArg>, I::CanonicalVarKinds) {
let mut var_kinds = self.var_kinds;
// See the rustc-dev-guide section about how we deal with universes
// during canonicalization in the new solver.
match self.canonicalize_mode {
let max_universe = match self.canonicalize_mode {
// All placeholders and vars are canonicalized in the root universe.
CanonicalizeMode::Input { .. } => {
debug_assert!(
var_kinds.iter().all(|var| var.universe() == ty::UniverseIndex::ROOT),
"expected all vars to be canonicalized in root universe: {var_kinds:#?}"
);
let var_kinds = self.delegate.cx().mk_canonical_var_kinds(&var_kinds);
(ty::UniverseIndex::ROOT, var_kinds)
ty::UniverseIndex::ROOT
}
// When canonicalizing a response we map a universes already entered
// by the caller to the root universe and only return useful universe
@ -302,15 +308,15 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
);
*var = var.with_updated_universe(new_uv);
}
let max_universe = var_kinds
var_kinds
.iter()
.map(|kind| kind.universe())
.max()
.unwrap_or(ty::UniverseIndex::ROOT);
let var_kinds = self.delegate.cx().mk_canonical_var_kinds(&var_kinds);
(max_universe, var_kinds)
.unwrap_or(ty::UniverseIndex::ROOT)
}
}
};
let var_kinds = self.delegate.cx().mk_canonical_var_kinds(&var_kinds);
(max_universe, self.variables, var_kinds)
}
fn inner_fold_ty(&mut self, t: I::Ty) -> I::Ty {
@ -417,7 +423,7 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
// We don't canonicalize `ReStatic` in the `param_env` as we use it
// when checking whether a `ParamEnv` candidate is global.
ty::ReStatic => match self.canonicalize_mode {
CanonicalizeMode::Input(CanonicalizeInputKind::Predicate { .. }) => {
CanonicalizeMode::Input(CanonicalizeInputKind::Predicate) => {
CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
}
CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv)
@ -545,7 +551,7 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
match self.canonicalize_mode {
CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv)
| CanonicalizeMode::Response { max_input_universe: _ } => {}
CanonicalizeMode::Input(CanonicalizeInputKind::Predicate { .. }) => {
CanonicalizeMode::Input(CanonicalizeInputKind::Predicate) => {
panic!("erasing 'static in env")
}
}

View file

@ -59,10 +59,8 @@ where
D: SolverDelegate<Interner = I>,
I: Interner,
{
let mut orig_values = Default::default();
let canonical = Canonicalizer::canonicalize_input(
let (orig_values, canonical) = Canonicalizer::canonicalize_input(
delegate,
&mut orig_values,
QueryInput {
goal,
predefined_opaques_in_body: delegate.cx().mk_predefined_opaques_in_body(opaque_types),
@ -82,10 +80,7 @@ where
I: Interner,
T: TypeFoldable<I>,
{
let mut orig_values = Default::default();
let canonical =
Canonicalizer::canonicalize_response(delegate, max_input_universe, &mut orig_values, value);
canonical
Canonicalizer::canonicalize_response(delegate, max_input_universe, value)
}
/// After calling a canonical query, we apply the constraints returned
@ -157,7 +152,7 @@ where
//
// We therefore instantiate the existential variable in the canonical response with the
// inference variable of the input right away, which is more performant.
let mut opt_values = IndexVec::from_elem_n(None, response.variables.len());
let mut opt_values = IndexVec::from_elem_n(None, response.var_kinds.len());
for (original_value, result_value) in iter::zip(original_values, var_values.var_values.iter()) {
match result_value.kind() {
ty::GenericArgKind::Type(t) => {
@ -167,7 +162,7 @@ where
// more involved. They are also a lot rarer than region variables.
if let ty::Bound(index_kind, b) = t.kind()
&& !matches!(
response.variables.get(b.var().as_usize()).unwrap(),
response.var_kinds.get(b.var().as_usize()).unwrap(),
CanonicalVarKind::Ty { .. }
)
{
@ -189,7 +184,7 @@ where
}
}
}
CanonicalVarValues::instantiate(delegate.cx(), response.variables, |var_values, kind| {
CanonicalVarValues::instantiate(delegate.cx(), response.var_kinds, |var_values, kind| {
if kind.universe() != ty::UniverseIndex::ROOT {
// A variable from inside a binder of the query. While ideally these shouldn't
// exist at all (see the FIXME at the start of this method), we have to deal with
@ -308,7 +303,7 @@ where
let var_values = CanonicalVarValues { var_values: delegate.cx().mk_args(var_values) };
let state = inspect::State { var_values, data };
let state = eager_resolve_vars(delegate, state);
Canonicalizer::canonicalize_response(delegate, max_input_universe, &mut vec![], state)
Canonicalizer::canonicalize_response(delegate, max_input_universe, state)
}
// FIXME: needs to be pub to be accessed by downstream
@ -345,14 +340,14 @@ where
pub fn response_no_constraints_raw<I: Interner>(
cx: I,
max_universe: ty::UniverseIndex,
variables: I::CanonicalVarKinds,
var_kinds: I::CanonicalVarKinds,
certainty: Certainty,
) -> CanonicalResponse<I> {
ty::Canonical {
max_universe,
variables,
var_kinds,
value: Response {
var_values: ty::CanonicalVarValues::make_identity(cx, variables),
var_values: ty::CanonicalVarValues::make_identity(cx, var_kinds),
// FIXME: maybe we should store the "no response" version in cx, like
// we do for cx.types and stuff.
external_constraints: cx.mk_external_constraints(ExternalConstraintsData::default()),

View file

@ -97,7 +97,7 @@ where
/// The variable info for the `var_values`, only used to make an ambiguous response
/// with no constraints.
variables: I::CanonicalVarKinds,
var_kinds: I::CanonicalVarKinds,
/// What kind of goal we're currently computing, see the enum definition
/// for more info.
@ -325,7 +325,7 @@ where
// which we don't do within this evaluation context.
max_input_universe: ty::UniverseIndex::ROOT,
initial_opaque_types_storage_num_entries: Default::default(),
variables: Default::default(),
var_kinds: Default::default(),
var_values: CanonicalVarValues::dummy(),
current_goal_kind: CurrentGoalKind::Misc,
origin_span,
@ -376,7 +376,7 @@ where
let initial_opaque_types_storage_num_entries = delegate.opaque_types_storage_num_entries();
let mut ecx = EvalCtxt {
delegate,
variables: canonical_input.canonical.variables,
var_kinds: canonical_input.canonical.var_kinds,
var_values,
current_goal_kind: CurrentGoalKind::from_query_input(cx, input),
max_input_universe: canonical_input.canonical.max_universe,
@ -1323,7 +1323,7 @@ where
response_no_constraints_raw(
self.cx(),
self.max_input_universe,
self.variables,
self.var_kinds,
Certainty::Maybe { cause, opaque_types_jank },
)
}

View file

@ -47,7 +47,7 @@ where
let max_input_universe = outer.max_input_universe;
let mut nested = EvalCtxt {
delegate,
variables: outer.variables,
var_kinds: outer.var_kinds,
var_values: outer.var_values,
current_goal_kind: outer.current_goal_kind,
max_input_universe,

View file

@ -143,7 +143,7 @@ fn response_no_constraints<I: Interner>(
Ok(response_no_constraints_raw(
cx,
input.canonical.max_universe,
input.canonical.variables,
input.canonical.var_kinds,
certainty,
))
}