Skip to content

Commit

Permalink
Auto merge of #69808 - cjgillot:vtbl, r=<try>
Browse files Browse the repository at this point in the history
Avoid duplicating code for each query

There are at the moment roughly 170 queries in librustc.
The way `ty::query` is structured, a lot of code is duplicated for each query.
I suspect this to be responsible for a part of librustc'c compile time.

The first part of this PR reduces the amount of code generic on the query,
replacing it by code generic on the key-value types. I can split it out if needed.

In a second part, the non-inlined methods in the `QueryAccessors` and `QueryDescription` traits
are made into a virtual dispatch table. This allows to reduce even more the number of generated
functions.

This allows to save 1.5s on check build, and 10% on the size of the librustc.rlib.
(Attributed roughly half and half).
My computer is not good enough to measure properly compiling time.
I have no idea of the effect on performance. A perf run may be required.

cc #65031
  • Loading branch information
bors committed Mar 7, 2020
2 parents 823ff8c + f12bdf2 commit 576b7b6
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 259 deletions.
60 changes: 35 additions & 25 deletions src/librustc/ty/query/caches.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,93 @@
use crate::dep_graph::DepNodeIndex;
use crate::ty::query::config::QueryAccessors;
use crate::ty::query::plumbing::{QueryLookup, QueryState, QueryStateShard};
use crate::ty::TyCtxt;

use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sharded::Sharded;
use std::default::Default;
use std::hash::Hash;
use std::marker::PhantomData;

pub(crate) trait CacheSelector<K, V> {
type Cache: QueryCache<K, V>;
type Cache: QueryCache<Key = K, Value = V>;
}

pub(crate) trait QueryCache<K, V>: Default {
pub(crate) trait QueryCache: Default {
type Key;
type Value;
type Sharded: Default;

/// Checks if the query is already computed and in the cache.
/// It returns the shard index and a lock guard to the shard,
/// which will be used if the query is not in the cache and we need
/// to compute it.
fn lookup<'tcx, R, GetCache, OnHit, OnMiss, Q>(
fn lookup<'tcx, R, GetCache, OnHit, OnMiss>(
&self,
state: &'tcx QueryState<'tcx, Q>,
state: &'tcx QueryState<'tcx, Self>,
get_cache: GetCache,
key: K,
key: Self::Key,
// `on_hit` can be called while holding a lock to the query state shard.
on_hit: OnHit,
on_miss: OnMiss,
) -> R
where
Q: QueryAccessors<'tcx>,
GetCache: for<'a> Fn(&'a mut QueryStateShard<'tcx, Q>) -> &'a mut Self::Sharded,
OnHit: FnOnce(&V, DepNodeIndex) -> R,
OnMiss: FnOnce(K, QueryLookup<'tcx, Q>) -> R;
GetCache: for<'a> Fn(
&'a mut QueryStateShard<'tcx, Self::Key, Self::Sharded>,
) -> &'a mut Self::Sharded,
OnHit: FnOnce(&Self::Value, DepNodeIndex) -> R,
OnMiss: FnOnce(Self::Key, QueryLookup<'tcx, Self::Key, Self::Sharded>) -> R;

fn complete(
&self,
tcx: TyCtxt<'tcx>,
lock_sharded_storage: &mut Self::Sharded,
key: K,
value: V,
key: Self::Key,
value: Self::Value,
index: DepNodeIndex,
);

fn iter<R, L>(
&self,
shards: &Sharded<L>,
get_shard: impl Fn(&mut L) -> &mut Self::Sharded,
f: impl for<'a> FnOnce(Box<dyn Iterator<Item = (&'a K, &'a V, DepNodeIndex)> + 'a>) -> R,
f: impl for<'a> FnOnce(
Box<dyn Iterator<Item = (&'a Self::Key, &'a Self::Value, DepNodeIndex)> + 'a>,
) -> R,
) -> R;
}

pub struct DefaultCacheSelector;

impl<K: Eq + Hash, V: Clone> CacheSelector<K, V> for DefaultCacheSelector {
type Cache = DefaultCache;
type Cache = DefaultCache<K, V>;
}

#[derive(Default)]
pub struct DefaultCache;
pub struct DefaultCache<K, V>(PhantomData<(K, V)>);

