optimize: add a callgraph builder: RTA
authorJean-Sebastien Gelinas <calestar@gmail.com>
Thu, 20 Aug 2009 20:56:53 +0000 (16:56 -0400)
committerJean Privat <jean@pryen.org>
Mon, 14 Sep 2009 18:50:59 +0000 (14:50 -0400)
Signed-off-by: Jean-Sebastien Gelinas <calestar@gmail.com>
Signed-off-by: Jean Privat <jean@pryen.org>

src/analysis/analysis.nit
src/analysis/rta_analysis.nit [new file with mode: 0644]
src/nitc.nit

index d3b551b..c2a1845 100644 (file)
@@ -33,12 +33,13 @@ import reachable_method_analysis
 
 # Global Analysis implementation
 import cha_analysis
+import rta_analysis
 
 # Global Optimizations
 import dead_method_removal
 
 redef class ToolContext
-       readable writable var _global_callgraph: String = "cha"
+       readable writable var _global_callgraph: String = "rta"
        readable writable var _no_dead_method_removal: Bool = false
 end
 
@@ -51,6 +52,11 @@ redef class Program
                        var cha = new ChaBuilder(self)
                        cha.work
                        rma = cha.context
+               else if tc.global_callgraph == "rta" then
+                       var rta = new RtaBuilder(self)
+                       rta.work
+                       rma = rta.context
+                       ita = rta.context
                end
 
                # Ensure we have all analysis created
