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 CHANGED
@@ -1,6 +1,7 @@
1
- *.gem
2
1
  .bundle
3
2
  Gemfile.lock
4
3
  pkg/*
5
4
  .rbx/
6
- *.sublime-*
5
+ *.sublime-*
6
+ doc/
7
+ .yardoc/
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ -m markdown --protected
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in furnace.gemspec
4
4
  gemspec
5
+ gem 'bacon', github: 'chneukirchen/bacon'
data/Rakefile CHANGED
@@ -1 +1,12 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'bundler/setup'
3
+
4
+ task :default => :test
5
+
6
+ task :test do
7
+ require 'bacon'
8
+ Bacon.summary_at_exit
9
+ Dir["test/**/*_test.rb"].each do |file|
10
+ load file
11
+ end
12
+ end
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
@@ -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
- attr_reader :type, :children
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
- def update(type=nil, children=nil, properties={})
14
- new_type = type || @type
15
- new_children = children || @children
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.empty?
94
+ properties.nil?
20
95
  self
21
96
  else
22
- dup.send :initialize, new_type, new_children, properties
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
- str = "#{" " * indent}(#{fancy_type}"
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.each do |child|
46
- if (!children[0].is_a?(Node) && child.is_a?(Node)) ||
47
- (children[0].is_a?(Node) && child.is_a?(Node) &&
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
- str << " #{child.inspect}"
132
+ sexp << " #{child.inspect}"
52
133
  end
53
134
  end
54
135
 
55
- str << ")"
136
+ sexp << ")"
56
137
 
57
- str
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 StrictVisitor
3
- def visit(node)
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 visit_all(nodes)
19
+ def process_all(nodes)
20
20
  nodes.map do |node|
21
- visit node
21
+ process node
22
22
  end
23
23
  end
24
24
 
data/lib/furnace/ast.rb CHANGED
@@ -1,9 +1,18 @@
1
- require "furnace"
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
- require "furnace/ast/node"
4
- require "furnace/ast/visitor"
5
- require "furnace/ast/strict_visitor"
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
@@ -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 eliminate_unreachable!
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 exceptions
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
- @dominators = nil
241
- @postdominators = nil
59
+ super if defined?(super)
242
60
  end
243
61
 
244
62
  def to_graphviz
data/lib/furnace/cfg.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "set"
2
2
 
3
- require "furnace"
4
-
5
- require "furnace/cfg/graph"
6
- require "furnace/cfg/node"
3
+ module Furnace
4
+ require_relative "cfg/node"
5
+ require_relative "cfg/algorithms"
6
+ require_relative "cfg/graph"
7
+ end
@@ -1,3 +1,3 @@
1
1
  module Furnace
2
- VERSION = "0.3.0.beta1"
2
+ VERSION = "0.3.0.beta2"
3
3
  end