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.
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