diff --git a/src/analysis/rta_analysis.nit b/src/analysis/rta_analysis.nit
new file mode 100644 (file)
index 0000000..04473fd
--- /dev/null
@@ -0,0 +1,222 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2009 Jean-Sebastien Gelinas <calestar@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This package implements Reachable Type Analysis(RTA)
+package rta_analysis
+
+import instantiated_type_analysis
+import reachable_method_analysis
+import icode
+import program
+
+class RtaContext
+special ReachableMethodAnalysis
+special InstantiatedTypeAnalysis
+       init do end
+       readable var _instanciated_classes: HashSet[MMLocalClass] = new HashSet[MMLocalClass]
+
+       redef fun is_class_instantiated(local_class: MMLocalClass): Bool do return instanciated_classes.has(local_class)
+
+       readable var _reachable_iroutines: HashSet[IRoutine] = new HashSet[IRoutine]
+
+       redef fun is_iroutine_reachable(ir: nullable IRoutine): Bool do
+               return ir != null and reachable_iroutines.has(ir)
+       end
+
+       redef fun is_method_reachable(method: MMMethod): Bool do
+               return is_iroutine_reachable(method.iroutine)
+       end
+end
+
+class RtaBuilder
+       readable var _context : RtaContext
+       readable var _program: Program
+       readable var _iroutine_to_search: List[IRoutine] = new List[IRoutine]
+       readable var _call_sites: HashSet[IAbsCall] = new HashSet[IAbsCall]
+       readable var _called_methods: HashSet[MMMethod] = new HashSet[MMMethod]
+
+       init (p: Program) do
+               _context = new RtaContext()
+               _program = p
+       end
+
+       # Check if the class where the method was introduced is instantiated
+       # Also check every subclasses if they use the same 'version' of the
+       # method
+       fun check_method(m: MMMethod): Bool do
+               var m_cls = m.local_class.for_module(program.module)
+
+               if context.is_class_instantiated(m_cls) then return true
+
+               for cls in m_cls.cshe.smallers do
+                       if context.is_class_instantiated(cls) then return true
+               end
+               return false
+       end
+
+       # Check all call sites to find those that just became 'activated'
+       # by some new type instantiation
+       fun check_call_sites do
+               var calls_to_remove = new List[IAbsCall]
+               for call in call_sites do
+                       var m = call.property
+                       var all_added = true
+
+                       # Check for this method
+                       if check_method(m) then
+                               add_reachable_iroutine(m.iroutine)
+                       else
+                               all_added = false
+                       end
+
+                       # Check all methods 'under' this one in the hierarchy
+                       for other in m.prhe.smallers do
+                               if not other isa MMMethod then continue
+                               if check_method(other) then
+                                       add_reachable_iroutine(other.iroutine)
+                               else
+                                       all_added = false
+                               end
+                       end
+
+                       if all_added then calls_to_remove.add(call)
+               end
+
+               # If all sub-methods are added, no need to keep checking this call !
+               for call in calls_to_remove do
+                       call_sites.remove(call)
+               end
+       end
+
+       fun add_instantiated_class(cls: MMLocalClass) do
+               if context.is_class_instantiated(cls) then return
+               context.instanciated_classes.add(cls)
+
+               check_call_sites
+       end
+
+       fun add_reachable_call(call: IAbsCall) do
+               var m = call.property
+               if called_methods.has(m) then return
+               if call_sites.has(call) then return
+
+               if m.global.is_init then
+                       called_methods.add(m)
+                       add_reachable_iroutine(m.iroutine)
+
+                       # No need to add the call site or to process other
+                       # call sites when it is an initializer: we know
+                       # exactly which one will get called
+               else
+                       call_sites.add(call)
+                       called_methods.add(m)
+                       check_call_sites
+               end
+       end
+
+       fun add_reachable_iroutine(ir: nullable IRoutine) do
+               if ir == null or context.is_iroutine_reachable(ir) then return
+               context.reachable_iroutines.add(ir)
+               iroutine_to_search.add(ir)
+       end
+
+       # Need to hard-code some automaticaly instanciated types !
+       private fun force_some_type_analysis do
+               var forced_types = ["Object", "Bool", "Float", "Int", "String", "NativeString", "Range", "Array", "ArrayIterator"]
+
+               for some_type in forced_types do
+                       if not program.module.has_global_class_named(some_type.to_symbol) then continue
+                       var cls_type = program.module.class_by_name(some_type.to_symbol)
+                       add_instantiated_class(cls_type)
+               end
+
+               if program.module.has_global_class_named("Pointer".to_symbol) then
+                       var ptr_class = program.module.class_by_name("Pointer".to_symbol)
+                       # Assume that all classes that are subclasses of Pointer
+                       # can be returned by the native interface
+                       # and are always counted as instantiated
+                       for ptr_sub_class in ptr_class.cshe.smallers do
+                               add_instantiated_class(ptr_sub_class)
+                       end
+               end
+
+               for cls in program.module.global_classes do
+                       if not cls.is_universal then continue
+                       add_instantiated_class(program.module[cls])
+               end
+       end
+
+       # Build the context associated with this builder
+       fun work do
+               if program.main_method == null then return
+
+               add_instantiated_class(program.main_class.as(not null))
+               add_reachable_iroutine(program.main_method.as(not null).iroutine)
+               force_some_type_analysis
+
+               while not iroutine_to_search.is_empty do
+                       var v = new RtaVisitor(self)
+                       var iroutine = iroutine_to_search.pop
+                       v.visit_icode(iroutine.body)
+               end
+       end
+end
+
+class RtaVisitor
+special ICodeVisitor
+       readable var _builder: RtaBuilder
+
+       redef fun visit_icode(ic)
+       do
+               if ic isa IStaticCall then
+                       # FIXME: take only the last property on the redef. hierarchie
+                       builder.add_reachable_iroutine(ic.property.iroutine)
+               else if ic isa INew then
+                       # FIXME: take only the last property on the redef. hierarchie
+                       var t = ic.stype
+                       var cls = t.for_module(builder.program.module).local_class
+                       var m = cls[ic.property.global].as(MMMethod)
+                       var r = cls.new_instance_iroutine[m]
+                       builder.add_instantiated_class(cls)
+                       builder.add_reachable_iroutine(r)
+               else if ic isa ISuper then
+                       # Add every parents ...
+                       for p in ic.property.prhe.greaters_and_self do
+                               if p isa MMMethod then
+                                       builder.add_reachable_iroutine(p.iroutine)
+                               end
+                       end
+               else if ic isa ICall then
+                       builder.add_reachable_call(ic)
+               else if ic isa ICheckInstance then
+                       var t = ic.stype
+                       var cls = t.for_module(builder.program.module).local_class
+                       var ir = cls.checknew_iroutine
+                       builder.add_reachable_iroutine(ir)
+               else if ic isa IInitAttributes then
+                       var t = ic.stype
+                       var cls = t.for_module(builder.program.module).local_class
+                       var ir = cls.init_var_iroutine
+                       builder.add_reachable_iroutine(ir)
+               end
+               super
+       end
+
+       init(b: RtaBuilder)
+       do
+               _builder = b
+       end
+end
index db91882..f64993d 100644 (file)
@@ -32,7 +32,7 @@ special AbstractCompiler
        readable var _opt_global: OptionBool = new OptionBool("Use global compilation", "--global")
        readable var _opt_global_no_STF_opt: OptionBool = new OptionBool("Do not use SFT optimization", "--no-global-SFT-optimization")
        readable var _opt_global_no_DMR_opt: OptionBool = new OptionBool("Do not use dead method removal optimization", "--no-global-DMR-optimization")
-       readable var _opt_global_callgraph: OptionEnum = new OptionEnum(["none", "cha"], "The algorithm to use to build the callgraph", 1, "--global-callgraph")
+       readable var _opt_global_callgraph: OptionEnum = new OptionEnum(["none", "cha", "rta"], "The algorithm to use to build the callgraph", 2, "--global-callgraph")
        readable var _opt_clibdir: OptionString = new OptionString("NIT C library directory", "--clibdir")
        readable var _opt_bindir: OptionString = new OptionString("NIT tools directory", "--bindir")
        readable var _opt_compdir: OptionString = new OptionString("Intermediate compilation directory", "--compdir")