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/test_helper.rb CHANGED
@@ -1,36 +1,25 @@
1
- $LOAD_PATH << File.expand_path('../../lib', __FILE__)
1
+ require 'bacon'
2
+ require 'bacon/colored_output'
2
3
 
3
- require 'furnace'
4
- include Furnace
4
+ class Should
5
+ def enumerate(iterator, array)
6
+ enum = @object.send(iterator)
5
7
 
6
- module Bacon
7
- module ColoredOutput
8
- def handle_specification(name)
9
- puts spaces + name
10
- yield
11
- puts if Counter[:context_depth] == 1
8
+ satisfy("##{iterator} should return an Enumerator, returns #{enum.class}") do
9
+ enum.instance_of? Enumerator
12
10
  end
13
11
 
14
- def handle_requirement(description)
15
- print spaces
16
-
17
- error = yield
18
-
19
- print error.empty? ? "\e[32m" : "\e[1;31m"
20
- print " - #{description}"
21
- puts error.empty? ? "\e[0m" : " [#{error}]\e[0m"
12
+ values = enum.to_a
13
+ satisfy("##{iterator} should yield #{array.inspect}, yields #{values.inspect}") do
14
+ (values - array).empty? && (array - values).empty?
22
15
  end
16
+ end
17
+ end
23
18
 
24
- def handle_summary
25
- print ErrorLog if Backtraces
26
- puts "%d specifications (%d requirements), %d failures, %d errors" %
27
- Counter.values_at(:specifications, :requirements, :failed, :errors)
28
- end
19
+ require 'simplecov'
20
+ SimpleCov.start
29
21
 
30
- def spaces
31
- " " * (Counter[:context_depth] - 1)
32
- end
33
- end
22
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
34
23
 
35
- extend ColoredOutput
36
- end
24
+ require 'furnace'
25
+ include Furnace
@@ -0,0 +1,74 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe Transform do
4
+ class Context
5
+ attr_accessor :value
6
+
7
+ def initialize(value)
8
+ @value = value
9
+ end
10
+ end
11
+
12
+ class Pass
13
+ attr_accessor :run_count
14
+
15
+ def initialize
16
+ @run_count = 0
17
+ end
18
+
19
+ def run(context)
20
+ @run_count += 1
21
+
22
+ if context.value > 0
23
+ context.value -= 1
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+ end
30
+
31
+ before do
32
+ @context = Context.new(3)
33
+ @pass = Pass.new
34
+ end
35
+
36
+ describe Transform::Pipeline do
37
+ should 'run all stages' do
38
+ pipeline = Transform::Pipeline.new([ @pass ] * 4)
39
+ pipeline.run(@context)
40
+
41
+ @context.value.should == 0
42
+ @pass.run_count.should == 4
43
+ end
44
+ end
45
+
46
+ describe Transform::Iterative do
47
+ should 'run until nothing changes' do
48
+ pipeline = Transform::Iterative.new([ @pass ] * 2)
49
+ pipeline.run(@context)
50
+
51
+ # [
52
+ # 3 pass 2 -> true
53
+ # 2 pass 1 -> true
54
+ # ]
55
+ # [
56
+ # 1 pass 0 -> true
57
+ # 0 pass 0 -> false
58
+ # ]
59
+ # [
60
+ # 0 pass 0 -> false
61
+ # 0 pass 0 -> false
62
+ # ]
63
+
64
+ @context.value.should == 0
65
+ @pass.run_count.should == 6
66
+ end
67
+
68
+ should 'signal if any changes were made' do
69
+ pipeline = Transform::Iterative.new([ @pass ] * 3)
70
+ pipeline.run(@context).should == true
71
+ pipeline.run(@context).should == false
72
+ end
73
+ end
74
+ end
metadata CHANGED
@@ -1,92 +1,150 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: furnace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
5
- prerelease:
4
+ prerelease: 6
5
+ version: 0.4.0.beta.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Peter Zotov
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-02 00:00:00.000000000 Z
12
+ date: 2013-01-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rake
16
- requirement: !ruby/object:Gem::Requirement
15
+ name: ansi
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: !binary |-
21
+ MA==
17
22
  none: false
