furnace 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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