+++ /dev/null
-"""A flow graph representation for Python bytecode"""\r
-\r
-import dis\r
-import types\r
-import sys\r
-\r
-from compiler import misc\r
-from compiler.consts \\r
- import CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS\r
-\r
-class FlowGraph:\r
- def __init__(self):\r
- self.current = self.entry = Block()\r
- self.exit = Block("exit")\r
- self.blocks = misc.Set()\r
- self.blocks.add(self.entry)\r
- self.blocks.add(self.exit)\r
-\r
- def startBlock(self, block):\r
- if self._debug:\r
- if self.current:\r
- print "end", repr(self.current)\r
- print " next", self.current.next\r
- print " prev", self.current.prev\r
- print " ", self.current.get_children()\r
- print repr(block)\r
- self.current = block\r
-\r
- def nextBlock(self, block=None):\r
- # XXX think we need to specify when there is implicit transfer\r
- # from one block to the next. might be better to represent this\r
- # with explicit JUMP_ABSOLUTE instructions that are optimized\r
- # out when they are unnecessary.\r
- #\r
- # I think this strategy works: each block has a child\r
- # designated as "next" which is returned as the last of the\r
- # children. because the nodes in a graph are emitted in\r
- # reverse post order, the "next" block will always be emitted\r
- # immediately after its parent.\r
- # Worry: maintaining this invariant could be tricky\r
- if block is None:\r
- block = self.newBlock()\r
-\r
- # Note: If the current block ends with an unconditional control\r
- # transfer, then it is techically incorrect to add an implicit\r
- # transfer to the block graph. Doing so results in code generation\r
- # for unreachable blocks. That doesn't appear to be very common\r
- # with Python code and since the built-in compiler doesn't optimize\r
- # it out we don't either.\r
- self.current.addNext(block)\r
- self.startBlock(block)\r
-\r
- def newBlock(self):\r
- b = Block()\r
- self.blocks.add(b)\r
- return b\r
-\r
- def startExitBlock(self):\r
- self.startBlock(self.exit)\r
-\r
- _debug = 0\r
-\r
- def _enable_debug(self):\r
- self._debug = 1\r
-\r
- def _disable_debug(self):\r
- self._debug = 0\r
-\r
- def emit(self, *inst):\r
- if self._debug:\r
- print "\t", inst\r
- if len(inst) == 2 and isinstance(inst[1], Block):\r
- self.current.addOutEdge(inst[1])\r
- self.current.emit(inst)\r
-\r
- def getBlocksInOrder(self):\r
- """Return the blocks in reverse postorder\r
-\r
- i.e. each node appears before all of its successors\r
- """\r
- order = order_blocks(self.entry, self.exit)\r
- return order\r
-\r
- def getBlocks(self):\r
- return self.blocks.elements()\r
-\r
- def getRoot(self):\r
- """Return nodes appropriate for use with dominator"""\r
- return self.entry\r
-\r
- def getContainedGraphs(self):\r
- l = []\r
- for b in self.getBlocks():\r
- l.extend(b.getContainedGraphs())\r
- return l\r
-\r
-\r
-def order_blocks(start_block, exit_block):\r
- """Order blocks so that they are emitted in the right order"""\r
- # Rules:\r
- # - when a block has a next block, the next block must be emitted just after\r
- # - when a block has followers (relative jumps), it must be emitted before\r
- # them\r
- # - all reachable blocks must be emitted\r
- order = []\r
-\r
- # Find all the blocks to be emitted.\r
- remaining = set()\r
- todo = [start_block]\r
- while todo:\r
- b = todo.pop()\r
- if b in remaining:\r
- continue\r
- remaining.add(b)\r
- for c in b.get_children():\r
- if c not in remaining:\r
- todo.append(c)\r
-\r
- # A block is dominated by another block if that block must be emitted\r
- # before it.\r
- dominators = {}\r
- for b in remaining:\r
- if __debug__ and b.next:\r
- assert b is b.next[0].prev[0], (b, b.next)\r
- # Make sure every block appears in dominators, even if no\r
- # other block must precede it.\r
- dominators.setdefault(b, set())\r
- # preceeding blocks dominate following blocks\r
- for c in b.get_followers():\r
- while 1:\r
- dominators.setdefault(c, set()).add(b)\r
- # Any block that has a next pointer leading to c is also\r
- # dominated because the whole chain will be emitted at once.\r
- # Walk backwards and add them all.\r
- if c.prev and c.prev[0] is not b:\r
- c = c.prev[0]\r
- else:\r
- break\r
-\r
- def find_next():\r
- # Find a block that can be emitted next.\r
- for b in remaining:\r
- for c in dominators[b]:\r
- if c in remaining:\r
- break # can't emit yet, dominated by a remaining block\r
- else:\r
- return b\r
- assert 0, 'circular dependency, cannot find next block'\r
-\r
- b = start_block\r
- while 1:\r
- order.append(b)\r
- remaining.discard(b)\r
- if b.next:\r
- b = b.next[0]\r
- continue\r
- elif b is not exit_block and not b.has_unconditional_transfer():\r
- order.append(exit_block)\r
- if not remaining:\r
- break\r
- b = find_next()\r
- return order\r
-\r
-\r
-class Block:\r
- _count = 0\r
-\r
- def __init__(self, label=''):\r
- self.insts = []\r
- self.outEdges = set()\r
- self.label = label\r
- self.bid = Block._count\r
- self.next = []\r
- self.prev = []\r
- Block._count = Block._count + 1\r
-\r
- def __repr__(self):\r
- if self.label:\r
- return "<block %s id=%d>" % (self.label, self.bid)\r
- else:\r
- return "<block id=%d>" % (self.bid)\r
-\r
- def __str__(self):\r
- insts = map(str, self.insts)\r
- return "<block %s %d:\n%s>" % (self.label, self.bid,\r
- '\n'.join(insts))\r
-\r
- def emit(self, inst):\r
- op = inst[0]\r
- self.insts.append(inst)\r
-\r
- def getInstructions(self):\r
- return self.insts\r
-\r
- def addOutEdge(self, block):\r
- self.outEdges.add(block)\r
-\r
- def addNext(self, block):\r
- self.next.append(block)\r
- assert len(self.next) == 1, map(str, self.next)\r
- block.prev.append(self)\r
- assert len(block.prev) == 1, map(str, block.prev)\r
-\r
- _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS',\r
- 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP',\r
- )\r
-\r
- def has_unconditional_transfer(self):\r
- """Returns True if there is an unconditional transfer to an other block\r
- at the end of this block. This means there is no risk for the bytecode\r
- executer to go past this block's bytecode."""\r
- try:\r
- op, arg = self.insts[-1]\r
- except (IndexError, ValueError):\r
- return\r
- return op in self._uncond_transfer\r
-\r
- def get_children(self):\r
- return list(self.outEdges) + self.next\r
-\r
- def get_followers(self):\r
- """Get the whole list of followers, including the next block."""\r
- followers = set(self.next)\r
- # Blocks that must be emitted *after* this one, because of\r
- # bytecode offsets (e.g. relative jumps) pointing to them.\r
- for inst in self.insts:\r
- if inst[0] in PyFlowGraph.hasjrel:\r
- followers.add(inst[1])\r
- return followers\r
-\r
- def getContainedGraphs(self):\r
- """Return all graphs contained within this block.\r
-\r
- For example, a MAKE_FUNCTION block will contain a reference to\r
- the graph for the function body.\r
- """\r
- contained = []\r
- for inst in self.insts:\r
- if len(inst) == 1:\r
- continue\r
- op = inst[1]\r
- if hasattr(op, 'graph'):\r
- contained.append(op.graph)\r
- return contained\r
-\r
-# flags for code objects\r
-\r
-# the FlowGraph is transformed in place; it exists in one of these states\r
-RAW = "RAW"\r
-FLAT = "FLAT"\r
-CONV = "CONV"\r
-DONE = "DONE"\r
-\r
-class PyFlowGraph(FlowGraph):\r
- super_init = FlowGraph.__init__\r
-\r
- def __init__(self, name, filename, args=(), optimized=0, klass=None):\r
- self.super_init()\r
- self.name = name\r
- self.filename = filename\r
- self.docstring = None\r
- self.args = args # XXX\r
- self.argcount = getArgCount(args)\r
- self.klass = klass\r
- if optimized:\r
- self.flags = CO_OPTIMIZED | CO_NEWLOCALS\r
- else:\r
- self.flags = 0\r
- self.consts = []\r
- self.names = []\r
- # Free variables found by the symbol table scan, including\r
- # variables used only in nested scopes, are included here.\r
- self.freevars = []\r
- self.cellvars = []\r
- # The closure list is used to track the order of cell\r
- # variables and free variables in the resulting code object.\r
- # The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both\r
- # kinds of variables.\r
- self.closure = []\r
- self.varnames = list(args) or []\r
- for i in range(len(self.varnames)):\r
- var = self.varnames[i]\r
- if isinstance(var, TupleArg):\r
- self.varnames[i] = var.getName()\r
- self.stage = RAW\r
-\r
- def setDocstring(self, doc):\r
- self.docstring = doc\r
-\r
- def setFlag(self, flag):\r
- self.flags = self.flags | flag\r
- if flag == CO_VARARGS:\r
- self.argcount = self.argcount - 1\r
-\r
- def checkFlag(self, flag):\r
- if self.flags & flag:\r
- return 1\r
-\r
- def setFreeVars(self, names):\r
- self.freevars = list(names)\r
-\r
- def setCellVars(self, names):\r
- self.cellvars = names\r
-\r
- def getCode(self):\r
- """Get a Python code object"""\r
- assert self.stage == RAW\r
- self.computeStackDepth()\r
- self.flattenGraph()\r
- assert self.stage == FLAT\r
- self.convertArgs()\r
- assert self.stage == CONV\r
- self.makeByteCode()\r
- assert self.stage == DONE\r
- return self.newCodeObject()\r
-\r
- def dump(self, io=None):\r
- if io:\r
- save = sys.stdout\r
- sys.stdout = io\r
- pc = 0\r
- for t in self.insts:\r
- opname = t[0]\r
- if opname == "SET_LINENO":\r
- print\r
- if len(t) == 1:\r
- print "\t", "%3d" % pc, opname\r
- pc = pc + 1\r
- else:\r
- print "\t", "%3d" % pc, opname, t[1]\r
- pc = pc + 3\r
- if io:\r
- sys.stdout = save\r
-\r
- def computeStackDepth(self):\r
- """Compute the max stack depth.\r
-\r
- Approach is to compute the stack effect of each basic block.\r
- Then find the path through the code with the largest total\r
- effect.\r
- """\r
- depth = {}\r
- exit = None\r
- for b in self.getBlocks():\r
- depth[b] = findDepth(b.getInstructions())\r
-\r
- seen = {}\r
-\r
- def max_depth(b, d):\r
- if b in seen:\r
- return d\r
- seen[b] = 1\r
- d = d + depth[b]\r
- children = b.get_children()\r
- if children:\r
- return max([max_depth(c, d) for c in children])\r
- else:\r
- if not b.label == "exit":\r
- return max_depth(self.exit, d)\r
- else:\r
- return d\r
-\r
- self.stacksize = max_depth(self.entry, 0)\r
-\r
- def flattenGraph(self):\r
- """Arrange the blocks in order and resolve jumps"""\r
- assert self.stage == RAW\r
- self.insts = insts = []\r
- pc = 0\r
- begin = {}\r
- end = {}\r
- for b in self.getBlocksInOrder():\r
- begin[b] = pc\r
- for inst in b.getInstructions():\r
- insts.append(inst)\r
- if len(inst) == 1:\r
- pc = pc + 1\r
- elif inst[0] != "SET_LINENO":\r
- # arg takes 2 bytes\r
- pc = pc + 3\r
- end[b] = pc\r
- pc = 0\r
- for i in range(len(insts)):\r
- inst = insts[i]\r
- if len(inst) == 1:\r
- pc = pc + 1\r
- elif inst[0] != "SET_LINENO":\r
- pc = pc + 3\r
- opname = inst[0]\r
- if opname in self.hasjrel:\r
- oparg = inst[1]\r
- offset = begin[oparg] - pc\r
- insts[i] = opname, offset\r
- elif opname in self.hasjabs:\r
- insts[i] = opname, begin[inst[1]]\r
- self.stage = FLAT\r
-\r
- hasjrel = set()\r
- for i in dis.hasjrel:\r
- hasjrel.add(dis.opname[i])\r
- hasjabs = set()\r
- for i in dis.hasjabs:\r
- hasjabs.add(dis.opname[i])\r
-\r
- def convertArgs(self):\r
- """Convert arguments from symbolic to concrete form"""\r
- assert self.stage == FLAT\r
- self.consts.insert(0, self.docstring)\r
- self.sort_cellvars()\r
- for i in range(len(self.insts)):\r
- t = self.insts[i]\r
- if len(t) == 2:\r
- opname, oparg = t\r
- conv = self._converters.get(opname, None)\r
- if conv:\r
- self.insts[i] = opname, conv(self, oparg)\r
- self.stage = CONV\r
-\r
- def sort_cellvars(self):\r
- """Sort cellvars in the order of varnames and prune from freevars.\r
- """\r
- cells = {}\r
- for name in self.cellvars:\r
- cells[name] = 1\r
- self.cellvars = [name for name in self.varnames\r
- if name in cells]\r
- for name in self.cellvars:\r
- del cells[name]\r
- self.cellvars = self.cellvars + cells.keys()\r
- self.closure = self.cellvars + self.freevars\r
-\r
- def _lookupName(self, name, list):\r
- """Return index of name in list, appending if necessary\r
-\r
- This routine uses a list instead of a dictionary, because a\r
- dictionary can't store two different keys if the keys have the\r
- same value but different types, e.g. 2 and 2L. The compiler\r
- must treat these two separately, so it does an explicit type\r
- comparison before comparing the values.\r
- """\r
- t = type(name)\r
- for i in range(len(list)):\r
- if t == type(list[i]) and list[i] == name:\r
- return i\r
- end = len(list)\r
- list.append(name)\r
- return end\r
-\r
- _converters = {}\r
- def _convert_LOAD_CONST(self, arg):\r
- if hasattr(arg, 'getCode'):\r
- arg = arg.getCode()\r
- return self._lookupName(arg, self.consts)\r
-\r
- def _convert_LOAD_FAST(self, arg):\r
- self._lookupName(arg, self.names)\r
- return self._lookupName(arg, self.varnames)\r
- _convert_STORE_FAST = _convert_LOAD_FAST\r
- _convert_DELETE_FAST = _convert_LOAD_FAST\r
-\r
- def _convert_LOAD_NAME(self, arg):\r
- if self.klass is None:\r
- self._lookupName(arg, self.varnames)\r
- return self._lookupName(arg, self.names)\r
-\r
- def _convert_NAME(self, arg):\r
- if self.klass is None:\r
- self._lookupName(arg, self.varnames)\r
- return self._lookupName(arg, self.names)\r
- _convert_STORE_NAME = _convert_NAME\r
- _convert_DELETE_NAME = _convert_NAME\r
- _convert_IMPORT_NAME = _convert_NAME\r
- _convert_IMPORT_FROM = _convert_NAME\r
- _convert_STORE_ATTR = _convert_NAME\r
- _convert_LOAD_ATTR = _convert_NAME\r
- _convert_DELETE_ATTR = _convert_NAME\r
- _convert_LOAD_GLOBAL = _convert_NAME\r
- _convert_STORE_GLOBAL = _convert_NAME\r
- _convert_DELETE_GLOBAL = _convert_NAME\r
-\r
- def _convert_DEREF(self, arg):\r
- self._lookupName(arg, self.names)\r
- self._lookupName(arg, self.varnames)\r
- return self._lookupName(arg, self.closure)\r
- _convert_LOAD_DEREF = _convert_DEREF\r
- _convert_STORE_DEREF = _convert_DEREF\r
-\r
- def _convert_LOAD_CLOSURE(self, arg):\r
- self._lookupName(arg, self.varnames)\r
- return self._lookupName(arg, self.closure)\r
-\r
- _cmp = list(dis.cmp_op)\r
- def _convert_COMPARE_OP(self, arg):\r
- return self._cmp.index(arg)\r
-\r
- # similarly for other opcodes...\r
-\r
- for name, obj in locals().items():\r
- if name[:9] == "_convert_":\r
- opname = name[9:]\r
- _converters[opname] = obj\r
- del name, obj, opname\r
-\r
- def makeByteCode(self):\r
- assert self.stage == CONV\r
- self.lnotab = lnotab = LineAddrTable()\r
- for t in self.insts:\r
- opname = t[0]\r
- if len(t) == 1:\r
- lnotab.addCode(self.opnum[opname])\r
- else:\r
- oparg = t[1]\r
- if opname == "SET_LINENO":\r
- lnotab.nextLine(oparg)\r
- continue\r
- hi, lo = twobyte(oparg)\r
- try:\r
- lnotab.addCode(self.opnum[opname], lo, hi)\r
- except ValueError:\r
- print opname, oparg\r
- print self.opnum[opname], lo, hi\r
- raise\r
- self.stage = DONE\r
-\r
- opnum = {}\r
- for num in range(len(dis.opname)):\r
- opnum[dis.opname[num]] = num\r
- del num\r
-\r
- def newCodeObject(self):\r
- assert self.stage == DONE\r
- if (self.flags & CO_NEWLOCALS) == 0:\r
- nlocals = 0\r
- else:\r
- nlocals = len(self.varnames)\r
- argcount = self.argcount\r
- if self.flags & CO_VARKEYWORDS:\r
- argcount = argcount - 1\r
- return types.CodeType(argcount, nlocals, self.stacksize, self.flags,\r
- self.lnotab.getCode(), self.getConsts(),\r
- tuple(self.names), tuple(self.varnames),\r
- self.filename, self.name, self.lnotab.firstline,\r
- self.lnotab.getTable(), tuple(self.freevars),\r
- tuple(self.cellvars))\r
-\r
- def getConsts(self):\r
- """Return a tuple for the const slot of the code object\r
-\r
- Must convert references to code (MAKE_FUNCTION) to code\r
- objects recursively.\r
- """\r
- l = []\r
- for elt in self.consts:\r
- if isinstance(elt, PyFlowGraph):\r
- elt = elt.getCode()\r
- l.append(elt)\r
- return tuple(l)\r
-\r
-def isJump(opname):\r
- if opname[:4] == 'JUMP':\r
- return 1\r
-\r
-class TupleArg:\r
- """Helper for marking func defs with nested tuples in arglist"""\r
- def __init__(self, count, names):\r
- self.count = count\r
- self.names = names\r
- def __repr__(self):\r
- return "TupleArg(%s, %s)" % (self.count, self.names)\r
- def getName(self):\r
- return ".%d" % self.count\r
-\r
-def getArgCount(args):\r
- argcount = len(args)\r
- if args:\r
- for arg in args:\r
- if isinstance(arg, TupleArg):\r
- numNames = len(misc.flatten(arg.names))\r
- argcount = argcount - numNames\r
- return argcount\r
-\r
-def twobyte(val):\r
- """Convert an int argument into high and low bytes"""\r
- assert isinstance(val, int)\r
- return divmod(val, 256)\r
-\r
-class LineAddrTable:\r
- """lnotab\r
-\r
- This class builds the lnotab, which is documented in compile.c.\r
- Here's a brief recap:\r
-\r
- For each SET_LINENO instruction after the first one, two bytes are\r
- added to lnotab. (In some cases, multiple two-byte entries are\r
- added.) The first byte is the distance in bytes between the\r
- instruction for the last SET_LINENO and the current SET_LINENO.\r
- The second byte is offset in line numbers. If either offset is\r
- greater than 255, multiple two-byte entries are added -- see\r
- compile.c for the delicate details.\r
- """\r
-\r
- def __init__(self):\r
- self.code = []\r
- self.codeOffset = 0\r
- self.firstline = 0\r
- self.lastline = 0\r
- self.lastoff = 0\r
- self.lnotab = []\r
-\r
- def addCode(self, *args):\r
- for arg in args:\r
- self.code.append(chr(arg))\r
- self.codeOffset = self.codeOffset + len(args)\r
-\r
- def nextLine(self, lineno):\r
- if self.firstline == 0:\r
- self.firstline = lineno\r
- self.lastline = lineno\r
- else:\r
- # compute deltas\r
- addr = self.codeOffset - self.lastoff\r
- line = lineno - self.lastline\r
- # Python assumes that lineno always increases with\r
- # increasing bytecode address (lnotab is unsigned char).\r
- # Depending on when SET_LINENO instructions are emitted\r
- # this is not always true. Consider the code:\r
- # a = (1,\r
- # b)\r
- # In the bytecode stream, the assignment to "a" occurs\r
- # after the loading of "b". This works with the C Python\r
- # compiler because it only generates a SET_LINENO instruction\r
- # for the assignment.\r
- if line >= 0:\r
- push = self.lnotab.append\r
- while addr > 255:\r
- push(255); push(0)\r
- addr -= 255\r
- while line > 255:\r
- push(addr); push(255)\r
- line -= 255\r
- addr = 0\r
- if addr > 0 or line > 0:\r
- push(addr); push(line)\r
- self.lastline = lineno\r
- self.lastoff = self.codeOffset\r
-\r
- def getCode(self):\r
- return ''.join(self.code)\r
-\r
- def getTable(self):\r
- return ''.join(map(chr, self.lnotab))\r
-\r
-class StackDepthTracker:\r
- # XXX 1. need to keep track of stack depth on jumps\r
- # XXX 2. at least partly as a result, this code is broken\r
-\r
- def findDepth(self, insts, debug=0):\r
- depth = 0\r
- maxDepth = 0\r
- for i in insts:\r
- opname = i[0]\r
- if debug:\r
- print i,\r
- delta = self.effect.get(opname, None)\r
- if delta is not None:\r
- depth = depth + delta\r
- else:\r
- # now check patterns\r
- for pat, pat_delta in self.patterns:\r
- if opname[:len(pat)] == pat:\r
- delta = pat_delta\r
- depth = depth + delta\r
- break\r
- # if we still haven't found a match\r
- if delta is None:\r
- meth = getattr(self, opname, None)\r
- if meth is not None:\r
- depth = depth + meth(i[1])\r
- if depth > maxDepth:\r
- maxDepth = depth\r
- if debug:\r
- print depth, maxDepth\r
- return maxDepth\r
-\r
- effect = {\r
- 'POP_TOP': -1,\r
- 'DUP_TOP': 1,\r
- 'LIST_APPEND': -1,\r
- 'SET_ADD': -1,\r
- 'MAP_ADD': -2,\r
- 'SLICE+1': -1,\r
- 'SLICE+2': -1,\r
- 'SLICE+3': -2,\r
- 'STORE_SLICE+0': -1,\r
- 'STORE_SLICE+1': -2,\r
- 'STORE_SLICE+2': -2,\r
- 'STORE_SLICE+3': -3,\r
- 'DELETE_SLICE+0': -1,\r
- 'DELETE_SLICE+1': -2,\r
- 'DELETE_SLICE+2': -2,\r
- 'DELETE_SLICE+3': -3,\r
- 'STORE_SUBSCR': -3,\r
- 'DELETE_SUBSCR': -2,\r
- # PRINT_EXPR?\r
- 'PRINT_ITEM': -1,\r
- 'RETURN_VALUE': -1,\r
- 'YIELD_VALUE': -1,\r
- 'EXEC_STMT': -3,\r
- 'BUILD_CLASS': -2,\r
- 'STORE_NAME': -1,\r
- 'STORE_ATTR': -2,\r
- 'DELETE_ATTR': -1,\r
- 'STORE_GLOBAL': -1,\r
- 'BUILD_MAP': 1,\r
- 'COMPARE_OP': -1,\r
- 'STORE_FAST': -1,\r
- 'IMPORT_STAR': -1,\r
- 'IMPORT_NAME': -1,\r
- 'IMPORT_FROM': 1,\r
- 'LOAD_ATTR': 0, # unlike other loads\r
- # close enough...\r
- 'SETUP_EXCEPT': 3,\r
- 'SETUP_FINALLY': 3,\r
- 'FOR_ITER': 1,\r
- 'WITH_CLEANUP': -1,\r
- }\r
- # use pattern match\r
- patterns = [\r
- ('BINARY_', -1),\r
- ('LOAD_', 1),\r
- ]\r
-\r
- def UNPACK_SEQUENCE(self, count):\r
- return count-1\r
- def BUILD_TUPLE(self, count):\r
- return -count+1\r
- def BUILD_LIST(self, count):\r
- return -count+1\r
- def BUILD_SET(self, count):\r
- return -count+1\r
- def CALL_FUNCTION(self, argc):\r
- hi, lo = divmod(argc, 256)\r
- return -(lo + hi * 2)\r
- def CALL_FUNCTION_VAR(self, argc):\r
- return self.CALL_FUNCTION(argc)-1\r
- def CALL_FUNCTION_KW(self, argc):\r
- return self.CALL_FUNCTION(argc)-1\r
- def CALL_FUNCTION_VAR_KW(self, argc):\r
- return self.CALL_FUNCTION(argc)-2\r
- def MAKE_FUNCTION(self, argc):\r
- return -argc\r
- def MAKE_CLOSURE(self, argc):\r
- # XXX need to account for free variables too!\r
- return -argc\r
- def BUILD_SLICE(self, argc):\r
- if argc == 2:\r
- return -1\r
- elif argc == 3:\r
- return -2\r
- def DUP_TOPX(self, argc):\r
- return argc\r
-\r
-findDepth = StackDepthTracker().findDepth\r