2396: Switch to variant-granularity field type inference r=flodiebold a=matklad

r? @flodiebold 

Previously, we had a `ty` query for each field. This PR switcthes to a query per struct, which returns an `ArenaMap` with `Ty`s. 

I don't know which approach is better. What is bugging me about the original approach is that, if we do all queries on the "leaf" defs, in practice we get a ton of queries which repeatedly reach into the parent definition to compute module, resolver, etc. This *seems* wasteful (but I don't think this is really what causes any perf problems for us). 

At the same time, I've been looking at Kotlin, and they seem to use the general pattern of analyzing the *parent* definition, and storing info about children into a `BindingContext`. 

I don't really which way is preferable. I think I want to try this approach, where query granularity generally mirrors the data granularity. The primary motivation for me here is probably just hope that we can avoid adding a ton of helpers to a `StructField`, and maybe in general avoid the need to switch to a global `StructField`, using `LocalStructFieldId` most of the time internally. 

For external API (ie, for `ra_ide_api`), I think we should continue with fine-grained `StructField::ty` approach, which internally fetches the table for the whole struct and indexes into it.

In terms of actual memory savings, the results are as follows:

```
This PR:
   142kb FieldTypesQuery (deps)
    38kb FieldTypesQuery

Status Quo:
   208kb TypeForFieldQuery (deps)
    18kb TypeForFieldQuery
```

Note how the table itself occupies more than twice as much space! I don't have an explanation for this: a plausible hypothesis is that single-field structs are very common and for them the table is a pessimisation. 

THere's noticiable wallclock time difference.

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2019-11-24 21:45:26 +00:00 committed by GitHub
commit f7f9757b6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 68 additions and 42 deletions

View file

@ -85,7 +85,7 @@ impl<ID: ArenaId, T> Arena<ID, T> {
self.data.push(value); self.data.push(value);
ID::from_raw(id) ID::from_raw(id)
} }
pub fn iter(&self) -> impl Iterator<Item = (ID, &T)> + ExactSizeIterator { pub fn iter(&self) -> impl Iterator<Item = (ID, &T)> + ExactSizeIterator + DoubleEndedIterator {
self.data.iter().enumerate().map(|(idx, value)| (ID::from_raw(RawId(idx as u32)), value)) self.data.iter().enumerate().map(|(idx, value)| (ID::from_raw(RawId(idx as u32)), value))
} }
} }

View file

