/rpython/jit/codewriter/jtransform.py
Python | 2170 lines | 2044 code | 67 blank | 59 comment | 101 complexity | fd99a2f9e14c94d81b1ce8e9274c221b MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
Large files files are truncated, but you can click here to view the full file
- import py
- from rpython.jit.codewriter import support, heaptracker, longlong
- from rpython.jit.codewriter.effectinfo import EffectInfo
- from rpython.jit.codewriter.flatten import ListOfKind, IndirectCallTargets
- from rpython.jit.codewriter.policy import log
- from rpython.jit.metainterp import quasiimmut
- from rpython.jit.metainterp.history import getkind
- from rpython.jit.metainterp.typesystem import deref, arrayItem
- from rpython.jit.metainterp.blackhole import BlackholeInterpreter
- from rpython.flowspace.model import SpaceOperation, Variable, Constant,\
- c_last_exception
- from rpython.rlib import objectmodel
- from rpython.rlib.jit import _we_are_jitted
- from rpython.rlib.rgc import lltype_is_gc
- from rpython.rtyper.lltypesystem import lltype, llmemory, rstr, rffi
- from rpython.rtyper.lltypesystem import rbytearray
- from rpython.rtyper import rclass
- from rpython.rtyper.rclass import IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY
- from rpython.translator.unsimplify import varoftype
- class UnsupportedMallocFlags(Exception):
- pass
- def transform_graph(graph, cpu=None, callcontrol=None, portal_jd=None):
- """Transform a control flow graph to make it suitable for
- being flattened in a JitCode.
- """
- constant_fold_ll_issubclass(graph, cpu)
- t = Transformer(cpu, callcontrol, portal_jd)
- t.transform(graph)
- def constant_fold_ll_issubclass(graph, cpu):
- # ll_issubclass can be inserted by the inliner to check exception types.
- # See corner case metainterp.test.test_exception:test_catch_different_class
- if cpu is None:
- return
- excmatch = cpu.rtyper.exceptiondata.fn_exception_match
- for block in list(graph.iterblocks()):
- for i, op in enumerate(block.operations):
- if (op.opname == 'direct_call' and
- all(isinstance(a, Constant) for a in op.args) and
- op.args[0].value._obj is excmatch._obj):
- constant_result = excmatch(*[a.value for a in op.args[1:]])
- block.operations[i] = SpaceOperation(
- 'same_as',
- [Constant(constant_result, lltype.Bool)],
- op.result)
- if block.exitswitch is op.result:
- block.exitswitch = None
- block.recloseblock(*[link for link in block.exits
- if link.exitcase == constant_result])
- def integer_bounds(size, unsigned):
- if unsigned:
- return 0, 1 << (8 * size)
- else:
- return -(1 << (8 * size - 1)), 1 << (8 * size - 1)
- class Transformer(object):
- vable_array_vars = None
- def __init__(self, cpu=None, callcontrol=None, portal_jd=None):
- self.cpu = cpu
- self.callcontrol = callcontrol
- self.portal_jd = portal_jd # non-None only for the portal graph(s)
- def transform(self, graph):
- self.graph = graph
- for block in list(graph.iterblocks()):
- self.optimize_block(block)
- def optimize_block(self, block):
- if block.operations == ():
- return
- self.vable_array_vars = {}
- self.vable_flags = {}
- renamings = {}
- renamings_constants = {} # subset of 'renamings', {Var:Const} only
- newoperations = []
- #
- def do_rename(var, var_or_const):
- if var.concretetype is lltype.Void:
- renamings[var] = Constant(None, lltype.Void)
- return
- renamings[var] = var_or_const
- if isinstance(var_or_const, Constant):
- value = var_or_const.value
- value = lltype._cast_whatever(var.concretetype, value)
- renamings_constants[var] = Constant(value, var.concretetype)
- #
- for op in block.operations:
- if renamings_constants:
- op = self._do_renaming(renamings_constants, op)
- oplist = self.rewrite_operation(op)
- #
- count_before_last_operation = len(newoperations)
- if not isinstance(oplist, list):
- oplist = [oplist]
- for op1 in oplist:
- if isinstance(op1, SpaceOperation):
- newoperations.append(self._do_renaming(renamings, op1))
- elif op1 is None:
- # rewrite_operation() returns None to mean "has no real
- # effect, the result should just be renamed to args[0]"
- if op.result is not None:
- do_rename(op.result, renamings.get(op.args[0],
- op.args[0]))
- elif isinstance(op1, Constant):
- do_rename(op.result, op1)
- else:
- raise TypeError(repr(op1))
- #
- if block.canraise:
- if len(newoperations) == count_before_last_operation:
- self._killed_exception_raising_operation(block)
- block.operations = newoperations
- block.exitswitch = renamings.get(block.exitswitch, block.exitswitch)
- self.follow_constant_exit(block)
- self.optimize_goto_if_not(block)
- if isinstance(block.exitswitch, tuple):
- self._check_no_vable_array(block.exitswitch)
- for link in block.exits:
- self._check_no_vable_array(link.args)
- self._do_renaming_on_link(renamings, link)
- def _do_renaming(self, rename, op):
- op = SpaceOperation(op.opname, op.args[:], op.result)
- for i, v in enumerate(op.args):
- if isinstance(v, Variable):
- if v in rename:
- op.args[i] = rename[v]
- elif isinstance(v, ListOfKind):
- newlst = []
- for x in v:
- if x in rename:
- x = rename[x]
- newlst.append(x)
- op.args[i] = ListOfKind(v.kind, newlst)
- return op
- def _check_no_vable_array(self, list):
- if not self.vable_array_vars:
- return
- for v in list:
- if v in self.vable_array_vars:
- vars = self.vable_array_vars[v]
- (v_base, arrayfielddescr, arraydescr) = vars
- raise AssertionError(
- "A virtualizable array is passed around; it should\n"
- "only be used immediately after being read. Note\n"
- "that a possible cause is indexing with an index not\n"
- "known non-negative, or catching IndexError, or\n"
- "not inlining at all (for tests: use listops=True).\n"
- "This is about: %r\n"
- "Occurred in: %r" % (arrayfielddescr, self.graph))
- # extra explanation: with the way things are organized in
- # rpython/rlist.py, the ll_getitem becomes a function call
- # that is typically meant to be inlined by the JIT, but
- # this does not work with vable arrays because
- # jtransform.py expects the getfield and the getarrayitem
- # to be in the same basic block. It works a bit as a hack
- # for simple cases where we performed the backendopt
- # inlining before (even with a very low threshold, because
- # there is _always_inline_ on the relevant functions).
- def _do_renaming_on_link(self, rename, link):
- for i, v in enumerate(link.args):
- if isinstance(v, Variable):
- if v in rename:
- link.args[i] = rename[v]
- def _killed_exception_raising_operation(self, block):
- assert block.exits[0].exitcase is None
- block.exits = block.exits[:1]
- block.exitswitch = None
- # ----------
- def follow_constant_exit(self, block):
- v = block.exitswitch
- if isinstance(v, Constant) and not block.canraise:
- llvalue = v.value
- for link in block.exits:
- if link.llexitcase == llvalue:
- break
- else:
- assert link.exitcase == 'default'
- block.exitswitch = None
- link.exitcase = link.llexitcase = None
- block.recloseblock(link)
- def optimize_goto_if_not(self, block):
- """Replace code like 'v = int_gt(x,y); exitswitch = v'
- with just 'exitswitch = ('int_gt',x,y)'."""
- if len(block.exits) != 2:
- return False
- v = block.exitswitch
- if (block.canraise or isinstance(v, tuple)
- or v.concretetype != lltype.Bool):
- return False
- for op in block.operations[::-1]:
- if v in op.args:
- return False # variable is also used in cur block
- if v is op.result:
- if op.opname not in ('int_lt', 'int_le', 'int_eq', 'int_ne',
- 'int_gt', 'int_ge',
- 'float_lt', 'float_le', 'float_eq',
- 'float_ne', 'float_gt', 'float_ge',
- 'int_is_zero', 'int_is_true',
- 'ptr_eq', 'ptr_ne',
- 'ptr_iszero', 'ptr_nonzero'):
- return False # not a supported operation
- # ok! optimize this case
- block.operations.remove(op)
- block.exitswitch = (op.opname,) + tuple(op.args)
- #if op.opname in ('ptr_iszero', 'ptr_nonzero'):
- block.exitswitch += ('-live-before',)
- # if the variable escape to the next block along a link,
- # replace it with a constant, because we know its value
- for link in block.exits:
- while v in link.args:
- index = link.args.index(v)
- link.args[index] = Constant(link.llexitcase,
- lltype.Bool)
- return True
- return False
- # ----------
- def rewrite_operation(self, op):
- try:
- rewrite = _rewrite_ops[op.opname]
- except KeyError:
- raise Exception("the JIT doesn't support the operation %r"
- " in %r" % (op, getattr(self, 'graph', '?')))
- return rewrite(self, op)
- def rewrite_op_same_as(self, op):
- if op.args[0] in self.vable_array_vars:
- self.vable_array_vars[op.result]= self.vable_array_vars[op.args[0]]
- def rewrite_op_cast_ptr_to_adr(self, op):
- if lltype_is_gc(op.args[0].concretetype):
- raise Exception("cast_ptr_to_adr for GC types unsupported")
- def rewrite_op_cast_pointer(self, op):
- newop = self.rewrite_op_same_as(op)
- assert newop is None
- return
- # disabled for now
- if (self._is_rclass_instance(op.args[0]) and
- self._is_rclass_instance(op.result)):
- FROM = op.args[0].concretetype.TO
- TO = op.result.concretetype.TO
- if lltype._castdepth(TO, FROM) > 0:
- vtable = heaptracker.get_vtable_for_gcstruct(self.cpu, TO)
- if vtable.subclassrange_max - vtable.subclassrange_min == 1:
- # it's a precise class check
- const_vtable = Constant(vtable, lltype.typeOf(vtable))
- return [None, # hack, do the right renaming from op.args[0] to op.result
- SpaceOperation("record_exact_class", [op.args[0], const_vtable], None)]
- def rewrite_op_likely(self, op):
- return None # "no real effect"
- def rewrite_op_unlikely(self, op):
- return None # "no real effect"
- def rewrite_op_raw_malloc_usage(self, op):
- if self.cpu.translate_support_code or isinstance(op.args[0], Variable):
- return # the operation disappears
- else:
- # only for untranslated tests: get a real integer estimate
- arg = op.args[0].value
- arg = llmemory.raw_malloc_usage(arg)
- return [Constant(arg, lltype.Signed)]
- def rewrite_op_jit_record_exact_class(self, op):
- return SpaceOperation("record_exact_class", [op.args[0], op.args[1]], None)
- def rewrite_op_cast_bool_to_int(self, op): pass
- def rewrite_op_cast_bool_to_uint(self, op): pass
- def rewrite_op_cast_char_to_int(self, op): pass
- def rewrite_op_cast_unichar_to_int(self, op): pass
- def rewrite_op_cast_int_to_char(self, op): pass
- def rewrite_op_cast_int_to_unichar(self, op): pass
- def rewrite_op_cast_int_to_uint(self, op): pass
- def rewrite_op_cast_uint_to_int(self, op): pass
- def _rewrite_symmetric(self, op):
- """Rewrite 'c1+v2' into 'v2+c1' in an attempt to avoid generating
- too many variants of the bytecode."""
- if (isinstance(op.args[0], Constant) and
- isinstance(op.args[1], Variable)):
- reversename = {'int_lt': 'int_gt',
- 'int_le': 'int_ge',
- 'int_gt': 'int_lt',
- 'int_ge': 'int_le',
- 'uint_lt': 'uint_gt',
- 'uint_le': 'uint_ge',
- 'uint_gt': 'uint_lt',
- 'uint_ge': 'uint_le',
- 'float_lt': 'float_gt',
- 'float_le': 'float_ge',
- 'float_gt': 'float_lt',
- 'float_ge': 'float_le',
- }.get(op.opname, op.opname)
- return SpaceOperation(reversename,
- [op.args[1], op.args[0]] + op.args[2:],
- op.result)
- else:
- return op
- rewrite_op_int_add = _rewrite_symmetric
- rewrite_op_int_mul = _rewrite_symmetric
- rewrite_op_int_and = _rewrite_symmetric
- rewrite_op_int_or = _rewrite_symmetric
- rewrite_op_int_xor = _rewrite_symmetric
- rewrite_op_int_lt = _rewrite_symmetric
- rewrite_op_int_le = _rewrite_symmetric
- rewrite_op_int_gt = _rewrite_symmetric
- rewrite_op_int_ge = _rewrite_symmetric
- rewrite_op_uint_lt = _rewrite_symmetric
- rewrite_op_uint_le = _rewrite_symmetric
- rewrite_op_uint_gt = _rewrite_symmetric
- rewrite_op_uint_ge = _rewrite_symmetric
- rewrite_op_float_add = _rewrite_symmetric
- rewrite_op_float_mul = _rewrite_symmetric
- rewrite_op_float_lt = _rewrite_symmetric
- rewrite_op_float_le = _rewrite_symmetric
- rewrite_op_float_gt = _rewrite_symmetric
- rewrite_op_float_ge = _rewrite_symmetric
- def rewrite_op_int_add_ovf(self, op):
- op0 = self._rewrite_symmetric(op)
- op1 = SpaceOperation('-live-', [], None)
- return [op1, op0]
- rewrite_op_int_mul_ovf = rewrite_op_int_add_ovf
- def rewrite_op_int_sub_ovf(self, op):
- op1 = SpaceOperation('-live-', [], None)
- return [op1, op]
- def _noop_rewrite(self, op):
- return op
- rewrite_op_convert_float_bytes_to_longlong = _noop_rewrite
- rewrite_op_convert_longlong_bytes_to_float = _noop_rewrite
- cast_ptr_to_weakrefptr = _noop_rewrite
- cast_weakrefptr_to_ptr = _noop_rewrite
- # ----------
- # Various kinds of calls
- def rewrite_op_direct_call(self, op):
- kind = self.callcontrol.guess_call_kind(op)
- return getattr(self, 'handle_%s_call' % kind)(op)
- def rewrite_op_indirect_call(self, op):
- kind = self.callcontrol.guess_call_kind(op)
- return getattr(self, 'handle_%s_indirect_call' % kind)(op)
- def rewrite_call(self, op, namebase, initialargs, args=None,
- calldescr=None):
- """Turn 'i0 = direct_call(fn, i1, i2, ref1, ref2)'
- into 'i0 = xxx_call_ir_i(fn, descr, [i1,i2], [ref1,ref2])'.
- The name is one of '{residual,direct}_call_{r,ir,irf}_{i,r,f,v}'."""
- if args is None:
- args = op.args[1:]
- self._check_no_vable_array(args)
- lst_i, lst_r, lst_f = self.make_three_lists(args)
- reskind = getkind(op.result.concretetype)[0]
- if lst_f or reskind == 'f': kinds = 'irf'
- elif lst_i: kinds = 'ir'
- else: kinds = 'r'
- sublists = []
- if 'i' in kinds: sublists.append(lst_i)
- if 'r' in kinds: sublists.append(lst_r)
- if 'f' in kinds: sublists.append(lst_f)
- if calldescr is not None:
- sublists.append(calldescr)
- return SpaceOperation('%s_%s_%s' % (namebase, kinds, reskind),
- initialargs + sublists, op.result)
- def make_three_lists(self, vars):
- args_i = []
- args_r = []
- args_f = []
- for v in vars:
- self.add_in_correct_list(v, args_i, args_r, args_f)
- return [ListOfKind('int', args_i),
- ListOfKind('ref', args_r),
- ListOfKind('float', args_f)]
- def add_in_correct_list(self, v, lst_i, lst_r, lst_f):
- kind = getkind(v.concretetype)
- if kind == 'void': return
- elif kind == 'int': lst = lst_i
- elif kind == 'ref': lst = lst_r
- elif kind == 'float': lst = lst_f
- else: raise AssertionError(kind)
- lst.append(v)
- def handle_residual_call(self, op, extraargs=[], may_call_jitcodes=False,
- oopspecindex=EffectInfo.OS_NONE,
- extraeffect=None,
- extradescr=None):
- """A direct_call turns into the operation 'residual_call_xxx' if it
- is calling a function that we don't want to JIT. The initial args
- of 'residual_call_xxx' are the function to call, and its calldescr."""
- calldescr = self.callcontrol.getcalldescr(op, oopspecindex=oopspecindex,
- extraeffect=extraeffect,
- extradescr=extradescr)
- op1 = self.rewrite_call(op, 'residual_call',
- [op.args[0]] + extraargs, calldescr=calldescr)
- if may_call_jitcodes or self.callcontrol.calldescr_canraise(calldescr):
- op1 = [op1, SpaceOperation('-live-', [], None)]
- return op1
- def handle_regular_call(self, op):
- """A direct_call turns into the operation 'inline_call_xxx' if it
- is calling a function that we want to JIT. The initial arg of
- 'inline_call_xxx' is the JitCode of the called function."""
- [targetgraph] = self.callcontrol.graphs_from(op)
- jitcode = self.callcontrol.get_jitcode(targetgraph,
- called_from=self.graph)
- op0 = self.rewrite_call(op, 'inline_call', [jitcode])
- op1 = SpaceOperation('-live-', [], None)
- return [op0, op1]
- def handle_builtin_call(self, op):
- oopspec_name, args = support.decode_builtin_call(op)
- # dispatch to various implementations depending on the oopspec_name
- if oopspec_name.startswith('list.') or oopspec_name.startswith('newlist'):
- prepare = self._handle_list_call
- elif oopspec_name.startswith('int.'):
- prepare = self._handle_int_special
- elif oopspec_name.startswith('stroruni.'):
- prepare = self._handle_stroruni_call
- elif oopspec_name == 'str.str2unicode':
- prepare = self._handle_str2unicode_call
- elif oopspec_name.startswith('virtual_ref'):
- prepare = self._handle_virtual_ref_call
- elif oopspec_name.startswith('jit.'):
- prepare = self._handle_jit_call
- elif oopspec_name.startswith('libffi_'):
- prepare = self._handle_libffi_call
- elif oopspec_name.startswith('math.sqrt'):
- prepare = self._handle_math_sqrt_call
- elif oopspec_name.startswith('rgc.'):
- prepare = self._handle_rgc_call
- elif oopspec_name.startswith('rvmprof.'):
- prepare = self._handle_rvmprof_call
- elif oopspec_name.endswith('dict.lookup'):
- # also ordereddict.lookup
- prepare = self._handle_dict_lookup_call
- else:
- prepare = self.prepare_builtin_call
- try:
- op1 = prepare(op, oopspec_name, args)
- except NotSupported:
- op1 = op
- # If the resulting op1 is still a direct_call, turn it into a
- # residual_call.
- if isinstance(op1, SpaceOperation) and op1.opname == 'direct_call':
- op1 = self.handle_residual_call(op1)
- return op1
- def handle_recursive_call(self, op):
- jitdriver_sd = self.callcontrol.jitdriver_sd_from_portal_runner_ptr(
- op.args[0].value)
- assert jitdriver_sd is not None
- ops = self.promote_greens(op.args[1:], jitdriver_sd.jitdriver)
- num_green_args = len(jitdriver_sd.jitdriver.greens)
- args = ([Constant(jitdriver_sd.index, lltype.Signed)] +
- self.make_three_lists(op.args[1:1+num_green_args]) +
- self.make_three_lists(op.args[1+num_green_args:]))
- kind = getkind(op.result.concretetype)[0]
- op0 = SpaceOperation('recursive_call_%s' % kind, args, op.result)
- op1 = SpaceOperation('-live-', [], None)
- return ops + [op0, op1]
- handle_residual_indirect_call = handle_residual_call
- def handle_regular_indirect_call(self, op):
- """An indirect call where at least one target has a JitCode."""
- lst = []
- for targetgraph in self.callcontrol.graphs_from(op):
- jitcode = self.callcontrol.get_jitcode(targetgraph,
- called_from=self.graph)
- lst.append(jitcode)
- op0 = SpaceOperation('-live-', [], None)
- op1 = SpaceOperation('int_guard_value', [op.args[0]], None)
- op2 = self.handle_residual_call(op, [IndirectCallTargets(lst)], True)
- result = [op0, op1]
- if isinstance(op2, list):
- result += op2
- else:
- result.append(op2)
- return result
- def prepare_builtin_call(self, op, oopspec_name, args,
- extra=None, extrakey=None):
- argtypes = [v.concretetype for v in args]
- resulttype = op.result.concretetype
- c_func, TP = support.builtin_func_for_spec(self.cpu.rtyper,
- oopspec_name, argtypes,
- resulttype, extra, extrakey)
- return SpaceOperation('direct_call', [c_func] + args, op.result)
- def _do_builtin_call(self, op, oopspec_name=None, args=None,
- extra=None, extrakey=None):
- if oopspec_name is None: oopspec_name = op.opname
- if args is None: args = op.args
- op1 = self.prepare_builtin_call(op, oopspec_name, args,
- extra, extrakey)
- return self.rewrite_op_direct_call(op1)
- # XXX some of the following functions should not become residual calls
- # but be really compiled
- rewrite_op_int_abs = _do_builtin_call
- rewrite_op_int_floordiv = _do_builtin_call
- rewrite_op_int_mod = _do_builtin_call
- rewrite_op_llong_abs = _do_builtin_call
- rewrite_op_llong_floordiv = _do_builtin_call
- rewrite_op_llong_mod = _do_builtin_call
- rewrite_op_ullong_floordiv = _do_builtin_call
- rewrite_op_ullong_mod = _do_builtin_call
- rewrite_op_gc_identityhash = _do_builtin_call
- rewrite_op_gc_id = _do_builtin_call
- rewrite_op_gc_pin = _do_builtin_call
- rewrite_op_gc_unpin = _do_builtin_call
- rewrite_op_cast_float_to_uint = _do_builtin_call
- rewrite_op_cast_uint_to_float = _do_builtin_call
- rewrite_op_weakref_create = _do_builtin_call
- rewrite_op_weakref_deref = _do_builtin_call
- rewrite_op_gc_add_memory_pressure = _do_builtin_call
- # ----------
- # getfield/setfield/mallocs etc.
- def rewrite_op_hint(self, op):
- hints = op.args[1].value
- # hack: if there are both 'promote' and 'promote_string', kill
- # one of them based on the type of the value
- if hints.get('promote_string') and hints.get('promote'):
- hints = hints.copy()
- if op.args[0].concretetype == lltype.Ptr(rstr.STR):
- del hints['promote']
- else:
- del hints['promote_string']
- if hints.get('promote') and op.args[0].concretetype is not lltype.Void:
- assert op.args[0].concretetype != lltype.Ptr(rstr.STR)
- kind = getkind(op.args[0].concretetype)
- op0 = SpaceOperation('-live-', [], None)
- op1 = SpaceOperation('%s_guard_value' % kind, [op.args[0]], None)
- # the special return value None forces op.result to be considered
- # equal to op.args[0]
- return [op0, op1, None]
- if (hints.get('promote_string') and
- op.args[0].concretetype is not lltype.Void):
- S = lltype.Ptr(rstr.STR)
- assert op.args[0].concretetype == S
- self._register_extra_helper(EffectInfo.OS_STREQ_NONNULL,
- "str.eq_nonnull",
- [S, S],
- lltype.Signed,
- EffectInfo.EF_ELIDABLE_CANNOT_RAISE)
- descr, p = self.callcontrol.callinfocollection.callinfo_for_oopspec(
- EffectInfo.OS_STREQ_NONNULL)
- # XXX this is fairly ugly way of creating a constant,
- # however, callinfocollection has no better interface
- c = Constant(p.adr.ptr, lltype.typeOf(p.adr.ptr))
- op1 = SpaceOperation('str_guard_value', [op.args[0], c, descr],
- op.result)
- return [SpaceOperation('-live-', [], None), op1, None]
- if hints.get('force_virtualizable'):
- return SpaceOperation('hint_force_virtualizable', [op.args[0]], None)
- if hints.get('force_no_const'): # for tests only
- assert getkind(op.args[0].concretetype) == 'int'
- return SpaceOperation('int_same_as', [op.args[0]], op.result)
- log.WARNING('ignoring hint %r at %r' % (hints, self.graph))
- def _rewrite_raw_malloc(self, op, name, args):
- d = op.args[1].value.copy()
- d.pop('flavor')
- add_memory_pressure = d.pop('add_memory_pressure', False)
- zero = d.pop('zero', False)
- track_allocation = d.pop('track_allocation', True)
- if d:
- raise UnsupportedMallocFlags(d)
- if zero:
- name += '_zero'
- if add_memory_pressure:
- name += '_add_memory_pressure'
- if not track_allocation:
- name += '_no_track_allocation'
- TYPE = op.args[0].value
- op1 = self.prepare_builtin_call(op, name, args, (TYPE,), TYPE)
- if name.startswith('raw_malloc_varsize') and TYPE.OF == lltype.Char:
- return self._handle_oopspec_call(op1, args,
- EffectInfo.OS_RAW_MALLOC_VARSIZE_CHAR,
- EffectInfo.EF_CAN_RAISE)
- return self.rewrite_op_direct_call(op1)
- def rewrite_op_malloc_varsize(self, op):
- if op.args[1].value['flavor'] == 'raw':
- return self._rewrite_raw_malloc(op, 'raw_malloc_varsize',
- [op.args[2]])
- if op.args[0].value == rstr.STR:
- return SpaceOperation('newstr', [op.args[2]], op.result)
- elif op.args[0].value == rstr.UNICODE:
- return SpaceOperation('newunicode', [op.args[2]], op.result)
- else:
- # XXX only strings or simple arrays for now
- ARRAY = op.args[0].value
- arraydescr = self.cpu.arraydescrof(ARRAY)
- if op.args[1].value.get('zero', False):
- opname = 'new_array_clear'
- elif ((isinstance(ARRAY.OF, lltype.Ptr) and ARRAY.OF._needsgc()) or
- isinstance(ARRAY.OF, lltype.Struct)):
- opname = 'new_array_clear'
- else:
- opname = 'new_array'
- return SpaceOperation(opname, [op.args[2], arraydescr], op.result)
- def zero_contents(self, ops, v, TYPE):
- if isinstance(TYPE, lltype.Struct):
- for name, FIELD in TYPE._flds.iteritems():
- if isinstance(FIELD, lltype.Struct):
- # substruct
- self.zero_contents(ops, v, FIELD)
- else:
- c_name = Constant(name, lltype.Void)
- c_null = Constant(FIELD._defl(), FIELD)
- op = SpaceOperation('setfield', [v, c_name, c_null],
- None)
- self.extend_with(ops, self.rewrite_op_setfield(op,
- override_type=TYPE))
- elif isinstance(TYPE, lltype.Array):
- assert False # this operation disappeared
- else:
- raise TypeError("Expected struct or array, got '%r'", (TYPE,))
- if len(ops) == 1:
- return ops[0]
- return ops
- def extend_with(self, l, ops):
- if ops is None:
- return
- if isinstance(ops, list):
- l.extend(ops)
- else:
- l.append(ops)
- def rewrite_op_free(self, op):
- d = op.args[1].value.copy()
- assert d['flavor'] == 'raw'
- d.pop('flavor')
- track_allocation = d.pop('track_allocation', True)
- if d:
- raise UnsupportedMallocFlags(d)
- STRUCT = op.args[0].concretetype.TO
- name = 'raw_free'
- if not track_allocation:
- name += '_no_track_allocation'
- op1 = self.prepare_builtin_call(op, name, [op.args[0]], (STRUCT,),
- STRUCT)
- if name.startswith('raw_free'):
- return self._handle_oopspec_call(op1, [op.args[0]],
- EffectInfo.OS_RAW_FREE,
- EffectInfo.EF_CANNOT_RAISE)
- return self.rewrite_op_direct_call(op1)
- def rewrite_op_getarrayitem(self, op):
- ARRAY = op.args[0].concretetype.TO
- if self._array_of_voids(ARRAY):
- return []
- if isinstance(ARRAY, lltype.FixedSizeArray):
- raise NotImplementedError(
- "%r uses %r, which is not supported by the JIT codewriter"
- % (self.graph, ARRAY))
- if op.args[0] in self.vable_array_vars: # for virtualizables
- vars = self.vable_array_vars[op.args[0]]
- (v_base, arrayfielddescr, arraydescr) = vars
- kind = getkind(op.result.concretetype)
- return [SpaceOperation('-live-', [], None),
- SpaceOperation('getarrayitem_vable_%s' % kind[0],
- [v_base, op.args[1], arrayfielddescr,
- arraydescr], op.result)]
- # normal case follows
- pure = ''
- immut = ARRAY._immutable_field(None)
- if immut:
- pure = '_pure'
- arraydescr = self.cpu.arraydescrof(ARRAY)
- kind = getkind(op.result.concretetype)
- if ARRAY._gckind != 'gc':
- assert ARRAY._gckind == 'raw'
- if kind == 'r':
- raise Exception("getarrayitem_raw_r not supported")
- pure = '' # always redetected from pyjitpl.py: we don't need
- # a '_pure' version of getarrayitem_raw
- return SpaceOperation('getarrayitem_%s_%s%s' % (ARRAY._gckind,
- kind[0], pure),
- [op.args[0], op.args[1], arraydescr],
- op.result)
- def rewrite_op_setarrayitem(self, op):
- ARRAY = op.args[0].concretetype.TO
- if self._array_of_voids(ARRAY):
- return []
- if isinstance(ARRAY, lltype.FixedSizeArray):
- raise NotImplementedError(
- "%r uses %r, which is not supported by the JIT codewriter"
- % (self.graph, ARRAY))
- if op.args[0] in self.vable_array_vars: # for virtualizables
- vars = self.vable_array_vars[op.args[0]]
- (v_base, arrayfielddescr, arraydescr) = vars
- kind = getkind(op.args[2].concretetype)
- return [SpaceOperation('-live-', [], None),
- SpaceOperation('setarrayitem_vable_%s' % kind[0],
- [v_base, op.args[1], op.args[2],
- arrayfielddescr, arraydescr], None)]
- arraydescr = self.cpu.arraydescrof(ARRAY)
- kind = getkind(op.args[2].concretetype)
- return SpaceOperation('setarrayitem_%s_%s' % (ARRAY._gckind, kind[0]),
- [op.args[0], op.args[1], op.args[2], arraydescr],
- None)
- def rewrite_op_getarraysize(self, op):
- ARRAY = op.args[0].concretetype.TO
- assert ARRAY._gckind == 'gc'
- if op.args[0] in self.vable_array_vars: # for virtualizables
- vars = self.vable_array_vars[op.args[0]]
- (v_base, arrayfielddescr, arraydescr) = vars
- return [SpaceOperation('-live-', [], None),
- SpaceOperation('arraylen_vable',
- [v_base, arrayfielddescr, arraydescr],
- op.result)]
- # normal case follows
- arraydescr = self.cpu.arraydescrof(ARRAY)
- return SpaceOperation('arraylen_gc', [op.args[0], arraydescr],
- op.result)
- def rewrite_op_getarraysubstruct(self, op):
- ARRAY = op.args[0].concretetype.TO
- assert ARRAY._gckind == 'raw'
- assert ARRAY._hints.get('nolength') is True
- return self.rewrite_op_direct_ptradd(op)
- def _array_of_voids(self, ARRAY):
- return ARRAY.OF == lltype.Void
- def rewrite_op_getfield(self, op):
- if self.is_typeptr_getset(op):
- return self.handle_getfield_typeptr(op)
- # turn the flow graph 'getfield' operation into our own version
- [v_inst, c_fieldname] = op.args
- RESULT = op.result.concretetype
- if RESULT is lltype.Void:
- return
- # check for virtualizable
- try:
- if self.is_virtualizable_getset(op):
- descr = self.get_virtualizable_field_descr(op)
- kind = getkind(RESULT)[0]
- return [SpaceOperation('-live-', [], None),
- SpaceOperation('getfield_vable_%s' % kind,
- [v_inst, descr], op.result)]
- except VirtualizableArrayField as e:
- # xxx hack hack hack
- vinfo = e.args[1]
- arrayindex = vinfo.array_field_counter[op.args[1].value]
- arrayfielddescr = vinfo.array_field_descrs[arrayindex]
- arraydescr = vinfo.array_descrs[arrayindex]
- self.vable_array_vars[op.result] = (op.args[0],
- arrayfielddescr,
- arraydescr)
- return []
- # check for _immutable_fields_ hints
- immut = v_inst.concretetype.TO._immutable_field(c_fieldname.value)
- need_live = False
- if immut:
- if (self.callcontrol is not None and
- self.callcontrol.could_be_green_field(v_inst.concretetype.TO,
- c_fieldname.value)):
- pure = '_greenfield'
- need_live = True
- else:
- pure = '_pure'
- else:
- pure = ''
- self.check_field_access(v_inst.concretetype.TO)
- argname = getattr(v_inst.concretetype.TO, '_gckind', 'gc')
- descr = self.cpu.fielddescrof(v_inst.concretetype.TO,
- c_fieldname.value)
- kind = getkind(RESULT)[0]
- if argname != 'gc':
- assert argname == 'raw'
- if (kind, pure) == ('r', ''):
- # note: a pure 'getfield_raw_r' is used e.g. to load class
- # attributes that are GC objects, so that one is supported.
- raise Exception("getfield_raw_r (without _pure) not supported")
- pure = '' # always redetected from pyjitpl.py: we don't need
- # a '_pure' version of getfield_raw
- #
- op1 = SpaceOperation('getfield_%s_%s%s' % (argname, kind, pure),
- [v_inst, descr], op.result)
- #
- if immut in (IR_QUASIIMMUTABLE, IR_QUASIIMMUTABLE_ARRAY):
- op1.opname += "_pure"
- descr1 = self.cpu.fielddescrof(
- v_inst.concretetype.TO,
- quasiimmut.get_mutate_field_name(c_fieldname.value))
- return [SpaceOperation('-live-', [], None),
- SpaceOperation('record_quasiimmut_field',
- [v_inst, descr, descr1], None),
- op1]
- if need_live:
- return [SpaceOperation('-live-', [], None), op1]
- return op1
- def rewrite_op_setfield(self, op, override_type=None):
- if self.is_typeptr_getset(op):
- # ignore the operation completely -- instead, it's done by 'new'
- return
- self._check_no_vable_array(op.args)
- # turn the flow graph 'setfield' operation into our own version
- [v_inst, c_fieldname, v_value] = op.args
- RESULT = v_value.concretetype
- if override_type is not None:
- TYPE = override_type
- else:
- TYPE = v_inst.concretetype.TO
- if RESULT is lltype.Void:
- return
- # check for virtualizable
- if self.is_virtualizable_getset(op):
- descr = self.get_virtualizable_field_descr(op)
- kind = getkind(RESULT)[0]
- return [SpaceOperation('-live-', [], None),
- SpaceOperation('setfield_vable_%s' % kind,
- [v_inst, v_value, descr], None)]
- self.check_field_access(TYPE)
- if override_type:
- argname = 'gc'
- else:
- argname = getattr(TYPE, '_gckind', 'gc')
- descr = self.cpu.fielddescrof(TYPE, c_fieldname.value)
- kind = getkind(RESULT)[0]
- if argname == 'raw' and kind == 'r':
- raise Exception("setfield_raw_r not supported")
- return SpaceOperation('setfield_%s_%s' % (argname, kind),
- [v_inst, v_value, descr],
- None)
- def rewrite_op_getsubstruct(self, op):
- STRUCT = op.args[0].concretetype.TO
- argname = getattr(STRUCT, '_gckind', 'gc')
- if argname != 'raw':
- raise Exception("%r: only supported for gckind=raw" % (op,))
- ofs = llmemory.offsetof(STRUCT, op.args[1].value)
- return SpaceOperation('int_add',
- [op.args[0], Constant(ofs, lltype.Signed)],
- op.result)
- def is_typeptr_getset(self, op):
- return (op.args[1].value == 'typeptr' and
- op.args[0].concretetype.TO._hints.get('typeptr'))
- def check_field_access(self, STRUCT):
- # check against a GcStruct with a nested GcStruct as a first argument
- # but which is not an object at all; see metainterp/test/test_loop,
- # test_regular_pointers_in_short_preamble.
- if not isinstance(STRUCT, lltype.GcStruct):
- return
- if STRUCT._first_struct() == (None, None):
- return
- PARENT = STRUCT
- while not PARENT._hints.get('typeptr'):
- _, PARENT = PARENT._first_struct()
- if PARENT is None:
- raise NotImplementedError("%r is a GcStruct using nesting but "
- "not inheriting from object" %
- (STRUCT,))
- def get_vinfo(self, v_virtualizable):
- if self.callcontrol is None: # for tests
- return None
- return self.callcontrol.get_vinfo(v_virtualizable.concretetype)
- def is_virtualizable_getset(self, op):
- # every access of an object of exactly the type VTYPEPTR is
- # likely to be a virtualizable access, but we still have to
- # check it in pyjitpl.py.
- vinfo = self.get_vinfo(op.args[0])
- if vinfo is None:
- return False
- res = False
- if op.args[1].value in vinfo.static_field_to_extra_box:
- res = True
- if op.args[1].value in vinfo.array_fields:
- res = VirtualizableArrayField(self.graph, vinfo)
- if res:
- flags = self.vable_flags[op.args[0]]
- if 'fresh_virtualizable' in flags:
- return False
- if isinstance(res, Exception):
- raise res
- return res
- def get_virtualizable_field_descr(self, op):
- fieldname = op.args[1].value
- vinfo = self.get_vinfo(op.args[0])
- index = vinfo.static_field_to_extra_box[fieldname]
- return vinfo.static_field_descrs[index]
- def handle_getfield_typeptr(self, op):
- if isinstance(op.args[0], Constant):
- cls = op.args[0].value.typeptr
- return Constant(cls, concretetype=rclass.CLASSTYPE)
- op0 = SpaceOperation('-live-', [], None)
- op1 = SpaceOperation('guard_class', [op.args[0]], op.result)
- return [op0, op1]
- def rewrite_op_malloc(self, op):
- d = op.args[1].value
- if d.get('nonmovable', False):
- raise UnsupportedMallocFlags(d)
- if d['flavor'] == 'raw':
- return self._rewrite_raw_malloc(op, 'raw_malloc_fixedsize', [])
- #
- if d.get('zero', False):
- zero = True
- else:
- zero = False
- STRUCT = op.args[0].value
- vtable = heaptracker.get_vtable_for_gcstruct(self.cpu, STRUCT)
- if vtable:
- # do we have a __del__?
- try:
- rtti = lltype.getRuntimeTypeInfo(STRUCT)
- except ValueError:
- pass
- else:
- if hasattr(rtti._obj, 'destructor_funcptr'):
- RESULT = lltype.Ptr(STRUCT)
- assert RESULT == op.result.concretetype
- return self._do_builtin_call(op, 'alloc_with_del', [],
- extra=(RESULT, vtable),
- extrakey=STRUCT)
- opname = 'new_with_vtable'
- else:
- opname = 'new'
- vtable = lltype.nullptr(rclass.OBJECT_VTABLE)
- sizedescr = self.cpu.sizeof(STRUCT, vtable)
- op1 = SpaceOperation(opname, [sizedescr], op.result)
- if zero:
- return self.zero_contents([op1], op.result, STRUCT)
- return op1
- def _has_gcptrs_in(self, STRUCT):
- if isinstance(STRUCT, lltype.Array):
- ITEM = STRUCT.OF
- if isinstance(ITEM, lltype.Struct):
- STRUCT = ITEM
- else:
- return isinstance(ITEM, lltype.Ptr) and ITEM._needsgc()
- for FIELD in STRUCT._flds.values():
- if isinstance(FIELD, lltype.Ptr) and FIELD._needsgc():
- return True
- elif isinstance(FIELD, lltype.Struct):
- if self._has_gcptrs_in(FIELD):
- return True
- return False
- def rewrite_op_getinteriorarraysize(self, op):
- # only supports strings and unicodes
- assert len(op.args) == 2
- assert op.args[1].value == 'chars'
- optype = op.args[0].concretetype
- if optype == lltype.Ptr(rstr.STR):
- opname = "strlen"
- elif optype == lltype.Ptr(rstr.UNICODE):
- opname = "unicodelen"
- elif optype == lltype.Ptr(rbytearray.BYTEARRAY):
- bytearraydescr = self.cpu.arraydescrof(rbytearray.BYTEARRAY)
- return SpaceOperation('arraylen_gc', [op.args[0], bytearraydescr],
- op.result)
- else:
- assert 0, "supported type %r" % (optype,)
- return SpaceOperation(opname, [op.args[0]], op.result)
- def rewrite_op_getinteriorfield(self, op):
- assert len(op.args) == 3
- optype = op.args[0].concretetype
- if optype == lltype.Ptr(rstr.STR):
- opname = "strgetitem"
- return SpaceOperation(opname, [op.args[0], op.args[2]], op.result)
- elif optype == lltype.Ptr(rstr.UNICODE):
- opname = "unicodegetitem"
- return SpaceOperation(opname, [op.args[0], op.args[2]], op.result)
- elif optype == lltype.Ptr(rbytearray.BYTEARRAY):
- bytearraydescr = self.cpu.arraydescrof(rbytearray.BYTEARRAY)
- v_index = op.args[2]
- return SpaceOperation('getarrayitem_gc_i',
- [op.args[0], v_index, bytearraydescr],
- op.result)
- elif op.result.concretetype is lltype.Void:
- return
- elif isinstance(op.args[0].concretetype.TO, lltype.GcArray):
- # special-case 1: GcArray of Struct
- v_inst, v_index, c_field = op.args
- STRUCT = v_inst.concretetype.TO.OF
- assert isinstance(STRUCT, lltype.Struct)
- descr = self.cpu.interiorfielddescrof(v_inst.concretetype.TO,
- c_field.value)
- args = [v_inst, v_index, descr]
- kind = getkind(op.result.concretetype)[0]
- return SpaceOperation('getinteriorfield_gc_%s' % kind, args,
- op.result)
- #elif isinstance(op.args[0].concretetype.TO, lltype.GcStruct):
- # # special-case 2: GcStruct with Array field
- # ---was added in the faster-rstruct branch,---
- # ---no longer directly supported---
- # v_inst, c_field, v_index = op.args
- # STRUCT = v_inst.concretetype.TO
- # ARRAY = getattr(STRUCT, c_field.value)
- # assert isinstance(ARRAY, lltype.Array)
- # arraydescr = self.cpu.arraydescrof(STRUCT)
- # kind = getkind(op.result.concretetype)[0]
- # assert kind in ('i', 'f')
- # return SpaceOperation('getarrayitem_gc_%s' % kind,
- # [op.args[0], v_index, arraydescr],
- # op.result)
- else:
- assert False, 'not supported'
- def rewrite_op_setinteriorfield(self, op):
- assert len(op.args) == 4
- optype = op.args[0].concretetype
- if optype == lltype.Ptr(rstr.STR):
- opname = "strsetitem"
- return SpaceOperation(opname, [op.args[0], op.args[2], op.args[3]],
- op.result)
- elif optype == lltype.Ptr(rstr.UNICODE):
- opname = "unicodesetitem"
- return SpaceOperation(opname, [op.args[0], op.args[2], op.args[3]],
- op.result)
- elif optype == lltype.Ptr(rbytearray.BYTEARRAY):
- bytearraydescr = self.cpu.arraydescrof(rbytearray.BYTEARRAY)
- opname = "setarrayitem_gc_i"
- return SpaceOperation(opname, [op.args[0], op.args[2], op.args[3],
- bytearraydescr], op.result)
- else:
- v_inst, v_index, c_field, v_value = op.args
- if v_value.concretetype is lltype.Void:
- return
- # only GcArray of Struct supported
- assert isinstance(v_inst.concretetype.TO, lltype.GcArray)
- STRUCT = v_inst.concretetype.TO.OF
- assert isinstance(STRUCT, lltype.Struct)
- descr = self.cpu.interiorfielddescrof(v_inst.concretetype.TO,
- c_field.value)
- kind = getkind(v_value.concretetype)[0]
- args = [v_inst, v_index, v_value, descr]
- return SpaceOperation('setinteriorfield_gc_%s' % kind, args,
- op.result)
- def rewrite_op_raw_store(self, op):
- T = op.args[2].concretetype
- kind = getkind(T)[0]
- assert kind != 'r'
- descr = self.cpu.arraydescrof(rffi.CArray(T))
- return SpaceOperation('raw_store_%s' % kind,
- [op.args[0], op.args[1], op.args[2], descr],
- None)
- def rewrite_op_raw_load(self, op):
- T = op.result.concretetype
- kind = getkind(T)[0]
- assert kind != 'r'
- descr = self.cpu.arraydescrof(rffi.CArray(T))
- return SpaceOperation('raw_load_%s' % kind,
- [op.args[0], op.args[1], descr], op.result)
- def rewrite_op_gc_load_indexed(self, op):
- T = op.result.concretetype
- kind = getkind(T)[0]
- assert kind != 'r'
- descr = self.cpu.arraydescrof(rffi.CArray(T))
- if (not isinstance(op.args[2], Constant) or
- not isinstance(op.args[3], Constant)):
- raise NotImplementedError("gc_load_indexed: 'scale' and 'base_ofs'"
- " should be constants")
- # xxx hard-code the size in bytes at translation time, which is
- # probably fine and avoids lots of issues later
- bytes = descr.get_item_size_in_bytes()
- if descr.is_item_signed():
- bytes = -bytes
- c_bytes = Constant(bytes, lltype.Signed)
- return SpaceOperation('gc_load_indexed_%s' % kind,
- [op.args[0], op.args[1],
- op.args[2], op.args[3], c_bytes], op.result)
- def _rewrite_equality(self, op, opname):
- arg0, arg1 = op.args
- if isinstance(arg0, Constant) and not arg0.value:
- return SpaceOperation(opname, [arg1], op.result)
- …
Large files files are truncated, but you can click here to view the full file