Author: Philip Jenvey <[email protected]>
Branch: py3k
Changeset: r72097:53a145f0af1d
Date: 2014-06-17 20:10 -0700
http://bitbucket.org/pypy/pypy/changeset/53a145f0af1d/
Log: improve __context__ setup vis-a-vis the JIT:
defer its setup, when possible, until __context__ is explicitly
requested by searching for it through the traceback. it's not
possible to defer when __context__ comes from the current frame as
the new exception eventually overwrites it (in frame.last_exception)
in that case we do the setup sooner, when recording the traceback,
with little cost.. until we need to break __context__ chain cycles
(which I'll partly disable in the next commit)
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -36,44 +36,11 @@
def setup(self, w_type, w_value=None):
assert w_type is not None
- from pypy.objspace.std.typeobject import W_TypeObject
self.w_type = w_type
self._w_value = w_value
- # HACK: isinstance(w_type, W_TypeObject) won't translate under
- # the fake objspace, but w_type.__class__ is W_TypeObject does
- # and short circuits to a False constant there, causing the
- # isinstance to be ignored =[
- if (w_type is not None and w_type.__class__ is W_TypeObject and
- isinstance(w_type, W_TypeObject)):
- self.setup_context(w_type.space)
if not we_are_translated():
self.debug_excs = []
- def setup_context(self, space):
- # Implicit exception chaining
- last_operror = space.getexecutioncontext().sys_exc_info()
- if (last_operror is None or
- last_operror is get_cleared_operation_error(space)):
- return
-
- # We must normalize the value right now to check for cycles
- self.normalize_exception(space)
- w_value = self.get_w_value(space)
- w_last_value = last_operror.get_w_value(space)
- if not space.is_w(w_value, w_last_value):
- # Avoid reference cycles through the context chain. This is
- # O(chain length) but context chains are usually very short.
- w_obj = w_last_value
- while True:
- w_context = space.getattr(w_obj, space.wrap('__context__'))
- if space.is_w(w_context, space.w_None):
- break
- if space.is_w(w_context, w_value):
- space.setattr(w_obj, space.wrap('__context__'),
space.w_None)
- break
- w_obj = w_context
- space.setattr(w_value, space.wrap('__context__'), w_last_value)
-
def clear(self, space):
# XXX remove this method. The point is that we cannot always
# hack at 'self' to clear w_type and _w_value, because in some
@@ -350,6 +317,51 @@
"""
self._application_traceback = traceback
+ def record_context(self, space, frame):
+ """Record a __context__ for this exception from the current
+ frame if one exists.
+
+ __context__ is otherwise lazily determined from the
+ traceback. However the current frame.last_exception must be
+ checked for a __context__ before this OperationError overwrites
+ it (making the previous last_exception unavailable later on).
+ """
+ last_exception = frame.last_exception
+ if (last_exception is not None and not frame.hide() or
+ last_exception is get_cleared_operation_error(space)):
+ # normalize w_value so setup_context can check for cycles
+ self.normalize_exception(space)
+ w_value = self.get_w_value(space)
+ w_context = setup_context(space, w_value,
+ last_exception.get_w_value(space))
+ space.setattr(w_value, space.wrap('__context__'), w_context)
+
+
+def setup_context(space, w_exc, w_last):
+ """Determine the __context__ for w_exc from w_last and break
+ reference cycles in the __context__ chain.
+ """
+ if space.is_w(w_exc, w_last):
+ w_last = space.w_None
+ # w_last may also be space.w_None if from ClearedOpErr
+ if not space.is_w(w_last, space.w_None):
+ # Avoid reference cycles through the context chain. This is
+ # O(chain length) but context chains are usually very short.
+ w_obj = w_last
+ while True:
+ # XXX: __context__ becomes not so lazy when we're forced to
+ # access it here! Could this be defered till later? Or at
+ # least limit the check to W_BaseException.w_context
+ # (avoiding W_BaseException._setup_context)
+ w_context = space.getattr(w_obj, space.wrap('__context__'))
+ if space.is_w(w_context, space.w_None):
+ break
+ if space.is_w(w_context, w_exc):
+ space.setattr(w_obj, space.wrap('__context__'), space.w_None)
+ break
+ w_obj = w_context
+ return w_last
+
class ClearedOpErr:
def __init__(self, space):
diff --git a/pypy/interpreter/executioncontext.py
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -202,18 +202,21 @@
self._trace(frame, 'exception', None, operationerr)
#operationerr.print_detailed_traceback(self.space)
+ @staticmethod
+ def last_operr(space, frame):
+ while frame:
+ last = frame.last_exception
+ if (last is not None and
+ (not frame.hide() or
+ last is get_cleared_operation_error(space))):
+ return last
+ frame = frame.f_backref()
+ return None
+
def sys_exc_info(self): # attn: the result is not the wrapped
sys.exc_info() !!!
"""Implements sys.exc_info().
Return an OperationError instance or None."""
- frame = self.gettopframe()
- while frame:
- if frame.last_exception is not None:
- if (not frame.hide() or
- frame.last_exception is
- get_cleared_operation_error(self.space)):
- return frame.last_exception
- frame = frame.f_backref()
- return None
+ return self.last_operr(self.space, self.gettopframe())
def set_sys_exc_info(self, operror):
frame = self.gettopframe_nohidden()
diff --git a/pypy/interpreter/pytraceback.py b/pypy/interpreter/pytraceback.py
--- a/pypy/interpreter/pytraceback.py
+++ b/pypy/interpreter/pytraceback.py
@@ -57,6 +57,7 @@
tb = operror.get_traceback()
tb = PyTraceback(space, frame, last_instruction, tb)
operror.set_traceback(tb)
+ operror.record_context(space, frame)
def check_traceback(space, w_tb, msg):
diff --git a/pypy/interpreter/test/test_raise.py
b/pypy/interpreter/test/test_raise.py
--- a/pypy/interpreter/test/test_raise.py
+++ b/pypy/interpreter/test/test_raise.py
@@ -369,6 +369,23 @@
else:
fail("No exception raised")
+ def test_context_once_removed(self):
+ context = IndexError()
+ def func1():
+ func2()
+ def func2():
+ try:
+ 1/0
+ except ZeroDivisionError as e:
+ assert e.__context__ is context
+ else:
+ fail('No exception raised')
+ try:
+ raise context
+ except:
+ func1()
+
+
class AppTestTraceback:
def test_raise_with___traceback__(self):
diff --git a/pypy/module/exceptions/interp_exceptions.py
b/pypy/module/exceptions/interp_exceptions.py
--- a/pypy/module/exceptions/interp_exceptions.py
+++ b/pypy/module/exceptions/interp_exceptions.py
@@ -76,8 +76,8 @@
from pypy.interpreter.typedef import (TypeDef, GetSetProperty, descr_get_dict,
descr_set_dict, descr_del_dict)
from pypy.interpreter.gateway import interp2app
-from pypy.interpreter.error import OperationError
-from pypy.interpreter.pytraceback import check_traceback
+from pypy.interpreter.error import OperationError, setup_context
+from pypy.interpreter.pytraceback import PyTraceback, check_traceback
from rpython.rlib import rwin32
@@ -156,7 +156,27 @@
self.w_cause = w_newcause
def descr_getcontext(self, space):
- return self.w_context
+ w_context = self.w_context
+ if w_context is None:
+ self.w_context = w_context = self._setup_context(space)
+ return w_context
+
+ def _setup_context(self, space):
+ """Lazily determine __context__ from w_traceback"""
+ # XXX: w_traceback can be overwritten: it's not necessarily the
+ # authoratative traceback!
+ last_operr = None
+ w_traceback = self.w_traceback
+ if w_traceback is not None and isinstance(w_traceback, PyTraceback):
+ ec = space.getexecutioncontext()
+ # search for __context__ beginning in the previous frame. A
+ # __context__ from the top most frame would have already
+ # been handled by OperationError.record_context
+ last_operr = ec.last_operr(space, w_traceback.frame.f_backref())
+ if last_operr is None:
+ # no __context__
+ return space.w_None
+ return setup_context(space, self, last_operr.get_w_value(space))
def descr_setcontext(self, space, w_newcontext):
if not (space.is_w(w_newcontext, space.w_None) or
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit