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/.gitignore CHANGED
@@ -5,3 +5,4 @@ pkg/*
5
5
  *.sublime-*
6
6
  doc/
7
7
  .yardoc/
8
+ coverage/
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in furnace.gemspec
4
- gemspec
5
- gem 'bacon', github: 'chneukirchen/bacon'
4
+ gemspec
@@ -1,20 +1,20 @@
1
- Copyright (c) 2011-2012 Peter Zotov <whitequark@whitequark.org>
1
+ Copyright (c) 2011-2013 Peter Zotov <whitequark@whitequark.org>
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining a
4
- copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
9
  the following conditions:
10
10
 
11
- The above copyright notice and this permission notice shall be included
11
+ The above copyright notice and this permission notice shall be included
12
12
  in all copies or substantial portions of the Software.
13
13
 
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
20
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -5,11 +5,7 @@ task :default => :test
5
5
 
6
6
  desc "Run test suite"
7
7
  task :test do
8
- require 'bacon'
9
- Bacon.summary_at_exit
10
- Dir["test/**/*_test.rb"].each do |file|
11
- load file
12
- end
8
+ sh "bacon test/*_test.rb"
13
9
  end
14
10
 
15
11
  PAGES_REPO = 'git@github.com:whitequark/furnace'
data/furnace.gemspec CHANGED
@@ -17,8 +17,12 @@ Gem::Specification.new do |s|
17
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_development_dependency 'rake'
21
- s.add_development_dependency 'bacon', '~> 1.1'
20
+ s.add_dependency 'ansi'
21
+
22
+ s.add_development_dependency 'rake', '~> 10.0'
23
+ s.add_development_dependency 'bacon', '~> 1.2'
24
+ s.add_development_dependency 'bacon-colored_output'
25
+ s.add_development_dependency 'simplecov'
22
26
  s.add_development_dependency 'yard'
23
- s.add_development_dependency 'redcarpet'
27
+ s.add_development_dependency 'kramdown'
24
28
  end
@@ -130,6 +130,20 @@ module Furnace::AST
130
130
  "(#{fancy_type} ...)"
131
131
  end
132
132
 
133
+ # Returns {#children}. This is very useful in order to decompose nodes
134
+ # concisely. For example:
135
+ #
136
+ # node = s(:gasgn, :$foo, s(:integer, 1))
137
+ # s
138
+ # var_name, value = *node
139
+ # p var_name # => :$foo
140
+ # p value # => (integer 1)
141
+ #
142
+ # @return [Array]
143
+ def to_a
144
+ children
145
+ end
146
+
133
147
  # Converts `self` to a pretty-printed s-expression.
134
148
  #
135
149
  # @param [Integer] indent Base indentation level.
@@ -37,7 +37,7 @@ module Furnace::AST
37
37
  # def process_binary_op(node)
38
38
  # # Children aren't decomposed automatically; it is suggested to use Ruby
39
39
  # # multiple assignment expansion, as it is very convenient here.
40
- # left_expr, right_expr = node.children
40
+ # left_expr, right_expr = *node
41
41
  #
42
42
  # # AST::Node#updated won't change node type if nil is passed as a first
43
43
  # # argument, which allows to reuse the same handler for multiple node types
@@ -54,11 +54,11 @@ module Furnace::AST
54
54
  # def on_negate(node)
55
55
  # # It is also possible to use #process_all for more compact code
56
56
  # # if every child is a Node.
57
- # node.updated(nil, process_all(node.children))
57
+ # node.updated(nil, process_all(node))
58
58
  # end
59
59
  #
60
60
  # def on_store(node)
61
- # expr, variable_name = node.children
61
+ # expr, variable_name = *node
62
62
  #
63
63
  # # Note that variable_name is not a Node and thus isn't passed to #process.
64
64
  # node.updated(nil, [
@@ -74,7 +74,7 @@ module Furnace::AST
74
74
  # end
75
75
  #
76
76
  # def on_each(node)
77
- # node.updated(nil, process_all(node.children))
77
+ # node.updated(nil, process_all(node))
78
78
  # end
79
79
  # end
80
80
  #
@@ -95,7 +95,7 @@ module Furnace::AST
95
95
  # def compute_op(node)
96
96
  # # First, node children are processed and then unpacked to local
97
97
  # # variables.
98
- # nodes = process_all(node.children)
98
+ # nodes = process_all(node)
99
99
  #
100
100
  # if nodes.all? { |node| node.type == :integer }
