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
@@ -0,0 +1,92 @@
1
+ module Furnace
2
+ class SSA::Instruction < SSA::User
3
+ def self.opcode
4
+ @opcode ||= SSA.class_name_to_opcode(self)
5
+ end
6
+
7
+ def self.syntax(&block)
8
+ SSA::InstructionSyntax.new(self).evaluate(&block)
9
+ end
10
+
11
+ attr_accessor :basic_block
12
+
13
+ def initialize(basic_block, operands=[], name=nil)
14
+ super(basic_block.function, operands, name)
15
+ @basic_block = basic_block
16
+ end
17
+
18
+ def initialize_copy(original)
19
+ super
20
+
21
+ @operands = nil
22
+ end
23
+
24
+ def opcode
25
+ self.class.opcode
26
+ end
27
+
28
+ def remove
29
+ @basic_block.remove self
30
+ detach
31
+ end
32
+
33
+ def replace_with(value)
34
+ replace_all_uses_with value
35
+
36
+ if value.is_a? SSA::Instruction
37
+ @basic_block.replace self, value
38
+ detach
39
+ else
40
+ remove
41
+ end
42
+ end
43
+
44
+ def has_side_effects?
45
+ false
46
+ end
47
+
48
+ def terminator?
49
+ false
50
+ end
51
+
52
+ def pretty_print(p=SSA::PrettyPrinter.new)
53
+ unless type == SSA.void
54
+ p.type type
55
+ p.name name
56
+ p.text '='
57
+ end
58
+
59
+ if valid?
60
+ p.keyword opcode
61
+ else
62
+ p.keyword_invalid opcode
63
+ end
64
+
65
+ pretty_parameters(p)
66
+ pretty_operands(p)
67
+
68
+ p
69
+ end
70
+
71
+ def inspect_as_value(p=SSA::PrettyPrinter.new)
72
+ if type == SSA.void
73
+ p.type type
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ def pretty_parameters(p)
82
+ end
83
+
84
+ def pretty_operands(p)
85
+ if @operands
86
+ p.values @operands
87
+ else
88
+ p.text '<DETACHED>'
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,109 @@
1
+ module Furnace
2
+ class SSA::InstructionSyntax
3
+ def initialize(klass)
4
+ @klass = klass
5
+ @operands = {}
6
+ @splat = nil
7
+ end
8
+
9
+ def evaluate
10
+ yield self
11
+
12
+ codegen
13
+ end
14
+
15
+ def operand(name, type=nil)
16
+ check_for_splat
17
+
18
+ type = type.to_type unless type.nil?
19
+ @operands[name.to_sym] = type
20
+ end
21
+
22
+ def splat(name)
23
+ check_for_splat
24
+
25
+ @splat = name.to_sym
26
+ end
27
+
28
+ protected
29
+
30
+ def check_for_splat
31
+ if @splat
32
+ raise ArgumentError, "There should be at most one splat operand in tail position"
33
+ end
34
+ end
35
+
36
+ def codegen
37
+ operands, splat = @operands, @splat
38
+
39
+ @klass.class_eval do
40
+ operands.each_with_index do |(operand, type), index|
41
+ define_method(operand) do
42
+ @operands[index]
43
+ end
44
+
45
+ define_method(:"#{operand}=") do |value|
46
+ value = value.to_value
47
+
48
+ @operands[index].remove_use self if @operands[index]
49
+ @operands[index] = value
50
+ value.add_use self if value
51
+
52
+ value
53
+ end
54
+ end
55
+
56
+ if splat
57
+ define_method splat do
58
+ @operands[operands.size..-1]
59
+ end
60
+
61
+ define_method(:"#{splat}=") do |values|
62
+ values = values.map(&:to_value)
63
+
64
+ update_use_lists do
65
+ @operands[operands.size, @operands.size - operands.size] = values
66
+ end
67
+
68
+ values
69
+ end
70
+ end
71
+
72
+ define_method(:operands=) do |values|
73
+ if splat && values.size < operands.size
74
+ raise ArgumentError, "Not enough operands provided: #{values.size} for #{operands.size}"
75
+ elsif !splat && values.size != operands.size
76
+ raise ArgumentError, "Incorrect number of operands provided: #{values.size} for #{operands.size}"
77
+ end
78
+
79
+ values = values.map(&:to_value)
80
+
81
+ update_use_lists do
82
+ @operands = values
83
+ end
84
+
85
+ verify!
86
+
87
+ values
88
+ end
89
+
90
+ define_method(:verify!) do |ignore_nil_types=true|
91
+ return if @operands.nil?
92
+
93
+ operands.each_with_index do |(operand, type), index|
94
+ next if type.nil?
95
+
96
+ value = send operand
97
+ next if ignore_nil_types && value.type.nil?
98
+
99
+ if value.type.nil? || !value.type.subtype_of?(type)
100
+ raise TypeError, "Wrong type for operand ##{index + 1} `#{operand}': #{SSA.inspect_type type} is expected, #{SSA.inspect_type value.type} is present"
101
+ end
102
+ end
103
+
104
+ nil
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,11 @@
1
+ module Furnace
2
+ class SSA::BranchInsn < SSA::TerminatorInstruction
3
+ syntax do |s|
4
+ s.operand :target, SSA::BasicBlock
5
+ end
6
+
7
+ def exits?
8
+ false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,63 @@
1
+ module Furnace
2
+ class SSA::PhiInsn < SSA::GenericInstruction
3
+ def initialize(basic_block, type, operands={}, name=nil)
4
+ super(basic_block, type, operands, name)
5
+ end
6
+
7
+ def each_operand(&block)
8
+ return to_enum(:each_operand) if block.nil?
9
+
10
+ if @operands
11
+ @operands.each do |basic_block, value|
12
+ yield basic_block
13
+ yield value
14
+ end
15
+ end
16
+ end
17
+
18
+ def operands=(operands)
19
+ update_use_lists do
20
+ @operands = operands
21
+ end
22
+ end
23
+
24
+ def translate_operands(map)
25
+ Hash[@operands.map do |basic_block, value|
26
+ [ map[basic_block], map[value] ]
27
+ end]
28
+ end
29
+
30
+ protected
31
+
32
+ def pretty_operands(p)
33
+ @operands.each_with_index do |(basic_block, value), index|
34
+ p.name basic_block.name
35
+ p.text '=>'
36
+ value.inspect_as_value p
37
+
38
+ p << ',' if index < @operands.count - 1
39
+ end
40
+ end
41
+
42
+ def replace_uses_of_operands(use, new_use)
43
+ if @operands.include? use
44
+ value = @operands[use]
45
+ @operands.delete use
46
+ @operands[new_use] = value
47
+
48
+ true
49
+ else
50
+ found = false
51
+
52
+ @operands.each do |basic_block, operand|
53
+ if operand == use
54
+ found = true
55
+ @operands[basic_block] = new_use
56
+ end
57
+ end
58
+
59
+ found
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ module Furnace
2
+ class SSA::ReturnInsn < SSA::TerminatorInstruction
3
+ syntax do |s|
4
+ s.operand :value
5
+ end
6
+
7
+ def exits?
8
+ true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ module Furnace
2
+ class SSA::Module
3
+ def initialize
4
+ @functions = {}
5
+ @next_id = 0
6
+ end
7
+
8
+ def to_a
9
+ @functions.values
10
+ end
11
+
12
+ def each(&block)
13
+ @functions.values.each(&block)
14
+ end
15
+
16
+ def include?(name)
17
+ @functions.include? name
18
+ end
19
+
20
+ def [](name)
21
+ unless @functions.include? name
22
+ raise ArgumentError, "function #{name} is not found"
23
+ end
24
+
25
+ @functions[name]
26
+ end
27
+
28
+ def add(function, name_prefix=nil)
29
+ if name_prefix ||
30
+ function.name.nil? ||
31
+ @functions.include?(function.name)
32
+
33
+ basename = name_prefix || function.name || 'function'
34
+ basename = basename.gsub /;\d+$/, ''
35
+
36
+ function.name = "#{basename};#{make_id}"
37
+ end
38
+
39
+ @functions[function.name] = function
40
+ end
41
+
42
+ def remove(name)
43
+ @functions.delete name
44
+ end
45
+
46
+ protected
47
+
48
+ def make_id
49
+ @next_id += 1
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ module Furnace
2
+ class SSA::NamedValue < SSA::Value
3
+ attr_accessor :function
4
+ attr_reader :name
5
+
6
+ def initialize(function, name)
7
+ super()
8
+
9
+ @function = function
10
+ self.name = name
11
+ end
12
+
13
+ def name=(name)
14
+ @name = @function.make_name(name)
15
+ end
16
+
17
+ def inspect_as_value(p=SSA::PrettyPrinter.new)
18
+ p.name name
19
+ end
20
+
21
+ def inspect
22
+ pretty_print
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,113 @@
1
+ require 'ansi'
2
+
3
+ module Furnace
4
+ class SSA::PrettyPrinter
5
+ @colorize = true
6
+
7
+ class << self
8
+ attr_accessor :colorize
9
+ end
10
+
11
+ def initialize(colorize=self.class.colorize)
12
+ @colorize = colorize
13
+ @buffer = ""
14
+ @need_space = false
15
+
16
+ yield self if block_given?
17
+ end
18
+
19
+ def to_s
20
+ @buffer
21
+ end
22
+
23
+ alias to_str to_s
24
+
25
+ def ==(other)
26
+ to_s == other
27
+ end
28
+
29
+ def =~(other)
30
+ to_s =~ other
31
+ end
32
+
33
+ def <<(what)
34
+ @buffer << what
35
+
36
+ self
37
+ end
38
+
39
+ def text(*what)
40
+ what = what.map(&:to_s)
41
+ return if what.all?(&:empty?)
42
+
43
+ ensure_space do
44
+ self << what.join
45
+ end
46
+ end
47
+
48
+ def newline
49
+ @need_space = false
50
+
51
+ self << "\n"
52
+ end
53
+
54
+ def name(what)
55
+ text '%', what
56
+ end
57
+
58
+ def type(what)
59
+ text with_ansi(:green) { Furnace::SSA.inspect_type(what) }
60
+ end
61
+
62
+ def keyword(what)
63
+ text with_ansi(:bright, :white) { what.to_s }
64
+ end
65
+
66
+ def keyword_invalid(what)
67
+ if @colorize
68
+ text with_ansi(:bright, :red) { what.to_s }
69
+ else
70
+ text "!#{what}".to_s
71
+ end
72
+ end
73
+
74
+ def objects(objects, separator=",", printer=:pretty_print)
75
+ objects.each_with_index do |object, index|
76
+ object.send(printer, self)
77
+
78
+ self << separator if index < objects.count - 1
79
+ end
80
+
81
+ self
82
+ end
83
+
84
+ def values(values, separator=",")
85
+ objects(values, separator, :inspect_as_value)
86
+ end
87
+
88
+ protected
89
+
90
+ def with_ansi(*colors)
91
+ string = yield
92
+
93
+ if @colorize
94
+ ANSI::Code.ansi(yield, *colors)
95
+ else
96
+ yield
97
+ end
98
+ end
99
+
100
+ def ensure_space(need_space_after=true)
101
+ if @need_space
102
+ self << " "
103
+ @need_space = false
104
+ end
105
+
106
+ yield
107
+
108
+ @need_space = need_space_after
109
+
110
+ self
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,24 @@
1
+ module Furnace
2
+ class SSA::TerminatorInstruction < SSA::Instruction
3
+ def has_side_effects?
4
+ true
5
+ end
6
+
7
+ def terminator?
8
+ true
9
+ end
10
+
11
+ def exits?
12
+ raise NotImplementedError, "reimplement SSA::TerminatorInstruction#exits? in a subclass"
13
+ end
14
+
15
+ def successors
16
+ operands.
17
+ select do |value|
18
+ value.type == SSA::BasicBlockType.instance
19
+ end.map do |value|
20
+ value.name
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Furnace
2
+ class SSA::Type
3
+ def to_type
4
+ self
5
+ end
6
+
7
+ def monotype?
8
+ true
9
+ end
10
+
11
+ def ==(other)
12
+ other.instance_of?(self.class)
13
+ end
14
+
15
+ def hash
16
+ [self.class].hash
17
+ end
18
+
19
+ def eql?(other)
20
+ self == other
21
+ end
22
+
23
+ def subtype_of?(other)
24
+ self == other
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ module Furnace
2
+ class SSA::BasicBlockType < SSA::Type
3
+ def self.instance
4
+ @instance ||= new
5
+ end
6
+
7
+ def inspect
8
+ "label"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Furnace
2
+ class SSA::FunctionType < SSA::Type
3
+ def self.instance
4
+ @instance ||= new
5
+ end
6
+
7
+ def inspect
8
+ "function"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Furnace
2
+ class SSA::VoidType < SSA::Type
3
+ def inspect
4
+ 'void'
5
+ end
6
+
7
+ def self.instance
8
+ @instance ||= new
9
+ end
10
+
11
+ def self.value
12
+ @value ||= SSA::Constant.new(instance, nil)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,84 @@
1
+ module Furnace
2
+ class SSA::User < SSA::NamedValue
3
+ attr_reader :operands
4
+
5
+ def initialize(function, operands=[], name=nil)
6
+ super(function, name)
7
+
8
+ self.operands = operands
9
+ end
10
+
11
+ def each_operand(&block)
12
+ @operands.each &block if @operands
13
+ end
14
+
15
+ def operands=(operands)
16
+ update_use_lists do
17
+ @operands = operands.map(&:to_value)
18
+ end
19
+ end
20
+
21
+ def detach
22
+ update_use_lists do
23
+ @operands = nil
24
+ end
25
+ end
26
+
27
+ def translate_operands(map)
28
+ @operands.map do |operand|
29
+ map[operand]
30
+ end
31
+ end
32
+
33
+ def replace_uses_of(value, new_value)
34
+ if replace_uses_of_operands(value, new_value)
35
+ value.remove_use(self)
36
+ new_value.add_use(self)
37
+ else
38
+ raise ArgumentError, "#{value.inspect} is not used in #{self.inspect}"
39
+ end
40
+
41
+ self
42
+ end
43
+
44
+ def valid?(*args)
45
+ verify!(*args)
46
+ true
47
+ rescue TypeError
48
+ false
49
+ end
50
+
51
+ def verify!
52
+ # do nothing
53
+ end
54
+
55
+ protected
56
+
57
+ def update_use_lists
58
+ each_operand do |operand|
59
+ operand.remove_use(self)
60
+ end
61
+
62
+ value = yield
63
+
64
+ each_operand do |operand|
65
+ operand.add_use(self)
66
+ end
67
+
68
+ value
69
+ end
70
+
71
+ def replace_uses_of_operands(value, new_value)
72
+ found = false
73
+
74
+ @operands.each_with_index do |operand, index|
75
+ if operand == value
76
+ found = true
77
+ @operands[index] = new_value
78
+ end
79
+ end
80
+
81
+ found
82
+ end
83
+ end
84
+ end