Commits

Antonio Cuni committed 687086c

Inline oosends we can dispatch at compile-time because they call
non-virtual methods.

Make backend_optimizations() always call check_virtual_methods when
using ootypesystem.

Make ootype.ROOT the default Instance where to start from when
checking virtual methods.

  • Participants
  • Parent commits 5ac3782

Comments (0)

Files changed (8)

pypy/translator/backendopt/all.py

 from pypy.translator.backendopt.mallocprediction import clever_inlining_and_malloc_removal
 from pypy.translator.backendopt.removeassert import remove_asserts
 from pypy.translator.backendopt.support import log
+from pypy.translator.backendopt.checkvirtual import check_virtual_methods
 from pypy.objspace.flow.model import checkgraph
 
 def backend_optimizations(translator, graphs=None, secondary=False, **kwds):
     if config.raisingop2direct_call:
         raisingop2direct_call(translator, graphs)
 
+    if translator.rtyper.type_system.name == 'ootypesystem':
+        check_virtual_methods()
+
     # remove obvious no-ops
     for graph in graphs:
         removenoops.remove_same_as(graph)

pypy/translator/backendopt/checkvirtual.py

 non-virtual calls, such as JVM).
 """
 
-def check_virtual_methods(INSTANCE, super_methods = {}):
+from pypy.rpython.ootypesystem import ootype
+
+def check_virtual_methods(INSTANCE=ootype.ROOT, super_methods = {}):
     my_methods = super_methods.copy()
     for name, method in INSTANCE._methods.iteritems():
         method._virtual = False

pypy/translator/backendopt/inline.py

 class CannotInline(Exception):
     pass
 
+def get_meth_from_oosend(op):
+    method_name = op.args[0].value
+    INSTANCE = op.args[1].concretetype
+    _, meth = INSTANCE._lookup(op.args[0].value)
+    virtual = getattr(meth, '_virtual', True)
+    if virtual:
+        return None
+    else:
+        return meth
+
 def collect_called_graphs(graph, translator):
     graphs_or_something = {}
     for block in graph.iterblocks():
                     for graph in graphs:
                         graphs_or_something[graph] = True
             if op.opname == 'oosend':
-                graphs_or_something[op.args[0]] = True # XXX?
+                meth = get_meth_from_oosend(op)
+                key = getattr(meth, 'graph', op.args[0])
+                graphs_or_something[key] = True
     return graphs_or_something
 
 def iter_callsites(graph, calling_what):
     for block in graph.iterblocks():
         for i, op in enumerate(block.operations):
-            if not op.opname == "direct_call":
+            if op.opname == "direct_call":
+                funcobj = get_funcobj(op.args[0].value)
+            elif op.opname == "oosend":
+                funcobj = get_meth_from_oosend(op)
+                if funcobj is None:
+                    continue # cannot inline virtual methods
+            else:
                 continue
-            funcobj = get_funcobj(op.args[0].value)
+
             graph = getattr(funcobj, 'graph', None)
             # accept a function or a graph as 'inline_func'
             if (graph is calling_what or
         self.cleanup()
         return count
 
+    def get_graph_from_op(self, op):
+        assert op.opname in ('direct_call', 'oosend')
+        if op.opname == 'direct_call':
+            return get_funcobj(self.op.args[0].value).graph
+        else:
+            return get_meth_from_oosend(op).graph
+
     def inline_once(self, block, index_operation):
         self.varmap = {}
         self._copied_blocks = {}
         self.op = block.operations[index_operation]
-        self.graph_to_inline = get_funcobj(self.op.args[0].value).graph
+        self.graph_to_inline = self.get_graph_from_op(self.op)
         self.exception_guarded = False
         if (block.exitswitch == c_last_exception and
             index_operation == len(block.operations) - 1):
     def search_for_calls(self, block):
         d = {}
         for i, op in enumerate(block.operations):
-            if not op.opname == "direct_call":
+            if op.opname == "direct_call":
+                funcobj = get_funcobj(op.args[0].value)
+            elif op.opname == "oosend":
+                funcobj = get_meth_from_oosend(op)
+                if funcobj is None:
+                    continue
+            else:
                 continue
-            funcobj = get_funcobj(op.args[0].value)
             graph = getattr(funcobj, 'graph', None)
             # accept a function or a graph as 'inline_func'
             if (graph is self.inline_func or
                                    'dont_inline', False):
                             continue
                         result.append((parentgraph, graph))
+                if op.opname == "oosend":
+                    meth = get_meth_from_oosend(op)
+                    graph = getattr(meth, 'graph', None)
+                    if graph is not None:
+                        result.append((parentgraph, graph))
     return result
-
-
+    
 def instrument_inline_candidates(graphs, multiplier):
     threshold = BASE_INLINE_THRESHOLD * multiplier
     cache = {None: False}

pypy/translator/backendopt/test/test_all.py

     type_system = 'ootype'
     check_malloc_removed = OOTypeMallocRemovalTest.check_malloc_removed
     
-    def test_big(self):
-        py.test.skip('FIXME! It should pass as long as oosend is inlined')

pypy/translator/backendopt/test/test_checkvirtual.py

     A = Instance("A", ROOT)
     addMethods(A, {"foo": meth(Meth([], Void))})
 
-    check_virtual_methods(ROOT)
+    check_virtual_methods()
     assert A._methods["foo"]._virtual == False
 
 def test_checkvirtual_simple():
     
     addMethods(B, {"foo": meth(Meth([], Void))})
 
-    check_virtual_methods(ROOT)
+    check_virtual_methods()
     assert A._methods["foo"]._virtual == True
     assert A._methods["bar"]._virtual == False
     assert B._methods["foo"]._virtual == False
     
     addMethods(C, {"foo": meth(Meth([], Void))})
 
-    check_virtual_methods(ROOT)
+    check_virtual_methods()
     assert A._methods["foo"]._virtual == True
     assert A._methods["bar"]._virtual == False
     assert "foo" not in B._methods
     
     addMethods(B1, {"foo": meth(Meth([], Void))})
 
-    check_virtual_methods(ROOT)
+    check_virtual_methods()
     assert A._methods["foo"]._virtual == True
     assert A._methods["bar"]._virtual == False
     assert B1._methods["foo"]._virtual == False

pypy/translator/backendopt/test/test_inline.py

 from pypy.translator.backendopt.inline import collect_called_graphs
 from pypy.translator.backendopt.inline import measure_median_execution_cost
 from pypy.translator.backendopt.inline import instrument_inline_candidates
+from pypy.translator.backendopt.checkvirtual import check_virtual_methods
 from pypy.translator.translator import TranslationContext, graphof
 from pypy.rpython.llinterp import LLInterpreter
 from pypy.rpython.test.tool import LLRtypeMixin, OORtypeMixin
             return interp.eval_graph(graphof(t, entry), args)
         return eval_func
 
-    def check_auto_inlining(self, func, sig, multiplier=None, call_count_check=False):
+    def check_auto_inlining(self, func, sig, multiplier=None, call_count_check=False,
+                            checkvirtual=False):
         t = self.translate(func, sig)
+        if checkvirtual:
+            check_virtual_methods()
         if option.view:
             t.view()
         # inline!
         expected = fn(0)
         res = eval_func([0])
         assert res == expected
+
+    def test_oosend(self):
+        class A:
+            def foo(self, x):
+                return x
+        def fn(x):
+            a = A()
+            return a.foo(x)
+
+        eval_func, t = self.check_auto_inlining(fn, [int], checkvirtual=True)
+        expected = fn(42)
+        res = eval_func([42])
+        assert res == expected
+
+    def test_not_inline_oosend(self):
+        class A:
+            def foo(self, x):
+                return x
+        class B(A):
+            def foo(self, x):
+                return x+1
+
+        def fn(flag, x):
+            if flag:
+                obj = A()
+            else:
+                obj = B()
+            return obj.foo(x)
+
+        eval_func, t = self.check_auto_inlining(fn, [bool, int], checkvirtual=True)
+        expected = fn(True, 42)
+        res = eval_func([True, 42])
+        assert res == expected
+
+    def test_classattr(self):
+        class A:
+            attr = 666
+        class B(A):
+            attr = 42
+        def fn5():
+            b = B()
+            return b.attr
+
+        eval_func, t = self.check_auto_inlining(fn5, [], checkvirtual=True)
+        res = eval_func([])
+        assert res == 42
+

pypy/translator/backendopt/test/test_malloc.py

         self.check(fn4, [int], [42], 42)
 
     def test_fn5(self):
-        self._skip_oo('It will work as soon as trivial oosend are inlined')
         class A:
             attr = 666
         class B(A):
             return x.foo
         self.check(fn, [], [], 42)
 
+    def test_fn5(self):
+        # don't test this in ootype because the class attribute access
+        # is turned into an oosend which prevents malloc removal to
+        # work unless we inline first. See test_classattr in
+        # test_inline.py
+        pass

pypy/translator/driver.py

             heap2stack=False,
             clever_malloc_removal=False)
         if self.config.translation.backend == 'cli':
-            from pypy.translator.backendopt.checkvirtual import check_virtual_methods
-            from pypy.rpython.ootypesystem import ootype
-            check_virtual_methods(ootype.ROOT)
             opt['merge_if_blocks'] = True
             opt['inline_threshold'] = 1
             opt['mallocs'] = True