Author: Alex Gaynor <alex.gay...@gmail.com> Branch: Changeset: r46053:25e36db72459 Date: 2011-07-28 09:07 -0700 http://bitbucket.org/pypy/pypy/changeset/25e36db72459/
Log: merged upstream. diff --git a/pypy/jit/codewriter/call.py b/pypy/jit/codewriter/call.py --- a/pypy/jit/codewriter/call.py +++ b/pypy/jit/codewriter/call.py @@ -228,8 +228,10 @@ elif loopinvariant: extraeffect = EffectInfo.EF_LOOPINVARIANT elif elidable: - # XXX check what to do about exceptions (also MemoryError?) - extraeffect = EffectInfo.EF_ELIDABLE + if self._canraise(op): + extraeffect = EffectInfo.EF_ELIDABLE_CAN_RAISE + else: + extraeffect = EffectInfo.EF_ELIDABLE_CANNOT_RAISE elif self._canraise(op): extraeffect = EffectInfo.EF_CAN_RAISE else: @@ -263,7 +265,7 @@ def calldescr_canraise(self, calldescr): effectinfo = calldescr.get_extra_info() return (effectinfo is None or - effectinfo.extraeffect >= EffectInfo.EF_CAN_RAISE) + effectinfo.extraeffect > EffectInfo.EF_CANNOT_RAISE) def jitdriver_sd_from_portal_graph(self, graph): for jd in self.jitdrivers_sd: diff --git a/pypy/jit/codewriter/effectinfo.py b/pypy/jit/codewriter/effectinfo.py --- a/pypy/jit/codewriter/effectinfo.py +++ b/pypy/jit/codewriter/effectinfo.py @@ -9,10 +9,11 @@ _cache = {} # the 'extraeffect' field is one of the following values: - EF_ELIDABLE = 0 #elidable function (and cannot raise) + EF_ELIDABLE_CANNOT_RAISE = 0 #elidable function (and cannot raise) EF_LOOPINVARIANT = 1 #special: call it only once per loop EF_CANNOT_RAISE = 2 #a function which cannot raise - EF_CAN_RAISE = 3 #normal function (can raise) + EF_ELIDABLE_CAN_RAISE = 3 #elidable function (but can raise) + EF_CAN_RAISE = 4 #normal function (can raise) EF_FORCES_VIRTUAL_OR_VIRTUALIZABLE = 5 #can raise and force virtualizables # the 'oopspecindex' field is one of the following values: @@ -94,7 +95,8 @@ result.readonly_descrs_fields = readonly_descrs_fields result.readonly_descrs_arrays = readonly_descrs_arrays if extraeffect == EffectInfo.EF_LOOPINVARIANT or \ - extraeffect == EffectInfo.EF_ELIDABLE: + extraeffect == EffectInfo.EF_ELIDABLE_CANNOT_RAISE or \ + extraeffect == EffectInfo.EF_ELIDABLE_CAN_RAISE: result.write_descrs_fields = [] result.write_descrs_arrays = [] else: diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py --- a/pypy/jit/codewriter/jtransform.py +++ b/pypy/jit/codewriter/jtransform.py @@ -902,7 +902,7 @@ op1 = self.prepare_builtin_call(op, "llong_%s", args) op2 = self._handle_oopspec_call(op1, args, EffectInfo.OS_LLONG_%s, - EffectInfo.EF_ELIDABLE) + EffectInfo.EF_ELIDABLE_CANNOT_RAISE) if %r == "TO_INT": assert op2.result.concretetype == lltype.Signed return op2 @@ -1363,15 +1363,15 @@ otherindex += EffectInfo._OS_offset_uni self._register_extra_helper(otherindex, othername, argtypes, resulttype, - EffectInfo.EF_ELIDABLE) + EffectInfo.EF_ELIDABLE_CANNOT_RAISE) # return self._handle_oopspec_call(op, args, dict[oopspec_name], - EffectInfo.EF_ELIDABLE) + EffectInfo.EF_ELIDABLE_CANNOT_RAISE) def _handle_str2unicode_call(self, op, oopspec_name, args): - # ll_str2unicode is not EF_ELIDABLE, because it can raise - # UnicodeDecodeError... - return self._handle_oopspec_call(op, args, EffectInfo.OS_STR2UNICODE) + # ll_str2unicode can raise UnicodeDecodeError + return self._handle_oopspec_call(op, args, EffectInfo.OS_STR2UNICODE, + EffectInfo.EF_ELIDABLE_CAN_RAISE) # ---------- # VirtualRefs. @@ -1415,7 +1415,7 @@ def _handle_math_sqrt_call(self, op, oopspec_name, args): return self._handle_oopspec_call(op, args, EffectInfo.OS_MATH_SQRT, - EffectInfo.EF_ELIDABLE) + EffectInfo.EF_ELIDABLE_CANNOT_RAISE) def rewrite_op_jit_force_quasi_immutable(self, op): v_inst, c_fieldname = op.args diff --git a/pypy/jit/codewriter/support.py b/pypy/jit/codewriter/support.py --- a/pypy/jit/codewriter/support.py +++ b/pypy/jit/codewriter/support.py @@ -20,6 +20,7 @@ from pypy.rpython.annlowlevel import MixLevelHelperAnnotator from pypy.jit.metainterp.typesystem import deref from pypy.rlib import rgc +from pypy.rlib.jit import elidable from pypy.rlib.rarithmetic import r_longlong, r_ulonglong, r_uint, intmask def getargtypes(annotator, values): @@ -167,9 +168,14 @@ _ll_5_list_ll_arraycopy = rgc.ll_arraycopy +@elidable def _ll_1_gc_identityhash(x): return lltype.identityhash(x) +# the following function should not be "@elidable": I can think of +# a corner case in which id(const) is constant-folded, and then 'const' +# disappears and is collected too early (possibly causing another object +# with the same id() to appear). def _ll_1_gc_id(ptr): return llop.gc_id(lltype.Signed, ptr) diff --git a/pypy/jit/codewriter/test/test_jtransform.py b/pypy/jit/codewriter/test/test_jtransform.py --- a/pypy/jit/codewriter/test/test_jtransform.py +++ b/pypy/jit/codewriter/test/test_jtransform.py @@ -120,9 +120,9 @@ assert argtypes[0] == [v.concretetype for v in op.args[1:]] assert argtypes[1] == op.result.concretetype if oopspecindex == EI.OS_STR2UNICODE: - assert extraeffect == None # not pure, can raise! + assert extraeffect == EI.EF_ELIDABLE_CAN_RAISE else: - assert extraeffect == EI.EF_ELIDABLE + assert extraeffect == EI.EF_ELIDABLE_CANNOT_RAISE return 'calldescr-%d' % oopspecindex def calldescr_canraise(self, calldescr): return False diff --git a/pypy/jit/metainterp/optimizeopt/__init__.py b/pypy/jit/metainterp/optimizeopt/__init__.py --- a/pypy/jit/metainterp/optimizeopt/__init__.py +++ b/pypy/jit/metainterp/optimizeopt/__init__.py @@ -55,7 +55,7 @@ def optimize_loop_1(metainterp_sd, loop, enable_opts, - inline_short_preamble=True, retraced=False): + inline_short_preamble=True, retraced=False, bridge=False): """Optimize loop.operations to remove internal overheadish operations. """ @@ -64,7 +64,7 @@ if unroll: optimize_unroll(metainterp_sd, loop, optimizations) else: - optimizer = Optimizer(metainterp_sd, loop, optimizations) + optimizer = Optimizer(metainterp_sd, loop, optimizations, bridge) optimizer.propagate_all_forward() def optimize_bridge_1(metainterp_sd, bridge, enable_opts, @@ -76,7 +76,7 @@ except KeyError: pass optimize_loop_1(metainterp_sd, bridge, enable_opts, - inline_short_preamble, retraced) + inline_short_preamble, retraced, bridge=True) if __name__ == '__main__': print ALL_OPTS_NAMES diff --git a/pypy/jit/metainterp/optimizeopt/optimizer.py b/pypy/jit/metainterp/optimizeopt/optimizer.py --- a/pypy/jit/metainterp/optimizeopt/optimizer.py +++ b/pypy/jit/metainterp/optimizeopt/optimizer.py @@ -248,10 +248,11 @@ class Optimizer(Optimization): - def __init__(self, metainterp_sd, loop, optimizations=None): + def __init__(self, metainterp_sd, loop, optimizations=None, bridge=False): self.metainterp_sd = metainterp_sd self.cpu = metainterp_sd.cpu self.loop = loop + self.bridge = bridge self.values = {} self.interned_refs = self.cpu.ts.new_ref_dict() self.resumedata_memo = resume.ResumeDataLoopMemo(metainterp_sd) @@ -407,9 +408,7 @@ return CVAL_ZERO def propagate_all_forward(self): - self.exception_might_have_happened = True - # ^^^ at least at the start of bridges. For loops, we could set - # it to False, but we probably don't care + self.exception_might_have_happened = self.bridge self.newoperations = [] self.first_optimization.propagate_begin_forward() self.i = 0 diff --git a/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py --- a/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py +++ b/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py @@ -693,7 +693,6 @@ """ expected = """ [i] - guard_no_exception() [] i1 = int_add(i, 3) i2 = call(i1, descr=nonwritedescr) guard_no_exception() [i1, i2] diff --git a/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py --- a/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py +++ b/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py @@ -889,12 +889,10 @@ i3 = call(i2, descr=nonwritedescr) jump(i1) # the exception is considered lost when we loop back """ - # note that 'guard_no_exception' at the very start must be kept - # around: bridges may start with one. (In case of loops we could - # remove it, but we probably don't care.) + # note that 'guard_no_exception' at the very start is kept around + # for bridges, but not for loops preamble = """ [i] - guard_no_exception() [] i1 = int_add(i, 3) i2 = call(i1, descr=nonwritedescr) guard_no_exception() [i1, i2] @@ -2993,6 +2991,38 @@ ''' self.optimize_loop(ops, expected, preamble, call_pure_results) + def test_call_pure_constant_folding_exc(self): + # CALL_PURE may be followed by GUARD_NO_EXCEPTION + arg_consts = [ConstInt(i) for i in (123456, 4, 5, 6)] + call_pure_results = {tuple(arg_consts): ConstInt(42)} + ops = ''' + [i0, i1, i2] + escape(i1) + escape(i2) + i3 = call_pure(123456, 4, 5, 6, descr=plaincalldescr) + guard_no_exception() [] + i4 = call_pure(123456, 4, i0, 6, descr=plaincalldescr) + guard_no_exception() [] + jump(i0, i3, i4) + ''' + preamble = ''' + [i0, i1, i2] + escape(i1) + escape(i2) + i4 = call(123456, 4, i0, 6, descr=plaincalldescr) + guard_no_exception() [] + jump(i0, i4) + ''' + expected = ''' + [i0, i2] + escape(42) + escape(i2) + i4 = call(123456, 4, i0, 6, descr=plaincalldescr) + guard_no_exception() [] + jump(i0, i4) + ''' + self.optimize_loop(ops, expected, preamble, call_pure_results) + # ---------- def test_vref_nonvirtual_nonescape(self): diff --git a/pypy/jit/metainterp/optimizeopt/unroll.py b/pypy/jit/metainterp/optimizeopt/unroll.py --- a/pypy/jit/metainterp/optimizeopt/unroll.py +++ b/pypy/jit/metainterp/optimizeopt/unroll.py @@ -543,8 +543,10 @@ elif opnum == rop.CALL: effectinfo = descr.get_extra_info() if effectinfo is not None: - if effectinfo.extraeffect == EffectInfo.EF_LOOPINVARIANT or \ - effectinfo.extraeffect == EffectInfo.EF_ELIDABLE: + ef = effectinfo.extraeffect + if ef == EffectInfo.EF_LOOPINVARIANT or \ + ef == EffectInfo.EF_ELIDABLE_CANNOT_RAISE or \ + ef == EffectInfo.EF_ELIDABLE_CAN_RAISE: return True return False diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py --- a/pypy/jit/metainterp/pyjitpl.py +++ b/pypy/jit/metainterp/pyjitpl.py @@ -1199,7 +1199,7 @@ return self.metainterp.execute_and_record(opnum, descr, *argboxes) @specialize.arg(1) - def execute_varargs(self, opnum, argboxes, descr, exc): + def execute_varargs(self, opnum, argboxes, descr, exc, pure): self.metainterp.clear_exception() resbox = self.metainterp.execute_and_record_varargs(opnum, argboxes, descr=descr) @@ -1207,6 +1207,9 @@ self.make_result_of_lastop(resbox) # ^^^ this is done before handle_possible_exception() because we # need the box to show up in get_list_of_active_boxes() + if pure and self.metainterp.last_exc_value_box is None: + resbox = self.metainterp.record_result_of_call_pure(resbox) + exc = exc and not isinstance(resbox, Const) if exc: self.metainterp.handle_possible_exception() else: @@ -1269,16 +1272,14 @@ return resbox else: effect = effectinfo.extraeffect - if effect == effectinfo.EF_CANNOT_RAISE: - return self.execute_varargs(rop.CALL, allboxes, descr, False) - elif effect == effectinfo.EF_ELIDABLE: - return self.metainterp.record_result_of_call_pure( - self.execute_varargs(rop.CALL, allboxes, descr, False)) - elif effect == effectinfo.EF_LOOPINVARIANT: + if effect == effectinfo.EF_LOOPINVARIANT: return self.execute_varargs(rop.CALL_LOOPINVARIANT, allboxes, - descr, False) - else: - return self.execute_varargs(rop.CALL, allboxes, descr, True) + descr, False, False) + exc = (effect != effectinfo.EF_CANNOT_RAISE and + effect != effectinfo.EF_ELIDABLE_CANNOT_RAISE) + pure = (effect == effectinfo.EF_ELIDABLE_CAN_RAISE or + effect == effectinfo.EF_ELIDABLE_CANNOT_RAISE) + return self.execute_varargs(rop.CALL, allboxes, descr, exc, pure) def do_residual_or_indirect_call(self, funcbox, calldescr, argboxes): """The 'residual_call' operation is emitted in two cases: @@ -1686,8 +1687,12 @@ return if opnum == rop.CALL: effectinfo = descr.get_extra_info() - if effectinfo.extraeffect == effectinfo.EF_ELIDABLE: - return + if effectinfo is not None: + ef = effectinfo.extraeffect + if ef == effectinfo.EF_LOOPINVARIANT or \ + ef == effectinfo.EF_ELIDABLE_CANNOT_RAISE or \ + ef == effectinfo.EF_ELIDABLE_CAN_RAISE: + return if self.heap_cache: self.heap_cache.clear() if self.heap_array_cache: @@ -2375,6 +2380,7 @@ tobox = newbox if change: self.heap_cache[descr] = frombox, tobox + # XXX what about self.heap_array_cache? def find_biggest_function(self): start_stack = [] diff --git a/pypy/jit/metainterp/test/support.py b/pypy/jit/metainterp/test/support.py --- a/pypy/jit/metainterp/test/support.py +++ b/pypy/jit/metainterp/test/support.py @@ -277,3 +277,15 @@ NODE._add_fields({'value': ootype.Signed, 'next': NODE}) return NODE + +# ____________________________________________________________ + +class _Foo: + pass + +def noConst(x): + """Helper function for tests, returning 'x' as a BoxInt/BoxPtr + even if it is a ConstInt/ConstPtr.""" + f1 = _Foo(); f2 = _Foo() + f1.x = x; f2.x = 0 + return f1.x diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -14,7 +14,7 @@ from pypy.rpython.lltypesystem import lltype, llmemory, rffi from pypy.rpython.ootypesystem import ootype from pypy.jit.metainterp.optimizeopt import ALL_OPTS_DICT -from pypy.jit.metainterp.test.support import LLJitMixin, OOJitMixin +from pypy.jit.metainterp.test.support import LLJitMixin, OOJitMixin, noConst class BasicTests: @@ -407,6 +407,58 @@ # the CALL_PURE is constant-folded away by optimizeopt.py self.check_loops(int_sub=1, call=0, call_pure=0, getfield_gc=0) + def test_elidable_raising(self): + myjitdriver = JitDriver(greens = ['m'], reds = ['n']) + @elidable + def externfn(x): + if x <= 0: + raise ValueError + return x - 1 + def f(n, m): + while n > 0: + myjitdriver.can_enter_jit(n=n, m=m) + myjitdriver.jit_merge_point(n=n, m=m) + try: + n -= externfn(m) + except ValueError: + n -= 1 + return n + res = self.meta_interp(f, [22, 6]) + assert res == -3 + # the CALL_PURE is constant-folded away during tracing + self.check_loops(int_sub=1, call=0, call_pure=0) + # + res = self.meta_interp(f, [22, -5]) + assert res == 0 + # raises: becomes CALL and is not constant-folded away + self.check_loops(int_sub=1, call=1, call_pure=0) + + def test_elidable_raising_2(self): + myjitdriver = JitDriver(greens = ['m'], reds = ['n']) + @elidable + def externfn(x): + if x <= 0: + raise ValueError + return x - 1 + def f(n, m): + while n > 0: + myjitdriver.can_enter_jit(n=n, m=m) + myjitdriver.jit_merge_point(n=n, m=m) + try: + n -= externfn(noConst(m)) + except ValueError: + n -= 1 + return n + res = self.meta_interp(f, [22, 6]) + assert res == -3 + # the CALL_PURE is constant-folded away by optimizeopt.py + self.check_loops(int_sub=1, call=0, call_pure=0) + # + res = self.meta_interp(f, [22, -5]) + assert res == 0 + # raises: becomes CALL and is not constant-folded away + self.check_loops(int_sub=1, call=1, call_pure=0) + def test_constant_across_mp(self): myjitdriver = JitDriver(greens = [], reds = ['n']) class X(object): diff --git a/pypy/jit/metainterp/test/test_dict.py b/pypy/jit/metainterp/test/test_dict.py --- a/pypy/jit/metainterp/test/test_dict.py +++ b/pypy/jit/metainterp/test/test_dict.py @@ -157,7 +157,7 @@ # the same arguments are not folded, because we have conflicting # definitions of pure, once strhash can be appropriately folded # this should be decreased to seven. - self.check_loops({"call": 8, "guard_false": 1, "guard_no_exception": 5, + self.check_loops({"call": 8, "guard_false": 1, "guard_no_exception": 6, "guard_true": 1, "int_and": 1, "int_gt": 1, "int_is_true": 1, "int_sub": 1, "jump": 1, "new_with_vtable": 1, "setfield_gc": 1}) diff --git a/pypy/jit/metainterp/test/test_string.py b/pypy/jit/metainterp/test/test_string.py --- a/pypy/jit/metainterp/test/test_string.py +++ b/pypy/jit/metainterp/test/test_string.py @@ -358,3 +358,22 @@ self.check_loops(call=3, # str(), _str(), escape() newunicode=1, unicodegetitem=0, unicodesetitem=1, copyunicodecontent=1) + + def test_str2unicode_fold(self): + _str = self._str + jitdriver = JitDriver(greens = ['g'], reds = ['m']) + @dont_look_inside + def escape(x): + print str(x) + def f(g, m): + g = str(g) + while m >= 0: + jitdriver.can_enter_jit(g=g, m=m) + jitdriver.jit_merge_point(g=g, m=m) + escape(_str(g)) + m -= 1 + return 42 + self.meta_interp(f, [6, 7]) + self.check_loops(call_pure=0, call=1, + newunicode=0, unicodegetitem=0, + unicodesetitem=0, copyunicodecontent=0) diff --git a/pypy/jit/metainterp/warmspot.py b/pypy/jit/metainterp/warmspot.py --- a/pypy/jit/metainterp/warmspot.py +++ b/pypy/jit/metainterp/warmspot.py @@ -409,6 +409,7 @@ jd.warmstate = state def crash_in_jit(e): + tb = not we_are_translated() and sys.exc_info()[2] try: raise e except JitException: @@ -422,8 +423,8 @@ print "~~~ Crash in JIT!" print '~~~ %s: %s' % (e.__class__, e) if sys.stdout == sys.__stdout__: - import pdb; pdb.post_mortem(sys.exc_info()[2]) - raise + import pdb; pdb.post_mortem(tb) + raise e.__class__, e, tb fatalerror('~~~ Crash in JIT! %s' % (e,), traceback=True) crash_in_jit._dont_inline_ = True diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -20,7 +20,8 @@ Most importantly it doesn't mean that an elidable function has no observable side effect, but those side effects are idempotent (ie caching). - For now, such a function should never raise an exception. + The function can raise an exception, in which case this decorator is + ignored. """ func._elidable_function_ = True return func _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit