Author: Carl Friedrich Bolz <cfb...@gmx.de> Branch: value-profiling Changeset: r79949:516d4b0fc7fa Date: 2015-10-03 11:47 +0100 http://bitbucket.org/pypy/pypy/changeset/516d4b0fc7fa/
Log: merge default diff too long, truncating to 2000 out of 2750 lines diff --git a/pypy/module/__builtin__/interp_classobj.py b/pypy/module/__builtin__/interp_classobj.py --- a/pypy/module/__builtin__/interp_classobj.py +++ b/pypy/module/__builtin__/interp_classobj.py @@ -272,11 +272,9 @@ return space.w_NotImplemented return space.call_function(w_meth, w_b) else: - # here, if coerce returns a non-W_Instance object as first - # argument, then give up. The idea is that this strange - # case should already have been handled by the binaryop() - # called from descroperation first. - return space.w_NotImplemented + # fall back to space.xxx() if coerce returns a non-W_Instance + # object as first argument + return getattr(space, objspacename)(w_b, w_a) rbinaryop.func_name = "r" + name return binaryop, rbinaryop @@ -658,7 +656,7 @@ return space.w_NotImplemented return space.call_function(w_func, w_other) else: - return space.w_NotImplemented + return space.pow(w_b, w_a, space.w_None) else: # CPython also doesn't try coercion in this case w_func = self.getattr(space, '__rpow__', False) diff --git a/pypy/module/__builtin__/test/test_classobj.py b/pypy/module/__builtin__/test/test_classobj.py --- a/pypy/module/__builtin__/test/test_classobj.py +++ b/pypy/module/__builtin__/test/test_classobj.py @@ -425,6 +425,14 @@ return 42 assert B() + B() == 42 + def test_coerce_reverse(self): + class CoerceNumber: + def __coerce__(self, other): + assert isinstance(other, int) + return (6, other) + assert 5 + CoerceNumber() == 11 + assert 2 ** CoerceNumber() == 64 + def test_binaryop(self): class A: def __add__(self, other): diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py --- a/pypy/module/micronumpy/support.py +++ b/pypy/module/micronumpy/support.py @@ -39,7 +39,10 @@ def product_check(s): i = 1 for x in s: - i = ovfcheck(i * x) + try: + i = ovfcheck(i * x) + except OverflowError: + raise return i def check_and_adjust_index(space, index, size, axis): diff --git a/pypy/objspace/std/intobject.py b/pypy/objspace/std/intobject.py --- a/pypy/objspace/std/intobject.py +++ b/pypy/objspace/std/intobject.py @@ -249,11 +249,17 @@ ix = 1 while iw > 0: if iw & 1: - ix = ovfcheck(ix * temp) + try: + ix = ovfcheck(ix * temp) + except OverflowError: + raise iw >>= 1 # Shift exponent down by 1 bit if iw == 0: break - temp = ovfcheck(temp * temp) # Square the value of temp + try: + temp = ovfcheck(temp * temp) # Square the value of temp + except OverflowError: + raise if iz: # If we did a multiplication, perform a modulo ix %= iz diff --git a/rpython/jit/backend/arm/regalloc.py b/rpython/jit/backend/arm/regalloc.py --- a/rpython/jit/backend/arm/regalloc.py +++ b/rpython/jit/backend/arm/regalloc.py @@ -671,6 +671,8 @@ a0, a1 = boxes imm_a1 = check_imm_box(a1) l0 = self.make_sure_var_in_reg(a0, boxes) + op.getdescr().make_a_counter_per_value(op, + self.cpu.all_reg_indexes[l0.value]) if not imm_a1: l1 = self.make_sure_var_in_reg(a1, boxes) else: diff --git a/rpython/jit/backend/llgraph/runner.py b/rpython/jit/backend/llgraph/runner.py --- a/rpython/jit/backend/llgraph/runner.py +++ b/rpython/jit/backend/llgraph/runner.py @@ -40,6 +40,10 @@ self.inputargs = map(mapping, inputargs) self.operations = [] for op in operations: + if op.getopnum() == rop.GUARD_VALUE: + # we don't care about the value 13 here, because we gonna + # fish it from the extra slot on frame anyway + op.getdescr().make_a_counter_per_value(op, 13) if op.getdescr() is not None: if op.is_guard() or op.getopnum() == rop.FINISH: newdescr = op.getdescr() @@ -372,6 +376,18 @@ except ExecutionFinished, e: return e.deadframe + def get_value_direct(self, deadframe, tp, index): + v = deadframe._extra_value + if tp == 'i': + assert lltype.typeOf(v) == lltype.Signed + elif tp == 'r': + assert lltype.typeOf(v) == llmemory.GCREF + elif tp == 'f': + assert lltype.typeOf(v) == longlong.FLOATSTORAGE + else: + assert False + return v + def get_int_value(self, deadframe, index): v = deadframe._values[index] assert lltype.typeOf(v) == lltype.Signed @@ -775,11 +791,13 @@ _TYPE = llmemory.GCREF def __init__(self, latest_descr, values, - last_exception=None, saved_data=None): + last_exception=None, saved_data=None, + extra_value=None): self._latest_descr = latest_descr self._values = values self._last_exception = last_exception self._saved_data = saved_data + self._extra_value = extra_value class LLFrame(object): @@ -872,7 +890,7 @@ # ----------------------------------------------------- - def fail_guard(self, descr, saved_data=None): + def fail_guard(self, descr, saved_data=None, extra_value=None): values = [] for box in self.current_op.getfailargs(): if box is not None: @@ -887,7 +905,7 @@ else: raise ExecutionFinished(LLDeadFrame(descr, values, self.last_exception, - saved_data)) + saved_data, extra_value)) def execute_force_spill(self, _, arg): pass @@ -909,7 +927,7 @@ def execute_guard_value(self, descr, arg1, arg2): if arg1 != arg2: - self.fail_guard(descr) + self.fail_guard(descr, extra_value=arg1) def execute_guard_nonnull(self, descr, arg): if not arg: @@ -1028,7 +1046,6 @@ def execute_guard_overflow(self, descr): if not self.overflow_flag: self.fail_guard(descr) - return lltype.nullptr(llmemory.GCREF.TO) # I think it's fine.... def execute_jump(self, descr, *args): raise Jump(descr._llgraph_target, args) diff --git a/rpython/jit/backend/llsupport/llmodel.py b/rpython/jit/backend/llsupport/llmodel.py --- a/rpython/jit/backend/llsupport/llmodel.py +++ b/rpython/jit/backend/llsupport/llmodel.py @@ -389,20 +389,40 @@ descr = self.get_latest_descr(deadframe) return rffi.cast(lltype.Signed, descr.rd_locs[index]) * WORD + @specialize.arg(2) + def get_value_direct(self, deadframe, tp, index): + if tp == 'i': + return self.get_int_value_direct(deadframe, index * WORD) + elif tp == 'r': + return self.get_ref_value_direct(deadframe, index * WORD) + elif tp == 'f': + return self.get_float_value_direct(deadframe, index * WORD) + else: + assert False + def get_int_value(self, deadframe, index): pos = self._decode_pos(deadframe, index) + return self.get_int_value_direct(deadframe, pos) + + def get_int_value_direct(self, deadframe, pos): descr = self.gc_ll_descr.getframedescrs(self).arraydescr ofs = self.unpack_arraydescr(descr) return self.read_int_at_mem(deadframe, pos + ofs, WORD, 1) def get_ref_value(self, deadframe, index): pos = self._decode_pos(deadframe, index) + return self.get_ref_value_direct(deadframe, pos) + + def get_ref_value_direct(self, deadframe, pos): descr = self.gc_ll_descr.getframedescrs(self).arraydescr ofs = self.unpack_arraydescr(descr) return self.read_ref_at_mem(deadframe, pos + ofs) def get_float_value(self, deadframe, index): pos = self._decode_pos(deadframe, index) + return self.get_float_value_direct(deadframe, pos) + + def get_float_value_direct(self, deadframe, pos): descr = self.gc_ll_descr.getframedescrs(self).arraydescr ofs = self.unpack_arraydescr(descr) return self.read_float_at_mem(deadframe, pos + ofs) diff --git a/rpython/jit/backend/test/test_random.py b/rpython/jit/backend/test/test_random.py --- a/rpython/jit/backend/test/test_random.py +++ b/rpython/jit/backend/test/test_random.py @@ -22,6 +22,8 @@ self.operations = subops class FakeMetaInterp(object): + ovf_flag = False + def execute_raised(self, exc, constant=False): self._got_exc = exc @@ -365,9 +367,9 @@ def produce_into(self, builder, r): fail_subset = builder.subset_of_intvars(r) original_intvars = builder.intvars[:] + builder.fakemetainterp.ovf_flag = False super(AbstractOvfOperation, self).produce_into(builder, r) - if builder.fakemetainterp._got_exc: # overflow detected - assert isinstance(builder.fakemetainterp._got_exc, OverflowError) + if builder.fakemetainterp.ovf_flag: # overflow detected op = ResOperation(rop.GUARD_OVERFLOW, []) # the overflowed result should not be used any more, but can # be used on the failure path: recompute fail_subset including diff --git a/rpython/jit/backend/x86/regalloc.py b/rpython/jit/backend/x86/regalloc.py --- a/rpython/jit/backend/x86/regalloc.py +++ b/rpython/jit/backend/x86/regalloc.py @@ -424,6 +424,8 @@ def consider_guard_value(self, op): x = self.make_sure_var_in_reg(op.getarg(0)) + loc = self.assembler.cpu.all_reg_indexes[x.value] + op.getdescr().make_a_counter_per_value(op, loc) y = self.loc(op.getarg(1)) self.perform_guard(op, [x, y], None) diff --git a/rpython/jit/codewriter/codewriter.py b/rpython/jit/codewriter/codewriter.py --- a/rpython/jit/codewriter/codewriter.py +++ b/rpython/jit/codewriter/codewriter.py @@ -48,7 +48,7 @@ # which means mostly producing a linear list of operations and # inserting jumps or conditional jumps. This is a list of tuples # of the shape ("opname", arg1, ..., argN) or (Label(...),). - ssarepr = flatten_graph(graph, regallocs) + ssarepr = flatten_graph(graph, regallocs, cpu=self.callcontrol.cpu) # # step 3b: compute the liveness around certain operations compute_liveness(ssarepr) diff --git a/rpython/jit/codewriter/flatten.py b/rpython/jit/codewriter/flatten.py --- a/rpython/jit/codewriter/flatten.py +++ b/rpython/jit/codewriter/flatten.py @@ -1,4 +1,4 @@ -from rpython.flowspace.model import Variable, Constant +from rpython.flowspace.model import Variable, Constant, c_last_exception from rpython.jit.metainterp.history import AbstractDescr, getkind from rpython.rtyper.lltypesystem import lltype @@ -60,10 +60,11 @@ # ____________________________________________________________ -def flatten_graph(graph, regallocs, _include_all_exc_links=False): +def flatten_graph(graph, regallocs, _include_all_exc_links=False, + cpu=None): """Flatten the graph into an SSARepr, with already-computed register allocations. 'regallocs' in a dict {kind: RegAlloc}.""" - flattener = GraphFlattener(graph, regallocs, _include_all_exc_links) + flattener = GraphFlattener(graph, regallocs, _include_all_exc_links, cpu) flattener.enforce_input_args() flattener.generate_ssa_form() return flattener.ssarepr @@ -71,9 +72,11 @@ class GraphFlattener(object): - def __init__(self, graph, regallocs, _include_all_exc_links=False): + def __init__(self, graph, regallocs, _include_all_exc_links=False, + cpu=None): self.graph = graph self.regallocs = regallocs + self.cpu = cpu self._include_all_exc_links = _include_all_exc_links self.registers = {} if graph: @@ -100,7 +103,7 @@ self.seen_blocks = {} self.make_bytecode_block(self.graph.startblock) - def make_bytecode_block(self, block): + def make_bytecode_block(self, block, handling_ovf=False): if block.exits == (): self.make_return(block.inputargs) return @@ -114,9 +117,15 @@ # operations = block.operations for i, op in enumerate(operations): + if '_ovf' in op.opname: + if (len(block.exits) not in (2, 3) or + block.exitswitch is not c_last_exception): + raise Exception("detected a block containing ovfcheck()" + " but no OverflowError is caught, this" + " is not legal in jitted blocks") self.serialize_op(op) # - self.insert_exits(block) + self.insert_exits(block, handling_ovf) def make_return(self, args): if len(args) == 1: @@ -136,16 +145,16 @@ raise Exception("?") self.emitline("---") - def make_link(self, link): + def make_link(self, link, handling_ovf): if (link.target.exits == () and link.last_exception not in link.args and link.last_exc_value not in link.args): self.make_return(link.args) # optimization only return self.insert_renamings(link) - self.make_bytecode_block(link.target) + self.make_bytecode_block(link.target, handling_ovf) - def make_exception_link(self, link): + def make_exception_link(self, link, handling_ovf): # Like make_link(), but also introduces the 'last_exception' and # 'last_exc_value' as variables if needed. Also check if the link # is jumping directly to the re-raising exception block. @@ -153,54 +162,74 @@ assert link.last_exc_value is not None if link.target.operations == () and link.args == [link.last_exception, link.last_exc_value]: - self.emitline("reraise") + if handling_ovf: + exc_data = self.cpu.rtyper.exceptiondata + ll_ovf = exc_data.get_standard_ll_exc_instance_by_class( + OverflowError) + c = Constant(ll_ovf, concretetype=lltype.typeOf(ll_ovf)) + self.emitline("raise", c) + else: + self.emitline("reraise") self.emitline("---") return # done - self.make_link(link) + self.make_link(link, handling_ovf) - def insert_exits(self, block): + def insert_exits(self, block, handling_ovf=False): if len(block.exits) == 1: # A single link, fall-through link = block.exits[0] assert link.exitcase in (None, False, True) # the cases False or True should not really occur, but can show # up in the manually hacked graphs for generators... - self.make_link(link) + self.make_link(link, handling_ovf) # elif block.canraise: # An exception block. See test_exc_exitswitch in test_flatten.py # for an example of what kind of code this makes. index = -1 - while True: - lastopname = block.operations[index].opname - if lastopname != '-live-': - break - index -= 1 + opname = block.operations[index].opname + if '_ovf' in opname: + # ovf checking operation as a lat thing, -live- should be + # one before it + line = self.popline() + self.emitline(opname[:7] + '_jump_if_ovf', + TLabel(block.exits[1]), *line[1:]) + assert len(block.exits) in (2, 3) + self.make_link(block.exits[0], False) + self.emitline(Label(block.exits[1])) + self.make_exception_link(block.exits[1], True) + if len(block.exits) == 3: + assert block.exits[2].exitcase is Exception + self.make_exception_link(block.exits[2], False) + return + else: + while True: + lastopname = block.operations[index].opname + if lastopname != '-live-': + break + index -= 1 assert block.exits[0].exitcase is None # is this always True? # if not self._include_all_exc_links: if index == -1: # cannot raise: the last instruction is not # actually a '-live-' - self.make_link(block.exits[0]) + self.make_link(block.exits[0], False) return # self.emitline('catch_exception', TLabel(block.exits[0])) - self.make_link(block.exits[0]) + self.make_link(block.exits[0], False) self.emitline(Label(block.exits[0])) for link in block.exits[1:]: - if (link.exitcase is Exception or - (link.exitcase is OverflowError and - lastopname.startswith('int_') and - lastopname.endswith('_ovf'))): + if link.exitcase is Exception: # this link captures all exceptions - self.make_exception_link(link) + self.make_exception_link(link, False) break self.emitline('goto_if_exception_mismatch', Constant(link.llexitcase, lltype.typeOf(link.llexitcase)), TLabel(link)) - self.make_exception_link(link) + self.make_exception_link(link, False) self.emitline(Label(link)) else: # no link captures all exceptions, so we have to put a reraise @@ -216,29 +245,26 @@ if linkfalse.llexitcase == True: linkfalse, linktrue = linktrue, linkfalse opname = 'goto_if_not' - livebefore = False if isinstance(block.exitswitch, tuple): # special case produced by jtransform.optimize_goto_if_not() opname = 'goto_if_not_' + block.exitswitch[0] opargs = block.exitswitch[1:] if opargs[-1] == '-live-before': - livebefore = True opargs = opargs[:-1] else: assert block.exitswitch.concretetype == lltype.Bool opargs = [block.exitswitch] # lst = self.flatten_list(opargs) + [TLabel(linkfalse)] - if livebefore: - self.emitline('-live-') + self.emitline('-live-') self.emitline(opname, *lst) - if not livebefore: - self.emitline('-live-', TLabel(linkfalse)) + #if not livebefore: + # self.emitline('-live-', TLabel(linkfalse)) # true path: - self.make_link(linktrue) + self.make_link(linktrue, handling_ovf) # false path: self.emitline(Label(linkfalse)) - self.make_link(linkfalse) + self.make_link(linkfalse, handling_ovf) # else: # A switch. @@ -261,7 +287,7 @@ switchdict) # emit the default path if block.exits[-1].exitcase == 'default': - self.make_link(block.exits[-1]) + self.make_link(block.exits[-1], handling_ovf) else: self.emitline("unreachable") self.emitline("---") @@ -275,7 +301,7 @@ # if the switched value doesn't match any case. self.emitline(Label(switch)) self.emitline('-live-') - self.make_link(switch) + self.make_link(switch, handling_ovf) def insert_renamings(self, link): renamings = {} @@ -323,6 +349,9 @@ def emitline(self, *line): self.ssarepr.insns.append(line) + def popline(self): + return self.ssarepr.insns.pop() + def flatten_list(self, arglist): args = [] for v in arglist: diff --git a/rpython/jit/codewriter/jtransform.py b/rpython/jit/codewriter/jtransform.py --- a/rpython/jit/codewriter/jtransform.py +++ b/rpython/jit/codewriter/jtransform.py @@ -8,7 +8,8 @@ from rpython.jit.metainterp.history import getkind from rpython.jit.metainterp.typesystem import deref, arrayItem from rpython.jit.metainterp.blackhole import BlackholeInterpreter -from rpython.flowspace.model import SpaceOperation, Variable, Constant +from rpython.flowspace.model import SpaceOperation, Variable, Constant,\ + c_last_exception from rpython.rlib import objectmodel from rpython.rlib.jit import _we_are_jitted from rpython.rlib.rgc import lltype_is_gc @@ -211,8 +212,8 @@ # ok! optimize this case block.operations.remove(op) block.exitswitch = (op.opname,) + tuple(op.args) - if op.opname in ('ptr_iszero', 'ptr_nonzero'): - block.exitswitch += ('-live-before',) + #if op.opname in ('ptr_iszero', 'ptr_nonzero'): + block.exitswitch += ('-live-before',) # if the variable escape to the next block along a link, # replace it with a constant, because we know its value for link in block.exits: @@ -333,13 +334,13 @@ def rewrite_op_int_add_ovf(self, op): op0 = self._rewrite_symmetric(op) op1 = SpaceOperation('-live-', [], None) - return [op0, op1] + return [op1, op0] rewrite_op_int_mul_ovf = rewrite_op_int_add_ovf def rewrite_op_int_sub_ovf(self, op): op1 = SpaceOperation('-live-', [], None) - return [op, op1] + return [op1, op] def _noop_rewrite(self, op): return op diff --git a/rpython/jit/codewriter/test/test_codewriter.py b/rpython/jit/codewriter/test/test_codewriter.py --- a/rpython/jit/codewriter/test/test_codewriter.py +++ b/rpython/jit/codewriter/test/test_codewriter.py @@ -76,11 +76,11 @@ assert jitcode.num_regs_i() == 2 assert jitcode.num_regs_r() == 0 assert jitcode.num_regs_f() == 0 - assert jitcode._live_vars(5) == '%i0 %i1' + assert jitcode._live_vars(0) == '%i0 %i1' # from rpython.jit.codewriter.jitcode import MissingLiveness for i in range(len(jitcode.code)+1): - if i != 5: + if i != 0: py.test.raises(MissingLiveness, jitcode._live_vars, i) def test_call(): diff --git a/rpython/jit/codewriter/test/test_flatten.py b/rpython/jit/codewriter/test/test_flatten.py --- a/rpython/jit/codewriter/test/test_flatten.py +++ b/rpython/jit/codewriter/test/test_flatten.py @@ -140,6 +140,7 @@ def encoding_test(self, func, args, expected, transform=False, liveness=False, cc=None, jd=None): + graphs = self.make_graphs(func, args) #graphs[0].show() if transform: @@ -147,7 +148,8 @@ cc = cc or FakeCallControl() transform_graph(graphs[0], FakeCPU(self.rtyper), cc, jd) ssarepr = flatten_graph(graphs[0], fake_regallocs(), - _include_all_exc_links=not transform) + _include_all_exc_links=not transform, + cpu=FakeCPU(self.rtyper)) if liveness: from rpython.jit.codewriter.liveness import compute_liveness compute_liveness(ssarepr) @@ -169,8 +171,8 @@ return n + 1 self.encoding_test(f, [10], """ int_gt %i0, $0 -> %i1 + -live- goto_if_not %i1, L1 - -live- L1 int_copy %i0 -> %i2 int_sub %i2, $3 -> %i3 int_copy %i3 -> %i4 @@ -194,8 +196,8 @@ int_copy %i1 -> %i3 L1: int_gt %i2, $0 -> %i4 + -live- goto_if_not %i4, L2 - -live- L2 int_copy %i2 -> %i5 int_copy %i3 -> %i6 int_add %i6, %i5 -> %i7 @@ -218,8 +220,8 @@ int_copy %i0 -> %i2 int_copy %i1 -> %i3 L1: + -live- goto_if_not_int_gt %i2, $0, L2 - -live- L2 int_copy %i2 -> %i4 int_copy %i3 -> %i5 int_add %i5, %i4 -> %i6 @@ -457,8 +459,8 @@ # note that 'goto_if_not_int_is_true' is not the same thing # as just 'goto_if_not', because the last one expects a boolean self.encoding_test(f, [7], """ + -live- goto_if_not_int_is_true %i0, L1 - -live- L1 int_return $False --- L1: @@ -523,8 +525,8 @@ else: return m2 self.encoding_test(f, [4, 5, 6], """ + -live- %i0, %i1, %i2 goto_if_not_int_is_true %i0, L1 - -live- %i1, %i2, L1 int_return %i1 --- L1: @@ -538,15 +540,59 @@ except OverflowError: return 42 self.encoding_test(f, [7, 2], """ - int_add_ovf %i0, %i1 -> %i2 - -live- %i2 - catch_exception L1 + -live- %i0, %i1 + int_add_jump_if_ovf L1, %i0, %i1 -> %i2 int_return %i2 --- L1: int_return $42 """, transform=True, liveness=True) + def test_multiple_int_add_ovf(self): + def f(i, j): + try: + ovfcheck(j + i) + return ovfcheck(i + j) + except OverflowError: + return 42 + self.encoding_test(f, [7, 2], """ + -live- %i0, %i1 + int_add_jump_if_ovf L1, %i1, %i0 -> %i2 + int_copy %i1 -> %i3 + int_copy %i0 -> %i4 + -live- %i3, %i4 + int_add_jump_if_ovf L2, %i4, %i3 -> %i5 + int_return %i5 + --- + L2: + int_return $42 + --- + L1: + int_return $42 + """, transform=True, liveness=True) + + def test_ovfcheck_no_catch(self): + def f(i, j): + return ovfcheck(i + j) + err = py.test.raises(Exception, "self.encoding_test(f, [7, 2], ''," + "transform=True, liveness=True)") + assert "ovfcheck()" in str(err) + + def test_ovfcheck_reraise(self): + def f(i, j): + try: + ovfcheck(j + i) + except OverflowError: + raise + self.encoding_test(f, [7, 2], """ + -live- %i0, %i1 + int_add_jump_if_ovf L1, %i1, %i0 -> %i2 + void_return + --- + L1: + raise $<* struct object> + """, transform=True, liveness=True) + def test_residual_call_raising(self): @dont_look_inside def g(i, j): diff --git a/rpython/jit/codewriter/test/test_jtransform.py b/rpython/jit/codewriter/test/test_jtransform.py --- a/rpython/jit/codewriter/test/test_jtransform.py +++ b/rpython/jit/codewriter/test/test_jtransform.py @@ -15,7 +15,7 @@ for prod in result: yield tuple(prod) -from rpython.flowspace.model import FunctionGraph, Block, Link +from rpython.flowspace.model import FunctionGraph, Block, Link, c_last_exception from rpython.flowspace.model import SpaceOperation, Variable, Constant from rpython.rtyper.lltypesystem import lltype, llmemory, rstr, rffi from rpython.rtyper import rclass @@ -187,7 +187,7 @@ res = Transformer().optimize_goto_if_not(block) assert res == True assert block.operations == [sp1, sp2] - assert block.exitswitch == ('int_gt', v1, v2) + assert block.exitswitch == ('int_gt', v1, v2, '-live-before') assert block.exits == exits def test_optimize_goto_if_not__incoming(): @@ -211,7 +211,7 @@ res = Transformer().optimize_goto_if_not(block) assert res == True assert block.operations == [] - assert block.exitswitch == ('int_gt', v1, v2) + assert block.exitswitch == ('int_gt', v1, v2, '-live-before') assert block.exits == exits assert exits[1].args == [const(True)] @@ -235,7 +235,7 @@ res = Transformer().optimize_goto_if_not(block) assert res == True assert block.operations == [] - assert block.exitswitch == (opname, v1, v2) + assert block.exitswitch == (opname, v1, v2, '-live-before') assert block.exits == exits def test_optimize_goto_if_not__ptr_iszero(): @@ -287,7 +287,7 @@ for v2 in [varoftype(lltype.Signed), const(43)]: op = SpaceOperation('int_add_nonneg_ovf', [v1, v2], v3) oplist = Transformer(FakeCPU()).rewrite_operation(op) - op0, op1 = oplist + op1, op0 = oplist assert op0.opname == 'int_add_ovf' if isinstance(v1, Constant) and isinstance(v2, Variable): assert op0.args == [v2, v1] diff --git a/rpython/jit/codewriter/test/test_regalloc.py b/rpython/jit/codewriter/test/test_regalloc.py --- a/rpython/jit/codewriter/test/test_regalloc.py +++ b/rpython/jit/codewriter/test/test_regalloc.py @@ -63,8 +63,8 @@ self.check_assembler(graph, """ L1: int_gt %i0, $0 -> %i2 + -live- goto_if_not %i2, L2 - -live- L2 int_add %i1, %i0 -> %i1 int_sub %i0, $1 -> %i0 goto L1 @@ -82,8 +82,8 @@ self.check_assembler(graph, """ L1: int_gt %i0, $0 -> %i2 + -live- goto_if_not %i2, L2 - -live- L2 int_push %i1 int_copy %i0 -> %i1 int_pop -> %i0 @@ -102,8 +102,8 @@ self.check_assembler(graph, """ L1: int_gt %i0, $0 -> %i0 + -live- goto_if_not %i0, L2 - -live- L2 int_copy %i1 -> %i0 int_copy $2 -> %i1 goto L1 @@ -121,8 +121,8 @@ self.check_assembler(graph, """ L1: int_gt %i0, $0 -> %i3 + -live- goto_if_not %i3, L2 - -live- L2 int_push %i1 int_copy %i2 -> %i1 int_copy %i0 -> %i2 @@ -142,8 +142,8 @@ self.check_assembler(graph, """ L1: int_gt %i0, $0 -> %i3 + -live- goto_if_not %i3, L2 - -live- L2 int_copy %i2 -> %i1 goto L1 --- @@ -236,8 +236,8 @@ self.check_assembler(graph, """ int_lshift %i0, %i1 -> %i2 int_rshift %i2, %i1 -> %i1 + -live- goto_if_not_int_ne %i1, %i0, L1 - -live- L1 raise $<* struct object> --- L1: diff --git a/rpython/jit/metainterp/blackhole.py b/rpython/jit/metainterp/blackhole.py --- a/rpython/jit/metainterp/blackhole.py +++ b/rpython/jit/metainterp/blackhole.py @@ -212,6 +212,20 @@ assert lltype.typeOf(result) is longlong.FLOATSTORAGE self.registers_f[ord(code[position])] = result position += 1 + elif resulttype == "iL": + result, new_position = result + if new_position != -1: + position = new_position + next_argcode = next_argcode + 2 + else: + assert argcodes[next_argcode] == '>' + assert argcodes[next_argcode + 1] == 'i' + next_argcode = next_argcode + 2 + if lltype.typeOf(result) is lltype.Bool: + result = int(result) + assert lltype.typeOf(result) is lltype.Signed + self.registers_i[ord(code[position])] = result + position += 1 elif resulttype == 'L': assert result >= 0 position = result @@ -394,17 +408,26 @@ def bhimpl_int_mul(a, b): return intmask(a * b) - @arguments("i", "i", returns="i") - def bhimpl_int_add_ovf(a, b): - return ovfcheck(a + b) + @arguments("L", "i", "i", returns="iL") + def bhimpl_int_add_jump_if_ovf(label, a, b): + try: + return ovfcheck(a + b), -1 + except OverflowError: + return 0, label - @arguments("i", "i", returns="i") - def bhimpl_int_sub_ovf(a, b): - return ovfcheck(a - b) + @arguments("L", "i", "i", returns="iL") + def bhimpl_int_sub_jump_if_ovf(label, a, b): + try: + return ovfcheck(a - b), -1 + except OverflowError: + return 0, label - @arguments("i", "i", returns="i") - def bhimpl_int_mul_ovf(a, b): - return ovfcheck(a * b) + @arguments("L", "i", "i", returns="iL") + def bhimpl_int_mul_jump_if_ovf(label, a, b): + try: + return ovfcheck(a * b), -1 + except OverflowError: + return 0, label @arguments("i", "i", returns="i") def bhimpl_int_floordiv(a, b): @@ -1465,57 +1488,9 @@ assert kind == 'v' return lltype.nullptr(rclass.OBJECTPTR.TO) - def _prepare_resume_from_failure(self, opnum, deadframe): - from rpython.jit.metainterp.resoperation import rop - # - if opnum == rop.GUARD_FUTURE_CONDITION: - pass - elif opnum == rop.GUARD_TRUE: - # Produced directly by some goto_if_not_xxx() opcode that did not - # jump, but which must now jump. The pc is just after the opcode. - self.position = self.jitcode.follow_jump(self.position) - # - elif opnum == rop.GUARD_FALSE: - # Produced directly by some goto_if_not_xxx() opcode that jumped, - # but which must no longer jump. The pc is just after the opcode. - pass - # - elif opnum == rop.GUARD_VALUE or opnum == rop.GUARD_CLASS: - # Produced by guard_class(), xxx_guard_value(), or a few other - # opcodes like switch(). The pc is at the start of the opcode - # (so it will be redone). - pass - # - elif (opnum == rop.GUARD_NONNULL or - opnum == rop.GUARD_ISNULL or - opnum == rop.GUARD_NONNULL_CLASS): - # Produced by goto_if_not_ptr_{non,is}zero(). The pc is at the - # start of the opcode (so it will be redone); this is needed - # because of GUARD_NONNULL_CLASS. - pass - # - elif (opnum == rop.GUARD_NO_EXCEPTION or - opnum == rop.GUARD_EXCEPTION or - opnum == rop.GUARD_NOT_FORCED): - return lltype.cast_opaque_ptr(rclass.OBJECTPTR, - self.cpu.grab_exc_value(deadframe)) - # - elif opnum == rop.GUARD_NO_OVERFLOW: - # Produced by int_xxx_ovf(). The pc is just after the opcode. - # We get here because it did not used to overflow, but now it does. - return get_llexception(self.cpu, OverflowError()) - # - elif opnum == rop.GUARD_OVERFLOW: - # Produced by int_xxx_ovf(). The pc is just after the opcode. - # We get here because it used to overflow, but now it no longer - # does. - pass - elif opnum == rop.GUARD_NOT_INVALIDATED: - pass - else: - from rpython.jit.metainterp.resoperation import opname - raise NotImplementedError(opname[opnum]) - return lltype.nullptr(rclass.OBJECTPTR.TO) + def _prepare_resume_from_failure(self, deadframe): + return lltype.cast_opaque_ptr(rclass.OBJECTPTR, + self.cpu.grab_exc_value(deadframe)) # connect the return of values from the called frame to the # 'xxx_call_yyy' instructions from the caller frame @@ -1641,8 +1616,7 @@ deadframe, all_virtuals) - current_exc = blackholeinterp._prepare_resume_from_failure( - resumedescr.guard_opnum, deadframe) + current_exc = blackholeinterp._prepare_resume_from_failure(deadframe) _run_forever(blackholeinterp, current_exc) resume_in_blackhole._dont_inline_ = True diff --git a/rpython/jit/metainterp/compile.py b/rpython/jit/metainterp/compile.py --- a/rpython/jit/metainterp/compile.py +++ b/rpython/jit/metainterp/compile.py @@ -767,12 +767,15 @@ # fetch the actual value of the guard_value, possibly turning # it to an integer if typetag == self.TY_INT: - intval = metainterp_sd.cpu.get_int_value(deadframe, index) + intval = metainterp_sd.cpu.get_value_direct(deadframe, 'i', + index) elif typetag == self.TY_REF: - refval = metainterp_sd.cpu.get_ref_value(deadframe, index) + refval = metainterp_sd.cpu.get_value_direct(deadframe, 'r', + index) intval = lltype.cast_ptr_to_int(refval) elif typetag == self.TY_FLOAT: - floatval = metainterp_sd.cpu.get_float_value(deadframe, index) + floatval = metainterp_sd.cpu.get_value_direct(deadframe, 'f', + index) intval = longlong.gethash_fast(floatval) else: assert 0, typetag @@ -788,11 +791,6 @@ increment = jitdriver_sd.warmstate.increment_trace_eagerness return jitcounter.tick(hash, increment) - def get_index_of_guard_value(self): - if (self.status & self.ST_TYPE_MASK) == 0: - return -1 - return intmask(self.status >> self.ST_SHIFT) - def start_compiling(self): # start tracing and compiling from this guard. self.status |= self.ST_BUSY_FLAG @@ -819,62 +817,24 @@ new_loop.original_jitcell_token, metainterp.box_names_memo) - def make_a_counter_per_value(self, guard_value_op): + def make_a_counter_per_value(self, guard_value_op, index): assert guard_value_op.getopnum() == rop.GUARD_VALUE box = guard_value_op.getarg(0) - try: - i = guard_value_op.getfailargs().index(box) - except ValueError: - return # xxx probably very rare + if box.type == history.INT: + ty = self.TY_INT + elif box.type == history.REF: + ty = self.TY_REF + elif box.type == history.FLOAT: + ty = self.TY_FLOAT else: - if box.type == history.INT: - ty = self.TY_INT - elif box.type == history.REF: - ty = self.TY_REF - elif box.type == history.FLOAT: - ty = self.TY_FLOAT - else: - assert 0, box.type - self.status = ty | (r_uint(i) << self.ST_SHIFT) + assert 0, box.type + self.status = ty | (r_uint(index) << self.ST_SHIFT) -class ResumeGuardNonnullDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_NONNULL - -class ResumeGuardIsnullDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_ISNULL - -class ResumeGuardClassDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_CLASS - -class ResumeGuardTrueDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_TRUE - -class ResumeGuardFalseDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_FALSE - -class ResumeGuardNonnullClassDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_NONNULL_CLASS - -class ResumeGuardExceptionDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_EXCEPTION - -class ResumeGuardNoExceptionDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_NO_EXCEPTION - -class ResumeGuardOverflowDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_OVERFLOW - -class ResumeGuardNoOverflowDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_NO_OVERFLOW - -class ResumeGuardValueDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_VALUE - -class ResumeGuardNotInvalidated(ResumeGuardDescr): - guard_opnum = rop.GUARD_NOT_INVALIDATED +class ResumeGuardExcDescr(ResumeGuardDescr): + pass class ResumeAtPositionDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_FUTURE_CONDITION + pass class AllVirtuals: llopaque = True @@ -895,8 +855,6 @@ class ResumeGuardForcedDescr(ResumeGuardDescr): - guard_opnum = rop.GUARD_NOT_FORCED - def _init(self, metainterp_sd, jitdriver_sd): # to please the annotator self.metainterp_sd = metainterp_sd @@ -959,37 +917,13 @@ if opnum == rop.GUARD_NOT_FORCED or opnum == rop.GUARD_NOT_FORCED_2: resumedescr = ResumeGuardForcedDescr() resumedescr._init(optimizer.metainterp_sd, optimizer.jitdriver_sd) - elif opnum == rop.GUARD_NOT_INVALIDATED: - resumedescr = ResumeGuardNotInvalidated() - elif opnum == rop.GUARD_FUTURE_CONDITION: - resumedescr = ResumeAtPositionDescr() - elif opnum == rop.GUARD_VALUE: - resumedescr = ResumeGuardValueDescr() - elif opnum == rop.GUARD_NONNULL: - resumedescr = ResumeGuardNonnullDescr() - elif opnum == rop.GUARD_ISNULL: - resumedescr = ResumeGuardIsnullDescr() - elif opnum == rop.GUARD_NONNULL_CLASS: - resumedescr = ResumeGuardNonnullClassDescr() - elif opnum == rop.GUARD_CLASS: - resumedescr = ResumeGuardClassDescr() - elif opnum == rop.GUARD_TRUE: - resumedescr = ResumeGuardTrueDescr() - elif opnum == rop.GUARD_FALSE: - resumedescr = ResumeGuardFalseDescr() - elif opnum == rop.GUARD_EXCEPTION: - resumedescr = ResumeGuardExceptionDescr() - elif opnum == rop.GUARD_NO_EXCEPTION: - resumedescr = ResumeGuardNoExceptionDescr() - elif opnum == rop.GUARD_OVERFLOW: - resumedescr = ResumeGuardOverflowDescr() - elif opnum == rop.GUARD_NO_OVERFLOW: - resumedescr = ResumeGuardNoOverflowDescr() elif opnum in (rop.GUARD_IS_OBJECT, rop.GUARD_SUBCLASS, rop.GUARD_GC_TYPE): # note - this only happens in tests resumedescr = ResumeAtPositionDescr() + elif opnum in (rop.GUARD_EXCEPTION, rop.GUARD_NO_EXCEPTION): + resumedescr = ResumeGuardExcDescr() else: - assert False + resumedescr = ResumeGuardDescr() return resumedescr class ResumeFromInterpDescr(ResumeDescr): diff --git a/rpython/jit/metainterp/executor.py b/rpython/jit/metainterp/executor.py --- a/rpython/jit/metainterp/executor.py +++ b/rpython/jit/metainterp/executor.py @@ -253,7 +253,7 @@ z = ovfcheck(a + b) except OverflowError: assert metainterp is not None - metainterp.execute_raised(OverflowError(), constant=True) + metainterp.ovf_flag = True z = 0 return z @@ -264,7 +264,7 @@ z = ovfcheck(a - b) except OverflowError: assert metainterp is not None - metainterp.execute_raised(OverflowError(), constant=True) + metainterp.ovf_flag = True z = 0 return z @@ -275,7 +275,7 @@ z = ovfcheck(a * b) except OverflowError: assert metainterp is not None - metainterp.execute_raised(OverflowError(), constant=True) + metainterp.ovf_flag = True z = 0 return z diff --git a/rpython/jit/metainterp/history.py b/rpython/jit/metainterp/history.py --- a/rpython/jit/metainterp/history.py +++ b/rpython/jit/metainterp/history.py @@ -157,6 +157,9 @@ def __init__(self, identifier=None): self.identifier = identifier # for testing + def make_a_counter_per_value(self, op, index): + pass # for testing + @specialize.argtype(0) def newconst(value): @@ -540,6 +543,9 @@ def check_consistency_of_branch(operations, seen, check_descr=True): "NOT_RPYTHON" for num, op in enumerate(operations): + if op.is_ovf(): + assert operations[num + 1].getopnum() in (rop.GUARD_NO_OVERFLOW, + rop.GUARD_OVERFLOW) for i in range(op.numargs()): box = op.getarg(i) if not isinstance(box, Const): @@ -750,7 +756,6 @@ return tokens def check_history(self, expected=None, **check): - return insns = {} for op in self.operations: opname = op.getopname() diff --git a/rpython/jit/metainterp/jitprof.py b/rpython/jit/metainterp/jitprof.py --- a/rpython/jit/metainterp/jitprof.py +++ b/rpython/jit/metainterp/jitprof.py @@ -143,6 +143,7 @@ self._print_intline("guards", cnt[Counters.GUARDS]) self._print_intline("opt ops", cnt[Counters.OPT_OPS]) self._print_intline("opt guards", cnt[Counters.OPT_GUARDS]) + self._print_intline("opt guards shared", cnt[Counters.OPT_GUARDS_SHARED]) self._print_intline("forcings", cnt[Counters.OPT_FORCINGS]) self._print_intline("abort: trace too long", cnt[Counters.ABORT_TOO_LONG]) diff --git a/rpython/jit/metainterp/optimizeopt/heap.py b/rpython/jit/metainterp/optimizeopt/heap.py --- a/rpython/jit/metainterp/optimizeopt/heap.py +++ b/rpython/jit/metainterp/optimizeopt/heap.py @@ -11,7 +11,7 @@ from rpython.jit.metainterp.optimizeopt.shortpreamble import PreambleOp from rpython.jit.metainterp.optimize import InvalidLoop from rpython.jit.metainterp.resoperation import rop, ResOperation, OpHelpers,\ - AbstractResOp + AbstractResOp, GuardResOp from rpython.rlib.objectmodel import we_are_translated from rpython.jit.metainterp.optimizeopt import info @@ -288,7 +288,7 @@ cf = submap[index] = ArrayCachedField(index) return cf - def emit_operation(self, op): + def emit_operation(self, op): self.emitting_operation(op) self.emit_postponed_op() if (op.is_comparison() or op.is_call_may_force() diff --git a/rpython/jit/metainterp/optimizeopt/info.py b/rpython/jit/metainterp/optimizeopt/info.py --- a/rpython/jit/metainterp/optimizeopt/info.py +++ b/rpython/jit/metainterp/optimizeopt/info.py @@ -104,6 +104,11 @@ self.last_guard_pos = -1 def mark_last_guard(self, optimizer): + if (optimizer.getlastop() is None or + not optimizer.getlastop().is_guard()): + # there can be a really emitted operation that's not a guard + # e.g. a setfield, ignore those + return self.last_guard_pos = len(optimizer._newoperations) - 1 assert self.get_last_guard(optimizer).is_guard() diff --git a/rpython/jit/metainterp/optimizeopt/optimizer.py b/rpython/jit/metainterp/optimizeopt/optimizer.py --- a/rpython/jit/metainterp/optimizeopt/optimizer.py +++ b/rpython/jit/metainterp/optimizeopt/optimizer.py @@ -1,7 +1,6 @@ from rpython.jit.metainterp import jitprof, resume, compile from rpython.jit.metainterp.executor import execute_nonspec_const -from rpython.jit.metainterp.logger import LogOperations -from rpython.jit.metainterp.history import Const, ConstInt, REF, ConstPtr +from rpython.jit.metainterp.history import Const, ConstInt, ConstPtr from rpython.jit.metainterp.optimizeopt.intutils import IntBound,\ ConstIntBound, MININT, MAXINT, IntUnbounded from rpython.jit.metainterp.optimizeopt.util import make_dispatcher_method @@ -10,6 +9,7 @@ from rpython.jit.metainterp.optimizeopt import info from rpython.jit.metainterp.typesystem import llhelper from rpython.rlib.objectmodel import specialize, we_are_translated +from rpython.rlib.debug import debug_print @@ -260,6 +260,8 @@ self.optearlyforce = None self.optunroll = None + self._last_guard_op = None + self.set_optimizations(optimizations) self.setup() @@ -526,6 +528,7 @@ if extra_jump: self.first_optimization.propagate_forward(ops[-1]) self.resumedata_memo.update_counters(self.metainterp_sd.profiler) + return (BasicLoopInfo(newargs, self.quasi_immutable_deps), self._newoperations) @@ -566,6 +569,7 @@ op.setarg(i, arg) self.metainterp_sd.profiler.count(jitprof.Counters.OPT_OPS) if op.is_guard(): + assert isinstance(op, GuardResOp) self.metainterp_sd.profiler.count(jitprof.Counters.OPT_GUARDS) pendingfields = self.pendingfields self.pendingfields = None @@ -574,20 +578,85 @@ del self.replaces_guard[orig_op] return else: - guard_op = self.replace_op_with(op, op.getopnum()) - op = self.store_final_boxes_in_guard(guard_op, pendingfields) - # for unrolling - for farg in op.getfailargs(): - if farg: - self.force_box(farg) + op = self.emit_guard_operation(op, pendingfields) elif op.can_raise(): self.exception_might_have_happened = True + if ((op.has_no_side_effect() or op.is_guard() or op.is_jit_debug() or + op.is_ovf()) and not self.is_call_pure_pure_canraise(op)): + pass + else: + self._last_guard_op = None self._really_emitted_operation = op self._newoperations.append(op) + def emit_guard_operation(self, op, pendingfields): + guard_op = self.replace_op_with(op, op.getopnum()) + opnum = guard_op.getopnum() + if (self._last_guard_op and guard_op.getdescr() is None): + self.metainterp_sd.profiler.count_ops(opnum, + jitprof.Counters.OPT_GUARDS_SHARED) + op = self._copy_resume_data_from(guard_op, + self._last_guard_op) + else: + op = self.store_final_boxes_in_guard(guard_op, pendingfields) + self._last_guard_op = op + # for unrolling + for farg in op.getfailargs(): + if farg: + self.force_box(farg) + if op.getopnum() == rop.GUARD_EXCEPTION: + self._last_guard_op = None + return op + + def potentially_change_ovf_op_to_no_ovf(self, op): + # if last emitted operations was int_xxx_ovf and we are not emitting + # a guard_no_overflow change to int_add + if op.getopnum() != rop.GUARD_NO_OVERFLOW: + return + if not self._newoperations: + # got optimized otherwise + return + op = self._newoperations[-1] + if not op.is_ovf(): + return + newop = self.replace_op_with_no_ovf(op) + self._newoperations[-1] = newop + + def replace_op_with_no_ovf(self, op): + if op.getopnum() == rop.INT_MUL_OVF: + return self.replace_op_with(op, rop.INT_MUL) + elif op.getopnum() == rop.INT_ADD_OVF: + return self.replace_op_with(op, rop.INT_ADD) + elif op.getopnum() == rop.INT_SUB_OVF: + return self.replace_op_with(op, rop.INT_SUB) + else: + assert False + + + def _copy_resume_data_from(self, guard_op, last_guard_op): + if guard_op.getopnum() in (rop.GUARD_NO_EXCEPTION, rop.GUARD_EXCEPTION): + assert last_guard_op.getopnum() == rop.GUARD_NOT_FORCED + descr = compile.invent_fail_descr_for_op(guard_op.getopnum(), self) + descr.copy_all_attributes_from(last_guard_op.getdescr()) + guard_op.setdescr(descr) + descr.store_final_boxes(guard_op, last_guard_op.getfailargs(), + self.metainterp_sd) + assert isinstance(guard_op, GuardResOp) + if guard_op.getopnum() == rop.GUARD_VALUE: + guard_op = self._maybe_replace_guard_value(guard_op, descr) + return guard_op + def getlastop(self): return self._really_emitted_operation + def is_call_pure_pure_canraise(self, op): + if not op.is_call_pure(): + return False + effectinfo = op.getdescr().get_extra_info() + if effectinfo.check_can_raise(ignore_memoryerror=True): + return True + return False + def replace_guard_op(self, old_op_pos, new_op): old_op = self._newoperations[old_op_pos] assert old_op.is_guard() @@ -625,24 +694,26 @@ descr.store_final_boxes(op, newboxes, self.metainterp_sd) # if op.getopnum() == rop.GUARD_VALUE: - if op.getarg(0).type == 'i': - b = self.getintbound(op.getarg(0)) - if b.is_bool(): - # Hack: turn guard_value(bool) into guard_true/guard_false. - # This is done after the operation is emitted to let - # store_final_boxes_in_guard set the guard_opnum field of - # the descr to the original rop.GUARD_VALUE. - constvalue = op.getarg(1).getint() - if constvalue == 0: - opnum = rop.GUARD_FALSE - elif constvalue == 1: - opnum = rop.GUARD_TRUE - else: - raise AssertionError("uh?") - newop = self.replace_op_with(op, opnum, [op.getarg(0)], descr) - return newop - # a real GUARD_VALUE. Make it use one counter per value. - descr.make_a_counter_per_value(op) + op = self._maybe_replace_guard_value(op, descr) + return op + + def _maybe_replace_guard_value(self, op, descr): + if op.getarg(0).type == 'i': + b = self.getintbound(op.getarg(0)) + if b.is_bool(): + # Hack: turn guard_value(bool) into guard_true/guard_false. + # This is done after the operation is emitted to let + # store_final_boxes_in_guard set the guard_opnum field of + # the descr to the original rop.GUARD_VALUE. + constvalue = op.getarg(1).getint() + if constvalue == 0: + opnum = rop.GUARD_FALSE + elif constvalue == 1: + opnum = rop.GUARD_TRUE + else: + raise AssertionError("uh?") + newop = self.replace_op_with(op, opnum, [op.getarg(0)], descr) + return newop return op def optimize_default(self, op): diff --git a/rpython/jit/metainterp/optimizeopt/rewrite.py b/rpython/jit/metainterp/optimizeopt/rewrite.py --- a/rpython/jit/metainterp/optimizeopt/rewrite.py +++ b/rpython/jit/metainterp/optimizeopt/rewrite.py @@ -401,7 +401,7 @@ r = self.optimizer.metainterp_sd.logger_ops.repr_of_resop(op) raise InvalidLoop('A GUARD_VALUE (%s) was proven to ' 'always fail' % r) - descr = compile.ResumeGuardValueDescr() + descr = compile.ResumeGuardDescr() op = old_guard_op.copy_and_change(rop.GUARD_VALUE, args = [old_guard_op.getarg(0), op.getarg(1)], descr = descr) @@ -411,7 +411,6 @@ # not put in short preambles guard_xxx and guard_value # on the same box. self.optimizer.replace_guard(op, info) - descr.make_a_counter_per_value(op) # to be safe info.reset_last_guard_pos() return op @@ -456,7 +455,7 @@ if old_guard_op.getopnum() == rop.GUARD_NONNULL: # it was a guard_nonnull, which we replace with a # guard_nonnull_class. - descr = compile.ResumeGuardNonnullClassDescr() + descr = compile.ResumeGuardDescr() op = old_guard_op.copy_and_change (rop.GUARD_NONNULL_CLASS, args = [old_guard_op.getarg(0), op.getarg(1)], descr=descr) diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py --- a/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizebasic.py @@ -2022,6 +2022,7 @@ None) def test_merge_guard_class_guard_value(self): + py.test.skip("disabled") ops = """ [p1, i0, i1, i2, p2] guard_class(p1, ConstClass(node_vtable)) [i0] @@ -2055,6 +2056,7 @@ self.check_expanded_fail_descr("i0", rop.GUARD_NONNULL_CLASS) def test_merge_guard_nonnull_guard_value(self): + py.test.skip("disabled") ops = """ [p1, i0, i1, i2, p2] guard_nonnull(p1) [i0] @@ -2072,6 +2074,7 @@ self.check_expanded_fail_descr("i0", rop.GUARD_VALUE) def test_merge_guard_nonnull_guard_class_guard_value(self): + py.test.skip("disabled") ops = """ [p1, i0, i1, i2, p2] guard_nonnull(p1) [i0] @@ -2502,7 +2505,6 @@ if values is not None: fail_args = values fdescr = guard_op.getdescr() - assert fdescr.guard_opnum == guard_opnum reader = ResumeDataFakeReader(fdescr, fail_args, MyMetaInterp(self.cpu)) boxes = reader.consume_boxes() diff --git a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py --- a/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py +++ b/rpython/jit/metainterp/optimizeopt/test/test_optimizeopt.py @@ -2967,6 +2967,7 @@ assert "promote of a virtual" in exc.msg def test_merge_guard_class_guard_value(self): + py.test.skip("disabled") ops = """ [p1, i0, i1, i2, p2] guard_class(p1, ConstClass(node_vtable)) [i0] @@ -3012,6 +3013,7 @@ #self.check_expanded_fail_descr("i0", rop.GUARD_NONNULL_CLASS) def test_merge_guard_nonnull_guard_value(self): + py.test.skip("disabled") ops = """ [p1, i0, i1, i2, p2] guard_nonnull(p1) [i0] @@ -3035,6 +3037,7 @@ #self.check_expanded_fail_descr("i0", rop.GUARD_VALUE) def test_merge_guard_nonnull_guard_class_guard_value(self): + py.test.skip("disabled") ops = """ [p1, i0, i1, i2, p2] guard_nonnull(p1) [i0] diff --git a/rpython/jit/metainterp/pyjitpl.py b/rpython/jit/metainterp/pyjitpl.py --- a/rpython/jit/metainterp/pyjitpl.py +++ b/rpython/jit/metainterp/pyjitpl.py @@ -21,6 +21,7 @@ from rpython.rlib.unroll import unrolling_iterable from rpython.rtyper.lltypesystem import lltype, rffi, llmemory from rpython.rtyper import rclass +from rpython.rlib.objectmodel import compute_unique_id @@ -228,17 +229,23 @@ ''' % (_opimpl, FASTPATHS_SAME_BOXES[_opimpl.split("_")[-1]], _opimpl.upper()) ).compile() - for _opimpl in ['int_add_ovf', 'int_sub_ovf', 'int_mul_ovf']: + for (_opimpl, resop) in [ + ('int_add_jump_if_ovf', 'INT_ADD_OVF'), + ('int_sub_jump_if_ovf', 'INT_SUB_OVF'), + ('int_mul_jump_if_ovf', 'INT_MUL_OVF')]: exec py.code.Source(''' - @arguments("box", "box") - def opimpl_%s(self, b1, b2): - self.metainterp.clear_exception() + @arguments("label", "box", "box", "orgpc") + def opimpl_%s(self, lbl, b1, b2, orgpc): + self.metainterp.ovf_flag = False resbox = self.execute(rop.%s, b1, b2) - self.make_result_of_lastop(resbox) # same as execute_varargs() if not isinstance(resbox, Const): - self.metainterp.handle_possible_overflow_error() + return self.handle_possible_overflow_error(lbl, orgpc, + resbox) + elif self.metainterp.ovf_flag: + self.pc = lbl + return None # but don't emit GUARD_OVERFLOW return resbox - ''' % (_opimpl, _opimpl.upper())).compile() + ''' % (_opimpl, resop)).compile() for _opimpl in ['int_is_true', 'int_is_zero', 'int_neg', 'int_invert', 'cast_float_to_int', 'cast_int_to_float', @@ -330,37 +337,37 @@ def opimpl_goto(self, target): self.pc = target - @arguments("box", "label") - def opimpl_goto_if_not(self, box, target): + @arguments("box", "label", "orgpc") + def opimpl_goto_if_not(self, box, target, orgpc): switchcase = box.getint() if switchcase: opnum = rop.GUARD_TRUE else: opnum = rop.GUARD_FALSE - self.metainterp.generate_guard(opnum, box) + self.metainterp.generate_guard(opnum, box, resumepc=orgpc) if not switchcase: self.pc = target - @arguments("box", "label") - def opimpl_goto_if_not_int_is_true(self, box, target): + @arguments("box", "label", "orgpc") + def opimpl_goto_if_not_int_is_true(self, box, target, orgpc): condbox = self.execute(rop.INT_IS_TRUE, box) - self.opimpl_goto_if_not(condbox, target) + self.opimpl_goto_if_not(condbox, target, orgpc) - @arguments("box", "label") - def opimpl_goto_if_not_int_is_zero(self, box, target): + @arguments("box", "label", "orgpc") + def opimpl_goto_if_not_int_is_zero(self, box, target, orgpc): condbox = self.execute(rop.INT_IS_ZERO, box) - self.opimpl_goto_if_not(condbox, target) + self.opimpl_goto_if_not(condbox, target, orgpc) for _opimpl in ['int_lt', 'int_le', 'int_eq', 'int_ne', 'int_gt', 'int_ge', 'ptr_eq', 'ptr_ne']: exec py.code.Source(''' - @arguments("box", "box", "label") - def opimpl_goto_if_not_%s(self, b1, b2, target): + @arguments("box", "box", "label", "orgpc") + def opimpl_goto_if_not_%s(self, b1, b2, target, orgpc): if b1 is b2: condbox = %s else: condbox = self.execute(rop.%s, b1, b2) - self.opimpl_goto_if_not(condbox, target) + self.opimpl_goto_if_not(condbox, target, orgpc) ''' % (_opimpl, FASTPATHS_SAME_BOXES[_opimpl.split("_")[-1]], _opimpl.upper()) ).compile() @@ -419,7 +426,7 @@ assert box.getint() == 0 target = switchdict.dict[const1.getint()] self.metainterp.generate_guard(rop.GUARD_FALSE, box, - resumepc=target) + resumepc=orgpc) else: # found one of the cases self.implement_guard_value(valuebox, orgpc) @@ -1458,6 +1465,17 @@ def setup_resume_at_op(self, pc): self.pc = pc + def handle_possible_overflow_error(self, label, orgpc, resbox): + if self.metainterp.ovf_flag: + self.metainterp.generate_guard(rop.GUARD_OVERFLOW, None, + resumepc=orgpc) + self.pc = label + return None + else: + self.metainterp.generate_guard(rop.GUARD_NO_OVERFLOW, None, + resumepc=orgpc) + return resbox + def run_one_step(self): # Execute the frame forward. This method contains a loop that leaves # whenever the 'opcode_implementations' (which is one of the 'opimpl_' @@ -2023,7 +2041,7 @@ moreargs = [box] + extraargs else: moreargs = list(extraargs) - if opnum == rop.GUARD_EXCEPTION or opnum == rop.GUARD_OVERFLOW: + if opnum == rop.GUARD_EXCEPTION: guard_op = self.history.record(opnum, moreargs, lltype.nullptr(llmemory.GCREF.TO)) else: @@ -2310,7 +2328,7 @@ if isinstance(key, compile.ResumeAtPositionDescr): self.seen_loop_header_for_jdindex = self.jitdriver_sd.index try: - self.prepare_resume_from_failure(key.guard_opnum, deadframe) + self.prepare_resume_from_failure(deadframe, key) if self.resumekey_original_loop_token is None: # very rare case raise SwitchToBlackhole(Counters.ABORT_BRIDGE) self.interpret() @@ -2453,22 +2471,9 @@ else: assert 0 self.jitdriver_sd.warmstate.execute_assembler(loop_token, *args) - def prepare_resume_from_failure(self, opnum, deadframe): - frame = self.framestack[-1] - if opnum == rop.GUARD_FUTURE_CONDITION: - pass - elif opnum == rop.GUARD_TRUE: # a goto_if_not that jumps only now - frame.pc = frame.jitcode.follow_jump(frame.pc) - elif opnum == rop.GUARD_FALSE: # a goto_if_not that stops jumping; - pass # or a switch that was in its "default" case - elif opnum == rop.GUARD_VALUE or opnum == rop.GUARD_CLASS: - pass # the pc is already set to the *start* of the opcode - elif (opnum == rop.GUARD_NONNULL or - opnum == rop.GUARD_ISNULL or - opnum == rop.GUARD_NONNULL_CLASS): - pass # the pc is already set to the *start* of the opcode - elif opnum == rop.GUARD_NO_EXCEPTION or opnum == rop.GUARD_EXCEPTION: - exception = self.cpu.grab_exc_value(deadframe) + def prepare_resume_from_failure(self, deadframe, resumedescr): + exception = self.cpu.grab_exc_value(deadframe) + if isinstance(resumedescr, compile.ResumeGuardExcDescr): if exception: self.execute_ll_raised(lltype.cast_opaque_ptr(rclass.OBJECTPTR, exception)) @@ -2478,20 +2483,8 @@ self.handle_possible_exception() except ChangeFrame: pass - elif opnum == rop.GUARD_NOT_INVALIDATED: - pass # XXX we want to do something special in resume descr, - # but not now - elif opnum == rop.GUARD_NO_OVERFLOW: # an overflow now detected - self.execute_raised(OverflowError(), constant=True) - try: - self.finishframe_exception() - except ChangeFrame: - pass - elif opnum == rop.GUARD_OVERFLOW: # no longer overflowing - self.clear_exception() else: - from rpython.jit.metainterp.resoperation import opname - raise NotImplementedError(opname[opnum]) + assert not exception def get_procedure_token(self, greenkey, with_compiled_targets=False): JitCell = self.jitdriver_sd.warmstate.JitCell @@ -2774,18 +2767,6 @@ else: self.generate_guard(rop.GUARD_NO_EXCEPTION, None, []) - def handle_possible_overflow_error(self): - if self.last_exc_value: - op = self.generate_guard(rop.GUARD_OVERFLOW, None) - op.setref_base(lltype.cast_opaque_ptr(llmemory.GCREF, - self.last_exc_value)) - assert self.class_of_last_exc_is_const - self.last_exc_box = ConstPtr( - lltype.cast_opaque_ptr(llmemory.GCREF, self.last_exc_value)) - self.finishframe_exception() - else: - self.generate_guard(rop.GUARD_NO_OVERFLOW, None) - def assert_no_exception(self): assert not self.last_exc_value @@ -3251,16 +3232,17 @@ print '-> %r' % (resultbox,) assert argcodes[next_argcode] == '>' result_argcode = argcodes[next_argcode + 1] - assert resultbox.type == {'i': history.INT, - 'r': history.REF, - 'f': history.FLOAT}[result_argcode] + if 'ovf' not in name: + assert resultbox.type == {'i': history.INT, + 'r': history.REF, + 'f': history.FLOAT}[result_argcode] else: resultbox = unboundmethod(self, *args) # if resultbox is not None: self.make_result_of_lastop(resultbox) elif not we_are_translated(): - assert self._result_argcode in 'v?' + assert self._result_argcode in 'v?' or 'ovf' in name # unboundmethod = getattr(MIFrame, 'opimpl_' + name).im_func argtypes = unrolling_iterable(unboundmethod.argtypes) diff --git a/rpython/jit/metainterp/resoperation.py b/rpython/jit/metainterp/resoperation.py --- a/rpython/jit/metainterp/resoperation.py +++ b/rpython/jit/metainterp/resoperation.py @@ -236,6 +236,9 @@ return (self.getopnum() == rop.GUARD_OVERFLOW or self.getopnum() == rop.GUARD_NO_OVERFLOW) + def is_jit_debug(self): + return rop._JIT_DEBUG_FIRST <= self.getopnum() <= rop._JIT_DEBUG_LAST + def is_always_pure(self): return rop._ALWAYS_PURE_FIRST <= self.getopnum() <= rop._ALWAYS_PURE_LAST @@ -375,6 +378,7 @@ newop.rd_frame_info_list = self.rd_frame_info_list return newop + # =========== # type mixins # =========== @@ -689,7 +693,7 @@ 'GUARD_NO_EXCEPTION/0d/n', # may be called with an exception currently set 'GUARD_EXCEPTION/1d/r', # may be called with an exception currently set 'GUARD_NO_OVERFLOW/0d/n', - 'GUARD_OVERFLOW/0d/r', + 'GUARD_OVERFLOW/0d/n', 'GUARD_NOT_FORCED/0d/n', # may be called with an exception currently set 'GUARD_NOT_FORCED_2/0d/n', # same as GUARD_NOT_FORCED, but for finish() 'GUARD_NOT_INVALIDATED/0d/n', @@ -806,10 +810,12 @@ 'UNICODESETITEM/3/n', 'COND_CALL_GC_WB/1d/n', # [objptr] (for the write barrier) 'COND_CALL_GC_WB_ARRAY/2d/n', # [objptr, arrayindex] (write barr. for array) + '_JIT_DEBUG_FIRST', 'DEBUG_MERGE_POINT/*/n', # debugging only 'ENTER_PORTAL_FRAME/2/n', # debugging only 'LEAVE_PORTAL_FRAME/1/n', # debugging only 'JIT_DEBUG/*/n', # debugging only + '_JIT_DEBUG_LAST', 'VIRTUAL_REF_FINISH/2/n', # removed before it's passed to the backend 'COPYSTRCONTENT/5/n', # src, dst, srcstart, dststart, length 'COPYUNICODECONTENT/5/n', diff --git a/rpython/jit/metainterp/test/test_ajit.py b/rpython/jit/metainterp/test/test_ajit.py --- a/rpython/jit/metainterp/test/test_ajit.py +++ b/rpython/jit/metainterp/test/test_ajit.py @@ -115,10 +115,13 @@ while y > 0: myjitdriver.can_enter_jit(x=x, y=y, res=res) myjitdriver.jit_merge_point(x=x, y=y, res=res) - res += ovfcheck(x * x) - x += 1 - res += ovfcheck(x * x) - y -= 1 + try: + res += ovfcheck(x * x) + x += 1 + res += ovfcheck(x * x) + y -= 1 + except OverflowError: + assert 0 return res res = self.meta_interp(f, [6, 7]) assert res == 1323 @@ -151,7 +154,10 @@ myjitdriver.can_enter_jit(x=x, y=y, res=res) myjitdriver.jit_merge_point(x=x, y=y, res=res) b = y * 2 - res += ovfcheck(x * x) + b + try: + res += ovfcheck(x * x) + b + except OverflowError: + assert 0 y -= 1 return res res = self.meta_interp(f, [6, 7]) @@ -230,8 +236,8 @@ res = self.meta_interp(f, [6, 32, 16]) assert res == 1692 self.check_trace_count(3) - self.check_resops({'int_lt': 2, 'int_gt': 4, 'guard_false': 2, - 'guard_true': 4, 'int_sub': 4, 'jump': 3, + self.check_resops({'int_lt': 4, 'int_gt': 4, 'guard_false': 2, + 'guard_true': 6, 'int_sub': 4, 'jump': 3, 'int_mul': 3, 'int_add': 4}) def test_loop_invariant_mul_ovf2(self): @@ -400,7 +406,7 @@ return externfn(n, n+1) res = self.interp_operations(f, [6]) assert res == 42 - self.check_operations_history(int_add=1, int_mul=0, call=1, guard_no_exception=0) + self.check_operations_history(int_add=1, int_mul=0, call_i=1, guard_no_exception=0) def test_residual_call_elidable(self): def externfn(x, y): @@ -413,7 +419,7 @@ assert res == 42 # CALL_PURE is not recorded in the history if all-constant args self.check_operations_history(int_add=0, int_mul=0, - call=0, call_pure_i=0) + call_i=0, call_pure_i=0) def test_residual_call_elidable_1(self): @elidable @@ -425,7 +431,7 @@ assert res == 42 # CALL_PURE is recorded in the history if not-all-constant args self.check_operations_history(int_add=1, int_mul=0, - call=0, call_pure_i=1) + call_i=0, call_pure_i=1) def test_residual_call_elidable_2(self): myjitdriver = JitDriver(greens = [], reds = ['n']) @@ -653,11 +659,11 @@ # res = self.meta_interp(f, [3, 6], repeat=7, function_threshold=0) assert res == 6 - 4 - 5 - self.check_history(call=0) # because the trace starts in the middle + self.check_history(call_n=0) # because the trace starts in the middle # res = self.meta_interp(f, [60, 84], repeat=7) assert res == 84 - 61 - 62 - self.check_history(call=1) # because the trace starts immediately + self.check_history(call_n=1) # because the trace starts immediately def test_unroll_one_loop_iteration(self): def unroll(code): @@ -679,11 +685,11 @@ res = self.meta_interp(f, [1, 4, 1], enable_opts="", inline=True) assert res == f(1, 4, 1) - self.check_history(call_assembler=0) + self.check_history(call_assembler_i=0) res = self.meta_interp(f, [1, 4, 2], enable_opts="", inline=True) assert res == f(1, 4, 2) - self.check_history(call_assembler=1) + self.check_history(call_assembler_i=1) def test_format(self): def f(n): @@ -723,6 +729,7 @@ elif n == 7: a = 3 else: a = 2 x = intmask(x * 10 + a) + #print "XXXXXXXXXXXXXXXX", x i += 1 return x res = self.meta_interp(f, [0], backendopt=True) @@ -834,7 +841,7 @@ return a.foo * x res = self.interp_operations(f, [42]) assert res == 210 - self.check_operations_history(getfield_gc=1) + self.check_operations_history(getfield_gc_i=1) def test_getfield_immutable(self): class A: @@ -851,7 +858,7 @@ return a.foo * x res = self.interp_operations(f, [42]) assert res == 210 - self.check_operations_history(getfield_gc=0) + self.check_operations_history(getfield_gc_i=0) def test_setfield_bool(self): class A: @@ -882,6 +889,24 @@ res = self.interp_operations(f, [1, sys.maxint]) assert res == -42 + def test_ovf_raise(self): + def g(x, y): + try: + return ovfcheck(x * y) + except OverflowError: + raise + + def f(x, y): + try: + return g(x, y) + except OverflowError: + return 3 + + res = self.interp_operations(f, [sys.maxint, 2]) + assert res == 3 + res = self.interp_operations(f, [3, 2]) + assert res == 6 + def test_int_sub_ovf(self): def f(x, y): try: @@ -1356,7 +1381,7 @@ return g(a, b) res = self.interp_operations(f, [3, 5]) assert res == 8 - self.check_operations_history(int_add=0, call=1) + self.check_operations_history(int_add=0, call_i=1) def test_listcomp(self): myjitdriver = JitDriver(greens = [], reds = ['x', 'y', 'lst']) @@ -1380,7 +1405,7 @@ return tup[1] res = self.interp_operations(f, [3, 5]) assert res == 5 - self.check_operations_history(setfield_gc=2, getfield_gc_pure=0) + self.check_operations_history(setfield_gc=2, getfield_gc_pure_i=0) def test_oosend_look_inside_only_one(self): class A: @@ -1455,16 +1480,6 @@ res = self.meta_interp(f, [299], listops=True) assert res == f(299) self.check_resops(guard_class=0, guard_value=6) - # - # The original 'guard_class' is rewritten to be directly 'guard_value'. - # Check that this rewrite does not interfere with the descr, which - # should be a full-fledged multivalued 'guard_value' descr. - if self.basic: - for loop in get_stats().get_all_loops(): - for op in loop.get_operations(): - if op.getopname() == "guard_value": - descr = op.getdescr() - assert descr.get_index_of_guard_value() >= 0 def test_merge_guardnonnull_guardclass(self): myjitdriver = JitDriver(greens = [], reds = ['x', 'l']) @@ -1866,7 +1881,8 @@ res = self.meta_interp(g, [6, 20]) assert res == g(6, 20) self.check_trace_count(8) - self.check_resops(getarrayitem_gc_i=10) + # 6 extra from sharing guard data + self.check_resops(getarrayitem_gc_i=10 + 6) def test_multiple_specialied_versions_bridge(self): myjitdriver = JitDriver(greens = [], reds = ['y', 'x', 'z', 'res']) @@ -2055,8 +2071,8 @@ res = self.meta_interp(g, [3, 23]) assert res == 7068153 self.check_trace_count(6) - self.check_resops(guard_true=6, guard_class=2, int_mul=3, - int_add=3, guard_false=3) + self.check_resops(guard_true=8, guard_class=2, int_mul=3, + int_add=3, guard_false=4) def test_dont_trace_every_iteration(self): myjitdriver = JitDriver(greens = [], reds = ['a', 'b', 'i', 'sa']) @@ -2079,7 +2095,7 @@ self.check_enter_count(2) def test_current_trace_length(self): - myjitdriver = JitDriver(greens = ['g'], reds = ['x']) + myjitdriver = JitDriver(greens = ['g'], reds = ['x', 'l']) @dont_look_inside def residual(): print "hi there" @@ -2090,14 +2106,15 @@ residual() y += 1 def f(x, g): + l = [] n = 0 while x > 0: - myjitdriver.can_enter_jit(x=x, g=g) - myjitdriver.jit_merge_point(x=x, g=g) + myjitdriver.can_enter_jit(x=x, g=g, l=l) + myjitdriver.jit_merge_point(x=x, g=g, l=l) loop(g) x -= 1 - n = current_trace_length() - return n + l.append(current_trace_length()) + return l[-2] # not the blackholed version res = self.meta_interp(f, [5, 8]) assert 14 < res < 42 res = self.meta_interp(f, [5, 2]) @@ -2619,7 +2636,10 @@ node2.val = 7 if a >= 100: sa += 1 - sa += ovfcheck(i + i) + try: + sa += ovfcheck(i + i) + except OverflowError: + assert 0 node1 = A(i) i += 1 assert self.meta_interp(f, [20, 7]) == f(20, 7) @@ -2638,7 +2658,7 @@ i += 1 return sa assert self.meta_interp(f, [20]) == f(20) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit