[pypy-commit] pypy jit-improve-nested-loops: hg merge default
hakanardo
noreply at buildbot.pypy.org
Mon Dec 26 16:57:29 CET 2011
Author: Hakan Ardo <hakan at debian.org>
Branch: jit-improve-nested-loops
Changeset: r50876:f75c6c5a133a
Date: 2011-12-26 13:49 +0100
http://bitbucket.org/pypy/pypy/changeset/f75c6c5a133a/
Log: hg merge default
diff --git a/pypy/annotation/description.py b/pypy/annotation/description.py
--- a/pypy/annotation/description.py
+++ b/pypy/annotation/description.py
@@ -180,7 +180,12 @@
if name is None:
name = pyobj.func_name
if signature is None:
- signature = cpython_code_signature(pyobj.func_code)
+ if hasattr(pyobj, '_generator_next_method_of_'):
+ from pypy.interpreter.argument import Signature
+ signature = Signature(['entry']) # haaaaaack
+ defaults = ()
+ else:
+ signature = cpython_code_signature(pyobj.func_code)
if defaults is None:
defaults = pyobj.func_defaults
self.name = name
diff --git a/pypy/interpreter/eval.py b/pypy/interpreter/eval.py
--- a/pypy/interpreter/eval.py
+++ b/pypy/interpreter/eval.py
@@ -98,7 +98,6 @@
"Abstract. Get the expected number of locals."
raise TypeError, "abstract"
- @jit.dont_look_inside
def fast2locals(self):
# Copy values from the fastlocals to self.w_locals
if self.w_locals is None:
@@ -112,7 +111,6 @@
w_name = self.space.wrap(name)
self.space.setitem(self.w_locals, w_name, w_value)
- @jit.dont_look_inside
def locals2fast(self):
# Copy values from self.w_locals to the fastlocals
assert self.w_locals is not None
diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py
--- a/pypy/jit/backend/x86/assembler.py
+++ b/pypy/jit/backend/x86/assembler.py
@@ -39,6 +39,7 @@
from pypy.jit.codewriter.effectinfo import EffectInfo
from pypy.jit.codewriter import longlong
from pypy.rlib.rarithmetic import intmask
+from pypy.rlib.objectmodel import compute_unique_id
# darwin requires the stack to be 16 bytes aligned on calls. Same for gcc 4.5.0,
# better safe than sorry
@@ -147,12 +148,13 @@
def finish_once(self):
if self._debug:
debug_start('jit-backend-counts')
- for struct in self.loop_run_counters:
- if struct.bridge:
- prefix = 'bridge '
+ for i in range(len(self.loop_run_counters)):
+ struct = self.loop_run_counters[i]
+ if not struct.bridge:
+ prefix = 'TargetToken(%d)' % struct.number
else:
- prefix = 'loop '
- debug_print(prefix + str(struct.number) + ':' + str(struct.i))
+ prefix = 'bridge ' + str(struct.number)
+ debug_print(prefix + ':' + str(struct.i))
debug_stop('jit-backend-counts')
def _build_float_constants(self):
@@ -422,8 +424,8 @@
self.setup(looptoken)
if log:
- self._register_counter(False, looptoken.number)
- operations = self._inject_debugging_code(looptoken, operations)
+ operations = self._inject_debugging_code(looptoken, operations,
+ False, looptoken.number)
regalloc = RegAlloc(self, self.cpu.translate_support_code)
#
@@ -489,8 +491,8 @@
self.setup(original_loop_token)
if log:
- self._register_counter(True, descr_number)
- operations = self._inject_debugging_code(faildescr, operations)
+ operations = self._inject_debugging_code(faildescr, operations,
+ True, descr_number)
arglocs = self.rebuild_faillocs_from_descr(failure_recovery)
if not we_are_translated():
@@ -597,17 +599,21 @@
return self.mc.materialize(self.cpu.asmmemmgr, allblocks,
self.cpu.gc_ll_descr.gcrootmap)
- def _register_counter(self, bridge, number):
- if self._debug:
- # YYY very minor leak -- we need the counters to stay alive
- # forever, just because we want to report them at the end
- # of the process
- struct = lltype.malloc(DEBUG_COUNTER, flavor='raw',
- track_allocation=False)
- struct.i = 0
- struct.bridge = int(bridge)
+ def _register_counter(self, bridge, number, token):
+ # YYY very minor leak -- we need the counters to stay alive
+ # forever, just because we want to report them at the end
+ # of the process
+ struct = lltype.malloc(DEBUG_COUNTER, flavor='raw',
+ track_allocation=False)
+ struct.i = 0
+ struct.bridge = int(bridge)
+ if bridge:
struct.number = number
- self.loop_run_counters.append(struct)
+ else:
+ assert token
+ struct.number = compute_unique_id(token)
+ self.loop_run_counters.append(struct)
+ return struct
def _find_failure_recovery_bytecode(self, faildescr):
adr_jump_offset = faildescr._x86_adr_jump_offset
@@ -651,27 +657,37 @@
targettoken._x86_loop_code += rawstart
self.target_tokens_currently_compiling = None
+ def _append_debugging_code(self, operations, bridge, number, token):
+ counter = self._register_counter(bridge, number, token)
+ c_adr = ConstInt(rffi.cast(lltype.Signed, counter))
+ box = BoxInt()
+ box2 = BoxInt()
+ ops = [ResOperation(rop.GETFIELD_RAW, [c_adr],
+ box, descr=self.debug_counter_descr),
+ ResOperation(rop.INT_ADD, [box, ConstInt(1)], box2),
+ ResOperation(rop.SETFIELD_RAW, [c_adr, box2],
+ None, descr=self.debug_counter_descr)]
+ operations.extend(ops)
+
@specialize.argtype(1)
- def _inject_debugging_code(self, looptoken, operations):
+ def _inject_debugging_code(self, looptoken, operations, bridge, number):
if self._debug:
# before doing anything, let's increase a counter
s = 0
for op in operations:
s += op.getopnum()
looptoken._x86_debug_checksum = s
- c_adr = ConstInt(rffi.cast(lltype.Signed,
- self.loop_run_counters[-1]))
- box = BoxInt()
- box2 = BoxInt()
- ops = [ResOperation(rop.GETFIELD_RAW, [c_adr],
- box, descr=self.debug_counter_descr),
- ResOperation(rop.INT_ADD, [box, ConstInt(1)], box2),
- ResOperation(rop.SETFIELD_RAW, [c_adr, box2],
- None, descr=self.debug_counter_descr)]
- if operations[0].getopnum() == rop.LABEL:
- operations = [operations[0]] + ops + operations[1:]
- else:
- operations = ops + operations
+
+ newoperations = []
+ if bridge:
+ self._append_debugging_code(newoperations, bridge, number,
+ None)
+ for op in operations:
+ newoperations.append(op)
+ if op.getopnum() == rop.LABEL:
+ self._append_debugging_code(newoperations, bridge, number,
+ op.getdescr())
+ operations = newoperations
return operations
def _assemble(self, regalloc, operations):
diff --git a/pypy/jit/backend/x86/test/test_runner.py b/pypy/jit/backend/x86/test/test_runner.py
--- a/pypy/jit/backend/x86/test/test_runner.py
+++ b/pypy/jit/backend/x86/test/test_runner.py
@@ -519,16 +519,23 @@
from pypy.tool.logparser import parse_log_file, extract_category
from pypy.rlib import debug
+ targettoken, preambletoken = TargetToken(), TargetToken()
loop = """
[i0]
- label(i0, descr=targettoken)
+ label(i0, descr=preambletoken)
debug_merge_point('xyz', 0)
i1 = int_add(i0, 1)
i2 = int_ge(i1, 10)
guard_false(i2) []
- jump(i1, descr=targettoken)
+ label(i1, descr=targettoken)
+ debug_merge_point('xyz', 0)
+ i11 = int_add(i1, 1)
+ i12 = int_ge(i11, 10)
+ guard_false(i12) []
+ jump(i11, descr=targettoken)
"""
- ops = parse(loop, namespace={'targettoken': TargetToken()})
+ ops = parse(loop, namespace={'targettoken': targettoken,
+ 'preambletoken': preambletoken})
debug._log = dlog = debug.DebugLog()
try:
self.cpu.assembler.set_debug(True)
@@ -537,11 +544,15 @@
self.cpu.execute_token(looptoken, 0)
# check debugging info
struct = self.cpu.assembler.loop_run_counters[0]
- assert struct.i == 10
+ assert struct.i == 1
+ struct = self.cpu.assembler.loop_run_counters[1]
+ assert struct.i == 9
self.cpu.finish_once()
finally:
debug._log = None
- assert ('jit-backend-counts', [('debug_print', 'loop -1:10')]) in dlog
+ l1 = ('debug_print', preambletoken.repr_of_descr() + ':1')
+ l2 = ('debug_print', targettoken.repr_of_descr() + ':9')
+ assert ('jit-backend-counts', [l1, l2]) in dlog
def test_debugger_checksum(self):
loop = """
diff --git a/pypy/jit/backend/x86/test/test_zrpy_platform.py b/pypy/jit/backend/x86/test/test_zrpy_platform.py
--- a/pypy/jit/backend/x86/test/test_zrpy_platform.py
+++ b/pypy/jit/backend/x86/test/test_zrpy_platform.py
@@ -74,8 +74,8 @@
myjitdriver = jit.JitDriver(greens = [], reds = ['n'])
def entrypoint(argv):
- myjitdriver.set_param('threshold', 2)
- myjitdriver.set_param('trace_eagerness', 0)
+ jit.set_param(myjitdriver, 'threshold', 2)
+ jit.set_param(myjitdriver, 'trace_eagerness', 0)
n = 16
while n > 0:
myjitdriver.can_enter_jit(n=n)
diff --git a/pypy/jit/codewriter/call.py b/pypy/jit/codewriter/call.py
--- a/pypy/jit/codewriter/call.py
+++ b/pypy/jit/codewriter/call.py
@@ -42,8 +42,7 @@
except AttributeError:
pass
- def is_candidate(graph):
- return policy.look_inside_graph(graph)
+ is_candidate = policy.look_inside_graph
assert len(self.jitdrivers_sd) > 0
todo = [jd.portal_graph for jd in self.jitdrivers_sd]
diff --git a/pypy/jit/metainterp/history.py b/pypy/jit/metainterp/history.py
--- a/pypy/jit/metainterp/history.py
+++ b/pypy/jit/metainterp/history.py
@@ -1007,25 +1007,6 @@
# a jump back to itself and possibly a few bridges ending with finnish.
# Only the operations within the loop formed by that single jump will
# be counted.
-
- # XXX hacked version, ignore and remove me when jit-targets is merged.
- loops = self.get_all_loops()
- loops = [loop for loop in loops if 'Preamble' not in repr(loop)] #XXX
- assert len(loops) == 1
- loop, = loops
- jumpop = loop.operations[-1]
- assert jumpop.getopnum() == rop.JUMP
- insns = {}
- for op in loop.operations:
- opname = op.getopname()
- insns[opname] = insns.get(opname, 0) + 1
- return self._check_insns(insns, expected, check)
-
- def check_simple_loop(self, expected=None, **check):
- # Usefull in the simplest case when we have only one trace ending with
- # a jump back to itself and possibly a few bridges ending with finnish.
- # Only the operations within the loop formed by that single jump will
- # be counted.
loops = self.get_all_loops()
assert len(loops) == 1
loop = loops[0]
diff --git a/pypy/jit/metainterp/optimizeopt/test/test_multilabel.py b/pypy/jit/metainterp/optimizeopt/test/test_multilabel.py
--- a/pypy/jit/metainterp/optimizeopt/test/test_multilabel.py
+++ b/pypy/jit/metainterp/optimizeopt/test/test_multilabel.py
@@ -1,10 +1,13 @@
from __future__ import with_statement
from pypy.jit.metainterp.optimizeopt.test.test_util import (
- LLtypeMixin, BaseTest, Storage, _sortboxes, FakeDescrWithSnapshot)
+ LLtypeMixin, BaseTest, Storage, _sortboxes, FakeDescrWithSnapshot,
+ FakeMetaInterpStaticData)
from pypy.jit.metainterp.history import TreeLoop, JitCellToken, TargetToken
from pypy.jit.metainterp.resoperation import rop, opname, ResOperation
from pypy.jit.metainterp.optimize import InvalidLoop
from py.test import raises
+from pypy.jit.metainterp.optimizeopt.optimizer import Optimization
+from pypy.jit.metainterp.optimizeopt.util import make_dispatcher_method
class BaseTestMultiLabel(BaseTest):
enable_opts = "intbounds:rewrite:virtualize:string:earlyforce:pure:heap:unroll"
@@ -84,6 +87,8 @@
return optimized
+class OptimizeoptTestMultiLabel(BaseTestMultiLabel):
+
def test_simple(self):
ops = """
[i1]
@@ -381,6 +386,55 @@
"""
self.optimize_loop(ops, expected)
-class TestLLtype(BaseTestMultiLabel, LLtypeMixin):
+
+class OptRenameStrlen(Optimization):
+ def propagate_forward(self, op):
+ dispatch_opt(self, op)
+
+ def optimize_STRLEN(self, op):
+ newop = op.clone()
+ newop.result = op.result.clonebox()
+ self.emit_operation(newop)
+ self.make_equal_to(op.result, self.getvalue(newop.result))
+
+dispatch_opt = make_dispatcher_method(OptRenameStrlen, 'optimize_',
+ default=OptRenameStrlen.emit_operation)
+
+class BaseTestOptimizerRenamingBoxes(BaseTestMultiLabel):
+
+ def _do_optimize_loop(self, loop, call_pure_results):
+ from pypy.jit.metainterp.optimizeopt.unroll import optimize_unroll
+ from pypy.jit.metainterp.optimizeopt.util import args_dict
+ from pypy.jit.metainterp.optimizeopt.pure import OptPure
+
+ self.loop = loop
+ loop.call_pure_results = args_dict()
+ metainterp_sd = FakeMetaInterpStaticData(self.cpu)
+ optimize_unroll(metainterp_sd, loop, [OptRenameStrlen(), OptPure()], True)
+
+ def test_optimizer_renaming_boxes(self):
+ ops = """
+ [p1]
+ i1 = strlen(p1)
+ label(p1)
+ i2 = strlen(p1)
+ i3 = int_add(i2, 7)
+ jump(p1)
+ """
+ expected = """
+ [p1]
+ i1 = strlen(p1)
+ label(p1, i1)
+ i11 = same_as(i1)
+ i2 = int_add(i11, 7)
+ jump(p1, i11)
+ """
+ self.optimize_loop(ops, expected)
+
+
+
+class TestLLtype(OptimizeoptTestMultiLabel, LLtypeMixin):
pass
+class TestOptimizerRenamingBoxesLLtype(BaseTestOptimizerRenamingBoxes, LLtypeMixin):
+ pass
diff --git a/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py
--- a/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py
+++ b/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py
@@ -7759,7 +7759,7 @@
jump(i0, p0, i2)
"""
self.optimize_loop(ops, expected)
-
+
class TestLLtype(OptimizeOptTest, LLtypeMixin):
pass
diff --git a/pypy/jit/metainterp/optimizeopt/unroll.py b/pypy/jit/metainterp/optimizeopt/unroll.py
--- a/pypy/jit/metainterp/optimizeopt/unroll.py
+++ b/pypy/jit/metainterp/optimizeopt/unroll.py
@@ -265,7 +265,12 @@
self.optimizer.importable_values[value] = imp
newvalue = self.optimizer.getvalue(op.result)
newresult = newvalue.get_key_box()
- assert newresult is op.result or newvalue.is_constant()
+ # note that emitting here SAME_AS should not happen, but
+ # in case it does, we would prefer to be suboptimal in asm
+ # to a fatal RPython exception.
+ if newresult is not op.result and not newvalue.is_constant():
+ op = ResOperation(rop.SAME_AS, [op.result], newresult)
+ self.optimizer._newoperations.append(op)
self.optimizer.flush()
self.optimizer.emitting_dissabled = False
diff --git a/pypy/module/cpyext/methodobject.py b/pypy/module/cpyext/methodobject.py
--- a/pypy/module/cpyext/methodobject.py
+++ b/pypy/module/cpyext/methodobject.py
@@ -58,6 +58,7 @@
class W_PyCFunctionObject(Wrappable):
def __init__(self, space, ml, w_self, w_module=None):
self.ml = ml
+ self.name = rffi.charp2str(self.ml.c_ml_name)
self.w_self = w_self
self.w_module = w_module
@@ -69,7 +70,7 @@
flags &= ~(METH_CLASS | METH_STATIC | METH_COEXIST)
if space.is_true(w_kw) and not flags & METH_KEYWORDS:
raise OperationError(space.w_TypeError, space.wrap(
- rffi.charp2str(self.ml.c_ml_name) + "() takes no keyword arguments"))
+ self.name + "() takes no keyword arguments"))
func = rffi.cast(PyCFunction, self.ml.c_ml_meth)
length = space.int_w(space.len(w_args))
@@ -80,13 +81,12 @@
if length == 0:
return generic_cpy_call(space, func, w_self, None)
raise OperationError(space.w_TypeError, space.wrap(
- rffi.charp2str(self.ml.c_ml_name) + "() takes no arguments"))
+ self.name + "() takes no arguments"))
elif flags & METH_O:
if length != 1:
raise OperationError(space.w_TypeError,
space.wrap("%s() takes exactly one argument (%d given)" % (
- rffi.charp2str(self.ml.c_ml_name),
- length)))
+ self.name, length)))
w_arg = space.getitem(w_args, space.wrap(0))
return generic_cpy_call(space, func, w_self, w_arg)
elif flags & METH_VARARGS:
@@ -199,6 +199,7 @@
__call__ = interp2app(cfunction_descr_call),
__doc__ = GetSetProperty(W_PyCFunctionObject.get_doc),
__module__ = interp_attrproperty_w('w_module', cls=W_PyCFunctionObject),
+ __name__ = interp_attrproperty('name', cls=W_PyCFunctionObject),
)
W_PyCFunctionObject.typedef.acceptable_as_base_class = False
diff --git a/pypy/module/cpyext/test/test_methodobject.py b/pypy/module/cpyext/test/test_methodobject.py
--- a/pypy/module/cpyext/test/test_methodobject.py
+++ b/pypy/module/cpyext/test/test_methodobject.py
@@ -63,6 +63,7 @@
),
])
assert mod.getarg_O(1) == 1
+ assert mod.getarg_O.__name__ == "getarg_O"
raises(TypeError, mod.getarg_O)
raises(TypeError, mod.getarg_O, 1, 1)
diff --git a/pypy/objspace/flow/flowcontext.py b/pypy/objspace/flow/flowcontext.py
--- a/pypy/objspace/flow/flowcontext.py
+++ b/pypy/objspace/flow/flowcontext.py
@@ -185,7 +185,7 @@
class FlowExecutionContext(ExecutionContext):
def __init__(self, space, code, globals, constargs={}, outer_func=None,
- name=None):
+ name=None, is_generator=False):
ExecutionContext.__init__(self, space)
self.code = code
@@ -208,6 +208,7 @@
initialblock = SpamBlock(FrameState(frame).copy())
self.pendingblocks = collections.deque([initialblock])
self.graph = FunctionGraph(name or code.co_name, initialblock)
+ self.is_generator = is_generator
make_link = Link # overridable for transition tracking
@@ -247,6 +248,8 @@
return outcome, w_exc_cls, w_exc_value
def build_flow(self):
+ if self.is_generator:
+ self.produce_generator_mark()
while self.pendingblocks:
block = self.pendingblocks.popleft()
frame = self.create_frame()
@@ -259,9 +262,15 @@
self.topframeref = jit.non_virtual_ref(frame)
self.crnt_frame = frame
try:
- w_result = frame.dispatch(frame.pycode,
- frame.last_instr,
- self)
+ frame.frame_finished_execution = False
+ while True:
+ w_result = frame.dispatch(frame.pycode,
+ frame.last_instr,
+ self)
+ if frame.frame_finished_execution:
+ break
+ else:
+ self.generate_yield(frame, w_result)
finally:
self.crnt_frame = None
self.topframeref = old_frameref
@@ -307,6 +316,21 @@
del self.recorder
self.fixeggblocks()
+ def produce_generator_mark(self):
+ [initialblock] = self.pendingblocks
+ initialblock.operations.append(
+ SpaceOperation('generator_mark', [], Variable()))
+
+ def generate_yield(self, frame, w_result):
+ assert self.is_generator
+ self.recorder.crnt_block.operations.append(
+ SpaceOperation('yield', [w_result], Variable()))
+ # we must push a dummy value that will be POPped: it's the .send()
+ # passed into the generator (2.5 feature)
+ assert sys.version_info >= (2, 5)
+ frame.pushvalue(None)
+ frame.last_instr += 1
+
def fixeggblocks(self):
# EggBlocks reuse the variables of their previous block,
# which is deemed not acceptable for simplicity of the operations
diff --git a/pypy/objspace/flow/objspace.py b/pypy/objspace/flow/objspace.py
--- a/pypy/objspace/flow/objspace.py
+++ b/pypy/objspace/flow/objspace.py
@@ -8,6 +8,7 @@
from pypy.interpreter.pycode import PyCode, cpython_code_signature
from pypy.interpreter.module import Module
from pypy.interpreter.error import OperationError
+from pypy.interpreter.astcompiler.consts import CO_GENERATOR
from pypy.interpreter import pyframe, argument
from pypy.objspace.flow.model import *
from pypy.objspace.flow import flowcontext, operation, specialcase
@@ -247,15 +248,13 @@
return ecls
return None
- def build_flow(self, func, constargs={}):
+ def build_flow(self, func, constargs={}, tweak_for_generator=True):
"""
"""
if func.func_doc and func.func_doc.lstrip().startswith('NOT_RPYTHON'):
raise Exception, "%r is tagged as NOT_RPYTHON" % (func,)
code = func.func_code
- if code.co_flags & 32:
- # generator
- raise TypeError("%r is a generator" % (func,))
+ is_generator = bool(code.co_flags & CO_GENERATOR)
code = PyCode._from_code(self, code)
if func.func_closure is None:
cl = None
@@ -271,7 +270,8 @@
class outerfunc: # hack
closure = cl
ec = flowcontext.FlowExecutionContext(self, code, func.func_globals,
- constargs, outerfunc, name)
+ constargs, outerfunc, name,
+ is_generator)
graph = ec.graph
graph.func = func
# attach a signature and defaults to the graph
@@ -291,6 +291,11 @@
e = error.FlowingError(formated)
raise error.FlowingError, e, tb
checkgraph(graph)
+ #
+ if is_generator and tweak_for_generator:
+ from pypy.translator.generator import tweak_generator_graph
+ tweak_generator_graph(graph)
+ #
return graph
def fixedview(self, w_tuple, expected_length=None):
diff --git a/pypy/objspace/flow/test/test_generator.py b/pypy/objspace/flow/test/test_generator.py
new file mode 100644
--- /dev/null
+++ b/pypy/objspace/flow/test/test_generator.py
@@ -0,0 +1,18 @@
+from pypy.objspace.flow.test.test_objspace import Base
+
+
+class TestGenerator(Base):
+
+ def test_simple_generator(self):
+ def f(n):
+ i = 0
+ while i < n:
+ yield i
+ yield i
+ i += 1
+ graph = self.codetest(f, tweak_for_generator=False)
+ ops = self.all_operations(graph)
+ assert ops == {'generator_mark': 1,
+ 'lt': 1, 'is_true': 1,
+ 'yield': 2,
+ 'inplace_add': 1}
diff --git a/pypy/objspace/flow/test/test_objspace.py b/pypy/objspace/flow/test/test_objspace.py
--- a/pypy/objspace/flow/test/test_objspace.py
+++ b/pypy/objspace/flow/test/test_objspace.py
@@ -16,14 +16,14 @@
is_operator = getattr(operator, 'is_', operator.eq) # it's not there 2.2
class Base:
- def codetest(self, func):
+ def codetest(self, func, **kwds):
import inspect
try:
func = func.im_func
except AttributeError:
pass
#name = func.func_name
- graph = self.space.build_flow(func)
+ graph = self.space.build_flow(func, **kwds)
graph.source = inspect.getsource(func)
self.show(graph)
return graph
@@ -882,12 +882,6 @@
num = bytecode_spec.opmap[name]
flow_meth_names[num] = locals()['old_' + name]
- def test_generator(self):
- def f():
- yield 3
-
- py.test.raises(TypeError, "self.codetest(f)")
-
def test_dont_capture_RuntimeError(self):
class Foo:
def __hash__(self):
diff --git a/pypy/rpython/test/test_generator.py b/pypy/rpython/test/test_generator.py
new file mode 100644
--- /dev/null
+++ b/pypy/rpython/test/test_generator.py
@@ -0,0 +1,62 @@
+from pypy.rpython.test.tool import BaseRtypingTest, LLRtypeMixin, OORtypeMixin
+
+
+class BaseTestGenerator(BaseRtypingTest):
+
+ def test_simple_explicit(self):
+ def g(a, b, c):
+ yield a
+ yield b
+ yield c
+ def f():
+ gen = g(3, 5, 8)
+ x = gen.next() * 100
+ x += gen.next() * 10
+ x += gen.next()
+ return x
+ res = self.interpret(f, [])
+ assert res == 358
+
+ def test_cannot_merge(self):
+ # merging two different generators is not supported
+ # right now, but we can use workarounds like here
+ class MyGen:
+ _immutable_ = True
+ def next(self):
+ raise NotImplementedError
+ class MyG1(MyGen):
+ _immutable_ = True
+ def __init__(self, a):
+ self._gen = self.g1(a)
+ def next(self):
+ return self._gen.next()
+ @staticmethod
+ def g1(a):
+ yield a + 1
+ yield a + 2
+ class MyG2(MyGen):
+ _immutable_ = True
+ def __init__(self):
+ self._gen = self.g2()
+ def next(self):
+ return self._gen.next()
+ @staticmethod
+ def g2():
+ yield 42
+ def f(n):
+ if n > 0:
+ gen = MyG1(n)
+ else:
+ gen = MyG2()
+ return gen.next()
+ res = self.interpret(f, [10])
+ assert res == 11
+ res = self.interpret(f, [0])
+ assert res == 42
+
+
+class TestLLtype(BaseTestGenerator, LLRtypeMixin):
+ pass
+
+class TestOOtype(BaseTestGenerator, OORtypeMixin):
+ pass
diff --git a/pypy/tool/jitlogparser/parser.py b/pypy/tool/jitlogparser/parser.py
--- a/pypy/tool/jitlogparser/parser.py
+++ b/pypy/tool/jitlogparser/parser.py
@@ -3,6 +3,7 @@
from pypy.jit.metainterp.resoperation import opname
from pypy.jit.tool.oparser import OpParser
from pypy.tool.logparser import parse_log_file, extract_category
+from copy import copy
class Op(object):
bridge = None
@@ -387,6 +388,18 @@
loops.append(loop)
return log, loops
+def split_trace(trace):
+ labels = [i for i, op in enumerate(trace.operations)
+ if op.name == 'label']
+ labels = [0] + labels + [len(trace.operations) - 1]
+ parts = []
+ for i in range(len(labels) - 1):
+ start, stop = labels[i], labels[i+1]
+ part = copy(trace)
+ part.operations = trace.operations[start : stop + 1]
+ parts.append(part)
+
+ return parts
def parse_log_counts(input, loops):
if not input:
diff --git a/pypy/tool/jitlogparser/test/test_modulefinder.py b/pypy/tool/jitlogparser/test/test_modulefinder.py
--- a/pypy/tool/jitlogparser/test/test_modulefinder.py
+++ b/pypy/tool/jitlogparser/test/test_modulefinder.py
@@ -7,12 +7,14 @@
py.test.skip("Specific python 2.6 tests")
def test_gather_code_py():
+ py.test.skip("XXX broken, fix me")
fname = re.__file__
codes = gather_all_code_objs(fname)
assert len(codes) == 21
assert sorted(codes.keys()) == [102, 134, 139, 144, 153, 164, 169, 181, 188, 192, 197, 206, 229, 251, 266, 271, 277, 285, 293, 294, 308]
def test_load_code():
+ py.test.skip("XXX broken, fix me")
fname = re.__file__
code = gather_all_code_objs(fname)[144]
assert code.co_name == 'sub'
diff --git a/pypy/tool/jitlogparser/test/test_parser.py b/pypy/tool/jitlogparser/test/test_parser.py
--- a/pypy/tool/jitlogparser/test/test_parser.py
+++ b/pypy/tool/jitlogparser/test/test_parser.py
@@ -1,6 +1,6 @@
from pypy.tool.jitlogparser.parser import (SimpleParser, TraceForOpcode,
Function, adjust_bridges,
- import_log, Op)
+ import_log, split_trace, Op)
from pypy.tool.jitlogparser.storage import LoopStorage
import py, sys
@@ -231,3 +231,21 @@
myrepr = 'c = foobar(a, b, descr=mydescr)'
assert op.repr() == myrepr
assert op.repr() == myrepr # do it twice
+
+def test_split_trace():
+ loop = parse('''
+ [i7]
+ i9 = int_lt(i7, 1003)
+ label(i9)
+ guard_true(i9, descr=<Guard2>) []
+ i13 = getfield_raw(151937600, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+ label(i13)
+ i19 = int_lt(i13, 1003)
+ guard_true(i19, descr=<Guard2>) []
+ i113 = getfield_raw(151937600, descr=<SignedFieldDescr pypysig_long_struct.c_value 0>)
+ ''')
+ parts = split_trace(loop)
+ assert len(parts) == 3
+ assert len(parts[0].operations) == 2
+ assert len(parts[1].operations) == 4
+ assert len(parts[2].operations) == 4
diff --git a/pypy/translator/generator.py b/pypy/translator/generator.py
new file mode 100644
--- /dev/null
+++ b/pypy/translator/generator.py
@@ -0,0 +1,166 @@
+from pypy.objspace.flow.model import Block, Link, SpaceOperation, checkgraph
+from pypy.objspace.flow.model import Variable, Constant, FunctionGraph
+from pypy.translator.unsimplify import insert_empty_startblock
+from pypy.translator.unsimplify import split_block
+from pypy.translator.simplify import eliminate_empty_blocks
+from pypy.tool.sourcetools import func_with_new_name
+from pypy.interpreter.argument import Signature
+
+
+class AbstractPosition(object):
+ _immutable_ = True
+ _attrs_ = ()
+
+
+def tweak_generator_graph(graph):
+ if not hasattr(graph.func, '_generator_next_method_of_'):
+ # This is the first copy of the graph. We replace it with
+ # a small bootstrap graph.
+ GeneratorIterator = make_generatoriterator_class(graph)
+ replace_graph_with_bootstrap(GeneratorIterator, graph)
+ # We attach a 'next' method to the GeneratorIterator class
+ # that will invoke the real function, based on a second
+ # copy of the graph.
+ attach_next_method(GeneratorIterator, graph)
+ else:
+ # This is the second copy of the graph. Tweak it.
+ GeneratorIterator = graph.func._generator_next_method_of_
+ tweak_generator_body_graph(GeneratorIterator.Entry, graph)
+
+
+def make_generatoriterator_class(graph):
+ class GeneratorIterator(object):
+ class Entry(AbstractPosition):
+ _immutable_ = True
+ varnames = get_variable_names(graph.startblock.inputargs)
+ def __init__(self, entry):
+ self.current = entry
+ return GeneratorIterator
+
+def replace_graph_with_bootstrap(GeneratorIterator, graph):
+ Entry = GeneratorIterator.Entry
+ newblock = Block(graph.startblock.inputargs)
+ v_generator = Variable('generator')
+ v_entry = Variable('entry')
+ newblock.operations.append(
+ SpaceOperation('simple_call', [Constant(Entry)], v_entry))
+ assert len(graph.startblock.inputargs) == len(Entry.varnames)
+ for v, name in zip(graph.startblock.inputargs, Entry.varnames):
+ newblock.operations.append(
+ SpaceOperation('setattr', [v_entry, Constant(name), v],
+ Variable()))
+ newblock.operations.append(
+ SpaceOperation('simple_call', [Constant(GeneratorIterator), v_entry],
+ v_generator))
+ newblock.closeblock(Link([v_generator], graph.returnblock))
+ graph.startblock = newblock
+
+def attach_next_method(GeneratorIterator, graph):
+ func = graph.func
+ func = func_with_new_name(func, '%s__next' % (func.func_name,))
+ func._generator_next_method_of_ = GeneratorIterator
+ func._always_inline_ = True
+ #
+ def next(self):
+ entry = self.current
+ self.current = None
+ (next_entry, return_value) = func(entry)
+ self.current = next_entry
+ return return_value
+ GeneratorIterator.next = next
+ return func # for debugging
+
+def get_variable_names(variables):
+ seen = set()
+ result = []
+ for v in variables:
+ name = v._name.strip('_')
+ while name in seen:
+ name += '_'
+ result.append('g_' + name)
+ seen.add(name)
+ return result
+
+def _insert_reads(block, varnames):
+ assert len(varnames) == len(block.inputargs)
+ v_entry1 = Variable('entry')
+ for i, name in enumerate(varnames):
+ block.operations.insert(i,
+ SpaceOperation('getattr', [v_entry1, Constant(name)],
+ block.inputargs[i]))
+ block.inputargs = [v_entry1]
+
+def tweak_generator_body_graph(Entry, graph):
+ assert graph.startblock.operations[0].opname == 'generator_mark'
+ graph.startblock.operations.pop(0)
+ #
+ insert_empty_startblock(None, graph)
+ _insert_reads(graph.startblock, Entry.varnames)
+ Entry.block = graph.startblock
+ #
+ mappings = [Entry]
+ #
+ for block in list(graph.iterblocks()):
+ for exit in block.exits:
+ if exit.target is graph.returnblock:
+ exit.args = [Constant(StopIteration),
+ Constant(StopIteration())]
+ exit.target = graph.exceptblock
+ for index in range(len(block.operations)-1, -1, -1):
+ op = block.operations[index]
+ if op.opname == 'yield':
+ [v_yielded_value] = op.args
+ del block.operations[index]
+ newlink = split_block(None, block, index)
+ newblock = newlink.target
+ #
+ class Resume(AbstractPosition):
+ _immutable_ = True
+ block = newblock
+ Resume.__name__ = 'Resume%d' % len(mappings)
+ mappings.append(Resume)
+ varnames = get_variable_names(newlink.args)
+ #
+ _insert_reads(newblock, varnames)
+ #
+ v_resume = Variable('resume')
+ block.operations.append(
+ SpaceOperation('simple_call', [Constant(Resume)],
+ v_resume))
+ for i, name in enumerate(varnames):
+ block.operations.append(
+ SpaceOperation('setattr', [v_resume, Constant(name),
+ newlink.args[i]],
+ Variable()))
+ v_pair = Variable('pair')
+ block.operations.append(
+ SpaceOperation('newtuple', [v_resume, v_yielded_value],
+ v_pair))
+ newlink.args = [v_pair]
+ newlink.target = graph.returnblock
+ #
+ regular_entry_block = Block([Variable('entry')])
+ block = regular_entry_block
+ for Resume in mappings:
+ v_check = Variable()
+ block.operations.append(
+ SpaceOperation('simple_call', [Constant(isinstance),
+ block.inputargs[0],
+ Constant(Resume)],
+ v_check))
+ block.exitswitch = v_check
+ link1 = Link([block.inputargs[0]], Resume.block)
+ link1.exitcase = True
+ nextblock = Block([Variable('entry')])
+ link2 = Link([block.inputargs[0]], nextblock)
+ link2.exitcase = False
+ block.closeblock(link1, link2)
+ block = nextblock
+ block.closeblock(Link([Constant(AssertionError),
+ Constant(AssertionError("bad generator class"))],
+ graph.exceptblock))
+ graph.startblock = regular_entry_block
+ graph.signature = Signature(['entry'])
+ graph.defaults = ()
+ checkgraph(graph)
+ eliminate_empty_blocks(graph)
diff --git a/pypy/translator/test/test_generator.py b/pypy/translator/test/test_generator.py
new file mode 100644
--- /dev/null
+++ b/pypy/translator/test/test_generator.py
@@ -0,0 +1,156 @@
+from pypy.conftest import option
+from pypy.objspace.flow.objspace import FlowObjSpace
+from pypy.objspace.flow.model import Variable
+from pypy.interpreter.argument import Signature
+from pypy.translator.translator import TranslationContext
+from pypy.translator.generator import make_generatoriterator_class
+from pypy.translator.generator import replace_graph_with_bootstrap
+from pypy.translator.generator import get_variable_names
+from pypy.translator.generator import tweak_generator_body_graph
+from pypy.translator.generator import attach_next_method
+from pypy.translator.simplify import join_blocks
+
+
+# ____________________________________________________________
+
+def f_gen(n):
+ i = 0
+ while i < n:
+ yield i
+ i += 1
+
+class GeneratorIterator(object):
+ def __init__(self, entry):
+ self.current = entry
+ def next(self):
+ e = self.current
+ self.current = None
+ if isinstance(e, Yield1):
+ n = e.n_0
+ i = e.i_0
+ i += 1
+ else:
+ n = e.n_0
+ i = 0
+ if i < n:
+ e = Yield1()
+ e.n_0 = n
+ e.i_0 = i
+ self.current = e
+ return i
+ raise StopIteration
+
+ def __iter__(self):
+ return self
+
+class AbstractPosition(object):
+ _immutable_ = True
+class Entry1(AbstractPosition):
+ _immutable_ = True
+class Yield1(AbstractPosition):
+ _immutable_ = True
+
+def f_explicit(n):
+ e = Entry1()
+ e.n_0 = n
+ return GeneratorIterator(e)
+
+def test_explicit():
+ assert list(f_gen(10)) == list(f_explicit(10))
+
+def test_get_variable_names():
+ lst = get_variable_names([Variable('a'), Variable('b_'), Variable('a')])
+ assert lst == ['g_a', 'g_b', 'g_a_']
+
+# ____________________________________________________________
+
+
+class TestGenerator:
+
+ def test_replace_graph_with_bootstrap(self):
+ def func(n, x, y, z):
+ yield n
+ yield n
+ #
+ space = FlowObjSpace()
+ graph = space.build_flow(func, tweak_for_generator=False)
+ assert graph.startblock.operations[0].opname == 'generator_mark'
+ GeneratorIterator = make_generatoriterator_class(graph)
+ replace_graph_with_bootstrap(GeneratorIterator, graph)
+ if option.view:
+ graph.show()
+ block = graph.startblock
+ ops = block.operations
+ assert ops[0].opname == 'simple_call' # e = Entry1()
+ assert ops[1].opname == 'setattr' # e.g_n = n
+ assert ops[1].args[1].value == 'g_n'
+ assert ops[2].opname == 'setattr' # e.g_x = x
+ assert ops[2].args[1].value == 'g_x'
+ assert ops[3].opname == 'setattr' # e.g_y = y
+ assert ops[3].args[1].value == 'g_y'
+ assert ops[4].opname == 'setattr' # e.g_z = z
+ assert ops[4].args[1].value == 'g_z'
+ assert ops[5].opname == 'simple_call' # g = GeneratorIterator(e)
+ assert ops[5].args[1] == ops[0].result
+ assert len(ops) == 6
+ assert len(block.exits) == 1
+ assert block.exits[0].target is graph.returnblock
+
+ def test_tweak_generator_body_graph(self):
+ def f(n, x, y, z=3):
+ z *= 10
+ yield n + 1
+ z -= 10
+ #
+ space = FlowObjSpace()
+ graph = space.build_flow(f, tweak_for_generator=False)
+ class Entry:
+ varnames = ['g_n', 'g_x', 'g_y', 'g_z']
+ tweak_generator_body_graph(Entry, graph)
+ if option.view:
+ graph.show()
+ # XXX how to test directly that the graph is correct? :-(
+ assert len(graph.startblock.inputargs) == 1
+ assert graph.signature == Signature(['entry'])
+ assert graph.defaults == ()
+
+ def test_tweak_generator_graph(self):
+ def f(n, x, y, z):
+ z *= 10
+ yield n + 1
+ z -= 10
+ #
+ space = FlowObjSpace()
+ graph = space.build_flow(f, tweak_for_generator=False)
+ GeneratorIterator = make_generatoriterator_class(graph)
+ replace_graph_with_bootstrap(GeneratorIterator, graph)
+ func1 = attach_next_method(GeneratorIterator, graph)
+ if option.view:
+ graph.show()
+ #
+ assert func1._generator_next_method_of_ is GeneratorIterator
+ assert hasattr(GeneratorIterator, 'next')
+ #
+ graph_next = space.build_flow(GeneratorIterator.next.im_func)
+ join_blocks(graph_next)
+ if option.view:
+ graph_next.show()
+ #
+ graph1 = space.build_flow(func1, tweak_for_generator=False)
+ tweak_generator_body_graph(GeneratorIterator.Entry, graph1)
+ if option.view:
+ graph1.show()
+
+ def test_automatic(self):
+ def f(n, x, y, z):
+ z *= 10
+ yield n + 1
+ z -= 10
+ #
+ space = FlowObjSpace()
+ graph = space.build_flow(f) # tweak_for_generator=True
+ if option.view:
+ graph.show()
+ block = graph.startblock
+ assert len(block.exits) == 1
+ assert block.exits[0].target is graph.returnblock
More information about the pypy-commit
mailing list