Commits

Armin Rigo  committed 42c400c

Optimization for the JIT: do not escape the frame when seeing code that
reads e.g. 'sys.exc_info()[1]' or 'sys.exc_info()[:2]'. The frame would
escape only if we read the last item of the returned tuple. Lots of
tweaks needed, but at least in the simple cases it should work.

  • Participants
  • Parent commits 45e80fe

Comments (0)

Files changed (6)

File pypy/interpreter/baseobjspace.py

         self.check_signal_action = None   # changed by the signal module
         self.user_del_action = UserDelAction(self)
         self.frame_trace_action = FrameTraceAction(self)
+        self._code_of_sys_exc_info = None
 
         from pypy.interpreter.pycode import cpython_magic, default_magic
         self.our_magic = default_magic

File pypy/interpreter/executioncontext.py

         #operationerr.print_detailed_traceback(self.space)
 
     def _convert_exc(self, operr):
+        # Only for the flow object space
         return operr
 
     def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!!

File pypy/interpreter/function.py

         from pypy.interpreter.pycode import PyCode
 
         code = self.getcode() # hook for the jit
+        #
+        if (jit.we_are_jitted() and code is self.space._code_of_sys_exc_info
+                                and nargs == 0):
+            from pypy.module.sys.vm import exc_info_direct
+            return exc_info_direct(self.space, frame)
+        #
         fast_natural_arity = code.fast_natural_arity
         if nargs == fast_natural_arity:
             if nargs == 0:

File pypy/interpreter/gateway.py

             fn.add_to_table()
         if gateway.as_classmethod:
             fn = ClassMethod(space.wrap(fn))
+        #
+        from pypy.module.sys.vm import exc_info
+        if code._bltin is exc_info:
+            assert space._code_of_sys_exc_info is None
+            space._code_of_sys_exc_info = code
+        #
         return fn
 
 

File pypy/module/sys/test/test_sysmodule.py

         assert len(frames) == 1
         _, other_frame = frames.popitem()
         assert other_frame.f_code.co_name in ('other_thread', '?')
+
+
+class AppTestSysExcInfoDirect:
+
+    def setup_method(self, meth):
+        self.seen = []
+        from pypy.module.sys import vm
+        def exc_info_with_tb(*args):
+            self.seen.append("n")     # not optimized
+            return self.old[0](*args)
+        def exc_info_without_tb(*args):
+            self.seen.append("y")     # optimized
+            return self.old[1](*args)
+        self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb]
+        vm.exc_info_with_tb = exc_info_with_tb
+        vm.exc_info_without_tb = exc_info_without_tb
+        #
+        from pypy.rlib import jit
+        self.old2 = [jit.we_are_jitted]
+        jit.we_are_jitted = lambda: True
+
+    def teardown_method(self, meth):
+        from pypy.module.sys import vm
+        from pypy.rlib import jit
+        vm.exc_info_with_tb = self.old[0]
+        vm.exc_info_without_tb = self.old[1]
+        jit.we_are_jitted = self.old2[0]
+        #
+        assert ''.join(self.seen) == meth.expected
+
+    def test_returns_none(self):
+        import sys
+        assert sys.exc_info() == (None, None, None)
+        assert sys.exc_info()[0] is None
+        assert sys.exc_info()[1] is None
+        assert sys.exc_info()[2] is None
+        assert sys.exc_info()[:2] == (None, None)
+        assert sys.exc_info()[:3] == (None, None, None)
+        assert sys.exc_info()[0:2] == (None, None)
+        assert sys.exc_info()[2:4] == (None,)
+    test_returns_none.expected = 'nnnnnnnn'
+
+    def test_returns_subscr(self):
+        import sys
+        e = KeyError("boom")
+        try:
+            raise e
+        except:
+            assert sys.exc_info()[0] is KeyError  # y
+            assert sys.exc_info()[1] is e         # y
+            assert sys.exc_info()[2] is not None  # n
+            assert sys.exc_info()[-3] is KeyError # y
+            assert sys.exc_info()[-2] is e        # y
+            assert sys.exc_info()[-1] is not None # n
+    test_returns_subscr.expected = 'yynyyn'
+
+    def test_returns_slice_2(self):
+        import sys
+        e = KeyError("boom")
+        try:
+            raise e
+        except:
+            foo = sys.exc_info()                  # n
+            assert sys.exc_info()[:0] == ()       # y
+            assert sys.exc_info()[:1] == foo[:1]  # y
+            assert sys.exc_info()[:2] == foo[:2]  # y
+            assert sys.exc_info()[:3] == foo      # n
+            assert sys.exc_info()[:4] == foo      # n
+            assert sys.exc_info()[:-1] == foo[:2] # y
+            assert sys.exc_info()[:-2] == foo[:1] # y
+            assert sys.exc_info()[:-3] == ()      # y
+    test_returns_slice_2.expected = 'nyyynnyyy'
+
+    def test_returns_slice_3(self):
+        import sys
+        e = KeyError("boom")
+        try:
+            raise e
+        except:
+            foo = sys.exc_info()                   # n
+            assert sys.exc_info()[2:2] == ()       # y
+            assert sys.exc_info()[0:1] == foo[:1]  # y
+            assert sys.exc_info()[1:2] == foo[1:2] # y
+            assert sys.exc_info()[0:3] == foo      # n
+            assert sys.exc_info()[2:4] == foo[2:]  # n
+            assert sys.exc_info()[0:-1] == foo[:2] # y
+            assert sys.exc_info()[0:-2] == foo[:1] # y
+            assert sys.exc_info()[5:-3] == ()      # y
+    test_returns_slice_3.expected = 'nyyynnyyy'
+
+    def test_strange_invocation(self):
+        import sys
+        e = KeyError("boom")
+        try:
+            raise e
+        except:
+            a = []; k = {}
+            assert sys.exc_info(*a)[:0] == ()
+            assert sys.exc_info(**k)[:0] == ()
+    test_strange_invocation.expected = 'nn'
+
+    def test_call_in_subfunction(self):
+        import sys
+        def g():
+            # this case is not optimized, because we need to search the
+            # frame chain.  it's probably not worth the complications
+            return sys.exc_info()[1]
+        e = KeyError("boom")
+        try:
+            raise e
+        except:
+            assert g() is e
+    test_call_in_subfunction.expected = 'n'
+
+
+class AppTestSysExcInfoDirectCallMethod(AppTestSysExcInfoDirect):
+    def setup_class(cls):
+        cls.space = gettestobjspace(**{"objspace.opcodes.CALL_METHOD": True})

