furnace 0.3.1 → 0.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +4 -0
  3. data/Gemfile +1 -2
  4. data/{LICENSE → LICENSE.MIT} +14 -14
  5. data/Rakefile +1 -5
  6. data/furnace.gemspec +7 -3
  7. data/lib/furnace/ast/node.rb +14 -0
  8. data/lib/furnace/ast/processor.rb +7 -7
  9. data/lib/furnace/ssa/argument.rb +23 -0
  10. data/lib/furnace/ssa/basic_block.rb +117 -0
  11. data/lib/furnace/ssa/builder.rb +100 -0
  12. data/lib/furnace/ssa/constant.rb +43 -0
  13. data/lib/furnace/ssa/function.rb +191 -0
  14. data/lib/furnace/ssa/generic_instruction.rb +14 -0
  15. data/lib/furnace/ssa/generic_type.rb +16 -0
  16. data/lib/furnace/ssa/instruction.rb +92 -0
  17. data/lib/furnace/ssa/instruction_syntax.rb +109 -0
  18. data/lib/furnace/ssa/instructions/branch.rb +11 -0
  19. data/lib/furnace/ssa/instructions/phi.rb +63 -0
  20. data/lib/furnace/ssa/instructions/return.rb +11 -0
  21. data/lib/furnace/ssa/module.rb +52 -0
  22. data/lib/furnace/ssa/named_value.rb +25 -0
  23. data/lib/furnace/ssa/pretty_printer.rb +113 -0
  24. data/lib/furnace/ssa/terminator_instruction.rb +24 -0
  25. data/lib/furnace/ssa/type.rb +27 -0
  26. data/lib/furnace/ssa/types/basic_block.rb +11 -0
  27. data/lib/furnace/ssa/types/function.rb +11 -0
  28. data/lib/furnace/ssa/types/void.rb +15 -0
  29. data/lib/furnace/ssa/user.rb +84 -0
  30. data/lib/furnace/ssa/value.rb +62 -0
  31. data/lib/furnace/ssa.rb +66 -0
  32. data/lib/furnace/transform/iterative.rb +27 -0
  33. data/lib/furnace/transform/pipeline.rb +3 -3
  34. data/lib/furnace/version.rb +1 -1
  35. data/lib/furnace.rb +3 -3
  36. data/test/ast_test.rb +32 -3
  37. data/test/ssa_test.rb +1129 -0
  38. data/test/test_helper.rb +17 -28
  39. data/test/transform_test.rb +74 -0
  40. metadata +136 -58
  41. data/lib/furnace/cfg/algorithms.rb +0 -193
  42. data/lib/furnace/cfg/graph.rb +0 -99
  43. data/lib/furnace/cfg/node.rb +0 -78
  44. data/lib/furnace/cfg.rb +0 -7
  45. 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