blob: 6e079870a2fb96114c98cc4cc029dc29c752564f [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
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000152 self.consts = []
153 self.names = []
Jeremy Hyltona5058122000-02-14 14:14:29 +0000154 self.varnames = list(args) or []
Jeremy Hylton3ec7e2c2000-02-17 22:09:35 +0000155 for i in range(len(self.varnames)):
156 var = self.varnames[i]
157 if isinstance(var, TupleArg):
158 self.varnames[i] = var.getName()
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000159 self.stage = RAW
Jeremy Hyltona5058122000-02-14 14:14:29 +0000160
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000161 def setDocstring(self, doc):
162 self.docstring = doc
163 self.consts.insert(0, doc)
Jeremy Hylton2ce27b22000-02-16 00:50:29 +0000164
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000165 def setFlag(self, flag):
166 self.flags = self.flags | flag
167 if flag == CO_VARARGS:
168 self.argcount = self.argcount - 1
Jeremy Hyltona5058122000-02-14 14:14:29 +0000169
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000170 def getCode(self):
171 """Get a Python code object"""
172 if self.stage == RAW:
173 self.flattenGraph()
174 if self.stage == FLAT:
175 self.convertArgs()
176 if self.stage == CONV:
177 self.makeByteCode()
178 if self.stage == DONE:
179 return self.newCodeObject()
180 raise RuntimeError, "inconsistent PyFlowGraph state"
Jeremy Hyltona5058122000-02-14 14:14:29 +0000181
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000182 def dump(self, io=None):
183 if io:
184 save = sys.stdout
185 sys.stdout = io
186 pc = 0
Jeremy Hyltona5058122000-02-14 14:14:29 +0000187 for t in self.insts:
188 opname = t[0]
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000189 if opname == "SET_LINENO":
190 print
Jeremy Hyltona5058122000-02-14 14:14:29 +0000191 if len(t) == 1:
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000192 print "\t", "%3d" % pc, opname
193 pc = pc + 1
Jeremy Hyltona5058122000-02-14 14:14:29 +0000194 else:
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000195 print "\t", "%3d" % pc, opname, t[1]
196 pc = pc + 3
197 if io:
198 sys.stdout = save
Jeremy Hyltona5058122000-02-14 14:14:29 +0000199
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000200 def flattenGraph(self):
201 """Arrange the blocks in order and resolve jumps"""
202 assert self.stage == RAW
203 self.insts = insts = []
204 pc = 0
205 begin = {}
206 end = {}
207 for b in self.getBlocks():
208 begin[b] = pc
209 for inst in b.getInstructions():
210 insts.append(inst)
211 if len(inst) == 1:
212 pc = pc + 1
213 else:
214 # arg takes 2 bytes
215 pc = pc + 3
216 end[b] = pc
217 pc = 0
218 for i in range(len(insts)):
219 inst = insts[i]
220 if len(inst) == 1:
221 pc = pc + 1
222 else:
223 pc = pc + 3
224 opname = inst[0]
225 if self.hasjrel.has_elt(opname):
226 oparg = inst[1]
227 offset = begin[oparg] - pc
228 insts[i] = opname, offset
229 elif self.hasjabs.has_elt(opname):
230 insts[i] = opname, begin[inst[1]]
231 self.stacksize = findDepth(self.insts)
232 self.stage = FLAT
Jeremy Hyltona5058122000-02-14 14:14:29 +0000233
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000234 hasjrel = misc.Set()
235 for i in dis.hasjrel:
236 hasjrel.add(dis.opname[i])
237 hasjabs = misc.Set()
238 for i in dis.hasjabs:
239 hasjabs.add(dis.opname[i])
Jeremy Hyltona5058122000-02-14 14:14:29 +0000240
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000241 def convertArgs(self):
242 """Convert arguments from symbolic to concrete form"""
243 assert self.stage == FLAT
244 for i in range(len(self.insts)):
245 t = self.insts[i]
246 if len(t) == 2:
247 opname = t[0]
248 oparg = t[1]
249 conv = self._converters.get(opname, None)
250 if conv:
251 self.insts[i] = opname, conv(self, oparg)
252 self.stage = CONV
Jeremy Hyltona5058122000-02-14 14:14:29 +0000253
Jeremy Hyltonefd06942000-02-17 22:58:54 +0000254 def _lookupName(self, name, list):
255 """Return index of name in list, appending if necessary"""
Jeremy Hyltona5058122000-02-14 14:14:29 +0000256 if name in list:
Jeremy Hyltonefd06942000-02-17 22:58:54 +0000257 i = list.index(name)
258 # this is cheap, but incorrect in some cases, e.g 2 vs. 2L
259 if type(name) == type(list[i]):
260 return i
261 for i in range(len(list)):
262 elt = list[i]
263 if type(elt) == type(name) and elt == name:
264 return i
265 end = len(list)
266 list.append(name)
267 return end
Jeremy Hyltona5058122000-02-14 14:14:29 +0000268
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000269 _converters = {}
270 def _convert_LOAD_CONST(self, arg):
271 return self._lookupName(arg, self.consts)
272
273 def _convert_LOAD_FAST(self, arg):
274 self._lookupName(arg, self.names)
275 return self._lookupName(arg, self.varnames)
276 _convert_STORE_FAST = _convert_LOAD_FAST
277 _convert_DELETE_FAST = _convert_LOAD_FAST
278
279 def _convert_NAME(self, arg):
280 return self._lookupName(arg, self.names)
281 _convert_LOAD_NAME = _convert_NAME
282 _convert_STORE_NAME = _convert_NAME
283 _convert_DELETE_NAME = _convert_NAME
284 _convert_IMPORT_NAME = _convert_NAME
285 _convert_IMPORT_FROM = _convert_NAME
286 _convert_STORE_ATTR = _convert_NAME
287 _convert_LOAD_ATTR = _convert_NAME
288 _convert_DELETE_ATTR = _convert_NAME
289 _convert_LOAD_GLOBAL = _convert_NAME
290 _convert_STORE_GLOBAL = _convert_NAME
291 _convert_DELETE_GLOBAL = _convert_NAME
292
293 _cmp = list(dis.cmp_op)
294 def _convert_COMPARE_OP(self, arg):
295 return self._cmp.index(arg)
296
297 # similarly for other opcodes...
298
299 for name, obj in locals().items():
300 if name[:9] == "_convert_":
301 opname = name[9:]
302 _converters[opname] = obj
303 del name, obj, opname
304
305 def makeByteCode(self):
306 assert self.stage == CONV
307 self.lnotab = lnotab = LineAddrTable()
308 for t in self.insts:
309 opname = t[0]
310 if len(t) == 1:
311 lnotab.addCode(self.opnum[opname])
312 else:
313 oparg = t[1]
314 if opname == "SET_LINENO":
315 lnotab.nextLine(oparg)
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000316 hi, lo = twobyte(oparg)
317 try:
318 lnotab.addCode(self.opnum[opname], lo, hi)
319 except ValueError:
320 print opname, oparg
321 print self.opnum[opname], lo, hi
322 raise
323 self.stage = DONE
324
Jeremy Hyltona5058122000-02-14 14:14:29 +0000325 opnum = {}
326 for num in range(len(dis.opname)):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000327 opnum[dis.opname[num]] = num
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000328 del num
Jeremy Hyltona5058122000-02-14 14:14:29 +0000329
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000330 def newCodeObject(self):
331 assert self.stage == DONE
332 if self.flags == 0:
333 nlocals = 0
334 else:
335 nlocals = len(self.varnames)
336 argcount = self.argcount
337 if self.flags & CO_VARKEYWORDS:
338 argcount = argcount - 1
339 return new.code(argcount, nlocals, self.stacksize, self.flags,
340 self.lnotab.getCode(), self.getConsts(),
341 tuple(self.names), tuple(self.varnames),
Jeremy Hyltonbe317e62000-05-02 22:32:59 +0000342 self.filename, self.name, self.lnotab.firstline,
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000343 self.lnotab.getTable())
Jeremy Hyltona5058122000-02-14 14:14:29 +0000344
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000345 def getConsts(self):
346 """Return a tuple for the const slot of the code object
Jeremy Hyltona5058122000-02-14 14:14:29 +0000347
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000348 Must convert references to code (MAKE_FUNCTION) to code
349 objects recursively.
350 """
351 l = []
352 for elt in self.consts:
353 if isinstance(elt, PyFlowGraph):
354 elt = elt.getCode()
355 l.append(elt)
356 return tuple(l)
357
358def isJump(opname):
359 if opname[:4] == 'JUMP':
360 return 1
Jeremy Hyltona5058122000-02-14 14:14:29 +0000361
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000362class TupleArg:
363 """Helper for marking func defs with nested tuples in arglist"""
364 def __init__(self, count, names):
365 self.count = count
366 self.names = names
367 def __repr__(self):
368 return "TupleArg(%s, %s)" % (self.count, self.names)
369 def getName(self):
370 return ".nested%d" % self.count
371
372def getArgCount(args):
373 argcount = len(args)
374 if args:
375 for arg in args:
376 if isinstance(arg, TupleArg):
377 numNames = len(misc.flatten(arg.names))
378 argcount = argcount - numNames
379 return argcount
380
381def twobyte(val):
382 """Convert an int argument into high and low bytes"""
383 assert type(val) == types.IntType
384 return divmod(val, 256)
Jeremy Hyltona5058122000-02-14 14:14:29 +0000385
386class LineAddrTable:
387 """lnotab
388
389 This class builds the lnotab, which is undocumented but described
390 by com_set_lineno in compile.c. Here's an attempt at explanation:
391
392 For each SET_LINENO instruction after the first one, two bytes are
393 added to lnotab. (In some cases, multiple two-byte entries are
394 added.) The first byte is the distance in bytes between the
395 instruction for the last SET_LINENO and the current SET_LINENO.
396 The second byte is offset in line numbers. If either offset is
397 greater than 255, multiple two-byte entries are added -- one entry
398 for each factor of 255.
399 """
400
401 def __init__(self):
402 self.code = []
403 self.codeOffset = 0
404 self.firstline = 0
405 self.lastline = 0
406 self.lastoff = 0
407 self.lnotab = []
408
Jeremy Hyltonabd7ebf2000-03-06 18:53:14 +0000409 def addCode(self, *args):
410 for arg in args:
411 self.code.append(chr(arg))
412 self.codeOffset = self.codeOffset + len(args)
Jeremy Hyltona5058122000-02-14 14:14:29 +0000413
414 def nextLine(self, lineno):
415 if self.firstline == 0:
416 self.firstline = lineno
417 self.lastline = lineno
418 else:
419 # compute deltas
420 addr = self.codeOffset - self.lastoff
421 line = lineno - self.lastline
422 while addr > 0 or line > 0:
423 # write the values in 1-byte chunks that sum
424 # to desired value
425 trunc_addr = addr
426 trunc_line = line
427 if trunc_addr > 255:
428 trunc_addr = 255
429 if trunc_line > 255:
430 trunc_line = 255
431 self.lnotab.append(trunc_addr)
432 self.lnotab.append(trunc_line)
433 addr = addr - trunc_addr
434 line = line - trunc_line
435 self.lastline = lineno
436 self.lastoff = self.codeOffset
437
438 def getCode(self):
439 return string.join(self.code, '')
440
441 def getTable(self):
442 return string.join(map(chr, self.lnotab), '')
443
Jeremy Hyltona5058122000-02-14 14:14:29 +0000444class StackDepthTracker:
Jeremy Hylton36cc6a22000-03-16 20:06:59 +0000445 # XXX 1. need to keep track of stack depth on jumps
446 # XXX 2. at least partly as a result, this code is broken
Jeremy Hyltona5058122000-02-14 14:14:29 +0000447
448 def findDepth(self, insts):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000449 depth = 0
450 maxDepth = 0
451 for i in insts:
452 opname = i[0]
453 delta = self.effect.get(opname, 0)
454 if delta > 1:
455 depth = depth + delta
456 elif delta < 0:
457 if depth > maxDepth:
458 maxDepth = depth
459 depth = depth + delta
460 else:
461 if depth > maxDepth:
462 maxDepth = depth
463 # now check patterns
Jeremy Hyltonbe317e62000-05-02 22:32:59 +0000464 for pat, pat_delta in self.patterns:
Jeremy Hylton772dd412000-02-21 22:46:00 +0000465 if opname[:len(pat)] == pat:
Jeremy Hyltonbe317e62000-05-02 22:32:59 +0000466 delta = pat_delta
Jeremy Hylton772dd412000-02-21 22:46:00 +0000467 depth = depth + delta
468 break
469 # if we still haven't found a match
470 if delta == 0:
Jeremy Hyltonbe317e62000-05-02 22:32:59 +0000471 meth = getattr(self, opname, None)
472 if meth is not None:
473 depth = depth + meth(i[1])
Jeremy Hylton772dd412000-02-21 22:46:00 +0000474 if depth < 0:
475 depth = 0
476 return maxDepth
Jeremy Hyltona5058122000-02-14 14:14:29 +0000477
478 effect = {
Jeremy Hylton772dd412000-02-21 22:46:00 +0000479 'POP_TOP': -1,
480 'DUP_TOP': 1,
481 'SLICE+1': -1,
482 'SLICE+2': -1,
483 'SLICE+3': -2,
484 'STORE_SLICE+0': -1,
485 'STORE_SLICE+1': -2,
486 'STORE_SLICE+2': -2,
487 'STORE_SLICE+3': -3,
488 'DELETE_SLICE+0': -1,
489 'DELETE_SLICE+1': -2,
490 'DELETE_SLICE+2': -2,
491 'DELETE_SLICE+3': -3,
492 'STORE_SUBSCR': -3,
493 'DELETE_SUBSCR': -2,
494 # PRINT_EXPR?
495 'PRINT_ITEM': -1,
496 'LOAD_LOCALS': 1,
497 'RETURN_VALUE': -1,
498 'EXEC_STMT': -2,
499 'BUILD_CLASS': -2,
500 'STORE_NAME': -1,
501 'STORE_ATTR': -2,
502 'DELETE_ATTR': -1,
503 'STORE_GLOBAL': -1,
504 'BUILD_MAP': 1,
505 'COMPARE_OP': -1,
506 'STORE_FAST': -1,
507 }
Jeremy Hyltona5058122000-02-14 14:14:29 +0000508 # use pattern match
509 patterns = [
Jeremy Hylton772dd412000-02-21 22:46:00 +0000510 ('BINARY_', -1),
511 ('LOAD_', 1),
512 ('IMPORT_', 1),
513 ]
Jeremy Hyltonabd7ebf2000-03-06 18:53:14 +0000514
515 # special cases:
516 # UNPACK_TUPLE, UNPACK_LIST, BUILD_TUPLE,
Jeremy Hyltona5058122000-02-14 14:14:29 +0000517 # BUILD_LIST, CALL_FUNCTION, MAKE_FUNCTION, BUILD_SLICE
518 def UNPACK_TUPLE(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000519 return count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000520 def UNPACK_LIST(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000521 return count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000522 def BUILD_TUPLE(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000523 return -count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000524 def BUILD_LIST(self, count):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000525 return -count
Jeremy Hyltona5058122000-02-14 14:14:29 +0000526 def CALL_FUNCTION(self, argc):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000527 hi, lo = divmod(argc, 256)
528 return lo + hi * 2
Jeremy Hyltonbe317e62000-05-02 22:32:59 +0000529 def CALL_FUNCTION_VAR(self, argc):
530 return self.CALL_FUNCTION(argc)+1
531 def CALL_FUNCTION_KW(self, argc):
532 return self.CALL_FUNCTION(argc)+1
533 def CALL_FUNCTION_VAR_KW(self, argc):
534 return self.CALL_FUNCTION(argc)+2
Jeremy Hyltona5058122000-02-14 14:14:29 +0000535 def MAKE_FUNCTION(self, argc):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000536 return -argc
Jeremy Hyltona5058122000-02-14 14:14:29 +0000537 def BUILD_SLICE(self, argc):
Jeremy Hylton772dd412000-02-21 22:46:00 +0000538 if argc == 2:
539 return -1
540 elif argc == 3:
541 return -2
Jeremy Hyltona5058122000-02-14 14:14:29 +0000542
543findDepth = StackDepthTracker().findDepth