File pypy/module/sys/vm.py

     """Return the (type, value, traceback) of the most recent exception
 caught by an except clause in the current stack frame or in an older stack
 frame."""
+    return exc_info_with_tb(space)    # indirection for the tests
+
+def exc_info_with_tb(space):
     operror = space.getexecutioncontext().sys_exc_info()
     if operror is None:
         return space.newtuple([space.w_None,space.w_None,space.w_None])
         return space.newtuple([operror.w_type, operror.get_w_value(space),
                                space.wrap(operror.get_traceback())])
 
+def exc_info_without_tb(space, frame):
+    operror = frame.last_exception
+    return space.newtuple([operror.w_type, operror.get_w_value(space),
+                           space.w_None])
+
+def exc_info_direct(space, frame):
+    from pypy.tool import stdlib_opcode
+    # In order to make the JIT happy, we try to return (exc, val, None)
+    # instead of (exc, val, tb).  We can do that only if we recognize
+    # the following pattern in the bytecode:
+    #       CALL_FUNCTION/CALL_METHOD         <-- invoking me
+    #       LOAD_CONST 0, 1, -2 or -3
+    #       BINARY_SUBSCR
+    # or:
+    #       CALL_FUNCTION/CALL_METHOD
+    #       LOAD_CONST <=2
+    #       SLICE_2
+    # or:
+    #       CALL_FUNCTION/CALL_METHOD
+    #       LOAD_CONST any integer
+    #       LOAD_CONST <=2
+    #       SLICE_3
+    need_all_three_args = True
+    co = frame.getcode().co_code
+    p = frame.last_instr
+    if (ord(co[p]) == stdlib_opcode.CALL_FUNCTION or
+        ord(co[p]) == stdlib_opcode.CALL_METHOD):
+        if ord(co[p+3]) == stdlib_opcode.LOAD_CONST:
+            lo = ord(co[p+4])
+            hi = ord(co[p+5])
+            w_constant = frame.getconstant_w((hi * 256) | lo)
+            if space.isinstance_w(w_constant, space.w_int):
+                constant = space.int_w(w_constant)
+                if ord(co[p+6]) == stdlib_opcode.BINARY_SUBSCR:
+                    if -3 <= constant <= 1 and constant != -1:
+                        need_all_three_args = False
+                elif ord(co[p+6]) == stdlib_opcode.SLICE+2:
+                    if constant <= 2:
+                        need_all_three_args = False
+                elif (ord(co[p+6]) == stdlib_opcode.LOAD_CONST and
+                      ord(co[p+9]) == stdlib_opcode.SLICE+3):
+                    lo = ord(co[p+7])
+                    hi = ord(co[p+8])
+                    w_constant = frame.getconstant_w((hi * 256) | lo)
+                    if space.isinstance_w(w_constant, space.w_int):
+                        if space.int_w(w_constant) <= 2:
+                            need_all_three_args = False
+    #
+    if need_all_three_args or frame.last_exception is None or frame.hide():
+        return exc_info_with_tb(space)
+    else:
+        return exc_info_without_tb(space, frame)
+
 def exc_clear(space):
     """Clear global information on the current exception.  Subsequent calls
 to exc_info() will return (None,None,None) until another exception is