rpiet 0.1 → 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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