Skip to content

Commit

Permalink
Adds method and function deduplication to frontend.
Browse files Browse the repository at this point in the history
Adds function cached to query engine, and reuses previously compiled methods and functions.

Closes #5904.
  • Loading branch information
esdrubal committed May 20, 2024
1 parent 573a9ff commit 1353d41
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 58 deletions.
41 changes: 41 additions & 0 deletions sway-core/src/language/ty/declaration/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,27 @@ pub struct TyFunctionSig {
pub parameters: Vec<TypeId>,
}

impl DisplayWithEngines for TyFunctionSig {
fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
write!(f, "{:?}", engines.help_out(self))
}
}

impl DebugWithEngines for TyFunctionSig {
fn fmt(&self, f: &mut fmt::Formatter<'_>, engines: &Engines) -> fmt::Result {
write!(
f,
"fn({}) -> {}",
self.parameters
.iter()
.map(|p| format!("{}", engines.help_out(p)))
.collect::<Vec<_>>()
.join(", "),
engines.help_out(self.return_type),
)
}
}

impl TyFunctionSig {
pub fn from_fn_decl(fn_decl: &TyFunctionDecl) -> Self {
Self {
Expand All @@ -550,4 +571,24 @@ impl TyFunctionSig {
.collect::<Vec<_>>(),
}
}

pub fn is_concrete(&self, engines: &Engines) -> bool {
self.return_type.is_concrete(engines)
&& self.parameters.iter().all(|p| p.is_concrete(engines))
}

/// Returns a String representing the function.
/// When the function is monomorphized the returned String is unique.
/// Two monomorphized functions that generate the same String can be assumed to be the same.
pub fn get_type_str(&self, engines: &Engines) -> String {
format!(
"fn({}) -> {}",
self.parameters
.iter()
.map(|p| p.get_type_str(engines))
.collect::<Vec<_>>()
.join(", "),
self.return_type.get_type_str(engines),
)
}
}
39 changes: 38 additions & 1 deletion sway-core/src/query_engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use parking_lot::RwLock;
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::SystemTime};
use sway_error::error::CompileError;
use sway_error::warning::CompileWarning;
use sway_types::IdentUnique;

use crate::Programs;
use crate::decl_engine::{DeclId, DeclRef};
use crate::language::ty::{TyFunctionDecl, TyFunctionSig};
use crate::{Engines, Programs};

pub type ModulePath = Arc<PathBuf>;