23
+ requirement: !ruby/object:Gem::Requirement
18
24
  requirements:
19
- - - ! '>='
25
+ - - ">="
20
26
  - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :development
27
+ version: !binary |-
28
+ MA==
29
+ none: false
23
30
  prerelease: false
31
+ type: :runtime
32
+ - !ruby/object:Gem::Dependency
33
+ name: rake
24
34
  version_requirements: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '10.0'
25
39
  none: false
40
+ requirement: !ruby/object:Gem::Requirement
26
41
  requirements:
27
- - - ! '>='
42
+ - - "~>"
28
43
  - !ruby/object:Gem::Version
29
- version: '0'
44
+ version: '10.0'
45
+ none: false
46
+ prerelease: false
47
+ type: :development
30
48
  - !ruby/object:Gem::Dependency
31
49
  name: bacon
32
- requirement: !ruby/object:Gem::Requirement
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
33
55
  none: false
56
+ requirement: !ruby/object:Gem::Requirement
34
57
  requirements:
35
- - - ~>
58
+ - - "~>"
36
59
  - !ruby/object:Gem::Version
37
- version: '1.1'
38
- type: :development
60
+ version: '1.2'
61
+ none: false
39
62
  prerelease: false
63
+ type: :development
64
+ - !ruby/object:Gem::Dependency
65
+ name: bacon-colored_output
40
66
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
67
  requirements:
43
- - - ~>
68
+ - - ">="
44
69
  - !ruby/object:Gem::Version
45
- version: '1.1'
46
- - !ruby/object:Gem::Dependency
47
- name: yard
48
- requirement: !ruby/object:Gem::Requirement
70
+ version: !binary |-
71
+ MA==
49
72
  none: false
73
+ requirement: !ruby/object:Gem::Requirement
50
74
  requirements:
51
- - - ! '>='
75
+ - - ">="
52
76
  - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :development
77
+ version: !binary |-
78
+ MA==
79
+ none: false
55
80
  prerelease: false
81
+ type: :development
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov
56
84
  version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: !binary |-
89
+ MA==
57
90
  none: false
91
+ requirement: !ruby/object:Gem::Requirement
58
92
  requirements:
59
- - - ! '>='
93
+ - - ">="
60
94
  - !ruby/object:Gem::Version
61
- version: '0'
95
+ version: !binary |-
96
+ MA==
97
+ none: false
98
+ prerelease: false
99
+ type: :development
62
100
  - !ruby/object:Gem::Dependency
63
- name: redcarpet
64
- requirement: !ruby/object:Gem::Requirement
101
+ name: yard
102
+ version_requirements: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: !binary |-
107
+ MA==
65
108
  none: false
109
+ requirement: !ruby/object:Gem::Requirement
66
110
  requirements:
67
- - - ! '>='
111
+ - - ">="
68
112
  - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :development
113
+ version: !binary |-
114
+ MA==
115
+ none: false
71
116
  prerelease: false
117
+ type: :development
118
+ - !ruby/object:Gem::Dependency
119
+ name: kramdown
72
120
  version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: !binary |-
125
+ MA==
73
126
  none: false
127
+ requirement: !ruby/object:Gem::Requirement
74
128
  requirements:
75
- - - ! '>='
129
+ - - ">="
76
130
  - !ruby/object:Gem::Version
77
- version: '0'
78
- description: Furnace is a static code analysis framework for dynamic languages, aimed
79
- at efficient type and behavior inference.
131
+ version: !binary |-
132
+ MA==
133
+ none: false
134
+ prerelease: false
135
+ type: :development
136
+ description: Furnace is a static code analysis framework for dynamic languages, aimed at efficient type and behavior inference.
80
137
  email:
