rpiet 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +12 -0
  4. data/bin/color_wheel +84 -0
  5. data/bin/image_gen +39 -0
  6. data/bin/rpiet +68 -11
  7. data/images/counter.txt +7 -0
  8. data/lib/rpiet/asg/graph_interpreter.rb +39 -0
  9. data/lib/rpiet/asg/parser.rb +156 -0
  10. data/lib/rpiet/asg/visitor.rb +66 -0
  11. data/lib/rpiet/asg.rb +336 -0
  12. data/lib/rpiet/codel_chooser.rb +32 -4
  13. data/lib/rpiet/color.rb +70 -25
  14. data/lib/rpiet/cycle.rb +18 -7
  15. data/lib/rpiet/debugger/debugger.rb +298 -0
  16. data/lib/rpiet/debugger/stylesheet.css +88 -0
  17. data/lib/rpiet/direction_pointer.rb +49 -18
  18. data/lib/rpiet/event_handler.rb +62 -7
  19. data/lib/rpiet/group.rb +25 -8
  20. data/lib/rpiet/image/ascii_image.rb +8 -20
  21. data/lib/rpiet/image/image.rb +8 -3
  22. data/lib/rpiet/image/url_image.rb +28 -14
  23. data/lib/rpiet/interpreter.rb +72 -72
  24. data/lib/rpiet/ir/assembler.rb +87 -0
  25. data/lib/rpiet/ir/builder.rb +255 -0
  26. data/lib/rpiet/ir/cfg.rb +494 -0
  27. data/lib/rpiet/ir/instructions.rb +536 -0
  28. data/lib/rpiet/ir/ir_cfg_interpreter.rb +23 -0
  29. data/lib/rpiet/ir/ir_interpreter.rb +101 -0
  30. data/lib/rpiet/ir/ir_native_interpreter.rb +77 -0
  31. data/lib/rpiet/ir/jruby_backend.rb +279 -0
  32. data/lib/rpiet/ir/operands.rb +28 -0
  33. data/lib/rpiet/ir/passes/data_flow_problem.rb +32 -0
  34. data/lib/rpiet/ir/passes/flow_graph_node.rb +76 -0
  35. data/lib/rpiet/ir/passes/peephole.rb +214 -0
  36. data/lib/rpiet/ir/passes/push_pop_elimination_pass.rb +112 -0
  37. data/lib/rpiet/live_machine_state.rb +15 -0
  38. data/lib/rpiet/machine.rb +62 -32
  39. data/lib/rpiet/source.rb +83 -0
  40. data/lib/rpiet/version.rb +1 -1
  41. data/lib/rpiet.rb +2 -2
  42. data/rpiet.gemspec +19 -0
  43. data/spec/asg/visitor_spec.rb +41 -0
  44. data/spec/cycle_spec.rb +34 -34
  45. data/spec/direction_pointer_spec.rb +33 -6
  46. data/spec/group_spec.rb +73 -48
  47. data/spec/interpreter_spec.rb +161 -12
  48. data/spec/ir/assembler_spec.rb +122 -0
  49. data/spec/ir/builder_spec.rb +20 -0
  50. data/spec/ir/cfg_spec.rb +151 -0
  51. data/spec/ir/ir_interpreter_spec.rb +102 -0
  52. data/spec/ir/passes/push_pop_elimination_pass_spec.rb +34 -0
  53. data/spec/machine_spec.rb +5 -3
  54. data/spec/source_spec.rb +69 -0
  55. data/spec/spec_helper.rb +78 -0
  56. metadata +54 -16
  57. data/images/nfib.png +0 -0
