def emit_gc_load_or_indexed(self, op, ptr_box, index_box, itemsize, factor, offset, sign, type='i'): factor, offset, index_box = \ self._emit_mul_if_factor_offset_not_supported(index_box, factor, offset) # if sign: # encode signed into the itemsize value itemsize = -itemsize # optype = type if op is not None: optype = op.type if index_box is None: args = [ptr_box, ConstInt(offset), ConstInt(itemsize)] newload = ResOperation(OpHelpers.get_gc_load(optype), args) else: args = [ptr_box, index_box, ConstInt(factor), ConstInt(offset), ConstInt(itemsize)] newload = ResOperation(OpHelpers.get_gc_load_indexed(optype), args) if op is None: self.emit_op(newload) else: self.replace_op_with(op, newload) return newload
def expand(state, pack, args, arg, index): """ Expand a value into a vector box. useful for arith metic of one vector with a scalar (either constant/varialbe) """ left = pack.leftmost() box_type = arg.type expanded_map = state.expanded_map ops = state.invariant_oplist variables = state.invariant_vector_vars if not arg.is_constant() and arg not in state.inputargs: # cannot be created before the loop, expand inline ops = state.oplist variables = None for i, node in enumerate(pack.operations): op = node.getoperation() if not arg.same_box(op.getarg(index)): break i += 1 else: # note that heterogenous nodes are not yet tracked vecop = state.find_expanded([arg]) if vecop: args[index] = vecop return vecop left = pack.leftmost() vecinfo = forwarded_vecinfo(left) vecop = OpHelpers.create_vec_expand(arg, vecinfo.bytesize, vecinfo.signed, pack.numops()) ops.append(vecop) if variables is not None: variables.append(vecop) state.expand([arg], vecop) args[index] = vecop return vecop # quick search if it has already been expanded expandargs = [op.getoperation().getarg(index) for op in pack.operations] vecop = state.find_expanded(expandargs) if vecop: args[index] = vecop return vecop arg_vecinfo = forwarded_vecinfo(arg) vecop = OpHelpers.create_vec(arg.type, arg_vecinfo.bytesize, arg_vecinfo.signed, pack.opnum()) ops.append(vecop) for i,node in enumerate(pack.operations): op = node.getoperation() arg = op.getarg(index) arguments = [vecop, arg, ConstInt(i), ConstInt(1)] vecinfo = forwarded_vecinfo(vecop) vecop = OpHelpers.create_vec_pack(arg.type, arguments, vecinfo.bytesize, vecinfo.signed, vecinfo.count+1) ops.append(vecop) state.expand(expandargs, vecop) if variables is not None: variables.append(vecop) args[index] = vecop
def clear_caches(self, opnum, descr, argboxes): if (opnum == rop.SETFIELD_GC or opnum == rop.SETARRAYITEM_GC or opnum == rop.SETFIELD_RAW or opnum == rop.SETARRAYITEM_RAW or opnum == rop.SETINTERIORFIELD_GC or opnum == rop.COPYSTRCONTENT or opnum == rop.COPYUNICODECONTENT or opnum == rop.STRSETITEM or opnum == rop.UNICODESETITEM or opnum == rop.SETFIELD_RAW or opnum == rop.SETARRAYITEM_RAW or opnum == rop.SETINTERIORFIELD_RAW or opnum == rop.RECORD_EXACT_CLASS or opnum == rop.RAW_STORE or opnum == rop.ASSERT_NOT_NONE): return if (rop._OVF_FIRST <= opnum <= rop._OVF_LAST or rop._NOSIDEEFFECT_FIRST <= opnum <= rop._NOSIDEEFFECT_LAST or rop._GUARD_FIRST <= opnum <= rop._GUARD_LAST): return self.need_guard_not_invalidated = True # can do better, but good start if (OpHelpers.is_plain_call(opnum) or OpHelpers.is_call_loopinvariant(opnum) or OpHelpers.is_cond_call_value(opnum) or opnum == rop.COND_CALL): effectinfo = descr.get_extra_info() ef = effectinfo.extraeffect if (ef == effectinfo.EF_LOOPINVARIANT or ef == effectinfo.EF_ELIDABLE_CANNOT_RAISE or ef == effectinfo.EF_ELIDABLE_OR_MEMORYERROR or ef == effectinfo.EF_ELIDABLE_CAN_RAISE): return # A special case for ll_arraycopy, because it is so common, and its # effects are so well defined. elif effectinfo.oopspecindex == effectinfo.OS_ARRAYCOPY: self._clear_caches_arraycopy(opnum, descr, argboxes, effectinfo) return elif effectinfo.oopspecindex == effectinfo.OS_ARRAYMOVE: self._clear_caches_arraymove(opnum, descr, argboxes, effectinfo) return else: # Only invalidate things that are escaped # XXX can do better, only do it for the descrs in the effectinfo for descr, cache in self.heap_cache.iteritems(): cache.invalidate_unescaped() for descr, indices in self.heap_array_cache.iteritems(): for cache in indices.itervalues(): cache.invalidate_unescaped() return # XXX not completely sure, but I *think* it is needed to reset() the # state at least in the 'CALL_*' operations that release the GIL. We # tried to do only the kind of resetting done by the two loops just # above, but hit an assertion in "pypy test_multiprocessing.py". self.reset_keep_likely_virtuals()
def clear_caches(self, opnum, descr, argboxes): if (opnum == rop.SETFIELD_GC or opnum == rop.SETARRAYITEM_GC or opnum == rop.SETFIELD_RAW or opnum == rop.SETARRAYITEM_RAW or opnum == rop.SETINTERIORFIELD_GC or opnum == rop.COPYSTRCONTENT or opnum == rop.COPYUNICODECONTENT or opnum == rop.STRSETITEM or opnum == rop.UNICODESETITEM or opnum == rop.SETFIELD_RAW or opnum == rop.SETARRAYITEM_RAW or opnum == rop.SETINTERIORFIELD_RAW or opnum == rop.RAW_STORE): return if (rop._OVF_FIRST <= opnum <= rop._OVF_LAST or rop._NOSIDEEFFECT_FIRST <= opnum <= rop._NOSIDEEFFECT_LAST or rop._GUARD_FIRST <= opnum <= rop._GUARD_LAST): return if (OpHelpers.is_plain_call(opnum) or OpHelpers.is_call_loopinvariant(opnum) or OpHelpers.is_cond_call_value(opnum) or opnum == rop.COND_CALL): effectinfo = descr.get_extra_info() ef = effectinfo.extraeffect if (ef == effectinfo.EF_LOOPINVARIANT or ef == effectinfo.EF_ELIDABLE_CANNOT_RAISE or ef == effectinfo.EF_ELIDABLE_OR_MEMORYERROR or ef == effectinfo.EF_ELIDABLE_CAN_RAISE): return # A special case for ll_arraycopy, because it is so common, and its # effects are so well defined. elif effectinfo.oopspecindex == effectinfo.OS_ARRAYCOPY: self._clear_caches_arraycopy(opnum, descr, argboxes, effectinfo) return else: # Only invalidate things that are escaped # XXX can do better, only do it for the descrs in the effectinfo for descr, cache in self.heap_cache.iteritems(): cache.invalidate_unescaped() for descr, indices in self.heap_array_cache.iteritems(): for cache in indices.itervalues(): cache.invalidate_unescaped() return # XXX not completely sure, but I *think* it is needed to reset() the # state at least in the 'CALL_*' operations that release the GIL. We # tried to do only the kind of resetting done by the two loops just # above, but hit an assertion in "pypy test_multiprocessing.py". self.reset_keep_likely_virtuals()
def accumulate_prepare(self, state): vec_reg_size = state.vec_reg_size for pack in self.packs: if not pack.is_accumulating(): continue if pack.leftmost().is_guard(): # guard breaks dependencies, thus it is an accumulation pack continue for i,node in enumerate(pack.operations): op = node.getoperation() state.accumulation[op] = pack assert isinstance(pack, AccumPack) datatype = pack.getdatatype() bytesize = pack.getbytesize() count = vec_reg_size // bytesize signed = datatype == 'i' oplist = state.invariant_oplist # reset the box to zeros or ones if pack.reduce_init() == 0: vecop = OpHelpers.create_vec(datatype, bytesize, signed, count) oplist.append(vecop) opnum = rop.VEC_INT_XOR if datatype == FLOAT: # see PRECISION loss below raise NotImplementedError vecop = VecOperation(opnum, [vecop, vecop], vecop, count) oplist.append(vecop) elif pack.reduce_init() == 1: # PRECISION loss, because the numbers are accumulated (associative, commutative properties must hold) # you can end up a small number and a huge number that is finally multiplied. giving an # inprecision result, thus this is disabled now raise NotImplementedError # multiply is only supported by floats vecop = OpHelpers.create_vec_expand(ConstFloat(1.0), bytesize, signed, count) oplist.append(vecop) else: raise NotImplementedError("cannot handle %s" % pack.operator) # pack the scalar value args = [vecop, pack.getleftmostseed(), ConstInt(0), ConstInt(1)] vecop = OpHelpers.create_vec_pack(datatype, args, bytesize, signed, count) oplist.append(vecop) seed = pack.getleftmostseed() state.accumulation[seed] = pack # rename the variable with the box state.setvector_of_box(seed, 0, vecop) # prevent it from expansion state.renamer.start_renaming(seed, vecop)
def produce_potential_short_preamble_ops(self, sb): ops = self.optimizer._newoperations for i, op in enumerate(ops): if rop.is_always_pure(op.opnum): sb.add_pure_op(op) if rop.is_ovf( op.opnum) and ops[i + 1].getopnum() == rop.GUARD_NO_OVERFLOW: sb.add_pure_op(op) for i in self.call_pure_positions: op = ops[i] # don't move call_pure_with_exception in the short preamble... # issue #2015 # Also, don't move cond_call_value in the short preamble. # The issue there is that it's usually pointless to try to # because the 'value' argument is typically not a loop # invariant, and would really need to be in order to end up # in the short preamble. Maybe the code works anyway in the # other rare case, but better safe than sorry and don't try. effectinfo = op.getdescr().get_extra_info() if not effectinfo.check_can_raise(ignore_memoryerror=True): assert rop.is_call(op.opnum) if not OpHelpers.is_cond_call_value(op.opnum): sb.add_pure_op(op)
def inputarg_for_var(self, elem): try: return self._cache[elem] except KeyError: pass if elem[0] in 'ifrpv': box = OpHelpers.inputarg_from_tp(elem[0]) number = elem[1:] if elem.startswith('v'): pattern = re.compile('.*\[(\d+)x(i|f)(\d+)\]') match = pattern.match(elem) if match: box.datatype = match.group(2)[0] box.bytesize = int(match.group(3)) // 8 box.count = int(match.group(1)) box.signed == item_type == 'i' number = elem[1:elem.find('[')] else: number = elem[1:] for prefix, boxclass in self.boxkinds.iteritems(): if elem.startswith(prefix): box = boxclass() break else: raise ParseError("Unknown variable type: %s" % elem) self._cache[elem] = box box._str = elem return box
def add_op_to_short(self, shortop): if shortop.res in self.produced_short_boxes: return # already added due to dependencies self.boxes_in_production[shortop.res] = None try: if isinstance(shortop, CompoundOp): lst = shortop.flatten(self, []) if len(lst) == 0: return None else: index = self._pick_op_index(lst) pop = lst[index] for i in range(len(lst)): if i == index: continue opnum = OpHelpers.same_as_for_type(shortop.res.type) new_name = ResOperation(opnum, [shortop.res]) assert lst[i].short_op is not pop.short_op orig_op = lst[i].short_op.res lst[i].short_op.res = new_name lst[i].short_op.orig_op = orig_op lst[i].invented_name = True self.produced_short_boxes[new_name] = lst[i] else: pop = shortop.add_op_to_short(self) if pop is None: return self.produced_short_boxes[shortop.res] = pop finally: del self.boxes_in_production[shortop.res] return pop
def optimize_call_pure(self, op, start_index=0): # Step 1: check if all arguments are constant for i in range(start_index, op.numargs()): self.optimizer.force_box(op.getarg(i)) # XXX hack to ensure that virtuals that are # constant are presented that way result = self._can_optimize_call_pure(op, start_index=start_index) if result is not None: # this removes a CALL_PURE with all constant arguments. self.make_constant(op, result) self.last_emitted_operation = REMOVED return # Step 2: check if all arguments are the same as a previous # CALL_PURE. for pos in self.call_pure_positions: old_op = self.optimizer._newoperations[pos] if self.optimize_call_pure_old(op, old_op, start_index): return if self.extra_call_pure: for i, old_op in enumerate(self.extra_call_pure): if self.optimize_call_pure_old(op, old_op, start_index): if isinstance(old_op, PreambleOp): old_op = self.optimizer.force_op_from_preamble(old_op) self.extra_call_pure[i] = old_op return # replace CALL_PURE with just CALL (but keep COND_CALL_VALUE) if start_index == 0: opnum = OpHelpers.call_for_descr(op.getdescr()) newop = self.optimizer.replace_op_with(op, opnum) else: newop = op return self.emit_result(CallPureOptimizationResult(self, newop))
def _optimize_call_arrayop(self, op, source_box, dest_box, source_start_box, dest_start_box, length_box): length = self.get_constant_box(length_box) if length and length.getint() == 0: return True # 0-length arraycopy or arraymove source_info = getptrinfo(source_box) dest_info = getptrinfo(dest_box) source_start_box = self.get_constant_box(source_start_box) dest_start_box = self.get_constant_box(dest_start_box) extrainfo = op.getdescr().get_extra_info() if (source_start_box and dest_start_box and length and ((dest_info and dest_info.is_virtual()) or length.getint() <= 8) and ((source_info and source_info.is_virtual()) or length.getint() <= 8) and extrainfo.single_write_descr_array is not None): #<-sanity check source_start = source_start_box.getint() dest_start = dest_start_box.getint() arraydescr = extrainfo.single_write_descr_array if arraydescr.is_array_of_structs(): return False # not supported right now index_current = 0 index_delta = +1 index_stop = length.getint() if (source_box is dest_box and # ARRAYMOVE only source_start < dest_start): # iterate in reverse order index_current = index_stop - 1 index_delta = -1 index_stop = -1 # XXX fish fish fish while index_current != index_stop: index = index_current index_current += index_delta assert index >= 0 if source_info and source_info.is_virtual(): val = source_info.getitem(arraydescr, index + source_start) else: opnum = OpHelpers.getarrayitem_for_descr(arraydescr) newop = ResOperation( opnum, [source_box, ConstInt(index + source_start)], descr=arraydescr) self.optimizer.send_extra_operation(newop) val = newop if val is None: continue if dest_info and dest_info.is_virtual(): dest_info.setitem(arraydescr, index + dest_start, get_box_replacement(dest_box), val) else: newop = ResOperation( rop.SETARRAYITEM_GC, [dest_box, ConstInt(index + dest_start), val], descr=arraydescr) self.optimizer.send_extra_operation(newop) return True return False
def propagate_all_forward(self, inputargs, ops, call_pure_results=None, rename_inputargs=True, flush=True): if rename_inputargs: newargs = [] for inparg in inputargs: new_arg = OpHelpers.inputarg_from_tp(inparg.type) inparg.set_forwarded(new_arg) newargs.append(new_arg) self.init_inparg_dict_from(newargs) else: newargs = inputargs self.call_pure_results = call_pure_results if ops[-1].getopnum() in (rop.FINISH, rop.JUMP): last = len(ops) - 1 extra_jump = True else: extra_jump = False last = len(ops) for i in range(last): self._really_emitted_operation = None self.first_optimization.propagate_forward(ops[i]) # accumulate counters if flush: self.flush() 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)
def optimize_CALL_PURE_I(self, op): # Step 1: check if all arguments are constant for arg in op.getarglist(): self.optimizer.force_box(arg) # XXX hack to ensure that virtuals that are # constant are presented that way result = self._can_optimize_call_pure(op) if result is not None: # this removes a CALL_PURE with all constant arguments. self.make_constant(op, result) self.last_emitted_operation = REMOVED return # Step 2: check if all arguments are the same as a previous # CALL_PURE. for pos in self.call_pure_positions: old_op = self.optimizer._newoperations[pos] if self.optimize_call_pure(op, old_op): return if self.extra_call_pure: for i, old_op in enumerate(self.extra_call_pure): if self.optimize_call_pure(op, old_op): if isinstance(old_op, PreambleOp): old_op = self.optimizer.force_op_from_preamble(old_op) self.extra_call_pure[i] = old_op return # replace CALL_PURE with just CALL opnum = OpHelpers.call_for_descr(op.getdescr()) newop = self.optimizer.replace_op_with(op, opnum) self.emit_operation(newop) self.call_pure_positions.append(len(self.optimizer._newoperations) - 1)
def rewrite(self, operations): # we can only remember one malloc since the next malloc can possibly # collect; but we can try to collapse several known-size mallocs into # one, both for performance and to reduce the number of write # barriers. We do this on each "basic block" of operations, which in # this case means between CALLs or unknown-size mallocs. # operations = self.remove_bridge_exception(operations) for i in range(len(operations)): op = operations[i] assert op.get_forwarded() is None if op.getopnum() == rop.DEBUG_MERGE_POINT: continue # ---------- GETFIELD_GC ---------- if op.getopnum() in (rop.GETFIELD_GC_I, rop.GETFIELD_GC_F, rop.GETFIELD_GC_R): self.handle_getfield_gc(op) continue # ---------- turn NEWxxx into CALL_MALLOC_xxx ---------- if op.is_malloc(): self.handle_malloc_operation(op) continue if (op.is_guard() or self.could_merge_with_next_guard(op, i, operations)): self.emit_pending_zeros() elif op.can_malloc(): self.emitting_an_operation_that_can_collect() elif op.getopnum() == rop.LABEL: self.emitting_an_operation_that_can_collect() self._known_lengths.clear() # ---------- write barriers ---------- if self.gc_ll_descr.write_barrier_descr is not None: if op.getopnum() == rop.SETFIELD_GC: self.consider_setfield_gc(op) self.handle_write_barrier_setfield(op) continue if op.getopnum() == rop.SETINTERIORFIELD_GC: self.handle_write_barrier_setinteriorfield(op) continue if op.getopnum() == rop.SETARRAYITEM_GC: self.consider_setarrayitem_gc(op) self.handle_write_barrier_setarrayitem(op) continue else: # this is dead code, but in case we have a gc that does # not have a write barrier and does not zero memory, we would # need to clal it if op.getopnum() == rop.SETFIELD_GC: self.consider_setfield_gc(op) elif op.getopnum() == rop.SETARRAYITEM_GC: self.consider_setarrayitem_gc(op) # ---------- call assembler ----------- if OpHelpers.is_call_assembler(op.getopnum()): self.handle_call_assembler(op) continue if op.getopnum() == rop.JUMP or op.getopnum() == rop.FINISH: self.emit_pending_zeros() # self.emit_op(op) return self._newops
def optimize_CALL_PURE_I(self, op): # Step 1: check if all arguments are constant for arg in op.getarglist(): self.optimizer.force_box(arg) # XXX hack to ensure that virtuals that are # constant are presented that way result = self._can_optimize_call_pure(op) if result is not None: # this removes a CALL_PURE with all constant arguments. self.make_constant(op, result) self.last_emitted_operation = REMOVED return # Step 2: check if all arguments are the same as a previous # CALL_PURE. for pos in self.call_pure_positions: old_op = self.optimizer._newoperations[pos] if self.optimize_call_pure(op, old_op): return if self.extra_call_pure: for i, old_op in enumerate(self.extra_call_pure): if self.optimize_call_pure(op, old_op): if isinstance(old_op, PreambleOp): old_op = self.optimizer.force_op_from_preamble(old_op) self.extra_call_pure[i] = old_op return # replace CALL_PURE with just CALL opnum = OpHelpers.call_for_descr(op.getdescr()) newop = self.optimizer.replace_op_with(op, opnum) self.emit_operation(newop) self.call_pure_positions.append( len(self.optimizer._newoperations) - 1)
def optimize_COND_CALL(self, op): arg = op.getarg(0) b = self.getintbound(arg) if b.is_constant(): if b.getint() == 0: self.last_emitted_operation = REMOVED return opnum = OpHelpers.call_for_type(op.type) op = op.copy_and_change(opnum, args=op.getarglist()[1:]) return self.emit(op)
def unpack_from_vector(state, arg, index, count): """ Extract parts of the vector box into another vector box """ assert count > 0 assert index + count <= arg.count args = [arg, ConstInt(index), ConstInt(count)] vecop = OpHelpers.create_vec_unpack(arg.type, args, arg.bytesize, arg.signed, count) state.costmodel.record_vector_unpack(arg, index, count) state.oplist.append(vecop) return vecop
def produce_short_preamble_ops(self, structbox, descr, index, optimizer, shortboxes): if self._fields is None: return if descr.get_index() >= len(self._fields): # we don't know about this item return op = optimizer.get_box_replacement(self._fields[descr.get_index()]) opnum = OpHelpers.getfield_for_descr(descr) getfield_op = ResOperation(opnum, [structbox], descr=descr) shortboxes.add_heap_op(op, getfield_op)
def add_op_to_short(self, sb): op = self.res arglist = [] for arg in op.getarglist(): newarg = sb.produce_arg(arg) if newarg is None: return None arglist.append(newarg) opnum = OpHelpers.call_loopinvariant_for_descr(op.getdescr()) return ProducedShortOp(self, op.copy_and_change(opnum, args=arglist))
def optimize_COND_CALL(self, op): arg = op.getarg(0) b = self.getintbound(arg) if b.is_constant(): if b.getint() == 0: self.last_emitted_operation = REMOVED return opnum = OpHelpers.call_for_type(op.type) op = op.copy_and_change(opnum, args=op.getarglist()[1:]) self.emit_operation(op)
def optimize_COND_CALL_VALUE_I(self, op): # look if we know the nullness of the first argument info = self.getnullness(op.getarg(0)) if info == INFO_NONNULL: self.make_equal_to(op, op.getarg(0)) self.last_emitted_operation = REMOVED return if info == INFO_NULL: opnum = OpHelpers.call_pure_for_type(op.type) op = self.replace_op_with(op, opnum, args=op.getarglist()[1:]) return self.emit(op)
def unpack_from_vector(state, arg, index, count): """ Extract parts of the vector box into another vector box """ assert count > 0 vecinfo = forwarded_vecinfo(arg) assert index + count <= vecinfo.count args = [arg, ConstInt(index), ConstInt(count)] vecop = OpHelpers.create_vec_unpack(arg.type, args, vecinfo.bytesize, vecinfo.signed, count) state.costmodel.record_vector_unpack(arg, index, count) state.append_to_oplist(vecop) return vecop
def produce_short_preamble_ops(self, structbox, fielddescr, index, optimizer, shortboxes): if self._fields is None: return if fielddescr.get_index() >= len(self._fields): # we don't know about this item return op = optimizer.get_box_replacement(self._fields[fielddescr.get_index()]) opnum = OpHelpers.getfield_for_descr(fielddescr) getfield_op = ResOperation(opnum, [structbox], descr=fielddescr) shortboxes.add_heap_op(op, getfield_op)
def accumulate_prepare(self, state): vec_reg_size = state.vec_reg_size for pack in self.packs: if not pack.is_accumulating(): continue if pack.leftmost().is_guard(): # guard breaks dependencies, thus it is an accumulation pack continue for i,node in enumerate(pack.operations): op = node.getoperation() state.accumulation[op] = pack assert isinstance(pack, AccumPack) datatype = pack.getdatatype() bytesize = pack.getbytesize() count = vec_reg_size // bytesize signed = datatype == 'i' oplist = state.invariant_oplist # reset the box to zeros or ones if pack.reduce_init() == 0: vecop = OpHelpers.create_vec(datatype, bytesize, signed, count) oplist.append(vecop) vecop = VecOperation(rop.VEC_INT_XOR, [vecop, vecop], vecop, count) oplist.append(vecop) elif pack.reduce_init() == 1: # multiply is only supported by floats vecop = OpHelpers.create_vec_expand(ConstFloat(1.0), bytesize, signed, count) oplist.append(vecop) else: raise NotImplementedError("cannot handle %s" % pack.operator) # pack the scalar value args = [vecop, pack.getleftmostseed(), ConstInt(0), ConstInt(1)] vecop = OpHelpers.create_vec_pack(datatype, args, bytesize, signed, count) oplist.append(vecop) seed = pack.getleftmostseed() state.accumulation[seed] = pack # rename the variable with the box state.setvector_of_box(seed, 0, vecop) # prevent it from expansion state.renamer.start_renaming(seed, vecop)
def compile_tmp_callback(cpu, jitdriver_sd, greenboxes, redargtypes, memory_manager=None): """Make a LoopToken that corresponds to assembler code that just calls back the interpreter. Used temporarily: a fully compiled version of the code may end up replacing it. """ jitcell_token = make_jitcell_token(jitdriver_sd) # # record the target of a temporary callback to the interpreter jl.tmp_callback(jitcell_token) # nb_red_args = jitdriver_sd.num_red_args assert len(redargtypes) == nb_red_args inputargs = [] for kind in redargtypes: if kind == history.INT: box = InputArgInt() elif kind == history.REF: box = InputArgRef() elif kind == history.FLOAT: box = InputArgFloat() else: raise AssertionError inputargs.append(box) k = jitdriver_sd.portal_runner_adr funcbox = history.ConstInt(adr2int(k)) callargs = [funcbox] + greenboxes + inputargs # jd = jitdriver_sd opnum = OpHelpers.call_for_descr(jd.portal_calldescr) call_op = ResOperation(opnum, callargs, descr=jd.portal_calldescr) if call_op.type != 'v' is not None: finishargs = [call_op] else: finishargs = [] # faildescr = jitdriver_sd.propagate_exc_descr operations = [ call_op, ResOperation(rop.GUARD_NO_EXCEPTION, [], descr=faildescr), ResOperation(rop.FINISH, finishargs, descr=jd.portal_finishtoken) ] operations[1].setfailargs([]) operations = get_deep_immutable_oplist(operations) cpu.compile_loop(inputargs, operations, jitcell_token, log=False) if memory_manager is not None: # for tests memory_manager.keep_loop_alive(jitcell_token) return jitcell_token
def produce_short_preamble_ops(self, structbox, descr, index, optimizer, shortboxes): if self._items is None: return if index >= len(self._items): # we don't know about this item return item = self._items[index] if item is not None: op = optimizer.get_box_replacement(item) opnum = OpHelpers.getarrayitem_for_descr(descr) getarrayitem_op = ResOperation(opnum, [structbox, ConstInt(index)], descr=descr) shortboxes.add_heap_op(op, getarrayitem_op)
def create_short_inputargs(self, label_args): return self.short_inputargs short_inpargs = [] for i in range(len(label_args)): inparg = self.produced_short_boxes.get(label_args[i], None) if inparg is None: renamed = OpHelpers.inputarg_from_tp(label_args[i].type) short_inpargs.append(renamed) else: assert isinstance(inparg.short_op, ShortInputArg) short_inpargs.append(inparg.preamble_op) return short_inpargs
def add_op_to_short(self, sb): op = self.res arglist = [] for arg in op.getarglist(): newarg = sb.produce_arg(arg) if newarg is None: return None arglist.append(newarg) if rop.is_call(op.opnum): opnum = OpHelpers.call_pure_for_descr(op.getdescr()) else: opnum = op.getopnum() return ProducedShortOp(self, op.copy_and_change(opnum, args=arglist))
def produce_short_preamble_ops(self, structbox, descr, index, optimizer, shortboxes): if self._items is None: return if index >= len(self._items): # we don't know about this item return item = self._items[index] if item is not None: op = optimizer.get_box_replacement(item) opnum = OpHelpers.getarrayitem_for_descr(descr) getarrayitem_op = ResOperation( opnum, [structbox, ConstInt(index)], descr=descr) shortboxes.add_heap_op(op, getarrayitem_op)
def protect_speculative_operation(self, op): """When constant-folding a pure operation that reads memory from a gcref, make sure that the gcref is non-null and of a valid type. Otherwise, raise SpeculativeError. This should only occur when unrolling and optimizing the unrolled loop. Note that if cpu.supports_guard_gc_type is false, we can't really do this check at all, but then we don't unroll in that case. """ opnum = op.getopnum() cpu = self.cpu if OpHelpers.is_pure_getfield(opnum, op.getdescr()): fielddescr = op.getdescr() ref = self.get_constant_box(op.getarg(0)).getref_base() cpu.protect_speculative_field(ref, fielddescr) return elif (opnum == rop.GETARRAYITEM_GC_PURE_I or opnum == rop.GETARRAYITEM_GC_PURE_R or opnum == rop.GETARRAYITEM_GC_PURE_F or opnum == rop.ARRAYLEN_GC): arraydescr = op.getdescr() array = self.get_constant_box(op.getarg(0)).getref_base() cpu.protect_speculative_array(array, arraydescr) if opnum == rop.ARRAYLEN_GC: return arraylength = cpu.bh_arraylen_gc(array, arraydescr) elif (opnum == rop.STRGETITEM or opnum == rop.STRLEN): string = self.get_constant_box(op.getarg(0)).getref_base() cpu.protect_speculative_string(string) if opnum == rop.STRLEN: return arraylength = cpu.bh_strlen(string) elif (opnum == rop.UNICODEGETITEM or opnum == rop.UNICODELEN): unicode = self.get_constant_box(op.getarg(0)).getref_base() cpu.protect_speculative_unicode(unicode) if opnum == rop.UNICODELEN: return arraylength = cpu.bh_unicodelen(unicode) else: return index = self.get_constant_box(op.getarg(1)).getint() if not (0 <= index < arraylength): raise SpeculativeError
def compile_tmp_callback(cpu, jitdriver_sd, greenboxes, redargtypes, memory_manager=None): """Make a LoopToken that corresponds to assembler code that just calls back the interpreter. Used temporarily: a fully compiled version of the code may end up replacing it. """ jitcell_token = make_jitcell_token(jitdriver_sd) # # record the target of a temporary callback to the interpreter jl.tmp_callback(jitcell_token) # nb_red_args = jitdriver_sd.num_red_args assert len(redargtypes) == nb_red_args inputargs = [] for kind in redargtypes: if kind == history.INT: box = InputArgInt() elif kind == history.REF: box = InputArgRef() elif kind == history.FLOAT: box = InputArgFloat() else: raise AssertionError inputargs.append(box) k = jitdriver_sd.portal_runner_adr funcbox = history.ConstInt(heaptracker.adr2int(k)) callargs = [funcbox] + greenboxes + inputargs # jd = jitdriver_sd opnum = OpHelpers.call_for_descr(jd.portal_calldescr) call_op = ResOperation(opnum, callargs, descr=jd.portal_calldescr) if call_op.type != 'v' is not None: finishargs = [call_op] else: finishargs = [] # faildescr = jitdriver_sd.propagate_exc_descr operations = [ call_op, ResOperation(rop.GUARD_NO_EXCEPTION, [], descr=faildescr), ResOperation(rop.FINISH, finishargs, descr=jd.portal_finishtoken) ] operations[1].setfailargs([]) operations = get_deep_immutable_oplist(operations) cpu.compile_loop(inputargs, operations, jitcell_token, log=False) if memory_manager is not None: # for tests memory_manager.keep_loop_alive(jitcell_token) return jitcell_token
def pack_into_vector(state, tgt, tidx, src, sidx, scount): """ tgt = [1,2,3,4,_,_,_,_] src = [5,6,_,_] new_box = [1,2,3,4,5,6,_,_] after the operation, tidx=4, scount=2 """ assert sidx == 0 # restriction newcount = tgt.count + scount args = [tgt, src, ConstInt(tidx), ConstInt(scount)] vecop = OpHelpers.create_vec_pack(tgt.type, args, tgt.bytesize, tgt.signed, newcount) state.oplist.append(vecop) state.costmodel.record_vector_pack(src, sidx, scount) if not we_are_translated(): _check_vec_pack(vecop) return vecop
def patch_new_loop_to_load_virtualizable_fields(loop, jitdriver_sd, vable): # XXX merge with rewriting vinfo = jitdriver_sd.virtualizable_info extra_ops = [] inputargs = loop.inputargs vable_box = inputargs[jitdriver_sd.index_of_virtualizable] i = jitdriver_sd.num_red_args loop.inputargs = inputargs[:i] for descr in vinfo.static_field_descrs: assert i < len(inputargs) box = inputargs[i] opnum = OpHelpers.getfield_for_descr(descr) emit_op(extra_ops, ResOperation(opnum, [vable_box], 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) emit_op(extra_ops, arrayop) arraydescr = vinfo.array_descrs[arrayindex] assert i + arraylen <= len(inputargs) for index in range(arraylen): opnum = OpHelpers.getarrayitem_for_descr(arraydescr) box = inputargs[i] emit_op(extra_ops, ResOperation(opnum, [arrayop, ConstInt(index)], descr=arraydescr)) i += 1 box.set_forwarded(extra_ops[-1]) arrayindex += 1 assert i == len(inputargs) for op in loop.operations: emit_op(extra_ops, op) loop.operations = extra_ops
def patch_new_loop_to_load_virtualizable_fields(loop, jitdriver_sd, vable): # XXX merge with rewriting vinfo = jitdriver_sd.virtualizable_info extra_ops = [] inputargs = loop.inputargs vable_box = inputargs[jitdriver_sd.index_of_virtualizable] i = jitdriver_sd.num_red_args loop.inputargs = inputargs[:i] for descr in vinfo.static_field_descrs: assert i < len(inputargs) box = inputargs[i] opnum = OpHelpers.getfield_for_descr(descr) emit_op(extra_ops, 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=descr) emit_op(extra_ops, arrayop) arraydescr = vinfo.array_descrs[arrayindex] assert i + arraylen <= len(inputargs) for index in range(arraylen): opnum = OpHelpers.getarrayitem_for_descr(arraydescr) box = inputargs[i] emit_op(extra_ops, ResOperation(opnum, [arrayop, ConstInt(index)], descr=arraydescr)) i += 1 box.set_forwarded(extra_ops[-1]) arrayindex += 1 assert i == len(inputargs) for op in loop.operations: emit_op(extra_ops, op) loop.operations = extra_ops
def _optimize_CALL_ARRAYCOPY(self, op): length = self.get_constant_box(op.getarg(5)) if length and length.getint() == 0: return None # 0-length arraycopy source_info = self.getptrinfo(op.getarg(1)) dest_info = self.getptrinfo(op.getarg(2)) source_start_box = self.get_constant_box(op.getarg(3)) dest_start_box = self.get_constant_box(op.getarg(4)) extrainfo = op.getdescr().get_extra_info() if (source_start_box and dest_start_box and length and ((dest_info and dest_info.is_virtual()) or length.getint() <= 8) and ((source_info and source_info.is_virtual()) or length.getint() <= 8) and extrainfo.single_write_descr_array is not None): #<-sanity check source_start = source_start_box.getint() dest_start = dest_start_box.getint() arraydescr = extrainfo.single_write_descr_array if arraydescr.is_array_of_structs(): return self.emit(op) # not supported right now # XXX fish fish fish for index in range(length.getint()): if source_info and source_info.is_virtual(): val = source_info.getitem(arraydescr, index + source_start) else: opnum = OpHelpers.getarrayitem_for_descr(arraydescr) newop = ResOperation( opnum, [op.getarg(1), ConstInt(index + source_start)], descr=arraydescr) self.optimizer.send_extra_operation(newop) val = newop if val is None: continue if dest_info and dest_info.is_virtual(): dest_info.setitem(arraydescr, index + dest_start, self.get_box_replacement(op.getarg(2)), val) else: newop = ResOperation( rop.SETARRAYITEM_GC, [op.getarg(2), ConstInt(index + dest_start), val], descr=arraydescr) self.optimizer.send_extra_operation(newop) return None return self.emit(op)
def pack_into_vector(state, tgt, tidx, src, sidx, scount): """ tgt = [1,2,3,4,_,_,_,_] src = [5,6,_,_] new_box = [1,2,3,4,5,6,_,_] after the operation, tidx=4, scount=2 """ assert sidx == 0 # restriction vecinfo = forwarded_vecinfo(tgt) newcount = vecinfo.count + scount args = [tgt, src, ConstInt(tidx), ConstInt(scount)] vecop = OpHelpers.create_vec_pack(tgt.type, args, vecinfo.bytesize, vecinfo.signed, newcount) state.oplist.append(vecop) state.costmodel.record_vector_pack(src, sidx, scount) if not we_are_translated(): _check_vec_pack(vecop) return vecop
def _optimize_CALL_ARRAYCOPY(self, op): length = self.get_constant_box(op.getarg(5)) if length and length.getint() == 0: return True # 0-length arraycopy source_info = self.getptrinfo(op.getarg(1)) dest_info = self.getptrinfo(op.getarg(2)) source_start_box = self.get_constant_box(op.getarg(3)) dest_start_box = self.get_constant_box(op.getarg(4)) extrainfo = op.getdescr().get_extra_info() if (source_start_box and dest_start_box and length and ((dest_info and dest_info.is_virtual()) or length.getint() <= 8) and ((source_info and source_info.is_virtual()) or length.getint() <= 8) and len(extrainfo.write_descrs_arrays) == 1): # <-sanity check source_start = source_start_box.getint() dest_start = dest_start_box.getint() arraydescr = extrainfo.write_descrs_arrays[0] if arraydescr.is_array_of_structs(): return False # not supported right now # XXX fish fish fish for index in range(length.getint()): if source_info and source_info.is_virtual(): val = source_info.getitem(arraydescr, index + source_start) else: opnum = OpHelpers.getarrayitem_for_descr(arraydescr) newop = ResOperation(opnum, [op.getarg(1), ConstInt(index + source_start)], descr=arraydescr) self.optimizer.send_extra_operation(newop) val = newop if val is None: continue if dest_info and dest_info.is_virtual(): dest_info.setitem(arraydescr, index + dest_start, self.get_box_replacement(op.getarg(2)), val) else: newop = ResOperation(rop.SETARRAYITEM_GC, [op.getarg(2), ConstInt(index + dest_start), val], descr=arraydescr) self.emit_operation(newop) return True return False
def produce_short_preamble_ops(self, structbox, fielddescr, index, optimizer, shortboxes): if self._fields is None: return if fielddescr.get_index() >= len(self._fields): # we don't know about this item return op = get_box_replacement(self._fields[fielddescr.get_index()]) if op is None: # XXX same bug as in serialize_opt: # op should never be None, because that's an invariant violation in # AbstractCachedEntry. But it still seems to happen when the info # is attached to a Constant. At least we shouldn't crash. return opnum = OpHelpers.getfield_for_descr(fielddescr) getfield_op = ResOperation(opnum, [structbox], descr=fielddescr) shortboxes.add_heap_op(op, getfield_op)
def optimize_call_pure_old(self, op, old_op, start_index): if op.getdescr() is not old_op.getdescr(): return False # this will match a call_pure and a cond_call_value with # the same function and arguments old_start_index = OpHelpers.is_cond_call_value(old_op.opnum) if self._same_args(old_op, op, old_start_index, start_index): # all identical # this removes a CALL_PURE that has the same (non-constant) # arguments as a previous CALL_PURE. if isinstance(old_op, PreambleOp): # xxx obscure, it's dealt with in the caller old_op = old_op.op self.make_equal_to(op, old_op) self.last_emitted_operation = REMOVED return True return False
def ensure_unpacked(self, index, arg): if arg in self.seen or arg.is_vector(): return arg (pos, var) = self.getvector_of_box(arg) if var: if var in self.invariant_vector_vars: return arg if arg in self.accumulation: return arg args = [var, ConstInt(pos), ConstInt(1)] vecop = OpHelpers.create_vec_unpack(var.type, args, var.bytesize, var.signed, 1) self.renamer.start_renaming(arg, vecop) self.seen[vecop] = None self.costmodel.record_vector_unpack(var, pos, 1) self.oplist.append(vecop) return vecop return arg
def optimize_CALL_LOOPINVARIANT_I(self, op): arg = op.getarg(0) # 'arg' must be a Const, because residual_call in codewriter # expects a compile-time constant assert isinstance(arg, Const) key = make_hashable_int(arg.getint()) resvalue = self.loop_invariant_results.get(key, None) if resvalue is not None: resvalue = self.optimizer.force_op_from_preamble(resvalue) self.loop_invariant_results[key] = resvalue self.make_equal_to(op, resvalue) self.last_emitted_operation = REMOVED return # change the op to be a normal call, from the backend's point of view # there is no reason to have a separate operation for this newop = self.replace_op_with(op, OpHelpers.call_for_descr(op.getdescr())) return self.emit_result(CallLoopinvariantOptimizationResult(self, newop, op))
def ensure_unpacked(self, index, arg): if arg in self.seen or arg.is_vector(): return arg (pos, var) = self.getvector_of_box(arg) if var: if var in self.invariant_vector_vars: return arg if arg in self.accumulation: return arg args = [var, ConstInt(pos), ConstInt(1)] vecinfo = forwarded_vecinfo(var) vecop = OpHelpers.create_vec_unpack(var.type, args, vecinfo.bytesize, vecinfo.signed, 1) self.renamer.start_renaming(arg, vecop) self.seen[vecop] = None self.costmodel.record_vector_unpack(var, pos, 1) self.oplist.append(vecop) return vecop return arg
def optimize_call_pure_old(self, op, old_op, start_index): if op.getdescr() is not old_op.getdescr(): return False # this will match a call_pure and a cond_call_value with # the same function and arguments j = start_index old_start_index = OpHelpers.is_cond_call_value(old_op.opnum) for i in range(old_start_index, old_op.numargs()): box = old_op.getarg(i) if not get_box_replacement(op.getarg(j)).same_box(box): break j += 1 else: # all identical # this removes a CALL_PURE that has the same (non-constant) # arguments as a previous CALL_PURE. if isinstance(old_op, PreambleOp): # xxx obscure, it's dealt with in the caller old_op = old_op.op self.make_equal_to(op, old_op) self.last_emitted_operation = REMOVED return True return False
def optimize_call_pure_old(self, op, old_op, start_index): if op.getdescr() is not old_op.getdescr(): return False # this will match a call_pure and a cond_call_value with # the same function and arguments j = start_index old_start_index = OpHelpers.is_cond_call_value(old_op.opnum) for i in range(old_start_index, old_op.numargs()): box = old_op.getarg(i) if not self.get_box_replacement(op.getarg(j)).same_box(box): break j += 1 else: # all identical # this removes a CALL_PURE that has the same (non-constant) # arguments as a previous CALL_PURE. if isinstance(old_op, PreambleOp): # xxx obscure, it's dealt with in the caller old_op = old_op.op self.make_equal_to(op, old_op) self.last_emitted_operation = REMOVED return True return False
def produce_potential_short_preamble_ops(self, sb): ops = self.optimizer._newoperations for i, op in enumerate(ops): if rop.is_always_pure(op.opnum): sb.add_pure_op(op) if rop.is_ovf(op.opnum) and ops[i + 1].getopnum() == rop.GUARD_NO_OVERFLOW: sb.add_pure_op(op) for i in self.call_pure_positions: op = ops[i] # don't move call_pure_with_exception in the short preamble... # issue #2015 # Also, don't move cond_call_value in the short preamble. # The issue there is that it's usually pointless to try to # because the 'value' argument is typically not a loop # invariant, and would really need to be in order to end up # in the short preamble. Maybe the code works anyway in the # other rare case, but better safe than sorry and don't try. effectinfo = op.getdescr().get_extra_info() if not effectinfo.check_can_raise(ignore_memoryerror=True): assert rop.is_call(op.opnum) if not OpHelpers.is_cond_call_value(op.opnum): sb.add_pure_op(op)
def create_short_boxes(self, optimizer, inputargs, label_args): # all the potential operations that can be produced, subclasses # of AbstractShortOp self.potential_ops = OrderedDict() self.produced_short_boxes = {} # a way to produce const boxes, e.g. setfield_gc(p0, Const). # We need to remember those, but they don't produce any new boxes self.const_short_boxes = [] self.short_inputargs = [] for i in range(len(label_args)): box = label_args[i] renamed = OpHelpers.inputarg_from_tp(box.type) self.short_inputargs.append(renamed) self.potential_ops[box] = ShortInputArg(box, renamed) optimizer.produce_potential_short_preamble_ops(self) short_boxes = [] self.boxes_in_production = {} for shortop in self.potential_ops.values(): self.add_op_to_short(shortop) # for op, produced_op in self.produced_short_boxes.iteritems(): short_boxes.append(produced_op) for short_op in self.const_short_boxes: getfield_op = short_op.getfield_op args = getfield_op.getarglist() preamble_arg = self.produce_arg(args[0]) if preamble_arg is not None: preamble_op = getfield_op.copy_and_change( getfield_op.getopnum(), [preamble_arg] + args[1:]) produced_op = ProducedShortOp(short_op, preamble_op) short_boxes.append(produced_op) return short_boxes