furnace 0.1.1 → 0.1.2

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.
data/.gitignore CHANGED
@@ -3,3 +3,4 @@
3
3
  Gemfile.lock
4
4
  pkg/*
5
5
  .rbx/
6
+ *.sublime-*
@@ -5,10 +5,16 @@ module Furnace::CFG
5
5
 
6
6
  def initialize
7
7
  @nodes = Set.new
8
+
9
+ @source_map = nil
10
+ @label_map = {}
8
11
  end
9
12
 
10
13
  def find_node(label)
11
- if node = @nodes.find { |n| n.label == label }
14
+ if node = @label_map[label]
15
+ node
16
+ elsif node = @nodes.find { |n| n.label == label }
17
+ @label_map[label] = node
12
18
  node
13
19
  else
14
20
  raise "Cannot find CFG node #{label}"
@@ -16,19 +22,31 @@ module Furnace::CFG
16
22
  end
17
23
 
18
24
  def eliminate_unreachable!
19
- worklist = @nodes.dup
20
- while worklist.any?
21
- node = worklist.first
22
- worklist.delete node
25
+ queue = [entry]
26
+ reachable = Set[]
23
27
 
24
- next if node == @entry
28
+ while queue.any?
29
+ node = queue.shift
30
+ reachable.add node
25
31
 
26
- if node.sources.count == 0 ||
27
- node.sources == [node]
28
- @nodes.delete node
29
- flush
32
+ node.targets.each do |target|
33
+ unless reachable.include? target
34
+ queue.push target
35
+ end
36
+ end
37
+
38
+ if node.exception
39
+ unless reachable.include? node.exception
40
+ queue.push node.exception
41
+ end
30
42
  end
31
43
  end
44
+
45
+ @nodes.each do |node|
46
+ @nodes.delete node unless reachable.include? node
47
+ end
48
+
49
+ flush
32
50
  end
33
51
 
34
52
  def merge_redundant!
@@ -41,7 +59,9 @@ module Furnace::CFG
41
59
  next if target == @exit
42
60
 
43
61
  if node.targets.count == 1 &&
44
- target.sources.count == 1
62
+ target.sources.count == 1 &&
63
+ node.exception == target.exception
64
+
45
65
  node.insns.delete node.cti
46
66
  @nodes.delete node
47
67
  @nodes.delete target
@@ -101,6 +121,9 @@ module Furnace::CFG
101
121
  dom[source]
102
122
  end.reduce(:&)
103
123
 
124
+ # An exception handler header node has no regular sources.
125
+ pred = [] if pred.nil?
126
+
104
127
  current = Set[node].merge(pred)
105
128
  if current != dom[node]
106
129
  dom[node] = current
@@ -150,10 +173,11 @@ module Furnace::CFG
150
173
  nodes.replace all_nodes
151
174
  end
152
175
 
176
+ loops.default = nil
153
177
  loops
154
178
  end
155
179
 
156
- def source_map
180
+ def sources_for(node)
157
181
  unless @source_map
158
182
  @source_map = Hash.new { |h, k| h[k] = [] }
159
183
 
@@ -162,13 +186,18 @@ module Furnace::CFG
162
186
  @source_map[target] << node
163
187
  end
164
188
  end
189
+
190
+ @source_map.each do |node, sources|
191
+ sources.freeze
192
+ end
165
193
  end
166
194
 
167
- @source_map
195
+ @source_map[node]
168
196
  end
169
197
 
170
198
  def flush
171
199
  @source_map = nil
200
+ @label_map.clear
172
201
  end
173
202
 
174
203
  def to_graphviz
@@ -192,6 +221,10 @@ module Furnace::CFG
192
221
  node.target_labels.each_with_index do |label, idx|
193
222
  graph.edge node.label, label, "#{idx}"
194
223
  end
224
+
225
+ if node.exception_label
226
+ graph.edge node.label, node.exception_label, "Exc", color: 'orange'
227
+ end
195
228
  end
196
229
  end
197
230
  end
@@ -2,17 +2,23 @@ module Furnace::CFG
2
2
  class Node
3
3
  attr_reader :cfg, :label
4
4
 
5
- attr_reader :instructions, :control_transfer_instruction
6
- alias :insns :instructions
7
- alias :cti :control_transfer_instruction
5
+ attr_accessor :target_labels, :exception_label
6
+ attr_accessor :instructions, :control_transfer_instruction
8
7
 
9
- def initialize(cfg, label=nil, insns=[], cti=nil, target_labels=[])
8
+ alias :insns :instructions
9
+ alias :insns= :instructions=
10
+ alias :cti :control_transfer_instruction
11
+ alias :cti= :control_transfer_instruction=
12
+
13
+ def initialize(cfg, label=nil, insns=[], cti=nil,
14
+ target_labels=[], exception_label=nil)
10
15
  @cfg, @label = cfg, label
11
16
 
12
17
  @instructions = insns
13
18
  @control_transfer_instruction = cti
14
19
 
15
- @target_labels = target_labels
20
+ @target_labels = target_labels
21
+ @exception_label = exception_label
16
22
  end
17
23
 
18
24
  def target_labels
@@ -22,7 +28,7 @@ module Furnace::CFG
22
28
  def targets
23
29
  @target_labels.map do |label|
24
30
  @cfg.find_node label
25
- end
31
+ end.freeze
26
32
  end
27
33
 
28
34
  def source_labels
@@ -30,7 +36,11 @@ module Furnace::CFG
30
36
  end
31
37
 
32
38
  def sources
33
- @cfg.source_map[self]
39
+ @cfg.sources_for(self)
40
+ end
41
+
42
+ def exception
43
+ @cfg.find_node @exception_label if @exception_label
34
44
  end
35
45
 
36
46
  def exits?
@@ -29,14 +29,21 @@ class Furnace::Graphviz
29
29
  label: label
30
30
  })
31
31
 
32
- @code << %Q{#{name.inspect} [#{options.map { |k,v| "#{k}=#{v}" }.join(",")}];\n}
32
+ @code << %Q{#{name.inspect} #{graphviz_options(options)};\n}
33
33
  end
34
34
 
35
- def edge(from, to, label="")
36
- @code << %Q{#{from.inspect} -> #{to.inspect} [label=#{label.inspect}];\n}
35
+ def edge(from, to, label="", options={})
36
+ options = options.merge({
37
+ label: label.inspect
38
+ })
39
+ @code << %Q{#{from.inspect} -> #{to.inspect} #{graphviz_options(options)};\n}
37
40
  end
38
41
 
39
42
  def to_s
40
43
  @code
41
44
  end
45
+
46
+ def graphviz_options(options)
47
+ "[#{options.map { |k,v| "#{k}=#{v}" }.join(",")}]"
48
+ end
42
49
  end
@@ -9,6 +9,5 @@ require "furnace/transform/rubinius/ast_normalize"
9
9
  require "furnace/transform/generic/label_normalize"
10
10
  require "furnace/transform/generic/cfg_build"
11
11
  require "furnace/transform/generic/cfg_normalize"
12
- require "furnace/transform/generic/anf_build"
13
12
 
14
13
  require "furnace/transform/optimizing/fold_constants"
@@ -1,3 +1,3 @@
1
1
  module Furnace
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: furnace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-19 00:00:00.000000000 Z
12
+ date: 2012-05-06 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Furnace is a static code analysis framework for dynamic languages, aimed
15
15
  at efficient type and behavior inference.
@@ -28,13 +28,6 @@ files:
28
28
  - bin/furnace
29
29
  - furnace.gemspec
30
30
  - lib/furnace.rb
31
- - lib/furnace/anf/edge.rb
32
- - lib/furnace/anf/graph.rb
33
- - lib/furnace/anf/if_node.rb
34
- - lib/furnace/anf/in_node.rb
35
- - lib/furnace/anf/let_node.rb
36
- - lib/furnace/anf/node.rb
37
- - lib/furnace/anf/return_node.rb
38
31
  - lib/furnace/ast.rb
39
32
  - lib/furnace/ast/matcher.rb
40
33
  - lib/furnace/ast/matcher/dsl.rb
@@ -55,7 +48,6 @@ files:
55
48
  - lib/furnace/code/token.rb
56
49
  - lib/furnace/graphviz.rb
57
50
  - lib/furnace/transform.rb
58
- - lib/furnace/transform/generic/anf_build.rb
59
51
  - lib/furnace/transform/generic/cfg_build.rb
60
52
  - lib/furnace/transform/generic/cfg_normalize.rb
61
53
  - lib/furnace/transform/generic/label_normalize.rb
@@ -84,9 +76,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
76
  version: '0'
85
77
  requirements: []
86
78
  rubyforge_project:
87
- rubygems_version: 1.8.17
79
+ rubygems_version: 1.8.23
88
80
  signing_key:
89
81
  specification_version: 3
90
82
  summary: A static code analysis framework
91
83
  test_files: []
92
- has_rdoc:
@@ -1,6 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class Edge < Struct.new(:source, :target, :param)
4
- end
5
- end
6
- end
@@ -1,60 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class Graph
4
- attr_reader :nodes, :edges
5
- attr_accessor :root
6
-
7
- def initialize
8
- @root = nil
9
- @nodes = Set.new
10
- @edges = Set.new
11
- end
12
-
13
- def find(label)
14
- @nodes.find { |node| node.label == label }
15
- end
16
-
17
- def eliminate_dead_code
18
- live_set = search
19
- @nodes &= live_set
20
- end
21
-
22
- def search
23
- seen_set = Set.new
24
- work_set = Set.new
25
-
26
- work_set.add @root
27
-
28
- while work_set.any?
29
- node = work_set.first
30
- work_set.delete node
31
- seen_set.add node
32
-
33
- yield node if block_given?
34
-
35
- node.leaving_edges.map(&:target).each do |target|
36
- work_set.add target unless seen_set.include? target
37
- end
38
- end
39
-
40
- seen_set
41
- end
42
-
43
- def to_graphviz
44
- Graphviz.new do |graph|
45
- @nodes.each do |node|
46
- graph.node node.object_id, node.to_human_readable
47
-
48
- case node
49
- when ANF::IfNode
50
- graph.edge node.object_id, node.leaving_edge(true).target.object_id, "true"
51
- graph.edge node.object_id, node.leaving_edge(false).target.object_id, "false"
52
- when ANF::LetNode, ANF::InNode
53
- graph.edge node.object_id, node.leaving_edge.target.object_id
54
- end
55
- end
56
- end
57
- end
58
- end
59
- end
60
- end
@@ -1,17 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class IfNode < Node
4
- attr_reader :condition
5
-
6
- def initialize(graph, condition)
7
- super(graph)
8
-
9
- @condition = condition
10
- end
11
-
12
- def to_human_readable
13
- "if\n#{humanize @condition}"
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class InNode < Node
4
- attr_reader :expressions
5
-
6
- def initialize(graph, expressions)
7
- super(graph)
8
-
9
- @expressions = expressions
10
- end
11
-
12
- def to_human_readable
13
- "in\n#{@expressions.map { |e| "#{e.to_sexp(1)}" }.join "\n"}"
14
- end
15
- end
16
- end
17
- end
@@ -1,37 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class LetNode < Node
4
- attr_reader :arguments
5
-
6
- def initialize(graph, arguments)
7
- super(graph)
8
-
9
- @arguments = arguments
10
- end
11
-
12
- def try_eliminate
13
- if identity?
14
- entering_edges.each do |edge|
15
- edge.target = leaving_edge.target
16
- end
17
- end
18
- end
19
-
20
- def identity?
21
- @arguments.reduce(true) { |r, (k, v)| r && (v === k) }
22
- end
23
-
24
- def try_propagate
25
- end
26
-
27
- def static?(node)
28
- [ NilClass, TrueClass, FalseClass, Fixnum, Symbol,
29
- AST::LocalVariable, AST::InstanceVariable ].include? node.class
30
- end
31
-
32
- def to_human_readable
33
- "let\n#{@arguments.map { |k, v| " #{k} = #{humanize v}" }.join "\n"}"
34
- end
35
- end
36
- end
37
- end
@@ -1,31 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class Node
4
- attr_reader :graph
5
-
6
- def initialize(graph)
7
- @graph = graph
8
- end
9
-
10
- def leaving_edges
11
- @graph.edges.select { |edge| edge.source == self }
12
- end
13
-
14
- def leaving_edge(param=nil)
15
- @graph.edges.find { |edge| edge.source == self && edge.param == param }
16
- end
17
-
18
- def entering_edges
19
- @graph.edges.select { |edge| edge.target == self }
20
- end
21
-
22
- def humanize(node)
23
- if node.is_a? AST::Node
24
- node.to_sexp
25
- else
26
- node.inspect
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,17 +0,0 @@
1
- module Furnace
2
- module ANF
3
- class ReturnNode < Node
4
- attr_reader :result
5
-
6
- def initialize(graph, result)
7
- super(graph)
8
-
9
- @result = result
10
- end
11
-
12
- def to_human_readable
13
- "return\n#{humanize @result}"
14
- end
15
- end
16
- end
17
- end
@@ -1,153 +0,0 @@
1
- module Furnace
2
- module Transform
3
- module Generic
4
- class ANFBuild
5
- include AST::Visitor
6
-
7
- def transform(cfg, method)
8
- @method_locals = method.local_names
9
-
10
- @last_label = -1
11
- @anf_nodes = Hash.new { |k,v| v }
12
- @anf_edges = []
13
-
14
- @anf = ANF::Graph.new
15
-
16
- cfg.nodes.each do |node|
17
- @locals = {}
18
- @node = node
19
-
20
- @last_anf_node = nil
21
-
22
- # At this point we can have no more than two edges.
23
- @default_edge = node.default_leaving_edge
24
- @other_edge = node.leaving_edge(node.operations.last.metadata[:label])
25
-
26
- # Transform the AST for each node to ANF, removing redundant root nodes
27
- # in the process.
28
- node.operations.delete_if do |operation|
29
- visit operation
30
-
31
- operation.type == :remove
32
- end
33
-
34
- # If there were no nodes created, fallback to default CFG edge.
35
- @last_anf_node ||= @default_edge.target_label
36
-
37
- # If some operations were done just for side effect, add an InNode.
38
- if node.operations.any?
39
- anf_in = ANF::InNode.new(@anf, node.operations)
40
- @anf.nodes.add anf_in
41
-
42
- @anf_edges << [ anf_in, @last_anf_node ]
43
- @last_anf_node = anf_in
44
- end
45
-
46
- # If any locals were rebound, add a LetNode.
47
- if @locals.any? || node.operations.any?
48
- anf_let = ANF::LetNode.new(@anf, passed_locals)
49
- @anf.nodes.add anf_let
50
-
51
- @anf_edges << [ anf_let, @last_anf_node ]
52
- @last_anf_node = anf_let
53
- end
54
-
55
- @anf_nodes[node.label] = @last_anf_node
56
- end
57
-
58
- # The root is a CFG node with label (ip) 0.
59
- @anf.root = @anf_nodes[0]
60
-
61
- @anf_edges.each do |(source_label, target_label, param)|
62
- @anf.edges.add ANF::Edge.new(@anf_nodes[source_label],
63
- @anf_nodes[target_label],
64
- param)
65
- end
66
-
67
- [ @anf, method ]
68
- end
69
-
70
- def passed_locals
71
- map = @method_locals.map do |name|
72
- # Is the name rebound?
73
- if @locals.include?(name)
74
- [ name, @locals[name] ]
75
- # Is it the middle of function?
76
- elsif @node.entering_edges.any?
77
- [ name, AST::LocalVariable.new(name) ]
78
- # Locals default to nil.
79
- else
80
- [ name, nil ]
81
- end
82
- end
83
-
84
- Hash[*map.flatten]
85
- end
86
-
87
- # (set-lvar :var value)
88
- def on_set_lvar(ast_node)
89
- @locals[ast_node.children.first] = ast_node.children.last
90
-
91
- ast_node.update(:remove)
92
- end
93
-
94
- # (jump-if compare_to condition)
95
- def on_jump_if(ast_node)
96
- if ast_node.children.first == true
97
- true_edge, false_edge = @other_edge, @default_edge
98
- else
99
- true_edge, false_edge = @default_edge, @other_edge
100
- end
101
-
102
- true_node = ANF::LetNode.new(@anf, passed_locals)
103
- false_node = ANF::LetNode.new(@anf, passed_locals)
104
- @last_anf_node = ANF::IfNode.new(@anf, ast_node.children.last)
105
-
106
- @anf.nodes.merge [ true_node, false_node, @last_anf_node ]
107
-
108
- @anf_edges << [ @last_anf_node, true_node, true ] <<
109
- [ true_node, true_edge.target_label ]
110
- @anf_edges << [ @last_anf_node, false_node, false ] <<
111
- [ false_node, false_edge.target_label ]
112
-
113
- ast_node.update(:remove)
114
- end
115
-
116
- # (return expression)
117
- def on_return(ast_node)
118
- @last_anf_node = ANF::ReturnNode.new(@anf, ast_node.children.last)
119
-
120
- @anf.nodes.add @last_anf_node
121
-
122
- ast_node.update(:remove)
123
- end
124
-
125
- # (get-lvar :x) -> %x
126
- def on_get_lvar(node)
127
- node.update(:expand, AST::LocalVariable.new(node.children.first))
128
- end
129
-
130
- # AST node labels do not make sense.
131
- def on_any(ast_node)
132
- ast_node.metadata.delete :label
133
- end
134
-
135
- def expand_node(node)
136
- node.update(:expand)
137
- end
138
-
139
- # Immediates do not have to carry metadata anymore.
140
- alias :on_true :expand_node
141
- alias :on_false :expand_node
142
- alias :on_nil :expand_node
143
- alias :on_fixnum :expand_node
144
- alias :on_literal :expand_node
145
-
146
- # We have a near infinite supply of small, unoccupied labels.
147
- def make_label
148
- @last_label -= 1
149
- end
150
- end
151
- end
152
- end
153
- end