Skip to content

Filter param-env caller bounds to the goal-relevant subset in canonical queries - #101

Draft
xmakro wants to merge 1 commit into
perf/base-0809from
perf/env-relevance
Draft

Filter param-env caller bounds to the goal-relevant subset in canonical queries#101
xmakro wants to merge 1 commit into
perf/base-0809from
perf/env-relevance

Conversation

@xmakro

@xmakro xmakro commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Every canonical query (the type ops, evaluate_obligation, query normalization, implied_outlives_bounds) keys its cache on the full ParamEnv. Two functions proving the literally identical goal get distinct cache keys whenever their where-clauses differ, even when none of those clauses can possibly influence the goal. The degenerate case is a trait method with where Self: 'static: every impl gets a distinct caller bound (S0: 'static, S1: 'static, ...), so the same MIR typeck goals re-run once per impl.

This adds a relevance filter at a new canonicalize_query_with_env_filter entry point, used by the trait-solving query family. For param-free goals it reduces the env to the subset of caller bounds that could participate in proving the goal:

  • Trait-like clauses (trait, projection, host-effect, const-arg/const-evaluatable) are kept only if their head is param-free. Generic params are rigid during solving, and a subgoal of a param-free goal can only mention params introduced by the right-hand sides of already-kept clauses, which a bitmask fixpoint accounts for (e.g. a kept <Vec<u8> as X>::Out == T pulls in T's clauses).
  • Type-outlives clauses are kept for alias or region-carrying subjects; a fully concrete rigid subject (S0: 'static) is vacuous inside a canonical query, since queries only collect region constraints and the env's outlives clauses are consulted caller-side against the unfiltered env.
  • Region-outlives, well-formed, and unstable-feature clauses are always kept.

Goals mentioning inference vars, non-region bound vars (embedded canonical values, e.g. user type annotations), or generic params keep the full env. The gating to param-free goals keeps the per-call cost at a few flag checks plus one hash lookup on a (caller_bounds, mask) cache; there is no goal walk on the hot path. Method probing intentionally stays on the unfiltered canonicalize_query, since it manufactures inference vars that are matched against the env beyond what the goal itself requires.

Clean A/B on the full local rustc-perf suite (from-scratch ThinLTO+jemalloc stage2, instructions:u, vs the 2026-08-09 base): mean -0.311% over 551 cells, 191 significant wins, 0 regressions. Highlights:

benchmark change
wg-grammar-new-solver check/debug/opt full -2.64% / -2.54% / -2.32%
hyper-1.6.0 check full / incr-full -2.25% / -2.38%
image-0.25.6 check full / incr-full -2.20% / -2.29%
projection-caching check full -1.80%
diesel-2.2.10 (all profiles, full + incr-full) -1.08% .. -1.21%
serde-1.0.219 (all profiles) -0.87% .. -0.94%
syn-2.0.101 check full -0.88%
bitmaps-3.2.1 neutral

Microbenchmark and validation detail (stage1, same-binary on/off via RUSTC_ENV_FILTER_DISABLE):

  • Stress case (400 impls with where Self: 'static, identical concrete method bodies): 3.034G -> 2.650G instructions, -12.65%; the control without the where-clause is unchanged, i.e. the filter recovers about two thirds of the pure fragmentation excess.
  • Filter stats on diesel (RUSTC_ENV_FILTER_STATS): 377k canonical queries, 90% served from the filtered-env cache, ~8k distinct filtered envs; executions drop across the family (evaluate_obligation -25%, type_op_prove_predicate -20%, dropck_outlives hits 5x) and the dep graph loses 28k nodes on this crate.
  • ui suite: 21720 passed, 0 failed.

Follow-ups worth exploring: extending filtering to goals that mention params (needs a cheap per-goal param mask, e.g. collected during canonicalization itself; a full-relevance prototype deduped substantially more but the per-call goal walk cost more than it saved), applying the same relevance test to the global selection/evaluation cache keys, and dropping region-outlives clauses once their in-query uses are audited.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant