blob: 3272419f8528a8daac1648ba9a179dd1cec55e15 [file] [log] [blame]
Jeremy Hylton36cc6a22000-03-16 20:06:59 +00001"""A flow graph representation for Python bytecode"""
Jeremy Hyltona5058122000-02-14 14:14:29 +00002
Jeremy Hyltona5058122000-02-14 14:14:29 +00003import dis
4import new
5import string
Jeremy Hylton36cc6a22000-03-16 20:06:59 +00006import types
Jeremy Hyltona5058122000-02-14 14:14:29 +00007
Jeremy Hylton36cc6a22000-03-16 20:06:59 +00008from compiler import misc
9
10class FlowGraph:
11 def __init__(self):
12 self.current = self.entry = Block()
13 self.exit = Block("exit")
14 self.blocks = misc.Set()
15 self.blocks.add(self.entry)
16 self.blocks.add(self.exit)
17
18 def startBlock(self, block):
19 self.current = block
20
21 def nextBlock(self, block=None):
22 if block is None:
23 block = self.newBlock()
24 # XXX think we need to specify when there is implicit transfer
25 # from one block to the next
26 #
27 # I think this strategy works: each block has a child
28 # designated as "next" which is returned as the last of the
29 # children. because the nodes in a graph are emitted in
30 # reverse post order, the "next" block will always be emitted
31 # immediately after its parent.
32 # Worry: maintaining this invariant could be tricky
33 self.current.addNext(block)
34 self.startBlock(block)
35
36 def newBlock(self):
37 b = Block()
38 self.blocks.add(b)
39 return b
40
41 def startExitBlock(self):
42 self.startBlock(self.exit)
43
44 def emit(self, *inst):
45 # XXX should jump instructions implicitly call nextBlock?
46 if inst[0] == 'RETURN_VALUE':
47 self.current.addOutEdge(self.exit)
48 self.current.emit(inst)
49
50 def getBlocks(self):
51 """Return the blocks in reverse postorder
52
53 i.e. each node appears before all of its successors
54 """
55 # XXX make sure every node that doesn't have an explicit next
56 # is set so that next points to exit
57 for b in self.blocks.elements():
58 if b is self.exit:
59 continue
60 if not b.next:
61 b.addNext(self.exit)
62 order = dfs_postorder(self.entry, {})
63 order.reverse()
64 # hack alert
65 if not self.exit in order:
66 order.append(self.exit)
67 return order
68
69def dfs_postorder(b, seen):
70 """Depth-first search of tree rooted at b, return in postorder"""
71 order = []
72 seen[b] = b
73 for c in b.children():
74 if seen.has_key(c):
75 continue
76 order = order + dfs_postorder(c, seen)
77 order.append(b)
78 return order
79
80class Block:
81 _count = 0
82
83 def __init__(self, label=''):
84 self.insts = []
85 self.inEdges = misc.Set()
86 self.outEdges = misc.Set()
87 self.label = label
88 self.bid = Block._count
89 self.next = []
90 Block._count = Block._count + 1
91
92 def __repr__(self):
93 if self.label:
94 return "<block %s id=%d len=%d>" % (self.label, self.bid,
95 len(self.insts))
96 else:
97 return "<block id=%d len=%d>" % (self.bid, len(self.insts))
98
99 def __str__(self):
100 insts = map(str, self.insts)
101 return "<block %s %d:\n%s>" % (self.label, self.bid,
102 string.join(insts, '\n'))
103
104 def emit(self, inst):
105 op = inst[0]
106 if op[:4] == 'JUMP':
107 self.outEdges.add(inst[1])
108 self.insts.append(inst)
109
110 def getInstructions(self):
111 return self.insts
112
113 def addInEdge(self, block):
114 self.inEdges.add(block)
115
116 def addOutEdge(self, block):
117 self.outEdges.add(block)
118
119 def addNext(self, block):
120 self.next.append(block)
121 assert len(self.next) == 1, map(str, self.next)
122
123 def children(self):
124 return self.outEdges.elements() + self.next
Jeremy Hyltona5058122000-02-14 14:14:29 +0000125
126# flags for code objects
127CO_OPTIMIZED = 0x0001
128CO_NEWLOCALS = 0x0002
129CO_VARARGS = 0x0004
130CO_VARKEYWORDS = 0x0008
131
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000132# the FlowGraph is transformed in place; it exists in one of these states
133RAW = "RAW"
134FLAT = "FLAT"
135CONV = "CONV"
136DONE = "DONE"
Jeremy Hylton3ec7e2c2000-02-17 22:09:35 +0000137
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000138class PyFlowGraph(FlowGraph):
139 super_init = FlowGraph.__init__
Jeremy Hyltona5058122000-02-14 14:14:29 +0000140
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000141 def __init__(self, name, filename, args=(), optimized=0):
142 self.super_init()
143 self.name = name
144 self.filename = filename
145 self.docstring = None
146 self.args = args # XXX
147 self.argcount = getArgCount(args)
148 if optimized:
149 self.flags = CO_OPTIMIZED | CO_NEWLOCALS
150 else:
151 self.flags = 0
152 self.firstlineno = None
153 self.consts = []
154 self.names = []
Jeremy Hyltona5058122000-02-14 14:14:29 +0000155 self.varnames = list(args) or []
Jeremy Hylton3ec7e2c2000-02-17 22:09:35 +0000156 for i in range(len(self.varnames)):
157 var = self.varnames[i]
158 if isinstance(var, TupleArg):
159 self.varnames[i] = var.getName()
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000160 self.stage = RAW
Jeremy Hyltona5058122000-02-14 14:14:29 +0000161
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000162 def setDocstring(self, doc):
163 self.docstring = doc
164 self.consts.insert(0, doc)
Jeremy Hylton2ce27b22000-02-16 00:50:29 +0000165
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000166 def setFlag(self, flag):
167 self.flags = self.flags | flag
168 if flag == CO_VARARGS:
169 self.argcount = self.argcount - 1
Jeremy Hyltona5058122000-02-14 14:14:29 +0000170
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000171 def getCode(self):
172 """Get a Python code object"""
173 if self.stage == RAW:
174 self.flattenGraph()
175 if self.stage == FLAT:
176 self.convertArgs()
177 if self.stage == CONV:
178 self.makeByteCode()
179 if self.stage == DONE:
180 return self.newCodeObject()
181 raise RuntimeError, "inconsistent PyFlowGraph state"
Jeremy Hyltona5058122000-02-14 14:14:29 +0000182
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000183 def dump(self, io=None):
184 if io:
185 save = sys.stdout
186 sys.stdout = io
187 pc = 0
Jeremy Hyltona5058122000-02-14 14:14:29 +0000188 for t in self.insts:
189 opname = t[0]
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000190 if opname == "SET_LINENO":
191 print
Jeremy Hyltona5058122000-02-14 14:14:29 +0000192 if len(t) == 1:
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000193 print "\t", "%3d" % pc, opname
194 pc = pc + 1
Jeremy Hyltona5058122000-02-14 14:14:29 +0000195 else:
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000196 print "\t", "%3d" % pc, opname, t[1]
197 pc = pc + 3
198 if io:
199 sys.stdout = save
Jeremy Hyltona5058122000-02-14 14:14:29 +0000200
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000201 def flattenGraph(self):
202 """Arrange the blocks in order and resolve jumps"""
203 assert self.stage == RAW
204 self.insts = insts = []
205 pc = 0
206 begin = {}
207 end = {}
208 for b in self.getBlocks():
209 begin[b] = pc
210 for inst in b.getInstructions():
211 insts.append(inst)
212 if len(inst) == 1:
213 pc = pc + 1
214 else:
215 # arg takes 2 bytes
216 pc = pc + 3
217 end[b] = pc
218 pc = 0
219 for i in range(len(insts)):
220 inst = insts[i]
221 if len(inst) == 1:
222 pc = pc + 1
223 else:
224 pc = pc + 3
225 opname = inst[0]
226 if self.hasjrel.has_elt(opname):
227 oparg = inst[1]
228 offset = begin[oparg] - pc
229 insts[i] = opname, offset
230 elif self.hasjabs.has_elt(opname):
231 insts[i] = opname, begin[inst[1]]
232 self.stacksize = findDepth(self.insts)
233 self.stage = FLAT
Jeremy Hyltona5058122000-02-14 14:14:29 +0000234
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000235 hasjrel = misc.Set()
236 for i in dis.hasjrel:
237 hasjrel.add(dis.opname[i])
238 hasjabs = misc.Set()
239 for i in dis.hasjabs:
240 hasjabs.add(dis.opname[i])
Jeremy Hyltona5058122000-02-14 14:14:29 +0000241
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000242 def convertArgs(self):
243 """Convert arguments from symbolic to concrete form"""
244 assert self.stage == FLAT
245 for i in range(len(self.insts)):
246 t = self.insts[i]
247 if len(t) == 2:
248 opname = t[0]
249 oparg = t[1]
250 conv = self._converters.get(opname, None)
251 if conv:
252 self.insts[i] = opname, conv(self, oparg)
253 self.stage = CONV
Jeremy Hyltona5058122000-02-14 14:14:29 +0000254
Jeremy Hyltonefd06942000-02-17 22:58:54 +0000255 def _lookupName(self, name, list):
256 """Return index of name in list, appending if necessary"""
Jeremy Hyltona5058122000-02-14 14:14:29 +0000257 if name in list:
Jeremy Hyltonefd06942000-02-17 22:58:54 +0000258 i = list.index(name)
259 # this is cheap, but incorrect in some cases, e.g 2 vs. 2L
260 if type(name) == type(list[i]):
261 return i
262 for i in range(len(list)):
263 elt = list[i]
264 if type(elt) == type(name) and elt == name:
265 return i
266 end = len(list)
267 list.append(name)
268 return end
Jeremy Hyltona5058122000-02-14 14:14:29 +0000269
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000270 _converters = {}
271 def _convert_LOAD_CONST(self, arg):
272 return self._lookupName(arg, self.consts)
273
274 def _convert_LOAD_FAST(self, arg):
275 self._lookupName(arg, self.names)
276 return self._lookupName(arg, self.varnames)
277 _convert_STORE_FAST = _convert_LOAD_FAST
278 _convert_DELETE_FAST = _convert_LOAD_FAST
279
280 def _convert_NAME(self, arg):
281 return self._lookupName(arg, self.names)
282 _convert_LOAD_NAME = _convert_NAME
283 _convert_STORE_NAME = _convert_NAME
284 _convert_DELETE_NAME = _convert_NAME
285 _convert_IMPORT_NAME = _convert_NAME
286 _convert_IMPORT_FROM = _convert_NAME
287 _convert_STORE_ATTR = _convert_NAME
288 _convert_LOAD_ATTR = _convert_NAME
289 _convert_DELETE_ATTR = _convert_NAME
290 _convert_LOAD_GLOBAL = _convert_NAME
291 _convert_STORE_GLOBAL = _convert_NAME
292 _convert_DELETE_GLOBAL = _convert_NAME
293
294 _cmp = list(dis.cmp_op)
295 def _convert_COMPARE_OP(self, arg):
296 return self._cmp.index(arg)
297
298 # similarly for other opcodes...
299
300 for name, obj in locals().items():
301 if name[:9] == "_convert_":
302 opname = name[9:]
303 _converters[opname] = obj
304 del name, obj, opname
305
306 def makeByteCode(self):
307 assert self.stage == CONV
308 self.lnotab = lnotab = LineAddrTable()
309 for t in self.insts:
310 opname = t[0]
311 if len(t) == 1:
312 lnotab.addCode(self.opnum[opname])
313 else:
314 oparg = t[1]
315 if opname == "SET_LINENO":
316 lnotab.nextLine(oparg)
317 if self.firstlineno is None:
318 self.firstlineno = oparg
319 hi, lo = twobyte(oparg)
320 try:
321 lnotab.addCode(self.opnum[opname], lo, hi)
322 except ValueError:
323 print opname, oparg
324 print self.opnum[opname], lo, hi
325 raise
326 self.stage = DONE
327
Jeremy Hyltona5058122000-02-14 14:14:29 +0000328 opnum = {}
329 for num in range(len(dis.opname)):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000330 opnum[dis.opname[num]] = num
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000331 del num
Jeremy Hyltona5058122000-02-14 14:14:29 +0000332
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000333 def newCodeObject(self):
334 assert self.stage == DONE
335 if self.flags == 0:
336 nlocals = 0
337 else:
338 nlocals = len(self.varnames)
339 argcount = self.argcount
340 if self.flags & CO_VARKEYWORDS:
341 argcount = argcount - 1
342 return new.code(argcount, nlocals, self.stacksize, self.flags,
343 self.lnotab.getCode(), self.getConsts(),
344 tuple(self.names), tuple(self.varnames),
345 self.filename, self.name, self.firstlineno,
346 self.lnotab.getTable())
Jeremy Hyltona5058122000-02-14 14:14:29 +0000347
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000348 def getConsts(self):
349 """Return a tuple for the const slot of the code object
Jeremy Hyltona5058122000-02-14 14:14:29 +0000350
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000351 Must convert references to code (MAKE_FUNCTION) to code
352 objects recursively.
353 """
354 l = []
355 for elt in self.consts:
356 if isinstance(elt, PyFlowGraph):
357 elt = elt.getCode()
358 l.append(elt)
359 return tuple(l)
360
361def isJump(opname):
362 if opname[:4] == 'JUMP':
363 return 1
Jeremy Hyltona5058122000-02-14 14:14:29 +0000364
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000365class TupleArg:
366 """Helper for marking func defs with nested tuples in arglist"""
367 def __init__(self, count, names):
368 self.count = count
369 self.names = names
370 def __repr__(self):
371 return "TupleArg(%s, %s)" % (self.count, self.names)
372 def getName(self):
373 return ".nested%d" % self.count
374
375def getArgCount(args):
376 argcount = len(args)
377 if args:
378 for arg in args:
379 if isinstance(arg, TupleArg):
380 numNames = len(misc.flatten(arg.names))
381 argcount = argcount - numNames
382 return argcount
383
384def twobyte(val):
385 """Convert an int argument into high and low bytes"""
386 assert type(val) == types.IntType
387 return divmod(val, 256)
Jeremy Hyltona5058122000-02-14 14:14:29 +0000388
389class LineAddrTable:
390 """lnotab
391
392 This class builds the lnotab, which is undocumented but described
393 by com_set_lineno in compile.c. Here's an attempt at explanation:
394
395 For each SET_LINENO instruction after the first one, two bytes are
396 added to lnotab. (In some cases, multiple two-byte entries are
397 added.) The first byte is the distance in bytes between the
398 instruction for the last SET_LINENO and the current SET_LINENO.
399 The second byte is offset in line numbers. If either offset is
400 greater than 255, multiple two-byte entries are added -- one entry
401 for each factor of 255.
402 """
403
404 def __init__(self):
405 self.code = []
406 self.codeOffset = 0
407 self.firstline = 0
408 self.lastline = 0
409 self.lastoff = 0
410 self.lnotab = []
411
Jeremy Hyltonabd7ebf2000-03-06 18:53:14 +0000412 def addCode(self, *args):
413 for arg in args:
414 self.code.append(chr(arg))
415 self.codeOffset = self.codeOffset + len(args)
Jeremy Hyltona5058122000-02-14 14:14:29 +0000416
417 def nextLine(self, lineno):
418 if self.firstline == 0:
419 self.firstline = lineno
420 self.lastline = lineno
421 else:
422 # compute deltas
423 addr = self.codeOffset - self.lastoff
424 line = lineno - self.lastline
425 while addr > 0 or line > 0:
426 # write the values in 1-byte chunks that sum
427 # to desired value
428 trunc_addr = addr
429 trunc_line = line
430 if trunc_addr > 255:
431 trunc_addr = 255
432 if trunc_line > 255:
433 trunc_line = 255
434 self.lnotab.append(trunc_addr)
435 self.lnotab.append(trunc_line)
436 addr = addr - trunc_addr
437 line = line - trunc_line
438 self.lastline = lineno
439 self.lastoff = self.codeOffset
440
441 def getCode(self):
442 return string.join(self.code, '')
443
444 def getTable(self):
445 return string.join(map(chr, self.lnotab), '')
446
Jeremy Hyltona5058122000-02-14 14:14:29 +0000447class StackDepthTracker:
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000448 # XXX 1. need to keep track of stack depth on jumps
449 # XXX 2. at least partly as a result, this code is broken
Jeremy Hyltona5058122000-02-14 14:14:29 +0000450
451 def findDepth(self, insts):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000452 depth = 0
453 maxDepth = 0
454 for i in insts:
455 opname = i[0]
456 delta = self.effect.get(opname, 0)
457 if delta > 1:
458 depth = depth + delta
459 elif delta < 0:
460 if depth > maxDepth:
461 maxDepth = depth
462 depth = depth + delta
463 else:
464 if depth > maxDepth:
465 maxDepth = depth
466 # now check patterns
467 for pat, delta in self.patterns:
468 if opname[:len(pat)] == pat:
469 depth = depth + delta
470 break
471 # if we still haven't found a match
472 if delta == 0:
473 meth = getattr(self, opname)
474 depth = depth + meth(i[1])
475 if depth < 0:
476 depth = 0
477 return maxDepth
Jeremy Hyltona5058122000-02-14 14:14:29 +0000478
479 effect = {
Jeremy Hylton772dd412000-02-21 22:46:00 +0000480 'POP_TOP': -1,
481 'DUP_TOP': 1,
482 'SLICE+1': -1,
483 'SLICE+2': -1,
484 'SLICE+3': -2,
485 'STORE_SLICE+0': -1,
486 'STORE_SLICE+1': -2,
487 'STORE_SLICE+2': -2,
488 'STORE_SLICE+3': -3,
489 'DELETE_SLICE+0': -1,
490 'DELETE_SLICE+1': -2,
491 'DELETE_SLICE+2': -2,
492 'DELETE_SLICE+3': -3,
493 'STORE_SUBSCR': -3,
494 'DELETE_SUBSCR': -2,
495 # PRINT_EXPR?
496 'PRINT_ITEM': -1,
497 'LOAD_LOCALS': 1,
498 'RETURN_VALUE': -1,
499 'EXEC_STMT': -2,
500 'BUILD_CLASS': -2,
501 'STORE_NAME': -1,
502 'STORE_ATTR': -2,
503 'DELETE_ATTR': -1,
504 'STORE_GLOBAL': -1,
505 'BUILD_MAP': 1,
506 'COMPARE_OP': -1,
507 'STORE_FAST': -1,
508 }
Jeremy Hyltona5058122000-02-14 14:14:29 +0000509 # use pattern match
510 patterns = [
Jeremy Hylton772dd412000-02-21 22:46:00 +0000511 ('BINARY_', -1),
512 ('LOAD_', 1),
513 ('IMPORT_', 1),
514 ]
Jeremy Hyltonabd7ebf2000-03-06 18:53:14 +0000515
516 # special cases:
517 # UNPACK_TUPLE, UNPACK_LIST, BUILD_TUPLE,
Jeremy Hyltona5058122000-02-14 14:14:29 +0000518 # BUILD_LIST, CALL_FUNCTION, MAKE_FUNCTION, BUILD_SLICE
519 def UNPACK_TUPLE(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000520 return count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000521 def UNPACK_LIST(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000522 return count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000523 def BUILD_TUPLE(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000524 return -count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000525 def BUILD_LIST(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000526 return -count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000527 def CALL_FUNCTION(self, argc):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000528 hi, lo = divmod(argc, 256)
529 return lo + hi * 2
Jeremy Hyltona5058122000-02-14 14:14:29 +0000530 def MAKE_FUNCTION(self, argc):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000531 return -argc
Jeremy Hyltona5058122000-02-14 14:14:29 +0000532 def BUILD_SLICE(self, argc):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000533 if argc == 2:
534 return -1
535 elif argc == 3:
536 return -2
Jeremy Hyltona5058122000-02-14 14:14:29 +0000537
538findDepth = StackDepthTracker().findDepth