Author: fijal Branch: Changeset: r83364:913dd4cf97ff Date: 2016-03-25 20:54 +0200 http://bitbucket.org/pypy/pypy/changeset/913dd4cf97ff/
Log: (fijal, arigo) Merge jit-leaner-frontend This branch separates resoperations used in optimizer from the ones used in the frontend, which now uses a more compact way to store traces. Additionally the heapcache has been reworked to the new model. The net effects are ~20% improvements in the speed of tracing. There is potential for more work which would avoid allocating ResOperations that are not emitted completely diff too long, truncating to 2000 out of 8331 lines diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py --- a/pypy/config/pypyoption.py +++ b/pypy/config/pypyoption.py @@ -46,7 +46,6 @@ except detect_cpu.ProcessorAutodetectError: pass - translation_modules = default_modules.copy() translation_modules.update([ "fcntl", "time", "select", "signal", "_rawffi", "zlib", "struct", "_md5", diff --git a/pypy/tool/gdb_pypy.py b/pypy/tool/gdb_pypy.py --- a/pypy/tool/gdb_pypy.py +++ b/pypy/tool/gdb_pypy.py @@ -288,9 +288,11 @@ RPyListPrinter.recursive = True try: itemlist = [] - for i in range(length): + for i in range(min(length, MAX_DISPLAY_LENGTH)): item = items[i] itemlist.append(str(item)) # may recurse here + if length > MAX_DISPLAY_LENGTH: + itemlist.append("...") str_items = ', '.join(itemlist) finally: RPyListPrinter.recursive = False diff --git a/rpython/config/translationoption.py b/rpython/config/translationoption.py --- a/rpython/config/translationoption.py +++ b/rpython/config/translationoption.py @@ -126,6 +126,9 @@ ChoiceOption("jit_profiler", "integrate profiler support into the JIT", ["off", "oprofile"], default="off"), + ChoiceOption("jit_opencoder_model", "the model limits the maximal length" + " of traces. Use big if you want to go bigger than " + "the default", ["big", "normal"], default="normal"), BoolOption("check_str_without_nul", "Forbid NUL chars in strings in some external function calls", default=False, cmdline=None), 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 @@ -455,7 +455,7 @@ if box is not frame.current_op: value = frame.env[box] else: - value = box.getvalue() # 0 or 0.0 or NULL + value = 0 # box.getvalue() # 0 or 0.0 or NULL else: value = None values.append(value) @@ -472,6 +472,13 @@ # ------------------------------------------------------------ + def setup_descrs(self): + all_descrs = [] + for k, v in self.descrs.iteritems(): + v.descr_index = len(all_descrs) + all_descrs.append(v) + return all_descrs + def calldescrof(self, FUNC, ARGS, RESULT, effect_info): key = ('call', getkind(RESULT), tuple([getkind(A) for A in ARGS]), diff --git a/rpython/jit/backend/llsupport/assembler.py b/rpython/jit/backend/llsupport/assembler.py --- a/rpython/jit/backend/llsupport/assembler.py +++ b/rpython/jit/backend/llsupport/assembler.py @@ -331,7 +331,7 @@ counter = self._register_counter(tp, number, token) c_adr = ConstInt(rffi.cast(lltype.Signed, counter)) operations.append( - ResOperation(rop.INCREMENT_DEBUG_COUNTER, [c_adr], None)) + ResOperation(rop.INCREMENT_DEBUG_COUNTER, [c_adr])) def _register_counter(self, tp, number, token): # YYY very minor leak -- we need the counters to stay alive diff --git a/rpython/jit/backend/llsupport/descr.py b/rpython/jit/backend/llsupport/descr.py --- a/rpython/jit/backend/llsupport/descr.py +++ b/rpython/jit/backend/llsupport/descr.py @@ -21,6 +21,30 @@ self._cache_call = {} self._cache_interiorfield = {} + def setup_descrs(self): + all_descrs = [] + for k, v in self._cache_size.iteritems(): + v.descr_index = len(all_descrs) + all_descrs.append(v) + for k, v in self._cache_field.iteritems(): + for k1, v1 in v.iteritems(): + v1.descr_index = len(all_descrs) + all_descrs.append(v1) + for k, v in self._cache_array.iteritems(): + v.descr_index = len(all_descrs) + all_descrs.append(v) + for k, v in self._cache_arraylen.iteritems(): + v.descr_index = len(all_descrs) + all_descrs.append(v) + for k, v in self._cache_call.iteritems(): + v.descr_index = len(all_descrs) + all_descrs.append(v) + for k, v in self._cache_interiorfield.iteritems(): + v.descr_index = len(all_descrs) + all_descrs.append(v) + assert len(all_descrs) < 2**15 + return all_descrs + def init_size_descr(self, STRUCT, sizedescr): pass 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 @@ -316,6 +316,9 @@ return ll_frame return execute_token + def setup_descrs(self): + return self.gc_ll_descr.setup_descrs() + # ------------------- helpers and descriptions -------------------- @staticmethod diff --git a/rpython/jit/backend/llsupport/regalloc.py b/rpython/jit/backend/llsupport/regalloc.py --- a/rpython/jit/backend/llsupport/regalloc.py +++ b/rpython/jit/backend/llsupport/regalloc.py @@ -683,7 +683,7 @@ for i in range(len(operations)-1, -1, -1): op = operations[i] if op.type != 'v': - if op not in last_used and op.has_no_side_effect(): + if op not in last_used and rop.has_no_side_effect(op.opnum): continue opnum = op.getopnum() for j in range(op.numargs()): @@ -695,7 +695,7 @@ if opnum != rop.JUMP and opnum != rop.LABEL: if arg not in last_real_usage: last_real_usage[arg] = i - if op.is_guard(): + if rop.is_guard(op.opnum): for arg in op.getfailargs(): if arg is None: # hole continue @@ -732,14 +732,7 @@ return longevity, last_real_usage def is_comparison_or_ovf_op(opnum): - from rpython.jit.metainterp.resoperation import opclasses - cls = opclasses[opnum] - # hack hack: in theory they are instance method, but they don't use - # any instance field, we can use a fake object - class Fake(cls): - pass - op = Fake() - return op.is_comparison() or op.is_ovf() + return rop.is_comparison(opnum) or rop.is_ovf(opnum) def valid_addressing_size(size): return size == 1 or size == 2 or size == 4 or size == 8 diff --git a/rpython/jit/backend/llsupport/rewrite.py b/rpython/jit/backend/llsupport/rewrite.py --- a/rpython/jit/backend/llsupport/rewrite.py +++ b/rpython/jit/backend/llsupport/rewrite.py @@ -103,7 +103,7 @@ orig_op.set_forwarded(op) replaced = True op.setarg(i, arg) - if op.is_guard(): + if rop.is_guard(op.opnum): if not replaced: op = op.copy_and_change(op.getopnum()) orig_op.set_forwarded(op) @@ -212,7 +212,7 @@ # self._emit_mul_if_factor_offset_not_supported(v_length, scale, 0) # op.setarg(1, ConstInt(scale)) # op.setarg(2, v_length) - if op.is_getarrayitem() or \ + if rop.is_getarrayitem(opnum) or \ opnum in (rop.GETARRAYITEM_RAW_I, rop.GETARRAYITEM_RAW_F): self.handle_getarrayitem(op) @@ -324,13 +324,13 @@ if self.transform_to_gc_load(op): continue # ---------- turn NEWxxx into CALL_MALLOC_xxx ---------- - if op.is_malloc(): + if rop.is_malloc(op.opnum): self.handle_malloc_operation(op) continue - if (op.is_guard() or + if (rop.is_guard(op.opnum) or self.could_merge_with_next_guard(op, i, operations)): self.emit_pending_zeros() - elif op.can_malloc(): + elif rop.can_malloc(op.opnum): self.emitting_an_operation_that_can_collect() elif op.getopnum() == rop.LABEL: self.emitting_an_operation_that_can_collect() @@ -370,8 +370,8 @@ # return True in cases where the operation and the following guard # should likely remain together. Simplified version of # can_merge_with_next_guard() in llsupport/regalloc.py. - if not op.is_comparison(): - return op.is_ovf() # int_xxx_ovf() / guard_no_overflow() + if not rop.is_comparison(op.opnum): + return rop.is_ovf(op.opnum) # int_xxx_ovf() / guard_no_overflow() if i + 1 >= len(operations): return False next_op = operations[i + 1] @@ -400,7 +400,6 @@ # it's hard to test all cases). Rewrite it away. value = int(opnum == rop.GUARD_FALSE) op1 = ResOperation(rop.SAME_AS_I, [ConstInt(value)]) - op1.setint(value) self.emit_op(op1) lst = op.getfailargs()[:] lst[i] = op1 @@ -633,8 +632,7 @@ args = [frame, arglist[jd.index_of_virtualizable]] else: args = [frame] - call_asm = ResOperation(op.getopnum(), args, - op.getdescr()) + call_asm = ResOperation(op.getopnum(), args, descr=op.getdescr()) self.replace_op_with(self.get_box_replacement(op), call_asm) self.emit_op(call_asm) @@ -708,7 +706,7 @@ def _gen_call_malloc_gc(self, args, v_result, descr): """Generate a CALL_MALLOC_GC with the given args.""" self.emitting_an_operation_that_can_collect() - op = ResOperation(rop.CALL_MALLOC_GC, args, descr) + op = ResOperation(rop.CALL_MALLOC_GC, args, descr=descr) self.replace_op_with(v_result, op) self.emit_op(op) # In general, don't add v_result to write_barrier_applied: diff --git a/rpython/jit/backend/test/test_ll_random.py b/rpython/jit/backend/test/test_ll_random.py --- a/rpython/jit/backend/test/test_ll_random.py +++ b/rpython/jit/backend/test/test_ll_random.py @@ -2,6 +2,7 @@ from rpython.rtyper.lltypesystem import lltype, llmemory, rffi, rstr from rpython.rtyper import rclass from rpython.jit.backend.test import test_random +from rpython.jit.backend.test.test_random import getint, getref_base, getref from rpython.jit.metainterp.resoperation import ResOperation, rop, optypes from rpython.jit.metainterp.history import ConstInt, ConstPtr, getkind from rpython.jit.codewriter import heaptracker @@ -169,7 +170,7 @@ if length == 0: raise test_random.CannotProduceOperation v_index = r.choice(self.intvars) - if not (0 <= v_index.getint() < length): + if not (0 <= getint(v_index) < length): v_index = ConstInt(r.random_integer() % length) return v_index @@ -311,7 +312,7 @@ def field_descr(self, builder, r): v, A = builder.get_structptr_var(r, type=lltype.Array, array_of_structs=True) - array = v.getref(lltype.Ptr(A)) + array = getref(lltype.Ptr(A), v) v_index = builder.get_index(len(array), r) choice = [] for name in A.OF._names: @@ -344,7 +345,7 @@ w = ConstInt(r.random_integer()) else: w = r.choice(builder.intvars) - value = w.getint() + value = getint(w) if rffi.cast(lltype.Signed, rffi.cast(TYPE, value)) == value: break builder.do(self.opnum, [v, w], descr) @@ -357,7 +358,7 @@ w = ConstInt(r.random_integer()) else: w = r.choice(builder.intvars) - value = w.getint() + value = getint(w) if rffi.cast(lltype.Signed, rffi.cast(TYPE, value)) == value: break builder.do(self.opnum, [v, v_index, w], descr) @@ -389,7 +390,7 @@ class GetArrayItemOperation(ArrayOperation): def field_descr(self, builder, r): v, A = builder.get_arrayptr_var(r) - array = v.getref(lltype.Ptr(A)) + array = getref(lltype.Ptr(A), v) v_index = builder.get_index(len(array), r) descr = self.array_descr(builder, A) return v, A, v_index, descr @@ -411,7 +412,7 @@ w = ConstInt(r.random_integer()) else: w = r.choice(builder.intvars) - value = w.getint() + value = getint(w) if rffi.cast(lltype.Signed, rffi.cast(A.OF, value)) == value: break builder.do(self.opnum, [v, v_index, w], descr) @@ -455,7 +456,7 @@ v_ptr = builder.do(self.opnum, [v_length]) getattr(builder, self.builder_cache).append(v_ptr) # Initialize the string. Is there a better way to do this? - for i in range(v_length.getint()): + for i in range(getint(v_length)): v_index = ConstInt(i) v_char = ConstInt(r.random_integer() % self.max) builder.do(self.set_char, [v_ptr, v_index, v_char]) @@ -471,9 +472,9 @@ current = getattr(builder, self.builder_cache) if current and r.random() < .8: v_string = r.choice(current) - string = v_string.getref(self.ptr) + string = getref(self.ptr, v_string) else: - string = self.alloc(builder.get_index(500, r).getint()) + string = self.alloc(getint(builder.get_index(500, r))) v_string = ConstPtr(lltype.cast_opaque_ptr(llmemory.GCREF, string)) current.append(v_string) for i in range(len(string.chars)): @@ -484,7 +485,7 @@ class AbstractGetItemOperation(AbstractStringOperation): def produce_into(self, builder, r): v_string = self.get_string(builder, r) - v_index = builder.get_index(len(v_string.getref(self.ptr).chars), r) + v_index = builder.get_index(len(getref(self.ptr, v_string).chars), r) builder.do(self.opnum, [v_string, v_index]) class AbstractSetItemOperation(AbstractStringOperation): @@ -492,7 +493,7 @@ v_string = self.get_string(builder, r) if isinstance(v_string, ConstPtr): raise test_random.CannotProduceOperation # setitem(Const, ...) - v_index = builder.get_index(len(v_string.getref(self.ptr).chars), r) + v_index = builder.get_index(len(getref(self.ptr, v_string).chars), r) v_target = ConstInt(r.random_integer() % self.max) builder.do(self.opnum, [v_string, v_index, v_target]) @@ -505,15 +506,15 @@ def produce_into(self, builder, r): v_srcstring = self.get_string(builder, r) v_dststring = self.get_string(builder, r) - src = v_srcstring.getref(self.ptr) - dst = v_dststring.getref(self.ptr) + src = getref(self.ptr, v_srcstring) + dst = getref(self.ptr, v_dststring) if src == dst: # because it's not a raise test_random.CannotProduceOperation # memmove(), but memcpy() srclen = len(src.chars) dstlen = len(dst.chars) v_length = builder.get_index(min(srclen, dstlen), r) - v_srcstart = builder.get_index(srclen - v_length.getint() + 1, r) - v_dststart = builder.get_index(dstlen - v_length.getint() + 1, r) + v_srcstart = builder.get_index(srclen - getint(v_length) + 1, r) + v_dststart = builder.get_index(dstlen - getint(v_length) + 1, r) builder.do(self.opnum, [v_srcstring, v_dststring, v_srcstart, v_dststart, v_length]) @@ -585,7 +586,7 @@ """ % funcargs).compile() vtableptr = v._hints['vtable']._as_ptr() d = { - 'ptr': S.getref_base(), + 'ptr': getref_base(S), 'vtable' : vtableptr, 'LLException' : LLException, } 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 @@ -11,11 +11,9 @@ from rpython.jit.metainterp.executor import _execute_arglist, wrap_constant from rpython.jit.metainterp.resoperation import opname from rpython.jit.codewriter import longlong -from rpython.rtyper.lltypesystem import lltype, rstr +from rpython.rtyper.lltypesystem import lltype, llmemory, rstr from rpython.rtyper import rclass -class PleaseRewriteMe(Exception): - pass class DummyLoop(object): def __init__(self, subops): @@ -27,6 +25,41 @@ def execute_raised(self, exc, constant=False): self._got_exc = exc + +def getint(v): + if isinstance(v, (ConstInt, InputArgInt)): + return v.getint() + else: + return v._example_int + +def getfloatstorage(v): + if isinstance(v, (ConstFloat, InputArgFloat)): + return v.getfloatstorage() + else: + return v._example_float + +def getfloat(v): + return longlong.getrealfloat(getfloatstorage(v)) + +def getref_base(v): + if isinstance(v, (ConstPtr, InputArgRef)): + return v.getref_base() + else: + return v._example_ref + +def getref(PTR, v): + return lltype.cast_opaque_ptr(PTR, getref_base(v)) + +def constbox(v): + if v.type == INT: + return ConstInt(getint(v)) + if v.type == FLOAT: + return ConstFloat(getfloatstorage(v)) + if v.type == REF: + return ConstPtr(getref_base(v)) + assert 0, v.type + + class OperationBuilder(object): def __init__(self, cpu, loop, vars): self.cpu = cpu @@ -57,11 +90,21 @@ def do(self, opnum, argboxes, descr=None): self.fakemetainterp._got_exc = None op = ResOperation(opnum, argboxes, descr) + argboxes = map(constbox, argboxes) result = _execute_arglist(self.cpu, self.fakemetainterp, opnum, argboxes, descr) if result is not None: - c_result = wrap_constant(result) - op.copy_value_from(c_result) + if lltype.typeOf(result) == lltype.Signed: + op._example_int = result + elif isinstance(result, bool): + op._example_int = int(result) + elif lltype.typeOf(result) == longlong.FLOATSTORAGE: + op._example_float = result + elif isinstance(result, float): + op._example_float = longlong.getfloatstorage(result) + else: + assert lltype.typeOf(result) == llmemory.GCREF + op._example_ref = result self.loop.operations.append(op) return op @@ -101,7 +144,7 @@ if v in names: args.append(names[v]) elif isinstance(v, ConstPtr): - assert not v.getref_base() # otherwise should be in the names + assert not getref_base(v) # otherwise should be in the names args.append('ConstPtr(lltype.nullptr(llmemory.GCREF.TO))') elif isinstance(v, ConstFloat): args.append('ConstFloat(longlong.getfloatstorage(%r))' @@ -198,10 +241,10 @@ # def writevar(v, nameprefix, init=''): if nameprefix == 'const_ptr': - if not v.getref_base(): + if not getref_base(v): return 'lltype.nullptr(llmemory.GCREF.TO)' - TYPE = v.getref_base()._obj.ORIGTYPE - cont = lltype.cast_opaque_ptr(TYPE, v.getref_base()) + TYPE = getref_base(v)._obj.ORIGTYPE + cont = lltype.cast_opaque_ptr(TYPE, getref_base(v)) if TYPE.TO._is_varsize(): if isinstance(TYPE.TO, lltype.GcStruct): lgt = len(cont.chars) @@ -252,9 +295,9 @@ for i, v in enumerate(self.loop.inputargs): assert not isinstance(v, Const) if v.type == FLOAT: - vals.append("longlong.getfloatstorage(%r)" % v.getfloat()) + vals.append("longlong.getfloatstorage(%r)" % getfloat(v)) else: - vals.append("%r" % v.getint()) + vals.append("%r" % getint(v)) print >>s, ' loop_args = [%s]' % ", ".join(vals) print >>s, ' frame = cpu.execute_token(looptoken, *loop_args)' if self.should_fail_by is None: @@ -264,10 +307,10 @@ for i, v in enumerate(fail_args): if v.type == FLOAT: print >>s, (' assert longlong.getrealfloat(' - 'cpu.get_float_value(frame, %d)) == %r' % (i, v.getfloatstorage())) + 'cpu.get_float_value(frame, %d)) == %r' % (i, getfloatstorage(v))) else: print >>s, (' assert cpu.get_int_value(frame, %d) == %d' - % (i, v.getint())) + % (i, getint(v))) self.names = names s.flush() @@ -295,7 +338,7 @@ builder.intvars.append(v_result) boolres = self.boolres if boolres == 'sometimes': - boolres = v_result.getint() in [0, 1] + boolres = getint(v_result) in [0, 1] if boolres: builder.boolvars.append(v_result) elif v_result.type == FLOAT: @@ -346,10 +389,10 @@ v_second = ConstInt((value & self.and_mask) | self.or_mask) else: v = r.choice(builder.intvars) - v_value = v.getint() + v_value = getint(v) if (v_value & self.and_mask) != v_value: v = builder.do(rop.INT_AND, [v, ConstInt(self.and_mask)]) - v_value = v.getint() + v_value = getint(v) if (v_value | self.or_mask) != v_value: v = builder.do(rop.INT_OR, [v, ConstInt(self.or_mask)]) v_second = v @@ -395,9 +438,9 @@ v_second = ConstFloat(r.random_float_storage()) else: v_second = r.choice(builder.floatvars) - if abs(v_first.getfloat()) > 1E100 or abs(v_second.getfloat()) > 1E100: + if abs(getfloat(v_first)) > 1E100 or abs(getfloat(v_second)) > 1E100: raise CannotProduceOperation # avoid infinities - if abs(v_second.getfloat()) < 1E-100: + if abs(getfloat(v_second)) < 1E-100: raise CannotProduceOperation # e.g. division by zero error self.put(builder, [v_first, v_second]) @@ -432,7 +475,7 @@ if not builder.floatvars: raise CannotProduceOperation box = r.choice(builder.floatvars) - if not (-sys.maxint-1 <= box.getfloat() <= sys.maxint): + if not (-sys.maxint-1 <= getfloat(box) <= sys.maxint): raise CannotProduceOperation # would give an overflow self.put(builder, [box]) @@ -440,8 +483,8 @@ def gen_guard(self, builder, r): v = builder.get_bool_var(r) op = ResOperation(self.opnum, [v]) - passing = ((self.opnum == rop.GUARD_TRUE and v.getint()) or - (self.opnum == rop.GUARD_FALSE and not v.getint())) + passing = ((self.opnum == rop.GUARD_TRUE and getint(v)) or + (self.opnum == rop.GUARD_FALSE and not getint(v))) return op, passing def produce_into(self, builder, r): @@ -459,8 +502,8 @@ raise CannotProduceOperation box = r.choice(builder.ptrvars)[0] op = ResOperation(self.opnum, [box]) - passing = ((self.opnum == rop.GUARD_NONNULL and box.getref_base()) or - (self.opnum == rop.GUARD_ISNULL and not box.getref_base())) + passing = ((self.opnum == rop.GUARD_NONNULL and getref_base(box)) or + (self.opnum == rop.GUARD_ISNULL and not getref_base(box))) return op, passing class GuardValueOperation(GuardOperation): @@ -470,14 +513,14 @@ other = r.choice(builder.intvars) else: if r.random() < 0.75: - value = v.getint() + value = getint(v) elif r.random() < 0.5: - value = v.getint() ^ 1 + value = getint(v) ^ 1 else: value = r.random_integer() other = ConstInt(value) op = ResOperation(self.opnum, [v, other]) - return op, (v.getint() == other.getint()) + return op, (getint(v) == getint(other)) # ____________________________________________________________ @@ -675,7 +718,7 @@ assert not hasattr(loop, '_targettoken') for i in range(position): op = loop.operations[i] - if (not op.has_no_side_effect() + if (not rop.has_no_side_effect(op.opnum) or op.type not in (INT, FLOAT)): position = i break # cannot move the LABEL later @@ -728,9 +771,9 @@ self.expected = {} for v in endvars: if v.type == INT: - self.expected[v] = v.getint() + self.expected[v] = getint(v) elif v.type == FLOAT: - self.expected[v] = v.getfloatstorage() + self.expected[v] = getfloatstorage(v) else: assert 0, v.type @@ -742,7 +785,7 @@ args = [] for box in self.startvars: if box not in self.loop.inputargs: - box = box.constbox() + box = constbox(box) args.append(box) self.cpu.compile_loop(self.loop.inputargs, [ResOperation(rop.JUMP, args, @@ -760,7 +803,7 @@ def clear_state(self): for v, S, fields in self.prebuilt_ptr_consts: - container = v.getref_base()._obj.container + container = getref_base(v)._obj.container for name, value in fields.items(): if isinstance(name, str): setattr(container, name, value) @@ -781,9 +824,9 @@ arguments = [] for box in self.loop.inputargs: if box.type == INT: - arguments.append(box.getint()) + arguments.append(getint(box)) elif box.type == FLOAT: - arguments.append(box.getfloatstorage()) + arguments.append(getfloatstorage(box)) else: assert 0, box.type deadframe = cpu.execute_token(self.runjitcelltoken(), *arguments) @@ -795,7 +838,7 @@ if v not in self.expected: assert v.getopnum() == rop.SAME_AS_I # special case assert isinstance(v.getarg(0), ConstInt) - self.expected[v] = v.getarg(0).getint() + self.expected[v] = getint(v.getarg(0)) if v.type == FLOAT: value = cpu.get_float_value(deadframe, i) else: @@ -807,7 +850,7 @@ ) exc = cpu.grab_exc_value(deadframe) if (self.guard_op is not None and - self.guard_op.is_guard_exception()): + rop.is_guard_exception(self.guard_op.getopnum())): if self.guard_op.getopnum() == rop.GUARD_NO_EXCEPTION: do_assert(exc, "grab_exc_value() should not be %r" % (exc,)) @@ -840,7 +883,7 @@ # generate the branch: a sequence of operations that ends in a FINISH subloop = DummyLoop([]) self.subloops.append(subloop) # keep around for debugging - if guard_op.is_guard_exception(): + if rop.is_guard_exception(guard_op.getopnum()): subloop.operations.append(exc_handling(guard_op)) bridge_builder = self.builder.fork(self.builder.cpu, subloop, op.getfailargs()[:]) @@ -876,9 +919,9 @@ args = [] for x in subset: if x.type == INT: - args.append(InputArgInt(x.getint())) + args.append(InputArgInt(getint(x))) elif x.type == FLOAT: - args.append(InputArgFloat(x.getfloatstorage())) + args.append(InputArgFloat(getfloatstorage(x))) else: assert 0, x.type rl = RandomLoop(self.builder.cpu, self.builder.fork, 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 @@ -358,11 +358,11 @@ assert self.assembler.mc._frame_size == DEFAULT_FRAME_BYTES self.rm.position = i self.xrm.position = i - if op.has_no_side_effect() and op not in self.longevity: + if rop.has_no_side_effect(op.opnum) and op not in self.longevity: i += 1 self.possibly_free_vars_for_op(op) continue - if not we_are_translated() and op.getopnum() == -127: + if not we_are_translated() and op.getopnum() == rop.FORCE_SPILL: self._consider_force_spill(op) else: oplist[op.getopnum()](self, op) 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 @@ -1585,7 +1585,6 @@ def _done_with_this_frame(self): # rare case: we only get there if the blackhole interps all returned # normally (in general we get a ContinueRunningNormally exception). - sd = self.builder.metainterp_sd kind = self._return_type if kind == 'v': raise jitexc.DoneWithThisFrameVoid() 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 @@ -27,12 +27,11 @@ class CompileData(object): memo = None + log_noopt = True def forget_optimization_info(self): - for arg in self.start_label.getarglist(): + for arg in self.trace.inputargs: arg.set_forwarded(None) - for op in self.operations: - op.set_forwarded(None) class LoopCompileData(CompileData): """ An object that accumulates all of the necessary info for @@ -40,15 +39,13 @@ This is the case of label() ops label() """ - def __init__(self, start_label, end_label, operations, - call_pure_results=None, enable_opts=None): - self.start_label = start_label - self.end_label = end_label + def __init__(self, trace, runtime_boxes, call_pure_results=None, + enable_opts=None): self.enable_opts = enable_opts - assert start_label.getopnum() == rop.LABEL - assert end_label.getopnum() == rop.LABEL - self.operations = operations + self.trace = trace self.call_pure_results = call_pure_results + assert runtime_boxes is not None + self.runtime_boxes = runtime_boxes def optimize(self, metainterp_sd, jitdriver_sd, optimizations, unroll): from rpython.jit.metainterp.optimizeopt.unroll import (UnrollOptimizer, @@ -56,23 +53,21 @@ if unroll: opt = UnrollOptimizer(metainterp_sd, jitdriver_sd, optimizations) - return opt.optimize_preamble(self.start_label, self.end_label, - self.operations, + return opt.optimize_preamble(self.trace, + self.runtime_boxes, self.call_pure_results, self.box_names_memo) else: opt = Optimizer(metainterp_sd, jitdriver_sd, optimizations) - return opt.propagate_all_forward(self.start_label.getarglist(), - self.operations, self.call_pure_results) + return opt.propagate_all_forward(self.trace, self.call_pure_results) class SimpleCompileData(CompileData): """ This represents label() ops jump with no extra info associated with the label """ - def __init__(self, start_label, operations, call_pure_results=None, + def __init__(self, trace, call_pure_results=None, enable_opts=None): - self.start_label = start_label - self.operations = operations + self.trace = trace self.call_pure_results = call_pure_results self.enable_opts = enable_opts @@ -81,17 +76,17 @@ #assert not unroll opt = Optimizer(metainterp_sd, jitdriver_sd, optimizations) - return opt.propagate_all_forward(self.start_label.getarglist(), - self.operations, self.call_pure_results) + return opt.propagate_all_forward(self.trace.get_iter(), + self.call_pure_results) class BridgeCompileData(CompileData): """ This represents ops() with a jump at the end that goes to some loop, we need to deal with virtual state and inlining of short preamble """ - def __init__(self, start_label, operations, call_pure_results=None, + def __init__(self, trace, runtime_boxes, call_pure_results=None, enable_opts=None, inline_short_preamble=False): - self.start_label = start_label - self.operations = operations + self.trace = trace + self.runtime_boxes = runtime_boxes self.call_pure_results = call_pure_results self.enable_opts = enable_opts self.inline_short_preamble = inline_short_preamble @@ -100,7 +95,7 @@ from rpython.jit.metainterp.optimizeopt.unroll import UnrollOptimizer opt = UnrollOptimizer(metainterp_sd, jitdriver_sd, optimizations) - return opt.optimize_bridge(self.start_label, self.operations, + return opt.optimize_bridge(self.trace, self.runtime_boxes, self.call_pure_results, self.inline_short_preamble, self.box_names_memo) @@ -109,12 +104,13 @@ """ This represents label() ops jump with extra info that's from the run of LoopCompileData. Jump goes to the same label """ - def __init__(self, start_label, end_jump, operations, state, + log_noopt = False + + def __init__(self, trace, celltoken, state, call_pure_results=None, enable_opts=None, inline_short_preamble=True): - self.start_label = start_label - self.end_jump = end_jump - self.operations = operations + self.trace = trace + self.celltoken = celltoken self.enable_opts = enable_opts self.state = state self.call_pure_results = call_pure_results @@ -125,9 +121,8 @@ assert unroll # we should not be here if it's disabled opt = UnrollOptimizer(metainterp_sd, jitdriver_sd, optimizations) - return opt.optimize_peeled_loop(self.start_label, self.end_jump, - self.operations, self.state, self.call_pure_results, - self.inline_short_preamble) + return opt.optimize_peeled_loop(self.trace, self.celltoken, self.state, + self.call_pure_results, self.inline_short_preamble) def show_procedures(metainterp_sd, procedure=None, error=None): # debugging @@ -208,23 +203,21 @@ # ____________________________________________________________ -def compile_simple_loop(metainterp, greenkey, start, inputargs, ops, jumpargs, - enable_opts): +def compile_simple_loop(metainterp, greenkey, trace, runtime_args, enable_opts, + cut_at): from rpython.jit.metainterp.optimizeopt import optimize_trace jitdriver_sd = metainterp.jitdriver_sd metainterp_sd = metainterp.staticdata jitcell_token = make_jitcell_token(jitdriver_sd) - label = ResOperation(rop.LABEL, inputargs[:], descr=jitcell_token) - jump_op = ResOperation(rop.JUMP, jumpargs[:], descr=jitcell_token) call_pure_results = metainterp.call_pure_results - data = SimpleCompileData(label, ops + [jump_op], - call_pure_results=call_pure_results, - enable_opts=enable_opts) + data = SimpleCompileData(trace, call_pure_results=call_pure_results, + enable_opts=enable_opts) try: loop_info, ops = optimize_trace(metainterp_sd, jitdriver_sd, data, metainterp.box_names_memo) except InvalidLoop: + trace.cut_at(cut_at) return None loop = create_empty_loop(metainterp) loop.original_jitcell_token = jitcell_token @@ -241,7 +234,7 @@ loop.check_consistency() jitcell_token.target_tokens = [target_token] send_loop_to_backend(greenkey, jitdriver_sd, metainterp_sd, loop, "loop", - inputargs, metainterp.box_names_memo) + runtime_args, metainterp.box_names_memo) record_loop_or_bridge(metainterp_sd, loop) return target_token @@ -255,6 +248,7 @@ metainterp_sd = metainterp.staticdata jitdriver_sd = metainterp.jitdriver_sd history = metainterp.history + trace = history.trace warmstate = jitdriver_sd.warmstate enable_opts = jitdriver_sd.warmstate.enable_opts @@ -264,16 +258,16 @@ enable_opts = enable_opts.copy() del enable_opts['unroll'] - ops = history.operations[start:] + jitcell_token = make_jitcell_token(jitdriver_sd) + cut_at = history.get_trace_position() + history.record(rop.JUMP, jumpargs, None, descr=jitcell_token) + if start != (0, 0, 0): + trace = trace.cut_trace_from(start, inputargs) if 'unroll' not in enable_opts or not metainterp.cpu.supports_guard_gc_type: - return compile_simple_loop(metainterp, greenkey, start, inputargs, ops, - jumpargs, enable_opts) - jitcell_token = make_jitcell_token(jitdriver_sd) - label = ResOperation(rop.LABEL, inputargs, - descr=TargetToken(jitcell_token)) - end_label = ResOperation(rop.LABEL, jumpargs, descr=jitcell_token) + return compile_simple_loop(metainterp, greenkey, trace, jumpargs, + enable_opts, cut_at) call_pure_results = metainterp.call_pure_results - preamble_data = LoopCompileData(label, end_label, ops, + preamble_data = LoopCompileData(trace, jumpargs, call_pure_results=call_pure_results, enable_opts=enable_opts) try: @@ -281,17 +275,15 @@ preamble_data, metainterp.box_names_memo) except InvalidLoop: + history.cut(cut_at) return None metainterp_sd = metainterp.staticdata jitdriver_sd = metainterp.jitdriver_sd - end_label = ResOperation(rop.LABEL, inputargs, - descr=jitcell_token) - jump_op = ResOperation(rop.JUMP, jumpargs, descr=jitcell_token) start_descr = TargetToken(jitcell_token, original_jitcell_token=jitcell_token) jitcell_token.target_tokens = [start_descr] - loop_data = UnrolledLoopData(end_label, jump_op, ops, start_state, + loop_data = UnrolledLoopData(trace, jitcell_token, start_state, call_pure_results=call_pure_results, enable_opts=enable_opts) try: @@ -299,11 +291,12 @@ loop_data, metainterp.box_names_memo) except InvalidLoop: + history.cut(cut_at) return None if ((warmstate.vec and jitdriver_sd.vec) or warmstate.vec_all): from rpython.jit.metainterp.optimizeopt.vector import optimize_vector - loop_info, loop_ops = optimize_vector(metainterp_sd, + loop_info, loop_ops = optimize_vector(trace, metainterp_sd, jitdriver_sd, warmstate, loop_info, loop_ops, jitcell_token) @@ -342,22 +335,20 @@ to the first operation. """ from rpython.jit.metainterp.optimizeopt import optimize_trace - from rpython.jit.metainterp.optimizeopt.optimizer import BasicLoopInfo - history = metainterp.history + trace = metainterp.history.trace.cut_trace_from(start, inputargs) metainterp_sd = metainterp.staticdata jitdriver_sd = metainterp.jitdriver_sd + history = metainterp.history loop_jitcell_token = metainterp.get_procedure_token(greenkey) assert loop_jitcell_token - end_label = ResOperation(rop.LABEL, inputargs[:], - descr=loop_jitcell_token) - jump_op = ResOperation(rop.JUMP, jumpargs[:], descr=loop_jitcell_token) + cut = history.get_trace_position() + history.record(rop.JUMP, jumpargs[:], None, descr=loop_jitcell_token) enable_opts = jitdriver_sd.warmstate.enable_opts - ops = history.operations[start:] call_pure_results = metainterp.call_pure_results - loop_data = UnrolledLoopData(end_label, jump_op, ops, start_state, + loop_data = UnrolledLoopData(trace, loop_jitcell_token, start_state, call_pure_results=call_pure_results, enable_opts=enable_opts) try: @@ -366,8 +357,9 @@ metainterp.box_names_memo) except InvalidLoop: # Fall back on jumping directly to preamble - jump_op = ResOperation(rop.JUMP, inputargs[:], descr=loop_jitcell_token) - loop_data = UnrolledLoopData(end_label, jump_op, [jump_op], start_state, + history.cut(cut) + history.record(rop.JUMP, jumpargs[:], None, descr=loop_jitcell_token) + loop_data = UnrolledLoopData(trace, loop_jitcell_token, start_state, call_pure_results=call_pure_results, enable_opts=enable_opts, inline_short_preamble=False) @@ -376,9 +368,13 @@ loop_data, metainterp.box_names_memo) except InvalidLoop: + history.cut(cut) return None - label_token = loop_info.label_op.getdescr() + label_op = loop_info.label_op + if label_op is None: + assert False, "unreachable code" # hint for some strange tests + label_token = label_op.getdescr() assert isinstance(label_token, TargetToken) if label_token.short_preamble: metainterp_sd.logger_ops.log_short_preamble([], @@ -445,13 +441,13 @@ box = inputargs[i] opnum = OpHelpers.getfield_for_descr(descr) emit_op(extra_ops, - ResOperation(opnum, [vable_box], descr)) + ResOperation(opnum, [vable_box], descr=descr)) box.set_forwarded(extra_ops[-1]) i += 1 arrayindex = 0 for descr in vinfo.array_field_descrs: arraylen = vinfo.get_array_length(vable, arrayindex) - arrayop = ResOperation(rop.GETFIELD_GC_R, [vable_box], descr) + arrayop = ResOperation(rop.GETFIELD_GC_R, [vable_box], descr=descr) emit_op(extra_ops, arrayop) arraydescr = vinfo.array_descrs[arrayindex] assert i + arraylen <= len(inputargs) @@ -1017,9 +1013,9 @@ metainterp_sd.stats.add_jitcell_token(jitcell_token) -def compile_trace(metainterp, resumekey): +def compile_trace(metainterp, resumekey, runtime_boxes): """Try to compile a new bridge leading from the beginning of the history - to some existing place. + to some existging place. """ from rpython.jit.metainterp.optimizeopt import optimize_trace @@ -1037,20 +1033,19 @@ else: inline_short_preamble = True inputargs = metainterp.history.inputargs[:] - operations = metainterp.history.operations - label = ResOperation(rop.LABEL, inputargs) + trace = metainterp.history.trace jitdriver_sd = metainterp.jitdriver_sd enable_opts = jitdriver_sd.warmstate.enable_opts call_pure_results = metainterp.call_pure_results - if operations[-1].getopnum() == rop.JUMP: - data = BridgeCompileData(label, operations[:], + if metainterp.history.ends_with_jump: + data = BridgeCompileData(trace, runtime_boxes, call_pure_results=call_pure_results, enable_opts=enable_opts, inline_short_preamble=inline_short_preamble) else: - data = SimpleCompileData(label, operations[:], + data = SimpleCompileData(trace, call_pure_results=call_pure_results, enable_opts=enable_opts) try: 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 @@ -9,7 +9,7 @@ from rpython.jit.metainterp.history import INT, REF, FLOAT, VOID, AbstractDescr from rpython.jit.metainterp.history import ConstInt, ConstFloat, ConstPtr from rpython.jit.metainterp import resoperation -from rpython.jit.metainterp.resoperation import rop +from rpython.jit.metainterp.resoperation import rop, opname from rpython.jit.metainterp.blackhole import BlackholeInterpreter, NULL from rpython.jit.codewriter import longlong @@ -314,7 +314,8 @@ def _make_execute_list(): execute_by_num_args = {} - for key, value in rop.__dict__.items(): + for key in opname.values(): + value = getattr(rop, key) if not key.startswith('_'): if (rop._FINAL_FIRST <= value <= rop._FINAL_LAST or rop._GUARD_FIRST <= value <= rop._GUARD_LAST): @@ -384,6 +385,11 @@ rop.CALL_MALLOC_NURSERY_VARSIZE_FRAME, rop.NURSERY_PTR_INCREMENT, rop.LABEL, + rop.ESCAPE_I, + rop.ESCAPE_N, + rop.ESCAPE_R, + rop.ESCAPE_F, + rop.FORCE_SPILL, rop.SAVE_EXC_CLASS, rop.SAVE_EXCEPTION, rop.RESTORE_EXCEPTION, diff --git a/rpython/jit/metainterp/graphpage.py b/rpython/jit/metainterp/graphpage.py --- a/rpython/jit/metainterp/graphpage.py +++ b/rpython/jit/metainterp/graphpage.py @@ -170,7 +170,8 @@ while True: op = operations[opindex] op_repr = op.repr(self.memo, graytext=True) - if op.getopnum() == rop.DEBUG_MERGE_POINT: + if (op.getopnum() == rop.DEBUG_MERGE_POINT and + self.metainterp_sd is not None): jd_sd = self.metainterp_sd.jitdrivers_sd[op.getarg(0).getint()] if jd_sd._get_printable_location_ptr: s = jd_sd.warmstate.get_location_str(op.getarglist()[3:]) diff --git a/rpython/jit/metainterp/heapcache.py b/rpython/jit/metainterp/heapcache.py --- a/rpython/jit/metainterp/heapcache.py +++ b/rpython/jit/metainterp/heapcache.py @@ -1,33 +1,59 @@ -from rpython.jit.metainterp.history import ConstInt +from rpython.jit.metainterp.history import Const, ConstInt +from rpython.jit.metainterp.history import FrontendOp, RefFrontendOp from rpython.jit.metainterp.resoperation import rop, OpHelpers +from rpython.jit.metainterp.executor import constant_from_op +from rpython.rlib.rarithmetic import r_uint32, r_uint +from rpython.rlib.objectmodel import always_inline -class HeapCacheValue(object): - def __init__(self, box): - self.box = box - self.likely_virtual = False - self.reset_keep_likely_virtual() +""" A big note: we don't do heap caches on Consts, because it used +to be done with the identity of the Const instance. This gives very wonky +results at best, so we decided to not do it at all. Can be fixed with +interning of Consts (already done on trace anyway) +""" - def reset_keep_likely_virtual(self): - self.known_class = False - self.known_nullity = False - # did we see the allocation during tracing? - self.seen_allocation = False - self.is_unescaped = False - self.nonstandard_virtualizable = False - self.length = None - self.dependencies = None +# RefFrontendOp._heapc_flags: +HF_LIKELY_VIRTUAL = 0x01 +HF_KNOWN_CLASS = 0x02 +HF_KNOWN_NULLITY = 0x04 +HF_SEEN_ALLOCATION = 0x08 # did we see the allocation during tracing? +HF_IS_UNESCAPED = 0x10 +HF_NONSTD_VABLE = 0x20 - def __repr__(self): - return 'HeapCacheValue(%s)' % (self.box, ) +_HF_VERSION_INC = 0x40 # must be last +_HF_VERSION_MAX = r_uint(2 ** 32 - _HF_VERSION_INC) + +@always_inline +def add_flags(ref_frontend_op, flags): + f = ref_frontend_op._get_heapc_flags() + f |= r_uint(flags) + ref_frontend_op._set_heapc_flags(f) + +@always_inline +def remove_flags(ref_frontend_op, flags): + f = ref_frontend_op._get_heapc_flags() + f &= r_uint(~flags) + ref_frontend_op._set_heapc_flags(f) + +@always_inline +def test_flags(ref_frontend_op, flags): + f = ref_frontend_op._get_heapc_flags() + return bool(f & r_uint(flags)) + +def maybe_replace_with_const(box): + if not isinstance(box, Const) and box.is_replaced_with_const(): + return constant_from_op(box) + else: + return box class CacheEntry(object): - def __init__(self): - # both are {from_value: to_value} dicts + def __init__(self, heapcache): + # both are {from_ref_box: to_field_box} dicts # the first is for boxes where we did not see the allocation, the # second for anything else. the reason that distinction makes sense is # because if we saw the allocation, we know it cannot alias with # anything else where we saw the allocation. + self.heapcache = heapcache self.cache_anything = {} self.cache_seen_allocation = {} @@ -36,112 +62,137 @@ self.cache_seen_allocation.clear() self.cache_anything.clear() - def _getdict(self, value): - if value.seen_allocation: + def _seen_alloc(self, ref_box): + if not isinstance(ref_box, RefFrontendOp): + return False + return self.heapcache._check_flag(ref_box, HF_SEEN_ALLOCATION) + + def _getdict(self, seen_alloc): + if seen_alloc: return self.cache_seen_allocation else: return self.cache_anything - def do_write_with_aliasing(self, value, fieldvalue): - self._clear_cache_on_write(value.seen_allocation) - self._getdict(value)[value] = fieldvalue + def do_write_with_aliasing(self, ref_box, fieldbox): + seen_alloc = self._seen_alloc(ref_box) + self._clear_cache_on_write(seen_alloc) + self._getdict(seen_alloc)[ref_box] = fieldbox - def read(self, value): - return self._getdict(value).get(value, None) + def read(self, ref_box): + dict = self._getdict(self._seen_alloc(ref_box)) + try: + res_box = dict[ref_box] + except KeyError: + return None + return maybe_replace_with_const(res_box) - def read_now_known(self, value, fieldvalue): - self._getdict(value)[value] = fieldvalue + def read_now_known(self, ref_box, fieldbox): + self._getdict(self._seen_alloc(ref_box))[ref_box] = fieldbox def invalidate_unescaped(self): self._invalidate_unescaped(self.cache_anything) self._invalidate_unescaped(self.cache_seen_allocation) def _invalidate_unescaped(self, d): - for value in d.keys(): - if not value.is_unescaped: - del d[value] + for ref_box in d.keys(): + if not self.heapcache.is_unescaped(ref_box): + del d[ref_box] class FieldUpdater(object): - def __init__(self, heapcache, value, cache, fieldvalue): - self.heapcache = heapcache - self.value = value + def __init__(self, ref_box, cache, fieldbox): + self.ref_box = ref_box self.cache = cache - if fieldvalue is not None: - self.currfieldbox = fieldvalue.box - else: - self.currfieldbox = None + self.currfieldbox = fieldbox # <= read directly from pyjitpl.py def getfield_now_known(self, fieldbox): - fieldvalue = self.heapcache.getvalue(fieldbox) - self.cache.read_now_known(self.value, fieldvalue) + self.cache.read_now_known(self.ref_box, fieldbox) def setfield(self, fieldbox): - fieldvalue = self.heapcache.getvalue(fieldbox) - self.cache.do_write_with_aliasing(self.value, fieldvalue) + self.cache.do_write_with_aliasing(self.ref_box, fieldbox) + +class DummyFieldUpdater(FieldUpdater): + def __init__(self): + self.currfieldbox = None + + def getfield_now_known(self, fieldbox): + pass + + def setfield(self, fieldbox): + pass + +dummy_field_updater = DummyFieldUpdater() class HeapCache(object): def __init__(self): + # Works with flags stored on RefFrontendOp._heapc_flags. + # There are two ways to do a global resetting of these flags: + # reset() and reset_keep_likely_virtual(). The basic idea is + # to use a version number in each RefFrontendOp, and in order + # to reset the flags globally, we increment the global version + # number in this class. Then when we read '_heapc_flags' we + # also check if the associated version number is up-to-date + # or not. More precisely, we have two global version numbers + # here: 'head_version' and 'likely_virtual_version'. Normally + # we use 'head_version'. For is_likely_virtual() though, we + # use the other, older version number. + self.head_version = r_uint(0) + self.likely_virtual_version = r_uint(0) self.reset() def reset(self): - # maps boxes to values - self.values = {} - # store the boxes that contain newly allocated objects, this maps the - # boxes to a bool, the bool indicates whether or not the object has - # escaped the trace or not (True means the box never escaped, False - # means it did escape), its presences in the mapping shows that it was - # allocated inside the trace - #if trace_branch: - #self.new_boxes = {} - # pass - #else: - #for box in self.new_boxes: - # self.new_boxes[box] = False - # pass - #if reset_virtuals: - # self.likely_virtuals = {} # only for jit.isvirtual() - # Tracks which boxes should be marked as escaped when the key box - # escapes. - #self.dependencies = {} - + # Global reset of all flags. Update both version numbers so + # that any access to '_heapc_flags' will be marked as outdated. + assert self.head_version < _HF_VERSION_MAX + self.head_version += _HF_VERSION_INC + self.likely_virtual_version = self.head_version + # # heap cache # maps descrs to CacheEntry self.heap_cache = {} # heap array cache - # maps descrs to {index: {from_value: to_value}} dicts + # maps descrs to {index: CacheEntry} dicts self.heap_array_cache = {} def reset_keep_likely_virtuals(self): - for value in self.values.itervalues(): - value.reset_keep_likely_virtual() + # Update only 'head_version', but 'likely_virtual_version' remains + # at its older value. + assert self.head_version < _HF_VERSION_MAX + self.head_version += _HF_VERSION_INC self.heap_cache = {} self.heap_array_cache = {} - def getvalue(self, box, create=True): - value = self.values.get(box, None) - if not value and create: - value = self.values[box] = HeapCacheValue(box) - return value + @always_inline + def test_head_version(self, ref_frontend_op): + return ref_frontend_op._get_heapc_flags() >= self.head_version - def getvalues(self, boxes): - return [self.getvalue(box) for box in boxes] + @always_inline + def test_likely_virtual_version(self, ref_frontend_op): + return ref_frontend_op._get_heapc_flags() >= self.likely_virtual_version + + def update_version(self, ref_frontend_op): + """Ensure the version of 'ref_frontend_op' is current. If not, + it will update 'ref_frontend_op' (removing most flags currently set). + """ + if not self.test_head_version(ref_frontend_op): + f = self.head_version + if (self.test_likely_virtual_version(ref_frontend_op) and + test_flags(ref_frontend_op, HF_LIKELY_VIRTUAL)): + f |= HF_LIKELY_VIRTUAL + ref_frontend_op._set_heapc_flags(f) + ref_frontend_op._heapc_deps = None def invalidate_caches(self, opnum, descr, argboxes): self.mark_escaped(opnum, descr, argboxes) self.clear_caches(opnum, descr, argboxes) def _escape_from_write(self, box, fieldbox): - value = self.getvalue(box, create=False) - fieldvalue = self.getvalue(fieldbox, create=False) - if (value is not None and value.is_unescaped and - fieldvalue is not None and fieldvalue.is_unescaped): - if value.dependencies is None: - value.dependencies = [] - value.dependencies.append(fieldvalue) - elif fieldvalue is not None: - self._escape(fieldvalue) + if self.is_unescaped(box) and self.is_unescaped(fieldbox): + deps = self._get_deps(box) + deps.append(fieldbox) + elif fieldbox is not None: + self._escape_box(fieldbox) def mark_escaped(self, opnum, descr, argboxes): if opnum == rop.SETFIELD_GC: @@ -176,19 +227,20 @@ self._escape_box(box) def _escape_box(self, box): - value = self.getvalue(box, create=False) - if not value: - return - self._escape(value) - - def _escape(self, value): - value.is_unescaped = False - value.likely_virtual = False - deps = value.dependencies - value.dependencies = None - if deps is not None: - for dep in deps: - self._escape(dep) + if isinstance(box, RefFrontendOp): + remove_flags(box, HF_LIKELY_VIRTUAL | HF_IS_UNESCAPED) + deps = box._heapc_deps + if deps is not None: + if not self.test_head_version(box): + box._heapc_deps = None + else: + # 'deps[0]' is abused to store the array length, keep it + if deps[0] is None: + box._heapc_deps = None + else: + box._heapc_deps = [deps[0]] + for i in range(1, len(deps)): + self._escape_box(deps[i]) def clear_caches(self, opnum, descr, argboxes): if (opnum == rop.SETFIELD_GC or @@ -241,7 +293,8 @@ self.reset_keep_likely_virtuals() def _clear_caches_arraycopy(self, opnum, desrc, argboxes, effectinfo): - seen_allocation_of_target = self.getvalue(argboxes[2]).seen_allocation + seen_allocation_of_target = self._check_flag( + argboxes[2], HF_SEEN_ALLOCATION) if ( isinstance(argboxes[3], ConstInt) and isinstance(argboxes[4], ConstInt) and @@ -285,74 +338,82 @@ return self.reset_keep_likely_virtuals() + def _get_deps(self, box): + if not isinstance(box, RefFrontendOp): + return None + self.update_version(box) + if box._heapc_deps is None: + box._heapc_deps = [None] + return box._heapc_deps + + def _check_flag(self, box, flag): + return (isinstance(box, RefFrontendOp) and + self.test_head_version(box) and + test_flags(box, flag)) + + def _set_flag(self, box, flag): + assert isinstance(box, RefFrontendOp) + self.update_version(box) + add_flags(box, flag) + def is_class_known(self, box): - value = self.getvalue(box, create=False) - if value: - return value.known_class - return False + return self._check_flag(box, HF_KNOWN_CLASS) def class_now_known(self, box): - self.getvalue(box).known_class = True + if isinstance(box, Const): + return + self._set_flag(box, HF_KNOWN_CLASS) def is_nullity_known(self, box): - value = self.getvalue(box, create=False) - if value: - return value.known_nullity - return False + if isinstance(box, Const): + return bool(box.getref_base()) + return self._check_flag(box, HF_KNOWN_NULLITY) def nullity_now_known(self, box): - self.getvalue(box).known_nullity = True + if isinstance(box, Const): + return + self._set_flag(box, HF_KNOWN_NULLITY) def is_nonstandard_virtualizable(self, box): - value = self.getvalue(box, create=False) - if value: - return value.nonstandard_virtualizable - return False + return self._check_flag(box, HF_NONSTD_VABLE) def nonstandard_virtualizables_now_known(self, box): - self.getvalue(box).nonstandard_virtualizable = True + self._set_flag(box, HF_NONSTD_VABLE) def is_unescaped(self, box): - value = self.getvalue(box, create=False) - if value: - return value.is_unescaped - return False + return self._check_flag(box, HF_IS_UNESCAPED) def is_likely_virtual(self, box): - value = self.getvalue(box, create=False) - if value: - return value.likely_virtual - return False + # note: this is different from _check_flag() + return (isinstance(box, RefFrontendOp) and + self.test_likely_virtual_version(box) and + test_flags(box, HF_LIKELY_VIRTUAL)) def new(self, box): - value = self.getvalue(box) - value.is_unescaped = True - value.likely_virtual = True - value.seen_allocation = True + assert isinstance(box, RefFrontendOp) + self.update_version(box) + add_flags(box, HF_LIKELY_VIRTUAL | HF_SEEN_ALLOCATION | HF_IS_UNESCAPED) def new_array(self, box, lengthbox): self.new(box) self.arraylen_now_known(box, lengthbox) def getfield(self, box, descr): - value = self.getvalue(box, create=False) - if value: - cache = self.heap_cache.get(descr, None) - if cache: - tovalue = cache.read(value) - if tovalue: - return tovalue.box + cache = self.heap_cache.get(descr, None) + if cache: + return cache.read(box) return None def get_field_updater(self, box, descr): - value = self.getvalue(box) + if not isinstance(box, RefFrontendOp): + return dummy_field_updater cache = self.heap_cache.get(descr, None) if cache is None: - cache = self.heap_cache[descr] = CacheEntry() - fieldvalue = None + cache = self.heap_cache[descr] = CacheEntry(self) + fieldbox = None else: - fieldvalue = cache.read(value) - return FieldUpdater(self, value, cache, fieldvalue) + fieldbox = cache.read(box) + return FieldUpdater(box, cache, fieldbox) def getfield_now_known(self, box, descr, fieldbox): upd = self.get_field_updater(box, descr) @@ -365,17 +426,12 @@ def getarrayitem(self, box, indexbox, descr): if not isinstance(indexbox, ConstInt): return None - value = self.getvalue(box, create=False) - if value is None: - return None index = indexbox.getint() cache = self.heap_array_cache.get(descr, None) if cache: indexcache = cache.get(index, None) if indexcache is not None: - resvalue = indexcache.read(value) - if resvalue: - return resvalue.box + return indexcache.read(box) return None def _get_or_make_array_cache_entry(self, indexbox, descr): @@ -385,16 +441,14 @@ cache = self.heap_array_cache.setdefault(descr, {}) indexcache = cache.get(index, None) if indexcache is None: - cache[index] = indexcache = CacheEntry() + cache[index] = indexcache = CacheEntry(self) return indexcache def getarrayitem_now_known(self, box, indexbox, fieldbox, descr): - value = self.getvalue(box) - fieldvalue = self.getvalue(fieldbox) indexcache = self._get_or_make_array_cache_entry(indexbox, descr) if indexcache: - indexcache.read_now_known(value, fieldvalue) + indexcache.read_now_known(box, fieldbox) def setarrayitem(self, box, indexbox, fieldbox, descr): if not isinstance(indexbox, ConstInt): @@ -402,25 +456,31 @@ if cache is not None: cache.clear() return - value = self.getvalue(box) - fieldvalue = self.getvalue(fieldbox) indexcache = self._get_or_make_array_cache_entry(indexbox, descr) if indexcache: - indexcache.do_write_with_aliasing(value, fieldvalue) + indexcache.do_write_with_aliasing(box, fieldbox) def arraylen(self, box): - value = self.getvalue(box, create=False) - if value and value.length: - return value.length.box + if (isinstance(box, RefFrontendOp) and + self.test_head_version(box) and + box._heapc_deps is not None): + res_box = box._heapc_deps[0] + if res_box is not None: + return maybe_replace_with_const(res_box) return None def arraylen_now_known(self, box, lengthbox): - value = self.getvalue(box) - value.length = self.getvalue(lengthbox) + # we store in '_heapc_deps' a list of boxes: the *first* box is + # the known length or None, and the remaining boxes are the + # regular dependencies. + if isinstance(box, Const): + return + deps = self._get_deps(box) + assert deps is not None + deps[0] = lengthbox def replace_box(self, oldbox, newbox): - value = self.getvalue(oldbox, create=False) - if value is None: - return - value.box = newbox - self.values[newbox] = value + # here, only for replacing a box with a const + if isinstance(oldbox, FrontendOp) and isinstance(newbox, Const): + assert newbox.same_constant(constant_from_op(oldbox)) + oldbox.set_replaced_with_const() 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 @@ -3,12 +3,17 @@ from rpython.rlib.objectmodel import we_are_translated, Symbolic from rpython.rlib.objectmodel import compute_unique_id, specialize from rpython.rlib.rarithmetic import r_int64, is_valid_int +from rpython.rlib.rarithmetic import LONG_BIT, intmask, r_uint +from rpython.rlib.jit import Counters from rpython.conftest import option -from rpython.jit.metainterp.resoperation import ResOperation, rop, AbstractValue +from rpython.jit.metainterp.resoperation import ResOperation, rop,\ + AbstractValue, oparity, AbstractResOp, IntOp, RefOp, FloatOp,\ + opclasses from rpython.jit.codewriter import heaptracker, longlong import weakref +from rpython.jit.metainterp import jitexc # ____________________________________________________________ @@ -22,6 +27,15 @@ FAILARGS_LIMIT = 1000 +class SwitchToBlackhole(jitexc.JitException): + def __init__(self, reason, raising_exception=False): + self.reason = reason + self.raising_exception = raising_exception + # ^^^ must be set to True if the SwitchToBlackhole is raised at a + # point where the exception on metainterp.last_exc_value + # is supposed to be raised. The default False means that it + # should just be copied into the blackhole interp, but not raised. + def getkind(TYPE, supports_floats=True, supports_longlong=True, supports_singlefloats=True): @@ -72,57 +86,10 @@ ) #compute_unique_id(box)) -class XxxAbstractValue(object): - __slots__ = () - - def getint(self): - raise NotImplementedError - - def getfloatstorage(self): - raise NotImplementedError - - def getfloat(self): - return longlong.getrealfloat(self.getfloatstorage()) - - def getref_base(self): - raise NotImplementedError - - def getref(self, TYPE): - raise NotImplementedError - getref._annspecialcase_ = 'specialize:arg(1)' - - def constbox(self): - raise NotImplementedError - - def getaddr(self): - "Only for raw addresses (BoxInt & ConstInt), not for GC addresses" - raise NotImplementedError - - def sort_key(self): - raise NotImplementedError - - def nonnull(self): - raise NotImplementedError - - def repr_rpython(self): - return '%s' % self - - def _get_str(self): - raise NotImplementedError - - def same_box(self, other): - return self is other - - def same_shape(self, other): - # only structured containers can compare their shape (vector box) - return True - - def getaccum(self): - return None - class AbstractDescr(AbstractValue): - __slots__ = () + __slots__ = ('descr_index',) llopaque = True + descr_index = -1 def repr_of_descr(self): return '%r' % (self,) @@ -204,7 +171,7 @@ class Const(AbstractValue): - __slots__ = () + _attrs_ = () @staticmethod def _new(x): @@ -638,43 +605,174 @@ # ____________________________________________________________ +FO_REPLACED_WITH_CONST = r_uint(1) +FO_POSITION_SHIFT = 1 +FO_POSITION_MASK = r_uint(0xFFFFFFFE) + + +class FrontendOp(AbstractResOp): + type = 'v' + _attrs_ = ('position_and_flags',) + + def __init__(self, pos): + # p is the 32-bit position shifted left by one (might be negative, + # but casted to the 32-bit UINT type) + p = rffi.cast(rffi.UINT, pos << FO_POSITION_SHIFT) + self.position_and_flags = r_uint(p) # zero-extended to a full word + + def get_position(self): + # p is the signed 32-bit position, from self.position_and_flags + p = rffi.cast(rffi.INT, self.position_and_flags) + return intmask(p) >> FO_POSITION_SHIFT + + def set_position(self, new_pos): + assert new_pos >= 0 + self.position_and_flags &= ~FO_POSITION_MASK + self.position_and_flags |= r_uint(new_pos << FO_POSITION_SHIFT) + + def is_replaced_with_const(self): + return bool(self.position_and_flags & FO_REPLACED_WITH_CONST) + + def set_replaced_with_const(self): + self.position_and_flags |= FO_REPLACED_WITH_CONST + + def __repr__(self): + return '%s(0x%x)' % (self.__class__.__name__, self.position_and_flags) + +class IntFrontendOp(IntOp, FrontendOp): + _attrs_ = ('position_and_flags', '_resint') + + def copy_value_from(self, other): + self._resint = other.getint() + +class FloatFrontendOp(FloatOp, FrontendOp): + _attrs_ = ('position_and_flags', '_resfloat') + + def copy_value_from(self, other): + self._resfloat = other.getfloatstorage() + +class RefFrontendOp(RefOp, FrontendOp): + _attrs_ = ('position_and_flags', '_resref', '_heapc_deps') + if LONG_BIT == 32: + _attrs_ += ('_heapc_flags',) # on 64 bit, this gets stored into the + _heapc_flags = r_uint(0) # high 32 bits of 'position_and_flags' + _heapc_deps = None + + def copy_value_from(self, other): + self._resref = other.getref_base() + + if LONG_BIT == 32: + def _get_heapc_flags(self): + return self._heapc_flags + def _set_heapc_flags(self, value): + self._heapc_flags = value + else: + def _get_heapc_flags(self): + return self.position_and_flags >> 32 + def _set_heapc_flags(self, value): + self.position_and_flags = ( + (self.position_and_flags & 0xFFFFFFFF) | + (value << 32)) + + class History(object): + ends_with_jump = False + trace = None + def __init__(self): - self.inputargs = None - self.operations = [] + self.descr_cache = {} + self.descrs = {} + self.consts = [] + self._cache = [] + + def set_inputargs(self, inpargs, metainterp_sd): + from rpython.jit.metainterp.opencoder import Trace + + self.trace = Trace(inpargs, metainterp_sd) + self.inputargs = inpargs + if self._cache: + # hack to record the ops *after* we know our inputargs + for (opnum, argboxes, op, descr) in self._cache: + pos = self.trace.record_op(opnum, argboxes, descr) + op.set_position(pos) + self._cache = None + + def length(self): + return self.trace._count - len(self.trace.inputargs) + + def get_trace_position(self): + return self.trace.cut_point() + + def cut(self, cut_at): + self.trace.cut_at(cut_at) + + def any_operation(self): + return self.trace._count > self.trace._start + + @specialize.argtype(2) + def set_op_value(self, op, value): + if value is None: + return + elif isinstance(value, bool): + op.setint(int(value)) + elif lltype.typeOf(value) == lltype.Signed: + op.setint(value) + elif lltype.typeOf(value) is longlong.FLOATSTORAGE: + op.setfloatstorage(value) + else: + assert lltype.typeOf(value) == llmemory.GCREF + op.setref_base(value) + + def _record_op(self, opnum, argboxes, descr=None): + from rpython.jit.metainterp.opencoder import FrontendTagOverflow + + try: + return self.trace.record_op(opnum, argboxes, descr) + except FrontendTagOverflow: + # note that with the default settings this one should not + # happen - however if we hit that case, we don't get + # anything disabled + raise SwitchToBlackhole(Counters.ABORT_TOO_LONG) @specialize.argtype(3) def record(self, opnum, argboxes, value, descr=None): - op = ResOperation(opnum, argboxes, descr) + if self.trace is None: + pos = 2**14 - 1 + else: + pos = self._record_op(opnum, argboxes, descr) if value is None: - assert op.type == 'v' + op = FrontendOp(pos) elif isinstance(value, bool): - assert op.type == 'i' - op.setint(int(value)) + op = IntFrontendOp(pos) elif lltype.typeOf(value) == lltype.Signed: - assert op.type == 'i' - op.setint(value) + op = IntFrontendOp(pos) elif lltype.typeOf(value) is longlong.FLOATSTORAGE: - assert op.type == 'f' - op.setfloatstorage(value) + op = FloatFrontendOp(pos) else: - assert lltype.typeOf(value) == llmemory.GCREF - assert op.type == 'r' - op.setref_base(value) - self.operations.append(op) + op = RefFrontendOp(pos) + if self.trace is None: + self._cache.append((opnum, argboxes, op, descr)) + self.set_op_value(op, value) return op + def record_nospec(self, opnum, argboxes, descr=None): + tp = opclasses[opnum].type + pos = self._record_op(opnum, argboxes, descr) + if tp == 'v': + return FrontendOp(pos) + elif tp == 'i': + return IntFrontendOp(pos) + elif tp == 'f': + return FloatFrontendOp(pos) + assert tp == 'r' + return RefFrontendOp(pos) + def record_default_val(self, opnum, argboxes, descr=None): - op = ResOperation(opnum, argboxes, descr) - assert op.is_same_as() + assert rop.is_same_as(opnum) + op = self.record_nospec(opnum, argboxes, descr) op.copy_value_from(argboxes[0]) - self.operations.append(op) return op - def substitute_operation(self, position, opnum, argboxes, descr=None): - resbox = self.operations[position].result - op = ResOperation(opnum, argboxes, resbox, descr) - self.operations[position] = op # ____________________________________________________________ @@ -720,15 +818,15 @@ compiled_count = 0 enter_count = 0 aborted_count = 0 - operations = None - def __init__(self): + def __init__(self, metainterp_sd): self.loops = [] self.locations = [] self.aborted_keys = [] self.invalidated_token_numbers = set() # <- not RPython self.jitcell_token_wrefs = [] self.jitcell_dicts = [] # <- not RPython + self.metainterp_sd = metainterp_sd def clear(self): del self.loops[:] @@ -747,7 +845,7 @@ self.jitcell_token_wrefs.append(weakref.ref(token)) def set_history(self, history): - self.operations = history.operations + self.history = history def aborted(self): self.aborted_count += 1 @@ -784,7 +882,9 @@ def check_history(self, expected=None, **check): insns = {} - for op in self.operations: + t = self.history.trace.get_iter() + while not t.done(): + op = t.next() opname = op.getopname() insns[opname] = insns.get(opname, 0) + 1 if expected is not None: diff --git a/rpython/jit/metainterp/logger.py b/rpython/jit/metainterp/logger.py --- a/rpython/jit/metainterp/logger.py +++ b/rpython/jit/metainterp/logger.py @@ -12,6 +12,19 @@ self.metainterp_sd = metainterp_sd self.guard_number = guard_number + def log_loop_from_trace(self, trace, memo): + if not have_debug_prints(): + return + inputargs, ops = self._unpack_trace(trace) + self.log_loop(inputargs, ops, memo=memo) + + def _unpack_trace(self, trace): + ops = [] + i = trace.get_iter() + while not i.done(): + ops.append(i.next()) + return i.inputargs, ops + def log_loop(self, inputargs, operations, number=0, type=None, ops_offset=None, name='', memo=None): if type is None: @@ -82,8 +95,11 @@ debug_stop("jit-log-short-preamble") return logops - def log_abort_loop(self, inputargs, operations, memo=None): + def log_abort_loop(self, trace, memo=None): debug_start("jit-abort-log") + if not have_debug_prints(): + return + inputargs, operations = self._unpack_trace(trace) logops = self._log_operations(inputargs, operations, ops_offset=None, memo=memo) debug_stop("jit-abort-log") _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit