furnace 0.3.0.beta1 → 0.3.0.beta2
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 +3 -2
- data/.yardopts +1 -0
- data/Gemfile +1 -0
- data/Rakefile +12 -1
- data/furnace.gemspec +3 -0
- data/lib/furnace/ast/node.rb +102 -17
- data/lib/furnace/ast/{strict_visitor.rb → processor.rb} +4 -4
- data/lib/furnace/ast.rb +17 -8
- data/lib/furnace/cfg/algorithms.rb +193 -0
- data/lib/furnace/cfg/graph.rb +5 -187
- data/lib/furnace/cfg.rb +5 -4
- data/lib/furnace/version.rb +1 -1
- data/lib/furnace.rb +7 -9
- data/test/ast_test.rb +166 -0
- data/test/test_helper.rb +36 -0
- metadata +43 -15
- data/lib/furnace/ast/matcher/dsl.rb +0 -50
- data/lib/furnace/ast/matcher/special.rb +0 -20
- data/lib/furnace/ast/matcher.rb +0 -198
- data/lib/furnace/ast/visitor.rb +0 -50
- data/lib/furnace/code/newline_token.rb +0 -9
- data/lib/furnace/code/nonterminal_token.rb +0 -16
- data/lib/furnace/code/separated_token.rb +0 -17
- data/lib/furnace/code/surrounded_token.rb +0 -21
- data/lib/furnace/code/terminal_token.rb +0 -9
- data/lib/furnace/code/token.rb +0 -73
- data/lib/furnace/code.rb +0 -10
data/.gitignore
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-m markdown --protected
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/furnace.gemspec
CHANGED
@@ -16,4 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
s.add_development_dependency 'bacon', '~> 1.1'
|
19
22
|
end
|
data/lib/furnace/ast/node.rb
CHANGED
@@ -1,28 +1,105 @@
|
|
1
1
|
module Furnace::AST
|
2
|
+
# Node is an immutable class, instances of which represent abstract
|
3
|
+
# syntax tree nodes. It combines semantic information (i.e. anything
|
4
|
+
# that affects the algorithmic properties of a program) with
|
5
|
+
# meta-information (line numbers or compiler intermediates).
|
6
|
+
#
|
7
|
+
# Notes on inheritance
|
8
|
+
# ====================
|
9
|
+
#
|
10
|
+
# The distinction between semantics and metadata is important. Complete
|
11
|
+
# semantic information should be contained within just the {#type} and
|
12
|
+
# {#children} of a Node instance; in other words, if an AST was to be
|
13
|
+
# stripped of all meta-information, it should remain a valid AST which
|
14
|
+
# could be successfully processed to yield a result with the same
|
15
|
+
# algorithmic properties.
|
16
|
+
#
|
17
|
+
# Thus, Node should never be inherited in order to define methods which
|
18
|
+
# affect or return semantic information, such as getters for `class_name`,
|
19
|
+
# `superclass` and `body` in the case of a hypothetical `ClassNode`. The
|
20
|
+
# correct solution is to use a generic Node with a {#type} of `:class`
|
21
|
+
# and three children. See also {Processor} for tips on working with such
|
22
|
+
# ASTs.
|
23
|
+
#
|
24
|
+
# On the other hand, Node can and should be inherited to define
|
25
|
+
# application-specific metadata (see also {#initialize}) or customize the
|
26
|
+
# printing format. It is expected that an application would have one or two
|
27
|
+
# such classes and use them across the entire codebase.
|
28
|
+
#
|
29
|
+
# The rationale for this pattern is extensibility and maintainability.
|
30
|
+
# Unlike static ones, dynamic languages do not require the presence of a
|
31
|
+
# predefined, rigid structure, nor does it improve dispatch efficiency,
|
32
|
+
# and while such a structure can certainly be defined, it does not add
|
33
|
+
# any value but incurs a maintaining cost.
|
34
|
+
# For example, extending the AST even with a transformation-local
|
35
|
+
# temporary node type requires making globally visible changes to
|
36
|
+
# the codebase.
|
37
|
+
#
|
2
38
|
class Node
|
3
|
-
|
39
|
+
# Returns the type of this node.
|
40
|
+
# @return [Symbol]
|
41
|
+
attr_reader :type
|
4
42
|
|
43
|
+
# Returns the children of this node.
|
44
|
+
# The returned value is frozen.
|
45
|
+
# @return [Array]
|
46
|
+
attr_reader :children
|
47
|
+
|
48
|
+
# Constructs a new instance of Node.
|
49
|
+
#
|
50
|
+
# The arguments `type` and `children` are converted with `to_sym` and
|
51
|
+
# `to_a` respectively. Additionally, the result of converting `children`
|
52
|
+
# is frozen. While mutating the arguments is generally considered harmful,
|
53
|
+
# the most common case is to pass an array literal to the constructor. If
|
54
|
+
# your code does not expect the argument to be frozen, use `#dup`.
|
55
|
+
#
|
56
|
+
# The `properties` hash is passed to {#assign_properties}.
|
5
57
|
def initialize(type, children=[], properties={})
|
6
|
-
@type, @children = type.to_sym, children.to_a
|
58
|
+
@type, @children = type.to_sym, children.to_a.freeze
|
59
|
+
|
60
|
+
assign_properties(properties)
|
61
|
+
|
62
|
+
freeze
|
63
|
+
end
|
64
|
+
|
65
|
+
# By default, each entry in the `properties` hash is assigned to
|
66
|
+
# a local variable in this instance of Node. A subclass should define
|
67
|
+
# attribute readers for such variables. The values passed in the hash
|
68
|
+
# are not frozen or whitelisted; such behavior can also be implemented\
|
69
|
+
# by subclassing Node and overriding this method.
|
70
|
+
def assign_properties(properties)
|
7
71
|
properties.each do |name, value|
|
8
72
|
instance_variable_set :"@#{name}", value
|
9
73
|
end
|
10
|
-
freeze
|
11
74
|
end
|
75
|
+
protected :assign_properties
|
12
76
|
|
13
|
-
|
14
|
-
|
15
|
-
|
77
|
+
protected :dup
|
78
|
+
|
79
|
+
# Returns a new instance of Node where non-nil arguments replace the
|
80
|
+
# corresponding fields of `self`.
|
81
|
+
#
|
82
|
+
# For example, `Node.new(:foo, [ 1, 2 ]).updated(:bar)` would yield
|
83
|
+
# `(bar 1 2)`, and `Node.new(:foo, [ 1, 2 ]).updated(nil, [])` would
|
84
|
+
# yield `(foo)`.
|
85
|
+
#
|
86
|
+
# If the resulting node would be identical to `self`, does nothing.
|
87
|
+
def updated(type=nil, children=nil, properties=nil)
|
88
|
+
new_type = type || @type
|
89
|
+
new_children = children || @children
|
90
|
+
new_properties = properties || {}
|
16
91
|
|
17
92
|
if @type == new_type &&
|
18
93
|
@children == new_children &&
|
19
|
-
properties.
|
94
|
+
properties.nil?
|
20
95
|
self
|
21
96
|
else
|
22
|
-
dup.send :initialize, new_type, new_children,
|
97
|
+
dup.send :initialize, new_type, new_children, new_properties
|
23
98
|
end
|
24
99
|
end
|
25
100
|
|
101
|
+
# Compares `self` to `other`, possibly converting with `to_ast`. Only
|
102
|
+
# `type` and `children` are compared; metadata is deliberately ignored.
|
26
103
|
def ==(other)
|
27
104
|
if equal?(other)
|
28
105
|
true
|
@@ -35,35 +112,43 @@ module Furnace::AST
|
|
35
112
|
end
|
36
113
|
end
|
37
114
|
|
115
|
+
# Converts `self` to a concise s-expression, omitting any children.
|
38
116
|
def to_s
|
39
117
|
"(#{fancy_type} ...)"
|
40
118
|
end
|
41
119
|
|
120
|
+
# Converts `self` to a pretty-printed s-expression.
|
42
121
|
def to_sexp(indent=0)
|
43
|
-
|
122
|
+
sexp = "#{" " * indent}(#{fancy_type}"
|
123
|
+
|
124
|
+
first_node_child = children.index do |child|
|
125
|
+
child.is_a?(Node) || child.is_a?(Array)
|
126
|
+
end || children.count
|
44
127
|
|
45
|
-
children.
|
46
|
-
if
|
47
|
-
|
48
|
-
child.children.any? { |c| c.is_a?(Node) || c.is_a?(Array) })
|
49
|
-
str << "\n#{child.to_sexp(indent + 1)}"
|
128
|
+
children.each_with_index do |child, idx|
|
129
|
+
if child.is_a?(Node) && idx >= first_node_child
|
130
|
+
sexp << "\n#{child.to_sexp(indent + 1)}"
|
50
131
|
else
|
51
|
-
|
132
|
+
sexp << " #{child.inspect}"
|
52
133
|
end
|
53
134
|
end
|
54
135
|
|
55
|
-
|
136
|
+
sexp << ")"
|
56
137
|
|
57
|
-
|
138
|
+
sexp
|
58
139
|
end
|
59
140
|
alias :inspect :to_sexp
|
60
141
|
|
142
|
+
# Returns `self`.
|
61
143
|
def to_ast
|
62
144
|
self
|
63
145
|
end
|
64
146
|
|
65
147
|
protected
|
66
148
|
|
149
|
+
# Returns `@type` with all underscores replaced by dashes. This allows
|
150
|
+
# to write symbol literals without quotes in Ruby sources and yet have
|
151
|
+
# nicely looking s-expressions.
|
67
152
|
def fancy_type
|
68
153
|
@type.to_s.gsub('_', '-')
|
69
154
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Furnace::AST
|
2
|
-
module
|
3
|
-
def
|
2
|
+
module Processor
|
3
|
+
def process(node)
|
4
4
|
if node
|
5
5
|
# Invoke a specific handler
|
6
6
|
on_handler = :"on_#{node.type}"
|
@@ -16,9 +16,9 @@ module Furnace::AST
|
|
16
16
|
node
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def process_all(nodes)
|
20
20
|
nodes.map do |node|
|
21
|
-
|
21
|
+
process node
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
data/lib/furnace/ast.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
|
-
|
1
|
+
module Furnace
|
2
|
+
# Furnace::AST is a library for manipulating abstract syntax trees.
|
3
|
+
#
|
4
|
+
# It embraces immutability; each AST node is inherently frozen at
|
5
|
+
# creation, and updating a child node requires recreating that node
|
6
|
+
# and its every parent, recursively.
|
7
|
+
# This is a design choice. It does create significant pressure on
|
8
|
+
# garbage collector, but completely eliminates all concurrency
|
9
|
+
# and aliasing problems.
|
10
|
+
#
|
11
|
+
# See also {Node} and {Processor} for additional
|
12
|
+
# recommendations and design patterns.
|
13
|
+
module AST
|
14
|
+
end
|
2
15
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require "furnace/ast/matcher/special"
|
8
|
-
require "furnace/ast/matcher/dsl"
|
9
|
-
require "furnace/ast/matcher"
|
16
|
+
require_relative "ast/node"
|
17
|
+
require_relative "ast/processor"
|
18
|
+
end
|
@@ -0,0 +1,193 @@
|
|
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
|
data/lib/furnace/cfg/graph.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Furnace::CFG
|
2
2
|
class Graph
|
3
|
+
include Algorithms
|
4
|
+
|
3
5
|
attr_reader :nodes
|
4
6
|
attr_accessor :entry, :exit
|
5
7
|
|
@@ -21,190 +23,7 @@ module Furnace::CFG
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
def
|
25
|
-
worklist = Set[ entry, exit ]
|
26
|
-
reachable = Set[]
|
27
|
-
|
28
|
-
while worklist.any?
|
29
|
-
node = worklist.first
|
30
|
-
worklist.delete node
|
31
|
-
reachable.add node
|
32
|
-
|
33
|
-
node.targets.each do |target|
|
34
|
-
unless reachable.include? target
|
35
|
-
worklist.add target
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
if node.exception
|
40
|
-
unless reachable.include? node.exception
|
41
|
-
worklist.add node.exception
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
@nodes.each do |node|
|
47
|
-
unless reachable.include? node
|
48
|
-
@nodes.delete node
|
49
|
-
yield node if block_given?
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
flush
|
54
|
-
end
|
55
|
-
|
56
|
-
def merge_redundant!
|
57
|
-
worklist = @nodes.dup
|
58
|
-
while worklist.any?
|
59
|
-
node = worklist.first
|
60
|
-
worklist.delete node
|
61
|
-
|
62
|
-
target = node.targets[0]
|
63
|
-
next if target == @exit
|
64
|
-
|
65
|
-
# Skip explicitly non-redundant nodes
|
66
|
-
if node.metadata[:keep]
|
67
|
-
next
|
68
|
-
end
|
69
|
-
|
70
|
-
if node.targets.uniq == [target] &&
|
71
|
-
target.sources.uniq == [node] &&
|
72
|
-
node.exception == target.exception
|
73
|
-
|
74
|
-
yield node, target if block_given?
|
75
|
-
|
76
|
-
node.insns.delete node.cti
|
77
|
-
@nodes.delete target
|
78
|
-
worklist.delete target
|
79
|
-
|
80
|
-
node.insns.concat target.insns
|
81
|
-
node.cti = target.cti
|
82
|
-
node.target_labels = target.target_labels
|
83
|
-
|
84
|
-
worklist.add node
|
85
|
-
|
86
|
-
flush
|
87
|
-
elsif node.targets.count == 1 &&
|
88
|
-
node.insns.empty?
|
89
|
-
|
90
|
-
target = node.targets.first
|
91
|
-
|
92
|
-
yield target, node if block_given?
|
93
|
-
|
94
|
-
node.sources.each do |source|
|
95
|
-
index = source.targets.index(node)
|
96
|
-
source.target_labels[index] = target.label
|
97
|
-
end
|
98
|
-
|
99
|
-
@nodes.delete node
|
100
|
-
worklist.delete node
|
101
|
-
|
102
|
-
if @entry == node
|
103
|
-
@entry = target
|
104
|
-
end
|
105
|
-
|
106
|
-
flush
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Shamelessly stolen from
|
112
|
-
# http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf
|
113
|
-
def compute_generic_domination(start, forward)
|
114
|
-
# values of β will give rise to dom!
|
115
|
-
dom = { start => Set[start] }
|
116
|
-
|
117
|
-
@nodes.each do |node|
|
118
|
-
next if node == start
|
119
|
-
dom[node] = @nodes.dup
|
120
|
-
end
|
121
|
-
|
122
|
-
change = true
|
123
|
-
while change
|
124
|
-
change = false
|
125
|
-
@nodes.each do |node|
|
126
|
-
next if node == start
|
127
|
-
|
128
|
-
# Are we computing dominators or postdominators?
|
129
|
-
if forward
|
130
|
-
edges = node.sources + node.exception_sources
|
131
|
-
elsif node.exception.nil?
|
132
|
-
edges = node.targets
|
133
|
-
else
|
134
|
-
edges = node.targets + [ node.exception ]
|
135
|
-
end
|
136
|
-
|
137
|
-
# Key Idea [for dominators]
|
138
|
-
# If a node dominates all
|
139
|
-
# predecessors of node n, then it
|
140
|
-
# also dominates node n.
|
141
|
-
pred = edges.map do |source|
|
142
|
-
dom[source]
|
143
|
-
end.reduce(:&)
|
144
|
-
|
145
|
-
# An exception handler header node has no regular sources.
|
146
|
-
pred = [] if pred.nil?
|
147
|
-
|
148
|
-
current = Set[node].merge(pred)
|
149
|
-
if current != dom[node]
|
150
|
-
dom[node] = current
|
151
|
-
change = true
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
dom
|
157
|
-
end
|
158
|
-
|
159
|
-
def dominators
|
160
|
-
@dominators ||= compute_generic_domination(@entry, true)
|
161
|
-
end
|
162
|
-
|
163
|
-
def postdominators
|
164
|
-
@postdominators ||= compute_generic_domination(@exit, false)
|
165
|
-
end
|
166
|
-
|
167
|
-
# See also {#dominators} for references.
|
168
|
-
def identify_loops
|
169
|
-
loops = Hash.new { |h,k| h[k] = Set.new }
|
170
|
-
|
171
|
-
dom = dominators
|
172
|
-
|
173
|
-
@nodes.each do |node|
|
174
|
-
node.targets.each do |target|
|
175
|
-
# Back edges
|
176
|
-
# A back edge of a natural loop is one whose
|
177
|
-
# target dominates its source.
|
178
|
-
if dom[node].include? target
|
179
|
-
loops[target].add node
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
# At this point, +loops+ contains a list of all nodes
|
185
|
-
# which have a back edge to the loop header. Expand
|
186
|
-
# it to the list of all nodes in the loop.
|
187
|
-
loops.each do |header, nodes|
|
188
|
-
# Natural loop
|
189
|
-
# The natural loop of a back edge (m→n), where
|
190
|
-
# n dominates m, is the set of nodes x such that n
|
191
|
-
# dominates x and there is a path from x to m not
|
192
|
-
# containing n.
|
193
|
-
pre_header = dom[header]
|
194
|
-
all_nodes = Set[header]
|
195
|
-
|
196
|
-
nodes.each do |node|
|
197
|
-
all_nodes.merge(dom[node] - pre_header)
|
198
|
-
end
|
199
|
-
|
200
|
-
nodes.replace all_nodes
|
201
|
-
end
|
202
|
-
|
203
|
-
loops.default = nil
|
204
|
-
loops
|
205
|
-
end
|
206
|
-
|
207
|
-
def sources_for(node, exceptions=false)
|
26
|
+
def sources_for(node, find_exceptions=false)
|
208
27
|
unless @source_map
|
209
28
|
@source_map = Hash.new { |h, k| h[k] = [] }
|
210
29
|
@exception_source_map = Hash.new { |h, k| h[k] = [] }
|
@@ -226,7 +45,7 @@ module Furnace::CFG
|
|
226
45
|
end
|
227
46
|
end
|
228
47
|
|
229
|
-
if
|
48
|
+
if find_exceptions
|
230
49
|
@exception_source_map[node]
|
231
50
|
else
|
232
51
|
@source_map[node]
|
@@ -237,8 +56,7 @@ module Furnace::CFG
|
|
237
56
|
@source_map = nil
|
238
57
|
@label_map.clear
|
239
58
|
|
240
|
-
|
241
|
-
@postdominators = nil
|
59
|
+
super if defined?(super)
|
242
60
|
end
|
243
61
|
|
244
62
|
def to_graphviz
|
data/lib/furnace/cfg.rb
CHANGED
data/lib/furnace/version.rb
CHANGED