impl<K: Eq + Hash, V: Clone> QueryCache<K, V> for DefaultCache {
impl<K, V> Default for DefaultCache<K, V> {
fn default() -> Self {
DefaultCache(PhantomData)
}
}

impl<K: Eq + Hash, V: Clone> QueryCache for DefaultCache<K, V> {
type Key = K;
type Value = V;
type Sharded = FxHashMap<K, (V, DepNodeIndex)>;

#[inline(always)]
fn lookup<'tcx, R, GetCache, OnHit, OnMiss, Q>(
fn lookup<'tcx, R, GetCache, OnHit, OnMiss>(
&self,
state: &'tcx QueryState<'tcx, Q>,
state: &'tcx QueryState<'tcx, Self>,
get_cache: GetCache,
key: K,
// `on_hit` can be called while holding a lock to the query state shard.
on_hit: OnHit,
on_miss: OnMiss,
) -> R
where
Q: QueryAccessors<'tcx>,
GetCache: for<'a> Fn(&'a mut QueryStateShard<'tcx, Q>) -> &'a mut Self::Sharded,
GetCache:
for<'a> Fn(&'a mut QueryStateShard<'tcx, K, Self::Sharded>) -> &'a mut Self::Sharded,
OnHit: FnOnce(&V, DepNodeIndex) -> R,
OnMiss: FnOnce(K, QueryLookup<'tcx, Q>) -> R,
OnMiss: FnOnce(K, QueryLookup<'tcx, K, Self::Sharded>) -> R,
{
let mut lookup = state.get_lookup(&key);
let lock = &mut *lookup.lock;
Expand All @@ -89,7 +100,6 @@ impl<K: Eq + Hash, V: Clone> QueryCache<K, V> for DefaultCache {
#[inline]
fn complete(
&self,
_: TyCtxt<'tcx>,
lock_sharded_storage: &mut Self::Sharded,
key: K,
value: V,
Expand Down
75 changes: 63 additions & 12 deletions src/librustc/ty/query/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::dep_graph::{DepKind, DepNode};
use crate::ty::query::caches::QueryCache;
use crate::ty::query::plumbing::CycleError;
use crate::ty::query::queries;
use crate::ty::query::{Query, QueryState};
use crate::ty::query::QueryState;
use crate::ty::TyCtxt;
use rustc_data_structures::profiling::ProfileCategory;
use rustc_hir::def_id::{CrateNum, DefId};
Expand All @@ -26,23 +26,65 @@ pub trait QueryConfig<'tcx> {
type Value: Clone;
}

pub(crate) struct QueryVtable<'tcx, K, V> {
pub anon: bool,
pub dep_kind: DepKind,
pub eval_always: bool,
pub name: &'static str,

// Don't use this method to compute query results, instead use the methods on TyCtxt
pub compute: fn(TyCtxt<'tcx>, K) -> V,

pub hash_result: fn(&mut StableHashingContext<'_>, &V) -> Option<Fingerprint>,
pub handle_cycle_error: fn(TyCtxt<'tcx>, CycleError<'tcx>) -> V,
pub cache_on_disk: fn(TyCtxt<'tcx>, K, Option<&V>) -> bool,
pub try_load_from_disk: fn(TyCtxt<'tcx>, SerializedDepNodeIndex) -> Option<V>,
}

impl<'tcx, K, V> QueryVtable<'tcx, K, V> {
pub(crate) fn compute(&self, tcx: TyCtxt<'tcx>, key: K) -> V {
(self.compute)(tcx, key)
}

pub(crate) fn hash_result(
&self,
hcx: &mut StableHashingContext<'_>,
value: &V,
) -> Option<Fingerprint> {
(self.hash_result)(hcx, value)
}

pub(crate) fn handle_cycle_error(&self, tcx: TyCtxt<'tcx>, error: CycleError<'tcx>) -> V {
(self.handle_cycle_error)(tcx, error)
}

pub(crate) fn cache_on_disk(&self, tcx: TyCtxt<'tcx>, key: K, value: Option<&V>) -> bool {
(self.cache_on_disk)(tcx, key, value)
}

pub(crate) fn try_load_from_disk(
&self,
tcx: TyCtxt<'tcx>,
index: SerializedDepNodeIndex,
) -> Option<V> {
(self.try_load_from_disk)(tcx, index)
}
}

pub(crate) trait QueryAccessors<'tcx>: QueryConfig<'tcx> {
const ANON: bool;
const EVAL_ALWAYS: bool;
const DEP_KIND: DepKind;

type Cache: QueryCache<Self::Key, Self::Value>;

fn query(key: Self::Key) -> Query<'tcx>;
type Cache: QueryCache<Key = Self::Key, Value = Self::Value>;

// Don't use this method to access query results, instead use the methods on TyCtxt
fn query_state<'a>(tcx: TyCtxt<'tcx>) -> &'a QueryState<'tcx, Self>;
fn query_state<'a>(tcx: TyCtxt<'tcx>) -> &'a QueryState<'tcx, Self::Cache>;

fn to_dep_node(tcx: TyCtxt<'tcx>, key: &Self::Key) -> DepNode;

fn dep_kind() -> DepKind;

// Don't use this method to compute query results, instead use the methods on TyCtxt
fn compute(tcx: TyCtxt<'tcx>, key: Self::Key) -> Self::Value;
const COMPUTE_FN: fn(TyCtxt<'tcx>, Self::Key) -> Self::Value;

fn hash_result(hcx: &mut StableHashingContext<'_>, result: &Self::Value)
-> Option<Fingerprint>;
Expand All @@ -61,12 +103,21 @@ pub(crate) trait QueryDescription<'tcx>: QueryAccessors<'tcx> {
fn try_load_from_disk(_: TyCtxt<'tcx>, _: SerializedDepNodeIndex) -> Option<Self::Value> {
bug!("QueryDescription::load_from_disk() called for an unsupported query.")
}

const VTABLE: QueryVtable<'tcx, Self::Key, Self::Value> = QueryVtable {
anon: Self::ANON,
dep_kind: Self::DEP_KIND,
eval_always: Self::EVAL_ALWAYS,
name: Self::NAME,
compute: Self::COMPUTE_FN,
hash_result: Self::hash_result,
handle_cycle_error: Self::handle_cycle_error,
cache_on_disk: Self::cache_on_disk,
try_load_from_disk: Self::try_load_from_disk,
};
}

impl<'tcx, M: QueryAccessors<'tcx, Key = DefId>> QueryDescription<'tcx> for M
where
<M as QueryAccessors<'tcx>>::Cache: QueryCache<DefId, <M as QueryConfig<'tcx>>::Value>,
{
impl<'tcx, M: QueryAccessors<'tcx, Key = DefId>> QueryDescription<'tcx> for M {
default fn describe(tcx: TyCtxt<'_>, def_id: DefId) -> Cow<'static, str> {
if !tcx.sess.verbose() {
format!("processing `{}`", tcx.def_path_str(def_id)).into()
Expand Down
1 change: 0 additions & 1 deletion src/librustc/ty/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ use rustc_attr as attr;
use rustc_span::symbol::Symbol;
use rustc_span::{Span, DUMMY_SP};
use std::borrow::Cow;
use std::convert::TryFrom;
use std::ops::Deref;
use std::sync::Arc;

Expand Down
22 changes: 13 additions & 9 deletions src/librustc/ty/query/on_disk_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::mir::{self, interpret};
use crate::session::{CrateDisambiguator, Session};
use crate::ty::codec::{self as ty_codec, TyDecoder, TyEncoder};
use crate::ty::context::TyCtxt;
use crate::ty::query::config::{QueryAccessors, QueryDescription, QueryVtable};
use crate::ty::{self, Ty};
use rustc_ast::ast::Ident;
use rustc_data_structures::fx::FxHashMap;
Expand Down Expand Up @@ -205,8 +206,10 @@ impl<'sess> OnDiskCache<'sess> {
macro_rules! encode_queries {
($($query:ident,)*) => {
$(
encode_query_results::<ty::query::queries::$query<'_>, _>(
encode_query_results(
tcx,
ty::query::queries::$query::query_state(tcx),
&ty::query::queries::$query::VTABLE,
enc,
qri
)?;
Expand Down Expand Up @@ -1022,26 +1025,27 @@ impl<'a> SpecializedDecoder<IntEncodedWithFixedSize> for opaque::Decoder<'a> {
}
}

fn encode_query_results<'a, 'tcx, Q, E>(
fn encode_query_results<'a, 'tcx, C, E>(
tcx: TyCtxt<'tcx>,
state: &'a super::plumbing::QueryState<'tcx, C>,
query: &QueryVtable<'tcx, C::Key, C::Value>,
encoder: &mut CacheEncoder<'a, 'tcx, E>,
query_result_index: &mut EncodedQueryResultIndex,
) -> Result<(), E::Error>
where
Q: super::config::QueryDescription<'tcx, Value: Encodable>,
C: super::caches::QueryCache,
E: 'a + TyEncoder,
C::Key: Clone,
C::Value: Encodable,
{
let _timer = tcx
.sess
.prof
.extra_verbose_generic_activity("encode_query_results_for", ::std::any::type_name::<Q>());
let _timer =
tcx.sess.prof.extra_verbose_generic_activity("encode_query_results_for", query.name);

let state = Q::query_state(tcx);
assert!(state.all_inactive());

state.iter_results(|results| {
for (key, value, dep_node) in results {
if Q::cache_on_disk(tcx, key.clone(), Some(&value)) {
if query.cache_on_disk(tcx, key.clone(), Some(&value)) {
let dep_node = SerializedDepNodeIndex::new(dep_node.index());

// Record position of the cache entry.
Expand Down
Loading

0 comments on commit 576b7b6

Please sign in to comment.