81
138
  - whitequark@whitequark.org
82
139
  executables: []
83
140
  extensions: []
84
141
  extra_rdoc_files: []
85
142
  files:
86
- - .gitignore
87
- - .yardopts
143
+ - ".gitignore"
144
+ - ".travis.yml"
145
+ - ".yardopts"
88
146
  - Gemfile
89
- - LICENSE
147
+ - LICENSE.MIT
90
148
  - Rakefile
91
149
  - furnace.gemspec
92
150
  - lib/furnace.rb
@@ -94,45 +152,65 @@ files:
94
152
  - lib/furnace/ast/node.rb
95
153
  - lib/furnace/ast/processor.rb
96
154
  - lib/furnace/ast/sexp.rb
97
- - lib/furnace/cfg.rb
98
- - lib/furnace/cfg/algorithms.rb
99
- - lib/furnace/cfg/graph.rb
100
- - lib/furnace/cfg/node.rb
101
155
  - lib/furnace/graphviz.rb
102
- - lib/furnace/transform/iterative_process.rb
156
+ - lib/furnace/ssa.rb
157
+ - lib/furnace/ssa/argument.rb
158
+ - lib/furnace/ssa/basic_block.rb
159
+ - lib/furnace/ssa/builder.rb
160
+ - lib/furnace/ssa/constant.rb
161
+ - lib/furnace/ssa/function.rb
162
+ - lib/furnace/ssa/generic_instruction.rb
163
+ - lib/furnace/ssa/generic_type.rb
164
+ - lib/furnace/ssa/instruction.rb
165
+ - lib/furnace/ssa/instruction_syntax.rb
166
+ - lib/furnace/ssa/instructions/branch.rb
167
+ - lib/furnace/ssa/instructions/phi.rb
168
+ - lib/furnace/ssa/instructions/return.rb
169
+ - lib/furnace/ssa/module.rb
170
+ - lib/furnace/ssa/named_value.rb
171
+ - lib/furnace/ssa/pretty_printer.rb
172
+ - lib/furnace/ssa/terminator_instruction.rb
173
+ - lib/furnace/ssa/type.rb
174
+ - lib/furnace/ssa/types/basic_block.rb
175
+ - lib/furnace/ssa/types/function.rb
176
+ - lib/furnace/ssa/types/void.rb
177
+ - lib/furnace/ssa/user.rb
178
+ - lib/furnace/ssa/value.rb
179
+ - lib/furnace/transform/iterative.rb
103
180
  - lib/furnace/transform/pipeline.rb
104
181
  - lib/furnace/version.rb
105
182
  - test/ast_test.rb
183
+ - test/ssa_test.rb
106
184
  - test/test_helper.rb
185
+ - test/transform_test.rb
107
186
  homepage: http://github.com/whitequark/furnace
108
187
  licenses: []
109
- post_install_message:
188
+ post_install_message:
110
189
  rdoc_options: []
111
190
  require_paths:
112
191
  - lib
113
192
  required_ruby_version: !ruby/object:Gem::Requirement
114
- none: false
115
193
  requirements:
116
- - - ! '>='
194
+ - - ">="
117
195
  - !ruby/object:Gem::Version
118
- version: '0'
119
196
  segments:
120
197
  - 0
121
- hash: -2809790994543949274
122
- required_rubygems_version: !ruby/object:Gem::Requirement
198
+ hash: 2
199
+ version: !binary |-
200
+ MA==
123
201
  none: false
202
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
203
  requirements:
125
- - - ! '>='
204
+ - - !binary |-
205
+ Pg==
126
206
  - !ruby/object:Gem::Version
127
- version: '0'
128
- segments:
129
- - 0
130
- hash: -2809790994543949274
207
+ version: 1.3.1
208
+ none: false
131
209
  requirements: []
