[pypy-svn] r50433 - in pypy/branch/astcompilertests/pypy/interpreter/astcompiler: . test
arigo at codespeak.net
arigo at codespeak.net
Mon Jan 7 19:35:37 CET 2008
Author: arigo
Date: Mon Jan 7 19:35:37 2008
New Revision: 50433
Modified:
pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pyassem.py
pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pycodegen.py
pypy/branch/astcompilertests/pypy/interpreter/astcompiler/test/test_compiler.py
Log:
Rewrite pyassem mostly from scratch to be simple, straightforward and fast
instead of complicated, buggily too clever, and slow. (Branch merge.)
Modified: pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pyassem.py
==============================================================================
--- pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pyassem.py (original)
+++ pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pyassem.py Mon Jan 7 19:35:37 2008
@@ -1,454 +1,25 @@
-"""A flow graph representation for Python bytecode"""
-
import sys
-
-from pypy.interpreter.astcompiler import misc, ast
from pypy.interpreter.astcompiler.consts \
import CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS
from pypy.interpreter.pycode import PyCode
-from pypy.interpreter.baseobjspace import W_Root
from pypy.tool import stdlib_opcode as pythonopcode
from pypy.interpreter.error import OperationError
+
class InternalCompilerError(Exception):
"""Something went wrong in the ast compiler."""
-class BlockSet:
- """A Set implementation specific to Blocks
- it uses Block.bid as keys to underlying dict"""
- def __init__(self):
- self.elts = {}
- def __len__(self):
- return len(self.elts)
- def __contains__(self, elt):
- return elt.bid in self.elts
- def add(self, elt):
- self.elts[elt.bid] = elt
- def elements(self):
- return self.elts.values()
- def has_elt(self, elt):
- return elt.bid in self.elts
- def remove(self, elt):
- del self.elts[elt.bid]
- def copy(self):
- c = BlockSet()
- c.elts.update(self.elts)
- return c
-
-
-class Instr:
- has_arg = False
-
- def __init__(self, op):
- self.op = op
-
-class InstrWithArg(Instr):
- has_arg = True
-
-class InstrName(InstrWithArg):
- def __init__(self, inst, name):
- Instr.__init__(self, inst)
- self.name = name
-
- def getArg(self):
- "NOT_RPYTHON"
- return self.name
-
-class InstrInt(InstrWithArg):
- def __init__(self, inst, intval):
- Instr.__init__(self, inst)
- self.intval = intval
-
- def getArg(self):
- "NOT_RPYTHON"
- return self.intval
-
-class InstrBlock(InstrWithArg):
- def __init__(self, inst, block):
- Instr.__init__(self, inst)
- self.block = block
-
- def getArg(self):
- "NOT_RPYTHON"
- return self.block
-
-class InstrObj(InstrWithArg):
- def __init__(self, inst, obj):
- Instr.__init__(self, inst)
- self.obj = obj
-
- def getArg(self):
- "NOT_RPYTHON"
- return self.obj
-
-class InstrCode(InstrWithArg):
- def __init__(self, inst, gen):
- Instr.__init__(self, inst)
- self.gen = gen
-
- def getArg(self):
- "NOT_RPYTHON"
- return self.gen
-
-class FlowGraph:
- def __init__(self, space):
- self.space = space
- self.current = self.entry = Block(space)
- self.exit = Block(space,"exit")
- self.blocks = BlockSet()
- self.blocks.add(self.entry)
- self.blocks.add(self.exit)
-
- def startBlock(self, block):
- if self._debug:
- if self.current:
- print "end", repr(self.current)
- print " next", self.current.next
- print " ", self.current.get_children()
- print repr(block)
- assert block is not None
- self.current = block
-
- def nextBlock(self, block=None):
- # XXX think we need to specify when there is implicit transfer
- # from one block to the next. might be better to represent this
- # with explicit JUMP_ABSOLUTE instructions that are optimized
- # out when they are unnecessary.
- #
- # I think this strategy works: each block has a child
- # designated as "next" which is returned as the last of the
- # children. because the nodes in a graph are emitted in
- # reverse post order, the "next" block will always be emitted
- # immediately after its parent.
- # Worry: maintaining this invariant could be tricky
- if block is None:
- block = self.newBlock()
-
- # Note: If the current block ends with an unconditional
- # control transfer, then it is incorrect to add an implicit
- # transfer to the block graph. The current code requires
- # these edges to get the blocks emitted in the right order,
- # however. :-( If a client needs to remove these edges, call
- # pruneEdges().
-
- self.current.addNext(block)
- self.startBlock(block)
-
- def newBlock(self):
- b = Block(self.space)
- self.blocks.add(b)
- return b
-
- def startExitBlock(self):
- self.startBlock(self.exit)
-
- _debug = 0
-
- def _enable_debug(self):
- self._debug = 1
-
- def _disable_debug(self):
- self._debug = 0
-
- def emit(self, inst):
- if self._debug:
- print "\t", inst
- if inst in ['RETURN_VALUE', 'YIELD_VALUE']:
- self.current.addOutEdge(self.exit)
- self.current.emit( Instr(inst) )
-
- #def emitop(self, inst, arg ):
- # if self._debug:
- # print "\t", inst, arg
- # self.current.emit( (inst,arg) )
-
- def emitop_obj(self, inst, obj ):
- if self._debug:
- print "\t", inst, repr(obj)
- self.current.emit( InstrObj(inst,obj) )
-
- def emitop_code(self, inst, obj ):
- if self._debug:
- print "\t", inst, repr(obj)
- self.current.emit( InstrCode(inst, obj) )
-
- def emitop_int(self, inst, intval ):
- if self._debug:
- print "\t", inst, intval
- assert isinstance(intval,int)
- self.current.emit( InstrInt(inst,intval) )
-
- def emitop_block(self, inst, block):
- if self._debug:
- print "\t", inst, block
- assert isinstance(block, Block)
- self.current.addOutEdge( block )
- self.current.emit( InstrBlock(inst,block) )
-
- def emitop_name(self, inst, name ):
- if self._debug:
- print "\t", inst, name
- assert isinstance(name,str)
- self.current.emit( InstrName(inst,name) )
-
- def getBlocksInOrder(self):
- """Return the blocks in reverse postorder
-
- i.e. each node appears before all of its successors
- """
- # TODO: What we need here is a topological sort that
-
-
- # XXX make sure every node that doesn't have an explicit next
- # is set so that next points to exit
- for b in self.blocks.elements():
- if b is self.exit:
- continue
- if not b.next:
- b.addNext(self.exit)
- order = dfs_postorder(self.entry, {})
- order.reverse()
- self.fixupOrder(order, self.exit)
- # hack alert
- if not self.exit in order:
- order.append(self.exit)
-
- return order
-
- def fixupOrder(self, blocks, default_next):
- """Fixup bad order introduced by DFS."""
-
- # XXX This is a total mess. There must be a better way to get
- # the code blocks in the right order.
-
- self.fixupOrderHonorNext(blocks, default_next)
- self.fixupOrderForward(blocks, default_next)
-
- def fixupOrderHonorNext(self, blocks, default_next):
- """Fix one problem with DFS.
-
- The DFS uses child block, but doesn't know about the special
- "next" block. As a result, the DFS can order blocks so that a
- block isn't next to the right block for implicit control
- transfers.
- """
- new_blocks = blocks
- blocks = blocks[:]
- del new_blocks[:]
- i = 0
- while i < len(blocks) - 1:
- b = blocks[i]
- n = blocks[i + 1]
- i += 1
- new_blocks.append(b)
- if not b.next or b.next[0] == default_next or b.next[0] == n:
- continue
- # The blocks are in the wrong order. Find the chain of
- # blocks to insert where they belong.
- cur = b
- chain = []
- elt = cur
- while elt.next and elt.next[0] != default_next:
- chain.append(elt.next[0])
- elt = elt.next[0]
- # Now remove the blocks in the chain from the current
- # block list, so that they can be re-inserted.
- for b in chain:
- for j in range(i + 1, len(blocks)):
- if blocks[j] == b:
- del blocks[j]
- break
- else:
- assert False, "Can't find block"
-
- new_blocks.extend(chain)
- if i == len(blocks) - 1:
- new_blocks.append(blocks[i])
-
- def fixupOrderForward(self, blocks, default_next):
- """Make sure all JUMP_FORWARDs jump forward"""
- index = {}
- chains = []
- cur = []
- for b in blocks:
- index[b.bid] = len(chains)
- cur.append(b)
- if b.next and b.next[0] == default_next:
- chains.append(cur)
- cur = []
- chains.append(cur)
-
- while 1:
- constraints = []
-
- for i in range(len(chains)):
- l = chains[i]
- for b in l:
- for c in b.get_children():
- if index[c.bid] < i:
- forward_p = 0
- for inst in b.insts:
- # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- # other bytecodes need the same logic, but
- # trying to do that throws this function into
- # an infinite loop. Sad sad sad.
- # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- if inst.op == 'JUMP_FORWARD':
- assert isinstance(inst, InstrBlock)
- if inst.block == c:
- forward_p = 1
- if not forward_p:
- continue
- constraints.append((index[c.bid], i))
-
- if not constraints:
- break
-
- # XXX just do one for now
- # do swaps to get things in the right order
- goes_before, a_chain = constraints[0]
- assert a_chain > goes_before >= 0
- c = chains[a_chain]
- del chains[a_chain]
- chains.insert(goes_before, c)
-
- del blocks[:]
- for c in chains:
- for b in c:
- blocks.append(b)
-
- def getBlocks(self):
- return self.blocks.elements()
-
- def getRoot(self):
- """Return nodes appropriate for use with dominator"""
- return self.entry
-
- def getContainedGraphs(self):
- l = []
- for b in self.getBlocks():
- l.extend(b.getContainedGraphs())
- return l
-
-def dfs_postorder(b, seen):
- """Depth-first search of tree rooted at b, return in postorder"""
- order = []
- seen[b.bid] = b
- for c in b.get_children():
- if c.bid in seen:
- continue
- order = order + dfs_postorder(c, seen)
- order.append(b)
- return order
-
-BlockCounter = misc.Counter(0)
-
-class Block:
-
- def __init__(self, space, label=''):
- self.insts = []
- self.inEdges = BlockSet()
- self.outEdges = BlockSet()
- self.label = label
- self.bid = BlockCounter.next()
- self.next = []
- self.space = space
-
- def __repr__(self):
- if self.label:
- return "<block %s id=%d>" % (self.label, self.bid)
- else:
- return "<block id=%d>" % (self.bid)
-
- def __str__(self):
- insts = [ str(i) for i in self.insts ]
- return "<block %s %d:\n%s>" % (self.label, self.bid,
- '\n'.join(insts))
-
- def emit(self, inst):
- op = inst.op
- if op[:4] == 'JUMP':
- assert isinstance(inst, InstrBlock)
- self.outEdges.add(inst.block)
-## if op=="LOAD_CONST":
-## assert isinstance( inst[1], W_Root ) or hasattr( inst[1], 'getCode')
- self.insts.append( inst )
-
- def getInstructions(self):
- return self.insts
-
- def addInEdge(self, block):
- self.inEdges.add(block)
-
- def addOutEdge(self, block):
- self.outEdges.add(block)
-
- def addNext(self, block):
- self.next.append(block)
- assert len(self.next) == 1, [ str(i) for i in self.next ]
-
- _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', 'YIELD_VALUE',
- 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP')
-
- def pruneNext(self):
- """Remove bogus edge for unconditional transfers
-
- Each block has a next edge that accounts for implicit control
- transfers, e.g. from a JUMP_IF_FALSE to the block that will be
- executed if the test is true.
-
- These edges must remain for the current assembler code to
- work. If they are removed, the dfs_postorder gets things in
- weird orders. However, they shouldn't be there for other
- purposes, e.g. conversion to SSA form. This method will
- remove the next edge when it follows an unconditional control
- transfer.
- """
- try:
- inst = self.insts[-1]
- except (IndexError, ValueError):
- return
- if inst.op in self._uncond_transfer:
- self.next = []
-
- def get_children(self):
- if self.next and self.next[0].bid in self.outEdges.elts:
- self.outEdges.remove(self.next[0])
- return self.outEdges.elements() + self.next
-
- def getContainedGraphs(self):
- """Return all graphs contained within this block.
-
- For example, a MAKE_FUNCTION block will contain a reference to
- the graph for the function body.
- """
- contained = []
- for inst in self.insts:
- if isinstance(inst, InstrCode):
- gen = inst.gen
- if gen:
- contained.append(gen)
- return contained
-
-# flags for code objects
-
-# the FlowGraph is transformed in place; it exists in one of these states
-RAW = "RAW"
-FLAT = "FLAT"
-CONV = "CONV"
-DONE = "DONE"
-
-class PyFlowGraph(FlowGraph):
+class PyFlowGraph(object):
def __init__(self, space, name, filename, argnames=None,
optimized=0, klass=0, newlocals=0):
- FlowGraph.__init__(self, space)
+ self.space = space
if argnames is None:
argnames = []
self.name = name
self.filename = filename
- self.docstring = space.w_None
+ self.w_docstring = space.w_None
self.argcount = len(argnames)
self.klass = klass
self.flags = 0
@@ -457,9 +28,8 @@
if newlocals:
self.flags |= CO_NEWLOCALS
- # XXX we need to build app-level dict here, bleh
+ # we need to build an app-level dict here
self.w_consts = space.newdict()
- #self.const_list = []
self.names = []
# Free variables found by the symbol table scan, including
# variables used only in nested scopes, are included here.
@@ -471,11 +41,14 @@
# kinds of variables.
self.closure = []
self.varnames = list(argnames)
- self.stage = RAW
- self.orderedblocks = []
+ # The bytecode we are building, as a list of characters
+ self.co_code = []
+ # Pending label targets to fix: [(label, index-in-co_code-to-fix, abs)]
+ self.pending_label_fixes = []
+ self.lnotab = None
- def setDocstring(self, doc):
- self.docstring = doc
+ def setDocstring(self, w_docstring):
+ self.w_docstring = w_docstring
def setFlag(self, flag):
self.flags = self.flags | flag
@@ -492,201 +65,76 @@
def setCellVars(self, names):
self.cellvars = names
- def getCode(self):
- """Get a Python code object"""
- if self.stage == RAW:
- self.computeStackDepth()
- self.convertArgs()
- if self.stage == CONV:
- self.flattenGraph()
- if self.stage == FLAT:
- self.makeByteCode()
- if self.stage == DONE:
- return self.newCodeObject()
- raise InternalCompilerError("inconsistent PyFlowGraph state")
-
- def dump(self, io=None):
- if io:
- save = sys.stdout
- sys.stdout = io
- pc = 0
- for t in self.insts:
- opname = t.op
- if opname == "SET_LINENO":
- print
- if not t.has_arg:
- print "\t", "%3d" % pc, opname
- pc = pc + 1
- else:
- print "\t", "%3d" % pc, opname, t.getArg()
- pc = pc + 3
- if io:
- sys.stdout = save
-
- def _max_depth(self, depth, seen, b, d):
- if b in seen:
- return d
- seen[b] = 1
- d = d + depth[b]
- children = b.get_children()
- if children:
- maxd = -1
- for c in children:
- childd =self._max_depth(depth, seen, c, d)
- if childd > maxd:
- maxd = childd
- return maxd
+ # ____________________________________________________________
+ # Simple instructions
+
+ def emit(self, opname):
+ self.co_code.append(chr(pythonopcode.opmap[opname]))
+
+ def emitop_extended_arg(self, intval):
+ assert intval <= 0x7FFFFFFF
+ self.emit('EXTENDED_ARG')
+ self.co_code.append(chr((intval >> 16) & 0xFF))
+ self.co_code.append(chr((intval >> 24) & 0xFF))
+ return intval & 0xFFFF
+ emitop_extended_arg._dont_inline_ = True
+
+ def emitop_setlineno(self, lineno):
+ if self.lnotab is None:
+ self.lnotab = LineAddrTable(lineno)
else:
- if not b.label == "exit":
- return self._max_depth(depth, seen, self.exit, d)
- else:
- return d
+ self.lnotab.nextLine(len(self.co_code), lineno)
+ emitop_setlineno._dont_inline_ = True
- def computeStackDepth(self):
- """Compute the max stack depth.
+ def emitop_int(self, opname, intval):
+ assert intval >= 0
+ if opname == "SET_LINENO":
+ self.emitop_setlineno(intval)
+ return
+ if intval > 0xFFFF:
+ intval = self.emitop_extended_arg(intval)
+ self.emit(opname)
+ self.co_code.append(chr(intval & 0xFF))
+ self.co_code.append(chr(intval >> 8))
- Approach is to compute the stack effect of each basic block.
- Then find the path through the code with the largest total
- effect.
- """
- depth = {}
- exit = None
- for b in self.getBlocks():
- depth[b] = findDepth(b.getInstructions())
-
- seen = {}
-
- self.stacksize = self._max_depth( depth, seen, self.entry, 0)
-
- def flattenGraph(self):
- """Arrange the blocks in order and resolve jumps"""
- assert self.stage == CONV
- self.insts = insts = []
- firstline = 0
- pc = 0
- begin = {}
- end = {}
- forward_refs = []
- for b in self.orderedblocks:
- # Prune any setlineno before the 'implicit return' block.
- if b is self.exit:
- while len(insts) and insts[-1].op == "SET_LINENO":
- insts.pop()
- begin[b] = pc
- for inst in b.getInstructions():
- if not inst.has_arg:
- insts.append(inst)
- pc = pc + 1
- elif inst.op != "SET_LINENO":
- if inst.op in self.hasjrel:
- assert isinstance(inst, InstrBlock)
- # relative jump - no extended arg
- block = inst.block
- inst = InstrInt(inst.op, 0)
- forward_refs.append( (block, inst, pc) )
- insts.append(inst)
- pc = pc + 3
- elif inst.op in self.hasjabs:
- # absolute jump - can be extended if backward
- assert isinstance(inst, InstrBlock)
- arg = inst.block
- if arg in begin:
- # can only extend argument if backward
- offset = begin[arg]
- hi = offset // 65536
- lo = offset % 65536
- if hi>0:
- # extended argument
- insts.append( InstrInt("EXTENDED_ARG", hi) )
- pc = pc + 3
- inst = InstrInt(inst.op, lo)
- else:
- inst = InstrInt(inst.op, 0)
- forward_refs.append( (arg, inst, pc ) )
- insts.append(inst)
- pc = pc + 3
- else:
- assert isinstance(inst, InstrInt)
- arg = inst.intval
- # numerical arg
- hi = arg // 65536
- lo = arg % 65536
- if hi>0:
- # extended argument
- insts.append( InstrInt("EXTENDED_ARG", hi) )
- inst.intval = lo
- pc = pc + 3
- insts.append(inst)
- pc = pc + 3
- else:
- insts.append(inst)
- if firstline == 0:
- firstline = inst.intval
- end[b] = pc
- pc = 0
-
- for block, inst, pc in forward_refs:
- opname = inst.op
- abspos = begin[block]
- if opname in self.hasjrel:
- offset = abspos - pc - 3
- if offset < 0:
- raise InternalCompilerError("unexpected backward jump")
- inst.intval = offset
- else:
- inst.intval = abspos
- self.firstline = firstline
- self.stage = FLAT
+ # ____________________________________________________________
+ # Instructions with an object argument (LOAD_CONST)
- hasjrel = {}
- for i in pythonopcode.hasjrel:
- hasjrel[pythonopcode.opname[i]] = True
- hasjabs = {}
- for i in pythonopcode.hasjabs:
- hasjabs[pythonopcode.opname[i]] = True
+ def emitop_obj(self, opname, w_obj):
+ index = self._lookupConst(w_obj, self.w_consts)
+ self.emitop_int(opname, index)
- def setconst(self, w_consts, w_item, value):
- space = self.space
- w_item_type = space.type(w_item)
- w_key = space.newtuple([w_item, w_item_type])
- space.setitem(w_consts, w_key, space.wrap(value))
-
- def convertArgs(self):
- """Convert arguments from symbolic to concrete form"""
- assert self.stage == RAW
+ def _lookupConst(self, w_obj, w_dict):
space = self.space
- self.orderedblocks = self.getBlocksInOrder()
- self.setconst(self.w_consts, self.docstring, 0)
- #self.const_list.insert(0, self.docstring)
- self.sort_cellvars()
-
- for b in self.orderedblocks:
- insts = b.getInstructions()
- for i in range(len(insts)):
- inst = insts[i]
- if inst.has_arg:
- opname = inst.op
- conv = self._converters.get(opname, None)
- if conv:
- insts[i] = conv(self, inst)
- self.stage = CONV
-
- def sort_cellvars(self):
- """Sort cellvars in the order of varnames and prune from freevars.
- """
- cells = {}
- for name in self.cellvars:
- cells[name] = 1
- self.cellvars = [name for name in self.varnames
- if name in cells]
- for name in self.cellvars:
- del cells[name]
- self.cellvars = self.cellvars + cells.keys()
- self.closure = self.cellvars + self.freevars
+ # insert the docstring first, if necessary
+ if not space.is_true(w_dict):
+ w_obj_type = space.type(self.w_docstring)
+ w_key = space.newtuple([self.w_docstring, w_obj_type])
+ space.setitem(w_dict, w_key, space.wrap(0))
+ # normal logic follows
+ w_obj_type = space.type(w_obj)
+ w_key = space.newtuple([w_obj, w_obj_type])
+ try:
+ w_result = space.getitem(w_dict, w_key)
+ except OperationError, operr:
+ if not operr.match(space, space.w_KeyError):
+ raise
+ w_result = space.len(w_dict)
+ space.setitem(w_dict, w_key, w_result)
+ return space.int_w(w_result)
+
+ # ____________________________________________________________
+ # Instructions with a name argument
+
+ def emitop_name(self, opname, name):
+ conv = self._converters[opname]
+ index = conv(self, name)
+ self.emitop_int(opname, index)
def _lookupName(self, name, list):
"""Return index of name in list, appending if necessary
"""
+ # XXX use dicts instead of lists
assert isinstance(name, str)
for i in range(len(list)):
if list[i] == name:
@@ -695,70 +143,14 @@
list.append(name)
return end
- def _cmpConsts(self, w_left, w_right):
- space = self.space
- t = space.type(w_left)
- if space.is_w(t, space.type(w_right)):
- if space.is_w(t, space.w_tuple):
- left_len = space.int_w(space.len(w_left))
- right_len = space.int_w(space.len(w_right))
- if left_len == right_len:
- for i in range(left_len):
- w_lefti = space.getitem(w_left, space.wrap(i))
- w_righti = space.getitem(w_right, space.wrap(i))
- if not self._cmpConsts(w_lefti, w_righti):
- return False
- return True
- elif space.eq_w(w_left, w_right):
- return True
- return False
-
- def _lookupConst(self, w_obj, w_dict):
- """
- This routine uses a list instead of a dictionary, because a
- dictionary can't store two different keys if the keys have the
- same value but different types, e.g. 2 and 2L. The compiler
- must treat these two separately, so it does an explicit type
- comparison before comparing the values.
- """
- space = self.space
- w_obj_type = space.type(w_obj)
- try:
- w_key = space.newtuple([w_obj, w_obj_type])
- return space.int_w(space.getitem(w_dict, w_key))
- except OperationError, operr:
- if not operr.match(space, space.w_KeyError):
- raise
- lgt = space.int_w(space.len(w_dict))
- self.setconst(w_dict, w_obj, lgt)
- return lgt
-
- _converters = {}
-
- def _convert_LOAD_CONST(self, inst):
- if isinstance(inst, InstrCode):
- w_obj = inst.gen.getCode()
- else:
- assert isinstance(inst, InstrObj)
- w_obj = inst.obj
- #assert w_obj is not None
- index = self._lookupConst(w_obj, self.w_consts)
- return InstrInt(inst.op, index)
-
- def _convert_LOAD_FAST(self, inst):
- assert isinstance(inst, InstrName)
- arg = inst.name
+ def _convert_LOAD_FAST(self, arg):
self._lookupName(arg, self.names)
- index= self._lookupName(arg, self.varnames)
- return InstrInt(inst.op, index)
+ return self._lookupName(arg, self.varnames)
_convert_STORE_FAST = _convert_LOAD_FAST
_convert_DELETE_FAST = _convert_LOAD_FAST
- def _convert_NAME(self, inst):
- assert isinstance(inst, InstrName)
- arg = inst.name
- index = self._lookupName(arg, self.names)
- return InstrInt(inst.op, index)
+ def _convert_NAME(self, arg):
+ return self._lookupName(arg, self.names)
_convert_LOAD_NAME = _convert_NAME
_convert_STORE_NAME = _convert_NAME
_convert_DELETE_NAME = _convert_NAME
@@ -772,73 +164,184 @@
_convert_DELETE_GLOBAL = _convert_NAME
_convert_LOOKUP_METHOD = _convert_NAME
- def _convert_DEREF(self, inst):
- assert isinstance(inst, InstrName)
- arg = inst.name
+ def _convert_DEREF(self, arg):
self._lookupName(arg, self.names)
- index = self._lookupName(arg, self.closure)
- return InstrInt(inst.op, index)
+ return self._lookupName(arg, self.closure)
_convert_LOAD_DEREF = _convert_DEREF
_convert_STORE_DEREF = _convert_DEREF
- def _convert_LOAD_CLOSURE(self, inst):
- assert isinstance(inst, InstrName)
- arg = inst.name
- index = self._lookupName(arg, self.closure)
- return InstrInt(inst.op, index)
-
- _cmp = list(pythonopcode.cmp_op)
- def _convert_COMPARE_OP(self, inst):
- assert isinstance(inst, InstrName)
- arg = inst.name
- index = self._cmp.index(arg)
- return InstrInt(inst.op, index)
-
+ def _convert_LOAD_CLOSURE(self, arg):
+ return self._lookupName(arg, self.closure)
- # similarly for other opcodes...
+ _cmp = list(pythonopcode.cmp_op)
+ def _convert_COMPARE_OP(self, arg):
+ return self._cmp.index(arg)
+ _converters = {}
for name, obj in locals().items():
if name[:9] == "_convert_":
opname = name[9:]
_converters[opname] = obj
del name, obj, opname
- def makeByteCode(self):
- assert self.stage == FLAT
- self.lnotab = lnotab = LineAddrTable(self.firstline)
- for t in self.insts:
- opname = t.op
- if self._debug:
- if not t.has_arg:
- print "x",opname
+ # ____________________________________________________________
+ # Labels and jumps
+
+ def newBlock(self):
+ """This really returns a new label, initially not pointing anywhere."""
+ return Label()
+
+ def nextBlock(self, label):
+ if label.position >= 0:
+ raise InternalCompilerError("Label target already seen")
+ label.position = len(self.co_code)
+
+ def emitop_block(self, opname, label):
+ absolute = opname in self.hasjabs
+ target = label.position
+ if target < 0: # unknown yet
+ i = len(self.co_code)
+ self.pending_label_fixes.append((label, i, absolute))
+ target = 0xFFFF
+ else:
+ if not absolute:
+ # if the target was already seen, it must be backward,
+ # which is forbidden for these instructions
+ raise InternalCompilerError("%s cannot do a back jump" %
+ (opname,))
+ self.emitop_int(opname, target)
+
+ hasjrel = {}
+ for i in pythonopcode.hasjrel:
+ hasjrel[pythonopcode.opname[i]] = True
+ hasjabs = {}
+ for i in pythonopcode.hasjabs:
+ hasjabs[pythonopcode.opname[i]] = True
+ del i
+
+ # ____________________________________________________________
+
+ def dump(self):
+ try:
+ self.fixLabelTargets()
+ except:
+ pass
+ if not hasattr(self, 'stacksize'):
+ self.stacksize = 99 # temporarily
+ co = self.newCodeObject()
+ co.dump()
+
+ def getCode(self):
+ self.fixLabelTargets()
+ self.computeStackDepth()
+ return self.newCodeObject()
+
+ def _setdepth(self, i, stackdepth):
+ if stackdepth < 0:
+ raise InternalCompilerError("negative stack depth")
+ depths = self._stackdepths
+ previous_value = depths[i]
+ if previous_value < 0:
+ if i <= self._stackdepth_seen_until:
+ raise InternalCompilerError("back jump to code that is "
+ "otherwise not reachable")
+ depths[i] = stackdepth
+ else:
+ if previous_value != stackdepth:
+ raise InternalCompilerError("inconsistent stack depth")
+
+ def computeStackDepth(self):
+ UNREACHABLE = -1
+ co_code = self.co_code
+ self._stackdepths = [UNREACHABLE] * len(co_code)
+ self._stackdepths[0] = 0
+ just_loaded_const = None
+ consts_w = self.getConsts()
+ finally_targets = {}
+ largestsize = 0
+ i = 0
+
+ while i < len(co_code):
+ curstackdepth = self._stackdepths[i]
+ if curstackdepth > largestsize:
+ largestsize = curstackdepth
+ self._stackdepth_seen_until = i
+
+ # decode the next instruction
+ opcode = ord(co_code[i])
+ if opcode >= pythonopcode.HAVE_ARGUMENT:
+ oparg = ord(co_code[i+1]) | (ord(co_code[i+2]) << 8)
+ i += 3
+ if opcode == pythonopcode.opmap['EXTENDED_ARG']:
+ opcode = ord(co_code[i])
+ assert opcode >= pythonopcode.HAVE_ARGUMENT
+ oparg = ((oparg << 16) |
+ ord(co_code[i+1]) | (ord(co_code[i+2]) << 8))
+ i += 3
+ else:
+ oparg = sys.maxint
+ i += 1
+
+ if curstackdepth == UNREACHABLE:
+ just_loaded_const = None
+ continue # ignore unreachable instructions
+
+ if opcode in DEPTH_OP_EFFECT_ALONG_JUMP:
+ if opcode in pythonopcode.hasjabs:
+ target_i = oparg
else:
- print "x",opname, t.getArg()
- if not t.has_arg:
- lnotab.addCode1(self.opnum[opname])
+ target_i = i + oparg
+ effect = DEPTH_OP_EFFECT_ALONG_JUMP[opcode]
+ self._setdepth(target_i, curstackdepth + effect)
+ if opcode == pythonopcode.opmap['SETUP_FINALLY']:
+ finally_targets[target_i] = None
+
+ try:
+ tracker = DEPTH_OP_TRACKER[opcode]
+ except KeyError:
+ pass
else:
- assert isinstance(t, InstrInt)
- oparg = t.intval
- if opname == "SET_LINENO":
- lnotab.nextLine(oparg)
- continue
- hi, lo = twobyte(oparg)
- try:
- lnotab.addCode3(self.opnum[opname], lo, hi)
- except ValueError:
- if self._debug:
- print opname, oparg
- print self.opnum[opname], lo, hi
- raise
- self.stage = DONE
-
- opnum = {}
- for num in range(len(pythonopcode.opname)):
- opnum[pythonopcode.opname[num]] = num
- # This seems to duplicate dis.opmap from opcode.opmap
- del num
+ if opcode == pythonopcode.opmap['MAKE_CLOSURE']:
+ # only supports "LOAD_CONST co / MAKE_CLOSURE n"
+ if just_loaded_const is None:
+ raise InternalCompilerError("MAKE_CLOSURE not "
+ "following LOAD_CONST")
+ codeobj = self.space.interp_w(PyCode, just_loaded_const)
+ nfreevars = len(codeobj.co_freevars)
+ effect = - nfreevars - oparg
+ else:
+ effect = tracker(oparg)
+ curstackdepth += effect
+ if i in finally_targets:
+ curstackdepth += 2 # see pyopcode.FinallyBlock.cleanup()
+ self._setdepth(i, curstackdepth)
+
+ if opcode == pythonopcode.opmap['LOAD_CONST']:
+ just_loaded_const = consts_w[oparg]
+ else:
+ just_loaded_const = None
+
+ self.stacksize = largestsize
+
+ def fixLabelTargets(self):
+ for label, i, absolute in self.pending_label_fixes:
+ target = label.position
+ if target < 0:
+ raise InternalCompilerError("Label target not found")
+ if not absolute:
+ target = target - (i+3) # relative jump
+ if target < 0:
+ raise InternalCompilerError("Unexpected backward jump")
+ if target > 0xFFFF:
+ # CPython has the same limitation, for the same practical
+ # reason
+ msg = "function too large (bytecode would jump too far away)"
+ space = self.space
+ raise OperationError(space.w_SystemError, space.wrap(msg))
+ self.co_code[i+1] = chr(target & 0xFF)
+ self.co_code[i+2] = chr(target >> 8)
def newCodeObject(self):
- assert self.stage == DONE
if (self.flags & CO_NEWLOCALS) == 0:
nlocals = 0
else:
@@ -846,47 +349,48 @@
argcount = self.argcount
if self.flags & CO_VARKEYWORDS:
argcount = argcount - 1
- # was return new.code, now we just return the parameters and let
- # the caller create the code object
+ if self.lnotab is None: # obscure case
+ firstline = 0
+ lnotab = ""
+ else:
+ firstline = self.lnotab.firstline
+ lnotab = self.lnotab.getTable()
return PyCode( self.space, argcount, nlocals,
self.stacksize, self.flags,
- self.lnotab.getCode(),
+ ''.join(self.co_code),
self.getConsts(),
self.names,
self.varnames,
self.filename, self.name,
- self.firstline,
- self.lnotab.getTable(),
+ firstline,
+ lnotab,
self.freevars,
self.cellvars
)
def getConsts(self):
"""Return a tuple for the const slot of the code object
-
- Must convert references to code (MAKE_FUNCTION) to code
- objects recursively.
"""
+ # sanity-check
+ index = self._lookupConst(self.w_docstring, self.w_consts)
+ if index != 0:
+ raise InternalCompilerError("setDocstring() called too late")
space = self.space
- l_w = [None] * space.int_w(space.len(self.w_consts))
keys_w = space.unpackiterable(self.w_consts)
+ l_w = [None] * len(keys_w)
for w_key in keys_w:
index = space.int_w(space.getitem(self.w_consts, w_key))
w_v = space.unpacktuple(w_key)[0]
l_w[index] = w_v
return l_w
-def isJump(opname):
- if opname[:4] == 'JUMP':
- return 1
-
-def twobyte(val):
- """Convert an int argument into high and low bytes"""
- assert isinstance(val,int)
- assert 0 <= val < 65536
- hi = val // 256
- lo = val % 256
- return hi, lo
+# ____________________________________________________________
+
+class Label(object):
+ position = -1
+
+# ____________________________________________________________
+# Encoding the line numbers in lnotab
class LineAddrTable:
"""lnotab
@@ -904,26 +408,14 @@
"""
def __init__(self, firstline):
- self.code = []
- self.codeOffset = 0
self.firstline = firstline
self.lastline = firstline
self.lastoff = 0
- self.lnotab = []
+ self.lnotab = [] # list of characters
- def addCode1(self, op ):
- self.code.append(chr(op))
- self.codeOffset = self.codeOffset + 1
-
- def addCode3(self, op, hi, lo):
- self.code.append(chr(op))
- self.code.append(chr(hi))
- self.code.append(chr(lo))
- self.codeOffset = self.codeOffset + 3
-
- def nextLine(self, lineno):
+ def nextLine(self, codeOffset, lineno):
# compute deltas
- addr = self.codeOffset - self.lastoff
+ addr = codeOffset - self.lastoff
line = lineno - self.lastline
# Python assumes that lineno always increases with
# increasing bytecode address (lnotab is unsigned char).
@@ -938,23 +430,22 @@
if line >= 0:
push = self.lnotab.append
while addr > 255:
- push(255); push(0)
+ push(chr(255)); push(chr(0))
addr -= 255
while line > 255:
- push(addr); push(255)
+ push(chr(addr)); push(chr(255))
line -= 255
addr = 0
if addr > 0 or line > 0:
- push(addr); push(line)
+ push(chr(addr)); push(chr(line))
self.lastline = lineno
- self.lastoff = self.codeOffset
-
- def getCode(self):
- return ''.join(self.code)
+ self.lastoff = codeOffset
def getTable(self):
- return ''.join( [ chr(i) for i in self.lnotab ] )
+ return ''.join(self.lnotab)
+# ____________________________________________________________
+# Stack depth tracking
def depth_UNPACK_SEQUENCE(count):
return count-1
@@ -974,11 +465,14 @@
return depth_CALL_FUNCTION(argc)-2
def depth_CALL_METHOD(argc):
return -argc-1
+def depth_CALL_LIKELY_BUILTIN(argc):
+ nargs = argc & 0xFF
+ return -nargs+1
def depth_MAKE_FUNCTION(argc):
return -argc
def depth_MAKE_CLOSURE(argc):
- # XXX need to account for free variables too!
- return -argc
+ raise InternalCompilerError("must special-case this in order to account"
+ " for the free variables")
def depth_BUILD_SLICE(argc):
if argc == 2:
return -1
@@ -989,57 +483,14 @@
def depth_DUP_TOPX(argc):
return argc
-DEPTH_OP_TRACKER = {
- "UNPACK_SEQUENCE" : depth_UNPACK_SEQUENCE,
- "BUILD_TUPLE" : depth_BUILD_TUPLE,
- "BUILD_LIST" : depth_BUILD_LIST,
- "CALL_FUNCTION" : depth_CALL_FUNCTION,
- "CALL_FUNCTION_VAR" : depth_CALL_FUNCTION_VAR,
- "CALL_FUNCTION_KW" : depth_CALL_FUNCTION_KW,
- "CALL_FUNCTION_VAR_KW" : depth_CALL_FUNCTION_VAR_KW,
- "MAKE_FUNCTION" : depth_MAKE_FUNCTION,
- "MAKE_CLOSURE" : depth_MAKE_CLOSURE,
- "BUILD_SLICE" : depth_BUILD_SLICE,
- "DUP_TOPX" : depth_DUP_TOPX,
- }
-
-class StackDepthTracker:
- # XXX 1. need to keep track of stack depth on jumps
- # XXX 2. at least partly as a result, this code is broken
- # XXX 3. Don't need a class here!
-
- def findDepth(self, insts, debug=0):
- depth = 0
- maxDepth = 0
- for i in insts:
- opname = i.op
- if debug:
- print i,
- delta = self.effect.get(opname, sys.maxint)
- if delta != sys.maxint:
- depth = depth + delta
- else:
- # now check patterns
- for pat, pat_delta in self.patterns:
- if opname[:len(pat)] == pat:
- delta = pat_delta
- depth = depth + delta
- break
- # if we still haven't found a match
- if delta == sys.maxint:
- meth = DEPTH_OP_TRACKER.get( opname, None )
- if meth is not None:
- assert isinstance(i, InstrInt)
- depth = depth + meth(i.intval)
- if depth > maxDepth:
- maxDepth = depth
- if debug:
- print depth, maxDepth
- return maxDepth
-
+def setup_stack_depth_tracker():
effect = {
+ 'STOP_CODE': 0,
+ 'NOP': 0,
+ 'EXTENDED_ARG': 0,
'POP_TOP': -1,
'DUP_TOP': 1,
+ 'SLICE+0': 0,
'SLICE+1': -1,
'SLICE+2': -1,
'SLICE+3': -2,
@@ -1053,36 +504,104 @@
'DELETE_SLICE+3': -3,
'STORE_SUBSCR': -3,
'DELETE_SUBSCR': -2,
- # PRINT_EXPR?
+ 'PRINT_EXPR': -1,
'PRINT_ITEM': -1,
- 'RETURN_VALUE': -1,
+ 'PRINT_ITEM_TO': -2,
+ 'PRINT_NEWLINE': 0,
+ 'PRINT_NEWLINE_TO': -1,
'YIELD_VALUE': -1,
'EXEC_STMT': -3,
'BUILD_CLASS': -2,
'STORE_NAME': -1,
+ 'DELETE_NAME': 0,
'STORE_ATTR': -2,
'DELETE_ATTR': -1,
'STORE_GLOBAL': -1,
+ 'DELETE_GLOBAL': 0,
+ 'STORE_DEREF': -1,
'BUILD_MAP': 1,
'COMPARE_OP': -1,
'STORE_FAST': -1,
+ 'DELETE_FAST': 0,
'IMPORT_STAR': -1,
'IMPORT_NAME': 0,
'IMPORT_FROM': 1,
'LOAD_ATTR': 0, # unlike other loads
- # close enough...
- 'SETUP_EXCEPT': 3,
- 'SETUP_FINALLY': 3,
+ 'GET_ITER': 0,
'FOR_ITER': 1,
- 'WITH_CLEANUP': 3,
+ 'BREAK_LOOP': 0,
+ 'CONTINUE_LOOP': 0,
+ 'POP_BLOCK': 0,
+ 'END_FINALLY': -3,
+ 'WITH_CLEANUP': -1,
'LOOKUP_METHOD': 1,
'LIST_APPEND': -2,
}
# use pattern match
patterns = [
+ ('ROT_', 0),
+ ('UNARY_', 0),
('BINARY_', -1),
+ ('INPLACE_', -1),
('LOAD_', 1),
+ ('SETUP_', 0),
+ ('JUMP_IF_', 0),
]
+ def gettracker(opname):
+ # first look for an explicit tracker
+ try:
+ return globals()['depth_' + opname]
+ except KeyError:
+ pass
+ # then look for an explicit constant effect
+ try:
+ delta = effect[opname]
+ except KeyError:
+ # then do pattern matching
+ for pat, delta in patterns:
+ if opname.startswith(pat):
+ break
+ else:
+ raise InternalCompilerError("no stack effect registered for "
+ + opname)
+ def tracker(argc):
+ return delta
+ return tracker
+
+ effect_along_jump = {
+ 'JUMP_FORWARD': 0,
+ 'JUMP_ABSOLUTE': 0,
+ 'JUMP_IF_TRUE': 0,
+ 'JUMP_IF_FALSE': 0,
+ 'FOR_ITER': -1,
+ 'SETUP_LOOP': 0,
+ 'SETUP_EXCEPT': 3,
+ 'SETUP_FINALLY': 3,
+ }
+ def geteffect_jump(opname):
+ try:
+ return effect_along_jump[opname]
+ except KeyError:
+ raise InternalCompilerError("no stack effect registered for "
+ "the branch of " + opname)
-findDepth = StackDepthTracker().findDepth
+ for opname, opcode in pythonopcode.opmap.items():
+ if opname in ops_interrupt_unconditionally:
+ continue
+ if opname not in ops_jump_unconditionally:
+ # the effect on the stack depth when execution goes from
+ # this instruction to the next one
+ DEPTH_OP_TRACKER[opcode] = gettracker(opname)
+ if opname in ops_jumps:
+ DEPTH_OP_EFFECT_ALONG_JUMP[opcode] = geteffect_jump(opname)
+
+
+ops_interrupt_unconditionally = ('RETURN_VALUE', 'RAISE_VARARGS',
+ 'CONTINUE_LOOP', 'BREAK_LOOP')
+ops_jump_unconditionally = ('JUMP_ABSOLUTE', 'JUMP_FORWARD')
+ops_jumps = list(PyFlowGraph.hasjrel) + list(PyFlowGraph.hasjabs)
+
+DEPTH_OP_TRACKER = {}
+DEPTH_OP_EFFECT_ALONG_JUMP = {}
+setup_stack_depth_tracker()
Modified: pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pycodegen.py
==============================================================================
--- pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pycodegen.py (original)
+++ pypy/branch/astcompilertests/pypy/interpreter/astcompiler/pycodegen.py Mon Jan 7 19:35:37 2008
@@ -161,7 +161,9 @@
return self.graph.emitop_obj( inst, obj )
def emitop_code(self, inst, gen):
- return self.graph.emitop_code( inst, gen )
+ code = gen.getCode()
+ w_code = self.space.wrap(code)
+ return self.graph.emitop_obj( inst, w_code )
def emitop_int(self, inst, op):
assert isinstance(op, int)
@@ -170,14 +172,10 @@
def emitop_block(self, inst, block):
return self.graph.emitop_block( inst, block )
- def nextBlock(self, block=None ):
+ def nextBlock(self, block ):
"""graph delegation"""
return self.graph.nextBlock( block )
- def startBlock(self, block ):
- """graph delegation"""
- return self.graph.startBlock( block )
-
def newBlock(self):
"""graph delegation"""
return self.graph.newBlock()
@@ -315,8 +313,6 @@
def visitFunction(self, node):
self._visitFuncOrLambda(node, isLambda=0)
space = self.space
- if not space.is_w(node.w_doc, space.w_None):
- self.setDocstring(node.w_doc)
self.storeName(node.name, node.lineno)
def visitLambda(self, node):
@@ -392,11 +388,10 @@
self.set_lineno(test)
nextTest = self.newBlock()
test.opt_accept_jump_if(self, False, nextTest)
- self.nextBlock()
self.emit('POP_TOP')
suite.accept( self )
self.emitop_block('JUMP_FORWARD', end)
- self.startBlock(nextTest)
+ self.nextBlock(nextTest)
self.emit('POP_TOP')
if node.else_:
node.else_.accept( self )
@@ -422,15 +417,14 @@
self.set_lineno(node, force=True)
if is_constant_true(self.space, node.test):
# "while 1:"
- self.nextBlock()
+ pass
else:
node.test.opt_accept_jump_if(self, False, else_)
- self.nextBlock()
self.emit('POP_TOP')
node.body.accept( self )
self.emitop_block('JUMP_ABSOLUTE', loop)
- self.startBlock(else_) # or just the POPs if not else clause
+ self.nextBlock(else_) # or just the POPs if not else clause
self.emit('POP_TOP')
self.emit('POP_BLOCK')
self.setups.pop()
@@ -475,7 +469,6 @@
if kind == LOOP:
self.set_lineno(node)
self.emitop_block('JUMP_ABSOLUTE', block)
- self.nextBlock()
elif kind == EXCEPT or kind == TRY_FINALLY:
self.set_lineno(node)
# find the block that starts the loop
@@ -492,7 +485,6 @@
if kind != LOOP:
raise SyntaxError( "'continue' not properly in loop", node.lineno)
self.emitop_block('CONTINUE_LOOP', loop_block)
- self.nextBlock()
elif kind == END_FINALLY:
msg = "'continue' not supported inside 'finally' clause"
raise SyntaxError( msg, node.lineno )
@@ -502,7 +494,6 @@
for child in node.nodes[:-1]:
child.accept( self )
self.emitop_block(jump, end)
- self.nextBlock()
self.emit('POP_TOP')
node.nodes[-1].accept( self )
self.nextBlock(end)
@@ -585,7 +576,6 @@
self.emit('ROT_THREE')
self.emitop('COMPARE_OP', op)
self.emitop_block('JUMP_IF_FALSE', cleanup)
- self.nextBlock()
self.emit('POP_TOP')
# now do the last comparison
if node.ops:
@@ -595,7 +585,7 @@
if len(node.ops) > 1:
end = self.newBlock()
self.emitop_block('JUMP_FORWARD', end)
- self.startBlock(cleanup)
+ self.nextBlock(cleanup)
self.emit('ROT_TWO')
self.emit('POP_TOP')
self.nextBlock(end)
@@ -634,11 +624,11 @@
if cont:
skip_one = self.newBlock()
self.emitop_block('JUMP_FORWARD', skip_one)
- self.startBlock(cont)
+ self.nextBlock(cont)
self.emit('POP_TOP')
self.nextBlock(skip_one)
self.emitop_block('JUMP_ABSOLUTE', start)
- self.startBlock(anchor)
+ self.nextBlock(anchor)
self._implicitNameOp('DELETE', tmpname)
self.__list_count = self.__list_count - 1
@@ -652,7 +642,6 @@
self.nextBlock(start)
self.set_lineno(node, force=True)
self.emitop_block('FOR_ITER', anchor)
- self.nextBlock()
node.assign.accept( self )
return start, anchor
@@ -711,11 +700,11 @@
if cont:
skip_one = self.newBlock()
self.emitop_block('JUMP_FORWARD', skip_one)
- self.startBlock(cont)
+ self.nextBlock(cont)
self.emit('POP_TOP')
self.nextBlock(skip_one)
self.emitop_block('JUMP_ABSOLUTE', start)
- self.startBlock(anchor)
+ self.nextBlock(anchor)
self.emitop_obj('LOAD_CONST', self.space.w_None)
def _visitGenExprFor(self, node):
@@ -731,7 +720,6 @@
self.nextBlock(start)
self.set_lineno(node, force=True)
self.emitop_block('FOR_ITER', anchor)
- self.nextBlock()
node.assign.accept( self )
return start, anchor
@@ -752,9 +740,7 @@
# XXX AssertionError appears to be special case -- it is always
# loaded as a global even if there is a local name. I guess this
# is a sort of renaming op.
- self.nextBlock()
node.test.opt_accept_jump_if(self, True, end)
- self.nextBlock()
self.emit('POP_TOP')
self.emitop('LOAD_GLOBAL', 'AssertionError')
if node.fail:
@@ -795,10 +781,9 @@
self.emit('POP_BLOCK')
self.setups.pop()
self.emitop_block('JUMP_FORWARD', lElse)
- self.startBlock(handlers)
+ self.nextBlock(handlers)
last = len(node.handlers) - 1
- next = None
for expr, target, body in node.handlers:
if expr:
self.set_lineno(expr)
@@ -807,7 +792,6 @@
self.emitop('COMPARE_OP', 'exception match')
next = self.newBlock()
self.emitop_block('JUMP_IF_FALSE', next)
- self.nextBlock()
self.emit('POP_TOP')
else:
next = None
@@ -819,8 +803,8 @@
self.emit('POP_TOP')
body.accept( self )
self.emitop_block('JUMP_FORWARD', end)
- self.nextBlock(next)
if expr: # XXX
+ self.nextBlock(next)
self.emit('POP_TOP')
self.emit('END_FINALLY')
if node.else_:
@@ -1430,7 +1414,7 @@
CodeGenerator.__init__(self, space, graph)
self.optimized = 1
- if not isLambda and not space.is_w(func.w_doc, space.w_None):
+ if not isLambda:
self.setDocstring(func.w_doc)
if func.varargs:
@@ -1444,7 +1428,6 @@
return self.module
def finish(self):
- self.graph.startExitBlock()
if not self.isLambda:
self.emitop_obj('LOAD_CONST', self.space.w_None)
self.emit('RETURN_VALUE')
@@ -1510,14 +1493,12 @@
CodeGenerator.__init__(self, space, graph)
self.graph.setFlag(CO_NEWLOCALS)
- if not space.is_w(klass.w_doc, space.w_None):
- self.setDocstring(klass.w_doc)
+ self.setDocstring(klass.w_doc)
def get_module(self):
return self.module
def finish(self):
- self.graph.startExitBlock()
self.emit('LOAD_LOCALS')
self.emit('RETURN_VALUE')
Modified: pypy/branch/astcompilertests/pypy/interpreter/astcompiler/test/test_compiler.py
==============================================================================
--- pypy/branch/astcompilertests/pypy/interpreter/astcompiler/test/test_compiler.py (original)
+++ pypy/branch/astcompilertests/pypy/interpreter/astcompiler/test/test_compiler.py Mon Jan 7 19:35:37 2008
@@ -325,6 +325,11 @@
"""doc"""; print 1
a=1
''', "doc"),
+ ('''
+ class Foo(object): pass
+ foo = Foo()
+ exec "'moduledoc'" in foo.__dict__
+ ''', "moduledoc"),
]:
yield self.simple_test, source, "foo.__doc__", expected
@@ -363,12 +368,35 @@
yield self.st, "k=2; x = sum(n+2 for n in [6, 1, k])", 'x', 15
yield self.st, "k=2; x = sum(n+2 for n in (6, 1, k))", 'x', 15
+ def test_closure(self):
+ decl = py.code.Source("""
+ def make_adder(n):
+ def add(m):
+ return n + m
+ return add
+ """)
+ decl = str(decl) + "\n"
+ yield self.st, decl + "x = make_adder(40)(2)", 'x', 42
+
+ def test_try_except_finally(self):
+ yield self.simple_test, """
+ try:
+ x = 5
+ try:
+ if x > 2:
+ raise ValueError
+ finally:
+ x += 1
+ except ValueError:
+ x *= 7
+ """, 'x', 42
+
def test_pprint(self):
# a larger example that showed a bug with jumps
# over more than 256 bytes
decl = py.code.Source("""
def _safe_repr(object, context, maxlevels, level):
- typ = _type(object)
+ typ = type(object)
if typ is str:
if 'locale' not in _sys.modules:
return repr(object), True, False
@@ -392,7 +420,7 @@
if issubclass(typ, dict) and r is dict.__repr__:
if not object:
return "{}", True, False
- objid = _id(object)
+ objid = id(object)
if maxlevels and level > maxlevels:
return "{...}", False, objid in context
if objid in context:
@@ -412,7 +440,7 @@
if krecur or vrecur:
recursive = True
del context[objid]
- return "{%s}" % _commajoin(components), readable, recursive
+ return "{%s}" % ', '.join(components), readable, recursive
if (issubclass(typ, list) and r is list.__repr__) or \
(issubclass(typ, tuple) and r is tuple.__repr__):
@@ -426,7 +454,7 @@
if not object:
return "()", True, False
format = "(%s)"
- objid = _id(object)
+ objid = id(object)
if maxlevels and level > maxlevels:
return format % "...", False, objid in context
if objid in context:
@@ -445,10 +473,13 @@
if orecur:
recursive = True
del context[objid]
- return format % _commajoin(components), readable, recursive
+ return format % ', '.join(components), readable, recursive
rep = repr(object)
return rep, (rep and not rep.startswith('<')), False
""")
decl = str(decl) + '\n'
- yield self.st, decl + 'x=_safe_repr([5], {}, 3, 0)', 'x', '[5]'
+ g = {}
+ exec decl in g
+ expected = g['_safe_repr']([5], {}, 3, 0)
+ yield self.st, decl + 'x=_safe_repr([5], {}, 3, 0)', 'x', expected
More information about the Pypy-commit
mailing list