furnace 0.3.1 → 0.4.0.beta.1
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.
- data/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +1 -2
- data/{LICENSE → LICENSE.MIT} +14 -14
- data/Rakefile +1 -5
- data/furnace.gemspec +7 -3
- data/lib/furnace/ast/node.rb +14 -0
- data/lib/furnace/ast/processor.rb +7 -7
- data/lib/furnace/ssa/argument.rb +23 -0
- data/lib/furnace/ssa/basic_block.rb +117 -0
- data/lib/furnace/ssa/builder.rb +100 -0
- data/lib/furnace/ssa/constant.rb +43 -0
- data/lib/furnace/ssa/function.rb +191 -0
- data/lib/furnace/ssa/generic_instruction.rb +14 -0
- data/lib/furnace/ssa/generic_type.rb +16 -0
- data/lib/furnace/ssa/instruction.rb +92 -0
- data/lib/furnace/ssa/instruction_syntax.rb +109 -0
- data/lib/furnace/ssa/instructions/branch.rb +11 -0
- data/lib/furnace/ssa/instructions/phi.rb +63 -0
- data/lib/furnace/ssa/instructions/return.rb +11 -0
- data/lib/furnace/ssa/module.rb +52 -0
- data/lib/furnace/ssa/named_value.rb +25 -0
- data/lib/furnace/ssa/pretty_printer.rb +113 -0
- data/lib/furnace/ssa/terminator_instruction.rb +24 -0
- data/lib/furnace/ssa/type.rb +27 -0
- data/lib/furnace/ssa/types/basic_block.rb +11 -0
- data/lib/furnace/ssa/types/function.rb +11 -0
- data/lib/furnace/ssa/types/void.rb +15 -0
- data/lib/furnace/ssa/user.rb +84 -0
- data/lib/furnace/ssa/value.rb +62 -0
- data/lib/furnace/ssa.rb +66 -0
- data/lib/furnace/transform/iterative.rb +27 -0
- data/lib/furnace/transform/pipeline.rb +3 -3
- data/lib/furnace/version.rb +1 -1
- data/lib/furnace.rb +3 -3
- data/test/ast_test.rb +32 -3
- data/test/ssa_test.rb +1129 -0
- data/test/test_helper.rb +17 -28
- data/test/transform_test.rb +74 -0
- metadata +136 -58
- data/lib/furnace/cfg/algorithms.rb +0 -193
- data/lib/furnace/cfg/graph.rb +0 -99
- data/lib/furnace/cfg/node.rb +0 -78
- data/lib/furnace/cfg.rb +0 -7
- data/lib/furnace/transform/iterative_process.rb +0 -26
data/test/ssa_test.rb
ADDED
@@ -0,0 +1,1129 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe SSA do
|
4
|
+
SSA::PrettyPrinter.colorize = false
|
5
|
+
|
6
|
+
class SSARubyType < SSA::GenericType
|
7
|
+
def initialize(ruby_type)
|
8
|
+
@ruby_type = ruby_type
|
9
|
+
end
|
10
|
+
|
11
|
+
def parameters
|
12
|
+
[@ruby_type]
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"^#{@ruby_type}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Class
|
21
|
+
def to_type
|
22
|
+
SSARubyType.new(self)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class BindingInsn < SSA::Instruction
|
27
|
+
def type
|
28
|
+
Binding.to_type
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class DupInsn < SSA::Instruction
|
33
|
+
def type
|
34
|
+
operands.first.type
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class TupleConcatInsn < SSA::Instruction
|
39
|
+
def type
|
40
|
+
Array.to_type
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class GenericInsn < SSA::GenericInstruction
|
45
|
+
end
|
46
|
+
|
47
|
+
class CondBranchInsn < SSA::TerminatorInstruction
|
48
|
+
syntax do |s|
|
49
|
+
s.operand :condition
|
50
|
+
s.operand :if_true, SSA::BasicBlock
|
51
|
+
s.operand :if_false, SSA::BasicBlock
|
52
|
+
end
|
53
|
+
|
54
|
+
def exits?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module TestScope
|
60
|
+
include SSA
|
61
|
+
|
62
|
+
BindingInsn = ::BindingInsn
|
63
|
+
DupInsn = ::DupInsn
|
64
|
+
TupleConcatInsn = ::TupleConcatInsn
|
65
|
+
|
66
|
+
class NestedInsn < SSA::Instruction; end
|
67
|
+
end
|
68
|
+
|
69
|
+
class TestBuilder < SSA::Builder
|
70
|
+
def self.scope
|
71
|
+
TestScope
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
before do
|
76
|
+
@function = SSA::Function.new('foo')
|
77
|
+
@basic_block = SSA::BasicBlock.new(@function)
|
78
|
+
@function.add @basic_block
|
79
|
+
@function.entry = @basic_block
|
80
|
+
end
|
81
|
+
|
82
|
+
def insn_noary(basic_block)
|
83
|
+
BindingInsn.new(basic_block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def insn_unary(basic_block, what)
|
87
|
+
DupInsn.new(basic_block, [what])
|
88
|
+
end
|
89
|
+
|
90
|
+
def insn_binary(basic_block, left, right)
|
91
|
+
TupleConcatInsn.new(basic_block, [left, right])
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'converts class names to opcodes' do
|
95
|
+
SSA.class_name_to_opcode(DupInsn).should == 'dup'
|
96
|
+
SSA.class_name_to_opcode(TupleConcatInsn).should == 'tuple_concat'
|
97
|
+
SSA.class_name_to_opcode(TestScope::NestedInsn).should == 'nested'
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'converts opcodes to class names' do
|
101
|
+
SSA.opcode_to_class_name('foo').should == 'FooInsn'
|
102
|
+
SSA.opcode_to_class_name('foo_bar').should == 'FooBarInsn'
|
103
|
+
SSA.opcode_to_class_name(:foo_bar).should == 'FooBarInsn'
|
104
|
+
end
|
105
|
+
|
106
|
+
describe SSA::PrettyPrinter do
|
107
|
+
it 'outputs chunks' do
|
108
|
+
SSA::PrettyPrinter.new do |p|
|
109
|
+
p.text 'foo'
|
110
|
+
end.to_s.should == 'foo'
|
111
|
+
|
112
|
+
SSA::PrettyPrinter.new do |p|
|
113
|
+
p.type Integer.to_type
|
114
|
+
end.to_s.should == '^Integer'
|
115
|
+
|
116
|
+
SSA::PrettyPrinter.new do |p|
|
117
|
+
p.keyword 'bar'
|
118
|
+
end.to_s.should == 'bar'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'ensures space between chunks' do
|
122
|
+
SSA::PrettyPrinter.new do |p|
|
123
|
+
p.text 'foo'
|
124
|
+
p.keyword 'doh'
|
125
|
+
p.text 'bar'
|
126
|
+
end.to_s.should == 'foo doh bar'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'adds no space inside #text chunk' do
|
130
|
+
SSA::PrettyPrinter.new do |p|
|
131
|
+
p.text 'foo', 'bar'
|
132
|
+
p.keyword 'squick'
|
133
|
+
end.to_s.should == 'foobar squick'
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'adds no space after newline' do
|
137
|
+
SSA::PrettyPrinter.new do |p|
|
138
|
+
p.text 'foo'
|
139
|
+
p.newline
|
140
|
+
p.text 'bar'
|
141
|
+
end.to_s.should == "foo\nbar"
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'converts objects to chunks with to_s' do
|
145
|
+
SSA::PrettyPrinter.new do |p|
|
146
|
+
p.text :foo
|
147
|
+
p.text 1
|
148
|
+
p.keyword :bar
|
149
|
+
end.to_s.should == 'foo 1 bar'
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'adds colors if requested' do
|
153
|
+
SSA::PrettyPrinter.new(true) do |p|
|
154
|
+
p.keyword :bar
|
155
|
+
end.to_s.should == "\e[1;37mbar\e[0m"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe SSA::Type do
|
160
|
+
before do
|
161
|
+
@type = SSA::Type.new
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'converts to type' do
|
165
|
+
@type.to_type.should.be.equal @type
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'is a monotype' do
|
169
|
+
@type.should.be.monotype
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should be ==, eql?, subtype_of? and have the same hash as another instance' do
|
173
|
+
other = SSA::Type.new
|
174
|
+
@type.should == other
|
175
|
+
@type.should.be.eql other
|
176
|
+
@type.should.be.subtype_of other
|
177
|
+
@type.hash.should == other.hash
|
178
|
+
end
|
179
|
+
|
180
|
+
describe SSA::GenericType do
|
181
|
+
before do
|
182
|
+
@type = Integer.to_type
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should compare its parameters' do
|
186
|
+
otheri = Integer.to_type
|
187
|
+
otherf = Float.to_type
|
188
|
+
|
189
|
+
@type.should == otheri
|
190
|
+
@type.should.be.eql otheri
|
191
|
+
@type.should.be.subtype_of otheri
|
192
|
+
@type.hash.should == otheri.hash
|
193
|
+
|
194
|
+
@type.should.not == otherf
|
195
|
+
@type.should.not.be.eql otherf
|
196
|
+
@type.should.not.be.subtype_of otherf
|
197
|
+
@type.hash.should.not == otherf.hash
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe SSA::Value do
|
203
|
+
before do
|
204
|
+
@val = SSA::Value.new
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'has void type' do
|
208
|
+
@val.type.should == SSA.void
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'is not constant' do
|
212
|
+
@val.should.not.be.constant
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'compares by identity through #to_value' do
|
216
|
+
@val.should.not == 1
|
217
|
+
@val.should == @val
|
218
|
+
|
219
|
+
val = @val
|
220
|
+
@val.should == Class.new { define_method(:to_value) { val } }.new
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'pretty prints' do
|
224
|
+
@val.pretty_print.should =~ %r{#<Furnace::SSA::Value}
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'has an use list' do
|
228
|
+
other = SSA::Value.new
|
229
|
+
@val.should.enumerate :each_use, []
|
230
|
+
@val.should.not.be.used
|
231
|
+
@val.use_count.should == 0
|
232
|
+
|
233
|
+
@val.add_use(other)
|
234
|
+
@val.should.enumerate :each_use, [other]
|
235
|
+
@val.should.be.used
|
236
|
+
@val.use_count.should == 1
|
237
|
+
|
238
|
+
@val.remove_use(other)
|
239
|
+
@val.should.enumerate :each_use, []
|
240
|
+
@val.should.not.be.used
|
241
|
+
@val.use_count.should == 0
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'can have all of its uses replaced' do
|
245
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
246
|
+
|
247
|
+
user = SSA::User.new(@function, [val1])
|
248
|
+
|
249
|
+
val1.replace_all_uses_with(val2)
|
250
|
+
|
251
|
+
val1.should.enumerate :each_use, []
|
252
|
+
val2.should.enumerate :each_use, [user]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe SSA::Constant do
|
257
|
+
before do
|
258
|
+
@imm = SSA::Constant.new(Integer, 1)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'pretty prints' do
|
262
|
+
@imm.pretty_print.should == '^Integer 1'
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'converts to value' do
|
266
|
+
@imm.to_value.should == @imm
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'can be compared' do
|
270
|
+
@imm.should == @imm
|
271
|
+
@imm.should == SSA::Constant.new(Integer, 1)
|
272
|
+
@imm.should.not == SSA::Constant.new(Integer, 2)
|
273
|
+
@imm.should.not == SSA::Constant.new(String, 1)
|
274
|
+
@imm.should.not == 1
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'is constant' do
|
278
|
+
@imm.should.be.constant
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
describe SSA::VoidType do
|
283
|
+
it 'allows to retrieve a constant' do
|
284
|
+
const = SSA.void_value
|
285
|
+
const.should.be.constant
|
286
|
+
const.type.should.be.instance_of SSA::VoidType
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'inspects as void' do
|
290
|
+
SSA.void.inspect.should == 'void'
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'inspects as void in constants' do
|
294
|
+
SSA.void_value.inspect.should == 'void'
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe SSA::NamedValue do
|
299
|
+
it 'receives unique names' do
|
300
|
+
values = 5.times.map { SSA::NamedValue.new(@function, nil) }
|
301
|
+
values.map(&:name).uniq.count.should == values.size
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'receives unique names even if explicitly specified name conflicts' do
|
305
|
+
i1 = SSA::NamedValue.new(@function, "foo")
|
306
|
+
i2 = SSA::NamedValue.new(@function, "foo")
|
307
|
+
i2.name.should.not == i1.name
|
308
|
+
|
309
|
+
i2.name = 'foo'
|
310
|
+
i2.name.should.not == i1.name
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe SSA::Argument do
|
315
|
+
before do
|
316
|
+
@val = SSA::Argument.new(@function, nil, 'foo')
|
317
|
+
end
|
318
|
+
|
319
|
+
it 'pretty prints' do
|
320
|
+
SSA::Argument.new(@function, nil, 'baz').pretty_print.
|
321
|
+
should == '<?> %baz'
|
322
|
+
|
323
|
+
SSA::Argument.new(@function, Integer, 'bar').pretty_print.
|
324
|
+
should == '^Integer %bar'
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'converts to value' do
|
328
|
+
@val.to_value.should == @val
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'can be compared' do
|
332
|
+
@val.should == @val
|
333
|
+
@val.should.not == 1
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'compares by identity' do
|
337
|
+
@val.should.not == SSA::Argument.new(@function, nil, 'foo')
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'is not constant' do
|
341
|
+
@val.should.not.be.constant
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'has side effects' do
|
345
|
+
@val.has_side_effects?.should == true
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe SSA::User do
|
350
|
+
it 'enumerates operands' do
|
351
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
352
|
+
|
353
|
+
user = SSA::User.new(@function, [val1, val2])
|
354
|
+
user.should.enumerate :each_operand, [val1, val2]
|
355
|
+
end
|
356
|
+
|
357
|
+
it 'populates use lists' do
|
358
|
+
val = SSA::Value.new
|
359
|
+
|
360
|
+
user = SSA::User.new(@function)
|
361
|
+
val.should.enumerate :each_use, []
|
362
|
+
|
363
|
+
user.operands = [val]
|
364
|
+
val.should.enumerate :each_use, [user]
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'updates use lists' do
|
368
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
369
|
+
|
370
|
+
user = SSA::User.new(@function)
|
371
|
+
val1.should.enumerate :each_use, []
|
372
|
+
val2.should.enumerate :each_use, []
|
373
|
+
|
374
|
+
user.operands = [val1]
|
375
|
+
val1.should.enumerate :each_use, [user]
|
376
|
+
val2.should.enumerate :each_use, []
|
377
|
+
|
378
|
+
user.operands = [val2]
|
379
|
+
val1.should.enumerate :each_use, []
|
380
|
+
val2.should.enumerate :each_use, [user]
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'detaches from values' do
|
384
|
+
val = SSA::Value.new
|
385
|
+
user = SSA::User.new(@function, [val])
|
386
|
+
|
387
|
+
val.should.enumerate :each_use, [user]
|
388
|
+
user.detach
|
389
|
+
val.should.enumerate :each_use, []
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'can replace uses of values' do
|
393
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
394
|
+
|
395
|
+
user = SSA::User.new(@function, [val1])
|
396
|
+
user.replace_uses_of(val1, val2)
|
397
|
+
|
398
|
+
val1.should.enumerate :each_use, []
|
399
|
+
val2.should.enumerate :each_use, [user]
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'barfs on #replace_uses_of if the value is not used' do
|
403
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
404
|
+
|
405
|
+
user = SSA::User.new(@function, [val1])
|
406
|
+
|
407
|
+
-> { user.replace_uses_of(val2, val1) }.should.raise ArgumentError
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
describe SSA::Instruction do
|
412
|
+
it 'is not terminator' do
|
413
|
+
i = insn_noary(@basic_block)
|
414
|
+
i.should.not.be.terminator
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'does not have side effects' do
|
418
|
+
i = insn_noary(@basic_block)
|
419
|
+
i.has_side_effects?.should == false
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'removes itself from basic block' do
|
423
|
+
i = insn_noary(@basic_block)
|
424
|
+
@basic_block.append i
|
425
|
+
|
426
|
+
i.remove
|
427
|
+
@basic_block.to_a.should.be.empty
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'replaces uses of itself with instructions' do
|
431
|
+
i1 = insn_noary(@basic_block)
|
432
|
+
@basic_block.append i1
|
433
|
+
|
434
|
+
i2 = insn_unary(@basic_block, i1)
|
435
|
+
@basic_block.append i2
|
436
|
+
|
437
|
+
i1a = insn_noary(@basic_block)
|
438
|
+
i1.replace_with i1a
|
439
|
+
|
440
|
+
@basic_block.to_a.should == [i1a, i2]
|
441
|
+
i2.operands.should == [i1a]
|
442
|
+
end
|
443
|
+
|
444
|
+
it 'replaces uses of itself with constants' do
|
445
|
+
i1 = insn_noary(@basic_block)
|
446
|
+
@basic_block.append i1
|
447
|
+
|
448
|
+
i2 = insn_unary(@basic_block, i1)
|
449
|
+
@basic_block.append i2
|
450
|
+
|
451
|
+
c1 = SSA::Constant.new(Integer, 1)
|
452
|
+
i1.replace_with c1
|
453
|
+
|
454
|
+
@basic_block.to_a.should == [i2]
|
455
|
+
i2.operands.should == [c1]
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'pretty prints' do
|
459
|
+
dup = DupInsn.new(@basic_block, [SSA::Constant.new(Integer, 1)])
|
460
|
+
dup.pretty_print.should == '^Integer %2 = dup ^Integer 1'
|
461
|
+
dup.inspect_as_value.should == '%2'
|
462
|
+
|
463
|
+
concat = TupleConcatInsn.new(@basic_block,
|
464
|
+
[SSA::Constant.new(Array, [1]), SSA::Constant.new(Array, [2,3])])
|
465
|
+
concat.pretty_print.should == '^Array %3 = tuple_concat ^Array [1], ^Array [2, 3]'
|
466
|
+
concat.inspect_as_value.should == '%3'
|
467
|
+
|
468
|
+
zero_arity = BindingInsn.new(@basic_block)
|
469
|
+
zero_arity.pretty_print.should == '^Binding %4 = binding'
|
470
|
+
zero_arity.inspect_as_value.should == '%4'
|
471
|
+
|
472
|
+
zero_all = TestScope::NestedInsn.new(@basic_block)
|
473
|
+
zero_all.pretty_print.should == 'nested'
|
474
|
+
zero_all.inspect_as_value.should == 'void'
|
475
|
+
end
|
476
|
+
|
477
|
+
describe SSA::GenericInstruction do
|
478
|
+
it 'has settable type' do
|
479
|
+
i = GenericInsn.new(@basic_block, Integer, [])
|
480
|
+
i.pretty_print.should =~ /\^Integer %\d+ = generic/
|
481
|
+
i.type = Binding
|
482
|
+
i.pretty_print.should =~ /\^Binding %\d+ = generic/
|
483
|
+
end
|
484
|
+
|
485
|
+
describe SSA::PhiInsn do
|
486
|
+
it 'accepts operand hash' do
|
487
|
+
-> {
|
488
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
489
|
+
{ @basic_block => SSA::Constant.new(Integer, 1) })
|
490
|
+
}.should.not.raise
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'enumerates operands' do
|
494
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
495
|
+
bb1, bb2 = 2.times.map { SSA::BasicBlock.new(@function) }
|
496
|
+
|
497
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
498
|
+
{ bb1 => val1, bb2 => val2 })
|
499
|
+
phi.should.enumerate :each_operand, [val1, val2, bb1, bb2]
|
500
|
+
end
|
501
|
+
|
502
|
+
it 'pretty prints' do
|
503
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
504
|
+
{ @basic_block => SSA::Constant.new(Integer, 1) })
|
505
|
+
phi.pretty_print.should =~
|
506
|
+
/<?> %\d = phi %\d => \^Integer 1/
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'maintains use chains' do
|
510
|
+
val = SSA::Value.new
|
511
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
512
|
+
{ @basic_block => val })
|
513
|
+
val.should.enumerate :each_use, [phi]
|
514
|
+
@basic_block.should.enumerate :each_use, [phi]
|
515
|
+
end
|
516
|
+
|
517
|
+
it 'can replace uses of values' do
|
518
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
519
|
+
|
520
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
521
|
+
{ @basic_block => val1 })
|
522
|
+
phi.replace_uses_of(val1, val2)
|
523
|
+
|
524
|
+
val1.should.enumerate :each_use, []
|
525
|
+
val2.should.enumerate :each_use, [phi]
|
526
|
+
end
|
527
|
+
|
528
|
+
it 'can replace uses of basic blocks' do
|
529
|
+
val = SSA::Value.new
|
530
|
+
bb2 = SSA::BasicBlock.new(@function)
|
531
|
+
|
532
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
533
|
+
{ @basic_block => val })
|
534
|
+
phi.replace_uses_of(@basic_block, bb2)
|
535
|
+
|
536
|
+
phi.operands.should == { bb2 => val }
|
537
|
+
@basic_block.should.enumerate :each_use, []
|
538
|
+
bb2.should.enumerate :each_use, [phi]
|
539
|
+
end
|
540
|
+
|
541
|
+
it 'barfs on #replace_uses_of if the value is not used' do
|
542
|
+
val1, val2 = 2.times.map { SSA::Value.new }
|
543
|
+
|
544
|
+
phi = SSA::PhiInsn.new(@basic_block, nil,
|
545
|
+
{ @basic_block => val1 })
|
546
|
+
|
547
|
+
-> { phi.replace_uses_of(val2, val1) }.should.raise ArgumentError
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
describe SSA::TerminatorInstruction do
|
553
|
+
it 'is a terminator' do
|
554
|
+
i = SSA::TerminatorInstruction.new(@basic_block, [])
|
555
|
+
i.should.be.terminator
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'has side effects' do
|
559
|
+
i = SSA::TerminatorInstruction.new(@basic_block, [])
|
560
|
+
i.has_side_effects?.should == true
|
561
|
+
end
|
562
|
+
|
563
|
+
it 'requires to implement #exits?' do
|
564
|
+
i = SSA::TerminatorInstruction.new(@basic_block, [])
|
565
|
+
-> { i.exits? }.should.raise NotImplementedError
|
566
|
+
end
|
567
|
+
|
568
|
+
describe SSA::BranchInsn do
|
569
|
+
it 'does not exit the method' do
|
570
|
+
i = SSA::BranchInsn.new(@basic_block, [@basic_block])
|
571
|
+
i.exits?.should == false
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
describe SSA::ReturnInsn do
|
576
|
+
it 'exits the method' do
|
577
|
+
i = SSA::ReturnInsn.new(@basic_block, [SSA.void_value])
|
578
|
+
i.exits?.should == true
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
describe SSA::BasicBlock do
|
585
|
+
it 'converts to value' do
|
586
|
+
@basic_block.to_value.should == @basic_block
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'has the type of BasicBlock' do
|
590
|
+
@basic_block.type.should == SSA::BasicBlock.to_type
|
591
|
+
end
|
592
|
+
|
593
|
+
it 'pretty prints' do
|
594
|
+
@basic_block.append insn_noary(@basic_block)
|
595
|
+
@basic_block.append insn_noary(@basic_block)
|
596
|
+
@basic_block.pretty_print.should ==
|
597
|
+
"1:\n ^Binding %2 = binding\n ^Binding %3 = binding\n"
|
598
|
+
end
|
599
|
+
|
600
|
+
it 'inspects as value' do
|
601
|
+
@basic_block.inspect_as_value.should == 'label %1'
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'is constant' do
|
605
|
+
@basic_block.constant?.should == true
|
606
|
+
end
|
607
|
+
|
608
|
+
it 'can append instructions' do
|
609
|
+
i1, i2 = 2.times.map { insn_noary(@basic_block) }
|
610
|
+
@basic_block.append i1
|
611
|
+
@basic_block.append i2
|
612
|
+
@basic_block.to_a.should == [i1, i2]
|
613
|
+
end
|
614
|
+
|
615
|
+
it 'can insert instructions' do
|
616
|
+
i1, i2, i3 = 3.times.map { insn_noary(@basic_block) }
|
617
|
+
@basic_block.append i1
|
618
|
+
-> { @basic_block.insert i3, i2 }.should.raise ArgumentError, %r|is not found|
|
619
|
+
@basic_block.append i3
|
620
|
+
@basic_block.insert i3, i2
|
621
|
+
@basic_block.to_a.should == [i1, i2, i3]
|
622
|
+
end
|
623
|
+
|
624
|
+
it 'is not affected by changes to #to_a value' do
|
625
|
+
i1 = insn_noary(@basic_block)
|
626
|
+
@basic_block.append i1
|
627
|
+
@basic_block.to_a.clear
|
628
|
+
@basic_block.to_a.size.should == 1
|
629
|
+
end
|
630
|
+
|
631
|
+
it 'enumerates instructions' do
|
632
|
+
i1 = insn_noary(@basic_block)
|
633
|
+
@basic_block.append i1
|
634
|
+
@basic_block.should.enumerate :each, [i1]
|
635
|
+
end
|
636
|
+
|
637
|
+
it 'enumerates instructions by type' do
|
638
|
+
i1 = BindingInsn.new(@basic_block)
|
639
|
+
@basic_block.append i1
|
640
|
+
|
641
|
+
i2 = GenericInsn.new(@basic_block)
|
642
|
+
@basic_block.append i2
|
643
|
+
|
644
|
+
@basic_block.each(BindingInsn).should.enumerate :each, [i1]
|
645
|
+
end
|
646
|
+
|
647
|
+
it 'can check for presence of instructions' do
|
648
|
+
i1, i2 = 2.times.map { insn_noary(@basic_block) }
|
649
|
+
@basic_block.append i1
|
650
|
+
@basic_block.should.include i1
|
651
|
+
@basic_block.should.not.include i2
|
652
|
+
end
|
653
|
+
|
654
|
+
it 'can remove instructions' do
|
655
|
+
i1, i2 = 2.times.map { insn_noary(@basic_block) }
|
656
|
+
@basic_block.append i1
|
657
|
+
@basic_block.append i2
|
658
|
+
@basic_block.remove i1
|
659
|
+
@basic_block.to_a.should == [i2]
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'can replace instructions' do
|
663
|
+
i1, i2, i3, i4 = 4.times.map { insn_noary(@basic_block) }
|
664
|
+
@basic_block.append i1
|
665
|
+
@basic_block.append i2
|
666
|
+
@basic_block.append i3
|
667
|
+
@basic_block.replace i2, i4
|
668
|
+
@basic_block.to_a.should == [i1, i4, i3]
|
669
|
+
end
|
670
|
+
|
671
|
+
describe 'with other blocks' do
|
672
|
+
before do
|
673
|
+
@branch_bb = @basic_block
|
674
|
+
@branch_bb.name = 'branch'
|
675
|
+
|
676
|
+
@body_bb = SSA::BasicBlock.new(@function, 'body')
|
677
|
+
@function.add @body_bb
|
678
|
+
|
679
|
+
@ret_bb = SSA::BasicBlock.new(@function, 'ret')
|
680
|
+
@function.add @ret_bb
|
681
|
+
|
682
|
+
@cond = DupInsn.new(@branch_bb,
|
683
|
+
[ SSA::Constant.new(TrueClass, true) ])
|
684
|
+
@branch_bb.append @cond
|
685
|
+
|
686
|
+
@cond_br = CondBranchInsn.new(@branch_bb,
|
687
|
+
[ @cond,
|
688
|
+
@body_bb,
|
689
|
+
@ret_bb ])
|
690
|
+
@branch_bb.append @cond_br
|
691
|
+
|
692
|
+
@uncond_br = SSA::BranchInsn.new(@body_bb,
|
693
|
+
[ @ret_bb ])
|
694
|
+
@body_bb.append @uncond_br
|
695
|
+
|
696
|
+
@ret = SSA::ReturnInsn.new(@ret_bb,
|
697
|
+
[ SSA.void_value ])
|
698
|
+
@ret_bb.append @ret
|
699
|
+
end
|
700
|
+
|
701
|
+
it 'can determine terminator' do
|
702
|
+
@branch_bb.terminator.should == @cond_br
|
703
|
+
@body_bb.terminator.should == @uncond_br
|
704
|
+
@ret_bb.terminator.should == @ret
|
705
|
+
end
|
706
|
+
|
707
|
+
it 'can determine successors' do
|
708
|
+
@branch_bb.successors.should.enumerate :each, [@body_bb, @ret_bb]
|
709
|
+
@body_bb.successors.should.enumerate :each, [@ret_bb]
|
710
|
+
@ret_bb.successors.should.enumerate :each, []
|
711
|
+
end
|
712
|
+
|
713
|
+
it 'can determine predecessors' do
|
714
|
+
@branch_bb.predecessors.should.enumerate :each, []
|
715
|
+
@body_bb.predecessors.should.enumerate :each, [@branch_bb]
|
716
|
+
@ret_bb.predecessors.should.enumerate :each, [@branch_bb, @body_bb]
|
717
|
+
end
|
718
|
+
|
719
|
+
it 'can determine predecessor names' do
|
720
|
+
@ret_bb.predecessor_names.should.enumerate :each, %w(branch body)
|
721
|
+
end
|
722
|
+
|
723
|
+
it 'can determine if it is an exit block' do
|
724
|
+
@branch_bb.exits?.should == false
|
725
|
+
@body_bb.exits?.should == false
|
726
|
+
@ret_bb.exits?.should == true
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
describe SSA::Function do
|
732
|
+
it 'converts to value' do
|
733
|
+
@function.to_value.should ==
|
734
|
+
SSA::Constant.new(SSA::Function, @function.name)
|
735
|
+
|
736
|
+
@function.name = 'foo'
|
737
|
+
@function.to_value.inspect_as_value.should ==
|
738
|
+
'function "foo"'
|
739
|
+
end
|
740
|
+
|
741
|
+
it 'converts to type' do
|
742
|
+
SSA::Function.to_type.should == SSA::FunctionType.instance
|
743
|
+
end
|
744
|
+
|
745
|
+
it 'generates numeric names in #make_name(nil)' do
|
746
|
+
@function.make_name.should =~ /^\d+$/
|
747
|
+
end
|
748
|
+
|
749
|
+
it 'appends numeric suffixes in #make_name(String) if needed' do
|
750
|
+
@function.make_name("foobar.i").should =~ /^foobar\.i$/
|
751
|
+
@function.make_name("foobar.i").should =~ /^foobar\.i\.\d+$/
|
752
|
+
@function.make_name("foobar.i").should =~ /^foobar\.i\.\d+$/
|
753
|
+
end
|
754
|
+
|
755
|
+
it 'finds blocks or raises an exception' do
|
756
|
+
@function.find('1').should == @basic_block
|
757
|
+
-> { @function.find('foobar') }.should.raise ArgumentError, %r|Cannot find|
|
758
|
+
end
|
759
|
+
|
760
|
+
it 'checks blocks for presence' do
|
761
|
+
@function.should.include '1'
|
762
|
+
@function.should.not.include 'foobar'
|
763
|
+
end
|
764
|
+
|
765
|
+
it 'removes blocks' do
|
766
|
+
@function.remove @basic_block
|
767
|
+
@function.should.not.include '1'
|
768
|
+
end
|
769
|
+
|
770
|
+
it 'iterates each instruction in each block' do
|
771
|
+
bb2 = SSA::BasicBlock.new(@function)
|
772
|
+
@function.add bb2
|
773
|
+
|
774
|
+
i1 = insn_noary(@basic_block)
|
775
|
+
@basic_block.append i1
|
776
|
+
|
777
|
+
i2 = insn_unary(@basic_block, i1)
|
778
|
+
bb2.append i2
|
779
|
+
|
780
|
+
@function.should.enumerate :each_instruction, [i1, i2]
|
781
|
+
end
|
782
|
+
|
783
|
+
it 'pretty prints' do
|
784
|
+
@function.name = 'foo'
|
785
|
+
@function.arguments = [
|
786
|
+
SSA::Argument.new(@function, Integer, 'count'),
|
787
|
+
SSA::Argument.new(@function, Binding, 'outer')
|
788
|
+
]
|
789
|
+
|
790
|
+
@basic_block.append insn_binary(@basic_block, *@function.arguments)
|
791
|
+
|
792
|
+
bb2 = SSA::BasicBlock.new(@function, 'foo')
|
793
|
+
@function.add bb2
|
794
|
+
bb2.append insn_unary(@basic_block, SSA::Constant.new(Integer, 1))
|
795
|
+
|
796
|
+
@function.pretty_print.should == <<-END
|
797
|
+
function void foo( ^Integer %count, ^Binding %outer ) {
|
798
|
+
1:
|
799
|
+
^Array %2 = tuple_concat %count, %outer
|
800
|
+
|
801
|
+
foo:
|
802
|
+
^Integer %3 = dup ^Integer 1
|
803
|
+
|
804
|
+
}
|
805
|
+
END
|
806
|
+
end
|
807
|
+
|
808
|
+
it 'duplicates all its content' do
|
809
|
+
@function.name = 'foo;1'
|
810
|
+
@function.arguments = [
|
811
|
+
SSA::Argument.new(@function, Integer, 'count'),
|
812
|
+
]
|
813
|
+
|
814
|
+
f1a1 = @function.arguments.first
|
815
|
+
|
816
|
+
f1bb1 = @function.entry
|
817
|
+
f1bb1.name = 'bb1'
|
818
|
+
|
819
|
+
f1bb2 = SSA::BasicBlock.new(@function, 'bb2')
|
820
|
+
@function.add f1bb2
|
821
|
+
|
822
|
+
f1i1 = insn_unary(@basic_block, f1a1)
|
823
|
+
@basic_block.append f1i1
|
824
|
+
|
825
|
+
f1c1 = SSA::Constant.new(Array, [1])
|
826
|
+
f1i2 = insn_binary(@basic_block, f1i1, f1c1)
|
827
|
+
f1bb2.append f1i2
|
828
|
+
|
829
|
+
f1bb3 = SSA::BasicBlock.new(@function, 'bb3')
|
830
|
+
@function.add f1bb3
|
831
|
+
|
832
|
+
f1phi = SSA::PhiInsn.new(f1bb3, nil,
|
833
|
+
{ f1bb1 => f1i1, f1bb2 => f1i2 })
|
834
|
+
f1bb3.append f1phi
|
835
|
+
|
836
|
+
f1 = @function
|
837
|
+
f2 = @function.dup
|
838
|
+
|
839
|
+
(f1.arguments & f2.arguments).should.be.empty
|
840
|
+
(f1.each.to_a & f2.each.to_a).should.be.empty
|
841
|
+
(f1.each_instruction.to_a & f2.each_instruction.to_a).should.be.empty
|
842
|
+
f2.original_name.should == f1.original_name
|
843
|
+
f2.name.should == f1.original_name
|
844
|
+
|
845
|
+
f1.entry.should.not == f2.entry
|
846
|
+
|
847
|
+
f2a1 = f2.arguments.first
|
848
|
+
|
849
|
+
f2bb1 = f2.find 'bb1'
|
850
|
+
f2i1 = f2bb1.to_a.first
|
851
|
+
f2bb2 = f2.find 'bb2'
|
852
|
+
f2i2 = f2bb2.to_a.first
|
853
|
+
f2bb3 = f2.find 'bb3'
|
854
|
+
f2phi = f2bb3.to_a.first
|
855
|
+
|
856
|
+
f2.entry.should == f2bb1
|
857
|
+
f2i1.operands.should == [f2a1]
|
858
|
+
f2a1.should.enumerate :each_use, [f2i1]
|
859
|
+
f2i2.operands.should == [f2i1, f1c1]
|
860
|
+
f2i1.should.enumerate :each_use, [f2i2, f2phi]
|
861
|
+
|
862
|
+
f1a1.should.enumerate :each_use, [f1i1]
|
863
|
+
f1i1.should.enumerate :each_use, [f1i2, f1phi]
|
864
|
+
|
865
|
+
f2.arguments.each do |arg|
|
866
|
+
arg.function.should == f2
|
867
|
+
end
|
868
|
+
|
869
|
+
f2.each_instruction do |insn|
|
870
|
+
insn.function.should == f2
|
871
|
+
f2.each.to_a.should.include insn.basic_block
|
872
|
+
end
|
873
|
+
|
874
|
+
f2.name = f1.name
|
875
|
+
f2.pretty_print.to_s.should == f1.pretty_print.to_s
|
876
|
+
end
|
877
|
+
end
|
878
|
+
|
879
|
+
describe SSA::Builder do
|
880
|
+
before do
|
881
|
+
@b = TestBuilder.new('foo',
|
882
|
+
[ [Integer, 'bar'], [Binding, 'baz'] ],
|
883
|
+
Float)
|
884
|
+
@f = @b.function
|
885
|
+
end
|
886
|
+
|
887
|
+
it 'has SSA as default scope' do
|
888
|
+
SSA::Builder.scope.should == ::Furnace::SSA
|
889
|
+
end
|
890
|
+
|
891
|
+
it 'correctly sets function attributes' do
|
892
|
+
@f.name.should == 'foo'
|
893
|
+
@f.arguments.each do |arg|
|
894
|
+
arg.function.should == @f
|
895
|
+
end
|
896
|
+
bar, = @f.arguments
|
897
|
+
bar.type.should == Integer.to_type
|
898
|
+
bar.name.should == 'bar'
|
899
|
+
@f.return_type.should == Float
|
900
|
+
|
901
|
+
bb = @f.find('1')
|
902
|
+
@f.entry.should == bb
|
903
|
+
end
|
904
|
+
|
905
|
+
it 'appends instructions' do
|
906
|
+
i1 = @b.append :binding
|
907
|
+
i2 = @b.append :nested
|
908
|
+
i1.should.be.instance_of BindingInsn
|
909
|
+
i2.should.be.instance_of TestScope::NestedInsn
|
910
|
+
end
|
911
|
+
|
912
|
+
it 'dispatches through method_missing' do
|
913
|
+
i1 = @b.binding
|
914
|
+
i1.should.be.instance_of BindingInsn
|
915
|
+
|
916
|
+
-> { @b.nonexistent }.should.raise NoMethodError
|
917
|
+
end
|
918
|
+
|
919
|
+
it 'builds ReturnInsn' do
|
920
|
+
@b.return SSA.void_value
|
921
|
+
i, = @b.block.to_a
|
922
|
+
i.should.be.instance_of SSA::ReturnInsn
|
923
|
+
i.operands.should == [SSA.void_value]
|
924
|
+
end
|
925
|
+
end
|
926
|
+
|
927
|
+
describe SSA::InstructionSyntax do
|
928
|
+
class SyntaxUntypedInsn < SSA::Instruction
|
929
|
+
syntax do |s|
|
930
|
+
s.operand :foo
|
931
|
+
s.operand :bar
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
class SyntaxTypedInsn < SSA::Instruction
|
936
|
+
syntax do |s|
|
937
|
+
s.operand :foo, Integer
|
938
|
+
end
|
939
|
+
end
|
940
|
+
|
941
|
+
class SyntaxSplatInsn < SSA::Instruction
|
942
|
+
syntax do |s|
|
943
|
+
s.operand :foo
|
944
|
+
s.splat :bars
|
945
|
+
end
|
946
|
+
end
|
947
|
+
|
948
|
+
before do
|
949
|
+
@iconst = SSA::Constant.new(Integer, 1)
|
950
|
+
@fconst = SSA::Constant.new(Float, 1.0)
|
951
|
+
@iinsn = DupInsn.new(@basic_block, [ @iconst ])
|
952
|
+
end
|
953
|
+
|
954
|
+
it 'accepts operands and decomposes them' do
|
955
|
+
i = SyntaxUntypedInsn.new(@basic_block, [ @iconst, @fconst ])
|
956
|
+
i.foo.should == @iconst
|
957
|
+
i.bar.should == @fconst
|
958
|
+
end
|
959
|
+
|
960
|
+
it 'allows to change operands through accessors' do
|
961
|
+
i = SyntaxUntypedInsn.new(@basic_block, [ @iconst, @fconst ])
|
962
|
+
i.foo = @iinsn
|
963
|
+
i.operands.should == [@iinsn, @fconst]
|
964
|
+
end
|
965
|
+
|
966
|
+
it 'does not accept wrong amount of operands' do
|
967
|
+
-> { SyntaxUntypedInsn.new(@basic_block, [ @iconst ]) }.
|
968
|
+
should.raise ArgumentError
|
969
|
+
-> { SyntaxUntypedInsn.new(@basic_block, [ @iconst, @iconst, @iconst ]) }.
|
970
|
+
should.raise ArgumentError
|
971
|
+
end
|
972
|
+
|
973
|
+
it 'accepts only correct typed operands' do
|
974
|
+
-> { SyntaxTypedInsn.new(@basic_block, [ @fconst ]) }.
|
975
|
+
should.raise TypeError
|
976
|
+
-> { SyntaxTypedInsn.new(@basic_block, [ @iconst ]) }.
|
977
|
+
should.not.raise
|
978
|
+
-> { SyntaxTypedInsn.new(@basic_block, [ @iinsn ]) }.
|
979
|
+
should.not.raise
|
980
|
+
end
|
981
|
+
|
982
|
+
it 'accepts splat' do
|
983
|
+
i = SyntaxSplatInsn.new(@basic_block, [ @iconst, @fconst, @iinsn ])
|
984
|
+
i.foo.should == @iconst
|
985
|
+
i.bars.should == [@fconst, @iinsn]
|
986
|
+
end
|
987
|
+
|
988
|
+
it 'does not accept wrong amount of operands with splat' do
|
989
|
+
-> { SyntaxSplatInsn.new(@basic_block, []) }.
|
990
|
+
should.raise ArgumentError
|
991
|
+
end
|
992
|
+
|
993
|
+
it 'only permits one last splat' do
|
994
|
+
-> {
|
995
|
+
Class.new(SSA::Instruction) {
|
996
|
+
syntax do |s|
|
997
|
+
s.splat :bars
|
998
|
+
s.operand :foo
|
999
|
+
end
|
1000
|
+
}
|
1001
|
+
}.should.raise ArgumentError
|
1002
|
+
|
1003
|
+
-> {
|
1004
|
+
Class.new(SSA::Instruction) {
|
1005
|
+
syntax do |s|
|
1006
|
+
s.splat :bars
|
1007
|
+
s.splat :foos
|
1008
|
+
end
|
1009
|
+
}
|
1010
|
+
}.should.raise ArgumentError
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
it 'allows to update splat' do
|
1014
|
+
i = SyntaxSplatInsn.new(@basic_block, [ @iconst, @fconst, @iinsn ])
|
1015
|
+
i.bars = [@iinsn, @fconst]
|
1016
|
+
i.bars.should == [@iinsn, @fconst]
|
1017
|
+
i.operands.should == [@iconst, @iinsn, @fconst]
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
it 'allows to inquire status' do
|
1021
|
+
i = SyntaxTypedInsn.new(@basic_block, [ @iconst ])
|
1022
|
+
i.should.be.valid
|
1023
|
+
i.foo = @fconst
|
1024
|
+
i.should.not.be.valid
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
it 'highlights invalid insns when pretty printing' do
|
1028
|
+
i = SyntaxTypedInsn.new(@basic_block, [ @iconst ])
|
1029
|
+
i.foo = @fconst
|
1030
|
+
i.pretty_print.should =~ /!syntax_typed/
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
it 'allows to treat nil type as error' do
|
1034
|
+
phi = SSA::PhiInsn.new(@basic_block, nil)
|
1035
|
+
|
1036
|
+
i = SyntaxTypedInsn.new(@basic_block, [ @iconst ])
|
1037
|
+
i.should.be.valid(true)
|
1038
|
+
i.should.be.valid(false)
|
1039
|
+
-> { i.verify!(false) }.should.not.raise
|
1040
|
+
|
1041
|
+
i.foo = phi
|
1042
|
+
i.should.be.valid(true)
|
1043
|
+
i.should.not.be.valid(false)
|
1044
|
+
-> { i.verify!(false) }.should.raise TypeError, %r|<?>|
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
it 'does not interfere with def-use tracking' do
|
1048
|
+
i = SyntaxUntypedInsn.new(@basic_block, [ @iconst, @fconst ])
|
1049
|
+
@fconst.should.enumerate :each_use, [ i ]
|
1050
|
+
|
1051
|
+
i.bar = @iinsn
|
1052
|
+
@fconst.should.enumerate :each_use, []
|
1053
|
+
@iinsn.should.enumerate :each_use, [ i ]
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
it 'does not break on replace_uses_of' do
|
1057
|
+
i = SyntaxUntypedInsn.new(@basic_block, [ @iconst, @fconst ])
|
1058
|
+
i.replace_uses_of @iconst, @fconst
|
1059
|
+
i.foo.should == @fconst
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
describe SSA::Module do
|
1064
|
+
before do
|
1065
|
+
@module = SSA::Module.new
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
it 'adds named functions' do
|
1069
|
+
f = SSA::Function.new('foo')
|
1070
|
+
@module.add f
|
1071
|
+
@module.to_a.should == [f]
|
1072
|
+
f.name.should == 'foo'
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
it 'adds unnamed functions and names them' do
|
1076
|
+
f = SSA::Function.new
|
1077
|
+
@module.add f
|
1078
|
+
f.name.should.not == nil
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
it 'adds named functions with explicit prefix' do
|
1082
|
+
f = SSA::Function.new('foo')
|
1083
|
+
@module.add f, 'bar'
|
1084
|
+
f.name.should == 'bar;1'
|
1085
|
+
f.original_name.should == 'foo'
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
it 'automatically renames functions with duplicate names' do
|
1089
|
+
f1 = SSA::Function.new('foo')
|
1090
|
+
@module.add f1
|
1091
|
+
f1.name.should == 'foo'
|
1092
|
+
f1.original_name.should == 'foo'
|
1093
|
+
|
1094
|
+
f2 = SSA::Function.new('foo')
|
1095
|
+
@module.add f2
|
1096
|
+
f2.name.should == 'foo;1'
|
1097
|
+
f2.original_name.should == 'foo'
|
1098
|
+
|
1099
|
+
f3 = SSA::Function.new('foo;1')
|
1100
|
+
@module.add f3
|
1101
|
+
f3.name.should == 'foo;2'
|
1102
|
+
f3.original_name.should == 'foo;1'
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
it 'retrieves functions' do
|
1106
|
+
f1 = SSA::Function.new('foo')
|
1107
|
+
@module.add f1
|
1108
|
+
@module['foo'].should == f1
|
1109
|
+
-> { @module['bar'] }.should.raise ArgumentError
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
it 'enumerates functions' do
|
1113
|
+
f1 = SSA::Function.new('foo')
|
1114
|
+
@module.add f1
|
1115
|
+
f2 = SSA::Function.new('bar')
|
1116
|
+
@module.add f2
|
1117
|
+
|
1118
|
+
@module.should.enumerate :each, [f1, f2]
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
it 'removes functions' do
|
1122
|
+
f1 = SSA::Function.new('foo')
|
1123
|
+
@module.add f1
|
1124
|
+
@module.should.include 'foo'
|
1125
|
+
@module.remove 'foo'
|
1126
|
+
@module.should.not.include 'foo'
|
1127
|
+
end
|
1128
|
+
end
|
1129
|
+
end
|