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