1 """A flow graph representation for Python bytecode""" 2 3 import dis 4 import new 5 import sys 6 7 from compiler import misc 8 from compiler.consts \ 9 import CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS 10 11 class FlowGraph: 12 def __init__(self): 13 self.current = self.entry = Block() 14 self.exit = Block("exit") 15 self.blocks = misc.Set() 16 self.blocks.add(self.entry) 17 self.blocks.add(self.exit) 18 19 def startBlock(self, block): 20 if self._debug: 21 if self.current: 22 print "end", repr(self.current) 23 print " next", self.current.next 24 print " ", self.current.get_children() 25 print repr(block) 26 self.current = block 27 28 def nextBlock(self, block=None): 29 # XXX think we need to specify when there is implicit transfer 30 # from one block to the next. might be better to represent this 31 # with explicit JUMP_ABSOLUTE instructions that are optimized 32 # out when they are unnecessary. 33 # 34 # I think this strategy works: each block has a child 35 # designated as "next" which is returned as the last of the 36 # children. because the nodes in a graph are emitted in 37 # reverse post order, the "next" block will always be emitted 38 # immediately after its parent. 39 # Worry: maintaining this invariant could be tricky 40 if block is None: 41 block = self.newBlock() 42 43 # Note: If the current block ends with an unconditional 44 # control transfer, then it is incorrect to add an implicit 45 # transfer to the block graph. The current code requires 46 # these edges to get the blocks emitted in the right order, 47 # however. :-( If a client needs to remove these edges, call 48 # pruneEdges(). 49 50 self.current.addNext(block) 51 self.startBlock(block) 52 53 def newBlock(self): 54 b = Block() 55 self.blocks.add(b) 56 return b 57 58 def startExitBlock(self): 59 self.startBlock(self.exit) 60 61 _debug = 0 62 63 def _enable_debug(self): 64 self._debug = 1 65 66 def _disable_debug(self): 67 self._debug = 0 68 69 def emit(self, *inst): 70 if self._debug: 71 print "\t", inst 72 if inst[0] in ['RETURN_VALUE', 'YIELD_VALUE']: 73 self.current.addOutEdge(self.exit) 74 if len(inst) == 2 and isinstance(inst[1], Block): 75 self.current.addOutEdge(inst[1]) 76 self.current.emit(inst) 77 78 def getBlocksInOrder(self): 79 """Return the blocks in reverse postorder 80 81 i.e. each node appears before all of its successors 82 """ 83 # XXX make sure every node that doesn't have an explicit next 84 # is set so that next points to exit 85 for b in self.blocks.elements(): 86 if b is self.exit: 87 continue 88 if not b.next: 89 b.addNext(self.exit) 90 order = dfs_postorder(self.entry, {}) 91 order.reverse() 92 self.fixupOrder(order, self.exit) 93 # hack alert 94 if not self.exit in order: 95 order.append(self.exit) 96 97 return order 98 99 def fixupOrder(self, blocks, default_next): 100 """Fixup bad order introduced by DFS.""" 101 102 # XXX This is a total mess. There must be a better way to get 103 # the code blocks in the right order. 104 105 self.fixupOrderHonorNext(blocks, default_next) 106 self.fixupOrderForward(blocks, default_next) 107 108 def fixupOrderHonorNext(self, blocks, default_next): 109 """Fix one problem with DFS. 110 111 The DFS uses child block, but doesn't know about the special 112 "next" block. As a result, the DFS can order blocks so that a 113 block isn't next to the right block for implicit control 114 transfers. 115 """ 116 index = {} 117 for i in range(len(blocks)): 118 index[blocks[i]] = i 119 120 for i in range(0, len(blocks) - 1): 121 b = blocks[i] 122 n = blocks[i + 1] 123 if not b.next or b.next[0] == default_next or b.next[0] == n: 124 continue 125 # The blocks are in the wrong order. Find the chain of 126 # blocks to insert where they belong. 127 cur = b 128 chain = [] 129 elt = cur 130 while elt.next and elt.next[0] != default_next: 131 chain.append(elt.next[0]) 132 elt = elt.next[0] 133 # Now remove the blocks in the chain from the current 134 # block list, so that they can be re-inserted. 135 l = [] 136 for b in chain: 137 assert index[b] > i 138 l.append((index[b], b)) 139 l.sort() 140 l.reverse() 141 for j, b in l: 142 del blocks[index[b]] 143 # Insert the chain in the proper location 144 blocks[i:i + 1] = [cur] + chain 145 # Finally, re-compute the block indexes 146 for i in range(len(blocks)): 147 index[blocks[i]] = i 148 149 def fixupOrderForward(self, blocks, default_next): 150 """Make sure all JUMP_FORWARDs jump forward""" 151 index = {} 152 chains = [] 153 cur = [] 154 for b in blocks: 155 index[b] = len(chains) 156 cur.append(b) 157 if b.next and b.next[0] == default_next: 158 chains.append(cur) 159 cur = [] 160 chains.append(cur) 161 162 while 1: 163 constraints = [] 164 165 for i in range(len(chains)): 166 l = chains[i] 167 for b in l: 168 for c in b.get_children(): 169 if index[c] < i: 170 forward_p = 0 171 for inst in b.insts: 172 if inst[0] == 'JUMP_FORWARD': 173 if inst[1] == c: 174 forward_p = 1 175 if not forward_p: 176 continue 177 constraints.append((index[c], i)) 178 179 if not constraints: 180 break 181 182 # XXX just do one for now 183 # do swaps to get things in the right order 184 goes_before, a_chain = constraints[0] 185 assert a_chain > goes_before 186 c = chains[a_chain] 187 chains.remove(c) 188 chains.insert(goes_before, c) 189 190 del blocks[:] 191 for c in chains: 192 for b in c: 193 blocks.append(b) 194 195 def getBlocks(self): 196 return self.blocks.elements() 197 198 def getRoot(self): 199 """Return nodes appropriate for use with dominator""" 200 return self.entry 201 202 def getContainedGraphs(self): 203 l = [] 204 for b in self.getBlocks(): 205 l.extend(b.getContainedGraphs()) 206 return l 207 208 def dfs_postorder(b, seen): 209 """Depth-first search of tree rooted at b, return in postorder""" 210 order = [] 211 seen[b] = b 212 for c in b.get_children(): 213 if seen.has_key(c): 214 continue 215 order = order + dfs_postorder(c, seen) 216 order.append(b) 217 return order 218 219 class Block: 220 _count = 0 221 222 def __init__(self, label=''): 223 self.insts = [] 224 self.inEdges = misc.Set() 225 self.outEdges = misc.Set() 226 self.label = label 227 self.bid = Block._count 228 self.next = [] 229 Block._count = Block._count + 1 230 231 def __repr__(self): 232 if self.label: 233 return "<block %s id=%d>" % (self.label, self.bid) 234 else: 235 return "<block id=%d>" % (self.bid) 236 237 def __str__(self): 238 insts = map(str, self.insts) 239 return "<block %s %d:\n%s>" % (self.label, self.bid, 240 '\n'.join(insts)) 241 242 def emit(self, inst): 243 op = inst[0] 244 if op[:4] == 'JUMP': 245 self.outEdges.add(inst[1]) 246 self.insts.append(inst) 247 248 def getInstructions(self): 249 return self.insts 250 251 def addInEdge(self, block): 252 self.inEdges.add(block) 253 254 def addOutEdge(self, block): 255 self.outEdges.add(block) 256 257 def addNext(self, block): 258 self.next.append(block) 259 assert len(self.next) == 1, map(str, self.next) 260 261 _uncond_transfer = ('RETURN_VALUE', 'RAISE_VARARGS', 'YIELD_VALUE', 262 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'CONTINUE_LOOP') 263 264 def pruneNext(self): 265 """Remove bogus edge for unconditional transfers 266 267 Each block has a next edge that accounts for implicit control 268 transfers, e.g. from a JUMP_IF_FALSE to the block that will be 269 executed if the test is true. 270 271 These edges must remain for the current assembler code to 272 work. If they are removed, the dfs_postorder gets things in 273 weird orders. However, they shouldn't be there for other 274 purposes, e.g. conversion to SSA form. This method will 275 remove the next edge when it follows an unconditional control 276 transfer. 277 """ 278 try: 279 op, arg = self.insts[-1] 280 except (IndexError, ValueError): 281 return 282 if op in self._uncond_transfer: 283 self.next = [] 284 285 def get_children(self): 286 if self.next and self.next[0] in self.outEdges: 287 self.outEdges.remove(self.next[0]) 288 return self.outEdges.elements() + self.next 289 290 def getContainedGraphs(self): 291 """Return all graphs contained within this block. 292 293 For example, a MAKE_FUNCTION block will contain a reference to 294 the graph for the function body. 295 """ 296 contained = [] 297 for inst in self.insts: 298 if len(inst) == 1: 299 continue 300 op = inst[1] 301 if hasattr(op, 'graph'): 302 contained.append(op.graph) 303 return contained 304 305 # flags for code objects 306 307 # the FlowGraph is transformed in place; it exists in one of these states 308 RAW = "RAW" 309 FLAT = "FLAT" 310 CONV = "CONV" 311 DONE = "DONE" 312 313 class PyFlowGraph(FlowGraph): 314 super_init = FlowGraph.__init__ 315 316 def __init__(self, name, filename, args=(), optimized=0, klass=None): 317 self.super_init() 318 self.name = name 319 self.filename = filename 320 self.docstring = None 321 self.args = args # XXX 322 self.argcount = getArgCount(args) 323 self.klass = klass 324 if optimized: 325 self.flags = CO_OPTIMIZED | CO_NEWLOCALS 326 else: 327 self.flags = 0 328 self.consts = [] 329 self.names = [] 330 # Free variables found by the symbol table scan, including 331 # variables used only in nested scopes, are included here. 332 self.freevars = [] 333 self.cellvars = [] 334 # The closure list is used to track the order of cell 335 # variables and free variables in the resulting code object. 336 # The offsets used by LOAD_CLOSURE/LOAD_DEREF refer to both 337 # kinds of variables. 338 self.closure = [] 339 self.varnames = list(args) or [] 340 for i in range(len(self.varnames)): 341 var = self.varnames[i] 342 if isinstance(var, TupleArg): 343 self.varnames[i] = var.getName() 344 self.stage = RAW 345 346 def setDocstring(self, doc): 347 self.docstring = doc 348 349 def setFlag(self, flag): 350 self.flags = self.flags | flag 351 if flag == CO_VARARGS: 352 self.argcount = self.argcount - 1 353 354 def checkFlag(self, flag): 355 if self.flags & flag: 356 return 1 357 358 def setFreeVars(self, names): 359 self.freevars = list(names) 360 361 def setCellVars(self, names): 362 self.cellvars = names 363 364 def getCode(self): 365 """Get a Python code object""" 366 assert self.stage == RAW 367 self.computeStackDepth() 368 self.flattenGraph() 369 assert self.stage == FLAT 370 self.convertArgs() 371 assert self.stage == CONV 372 self.makeByteCode() 373 assert self.stage == DONE 374 return self.newCodeObject() 375 376 def dump(self, io=None): 377 if io: 378 save = sys.stdout 379 sys.stdout = io 380 pc = 0 381 for t in self.insts: 382 opname = t[0] 383 if opname == "SET_LINENO": 384 print 385 if len(t) == 1: 386 print "\t", "%3d" % pc, opname 387 pc = pc + 1 388 else: 389 print "\t", "%3d" % pc, opname, t[1] 390 pc = pc + 3 391 if io: 392 sys.stdout = save 393 394 def computeStackDepth(self): 395 """Compute the max stack depth. 396 397 Approach is to compute the stack effect of each basic block. 398 Then find the path through the code with the largest total 399 effect. 400 """ 401 depth = {} 402 exit = None 403 for b in self.getBlocks(): 404 depth[b] = findDepth(b.getInstructions()) 405 406 seen = {} 407 408 def max_depth(b, d): 409 if seen.has_key(b): 410 return d 411 seen[b] = 1 412 d = d + depth[b] 413 children = b.get_children() 414 if children: 415 return max([max_depth(c, d) for c in children]) 416 else: 417 if not b.label == "exit": 418 return max_depth(self.exit, d) 419 else: 420 return d 421 422 self.stacksize = max_depth(self.entry, 0) 423 424 def flattenGraph(self): 425 """Arrange the blocks in order and resolve jumps""" 426 assert self.stage == RAW 427 self.insts = insts = [] 428 pc = 0 429 begin = {} 430 end = {} 431 for b in self.getBlocksInOrder(): 432 begin[b] = pc 433 for inst in b.getInstructions(): 434 insts.append(inst) 435 if len(inst) == 1: 436 pc = pc + 1 437 elif inst[0] != "SET_LINENO": 438 # arg takes 2 bytes 439 pc = pc + 3 440 end[b] = pc 441 pc = 0 442 for i in range(len(insts)): 443 inst = insts[i] 444 if len(inst) == 1: 445 pc = pc + 1 446 elif inst[0] != "SET_LINENO": 447 pc = pc + 3 448 opname = inst[0] 449 if self.hasjrel.has_elt(opname): 450 oparg = inst[1] 451 offset = begin[oparg] - pc 452 insts[i] = opname, offset 453 elif self.hasjabs.has_elt(opname): 454 insts[i] = opname, begin[inst[1]] 455 self.stage = FLAT 456 457 hasjrel = misc.Set() 458 for i in dis.hasjrel: 459 hasjrel.add(dis.opname[i]) 460 hasjabs = misc.Set() 461 for i in dis.hasjabs: 462 hasjabs.add(dis.opname[i]) 463 464 def convertArgs(self): 465 """Convert arguments from symbolic to concrete form""" 466 assert self.stage == FLAT 467 self.consts.insert(0, self.docstring) 468 self.sort_cellvars() 469 for i in range(len(self.insts)): 470 t = self.insts[i] 471 if len(t) == 2: 472 opname, oparg = t 473 conv = self._converters.get(opname, None) 474 if conv: 475 self.insts[i] = opname, conv(self, oparg) 476 self.stage = CONV 477 478 def sort_cellvars(self): 479 """Sort cellvars in the order of varnames and prune from freevars. 480 """ 481 cells = {} 482 for name in self.cellvars: 483 cells[name] = 1 484 self.cellvars = [name for name in self.varnames 485 if cells.has_key(name)] 486 for name in self.cellvars: 487 del cells[name] 488 self.cellvars = self.cellvars + cells.keys() 489 self.closure = self.cellvars + self.freevars 490 491 def _lookupName(self, name, list): 492 """Return index of name in list, appending if necessary 493 494 This routine uses a list instead of a dictionary, because a 495 dictionary can't store two different keys if the keys have the 496 same value but different types, e.g. 2 and 2L. The compiler 497 must treat these two separately, so it does an explicit type 498 comparison before comparing the values. 499 """ 500 t = type(name) 501 for i in range(len(list)): 502 if t == type(list[i]) and list[i] == name: 503 return i 504 end = len(list) 505 list.append(name) 506 return end 507 508 _converters = {} 509 def _convert_LOAD_CONST(self, arg): 510 if hasattr(arg, 'getCode'): 511 arg = arg.getCode() 512 return self._lookupName(arg, self.consts) 513 514 def _convert_LOAD_FAST(self, arg): 515 self._lookupName(arg, self.names) 516 return self._lookupName(arg, self.varnames) 517 _convert_STORE_FAST = _convert_LOAD_FAST 518 _convert_DELETE_FAST = _convert_LOAD_FAST 519 520 def _convert_LOAD_NAME(self, arg): 521 if self.klass is None: 522 self._lookupName(arg, self.varnames) 523 return self._lookupName(arg, self.names) 524 525 def _convert_NAME(self, arg): 526 if self.klass is None: 527 self._lookupName(arg, self.varnames) 528 return self._lookupName(arg, self.names) 529 _convert_STORE_NAME = _convert_NAME 530 _convert_DELETE_NAME = _convert_NAME 531 _convert_IMPORT_NAME = _convert_NAME 532 _convert_IMPORT_FROM = _convert_NAME 533 _convert_STORE_ATTR = _convert_NAME 534 _convert_LOAD_ATTR = _convert_NAME 535 _convert_DELETE_ATTR = _convert_NAME 536 _convert_LOAD_GLOBAL = _convert_NAME 537 _convert_STORE_GLOBAL = _convert_NAME 538 _convert_DELETE_GLOBAL = _convert_NAME 539 540 def _convert_DEREF(self, arg): 541 self._lookupName(arg, self.names) 542 self._lookupName(arg, self.varnames) 543 return self._lookupName(arg, self.closure) 544 _convert_LOAD_DEREF = _convert_DEREF 545 _convert_STORE_DEREF = _convert_DEREF 546 547 def _convert_LOAD_CLOSURE(self, arg): 548 self._lookupName(arg, self.varnames) 549 return self._lookupName(arg, self.closure) 550 551 _cmp = list(dis.cmp_op) 552 def _convert_COMPARE_OP(self, arg): 553 return self._cmp.index(arg) 554 555 # similarly for other opcodes... 556 557 for name, obj in locals().items(): 558 if name[:9] == "_convert_": 559 opname = name[9:] 560 _converters[opname] = obj 561 del name, obj, opname 562 563 def makeByteCode(self): 564 assert self.stage == CONV 565 self.lnotab = lnotab = LineAddrTable() 566 for t in self.insts: 567 opname = t[0] 568 if len(t) == 1: 569 lnotab.addCode(self.opnum[opname]) 570 else: 571 oparg = t[1] 572 if opname == "SET_LINENO": 573 lnotab.nextLine(oparg) 574 continue 575 hi, lo = twobyte(oparg) 576 try: 577 lnotab.addCode(self.opnum[opname], lo, hi) 578 except ValueError: 579 print opname, oparg 580 print self.opnum[opname], lo, hi 581 raise 582 self.stage = DONE 583 584 opnum = {} 585 for num in range(len(dis.opname)): 586 opnum[dis.opname[num]] = num 587 del num 588 589 def newCodeObject(self): 590 assert self.stage == DONE 591 if (self.flags & CO_NEWLOCALS) == 0: 592 nlocals = 0 593 else: 594 nlocals = len(self.varnames) 595 argcount = self.argcount 596 if self.flags & CO_VARKEYWORDS: 597 argcount = argcount - 1 598 return new.code(argcount, nlocals, self.stacksize, self.flags, 599 self.lnotab.getCode(), self.getConsts(), 600 tuple(self.names), tuple(self.varnames), 601 self.filename, self.name, self.lnotab.firstline, 602 self.lnotab.getTable(), tuple(self.freevars), 603 tuple(self.cellvars)) 604 605 def getConsts(self): 606 """Return a tuple for the const slot of the code object 607 608 Must convert references to code (MAKE_FUNCTION) to code 609 objects recursively. 610 """ 611 l = [] 612 for elt in self.consts: 613 if isinstance(elt, PyFlowGraph): 614 elt = elt.getCode() 615 l.append(elt) 616 return tuple(l) 617 618 def isJump(opname): 619 if opname[:4] == 'JUMP': 620 return 1 621 622 class TupleArg: 623 """Helper for marking func defs with nested tuples in arglist""" 624 def __init__(self, count, names): 625 self.count = count 626 self.names = names 627 def __repr__(self): 628 return "TupleArg(%s, %s)" % (self.count, self.names) 629 def getName(self): 630 return ".%d" % self.count 631 632 def getArgCount(args): 633 argcount = len(args) 634 if args: 635 for arg in args: 636 if isinstance(arg, TupleArg): 637 numNames = len(misc.flatten(arg.names)) 638 argcount = argcount - numNames 639 return argcount 640 641 def twobyte(val): 642 """Convert an int argument into high and low bytes""" 643 assert isinstance(val, int) 644 return divmod(val, 256) 645 646 class LineAddrTable: 647 """lnotab 648 649 This class builds the lnotab, which is documented in compile.c. 650 Here's a brief recap: 651 652 For each SET_LINENO instruction after the first one, two bytes are 653 added to lnotab. (In some cases, multiple two-byte entries are 654 added.) The first byte is the distance in bytes between the 655 instruction for the last SET_LINENO and the current SET_LINENO. 656 The second byte is offset in line numbers. If either offset is 657 greater than 255, multiple two-byte entries are added -- see 658 compile.c for the delicate details. 659 """ 660 661 def __init__(self): 662 self.code = [] 663 self.codeOffset = 0 664 self.firstline = 0 665 self.lastline = 0 666 self.lastoff = 0 667 self.lnotab = [] 668 669 def addCode(self, *args): 670 for arg in args: 671 self.code.append(chr(arg)) 672 self.codeOffset = self.codeOffset + len(args) 673 674 def nextLine(self, lineno): 675 if self.firstline == 0: 676 self.firstline = lineno 677 self.lastline = lineno 678 else: 679 # compute deltas 680 addr = self.codeOffset - self.lastoff 681 line = lineno - self.lastline 682 # Python assumes that lineno always increases with 683 # increasing bytecode address (lnotab is unsigned char). 684 # Depending on when SET_LINENO instructions are emitted 685 # this is not always true. Consider the code: 686 # a = (1, 687 # b) 688 # In the bytecode stream, the assignment to "a" occurs 689 # after the loading of "b". This works with the C Python 690 # compiler because it only generates a SET_LINENO instruction 691 # for the assignment. 692 if line >= 0: 693 push = self.lnotab.append 694 while addr > 255: 695 push(255); push(0) 696 addr -= 255 697 while line > 255: 698 push(addr); push(255) 699 line -= 255 700 addr = 0 701 if addr > 0 or line > 0: 702 push(addr); push(line) 703 self.lastline = lineno 704 self.lastoff = self.codeOffset 705 706 def getCode(self): 707 return ''.join(self.code) 708 709 def getTable(self): 710 return ''.join(map(chr, self.lnotab)) 711 712 class StackDepthTracker: 713 # XXX 1. need to keep track of stack depth on jumps 714 # XXX 2. at least partly as a result, this code is broken 715 716 def findDepth(self, insts, debug=0): 717 depth = 0 718 maxDepth = 0 719 for i in insts: 720 opname = i[0] 721 if debug: 722 print i, 723 delta = self.effect.get(opname, None) 724 if delta is not None: 725 depth = depth + delta 726 else: 727 # now check patterns 728 for pat, pat_delta in self.patterns: 729 if opname[:len(pat)] == pat: 730 delta = pat_delta 731 depth = depth + delta 732 break 733 # if we still haven't found a match 734 if delta is None: 735 meth = getattr(self, opname, None) 736 if meth is not None: 737 depth = depth + meth(i[1]) 738 if depth > maxDepth: 739 maxDepth = depth 740 if debug: 741 print depth, maxDepth 742 return maxDepth 743 744 effect = { 745 'POP_TOP': -1, 746 'DUP_TOP': 1, 747 'LIST_APPEND': -2, 748 'SLICE+1': -1, 749 'SLICE+2': -1, 750 'SLICE+3': -2, 751 'STORE_SLICE+0': -1, 752 'STORE_SLICE+1': -2, 753 'STORE_SLICE+2': -2, 754 'STORE_SLICE+3': -3, 755 'DELETE_SLICE+0': -1, 756 'DELETE_SLICE+1': -2, 757 'DELETE_SLICE+2': -2, 758 'DELETE_SLICE+3': -3, 759 'STORE_SUBSCR': -3, 760 'DELETE_SUBSCR': -2, 761 # PRINT_EXPR? 762 'PRINT_ITEM': -1, 763 'RETURN_VALUE': -1, 764 'YIELD_VALUE': -1, 765 'EXEC_STMT': -3, 766 'BUILD_CLASS': -2, 767 'STORE_NAME': -1, 768 'STORE_ATTR': -2, 769 'DELETE_ATTR': -1, 770 'STORE_GLOBAL': -1, 771 'BUILD_MAP': 1, 772 'COMPARE_OP': -1, 773 'STORE_FAST': -1, 774 'IMPORT_STAR': -1, 775 'IMPORT_NAME': -1, 776 'IMPORT_FROM': 1, 777 'LOAD_ATTR': 0, # unlike other loads 778 # close enough... 779 'SETUP_EXCEPT': 3, 780 'SETUP_FINALLY': 3, 781 'FOR_ITER': 1, 782 'WITH_CLEANUP': -1, 783 } 784 # use pattern match 785 patterns = [ 786 ('BINARY_', -1), 787 ('LOAD_', 1), 788 ] 789 790 def UNPACK_SEQUENCE(self, count): 791 return count-1 792 def BUILD_TUPLE(self, count): 793 return -count+1 794 def BUILD_LIST(self, count): 795 return -count+1 796 def CALL_FUNCTION(self, argc): 797 hi, lo = divmod(argc, 256) 798 return -(lo + hi * 2) 799 def CALL_FUNCTION_VAR(self, argc): 800 return self.CALL_FUNCTION(argc)-1 801 def CALL_FUNCTION_KW(self, argc): 802 return self.CALL_FUNCTION(argc)-1 803 def CALL_FUNCTION_VAR_KW(self, argc): 804 return self.CALL_FUNCTION(argc)-2 805 def MAKE_FUNCTION(self, argc): 806 return -argc 807 def MAKE_CLOSURE(self, argc): 808 # XXX need to account for free variables too! 809 return -argc 810 def BUILD_SLICE(self, argc): 811 if argc == 2: 812 return -1 813 elif argc == 3: 814 return -2 815 def DUP_TOPX(self, argc): 816 return argc 817 818 findDepth = StackDepthTracker().findDepth