101
101
  # # If each of those nodes represents a literal, we can fold this
@@ -186,7 +186,7 @@ module Furnace::AST
186
186
  # # (divide
187
187
  # # (integer 1)
188
188
  # # (divide (integer 1) (integer 0))
189
- # left, right = process_all(node.children)
189
+ # left, right = process_all(node)
190
190
  #
191
191
  # if right.type == :integer &&
192
192
  # right.children.first == 0
@@ -250,7 +250,7 @@ module Furnace::AST
250
250
  # @param [Array<AST::Node>] nodes
251
251
  # @return [Array<AST::Node>]
252
252
  def process_all(nodes)
253
- nodes.map do |node|
253
+ nodes.to_a.map do |node|
254
254
  process node
255
255
  end
256
256
  end
@@ -0,0 +1,23 @@
1
+ module Furnace
2
+ class SSA::Argument < SSA::NamedValue
3
+ attr_reader :type
4
+
5
+ def initialize(function, type, name)
6
+ super(function, name)
7
+ self.type = type
8
+ end
9
+
10
+ def type=(type)
11
+ @type = type.to_type if type
12
+ end
13
+
14
+ def has_side_effects?
15
+ true
16
+ end
17
+
18
+ def pretty_print(p=SSA::PrettyPrinter.new)
19
+ p.type type
20
+ p.name name
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,117 @@
1
+ module Furnace
2
+ class SSA::BasicBlock < SSA::NamedValue
3
+ def initialize(function, name=nil, insns=[])
4
+ super(function, name)
5
+ @instructions = insns.to_a
6
+ end
7
+
8
+ def initialize_copy(original)
9
+ super
10
+
11
+ @instructions = []
12
+ end
13
+
14
+ def to_a
15
+ @instructions.dup
16
+ end
17
+
18
+ def include?(instruction)
19
+ @instructions.include? instruction
20
+ end
21
+
22
+ def each(type=nil, &proc)
23
+ if type.nil?
24
+ @instructions.each(&proc)
25
+ else
26
+ return to_enum(:each, type) if proc.nil?
27
+
28
+ @instructions.each do |insn|
29
+ if insn.instance_of? type
30
+ yield insn
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ alias each_instruction each
37
+
38
+ def append(instruction)
39
+ @instructions.push instruction
40
+ end
41
+
42
+ alias << append
43
+
44
+ def insert(before, instruction)
45
+ unless index = @instructions.index(before)
46
+ raise ArgumentError, "Instruction #{before} is not found"
47
+ end
48
+
49
+ @instructions.insert index, instruction
50
+ end
51
+
52
+ def replace(instruction, replace_with)
53
+ insert instruction, replace_with
54
+ remove instruction
55
+ end
56
+
57
+ def remove(instruction)
58
+ @instructions.delete instruction
59
+ end
60
+
61
+ def terminator
62
+ @instructions.last
63
+ end
64
+
65
+ def successor_names
66
+ terminator.successors
67
+ end
68
+
69
+ def successors
70
+ successor_names.map do |label|
71
+ @function.find(label)
72
+ end
73
+ end
74
+
75
+ def predecessor_names
76
+ predecessors.map(&:name)
77
+ end
78
+
79
+ def predecessors
80
+ @function.predecessors_for(@name)
81
+ end
82
+
83
+ def exits?
84
+ terminator.exits?
85
+ end
86
+
87
+ def self.to_type
88
+ SSA::BasicBlockType.instance
89
+ end
90
+
91
+ def type
92
+ self.class.to_type
93
+ end
94
+
95
+ def constant?
96
+ true
97
+ end
98
+
99
+ def pretty_print(p=SSA::PrettyPrinter.new)
100
+ p.text @name, ":"
101
+ p.newline
102
+
103
+ each do |insn|
104
+ p << ' '
105
+ insn.pretty_print(p)
106
+ p.newline
107
+ end
108
+
109
+ p
110
+ end
111
+
112
+ def inspect_as_value(p=SSA::PrettyPrinter.new)
113
+ p.keyword 'label'
114
+ p.name name
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,100 @@
1
+ module Furnace
2
+ class SSA::Builder
3
+ attr_reader :function
4
+ attr_accessor :block
5
+
6
+ def self.scope
7
+ SSA
8
+ end
9
+
10
+ def initialize(name, arguments=[], return_type=nil)
11
+ @function = SSA::Function.new(name, [], return_type)
12
+ @function.arguments = arguments.map do |(type, name)|
13
+ SSA::Argument.new(@function, type, name)
14
+ end
15
+
16
+ @block = @function.entry = add_block
17
+ end
18
+
19
+ def lookup_insn(opcode)
20
+ self.class.scope.const_get SSA.opcode_to_class_name(opcode)
21
+ end
22
+
23
+ def add_block
24
+ block = SSA::BasicBlock.new(@function)
25
+ @function.add block
26
+
27
+ if block_given?
28
+ branch block
29
+ @block = block
30
+
31
+ yield
32
+ else
33
+ block
34
+ end
35
+ end
36
+
37
+ def append(instruction, *args)
38
+ insn = lookup_insn(instruction).new(@block, *args)
39
+ @block.append insn
40
+
41
+ insn
42
+ end
43
+
44
+ def branch(target)
45
+ append(:branch, [ target ])
46
+ end
47
+
48
+ def phi(type, mapping)
49
+ append(:phi, type, Hash[mapping])
50
+ end
51
+
52
+ def fork(post_block)
53
+ old_block = @block
54
+ new_block = add_block
55
+
56
+ @block = new_block
57
+
58
+ value = yield old_block
59
+
60
+ branch post_block
61
+
62
+ [ new_block, value ]
63
+ ensure
64
+ @block = old_block
65
+ end
66
+
67
+ def control_flow_op(instruction, type=nil, uses)
68
+ cond_block = @block
69
+ post_block = add_block
70
+
71
+ mapping = yield cond_block, post_block
72
+
73
+ targets = mapping.map { |(target, _)| target }
74
+ append(instruction, uses + targets)
75
+
76
+ @block = post_block
77
+ phi(type, mapping.map do |(target, value)|
78
+ if target == post_block
79
+ [cond_block, value]
80
+ else
81
+ [target, value]
82
+ end
83
+ end)
84
+ end
85
+
86
+ def return(value)
87
+ append(:return, [ value ])
88
+ end
89
+
90
+ def method_missing(opcode, *args)
91
+ class_name = SSA.opcode_to_class_name(opcode)
92
+
93
+ if self.class.scope.const_defined? class_name
94
+ append opcode, *args
95
+ else
96
+ super
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,43 @@
1
+ module Furnace
2
+ class SSA::Constant < SSA::Value
3
+ attr_reader :type
4
+ attr_accessor :value
5
+
6
+ def initialize(type, value)
7
+ super()
8
+
9
+ self.type = type
10
+ @value = value
11
+ end
12
+
13
+ def type=(type)
14
+ @type = type.to_type
15
+ end
16
+
17
+ def constant?
18
+ true
19
+ end
20
+
21
+ def ==(other)
22
+ if other.respond_to? :to_value
23
+ other_value = other.to_value
24
+
25
+ other_value.constant? &&
26
+ other_value.type == type &&
27
+ other_value.value == value
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ def inspect_as_value(p=SSA::PrettyPrinter.new)
34
+ p.type type
35
+ p.text @value.inspect unless type == SSA.void
36
+ p
37
+ end
38
+
39
+ def inspect
40
+ pretty_print
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,191 @@
1
+ module Furnace
2
+ class SSA::Function
3
+ attr_reader :original_name
4
+ attr_accessor :name
5
+ attr_reader :arguments
6
+ attr_accessor :return_type
7
+
8
+ attr_accessor :entry
9
+
10
+ def initialize(name=nil, arguments=[], return_type=SSA.void)
11
+ @original_name = name
12
+ @name = name
13
+ self.arguments = arguments
14
+ @return_type = return_type
15
+
16
+ @basic_blocks = Set.new
17
+
18
+ @name_prefixes = [""].to_set
19
+ @next_name = 0
20
+ end
21
+
22
+ def initialize_copy(original)
23
+ @name = @original_name
24
+
25
+ value_map = Hash.new do |value_map, value|
26
+ new_value = value.dup
27
+ value_map[value] = new_value
28
+
29
+ if new_value.is_a? SSA::User
30
+ new_value.function = self
31
+ new_value.operands = value.translate_operands(value_map)
32
+ end
33
+
34
+ new_value
35
+ end
36
+
37
+ @arguments = @arguments.map do |arg|
38
+ new_arg = arg.dup
39
+ new_arg.function = self
40
+ value_map[arg] = new_arg
41
+
42
+ new_arg
43
+ end
44
+
45
+ @basic_blocks = @basic_blocks.map do |bb|
46
+ new_bb = bb.dup
47
+ new_bb.function = self
48
+
49
+ value_map[bb] = new_bb
50
+
51
+ new_bb
52
+ end
53
+
54
+ @entry = value_map[@entry]
55
+
56
+ original.each do |bb|
57
+ new_bb = value_map[bb]
58
+
59
+ bb.each do |insn|
60
+ new_insn = value_map[insn]
61
+ new_insn.basic_block = new_bb
62
+ new_bb.append new_insn
63
+ end
64
+ end
65
+ end
66
+
67
+ def arguments=(arguments)
68
+ @arguments = sanitize_arguments(arguments)
69
+ end
70
+
71
+ def make_name(prefix=nil)
72
+ if prefix.nil?
73
+ (@next_name += 1).to_s
74
+ else
75
+ prefix = prefix.to_s
76
+
77
+ if @name_prefixes.include? prefix
78
+ "#{prefix}.#{@next_name += 1}"
79
+ else
80
+ @name_prefixes.add prefix
81
+ prefix
82
+ end
83
+ end
84
+ end
85
+
86
+ def each(&proc)
87
+ @basic_blocks.each(&proc)
88
+ end
89
+
90
+ alias each_basic_block each
91
+
92
+ def include?(name)
93
+ @basic_blocks.any? { |n| n.name == name }
94
+ end
95
+
96
+ def find(name)
97
+ if block = @basic_blocks.find { |n| n.name == name }
98
+ block
99
+ else
100
+ raise ArgumentError, "Cannot find basic block #{name}"
101
+ end
102
+ end
103
+
104
+ def add(block)
105
+ @basic_blocks.add block
106
+ end
107
+
108
+ alias << add
109
+
110
+ def remove(block)
111
+ @basic_blocks.delete block
112
+ end
113
+
114
+ def each_instruction(type=nil, &proc)
115
+ return to_enum(:each_instruction, type) if proc.nil?
116
+
117
+ each do |block|
118
+ block.each(type, &proc)
119
+ end
120
+ end
121
+
122
+ def predecessors_for(name)
123
+ predecessors = Set[]
124
+
125
+ each do |block|
126
+ if block.successor_names.include? name
127
+ predecessors << block
128
+ end
129
+ end
130
+
131
+ predecessors
132
+ end
133
+
134
+ def self.to_type
135
+ SSA::FunctionType.instance
136
+ end
137
+
138
+ def to_value
139
+ SSA::Constant.new(self.class.to_type, @name)
140
+ end
141
+
142
+ def pretty_print(p=SSA::PrettyPrinter.new)
143
+ p.keyword 'function'
144
+ p.type @return_type
145
+ p.text @name, '('
146
+ p.objects @arguments
147
+ p.text ') {'
148
+ p.newline
149
+
150
+ each do |basic_block|
151
+ basic_block.pretty_print(p)
152
+ p.newline
153
+ end
154
+
155
+ p.text "}"
156
+ p.newline
157
+ end
158
+
159
+ alias inspect pretty_print
160
+
161
+ def to_graphviz
162
+ Graphviz.new do |graph|
163
+ @basic_blocks.each do |block|
164
+ options = {}
165
+
166
+ if @entry == block
167
+ options.merge!({ color: 'green' })
168
+ elsif block.returns?
169
+ options.merge!({ color: 'red' })
170
+ end
171
+
172
+ graph.node block.name, block.inspect, options
173
+
174
+ block.successor_names.each do |name|
175
+ graph.edge block.name, name
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ protected
182
+
183
+ def sanitize_arguments(arguments)
184
+ arguments.each_with_index do |argument, index|
185
+ if !argument.is_a?(SSA::Argument)
186
+ raise ArgumentError, "function #{@name} arguments: #{argument.inspect} (at #{index}) is not an Argument"
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,14 @@
1
+ module Furnace
2
+ class SSA::GenericInstruction < SSA::Instruction
3
+ attr_reader :type
4
+
5
+ def initialize(basic_block, type=nil, uses=[], name=basic_block.function.make_name)
6
+ super(basic_block, uses, name)
7
+ self.type = type
8
+ end
9
+
10
+ def type=(type)
11
+ @type = type.to_type if type
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Furnace
2
+ class SSA::GenericType < SSA::Type
3
+ def parameters
4
+ nil
5
+ end
6
+
7
+ def ==(other)
8
+ other.instance_of?(self.class) &&
9
+ other.parameters == parameters
10
+ end
11
+
12
+ def hash
13
+ [self.class, parameters].hash
14
+ end
15
+ end
16
+ end