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