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,77 @@
1
+ require_relative 'ir_interpreter'
2
+
3
+ module RPiet
4
+ module IR
5
+ class IRNativeInterpreter < IRInterpreter
6
+ def process_image(image)
7
+ graph = RPiet::ASG::Parser.new(image).run
8
+ builder = RPiet::Builder.new
9
+ builder.run graph
10
+ @instructions = builder.instructions
11
+ @cfg = CFG.new(@instructions)
12
+ @instructions = @cfg.instructions(Passes::Peephole)
13
+ @cfg.write_to_dot_file
14
+ # Note: left over from experiment but pop or push with n will construct an array and this outweighs the benefit
15
+ # combine_pushes_and_pops
16
+ generate_ruby_bb_methods
17
+ end
18
+
19
+ def run
20
+ method_name = :entry
21
+ while method_name do
22
+ method_name = send(method_name)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def generate_ruby_bb_methods
29
+ # Generate constants and things generated methods may rely on.
30
+ @instructions.each { |instr| eval instr.to_ruby_pre if instr.respond_to?(:to_ruby_pre) }
31
+
32
+ @cfg.basic_blocks.each do |bb|
33
+ s = ["def #{bb.label}()\n"]
34
+ bb.instrs.each do |instr|
35
+ lines = instr.to_ruby(@cfg, bb)
36
+ s << lines if lines
37
+ end
38
+ unless bb.instrs&.last&.jump?
39
+ ret = @cfg.outgoing_target(bb, :fall_through)&.label || 'nil'
40
+ s << " #{ret}\n"
41
+ end
42
+ s << "end\n"
43
+ code = s.join('')
44
+ puts "#{code}\n"
45
+ self.instance_eval code
46
+ if bb.instrs&.first.kind_of?(Instructions::LabelInstr)
47
+ bb.instrs.pop(bb.instrs.length - 1)
48
+ else
49
+ bb.instrs.clear
50
+ end
51
+ end
52
+ end
53
+
54
+ def combine_pushes_and_pops
55
+ @cfg.basic_blocks.each { |bb| multi_opt(Instructions::MultiplePushInstr, bb, :push, :operand) }
56
+ @cfg.basic_blocks.each { |bb| multi_opt(Instructions::MultiplePopInstr, bb, :pop, :result) }
57
+ end
58
+
59
+ def multi_opt(instr_type, bb, operation, field)
60
+ list = []
61
+ bb.instrs.each_with_index do |instr, i|
62
+ if instr.operation == operation
63
+ list << instr
64
+ else
65
+ if list.length > 1
66
+ bb.instrs[(i - list.length)...i] = instr_type.new(*list.map { |instr| instr.send(field) }.reverse)
67
+ end
68
+ list = []
69
+ end
70
+ end
71
+ if list.length > 1
72
+ bb.instrs[(bb.instrs.length - list.length)..-1] = instr_type.new(*list.map { |instr| instr.send(field) }.reverse)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,279 @@
1
+ require 'jruby'
2
+ require_relative '../parser/ser'
3
+ require_relative 'builder'
4
+
5
+ module Kernel
6
+ # returns a lambda containing the piet program loaded
7
+ def piet_require(filename, codel_size=1)
8
+ if filename =~ /.txt/
9
+ require_relative '../image/ascii_image'
10
+ image = RPiet::Image::AsciiImage.new(File.read(filename), codel_size)
11
+ else
12
+ filename = 'file:' + filename if File.exist? filename
13
+ require_relative '../image/url_image'
14
+ image = RPiet::Image::URLImage.new(filename, codel_size)
15
+ end
16
+
17
+ graph = RPiet::Parser.new(image).run
18
+ builder = RPiet::Builder.new
19
+ builder.run graph
20
+ puts builder.instructions.join("\n")
21
+ puts "Done"
22
+ runtime = JRuby.runtime
23
+ tc = runtime.current_context
24
+ scope = RPiet::JRubyBackend.new(runtime.getIRManager, tc.current_static_scope).build(builder.instructions, builder.cc, builder.dp)
25
+
26
+ body = scope.block_body
27
+ runtime.getJITCompiler.get_task_for(tc, body).run
28
+ block = org.jruby.runtime.Block.new(body, tc.currentBinding(runtime.object, tc.current_scope))
29
+
30
+ #set_trace_func proc { |event, *e| p e if event.to_s == "call" }
31
+
32
+ org.jruby.RubyProc.newProc(runtime, block, org.jruby.runtime.Block::Type::LAMBDA, filename, 0)
33
+ end
34
+
35
+ #public :p
36
+ end
37
+
38
+ class Array
39
+ def roll(depth, num)
40
+ num %= depth
41
+ return if depth <= 0 || num == 0
42
+ if num > 0
43
+ self[-depth..-1] = self[-num..-1] + self[-depth...-num]
44
+ elsif num < 0
45
+ self[-depth..-1] = self[-depth...-num] + self[-num..-1]
46
+ end
47
+ end
48
+ end
49
+
50
+ module RPiet
51
+ class JRubyBackend
52
+ DEBUG = false
53
+
54
+ java_import org.jruby.parser.StaticScope
55
+ java_import org.jruby.parser.StaticScopeFactory
56
+ java_import org.jruby.runtime.ArgumentDescriptor
57
+ java_import org.jruby.runtime.ArgumentType
58
+ java_import org.jruby.runtime.CallType
59
+ java_import org.jruby.runtime.RubyEvent
60
+ java_import org.jruby.runtime.Signature
61
+ java_import org.jruby.ir.Operation
62
+ java_import org.jruby.ir.IRClosure
63
+ java_import org.jruby.ir.instructions.BEQInstr
64
+ java_import org.jruby.ir.instructions.BNEInstr
65
+ java_import org.jruby.ir.instructions.CallInstr
66
+ java_import org.jruby.ir.instructions.CheckArityInstr
67
+ java_import org.jruby.ir.instructions.JumpInstr
68
+ java_import org.jruby.ir.instructions.LabelInstr
69
+ java_import org.jruby.ir.instructions.NopInstr
70
+ java_import org.jruby.ir.instructions.NoResultCallInstr
71
+ java_import org.jruby.ir.instructions.ReceivePreReqdArgInstr
72
+ java_import org.jruby.ir.instructions.ReturnInstr
73
+ java_import org.jruby.ir.instructions.TraceInstr
74
+ java_import org.jruby.ir.operands.CurrentScope
75
+ java_import org.jruby.ir.operands.Operand
76
+ java_import org.jruby.ir.operands.ScopeModule
77
+ java_import org.jruby.ir.operands.StringLiteral
78
+ java_import org.jruby.ir.operands.TemporaryLocalVariable
79
+ java_import org.jruby.ir.operands.UnexecutableNil
80
+
81
+ class NamedTemporaryVariable < TemporaryLocalVariable
82
+ def initialize(index, name)
83
+ super(index)
84
+ @name = name
85
+ end
86
+
87
+ def getPrefix
88
+ @name
89
+ end
90
+ end
91
+
92
+ def initialize(manager, containing_scope)
93
+ @manager = manager
94
+ "_in".to_java(:String).intern
95
+ "_out".to_java(:String).intern
96
+ param_names = ["_in", "_out"].to_java java.lang.String
97
+ static_scope = StaticScopeFactory.new_static_scope(containing_scope, StaticScope::Type::BLOCK, param_names, -1)
98
+ @scope = IRClosure.new(@manager, containing_scope.getIRScope, 0,
99
+ static_scope, Signature::TWO_ARGUMENTS)
100
+ @instructions = java.util.ArrayList.new
101
+ @variables = {} # piet -> jruby
102
+ @labels = {} # piet -> jruby
103
+ end
104
+
105
+ def add(instr)
106
+ @instructions.add instr
107
+ end
108
+
109
+ # Note: originally considered fully boxing by default but VM
110
+ # should be responsible so this is a call (plus this is way simpler)
111
+ def alu_op(instr)
112
+ add call(instr.result, instr.operand1, instr.oper, instr.operand2)
113
+ end
114
+
115
+ def call(result, receiver, name, *args)
116
+ if result
117
+ CallInstr.create(@scope, CallType::NORMAL, build_operand(result), name.to_s,
118
+ build_operand(receiver), build_operands(*args).to_java(Operand), nil)
119
+ else
120
+ recv = build_operand(receiver)
121
+ operands = build_operands(*args)
122
+ NoResultCallInstr.create(CallType::NORMAL, name.to_s, recv, operands.to_java(Operand),
123
+ nil, false)
124
+ end
125
+ end
126
+
127
+ def copy(a,b)
128
+ org.jruby.ir.instructions.CopyInstr.new build_operand(a), build_operand(b)
129
+ end
130
+
131
+ def build_operands(*operands)
132
+ operands.inject([]) do |array, operand|
133
+ array << build_operand(operand)
134
+ array
135
+ end
136
+ end
137
+
138
+ def build_operand(operand)
139
+ return operand if operand.kind_of? Operand
140
+
141
+ case operand.type
142
+ when :var then
143
+ temp_var_for(operand)
144
+ when :string then
145
+ StringLiteral.new operand.value
146
+ when :num then num(operand.value)
147
+ end
148
+ end
149
+
150
+ def label(piet_label)
151
+ @labels[piet_label] = @scope.get_new_label(piet_label.value) unless @labels[piet_label]
152
+ @labels[piet_label]
153
+ end
154
+
155
+ def num(value)
156
+ org.jruby.ir.operands.Fixnum.new value
157
+ end
158
+
159
+ def temp_var_for(variable)
160
+ @variables[variable] = temp_var unless @variables[variable]
161
+ @variables[variable]
162
+ end
163
+
164
+ def temp_var
165
+ @scope.create_temporary_variable
166
+ end
167
+
168
+ def build(piet_instructions, cc, dp)
169
+ build_prologue
170
+ build_args
171
+ build_pointers(cc, dp)
172
+ return_value = build_body(piet_instructions)
173
+ build_return return_value
174
+ @scope.allocateInterpreterContext @instructions
175
+ @scope
176
+ end
177
+
178
+ # 2 args (in, out)
179
+ def build_args
180
+ @scope.argument_descriptors = build_arg_descriptor
181
+ add CheckArityInstr.new(2, 0, false, false, -1)
182
+ _in = @scope.get_new_local_variable("_in", 0)
183
+ add ReceivePreReqdArgInstr.new(_in, 0)
184
+ _out = @scope.get_new_local_variable("_out", 0)
185
+ add ReceivePreReqdArgInstr.new(_out, 1)
186
+ end
187
+
188
+ def build_body(piet_instrs)
189
+ temp_var = temp_var() # we generally only need one and can reuse it.
190
+ input = @scope.lookupExistingLVar("_in")
191
+ output = @scope.lookupExistingLVar("_out")
192
+ stack_var = build_stack
193
+ piet_instrs.each do |instr|
194
+ next unless instr
195
+ case instr.type
196
+ when :noop
197
+ add NopInstr.NOP
198
+ when :add, :sub, :mult, :div, :mod, :pow then alu_op(instr)
199
+ when :push
200
+ add call(nil, stack_var, :push, instr.operand)
201
+ when :pop
202
+ add call(instr.result, stack_var, :pop)
203
+ when :roll
204
+ add call(nil, stack_var, :roll, instr.operand1, instr.operand2)
205
+ when :cin
206
+ add call(instr.result, input, :read, num(1))
207
+ when :nin
208
+ add call(temp_var, input, :gets)
209
+ add call(nil, output, :puts, temp_var)
210
+ add call(instr.result, temp_var, :to_i)
211
+ when :cout
212
+ add call(temp_var, instr.operand, :chr)
213
+ add call(nil, output, :print, temp_var)
214
+ when :nout
215
+ add call(nil, output, :print, instr.operand)
216
+ when :jump
217
+ add JumpInstr.new label(instr.label)
218
+ when :beq
219
+ add BEQInstr.create(*build_operands(instr.operand1, instr.operand2),
220
+ label(instr.label))
221
+ when :bne
222
+ add BNEInstr.create(label(instr.label),
223
+ *build_operands(instr.operand1, instr.operand2))
224
+ when :gt
225
+ add call(temp_var, instr.operand2, :>, instr.operand1)
226
+ add BNEInstr.create(label(instr.label), temp_var, @manager.true)
227
+ when :label
228
+ add LabelInstr.new(label(instr))
229
+ when :copy
230
+ add copy(instr.result, instr.operand)
231
+ when :node
232
+ add TraceInstr.new(RubyEvent::CALL, instr.to_s, "", -1) if DEBUG
233
+ end
234
+ end
235
+ add call(temp_var, stack_var, :pop)
236
+ temp_var
237
+ end
238
+
239
+ # Made custom-named type so we get better output in JRuby IR
240
+ # so we can more easily recognize special locals
241
+ def build_pointers(cc, dp)
242
+ cc_operand = build_operand(cc)
243
+ @variables[cc] = NamedTemporaryVariable.new cc_operand.offset, '%cc'
244
+ dp_operand = build_operand(dp)
245
+ @variables[dp] = NamedTemporaryVariable.new dp_operand.offset, '%dp'
246
+ end
247
+
248
+ # will be our live stack during execution.
249
+ # Note: JRuby IR has no primitives for manipulating arrays
250
+ # directly so we will be leveraging Ruby internally for
251
+ # stack manipulation
252
+ def build_stack
253
+ t = temp_var # alloc temp but the take it over with named one
254
+ NamedTemporaryVariable.new(t.offset, '%stack').tap do |stack_var|
255
+ add copy(stack_var, org.jruby.ir.operands.Array.new)
256
+ end
257
+ end
258
+
259
+ def build_arg_descriptor
260
+ [ArgumentDescriptor.new(ArgumentType.req, "_in"),
261
+ ArgumentDescriptor.new(ArgumentType.req, "_out")].to_java(ArgumentDescriptor)
262
+ end
263
+
264
+ def build_prologue
265
+ add @manager.receive_self_instr
266
+ add copy(@scope.current_scope_variable, CurrentScope::CURRENT_SCOPE[0])
267
+ add copy(@scope.current_module_variable, ScopeModule::SCOPE_MODULE[0])
268
+ end
269
+
270
+ def build_return(return_value)
271
+ if return_value && return_value != UnexecutableNil::U_NIL
272
+ add ReturnInstr.new(return_value)
273
+ end
274
+ # Note: I am ignoring all special handling logic for lambda here
275
+ # because I know I have no need for it but I wonder if JRuby IR
276
+ # will choke on this lack of support regardless?
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,28 @@
1
+ module RPiet
2
+ module IR
3
+ ##
4
+ # Using pure Ruby types for all operands but variable:
5
+ # 1. label => Symbol
6
+ # 2. char => String
7
+ # 3. numeric => Integer
8
+ module Operands
9
+ class Poperand
10
+ def to_s = "<pop>"
11
+ end
12
+
13
+ class VariableOperand
14
+ attr_reader :name
15
+ attr_accessor :value
16
+ def initialize(name)
17
+ @value, @name = nil, name
18
+ end
19
+
20
+ def eql?(other) = @name == other.name
21
+
22
+ def hash = [self.class, @name].hash
23
+
24
+ def to_s = "#@name#{@value ? %Q{:(#@value)} : ''}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ module RPiet
2
+ module IR
3
+ module Passes
4
+ class DataFlowProblem
5
+ attr_reader :computed, :worklist, :cfg
6
+ attr_accessor :debug
7
+
8
+ def initialize(cfg, flow_node_class)
9
+ @cfg, @flow_node_class, @debug = cfg, flow_node_class, false
10
+ end
11
+
12
+ def run
13
+ # we will walk forward but want to pop to remove entries so it will appear backwards!
14
+ @worklist = @cfg.postorder_bbs.map {|bb| @flow_node_class.new(self, bb) }
15
+ @node_map = {}
16
+ @computed = @worklist.each_with_object({}) do |node, h|
17
+ h[node] = true
18
+ @node_map[node.bb] = node
19
+ end
20
+
21
+ while !@worklist.empty?
22
+ @worklist.pop.compute_data_flow_info
23
+ end
24
+ end
25
+
26
+ def node_for(bb)
27
+ @node_map[bb]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,76 @@
1
+ module RPiet
2
+ module IR
3
+ module Passes
4
+ class FlowGraphNode
5
+ attr_reader :bb, :problem
6
+
7
+ def initialize(problem, bb)
8
+ @problem, @bb = problem, bb
9
+ end
10
+
11
+ def cfg = problem.cfg
12
+ def computed = problem.computed
13
+ def debug = problem.debug
14
+ def worklist = problem.worklist
15
+
16
+ def compute_data_flow_info
17
+ computed[self] = false
18
+
19
+ apply_pre_meet_handler
20
+ compute_flow_info
21
+ end
22
+
23
+ def compute_flow_info
24
+ puts "compute_flow_info for #{bb.label}" if debug
25
+ cfg.incoming_sources(bb).each do |source|
26
+ puts " <--- #{source.label}" if debug
27
+ compute_meet(problem.node_for(source))
28
+ end
29
+
30
+ solution do
31
+ bb.instrs.each { |instr| apply_transfer_function(instr) }
32
+ requeue_target_bbs(cfg.outgoing_targets(bb)) if solution_changed?
33
+ end
34
+ end
35
+
36
+ def solution
37
+ solution_init
38
+ yield
39
+ assign_outs
40
+ end
41
+
42
+ # Something changed and so we need to reprocess these flow nodes.
43
+ def requeue_target_bbs(bbs)
44
+ bbs.filter { |bb| !computed[bb] }.each do |bb|
45
+ computed[bb] = true
46
+ worklist << problem.node_for(bb)
47
+ end
48
+ end
49
+
50
+ def apply_pre_meet_handler
51
+ end
52
+
53
+ def apply_transfer_function(instr)
54
+ end
55
+
56
+ # See define_ins
57
+ def assign_outs
58
+ end
59
+
60
+ # Function which merges out values from the incoming flow nodes.
61
+ # Define appropriate logic to apply MOP (to merge results into a
62
+ # new in set for this node).
63
+ def compute_meet(node)
64
+ end
65
+
66
+ # Construct the data structure to represent the values of this node
67
+ # in terms to resolve with in and to become out.
68
+ def solution_init
69
+ end
70
+
71
+ def solution_changed?
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,214 @@
1
+ module RPiet
2
+ module IR
3
+ module Passes
4
+ class Peephole
5
+ def initialize(cfg)
6
+ @cfg = cfg
7
+ @pushes = {}
8
+ @processed = {}
9
+ end
10
+
11
+ def run
12
+ @cfg.linearize.each do |bb|
13
+ 3.times do
14
+ push_pop_elimination(bb)
15
+ constant_bb(bb)
16
+ constant_fold_bb(bb)
17
+ roll_elimination(bb)
18
+ end
19
+
20
+ poperand_optimize(bb)
21
+ remove_extra_cc_dp(bb)
22
+ end
23
+ @cfg.cull
24
+ end
25
+
26
+ def remove_extra_cc_dp(bb)
27
+ instructions = bb.instrs
28
+ @dps, @ccs = [], []
29
+
30
+ instructions.each do |instr|
31
+ case instr
32
+ when Instructions::DPRotateInstr
33
+ @dps = []
34
+ when Instructions::DPInstr
35
+ @dps << instr
36
+ when Instructions::CCToggleInstr
37
+ @ccs = []
38
+ when Instructions::CCInstr
39
+ @ccs << instr
40
+ end
41
+ end
42
+
43
+ @ccs[0...-1].each { |instr| instructions.delete(instr) } if @ccs.length > 1
44
+ @dps[0...-1].each { |instr| instructions.delete(instr) } if @dps.length > 1
45
+ end
46
+
47
+ def roll_elimination(bb)
48
+ instructions = bb.instrs
49
+ @roll_vars ||= 0
50
+ i = 0
51
+ while i < instructions.length
52
+ instr = instructions[i]
53
+ if instr.kind_of?(Instructions::RollInstr) && instr.constant?
54
+ new_instructions = []
55
+ new_variables = []
56
+ depth = instr.depth
57
+ depth.times do
58
+ variable = Operands::VariableOperand.new("r#{@roll_vars}")
59
+ @roll_vars += 1
60
+ new_variables << variable
61
+ new_instructions << Instructions::PopInstr.new(variable)
62
+ end
63
+ num = instr.num
64
+ num = depth+num if num < 0
65
+ new_variables[0...num].reverse_each do |var|
66
+ new_instructions << Instructions::PushInstr.new(var)
67
+ end
68
+ new_variables[num..-1].reverse_each do |var|
69
+ new_instructions << Instructions::PushInstr.new(var)
70
+ end
71
+
72
+ instructions[i, 1] = new_instructions
73
+ end
74
+ i += 1
75
+ end
76
+ end
77
+
78
+ def poperand_optimize(bb)
79
+ instructions = bb.instrs
80
+ pops = []
81
+ dead_instrs = []
82
+
83
+ uses = instructions.each_with_object({}) do |instr, h|
84
+ instr.operands.each do |operand|
85
+ if operand.kind_of?(Operands::VariableOperand)
86
+ h[operand] ||= 0
87
+ h[operand] += 1
88
+ end
89
+ end
90
+ end
91
+
92
+ i = 0
93
+ while i < instructions.length
94
+ instr = instructions[i]
95
+
96
+ if instr.kind_of?(Instructions::PopInstr)
97
+ #puts "pushing another pop #{instr}"
98
+ pops << instr
99
+ elsif instr.kind_of?(Instructions::PushInstr)
100
+ pops = []
101
+ elsif contains_variables(instr)
102
+ roll = true if instr.kind_of?(Instructions::RollInstr)
103
+ if !pops.empty?
104
+ #puts "processing #{instr} with these pops #{pops.map {|p| p.disasm }.join(", ")}"
105
+ instr.operands.each_with_index do |operand, i|
106
+ break if pops.empty?
107
+ # This is at best conservative but it is wrong
108
+ #puts "#{pops.last.result} <=> #{operand}"
109
+ if pops.last.result == operand && uses[operand] == 1
110
+ #puts "replacing #{operand} with a pop and removing #{pops.last.result}"
111
+ instr.operands[i] = Operands::Poperand.new
112
+ dead_instrs << pops.pop
113
+ end
114
+ end
115
+ end
116
+
117
+ pops = [] if roll
118
+ roll = false
119
+ elsif instr.respond_to?(:two_pop)
120
+ roll = true if instr.kind_of?(Instructions::RollInstr)
121
+ if pops.length >= 2
122
+ dead_instrs.concat(pops.pop(2))
123
+ instructions[i] = instr.two_pop
124
+ end
125
+ pops = [] if roll
126
+ roll = false
127
+ end
128
+ i += 1
129
+ end
130
+
131
+ dead_instrs.each do |instr|
132
+ instructions.delete(instr)
133
+ end
134
+ end
135
+
136
+ def contains_variables(instr)
137
+ instr.operands.filter { |operand| operand.kind_of?(Operands::VariableOperand) }
138
+ end
139
+
140
+ def constant_bb(bb)
141
+ instructions = bb.instrs
142
+ i = 0
143
+ constants = {}
144
+ while i < instructions.length
145
+ instr = instructions[i]
146
+
147
+ instr.operands.each_with_index do |operand, i|
148
+ instr.operands[i] = constants[operand] if constants[operand]
149
+ end
150
+
151
+ if instr.kind_of?(Instructions::CopyInstr)
152
+ constants[instr.result] = instr.operand
153
+ instructions.delete(instr)
154
+ else
155
+ i += 1
156
+ end
157
+ end
158
+ end
159
+
160
+ def constant_fold_bb(bb)
161
+ instructions = bb.instrs
162
+ i = 0
163
+ while i < instructions.length
164
+ instr = instructions[i]
165
+ if (instr.kind_of?(Instructions::MathInstr) || instr.kind_of?(Instructions::GTInstr)) && instr.constant?
166
+ instr.execute(nil)
167
+ instructions[i] = Instructions::CopyInstr.new(instr.result, instr.result.value)
168
+ end
169
+ i += 1
170
+ end
171
+ end
172
+
173
+ def remove_dead_edges(bb)
174
+ last_instr = bb.instrs.last
175
+ if last_instr.kind_of?(Instructions::TwoOperandJumpInstr) && instr.constant? && instr.execute(nil).nil?
176
+ instr.label
177
+
178
+
179
+ end
180
+ end
181
+
182
+ def push_pop_elimination(bb)
183
+ @processed[bb] = true
184
+ #puts "RUN for #{bb.label}"
185
+ pushes = [] # lifo instr list
186
+ instructions = bb.instrs
187
+
188
+ i = 0
189
+ while i < instructions.length
190
+ instr = instructions[i]
191
+ if instr.kind_of?(Instructions::PushInstr)
192
+ pushes << instr
193
+ elsif instr.kind_of?(Instructions::NoopInstr) && !instr.kind_of?(Instructions::LabelInstr)
194
+ instructions.delete(instr)
195
+ next
196
+ elsif instr.kind_of?(Instructions::RollInstr)
197
+ # Without knowing roll values we have no way to reason about roll so we just throw out all pushes
198
+ pushes = []
199
+ elsif instr.kind_of?(Instructions::PopInstr) && !pushes.empty?
200
+ last_push = pushes.pop
201
+ # count = count_map[last_push.operand]
202
+ #if !count || count == 1
203
+ instructions[i] = Instructions::CopyInstr.new(instr.result, last_push.operand)
204
+ instructions.delete(last_push)
205
+ #end
206
+ next
207
+ end
208
+ i += 1
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end