@ -305,7 +305,7 @@ impl StructField {
} }
pub fn ty(&self, db: &impl HirDatabase) -> Ty { pub fn ty(&self, db: &impl HirDatabase) -> Ty {
db.type_for_field(*self) db.field_types(self.parent.into())[self.id].clone()
} }
pub fn parent_def(&self, _db: &impl HirDatabase) -> VariantDef { pub fn parent_def(&self, _db: &impl HirDatabase) -> VariantDef {

View file

@ -2,6 +2,7 @@
use std::sync::Arc; use std::sync::Arc;
use ra_arena::map::ArenaMap;
use ra_db::salsa; use ra_db::salsa;
use crate::{ use crate::{
@ -11,15 +12,19 @@ use crate::{
CallableDef, FnSig, GenericPredicate, InferenceResult, Namespace, Substs, Ty, TypableDef, CallableDef, FnSig, GenericPredicate, InferenceResult, Namespace, Substs, Ty, TypableDef,
TypeCtor, TypeCtor,
}, },
Crate, DefWithBody, GenericDef, ImplBlock, StructField, Trait, Crate, DefWithBody, GenericDef, ImplBlock, Trait,
}; };
pub use hir_def::db::{ pub use hir_def::{
db::{
BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery,
DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery,
FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternDatabase, InternDatabaseStorage, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternDatabase,
LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, RawItemsWithSourceMapQuery, InternDatabaseStorage, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery,
StaticDataQuery, StructDataQuery, TraitDataQuery, TypeAliasDataQuery, RawItemsWithSourceMapQuery, StaticDataQuery, StructDataQuery, TraitDataQuery,
TypeAliasDataQuery,
},
LocalStructFieldId, VariantId,
}; };
pub use hir_expand::db::{ pub use hir_expand::db::{
AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery,
@ -35,8 +40,8 @@ pub trait HirDatabase: DefDatabase {
#[salsa::invoke(crate::ty::type_for_def)] #[salsa::invoke(crate::ty::type_for_def)]
fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty; fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty;
#[salsa::invoke(crate::ty::type_for_field)] #[salsa::invoke(crate::ty::field_types_query)]
fn type_for_field(&self, field: StructField) -> Ty; fn field_types(&self, var: VariantId) -> Arc<ArenaMap<LocalStructFieldId, Ty>>;
#[salsa::invoke(crate::ty::callable_item_sig)] #[salsa::invoke(crate::ty::callable_item_sig)]
fn callable_item_signature(&self, def: CallableDef) -> FnSig; fn callable_item_signature(&self, def: CallableDef) -> FnSig;

View file

@ -30,8 +30,9 @@ pub(crate) use autoderef::autoderef;
pub(crate) use infer::{infer_query, InferTy, InferenceResult}; pub(crate) use infer::{infer_query, InferTy, InferenceResult};
pub use lower::CallableDef; pub use lower::CallableDef;
pub(crate) use lower::{ pub(crate) use lower::{
callable_item_sig, generic_defaults_query, generic_predicates_for_param_query, callable_item_sig, field_types_query, generic_defaults_query,
generic_predicates_query, type_for_def, type_for_field, Namespace, TypableDef, generic_predicates_for_param_query, generic_predicates_query, type_for_def, Namespace,
TypableDef,
}; };
pub(crate) use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; pub(crate) use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment};

View file

@ -245,14 +245,17 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
ty_app!(TypeCtor::Adt(Adt::Struct(struct1)), st1), ty_app!(TypeCtor::Adt(Adt::Struct(struct1)), st1),
ty_app!(TypeCtor::Adt(Adt::Struct(struct2)), st2), ty_app!(TypeCtor::Adt(Adt::Struct(struct2)), st2),
) if struct1 == struct2 => { ) if struct1 == struct2 => {
let fields = struct1.fields(self.db); let field_tys = self.db.field_types(struct1.id.into());
let (last_field, prev_fields) = fields.split_last()?; let struct_data = self.db.struct_data(struct1.id.0);
let mut fields = struct_data.variant_data.fields().iter();
let (last_field_id, _data) = fields.next_back()?;
// Get the generic parameter involved in the last field. // Get the generic parameter involved in the last field.
let unsize_generic_index = { let unsize_generic_index = {
let mut index = None; let mut index = None;
let mut multiple_param = false; let mut multiple_param = false;
last_field.ty(self.db).walk(&mut |ty| match ty { field_tys[last_field_id].walk(&mut |ty| match ty {
&Ty::Param { idx, .. } => { &Ty::Param { idx, .. } => {
if index.is_none() { if index.is_none() {
index = Some(idx); index = Some(idx);
@ -271,8 +274,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
// Check other fields do not involve it. // Check other fields do not involve it.
let mut multiple_used = false; let mut multiple_used = false;
prev_fields.iter().for_each(|field| { fields.for_each(|(field_id, _data)| {
field.ty(self.db).walk(&mut |ty| match ty { field_tys[field_id].walk(&mut |ty| match ty {
&Ty::Param { idx, .. } if idx == unsize_generic_index => { &Ty::Param { idx, .. } if idx == unsize_generic_index => {
multiple_used = true multiple_used = true
} }

View file

@ -214,6 +214,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
self.unify(&ty, &expected.ty); self.unify(&ty, &expected.ty);
let substs = ty.substs().unwrap_or_else(Substs::empty); let substs = ty.substs().unwrap_or_else(Substs::empty);
let field_types =
def_id.map(|it| self.db.field_types(it.into())).unwrap_or_default();
for (field_idx, field) in fields.iter().enumerate() { for (field_idx, field) in fields.iter().enumerate() {
let field_def = def_id.and_then(|it| match it.field(self.db, &field.name) { let field_def = def_id.and_then(|it| match it.field(self.db, &field.name) {
Some(field) => Some(field), Some(field) => Some(field),
@ -228,8 +230,9 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
if let Some(field_def) = field_def { if let Some(field_def) = field_def {
self.result.record_field_resolutions.insert(field.expr, field_def); self.result.record_field_resolutions.insert(field.expr, field_def);
} }
let field_ty = let field_ty = field_def
field_def.map_or(Ty::Unknown, |field| field.ty(self.db)).subst(&substs); .map_or(Ty::Unknown, |it| field_types[it.id].clone())
.subst(&substs);
self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty));
} }
if let Some(expr) = spread { if let Some(expr) = spread {
@ -252,7 +255,9 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
.and_then(|idx| a_ty.parameters.0.get(idx).cloned()), .and_then(|idx| a_ty.parameters.0.get(idx).cloned()),
TypeCtor::Adt(Adt::Struct(s)) => s.field(self.db, name).map(|field| { TypeCtor::Adt(Adt::Struct(s)) => s.field(self.db, name).map(|field| {
self.write_field_resolution(tgt_expr, field); self.write_field_resolution(tgt_expr, field);
field.ty(self.db).subst(&a_ty.parameters) self.db.field_types(s.id.into())[field.id]
.clone()
.subst(&a_ty.parameters)
}), }),
_ => None, _ => None,
}, },

View file

@ -27,10 +27,11 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
let substs = ty.substs().unwrap_or_else(Substs::empty); let substs = ty.substs().unwrap_or_else(Substs::empty);
let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default();
for (i, &subpat) in subpats.iter().enumerate() { for (i, &subpat) in subpats.iter().enumerate() {
let expected_ty = def let expected_ty = def
.and_then(|d| d.field(self.db, &Name::new_tuple_field(i))) .and_then(|d| d.field(self.db, &Name::new_tuple_field(i)))
.map_or(Ty::Unknown, |field| field.ty(self.db)) .map_or(Ty::Unknown, |field| field_tys[field.id].clone())
.subst(&substs); .subst(&substs);
let expected_ty = self.normalize_associated_types_in(expected_ty); let expected_ty = self.normalize_associated_types_in(expected_ty);
self.infer_pat(subpat, &expected_ty, default_bm); self.infer_pat(subpat, &expected_ty, default_bm);
@ -56,10 +57,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
let substs = ty.substs().unwrap_or_else(Substs::empty); let substs = ty.substs().unwrap_or_else(Substs::empty);
let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default();
for subpat in subpats { for subpat in subpats {
let matching_field = def.and_then(|it| it.field(self.db, &subpat.name)); let matching_field = def.and_then(|it| it.field(self.db, &subpat.name));
let expected_ty = let expected_ty = matching_field
matching_field.map_or(Ty::Unknown, |field| field.ty(self.db)).subst(&substs); .map_or(Ty::Unknown, |field| field_tys[field.id].clone())
.subst(&substs);
let expected_ty = self.normalize_associated_types_in(expected_ty); let expected_ty = self.normalize_associated_types_in(expected_ty);
self.infer_pat(subpat.pat, &expected_ty, default_bm); self.infer_pat(subpat.pat, &expected_ty, default_bm);
} }

View file

@ -14,8 +14,9 @@ use hir_def::{
path::{GenericArg, PathSegment}, path::{GenericArg, PathSegment},
resolver::{HasResolver, Resolver, TypeNs}, resolver::{HasResolver, Resolver, TypeNs},
type_ref::{TypeBound, TypeRef}, type_ref::{TypeBound, TypeRef},
AdtId, GenericDefId, AdtId, GenericDefId, LocalStructFieldId, VariantId,
}; };
use ra_arena::map::ArenaMap;
use super::{ use super::{
FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
@ -29,7 +30,7 @@ use crate::{
}, },
util::make_mut_slice, util::make_mut_slice,
Const, Enum, EnumVariant, Function, GenericDef, ImplBlock, ModuleDef, Path, Static, Struct, Const, Enum, EnumVariant, Function, GenericDef, ImplBlock, ModuleDef, Path, Static, Struct,
StructField, Trait, TypeAlias, Union, VariantDef, Trait, TypeAlias, Union,
}; };
// FIXME: this is only really used in `type_for_def`, which contains a bunch of // FIXME: this is only really used in `type_for_def`, which contains a bunch of
@ -549,16 +550,23 @@ pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSi
} }
} }
/// Build the type of a specific field of a struct or enum variant. /// Build the type of all specific fields of a struct or enum variant.
pub(crate) fn type_for_field(db: &impl HirDatabase, field: StructField) -> Ty { pub(crate) fn field_types_query(
let parent_def = field.parent_def(db); db: &impl HirDatabase,
let resolver = match parent_def { variant_id: VariantId,
VariantDef::Struct(it) => it.id.resolver(db), ) -> Arc<ArenaMap<LocalStructFieldId, Ty>> {
VariantDef::EnumVariant(it) => it.parent.id.resolver(db), let (resolver, var_data) = match variant_id {
VariantId::StructId(it) => (it.resolver(db), db.struct_data(it.0).variant_data.clone()),
VariantId::EnumVariantId(it) => (
it.parent.resolver(db),
db.enum_data(it.parent).variants[it.local_id].variant_data.clone(),
),
}; };
let var_data = parent_def.variant_data(db); let mut res = ArenaMap::default();
let type_ref = &var_data.fields()[field.id].type_ref; for (field_id, field_data) in var_data.fields().iter() {
Ty::from_hir(db, &resolver, type_ref) res.insert(field_id, Ty::from_hir(db, &resolver, &field_data.type_ref))
}
Arc::new(res)
} }
/// This query exists only to be used when resolving short-hand associated types /// This query exists only to be used when resolving short-hand associated types

View file

@ -191,12 +191,6 @@ pub struct EnumVariantId {
pub struct LocalEnumVariantId(RawId); pub struct LocalEnumVariantId(RawId);
impl_arena_id!(LocalEnumVariantId); impl_arena_id!(LocalEnumVariantId);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VariantId {
EnumVariantId(EnumVariantId),
StructId(StructId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StructFieldId { pub struct StructFieldId {
pub parent: VariantId, pub parent: VariantId,
@ -437,6 +431,13 @@ impl_froms!(
ImplId ImplId
); );
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VariantId {
EnumVariantId(EnumVariantId),
StructId(StructId),
}
impl_froms!(VariantId: EnumVariantId, StructId);
trait Intern { trait Intern {
type ID; type ID;
fn intern(self, db: &impl db::DefDatabase) -> Self::ID; fn intern(self, db: &impl db::DefDatabase) -> Self::ID;

View file

@ -324,7 +324,7 @@ impl RootDatabase {
hir::db::ExprScopesQuery hir::db::ExprScopesQuery
hir::db::InferQuery hir::db::InferQuery
hir::db::TypeForDefQuery hir::db::TypeForDefQuery
hir::db::TypeForFieldQuery hir::db::FieldTypesQuery
hir::db::CallableItemSignatureQuery hir::db::CallableItemSignatureQuery
hir::db::GenericPredicatesQuery hir::db::GenericPredicatesQuery
hir::db::GenericDefaultsQuery hir::db::GenericDefaultsQuery