132
- rubyforge_project:
133
- rubygems_version: 1.8.23
134
- signing_key:
210
+ rubyforge_project:
211
+ rubygems_version: 1.8.24
212
+ signing_key:
135
213
  specification_version: 3
136
214
  summary: A static code analysis framework
137
215
  test_files: []
138
- has_rdoc:
216
+ has_rdoc:
@@ -1,193 +0,0 @@
1
- module Furnace::CFG
2
- module Algorithms
3
- def eliminate_unreachable!
4
- worklist = Set[ entry, exit ]
5
- reachable = Set[]
6
-
7
- while worklist.any?
8
- node = worklist.first
9
- worklist.delete node
10
- reachable.add node
11
-
12
- node.targets.each do |target|
13
- unless reachable.include? target
14
- worklist.add target
15
- end
16
- end
17
-
18
- if node.exception
19
- unless reachable.include? node.exception
20
- worklist.add node.exception
21
- end
22
- end
23
- end
24
-
25
- @nodes.each do |node|
26
- unless reachable.include? node
27
- @nodes.delete node
28
- yield node if block_given?
29
- end
30
- end
31
-
32
- flush
33
- end
34
-
35
- def merge_redundant!
36
- worklist = @nodes.dup
37
- while worklist.any?
38
- node = worklist.first
39
- worklist.delete node
40
-
41
- target = node.targets[0]
42
- next if target == @exit
43
-
44
- # Skip explicitly non-redundant nodes
45
- if node.metadata[:keep]
46
- next
47
- end
48
-
49
- if node.targets.uniq == [target] &&
50
- target.sources.uniq == [node] &&
51
- node.exception == target.exception
52
-
53
- yield node, target if block_given?
54
-
55
- node.insns.delete node.cti
56
- @nodes.delete target
57
- worklist.delete target
58
-
59
- node.insns.concat target.insns
60
- node.cti = target.cti
61
- node.target_labels = target.target_labels
62
-
63
- worklist.add node
64
-
65
- flush
66
- elsif node.targets.count == 1 &&
67
- node.insns.empty?
68
-
69
- target = node.targets.first
70
-
71
- yield target, node if block_given?
72
-
73
- node.sources.each do |source|
74
- index = source.targets.index(node)
75
- source.target_labels[index] = target.label
76
- end
77
-
78
- @nodes.delete node
79
- worklist.delete node
80
-
81
- if @entry == node
82
- @entry = target
83
- end
84
-
85
- flush
86
- end
87
- end
88
- end
89
-
90
- # Shamelessly stolen from
91
- # http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf
92
- def compute_generic_domination(start, forward)
93
- # values of β will give rise to dom!
94
- dom = { start => Set[start] }
95
-
96
- @nodes.each do |node|
97
- next if node == start
98
- dom[node] = @nodes.dup
99
- end
100
-
101
- change = true
102
- while change
103
- change = false
104
- @nodes.each do |node|
105
- next if node == start
106
-
107
- # Are we computing dominators or postdominators?
108
- if forward
109
- edges = node.sources + node.exception_sources
110
- elsif node.exception.nil?
111
- edges = node.targets
112
- else
113
- edges = node.targets + [ node.exception ]
114
- end
115
-
116
- # Key Idea [for dominators]
117
- # If a node dominates all
118
- # predecessors of node n, then it
119
- # also dominates node n.
120
- pred = edges.map do |source|
121
- dom[source]
122
- end.reduce(:&)
123
-
124
- # An exception handler header node has no regular sources.
125
- pred = [] if pred.nil?
126
-
127
- current = Set[node].merge(pred)
128
- if current != dom[node]
129
- dom[node] = current
130
- change = true
131
- end
132
- end
133
- end
134
-
135
- dom
136
- end
137
-
138
- def dominators
139
- @dominators ||= compute_generic_domination(@entry, true)
140
- end
141
-
142
- def postdominators
143
- @postdominators ||= compute_generic_domination(@exit, false)
144
- end
145
-
146
- # See also {#dominators} for references.
147
- def identify_loops
148
- loops = Hash.new { |h,k| h[k] = Set.new }
149
-
150
- dom = dominators
151
-
152
- @nodes.each do |node|
153
- node.targets.each do |target|
154
- # Back edges
155
- # A back edge of a natural loop is one whose
156
- # target dominates its source.
157
- if dom[node].include? target
158
- loops[target].add node
159
- end
160
- end
161
- end
162
-
163
- # At this point, +loops+ contains a list of all nodes
164
- # which have a back edge to the loop header. Expand
165
- # it to the list of all nodes in the loop.
166
- loops.each do |header, nodes|
167
- # Natural loop
168
- # The natural loop of a back edge (m→n), where
169
- # n dominates m, is the set of nodes x such that n
170
- # dominates x and there is a path from x to m not
171
- # containing n.
172
- pre_header = dom[header]
173
- all_nodes = Set[header]
174
-
175
- nodes.each do |node|
176
- all_nodes.merge(dom[node] - pre_header)
177
- end
178
-
179
- nodes.replace all_nodes
180
- end
181
-
182
- loops.default = nil
183
- loops
184
- end
185
-
186
- def flush
187
- @dominators = nil
188
- @postdominators = nil
189
-
190
- super if defined?(super)
191
- end
192
- end
193
- end
@@ -1,99 +0,0 @@
1
- module Furnace::CFG
2
- class Graph
3
- include Algorithms
4
-
5
- attr_reader :nodes
6
- attr_accessor :entry, :exit
7
-
8
- def initialize
9
- @nodes = Set.new
10
-
11
- @source_map = nil
12
- @label_map = {}
13
- end
14
-
15
- def find_node(label)
16
- if node = @label_map[label]
17
- node
18
- elsif node = @nodes.find { |n| n.label == label }
19
- @label_map[label] = node
20
- node
21
- else
22
- raise "Cannot find CFG node #{label}"
23
- end
24
- end
25
-
26
- def sources_for(node, find_exceptions=false)
27
- unless @source_map
28
- @source_map = Hash.new { |h, k| h[k] = [] }
29
- @exception_source_map = Hash.new { |h, k| h[k] = [] }
30
-
31
- @nodes.each do |node|
32
- node.targets.each do |target|
33
- @source_map[target] << node
34
- end
35
-
36
- @exception_source_map[node.exception] << node
37
- end
38
-
39
- @source_map.each do |node, sources|
40
- sources.freeze
41
- end
42
-
43
- @exception_source_map.each do |node, sources|
44
- sources.freeze
45
- end
46
- end
47
-
48
- if find_exceptions
49
- @exception_source_map[node]
50
- else
51
- @source_map[node]
52
- end
53
- end
54
-
55
- def flush
56
- @source_map = nil
57
- @label_map.clear
58
-
59
- super if defined?(super)
60
- end
61
-
62
- def to_graphviz
63
- Furnace::Graphviz.new do |graph|
64
- @nodes.each do |node|
65
- if node.label == nil
66
- contents = "<exit>"
67
- else
68
- contents = "<#{node.label.inspect}>"
69
- end
70
-
71
- if node.metadata.any?
72
- contents << "\n#{node.metadata.inspect}"
73
- end
74
-
75
- if node.insns.any?
76
- contents << "\n#{node.insns.map(&:inspect).join("\n")}"
77
- end
78
-
79
- options = {}
80
- if @entry == node
81
- options.merge! color: 'green'
82
- elsif @exit == node
83
- options.merge! color: 'red'
84
- end
85
-
86
- graph.node node.label, contents, options
87
-
88
- node.target_labels.each_with_index do |label, idx|
89
- graph.edge node.label, label, "#{idx}"
90
- end
91
-
92
- if node.exception_label
93
- graph.edge node.label, node.exception_label, "", color: 'orange'
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end