@@ -0,0 +1,494 @@
1
+ require_relative 'instructions'
2
+ require 'rgl/adjacency'
3
+ require 'rgl/dot'
4
+ require 'rgl/edge_properties_map'
5
+ require 'rgl/traversal'
6
+
7
+ module RPiet
8
+ module IR
9
+ # Basic Block is a collection of instructions. It will commonly be referred to as bb
10
+ # in code and in comments. bbs is multiple basic blacks.
11
+ class BasicBlock
12
+ attr_reader :label, :instrs
13
+ attr_accessor :debug
14
+
15
+ def initialize(label)
16
+ @label, @instrs = label, []
17
+ end
18
+
19
+ def add_instr(instr) = @instrs << instr
20
+
21
+ def last_instr
22
+ instrs&.last
23
+ end
24
+
25
+ # If this bb only meaningfully contains a jump (e.g. jump + noops/label)
26
+ def only_contains_jump?
27
+ instrs&.last.class == Instructions::JumpInstr && !instrs[0...-2].find { |instr| !instr.noop? }
28
+ end
29
+
30
+ # Maybe a bit hacky but inspect is for dot output. consider changing this.
31
+ def inspect
32
+ str = "name: #{@label}\n\n"
33
+ str << "#{@instrs.map {|i| i}.join('\l')}\\l" if !@instrs.empty?
34
+ str
35
+ end
36
+ alias :to_s :inspect
37
+ end
38
+
39
+ # CFG - Control Flow Graph
40
+ #
41
+ # A control flow graph is a directed graph which show the all paths in which a
42
+ # program can flow/traverse. Having this form for your program opens up the
43
+ # ability to perform various compiler optimizations.
44
+ #
45
+ # An if statement, for example, shows a possible flow if the test stmt of the
46
+ # if succeeds. If it fails then the the flow moves past the if body. The if
47
+ # body itself flows to that same point:
48
+ #
49
+ # if test
50
+ # foo
51
+ # end
52
+ # bar
53
+ #
54
+ # +--------------------+ +----------------+
55
+ # | bb: start | true | bb: if_body |
56
+ # +--------------------+------------>>>+----------------+
57
+ # | branch_true test | | call foo |
58
+ # +--------------------+ +----------------+
59
+ # | |
60
+ # | false |
61
+ # | |
62
+ # v |
63
+ # +--------------------+ |
64
+ # | bb after_if | |
65
+ # +--------------------+<<<--------------------+
66
+ # | call bar |
67
+ # +--------------------+
68
+ #
69
+ # This CFG also generates an additional exit BB. This is the last flow node
70
+ # before execution terminates. Entry BB is the first node you encounter
71
+ # during execution and where you typically start on any analysis.
72
+ class CFG
73
+ attr_reader :entry_bb, :exit_bb
74
+
75
+ def initialize(instrs)
76
+ @graph, @bb_map, @edge_labels = RGL::DirectedAdjacencyGraph.new, {}, {}
77
+ build(instrs)
78
+ end
79
+
80
+ def basic_blocks
81
+ @bb_map.values
82
+ end
83
+
84
+ # Get a sequential list of instructions from this CFG that can be executed.
85
+ # This will linearize and optimize the CFG before making the instruction
86
+ # list.
87
+ def instructions(*passes)
88
+ passes.each do |pass|
89
+ pass.new(self).run
90
+ end
91
+
92
+ bbs = linearize
93
+
94
+ #write_to_dot_file
95
+ bbs.each_with_object([]) { |bb, arr| arr.concat bb.instrs }
96
+ end
97
+
98
+ def write_to_dot_file
99
+ @graph.write_to_graphic_file
100
+ end
101
+
102
+ def outgoing_edges(bb, edge_type=nil)
103
+ if block_given?
104
+ @graph.edges.each do |edge|
105
+ yield edge if outgoing_edges_match?(bb, edge, edge_type)
106
+ end
107
+ else
108
+ @graph.edges.select { |edge| outgoing_edges_match?(bb, edge, edge_type) }
109
+ end
110
+ end
111
+
112
+ def outgoing_target(bb, edge_type)
113
+ outgoing_edges(bb, edge_type).map { |edge| edge.target }&.first
114
+ end
115
+
116
+ def outgoing_targets(bb)
117
+ outgoing_edges(bb).map { |edge| edge.target }
118
+ end
119
+
120
+ def incoming_sources(bb)
121
+ @graph.edges.select { |edge| edge.target == bb }.map {|edge| edge.source}
122
+ end
123
+
124
+ def new_bb(label)
125
+ @bb_map[label] = bb = BasicBlock.new(label)
126
+ @graph.add_vertex(bb)
127
+ @graph.set_vertex_options(bb, shape: 'box', style: 'rounded')
128
+ bb
129
+ end
130
+
131
+ def remove_bb(bb)
132
+ @bb_map.delete(bb.label)
133
+ @graph.remove_vertex(bb)
134
+ incoming_sources(bb).each { |other| remove_edge(other, bb) }
135
+ outgoing_targets(bb).each { |other| remove_edge(bb, other) }
136
+ end
137
+
138
+ def add_forward_edge(source_bb, target_label, forward_refs)
139
+ target_bb = @bb_map[target_label]
140
+
141
+ if target_bb
142
+ add_jump_edge(source_bb, target_bb)
143
+ else
144
+ forward_refs[target_label] ||= []
145
+ forward_refs[target_label] << source_bb
146
+ end
147
+ end
148
+
149
+ def add_edge(source_bb, target_bb, edge_type)
150
+ @edge_labels[[source_bb, target_bb]] = edge_type
151
+ @graph.add_edge(source_bb, target_bb)
152
+ @graph.set_edge_options(source_bb, target_bb, label: edge_type.to_s, color: edge_type_color(edge_type))
153
+ target_bb
154
+ end
155
+
156
+ def remove_edge(source_bb, target_bb)
157
+ @edge_labels.delete([source_bb, target_bb])
158
+ @graph.remove_edge(source_bb, target_bb)
159
+ end
160
+
161
+ def edge_type_color(edge_type) = edge_type == :jump ? 'blue' : 'green'
162
+
163
+ def add_jump_edge(source_bb, target_bb) = add_edge(source_bb, target_bb, :jump)
164
+ def add_fallthrough_edge(source_bb, target_bb) = add_edge(source_bb, target_bb, :fall_through)
165
+
166
+ def build(instrs)
167
+ forward_references = {}
168
+
169
+ current_bb = new_bb(:entry)
170
+ @entry_bb = current_bb
171
+ @exit_bb = new_bb(:exit)
172
+ @exit_bb.instrs << Instructions::LabelInstr.new(:exit)
173
+
174
+ just_jumped = false
175
+
176
+ instrs.each do |instr|
177
+ case instr
178
+ when Instructions::TwoOperandJumpInstr # :gt, :bne, :beq
179
+ just_jumped = false
180
+ current_bb.add_instr(instr)
181
+ add_forward_edge(current_bb, instr.value, forward_references)
182
+ current_bb = add_fallthrough_edge(current_bb, new_bb("fall_thru_#{instr.object_id}"))
183
+ when Instructions::ExitInstr
184
+ just_jumped = true
185
+ add_jump(current_bb, @exit_bb.label, instr)
186
+ add_jump_edge(current_bb, @exit_bb)
187
+ when Instructions::JumpInstr # :jump
188
+ current_bb.add_instr(instr)
189
+ add_forward_edge(current_bb, instr.value, forward_references)
190
+ just_jumped = true
191
+ when Instructions::LabelInstr
192
+ label = instr.value
193
+ bb = new_bb(label)
194
+ if just_jumped # jump foo\nlabel something_else\n
195
+ just_jumped = false
196
+ else
197
+ add_fallthrough_edge(current_bb, bb)
198
+ end
199
+ current_bb = bb
200
+ forward_references[label]&.each do |source_bb|
201
+ add_jump_edge(source_bb, bb)
202
+ end
203
+ current_bb.add_instr(instr)
204
+ else
205
+ just_jumped = false
206
+ current_bb.add_instr(instr)
207
+ end
208
+ end
209
+
210
+ has_edge = @graph.edges.find { |edge| edge.source == current_bb }
211
+ add_fallthrough_edge(current_bb, @exit_bb) unless has_edge
212
+
213
+ @edge_props = RGL::EdgePropertiesMap.new(@edge_labels, true)
214
+ end
215
+
216
+ def cull
217
+ #show_single_jump_bbs
218
+ cull_dead_bbs
219
+ cull_isolated_bbs
220
+ remove_empty_bbs
221
+ jump_reduction
222
+ cull_dead_bbs
223
+ cull_isolated_bbs
224
+ recombine_pntr
225
+ end
226
+
227
+ def show_single_jump_bbs
228
+ @bb_map.each_value do |bb|
229
+ puts "BB: #{bb.label}"
230
+ puts "---------------"
231
+ puts bb.instrs.map { |i| i.disasm }.join("\n")
232
+ puts
233
+ end
234
+ end
235
+
236
+ # doing up to 3 branches + 1 jump can be optimized by recombining these back
237
+ # into a pntr instruction which can just use a jump table
238
+ def recombine_pntr
239
+ pntrs = {}
240
+ @bb_map.each_value do |bb|
241
+ last_instr = bb.last_instr
242
+ #puts "BB #{bb.label} #{last_instr} #{bb.instrs.size}"
243
+ if bb.label.to_s =~ /pntr_3_(\d+)/
244
+ pntrs[$1] ||= []
245
+ pntrs[$1] << [$1, "3", bb]
246
+ next
247
+ end
248
+ if last_instr.kind_of?(Instructions::BEQInstr)
249
+ if last_instr.label.to_s =~ /pntr_(\d+)_(\d+)/
250
+ #puts "1: #{$1}, 2: #{$2}"
251
+ pntrs[$2] ||= []
252
+ pntrs[$2] << [$2, $1, bb]
253
+ end
254
+ end
255
+ end
256
+
257
+ pntrs.each_value.sort { |(step, i, _), (step2, i2, _)| step <=> step2 && i <=> i2 }
258
+
259
+ pntrs.each_value do |list|
260
+ new_instr = Instructions::PntrInstr.new(list[0][0])
261
+ zero_bb = nil
262
+ list.each.each_with_index do |(step, _, bb), i|
263
+ if i != 3
264
+ label = bb.instrs.last.label
265
+ if i == 0
266
+ compare = bb.instrs.last.operand1
267
+ new_instr.operands << compare
268
+ bb.instrs[-1] = new_instr
269
+ zero_bb = bb
270
+ else
271
+ add_edge(zero_bb, outgoing_target(bb, :jump), :jump)
272
+ remove_bb(bb)
273
+ end
274
+ new_instr.operands << label
275
+ else
276
+ if bb.only_contains_jump?
277
+ jump_bb = outgoing_target(bb, :jump)
278
+ remove_bb(bb)
279
+ bb = jump_bb
280
+ end
281
+ add_edge(zero_bb, bb, :jump)
282
+ new_instr.operands << bb.label
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def jump_reduction
289
+ @bb_map.each_value do |bb|
290
+ #puts "BB: #{bb.label}"
291
+ if @graph.out_degree(bb) == 1
292
+ next_bb = outgoing_target(bb, :fall_through)
293
+
294
+ if next_bb && @graph.out_degree(next_bb) == 1 && next_bb.only_contains_jump?
295
+ #puts "JUMP REPLACE: #{bb.label} -> #{next_bb.label}"
296
+ jump = next_bb.instrs.last
297
+ if bb.instrs[-1].kind_of?(Instructions::JumpInstr)
298
+ bb.instrs[-1] = jump
299
+ else
300
+ bb.instrs << jump
301
+ end
302
+ remove_edge(bb, next_bb)
303
+ add_edge(bb, outgoing_target(next_bb, :jump), :jump)
304
+ next
305
+ end
306
+
307
+ next_bb = outgoing_target(bb, :jump)
308
+ if next_bb && @graph.out_degree(next_bb) == 1 && next_bb.only_contains_jump?
309
+ #puts "JUMP REPLACE: #{bb.label} -> #{next_bb.label}"
310
+ jump = next_bb.instrs.last
311
+ bb.instrs[-1] = jump
312
+ remove_edge(bb, next_bb)
313
+ add_edge(bb, outgoing_target(next_bb, :jump), :jump)
314
+ next
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ def remove_empty_bbs
321
+ # This is a bit wonky but pntr[3] needs fall through to jump to body for pntr[3]
322
+ # I do not think the pattern can occur any other way so I am assuming a lot in this
323
+ # method about its structure. For CFG interp this has no impact but for displaying
324
+ # The CFG itself it removes an empty bb. It also makes recombining the pntr to
325
+ # a jump table simpler.
326
+ @bb_map.each_value do |bb|
327
+ if @graph.out_degree(bb) == 1 && bb.instrs.empty?
328
+ target = outgoing_target(bb, :fall_through)
329
+ source = incoming_sources(bb).first
330
+ remove_bb(bb)
331
+ add_edge(source, target, :fall_through)
332
+ end
333
+ end
334
+ end
335
+
336
+ def cull_dead_bbs
337
+ @bb_map.each_value do |bb|
338
+ jump_bb = outgoing_target(bb, :jump)
339
+
340
+ # How we make CFG will only have a fall_through edge if next bb has an incoming edge
341
+ # AND/OR if we have a jump. We do not act on the incoming edge so we only cull if we
342
+ # see a constant jump
343
+ next unless jump_bb
344
+
345
+ fallthrough_bb = outgoing_target(bb, :fall_through)
346
+ last_instr = bb.instrs.last
347
+ if last_instr&.constant? # We can elminate an edge
348
+ result = last_instr.execute(nil)
349
+ if result
350
+ remove_edge(bb, fallthrough_bb)
351
+ else
352
+ remove_edge(bb, jump_bb)
353
+ puts "DELETING: #{bb.label}:#{last_instr}"
354
+ bb.instrs.delete(last_instr)
355
+ end
356
+ end
357
+ end
358
+ end
359
+
360
+ def cull_isolated_bbs
361
+ @bb_map.each do |label, bb|
362
+ next if bb == @entry_bb
363
+
364
+ in_degree = incoming_sources(bb).size
365
+ if in_degree == 0 # If we cannot make it to this bb delete itself and its outgoing edged
366
+ remove_bb(bb)
367
+ end
368
+ end
369
+ end
370
+
371
+ def combine_bbs(bb)
372
+ other_bb = outgoing_targets(bb)[0]
373
+ puts "combining #{bb.label} with #{other_bb.label}" if debug
374
+ bb.instrs.concat(other_bb.instrs)
375
+ remove_edge(bb, other_bb)
376
+ replace_edge(bb, other_bb, :fall_through)
377
+ replace_edge(bb, other_bb, :jump)
378
+ end
379
+
380
+ def replace_edge(bb, other_bb, edge_type)
381
+ other_edge = outgoing_edges(other_bb, edge_type)
382
+ if other_edge
383
+ remove_edge(edge.source, edge.target)
384
+ add_edge(bb, edge.target, edge_type)
385
+ end
386
+ end
387
+
388
+ # Linearization rearranges basic blocks around fall throughs all being sequentially after
389
+ # the previous fall through bb. All bbs jumped to will get put after that.
390
+ #
391
+ # For these moved blocks we need to add a jump to the bb where execution continues from
392
+ # that bb unless the next bb happens to be right after it.
393
+ #
394
+ # The opposite case is true where last instr is a jump but the jump location happens to
395
+ # be the next bb. In that case we remove the jump.
396
+ #
397
+ # Piet only has two edge types (fall_through, jump) which makes this simpler than
398
+ # other languages which may return (from procedures/functions) or may raise exceptions.
399
+ def linearize
400
+ sorted_list, visited = [], {@exit_bb => true}
401
+ linearize_inner(sorted_list, visited, @entry_bb)
402
+ sorted_list << @exit_bb
403
+ recalculate_jumps(sorted_list)
404
+ sorted_list
405
+ end
406
+
407
+ def postorder_bbs
408
+ order = []
409
+ @graph.depth_first_visit(@entry_bb) { |v| order << v }
410
+ order
411
+ end
412
+
413
+ def postorder_bbs2
414
+ result, work_list, visited = [], [@entry_bb], {@entry_bb => true}
415
+
416
+ while !work_list.empty?
417
+ bb = work_list.last
418
+ all_children_visited = true
419
+ outgoing_targets(bb).each do |dest|
420
+ next if visited[dest]
421
+ visited[dest] = true
422
+ all_children_visited = false
423
+ if @graph.out_degree(dest) == 0 # should only be exit bb
424
+ result << dest
425
+ else
426
+ work_list << dest
427
+ end
428
+ end
429
+
430
+ if all_children_visited
431
+ work_list.pop
432
+ result << bb
433
+ end
434
+ end
435
+
436
+ result
437
+ end
438
+
439
+ def preorder_bbs
440
+ postorder_bbs.reverse
441
+ end
442
+
443
+ private
444
+
445
+ def linearize_inner(sorted_list, visited, bb)
446
+ return if visited[bb]
447
+ visited[bb] = true
448
+ sorted_list << bb
449
+
450
+ fall_through = outgoing_target(bb, :fall_through)
451
+ linearize_inner(sorted_list, visited, fall_through) if fall_through
452
+ outgoing_targets(bb).each do |jump|
453
+ next if jump == fall_through
454
+ linearize_inner(sorted_list, visited, jump) if jump
455
+ end
456
+ end
457
+
458
+ def outgoing_edges_match?(bb, edge, edge_type)
459
+ edge.source == bb && (!edge_type || @edge_props.edge_property(edge.source, edge.target) == edge_type)
460
+ end
461
+
462
+ def check_for_unneeded_jump(current_bb, next_bb, jump)
463
+ if jump.label == next_bb.label
464
+ remove_edge(current_bb, next_bb)
465
+ add_edge(current_bb, next_bb, :fall_through)
466
+ current_bb.instrs.pop
467
+ end
468
+ end
469
+
470
+ def check_for_needed_jump(current_bb, next_bb, last_instr)
471
+ return if current_bb == exit_bb
472
+ dest = outgoing_target(current_bb, :fall_through)
473
+ add_jump(current_bb, dest.label, last_instr) if dest && dest != next_bb
474
+ end
475
+
476
+ def add_jump(bb, label, last_instr)
477
+ jump = Instructions::JumpInstr.new(label)
478
+ jump.graph_node = last_instr&.graph_node
479
+ bb.instrs << jump
480
+ end
481
+
482
+ def recalculate_jumps(bbs)
483
+ bbs.each_with_index do |bb, index|
484
+ last_instr = bb.instrs.last
485
+ if last_instr&.operation == :jump
486
+ check_for_unneeded_jump(bb, bbs[index + 1], last_instr)
487
+ else
488
+ check_for_needed_jump(bb, bbs[index + 1], last_instr)
489
+ end
490
+ end
491
+ end
492
+ end
493
+ end
494
+ end