pep8analysis: reduce the max amount of looping to find fixed point
[nit.git] / contrib / pep8analysis / src / cfg / cfg_base.nit
1 import pipeline
2
3 import model
4
5 redef class AnalysisManager
6 fun build_cfg( model: Model ) : CFG
7 do
8 var cfg = new CFG( model )
9 return cfg
10 end
11 end
12
13 redef class ABranchInstruction
14 fun is_indirect: Bool
15 do
16 var op = n_operand
17 return op isa AAnyOperand and (once ["x","d"]).has(op.addressing_mode)
18 end
19 end
20
21 class CFG
22 var start : BasicBlock
23 var finish : BasicBlock
24 var addr_to_blocks = new HashMap[Int,BasicBlock]
25 var blocks = new Array[BasicBlock]
26
27 var has_function_calls = false
28
29 init (model: Model)
30 do
31 assert not model.lines.is_empty
32
33 var starts = [0]
34 var ends = [model.lines.last.address]
35
36 # check for indirect branches
37 #var indirect_jump_sites = new Array[Int]
38 var has_indirect_jump_sites = false
39
40 # detect basic block limits
41 for line in model.lines do
42 if line isa AInstructionLine then
43 var instr = line.n_instruction
44 if instr isa ABranchInstruction then
45 if instr.is_indirect then
46 #indirect_jump_sites.add(line.address)
47 has_indirect_jump_sites = true
48 manager.notes.add(new Warn(instr.location, "use of indirect jumps, the CFG may be wrong"))
49 else
50 var op = instr.n_operand
51 var dest = op.n_value.to_i
52 starts.add(dest)
53 end
54 ends.add(line.address)
55 if not instr isa ABrInstruction then
56 # next line is possible start
57 starts.add(line.address+4)
58 end
59 else if instr isa AStopInstruction or
60 instr isa ARetInstruction then
61 ends.add(line.address)
62 end
63 end
64 end
65
66 var addresses_in_memory = new Array[Int]
67 #if not indirect_jumps.is_empty then
68 if has_indirect_jump_sites then
69 # find possible jump destinations
70
71 for line in model.lines do
72 if line isa ADirectiveLine then
73 var dir = line.n_directive
74 if dir isa AAddrssDirective then
75 var dest = dir.n_value.to_i
76 starts.add(dest)
77 addresses_in_memory.add(dest)
78 end
79 end
80 end
81
82 # link indirect jumps to possible destinations
83 #for src in addresses_in_memory do
84 #end
85 end
86
87 # sort breakpoints in order
88 starts = starts.iterator.uniq.sort.to_a
89 ends = ends.iterator.uniq.sort.to_a
90
91 # create basic blocks
92 var current_block: nullable BasicBlock = null
93 var next_start_i = 0
94 var next_end_i = 0
95 for line in model.lines do
96 var addr = line.address
97 while next_start_i < starts.length and
98 starts[next_start_i] < addr do next_start_i += 1
99 if next_start_i < starts.length and
100 starts[next_start_i] == addr then
101 # is a dest, and not already started
102 current_block = new BasicBlock
103 blocks.add(current_block)
104 end
105
106 if current_block == null then
107 current_block = new BasicBlock
108 blocks.add(current_block)
109 end
110
111 current_block.lines.add(line)
112
113 while next_end_i < ends.length and
114 ends[next_end_i] < addr do next_end_i += 1
115 if next_end_i < ends.length and
116 ends[next_end_i] == addr then
117 # stops here, unless already at the end
118 current_block = null
119 end
120 end
121
122 # adds created blocks to instance attribute
123 for b in blocks do
124 # save them in the class attribute
125 addr_to_blocks[b.lines.first.address] = b
126 end
127
128 # start node
129 start = new BasicBlock.start
130 blocks.add(start)
131 if blocks.length > 0 and blocks.first != start then
132 start.successors.add(blocks.first)
133 blocks.first.predecessors.add(start)
134 end
135 addr_to_blocks[-2] = start
136
137 # end node
138 finish = new BasicBlock.finish
139 blocks.add(finish)
140 addr_to_blocks[-1] = finish
141
142 # set successors and predecessors
143 for b in blocks do if not b.lines.is_empty then
144 var line = b.lines.last
145
146 if line isa AInstructionLine then
147 var instr = line.n_instruction
148 if instr isa ABranchInstruction then
149 var op = instr.n_operand
150 if instr.is_indirect then
151 for dest in addresses_in_memory do
152 var db = addr_to_blocks[dest]
153 b.successors.add(db)
154 db.predecessors.add(b)
155 end
156 else
157 var dest = op.n_value.to_i
158 var db = addr_to_blocks[dest]
159 b.successors.add(db)
160 db.predecessors.add(b)
161 end
162 end
163
164 if not instr isa ABrInstruction and
165 not instr isa AStopInstruction and
166 not instr isa ACallInstruction and
167 not instr isa ARetInstruction then
168 # next line is possible start
169 var dest = line.address+4
170 if addr_to_blocks.has_key(dest) then
171 var db = addr_to_blocks[dest]
172 b.successors.add(db)
173 db.predecessors.add(b)
174 else
175 manager.notes.add(new Error(line.location, "invalid line following instruction"))
176 end
177 end
178
179 if instr isa ACallInstruction then
180 has_function_calls = true
181 var next_addr = line.address+4
182 if not addr_to_blocks.has_key(next_addr) then
183 print "error, no instruction following call {b.name}"
184 else
185 b.after_call = addr_to_blocks[next_addr]
186 end
187 end
188
189 if b.successors.is_empty and
190 not instr isa ARetInstruction then
191 b.successors.add(finish)
192 finish.predecessors.add(b)
193 end
194 end
195 end
196
197 # Verify use of function calls
198 # To be by the book, each path from the start to the end should have
199 # the same number of calls and rets. There should never be more rets
200 # than calls.
201
202 # TODO check for instructions not in a path from start to end
203
204 # TODO check if branching on variable
205
206 # TODO check there is there is a consistant use of the stack between
207 # calls and rets.
208 end
209
210 # duplicate function calls
211 # at each call site, the tree representing the
212 fun inline_functions
213 do
214 inline_functions_recursive(start, new List[BasicBlock]) #0)
215
216 # retain only blocks reachble from start
217 var reachables = new HashSet[BasicBlock]
218 var todo = new Array[BasicBlock]
219 todo.add(start)
220 while not todo.is_empty do
221 var n = todo.pop
222 if not reachables.has(n) then
223 reachables.add(n)
224 for s in n.successors do if not reachables.has(s) then
225 todo.add(s)
226 end
227 end
228 end
229 self.blocks = reachables.to_a
230 end
231
232 private fun inline_functions_recursive(b: BasicBlock, seq: List[BasicBlock]) #depth: Int)
233 do
234 # Protection against cycles
235 #if depth > 1000 then return
236 var sl = seq.length
237 var loop_length = 3
238 if sl > loop_length then
239 for i in [0..sl-loop_length[ do
240 var same = true
241 for j in [0..loop_length[ do if seq[i+j]!=seq[sl-3+j] then
242 same = false
243 break
244 end
245
246 if same then
247 print "recursive since {seq[i].name}"
248 return
249 end
250 end
251 end
252 #if seq.has(b) then return
253
254 if not b.lines.is_empty then
255 var line = b.lines.last
256 if line isa AInstructionLine then
257 var instr = line.n_instruction
258 if instr isa ACallInstruction then
259 # replace called by a dup
260 assert b.successors.length == 1
261 var called = b.successors.first
262 var rets = new HashSet[BasicBlock]
263 var n = called.duplicate_tree(
264 new HashMap[BasicBlock,BasicBlock], rets, 0, blocks)
265 b.successors[0] = n
266 n.predecessors.add(b)
267
268 if b.after_call == null then
269 print "Already inlined"
270 return
271 else
272 # TODO bring back assert
273 if n.predecessors.length > 1 then
274 print "many pred"
275 print "n {n.name}"
276 print "preds {n.predecessors.join(" ")}"
277 end
278 assert n.predecessors.length == 1 else print n.predecessors.length
279 assert b.successors.length == 1
280 # TODO add information about duplicated block that are not dead
281
282 # link!
283 var next = b.after_call.as(not null)
284 # Another protection against cycles
285 for ret in rets do
286 ret.successors.add(next)
287 next.predecessors.add(ret)
288 end
289 #print "linking {b.name} to {next.name} ret len {rets.length}"
290
291 b.after_call = null
292 end
293 end
294 end
295 end
296
297 var si = 0
298 while si < b.successors.length do
299 var s = b.successors[si]
300 seq.add(s)
301 inline_functions_recursive(s, seq)
302 seq.pop
303 si+=1
304 end
305 end
306
307 var watchdog writable = 0
308 fun link_ret_to_calls(b: BasicBlock, to_link_ori: List[BasicBlock], seq: List[BasicBlock], depth: Int): Bool
309 do
310 watchdog += 1
311 if watchdog == 10000 then
312 print "Error: Umanagable cycle detected"
313 return false
314 end
315
316 var sl = seq.length
317 var loop_length = 4
318 if sl > loop_length then
319 for i in [0..sl-loop_length[ do
320 var same = true
321 for j in [0..loop_length[ do if seq[i+j]!=seq[sl-loop_length+j] then
322 same = false
323 break
324 end
325
326 if same then
327 #print "recursive since {seq[i].name}"
328 return true
329 end
330 end
331 end
332
333 # copy to_list
334 var to_link = new List[BasicBlock]
335 to_link.add_all(to_link_ori)
336
337 if not b.lines.is_empty then
338 var line = b.lines.last
339 if line isa AInstructionLine then
340 var instr = line.n_instruction
341 if instr isa ACallInstruction then
342 to_link.push(b)
343
344 else if instr isa ARetInstruction then
345 if to_link.is_empty then
346 manager.notes.add( new Error(instr.location,"no CALL can be linked to return") )
347 return false
348 else
349 var caller = to_link.pop
350 # link!
351 var next = caller.after_call.as(not null)
352
353 # Another protection against cycles
354 if b.successors.has(next) then return true
355
356 b.successors.add(next)
357 next.predecessors.add(b)
358 end
359 end
360 end
361 end
362
363 var si = 0
364 while si < b.successors.length do
365 var s = b.successors[si]
366 seq.add(s)
367 if not link_ret_to_calls(s, to_link,seq,depth+1) then return false
368 seq.pop
369 si+=1
370 end
371
372 return true # OK
373 end
374 end
375
376 class BasicBlock
377 var name : String
378 var lines = new Array[ANonEmptyLine]
379 var successors = new Array[BasicBlock]
380 var predecessors = new Array[BasicBlock]
381 var after_call : nullable BasicBlock = null
382
383 init
384 do
385 var count = (once new Counter).next
386 name = "b{count}"
387 end
388 init named(name: String) do self.name = name
389 init start do name = "start"
390 init finish
391 do
392 name = "end"
393 end
394
395 fun duplicate_tree(dups: HashMap[BasicBlock,BasicBlock],
396 rets: Set[BasicBlock], calls: Int,
397 blocks: Array[BasicBlock]) : BasicBlock
398 do
399 if dups.has_key(self) then return dups[self]
400
401 var n = new BasicBlock.from(self)
402 dups[self] = n
403 blocks.add(n)
404 n.successors = new Array[BasicBlock]
405 n.predecessors = new Array[BasicBlock]
406
407 if after_call != null then
408 var nac = after_call.duplicate_tree(dups, rets, calls, blocks)
409 n.after_call = nac
410 calls += 1 # for within that call
411 end
412
413 for s in successors do
414 var ns = s.duplicate_tree(dups, rets, calls, blocks)
415 n.successors.add(ns)
416 ns.predecessors.add(n)
417 end
418
419 if calls == 0 and successors.is_empty then rets.add(n)
420
421 return n
422 end
423
424 init from(o: BasicBlock)
425 do
426 var count = (once new Counter).next
427 name = "c{count}_from_{o.name}"
428 lines = o.lines
429 successors = o.successors
430 predecessors = o.predecessors
431 after_call = o.after_call
432 end
433 end
434
435 private class Counter
436 var count: Int = -1
437 fun next : Int
438 do
439 count += 1
440 return count
441 end
442 end