/pypy/translator/transform.py
Python | 273 lines | 197 code | 24 blank | 52 comment | 48 complexity | 58005c721be1770d221242688b339cd9 MD5 | raw file
- """Flow Graph Transformation
- The difference between simplification and transformation is that
- transformation is based on annotations; it runs after the annotator
- completed.
- """
- import types
- from pypy.objspace.flow.model import SpaceOperation
- from pypy.objspace.flow.model import Variable, Constant, Link
- from pypy.objspace.flow.model import c_last_exception, checkgraph
- from pypy.annotation import model as annmodel
- from pypy.rlib.rstack import stack_check
- from pypy.rpython.lltypesystem import lltype
- def checkgraphs(self, blocks):
- seen = {}
- for block in blocks:
- graph = self.annotated[block]
- if graph not in seen:
- checkgraph(graph)
- seen[graph] = True
- def fully_annotated_blocks(self):
- """Ignore blocked blocks."""
- for block, is_annotated in self.annotated.iteritems():
- if is_annotated:
- yield block
- # XXX: Lots of duplicated codes. Fix this!
- # [a] * b
- # -->
- # c = newlist(a)
- # d = mul(c, int b)
- # -->
- # d = alloc_and_set(b, a)
- def transform_allocate(self, block_subset):
- """Transforms [a] * b to alloc_and_set(b, a) where b is int."""
- for block in block_subset:
- length1_lists = {} # maps 'c' to 'a', in the above notation
- for i in range(len(block.operations)):
- op = block.operations[i]
- if (op.opname == 'newlist' and
- len(op.args) == 1):
- length1_lists[op.result] = op.args[0]
- elif (op.opname == 'mul' and
- op.args[0] in length1_lists and
- self.gettype(op.args[1]) is int):
- new_op = SpaceOperation('alloc_and_set',
- (op.args[1], length1_lists[op.args[0]]),
- op.result)
- block.operations[i] = new_op
- # lst += string[x:y]
- # -->
- # b = getslice(string, x, y)
- # c = inplace_add(lst, b)
- # -->
- # c = extend_with_str_slice(lst, x, y, string)
- def transform_extend_with_str_slice(self, block_subset):
- """Transforms lst += string[x:y] to extend_with_str_slice"""
- for block in block_subset:
- slice_sources = {} # maps b to [string, slice] in the above notation
- for i in range(len(block.operations)):
- op = block.operations[i]
- if (op.opname == 'getslice' and
- self.gettype(op.args[0]) is str):
- slice_sources[op.result] = op.args
- elif (op.opname == 'inplace_add' and
- op.args[1] in slice_sources and
- self.gettype(op.args[0]) is list):
- v_string, v_x, v_y = slice_sources[op.args[1]]
- new_op = SpaceOperation('extend_with_str_slice',
- [op.args[0], v_x, v_y, v_string],
- op.result)
- block.operations[i] = new_op
- # lst += char*count [or count*char]
- # -->
- # b = mul(char, count) [or count, char]
- # c = inplace_add(lst, b)
- # -->
- # c = extend_with_char_count(lst, char, count)
- def transform_extend_with_char_count(self, block_subset):
- """Transforms lst += char*count to extend_with_char_count"""
- for block in block_subset:
- mul_sources = {} # maps b to (char, count) in the above notation
- for i in range(len(block.operations)):
- op = block.operations[i]
- if op.opname == 'mul':
- s0 = self.binding(op.args[0], None)
- s1 = self.binding(op.args[1], None)
- if (isinstance(s0, annmodel.SomeChar) and
- isinstance(s1, annmodel.SomeInteger)):
- mul_sources[op.result] = op.args[0], op.args[1]
- elif (isinstance(s1, annmodel.SomeChar) and
- isinstance(s0, annmodel.SomeInteger)):
- mul_sources[op.result] = op.args[1], op.args[0]
- elif (op.opname == 'inplace_add' and
- op.args[1] in mul_sources and
- self.gettype(op.args[0]) is list):
- v_char, v_count = mul_sources[op.args[1]]
- new_op = SpaceOperation('extend_with_char_count',
- [op.args[0], v_char, v_count],
- op.result)
- block.operations[i] = new_op
- def transform_dead_op_vars(self, block_subset):
- # we redo the same simplification from simplify.py,
- # to kill dead (never-followed) links,
- # which can possibly remove more variables.
- from pypy.translator.simplify import transform_dead_op_vars_in_blocks
- transform_dead_op_vars_in_blocks(block_subset)
- def transform_dead_code(self, block_subset):
- """Remove dead code: these are the blocks that are not annotated at all
- because the annotation considered that no conditional jump could reach
- them."""
- for block in block_subset:
- for link in block.exits:
- if link not in self.links_followed:
- lst = list(block.exits)
- lst.remove(link)
- block.exits = tuple(lst)
- if not block.exits:
- # oups! cannot reach the end of this block
- cutoff_alwaysraising_block(self, block)
- elif block.exitswitch == c_last_exception:
- # exceptional exit
- if block.exits[0].exitcase is not None:
- # killed the non-exceptional path!
- cutoff_alwaysraising_block(self, block)
- if len(block.exits) == 1:
- block.exitswitch = None
- block.exits[0].exitcase = None
- def cutoff_alwaysraising_block(self, block):
- "Fix a block whose end can never be reached at run-time."
- # search the operation that cannot succeed
- can_succeed = [op for op in block.operations
- if op.result in self.bindings]
- cannot_succeed = [op for op in block.operations
- if op.result not in self.bindings]
- n = len(can_succeed)
- # check consistency
- assert can_succeed == block.operations[:n]
- assert cannot_succeed == block.operations[n:]
- assert 0 <= n < len(block.operations)
- # chop off the unreachable end of the block
- del block.operations[n+1:]
- s_impossible = annmodel.SomeImpossibleValue()
- self.bindings[block.operations[n].result] = s_impossible
- # insert the equivalent of 'raise AssertionError'
- graph = self.annotated[block]
- msg = "Call to %r should have raised an exception" % (getattr(graph, 'func', None),)
- c1 = Constant(AssertionError)
- c2 = Constant(AssertionError(msg))
- errlink = Link([c1, c2], graph.exceptblock)
- block.recloseblock(errlink, *block.exits)
- # record new link to make the transformation idempotent
- self.links_followed[errlink] = True
- # fix the annotation of the exceptblock.inputargs
- etype, evalue = graph.exceptblock.inputargs
- s_type = annmodel.SomeObject()
- s_type.knowntype = type
- s_type.is_type_of = [evalue]
- s_value = annmodel.SomeInstance(self.bookkeeper.getuniqueclassdef(Exception))
- self.setbinding(etype, s_type)
- self.setbinding(evalue, s_value)
- # make sure the bookkeeper knows about AssertionError
- self.bookkeeper.getuniqueclassdef(AssertionError)
- def insert_stackcheck(ann):
- from pypy.tool.algo.graphlib import Edge, make_edge_dict, break_cycles
- edges = []
- graphs_to_patch = {}
- for callposition, (caller, callee) in ann.translator.callgraph.items():
- if getattr(getattr(callee, 'func', None), 'insert_stack_check_here', False):
- graphs_to_patch[callee] = True
- continue
- edge = Edge(caller, callee)
- edge.callposition = callposition
- edges.append(edge)
- for graph in graphs_to_patch:
- v = Variable()
- ann.setbinding(v, annmodel.SomeImpossibleValue())
- unwind_op = SpaceOperation('simple_call', [Constant(stack_check)], v)
- graph.startblock.operations.insert(0, unwind_op)
- edgedict = make_edge_dict(edges)
- for edge in break_cycles(edgedict, edgedict):
- caller = edge.source
- _, _, call_tag = edge.callposition
- if call_tag:
- caller_block, _ = call_tag
- else:
- ann.warning("cycle detected but no information on where to insert "
- "stack_check()")
- continue
- # caller block found, insert stack_check()
- v = Variable()
- # push annotation on v
- ann.setbinding(v, annmodel.SomeImpossibleValue())
- unwind_op = SpaceOperation('simple_call', [Constant(stack_check)], v)
- caller_block.operations.insert(0, unwind_op)
- def insert_ll_stackcheck(translator):
- from pypy.translator.backendopt.support import find_calls_from
- from pypy.rlib.rstack import stack_check
- from pypy.tool.algo.graphlib import Edge, make_edge_dict, break_cycles_v
- rtyper = translator.rtyper
- graph = rtyper.annotate_helper(stack_check, [])
- rtyper.specialize_more_blocks()
- stack_check_ptr = rtyper.getcallable(graph)
- stack_check_ptr_const = Constant(stack_check_ptr, lltype.typeOf(stack_check_ptr))
- edges = set()
- insert_in = set()
- for caller in translator.graphs:
- for block, callee in find_calls_from(translator, caller):
- if getattr(getattr(callee, 'func', None),
- 'insert_stack_check_here', False):
- insert_in.add(callee.startblock)
- continue
- if block is not caller.startblock:
- edges.add((caller.startblock, block))
- edges.add((block, callee.startblock))
- edgelist = [Edge(block1, block2) for (block1, block2) in edges]
- edgedict = make_edge_dict(edgelist)
- for block in break_cycles_v(edgedict, edgedict):
- insert_in.add(block)
- for block in insert_in:
- v = Variable()
- v.concretetype = lltype.Void
- unwind_op = SpaceOperation('direct_call', [stack_check_ptr_const], v)
- block.operations.insert(0, unwind_op)
- return len(insert_in)
- default_extra_passes = [
- transform_allocate,
- transform_extend_with_str_slice,
- transform_extend_with_char_count,
- ]
- def transform_graph(ann, extra_passes=None, block_subset=None):
- """Apply set of transformations available."""
- # WARNING: this produces incorrect results if the graph has been
- # modified by t.simplify() after it had been annotated.
- if extra_passes is None:
- extra_passes = default_extra_passes
- if block_subset is None:
- block_subset = fully_annotated_blocks(ann)
- if not isinstance(block_subset, dict):
- block_subset = dict.fromkeys(block_subset)
- if ann.translator:
- checkgraphs(ann, block_subset)
- transform_dead_code(ann, block_subset)
- for pass_ in extra_passes:
- pass_(ann, block_subset)
- # do this last, after the previous transformations had a
- # chance to remove dependency on certain variables
- transform_dead_op_vars(ann, block_subset)
- if ann.translator:
- checkgraphs(ann, block_subset)
-