/pypy/interpreter/executioncontext.py
Python | 625 lines | 441 code | 73 blank | 111 comment | 78 complexity | 125142d20ea767c6017265d8cf2ebbe1 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
- import sys
- from pypy.interpreter.error import OperationError, get_cleared_operation_error
- from rpython.rlib.unroll import unrolling_iterable
- from rpython.rlib.objectmodel import specialize
- from rpython.rlib import jit, rgc
- TICK_COUNTER_STEP = 100
- def app_profile_call(space, w_callable, frame, event, w_arg):
- space.call_function(w_callable,
- space.wrap(frame),
- space.wrap(event), w_arg)
- class ExecutionContext(object):
- """An ExecutionContext holds the state of an execution thread
- in the Python interpreter."""
- # XXX JIT: when tracing (but not when blackholing!), the following
- # XXX fields should be known to a constant None or False:
- # XXX self.w_tracefunc, self.profilefunc
- # XXX frame.is_being_profiled
- # XXX [fijal] but they're not. is_being_profiled is guarded a bit all
- # over the place as well as w_tracefunc
- _immutable_fields_ = ['profilefunc?', 'w_tracefunc?']
- def __init__(self, space):
- self.space = space
- self.topframeref = jit.vref_None
- self.w_tracefunc = None
- self.is_tracing = 0
- self.compiler = space.createcompiler()
- self.profilefunc = None
- self.w_profilefuncarg = None
- self.thread_disappeared = False # might be set to True after os.fork()
- @staticmethod
- def _mark_thread_disappeared(space):
- # Called in the child process after os.fork() by interp_posix.py.
- # Marks all ExecutionContexts except the current one
- # with 'thread_disappeared = True'.
- me = space.getexecutioncontext()
- for ec in space.threadlocals.getallvalues().values():
- if ec is not me:
- ec.thread_disappeared = True
- def gettopframe(self):
- return self.topframeref()
- @jit.unroll_safe
- def gettopframe_nohidden(self):
- frame = self.topframeref()
- while frame and frame.hide():
- frame = frame.f_backref()
- return frame
- @staticmethod
- @jit.unroll_safe # should usually loop 0 times, very rarely more than once
- def getnextframe_nohidden(frame):
- frame = frame.f_backref()
- while frame and frame.hide():
- frame = frame.f_backref()
- return frame
- def enter(self, frame):
- frame.f_backref = self.topframeref
- self.topframeref = jit.virtual_ref(frame)
- def leave(self, frame, w_exitvalue, got_exception):
- try:
- if self.profilefunc:
- self._trace(frame, 'leaveframe', w_exitvalue)
- finally:
- frame_vref = self.topframeref
- self.topframeref = frame.f_backref
- if frame.escaped or got_exception:
- # if this frame escaped to applevel, we must ensure that also
- # f_back does
- f_back = frame.f_backref()
- if f_back:
- f_back.mark_as_escaped()
- # force the frame (from the JIT point of view), so that it can
- # be accessed also later
- frame_vref()
- jit.virtual_ref_finish(frame_vref, frame)
- # ________________________________________________________________
- def c_call_trace(self, frame, w_func, args=None):
- "Profile the call of a builtin function"
- self._c_call_return_trace(frame, w_func, args, 'c_call')
- def c_return_trace(self, frame, w_func, args=None):
- "Profile the return from a builtin function"
- self._c_call_return_trace(frame, w_func, args, 'c_return')
- def _c_call_return_trace(self, frame, w_func, args, event):
- if self.profilefunc is None:
- frame.getorcreatedebug().is_being_profiled = False
- else:
- # undo the effect of the CALL_METHOD bytecode, which would be
- # that even on a built-in method call like '[].append()',
- # w_func is actually the unbound function 'append'.
- from pypy.interpreter.function import FunctionWithFixedCode
- if isinstance(w_func, FunctionWithFixedCode) and args is not None:
- w_firstarg = args.firstarg()
- if w_firstarg is not None:
- from pypy.interpreter.function import descr_function_get
- w_func = descr_function_get(self.space, w_func, w_firstarg,
- self.space.type(w_firstarg))
- #
- self._trace(frame, event, w_func)
- def c_exception_trace(self, frame, w_exc):
- "Profile function called upon OperationError."
- if self.profilefunc is None:
- frame.getorcreatedebug().is_being_profiled = False
- else:
- self._trace(frame, 'c_exception', w_exc)
- def call_trace(self, frame):
- "Trace the call of a function"
- if self.gettrace() is not None or self.profilefunc is not None:
- self._trace(frame, 'call', self.space.w_None)
- if self.profilefunc:
- frame.getorcreatedebug().is_being_profiled = True
- def return_trace(self, frame, w_retval):
- "Trace the return from a function"
- if self.gettrace() is not None:
- self._trace(frame, 'return', w_retval)
- def bytecode_trace(self, frame, decr_by=TICK_COUNTER_STEP):
- "Trace function called before each bytecode."
- # this is split into a fast path and a slower path that is
- # not invoked every time bytecode_trace() is.
- self.bytecode_only_trace(frame)
- actionflag = self.space.actionflag
- if actionflag.decrement_ticker(decr_by) < 0:
- actionflag.action_dispatcher(self, frame) # slow path
- bytecode_trace._always_inline_ = True
- def _run_finalizers_now(self):
- # Tests only: run the actions now, to ensure that the
- # finalizable objects are really finalized. Used notably by
- # pypy.tool.pytest.apptest.
- self.space.actionflag.action_dispatcher(self, None)
- def bytecode_only_trace(self, frame):
- """
- Like bytecode_trace() but doesn't invoke any other events besides the
- trace function.
- """
- if (frame.get_w_f_trace() is None or self.is_tracing or
- self.gettrace() is None):
- return
- self.run_trace_func(frame)
- bytecode_only_trace._always_inline_ = True
- @jit.unroll_safe
- def run_trace_func(self, frame):
- code = frame.pycode
- d = frame.getorcreatedebug()
- if d.instr_lb <= frame.last_instr < d.instr_ub:
- if frame.last_instr < d.instr_prev_plus_one:
- # We jumped backwards in the same line.
- self._trace(frame, 'line', self.space.w_None)
- else:
- size = len(code.co_lnotab) / 2
- addr = 0
- line = code.co_firstlineno
- p = 0
- lineno = code.co_lnotab
- while size > 0:
- c = ord(lineno[p])
- if (addr + c) > frame.last_instr:
- break
- addr += c
- if c:
- d.instr_lb = addr
- line += ord(lineno[p + 1])
- p += 2
- size -= 1
- if size > 0:
- while True:
- size -= 1
- if size < 0:
- break
- addr += ord(lineno[p])
- if ord(lineno[p + 1]):
- break
- p += 2
- d.instr_ub = addr
- else:
- d.instr_ub = sys.maxint
- if d.instr_lb == frame.last_instr: # At start of line!
- d.f_lineno = line
- self._trace(frame, 'line', self.space.w_None)
- d.instr_prev_plus_one = frame.last_instr + 1
- def bytecode_trace_after_exception(self, frame):
- "Like bytecode_trace(), but without increasing the ticker."
- actionflag = self.space.actionflag
- self.bytecode_only_trace(frame)
- if actionflag.get_ticker() < 0:
- actionflag.action_dispatcher(self, frame) # slow path
- bytecode_trace_after_exception._always_inline_ = 'try'
- # NB. this function is not inlined right now. backendopt.inline would
- # need some improvements to handle this case, but it's not really an
- # issue
- def exception_trace(self, frame, operationerr):
- "Trace function called upon OperationError."
- if self.gettrace() is not None:
- self._trace(frame, 'exception', None, operationerr)
- #operationerr.print_detailed_traceback(self.space)
- @jit.dont_look_inside
- @specialize.arg(1)
- def sys_exc_info(self, for_hidden=False):
- """Implements sys.exc_info().
- Return an OperationError instance or None.
- Ignores exceptions within hidden frames unless for_hidden=True
- is specified.
- # NOTE: the result is not the wrapped sys.exc_info() !!!
- """
- return self.gettopframe()._exc_info_unroll(self.space, for_hidden)
- def set_sys_exc_info(self, operror):
- frame = self.gettopframe_nohidden()
- if frame: # else, the exception goes nowhere and is lost
- frame.last_exception = operror
- def clear_sys_exc_info(self):
- # Find the frame out of which sys_exc_info() would return its result,
- # and hack this frame's last_exception to become the cleared
- # OperationError (which is different from None!).
- frame = self.gettopframe_nohidden()
- while frame:
- if frame.last_exception is not None:
- frame.last_exception = get_cleared_operation_error(self.space)
- break
- frame = self.getnextframe_nohidden(frame)
- @jit.dont_look_inside
- def settrace(self, w_func):
- """Set the global trace function."""
- if self.space.is_w(w_func, self.space.w_None):
- self.w_tracefunc = None
- else:
- self.force_all_frames()
- self.w_tracefunc = w_func
- # Increase the JIT's trace_limit when we have a tracefunc, it
- # generates a ton of extra ops.
- jit.set_param(None, 'trace_limit', 10000)
- def gettrace(self):
- return jit.promote(self.w_tracefunc)
- def setprofile(self, w_func):
- """Set the global trace function."""
- if self.space.is_w(w_func, self.space.w_None):
- self.profilefunc = None
- self.w_profilefuncarg = None
- else:
- self.setllprofile(app_profile_call, w_func)
- def getprofile(self):
- return self.w_profilefuncarg
- def setllprofile(self, func, w_arg):
- if func is not None:
- if w_arg is None:
- raise ValueError("Cannot call setllprofile with real None")
- self.force_all_frames(is_being_profiled=True)
- self.profilefunc = func
- self.w_profilefuncarg = w_arg
- def force_all_frames(self, is_being_profiled=False):
- # "Force" all frames in the sense of the jit, and optionally
- # set the flag 'is_being_profiled' on them. A forced frame is
- # one out of which the jit will exit: if it is running so far,
- # in a piece of assembler currently running a CALL_MAY_FORCE,
- # then being forced means that it will fail the following
- # GUARD_NOT_FORCED operation, and so fall back to interpreted
- # execution. (We get this effect simply by reading the f_back
- # field of all frames, during the loop below.)
- frame = self.gettopframe_nohidden()
- while frame:
- if is_being_profiled:
- frame.getorcreatedebug().is_being_profiled = True
- frame = self.getnextframe_nohidden(frame)
- def call_tracing(self, w_func, w_args):
- is_tracing = self.is_tracing
- self.is_tracing = 0
- try:
- return self.space.call(w_func, w_args)
- finally:
- self.is_tracing = is_tracing
- def _trace(self, frame, event, w_arg, operr=None):
- if self.is_tracing or frame.hide():
- return
- space = self.space
- # Tracing cases
- if event == 'call':
- w_callback = self.gettrace()
- else:
- w_callback = frame.get_w_f_trace()
- if w_callback is not None and event != "leaveframe":
- if operr is not None:
- w_value = operr.get_w_value(space)
- w_arg = space.newtuple([operr.w_type, w_value,
- space.wrap(operr.get_traceback())])
- d = frame.getorcreatedebug()
- if d.w_locals is not None:
- # only update the w_locals dict if it exists
- # if it does not exist yet and the tracer accesses it via
- # frame.f_locals, it is filled by PyFrame.getdictscope
- frame.fast2locals()
- self.is_tracing += 1
- try:
- try:
- w_result = space.call_function(w_callback, space.wrap(frame), space.wrap(event), w_arg)
- if space.is_w(w_result, space.w_None):
- d.w_f_trace = None
- else:
- d.w_f_trace = w_result
- except:
- self.settrace(space.w_None)
- d.w_f_trace = None
- raise
- finally:
- self.is_tracing -= 1
- if d.w_locals is not None:
- frame.locals2fast()
- # Profile cases
- if self.profilefunc is not None:
- if not (event == 'leaveframe' or
- event == 'call' or
- event == 'c_call' or
- event == 'c_return' or
- event == 'c_exception'):
- return
- last_exception = frame.last_exception
- if event == 'leaveframe':
- event = 'return'
- assert self.is_tracing == 0
- self.is_tracing += 1
- try:
- try:
- self.profilefunc(space, self.w_profilefuncarg,
- frame, event, w_arg)
- except:
- self.profilefunc = None
- self.w_profilefuncarg = None
- raise
- finally:
- frame.last_exception = last_exception
- self.is_tracing -= 1
- def checksignals(self):
- """Similar to PyErr_CheckSignals(). If called in the main thread,
- and if signals are pending for the process, deliver them now
- (i.e. call the signal handlers)."""
- if self.space.check_signal_action is not None:
- self.space.check_signal_action.perform(self, None)
- def _freeze_(self):
- raise Exception("ExecutionContext instances should not be seen during"
- " translation. Now is a good time to inspect the"
- " traceback and see where this one comes from :-)")
- class AbstractActionFlag(object):
- """This holds in an integer the 'ticker'. If threads are enabled,
- it is decremented at each bytecode; when it reaches zero, we release
- the GIL. And whether we have threads or not, it is forced to zero
- whenever we fire any of the asynchronous actions.
- """
- _immutable_fields_ = ["checkinterval_scaled?"]
- def __init__(self):
- self._periodic_actions = []
- self._nonperiodic_actions = []
- self.has_bytecode_counter = False
- self.fired_actions = None
- # the default value is not 100, unlike CPython 2.7, but a much
- # larger value, because we use a technique that not only allows
- # but actually *forces* another thread to run whenever the counter
- # reaches zero.
- self.checkinterval_scaled = 10000 * TICK_COUNTER_STEP
- self._rebuild_action_dispatcher()
- def fire(self, action):
- """Request for the action to be run before the next opcode."""
- if not action._fired:
- action._fired = True
- if self.fired_actions is None:
- self.fired_actions = []
- self.fired_actions.append(action)
- # set the ticker to -1 in order to force action_dispatcher()
- # to run at the next possible bytecode
- self.reset_ticker(-1)
- def register_periodic_action(self, action, use_bytecode_counter):
- """NOT_RPYTHON:
- Register the PeriodicAsyncAction action to be called whenever the
- tick counter becomes smaller than 0. If 'use_bytecode_counter' is
- True, make sure that we decrease the tick counter at every bytecode.
- This is needed for threads. Note that 'use_bytecode_counter' can be
- False for signal handling, because whenever the process receives a
- signal, the tick counter is set to -1 by C code in signals.h.
- """
- assert isinstance(action, PeriodicAsyncAction)
- # hack to put the release-the-GIL one at the end of the list,
- # and the report-the-signals one at the start of the list.
- if use_bytecode_counter:
- self._periodic_actions.append(action)
- self.has_bytecode_counter = True
- else:
- self._periodic_actions.insert(0, action)
- self._rebuild_action_dispatcher()
- def getcheckinterval(self):
- return self.checkinterval_scaled // TICK_COUNTER_STEP
- def setcheckinterval(self, interval):
- MAX = sys.maxint // TICK_COUNTER_STEP
- if interval < 1:
- interval = 1
- elif interval > MAX:
- interval = MAX
- self.checkinterval_scaled = interval * TICK_COUNTER_STEP
- self.reset_ticker(-1)
- def _rebuild_action_dispatcher(self):
- periodic_actions = unrolling_iterable(self._periodic_actions)
- @jit.unroll_safe
- def action_dispatcher(ec, frame):
- # periodic actions (first reset the bytecode counter)
- self.reset_ticker(self.checkinterval_scaled)
- for action in periodic_actions:
- action.perform(ec, frame)
- # nonperiodic actions
- list = self.fired_actions
- if list is not None:
- self.fired_actions = None
- # NB. in case there are several actions, we reset each
- # 'action._fired' to false only when we're about to call
- # 'action.perform()'. This means that if
- # 'action.fire()' happens to be called any time before
- # the corresponding perform(), the fire() has no
- # effect---which is the effect we want, because
- # perform() will be called anyway.
- for action in list:
- action._fired = False
- action.perform(ec, frame)
- action_dispatcher._dont_inline_ = True
- self.action_dispatcher = action_dispatcher
- class ActionFlag(AbstractActionFlag):
- """The normal class for space.actionflag. The signal module provides
- a different one."""
- _ticker = 0
- def get_ticker(self):
- return self._ticker
- def reset_ticker(self, value):
- self._ticker = value
- def decrement_ticker(self, by):
- value = self._ticker
- if self.has_bytecode_counter: # this 'if' is constant-folded
- if jit.isconstant(by) and by == 0:
- pass # normally constant-folded too
- else:
- value -= by
- self._ticker = value
- return value
- class AsyncAction(object):
- """Abstract base class for actions that must be performed
- asynchronously with regular bytecode execution, but that still need
- to occur between two opcodes, not at a completely random time.
- """
- _fired = False
- def __init__(self, space):
- self.space = space
- def fire(self):
- """Request for the action to be run before the next opcode.
- The action must have been registered at space initalization time."""
- self.space.actionflag.fire(self)
- def perform(self, executioncontext, frame):
- """To be overridden."""
- class PeriodicAsyncAction(AsyncAction):
- """Abstract base class for actions that occur automatically
- every sys.checkinterval bytecodes.
- """
- class UserDelAction(AsyncAction):
- """An action that invokes all pending app-level __del__() method.
- This is done as an action instead of immediately when the
- WRootFinalizerQueue is triggered, because the latter can occur more
- or less anywhere in the middle of code that might not be happy with
- random app-level code mutating data structures under its feet.
- """
- def __init__(self, space):
- AsyncAction.__init__(self, space)
- self.finalizers_lock_count = 0 # see pypy/module/gc
- self.enabled_at_app_level = True # see pypy/module/gc
- self.pending_with_disabled_del = None
- def perform(self, executioncontext, frame):
- self._run_finalizers()
- @jit.dont_look_inside
- def _run_finalizers(self):
- while True:
- w_obj = self.space.finalizer_queue.next_dead()
- if w_obj is None:
- break
- self._call_finalizer(w_obj)
- def gc_disabled(self, w_obj):
- # If we're running in 'gc.disable()' mode, record w_obj in the
- # "call me later" list and return True. In normal mode, return
- # False. Use this function from some _finalize_() methods:
- # if a _finalize_() method would call some user-defined
- # app-level function, like a weakref callback, then first do
- # 'if gc.disabled(self): return'. Another attempt at
- # calling _finalize_() will be made after 'gc.enable()'.
- # (The exact rule for when to use gc_disabled() or not is a bit
- # vague, but most importantly this includes all user-level
- # __del__().)
- pdd = self.pending_with_disabled_del
- if pdd is None:
- return False
- else:
- pdd.append(w_obj)
- return True
- def _call_finalizer(self, w_obj):
- # Before calling the finalizers, clear the weakrefs, if any.
- w_obj.clear_all_weakrefs()
- # Look up and call the app-level __del__, if any.
- space = self.space
- if w_obj.typedef is None:
- w_del = None # obscure case: for WeakrefLifeline
- else:
- w_del = space.lookup(w_obj, '__del__')
- if w_del is not None:
- if self.gc_disabled(w_obj):
- return
- try:
- space.get_and_call_function(w_del, w_obj)
- except Exception as e:
- report_error(space, e, "method __del__ of ", w_obj)
- # Call the RPython-level _finalize_() method.
- try:
- w_obj._finalize_()
- except Exception as e:
- report_error(space, e, "finalizer of ", w_obj)
- def report_error(space, e, where, w_obj):
- if isinstance(e, OperationError):
- e.write_unraisable(space, where, w_obj)
- e.clear(space) # break up reference cycles
- else:
- addrstring = w_obj.getaddrstring(space)
- msg = ("RPython exception %s in %s<%s at 0x%s> ignored\n" % (
- str(e), where, space.type(w_obj).name, addrstring))
- space.call_method(space.sys.get('stderr'), 'write',
- space.wrap(msg))
- def make_finalizer_queue(W_Root, space):
- """Make a FinalizerQueue subclass which responds to GC finalizer
- events by 'firing' the UserDelAction class above. It does not
- directly fetches the objects to finalize at all; they stay in the
- GC-managed queue, and will only be fetched by UserDelAction
- (between bytecodes)."""
- class WRootFinalizerQueue(rgc.FinalizerQueue):
- Class = W_Root
- def finalizer_trigger(self):
- space.user_del_action.fire()
- space.user_del_action = UserDelAction(space)
- space.finalizer_queue = WRootFinalizerQueue()