From d0b6de5b0d3a6db8841a37ecc5cf897532e93a0d Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Tue, 24 Jan 2023 19:01:07 +0000 Subject: [PATCH] Fix DCA for generic functions. Closes https://github.com/FuelLabs/sway/issues/3671. --- .../analyze_return_paths.rs | 6 +-- .../dead_code_analysis.rs | 54 ++++++++++++++----- .../flow_graph/namespace.rs | 27 +++++++--- .../src/language/ty/declaration/function.rs | 19 +++++++ .../dca/generic_fn_trait_contraint/Forc.lock | 3 ++ .../dca/generic_fn_trait_contraint/Forc.toml | 6 +++ .../generic_fn_trait_contraint/src/main.sw | 18 +++++++ .../dca/generic_fn_trait_contraint/test.toml | 5 ++ 8 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/test.toml diff --git a/sway-core/src/control_flow_analysis/analyze_return_paths.rs b/sway-core/src/control_flow_analysis/analyze_return_paths.rs index 3ea6f9b5cc9..c383c00e286 100644 --- a/sway-core/src/control_flow_analysis/analyze_return_paths.rs +++ b/sway-core/src/control_flow_analysis/analyze_return_paths.rs @@ -38,7 +38,7 @@ impl<'cfg> ControlFlowGraph<'cfg> { pub(crate) fn analyze_return_paths(&self, engines: Engines<'_>) -> Vec { let mut errors = vec![]; for ( - name, + (name, _sig), FunctionNamespaceEntry { entry_point, exit_point, @@ -305,9 +305,7 @@ fn connect_typed_fn_decl<'eng: 'cfg, 'cfg>( .to_typeinfo(fn_decl.return_type, &fn_decl.return_type_span) .unwrap_or_else(|_| TypeInfo::Tuple(Vec::new())), }; - graph - .namespace - .insert_function(fn_decl.name.clone(), namespace_entry); + graph.namespace.insert_function(fn_decl, namespace_entry); Ok(()) } diff --git a/sway-core/src/control_flow_analysis/dead_code_analysis.rs b/sway-core/src/control_flow_analysis/dead_code_analysis.rs index 774c23e36f2..73f70b25b01 100644 --- a/sway-core/src/control_flow_analysis/dead_code_analysis.rs +++ b/sway-core/src/control_flow_analysis/dead_code_analysis.rs @@ -152,9 +152,7 @@ impl<'cfg> ControlFlowGraph<'cfg> { &leaves, exit_node, tree_type, - NodeConnectionOptions { - force_struct_fields_connection: false, - }, + NodeConnectionOptions::default(), )?; leaves = l_leaves; @@ -188,7 +186,7 @@ fn collect_entry_points( /// This struct is used to pass node connection further down the tree as /// we are processing AST nodes. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] struct NodeConnectionOptions { /// When this is enabled, connect struct fields to the struct itself, /// thus making all struct fields considered as being used in the graph. @@ -689,9 +687,7 @@ fn connect_typed_fn_decl<'eng: 'cfg, 'cfg>( return_type: ty, }; - graph - .namespace - .insert_function(fn_decl.name.clone(), namespace_entry); + graph.namespace.insert_function(fn_decl, namespace_entry); connect_fn_params_struct_enums(engines, fn_decl, graph, entry_node)?; Ok(()) @@ -827,11 +823,45 @@ fn connect_expression<'eng: 'cfg, 'cfg>( } => { let fn_decl = decl_engine.get_function(function_decl_id.clone(), &expression_span)?; let mut is_external = false; + + // in the case of monomorphized functions, first check if we already have a node for + // it in the namespace. if not then we need to check to see if the namespace contains + // the decl id parents (the original generic non monomorphized decl id). + let mut exists = false; + let parents = decl_engine.find_all_parents(engines, function_decl_id.clone()); + for parent in parents { + if let Ok(parent) = decl_engine.get_function(parent.clone(), &expression_span) { + exists |= graph.namespace.get_function(&parent).is_some(); + } + } + // find the function in the namespace - let (fn_entrypoint, fn_exit_point) = graph - .namespace - .get_function(&fn_decl.name) - .cloned() + let fn_namespace_entry = graph.namespace.get_function(&fn_decl).cloned(); + + let mut leaves = leaves.to_vec(); + + // if the parent node exists in this module, then add the monomorphized version + // to the graph. + if fn_namespace_entry.is_none() && exists { + let (l_leaves, _new_exit_node) = connect_node( + engines, + &ty::TyAstNode { + content: ty::TyAstNodeContent::Declaration( + ty::TyDeclaration::FunctionDeclaration(function_decl_id.clone()), + ), + span: expression_span.clone(), + }, + graph, + &leaves, + exit_node, + tree_type, + NodeConnectionOptions::default(), + )?; + + leaves = l_leaves; + } + + let (fn_entrypoint, fn_exit_point) = fn_namespace_entry .map( |FunctionNamespaceEntry { entry_point, @@ -863,7 +893,7 @@ fn connect_expression<'eng: 'cfg, 'cfg>( } for leaf in leaves { - graph.add_edge(*leaf, fn_entrypoint, label.into()); + graph.add_edge(leaf, fn_entrypoint, label.into()); } // save the existing options value to restore after handling the arguments diff --git a/sway-core/src/control_flow_analysis/flow_graph/namespace.rs b/sway-core/src/control_flow_analysis/flow_graph/namespace.rs index 8b71586c9c7..1418b601daa 100644 --- a/sway-core/src/control_flow_analysis/flow_graph/namespace.rs +++ b/sway-core/src/control_flow_analysis/flow_graph/namespace.rs @@ -1,6 +1,9 @@ use super::{EntryPoint, ExitPoint}; use crate::{ - language::{ty, CallPath}, + language::{ + ty::{self, TyFunctionDeclaration, TyFunctionSig}, + CallPath, + }, type_system::TypeInfo, Ident, }; @@ -32,7 +35,7 @@ pub(crate) struct StructNamespaceEntry { /// of scope at this point, as that would have been caught earlier and aborted the compilation /// process. pub struct ControlFlowNamespace { - pub(crate) function_namespace: HashMap, + pub(crate) function_namespace: HashMap<(IdentUnique, TyFunctionSig), FunctionNamespaceEntry>, pub(crate) enum_namespace: HashMap)>, pub(crate) trait_namespace: HashMap, /// This is a mapping from trait name to method names and their node indexes @@ -45,13 +48,23 @@ pub struct ControlFlowNamespace { } impl ControlFlowNamespace { - pub(crate) fn get_function(&self, ident: &Ident) -> Option<&FunctionNamespaceEntry> { - let ident: IdentUnique = ident.into(); - self.function_namespace.get(&ident) + pub(crate) fn get_function( + &self, + fn_decl: &TyFunctionDeclaration, + ) -> Option<&FunctionNamespaceEntry> { + let ident: IdentUnique = fn_decl.name.clone().into(); + self.function_namespace + .get(&(ident, TyFunctionSig::from_fn_decl(fn_decl))) } - pub(crate) fn insert_function(&mut self, ident: Ident, entry: FunctionNamespaceEntry) { + pub(crate) fn insert_function( + &mut self, + fn_decl: &ty::TyFunctionDeclaration, + entry: FunctionNamespaceEntry, + ) { + let ident = &fn_decl.name; let ident: IdentUnique = ident.into(); - self.function_namespace.insert(ident, entry); + self.function_namespace + .insert((ident, TyFunctionSig::from_fn_decl(fn_decl)), entry); } pub(crate) fn get_constant(&self, ident: &Ident) -> Option<&NodeIndex> { self.const_namespace.get(ident) diff --git a/sway-core/src/language/ty/declaration/function.rs b/sway-core/src/language/ty/declaration/function.rs index c1831376051..2c7cc9e224f 100644 --- a/sway-core/src/language/ty/declaration/function.rs +++ b/sway-core/src/language/ty/declaration/function.rs @@ -372,3 +372,22 @@ impl TyFunctionParameter { self.name.as_str() == "self" } } + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TyFunctionSig { + pub return_type: TypeId, + pub parameters: Vec, +} + +impl TyFunctionSig { + pub fn from_fn_decl(fn_decl: &TyFunctionDeclaration) -> Self { + Self { + return_type: fn_decl.return_type, + parameters: fn_decl + .parameters + .iter() + .map(|p| p.type_id) + .collect::>(), + } + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.lock new file mode 100644 index 00000000000..5b775562f00 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'impl_trait_single' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.toml new file mode 100644 index 00000000000..bba3d7ba22a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "impl_trait_single" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/src/main.sw new file mode 100644 index 00000000000..c39ebe16f31 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/src/main.sw @@ -0,0 +1,18 @@ +script; + +pub trait MyEq { + fn my_eq(self, other: Self); +} + +impl MyEq for u64 { + fn my_eq(self, other: Self) { + } +} + +fn test_my_eq(x: T, y: T) where T: MyEq { + x.my_eq(y) +} + +fn main() { + test_my_eq(42, 42); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/test.toml new file mode 100644 index 00000000000..50bf5bf84ce --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/dca/generic_fn_trait_contraint/test.toml @@ -0,0 +1,5 @@ +category = "compile" + +# not: $()This declaration is never used + +# not: $()This trait is never implemented