rpiet 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +12 -0
- data/bin/color_wheel +84 -0
- data/bin/image_gen +39 -0
- data/bin/rpiet +68 -11
- data/images/counter.txt +7 -0
- data/lib/rpiet/asg/graph_interpreter.rb +39 -0
- data/lib/rpiet/asg/parser.rb +156 -0
- data/lib/rpiet/asg/visitor.rb +66 -0
- data/lib/rpiet/asg.rb +336 -0
- data/lib/rpiet/codel_chooser.rb +32 -4
- data/lib/rpiet/color.rb +70 -25
- data/lib/rpiet/cycle.rb +18 -7
- data/lib/rpiet/debugger/debugger.rb +298 -0
- data/lib/rpiet/debugger/stylesheet.css +88 -0
- data/lib/rpiet/direction_pointer.rb +49 -18
- data/lib/rpiet/event_handler.rb +62 -7
- data/lib/rpiet/group.rb +25 -8
- data/lib/rpiet/image/ascii_image.rb +8 -20
- data/lib/rpiet/image/image.rb +8 -3
- data/lib/rpiet/image/url_image.rb +28 -14
- data/lib/rpiet/interpreter.rb +72 -72
- data/lib/rpiet/ir/assembler.rb +87 -0
- data/lib/rpiet/ir/builder.rb +255 -0
- data/lib/rpiet/ir/cfg.rb +494 -0
- data/lib/rpiet/ir/instructions.rb +536 -0
- data/lib/rpiet/ir/ir_cfg_interpreter.rb +23 -0
- data/lib/rpiet/ir/ir_interpreter.rb +101 -0
- data/lib/rpiet/ir/ir_native_interpreter.rb +77 -0
- data/lib/rpiet/ir/jruby_backend.rb +279 -0
- data/lib/rpiet/ir/operands.rb +28 -0
- data/lib/rpiet/ir/passes/data_flow_problem.rb +32 -0
- data/lib/rpiet/ir/passes/flow_graph_node.rb +76 -0
- data/lib/rpiet/ir/passes/peephole.rb +214 -0
- data/lib/rpiet/ir/passes/push_pop_elimination_pass.rb +112 -0
- data/lib/rpiet/live_machine_state.rb +15 -0
- data/lib/rpiet/machine.rb +62 -32
- data/lib/rpiet/source.rb +83 -0
- data/lib/rpiet/version.rb +1 -1
- data/lib/rpiet.rb +2 -2
- data/rpiet.gemspec +19 -0
- data/spec/asg/visitor_spec.rb +41 -0
- data/spec/cycle_spec.rb +34 -34
- data/spec/direction_pointer_spec.rb +33 -6
- data/spec/group_spec.rb +73 -48
- data/spec/interpreter_spec.rb +161 -12
- data/spec/ir/assembler_spec.rb +122 -0
- data/spec/ir/builder_spec.rb +20 -0
- data/spec/ir/cfg_spec.rb +151 -0
- data/spec/ir/ir_interpreter_spec.rb +102 -0
- data/spec/ir/passes/push_pop_elimination_pass_spec.rb +34 -0
- data/spec/machine_spec.rb +5 -3
- data/spec/source_spec.rb +69 -0
- data/spec/spec_helper.rb +78 -0
- metadata +54 -16
- 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
|