Expand Down Expand Up @@ -43,11 +46,19 @@ pub struct ProgramsCacheEntry {

pub type ProgramsCacheMap = HashMap<ModulePath, ProgramsCacheEntry>;

#[derive(Clone, Debug)]
pub struct MethodCacheEntry {
pub fn_decl: DeclRef<DeclId<TyFunctionDecl>>,
}

pub type MethodsCacheMap = HashMap<(IdentUnique, String), MethodCacheEntry>;

#[derive(Debug, Default, Clone)]
pub struct QueryEngine {
// We want the below types wrapped in Arcs to optimize cloning from LSP.
parse_module_cache: Arc<RwLock<ModuleCacheMap>>,
programs_cache: Arc<RwLock<ProgramsCacheMap>>,
method_cache: Arc<RwLock<MethodsCacheMap>>,
}

impl QueryEngine {
Expand All @@ -73,4 +84,30 @@ impl QueryEngine {
let mut cache = self.programs_cache.write();
cache.insert(entry.path.clone(), entry);
}

pub fn get_function(
&self,
engines: &Engines,
ident: IdentUnique,
sig: TyFunctionSig,
) -> Option<DeclRef<DeclId<TyFunctionDecl>>> {
let cache = self.method_cache.read().unwrap();
cache
.get(&(ident, sig.get_type_str(engines)))
.map(|s| s.fn_decl.clone())
}

pub fn insert_function(
&self,
engines: &Engines,
ident: IdentUnique,
sig: TyFunctionSig,
fn_decl: DeclRef<DeclId<TyFunctionDecl>>,
) {
let mut cache = self.method_cache.write().unwrap();
cache.insert(
(ident, sig.get_type_str(engines)),
MethodCacheEntry { fn_decl },
);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::{
decl_engine::{DeclEngineInsert, DeclRefFunction, ReplaceDecls},
language::{ty, *},
language::{
ty::{self, TyFunctionSig},
*,
},
semantic_analysis::{ast_node::*, TypeCheckContext},
};
use indexmap::IndexMap;
use sway_error::error::CompileError;
use sway_types::Spanned;
use sway_types::{IdentUnique, Spanned};

const UNIFY_ARGS_HELP_TEXT: &str =
"The argument that has been provided to this function's type does \
Expand All @@ -21,7 +24,8 @@ pub(crate) fn instantiate_function_application(
arguments: Option<&[Expression]>,
span: Span,
) -> Result<ty::TyExpression, ErrorEmitted> {
let decl_engine = ctx.engines.de();
let engines = ctx.engines();
let decl_engine = engines.de();

let mut function_decl = (*decl_engine.get_function(&function_decl_ref)).clone();

Expand Down Expand Up @@ -62,22 +66,53 @@ pub(crate) fn instantiate_function_application(
&function_decl.parameters,
)?;

// Handle the trait constraints. This includes checking to see if the trait
// constraints are satisfied and replacing old decl ids based on the
// constraint with new decl ids based on the new type.
let decl_mapping = TypeParameter::gather_decl_mapping_from_trait_constraints(
handler,
ctx.by_ref(),
&function_decl.type_parameters,
function_decl.name.as_str(),
&call_path_binding.span(),
)?;
let mut function_return_type_id = function_decl.return_type.type_id;

let function_ident: IdentUnique = function_decl.name.clone().into();
let function_sig = TyFunctionSig::from_fn_decl(&function_decl);

function_decl.replace_decls(&decl_mapping, handler, &mut ctx)?;
let return_type = function_decl.return_type.clone();
let new_decl_ref = decl_engine
.insert(function_decl)
.with_parent(decl_engine, (*function_decl_ref.id()).into());
let new_decl_ref = if let Some(cached_fn_ref) =
ctx.engines()
.qe()
.get_function(engines, function_ident.clone(), function_sig.clone())
{
cached_fn_ref
} else {
// Handle the trait constraints. This includes checking to see if the trait
// constraints are satisfied and replacing old decl ids based on the
// constraint with new decl ids based on the new type.
let decl_mapping = TypeParameter::gather_decl_mapping_from_trait_constraints(
handler,
ctx.by_ref(),
&function_decl.type_parameters,
function_decl.name.as_str(),
&call_path_binding.span(),
)?;

function_decl.replace_decls(&decl_mapping, handler, &mut ctx)?;

let are_type_parameters_concrete = function_decl
.type_parameters
.iter()
.all(|p| p.type_id.is_concrete(engines));
let method_sig = TyFunctionSig::from_fn_decl(&function_decl);

function_return_type_id = function_decl.return_type.type_id;
let new_decl_ref = decl_engine
.insert(function_decl)
.with_parent(decl_engine, (*function_decl_ref.id()).into());

if method_sig.is_concrete(engines) && are_type_parameters_concrete {
ctx.engines().qe().insert_function(
engines,
function_ident,
method_sig,
new_decl_ref.clone(),
);
}

new_decl_ref
};

let exp = ty::TyExpression {
expression: ty::TyExpressionVariant::FunctionApplication {
Expand All @@ -91,7 +126,7 @@ pub(crate) fn instantiate_function_application(
contract_call_params: IndexMap::new(),
contract_caller: None,
},
return_type: return_type.type_id,
return_type: function_return_type_id,
span,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
},
language::{
parsed::*,
ty::{self, TyDecl, TyExpression},
ty::{self, TyDecl, TyExpression, TyFunctionSig},
*,
},
namespace::TryInsertingTraitImplOnFailure,
Expand All @@ -20,7 +20,7 @@ use sway_error::{
error::CompileError,
handler::{ErrorEmitted, Handler},
};
use sway_types::{constants, integer_bits::IntegerBits, BaseIdent};
use sway_types::{constants, integer_bits::IntegerBits, BaseIdent, IdentUnique};
use sway_types::{constants::CONTRACT_CALL_COINS_PARAMETER_NAME, Spanned};
use sway_types::{Ident, Span};

Expand Down Expand Up @@ -83,12 +83,13 @@ pub(crate) fn type_check_method_application(
.collect(),
)?;

let fn_ref = monomorphize_method(
let mut fn_ref = monomorphize_method(
handler,
ctx.by_ref(),
original_decl_ref.clone(),
method_name_binding.type_arguments.to_vec_mut(),
)?;

let mut method = (*decl_engine.get_function(&fn_ref)).clone();

// unify method return type with current ctx.type_annotation().
Expand Down Expand Up @@ -620,47 +621,73 @@ pub(crate) fn type_check_method_application(
let arguments =
unify_arguments_and_parameters(handler, ctx.by_ref(), &arguments, &method.parameters)?;

// This handles the case of substituting the generic blanket type by call_path_typeid.
if let Some(TyDecl::ImplTrait(t)) = method.clone().implementing_type {
let t = &engines.de().get(&t.decl_id).implementing_for;
if let TypeInfo::Custom {
qualified_call_path,
type_arguments: _,
root_type_id: _,
} = &*type_engine.get(t.initial_type_id)
{
for p in method.type_parameters.clone() {
if p.name_ident.as_str() == qualified_call_path.call_path.suffix.as_str() {
let type_subst = TypeSubstMap::from_type_parameters_and_type_arguments(
vec![t.initial_type_id],
vec![call_path_typeid],
);
method.subst(&type_subst, engines);
let mut method_return_type_id = method.return_type.type_id;

let method_ident: IdentUnique = method.name.clone().into();
let method_sig = TyFunctionSig::from_fn_decl(&method);

if let Some(cached_fn_ref) =
ctx.engines()
.qe()
.get_function(engines, method_ident.clone(), method_sig.clone())
{
fn_ref = cached_fn_ref;
} else {
// This handles the case of substituting the generic blanket type by call_path_typeid.
if let Some(TyDecl::ImplTrait(t)) = method.clone().implementing_type {
let t = &engines.de().get(&t.decl_id).implementing_for;
if let TypeInfo::Custom {
qualified_call_path,
type_arguments: _,
root_type_id: _,
} = &*type_engine.get(t.initial_type_id)
{
for p in method.type_parameters.clone() {
if p.name_ident.as_str() == qualified_call_path.call_path.suffix.as_str() {
let type_subst = TypeSubstMap::from_type_parameters_and_type_arguments(
vec![t.initial_type_id],
vec![call_path_typeid],
);
method.subst(&type_subst, engines);
}
}
}
}
}

// Handle the trait constraints. This includes checking to see if the trait
// constraints are satisfied and replacing old decl ids based on the
// constraint with new decl ids based on the new type.
let decl_mapping = TypeParameter::gather_decl_mapping_from_trait_constraints(
handler,
ctx.by_ref(),
&method.type_parameters,
method.name.as_str(),
&call_path.span(),
)
.ok();

if let Some(decl_mapping) = decl_mapping {
if !ctx.defer_monomorphization() {
method.replace_decls(&decl_mapping, handler, &mut ctx)?;
// Handle the trait constraints. This includes checking to see if the trait
// constraints are satisfied and replacing old decl ids based on the
// constraint with new decl ids based on the new type.
let decl_mapping = TypeParameter::gather_decl_mapping_from_trait_constraints(
handler,
ctx.by_ref(),
&method.type_parameters,
method.name.as_str(),
&call_path.span(),
)
.ok();

if let Some(decl_mapping) = decl_mapping {
if !ctx.defer_monomorphization() {
method.replace_decls(&decl_mapping, handler, &mut ctx)?;
}
}
}

let method_return_type_id = method.return_type.type_id;
decl_engine.replace(*fn_ref.id(), method);
let are_type_parameters_concrete = method
.type_parameters
.iter()
.all(|p| p.type_id.is_concrete(engines));

let method_sig = TyFunctionSig::from_fn_decl(&method);

method_return_type_id = method.return_type.type_id;
decl_engine.replace(*fn_ref.id(), method.clone());

if method_sig.is_concrete(engines) && are_type_parameters_concrete {
ctx.engines()
.qe()
.insert_function(engines, method_ident, method_sig, fn_ref.clone());
}
}

let fn_app = ty::TyExpressionVariant::FunctionApplication {
call_path: call_path.clone(),
Expand Down
18 changes: 18 additions & 0 deletions sway-core/src/type_system/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,20 @@ impl TypeId {
}))
}

pub(crate) fn is_concrete(&self, engines: &Engines) -> bool {
let nested_types = (*self).extract_nested_types(engines);
!nested_types.into_iter().any(|x| {
matches!(
x,
TypeInfo::UnknownGeneric { .. }
| TypeInfo::Custom { .. }
| TypeInfo::Placeholder(..)
| TypeInfo::TraitType { .. }
| TypeInfo::TypeParam(..)
)
})
}

/// `check_type_parameter_bounds` does two types of checks. Lets use the example below for demonstrating the two checks:
/// ```ignore
/// enum MyEnum<T> where T: MyAdd {
Expand Down Expand Up @@ -655,4 +669,8 @@ impl TypeId {
}
found_error
}

pub fn get_type_str(&self, engines: &Engines) -> String {
engines.te().get(*self).get_type_str(engines)
}
}

0 comments on commit 1353d41

Please sign in to comment.