furnace 0.0.1

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.
@@ -0,0 +1,153 @@
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
@@ -0,0 +1,89 @@
1
+ module Furnace
2
+ module Transform
3
+ module Generic
4
+ class VariableTracer
5
+ include AST::Visitor
6
+
7
+ attr_reader :read_set, :write_set
8
+
9
+ def initialize
10
+ @read_set = Set.new
11
+ @write_set = Set.new
12
+ @conflict = false
13
+ end
14
+
15
+ def reset
16
+ @read_set.clear
17
+ @write_set.clear
18
+ @conflict = false
19
+ end
20
+
21
+ def conflict?
22
+ @conflict
23
+ end
24
+
25
+ def on_set_lvar(node)
26
+ var = node.children.first
27
+
28
+ @write_set.add var
29
+ @conflict ||= @read_set.include?(var)
30
+ end
31
+
32
+ def on_get_lvar(node)
33
+ var = node.children.first
34
+
35
+ @read_set.add var
36
+ @conflict ||= @write_set.include?(var)
37
+ end
38
+ end
39
+
40
+ class CFGBuild
41
+ def transform(ast, target_map, method)
42
+ cfg = CFG::Graph.new
43
+
44
+ tracer = VariableTracer.new
45
+
46
+ ast.children.each do |child|
47
+ label = child.metadata[:label]
48
+
49
+ # Transfer control to the next operation directly if this
50
+ # is a jump target.
51
+ if target_map.include? label
52
+ cfg.transfer({ nil => label })
53
+ end
54
+
55
+ # Our CFG must also be easily convertible to ANF/SSA.
56
+ # Split blocks if a non-SSA variable operation is encountered.
57
+ tracer.visit child
58
+
59
+ if tracer.conflict?
60
+ cfg.transfer({ nil => child.metadata[:label] })
61
+ end
62
+
63
+ # Expand current operation.
64
+ cfg.expand label, child
65
+
66
+ # Transfer control non-sequentaly if needed.
67
+ if child.type == :jump
68
+ cfg.transfer({ label => child.children[0] })
69
+ elsif child.type == :jump_if
70
+ cfg.transfer({ label => child.children[0],
71
+ nil => child.next.metadata[:label] })
72
+ elsif child.type == :return
73
+ cfg.transfer({ })
74
+ elsif tracer.conflict?
75
+ # Reset tracer below.
76
+ else
77
+ next
78
+ end
79
+
80
+ # There was a conflict or a control transfer. Reset the tracer.
81
+ tracer.reset
82
+ end
83
+
84
+ [ cfg, method ]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,41 @@
1
+ module Furnace
2
+ module Transform
3
+ module Generic
4
+ class CFGNormalize
5
+ def transform(cfg, method)
6
+ cfg.nodes.each do |node|
7
+ # If a last operation is an unconditional jump, optimize it out.
8
+ last = node.operations.last
9
+ if last.type == :jump
10
+ edge = node.leaving_edge(last.metadata[:label])
11
+
12
+ node.operations.delete last
13
+
14
+ cfg.edges.delete edge
15
+ cfg.edges.add CFG::Edge.new(cfg, nil, node.label, edge.target.label)
16
+ end
17
+
18
+ # Remove no-ops.
19
+ node.operations.delete_if { |op| op.type == :nop }
20
+ end
21
+
22
+ # Remove empty nodes.
23
+ cfg.nodes.delete_if do |node|
24
+ if node.operations.empty?
25
+ node.entering_edges.each do |edge|
26
+ edge.target = node.default_leaving_edge.target
27
+ end
28
+ cfg.edges.subtract node.leaving_edges
29
+
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ [ cfg, method ]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ module Furnace
2
+ module Transform
3
+ module Generic
4
+ class LabelNormalize
5
+ include AST::Visitor
6
+
7
+ def transform(ast, method)
8
+ # Find the minimal label in each operation sub-tree.
9
+ # It's the operation entry point.
10
+ visit ast
11
+
12
+ # Traverse operations in reverse order and bypass all jump_target's,
13
+ # recording the forwarded address in label_map.
14
+ label_map = {}
15
+
16
+ last_real_operation = nil
17
+ ast.children.reverse.each do |child|
18
+ if child.type == :jump_target
19
+ label = last_real_operation
20
+ else
21
+ label = child.metadata[:label]
22
+ last_real_operation = label
23
+ end
24
+
25
+ label_map[child.metadata[:label]] = label
26
+ end
27
+
28
+ # Remove jump_target's.
29
+ ast.children.reject! { |c| c.type == :jump_target }
30
+
31
+ # Find all jumpable labels and substitute the addresses to forward
32
+ # through jump_target's.
33
+ target_map = []
34
+
35
+ ast.children.each do |child|
36
+ if child.type == :jump || child.type == :jump_if
37
+ forwarded_target = label_map[child.children[0]]
38
+ child.children[0] = forwarded_target
39
+
40
+ target_map << forwarded_target
41
+ end
42
+ end
43
+
44
+ [ ast, target_map, method ]
45
+ end
46
+
47
+ def on_any(node)
48
+ return if node.type == :root
49
+
50
+ child_nodes = node.children.select { |c| c.is_a? AST::Node }
51
+
52
+ new_label = child_nodes.map { |c| c.metadata[:label] }.compact.min
53
+ node.metadata[:label] = new_label if new_label
54
+
55
+ child_nodes.each { |c| c.metadata.delete :label }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ module Furnace
2
+ module Transform
3
+ module Optimizing
4
+ class FoldConstants
5
+ def transform(anf, method)
6
+ anf.nodes.each do |node|
7
+ if node.is_a? ANF::LetNode
8
+ node.try_propagate
9
+ node.try_eliminate
10
+ end
11
+ end
12
+ anf.eliminate_dead_code
13
+
14
+ [ anf, method ]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Furnace
2
+ module Transform
3
+ class Pipeline
4
+ def initialize(*stages)
5
+ @stages = stages
6
+ end
7
+
8
+ def run(*sequence)
9
+ @stages.each do |stage|
10
+ sequence = stage.transform *sequence
11
+ end
12
+
13
+ sequence
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ module Furnace
2
+ module Transform
3
+ module Rubinius
4
+ class ASTBuild
5
+ def transform(method)
6
+ stack = []
7
+ map = {}
8
+ serial = 0
9
+
10
+ ast = AST::Node.new(:root)
11
+
12
+ method.decode.each do |opcode|
13
+ ins = opcode.instruction
14
+
15
+ node = AST::Node.new("rbx_#{ins.opcode}")
16
+ node.metadata[:label] = opcode.ip
17
+ node.children += opcode.args
18
+
19
+ # Compute the real value of consumed values.
20
+ case ins.stack_consumed
21
+ when Fixnum
22
+ consumed = ins.stack_consumed
23
+ when Array
24
+ #p ins.stack_consumed, opcode.args
25
+ consumed = ins.stack_consumed[0] + opcode.args.last
26
+ end
27
+
28
+ # Pop consumed values and attach to current node.
29
+ consumed.times.map { map[stack.pop] }.reverse.each do |child|
30
+ child.parent = node
31
+ node.children << child
32
+ end
33
+
34
+ # Push back and map the results.
35
+ if ins.stack_produced == 0 || ins.opcode == :ret
36
+ node.parent = ast
37
+ ast.children << node
38
+ elsif ins.stack_produced == 1
39
+ map[serial] = node
40
+ stack.push serial
41
+
42
+ serial += 1
43
+ else
44
+ raise RuntimeError, "don't know what to do with opcode #{opcode.inspect}"
45
+ end
46
+ end
47
+
48
+ [ ast, method ]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,170 @@
1
+ module Furnace
2
+ module Transform
3
+ module Rubinius
4
+ class ASTNormalize
5
+ include AST::Visitor
6
+
7
+ def transform(ast, method)
8
+ @locals = method.local_names
9
+ @literals = method.literals
10
+
11
+ visit ast
12
+
13
+ [ ast, method ]
14
+ end
15
+
16
+ # (rbx-meta-push-0) -> 0
17
+ def on_rbx_meta_push_0(node)
18
+ node.update(:fixnum, [0], :constant => true)
19
+ end
20
+
21
+ # (rbx-meta-push-1) -> 1
22
+ def on_rbx_meta_push_1(node)
23
+ node.update(:fixnum, [1], :constant => true)
24
+ end
25
+
26
+ # (rbx-meta-push-2) -> 2
27
+ def on_rbx_meta_push_2(node)
28
+ node.update(:fixnum, [2], :constant => true)
29
+ end
30
+
31
+ # (rbx-push-int f) -> f
32
+ def on_rbx_push_int(node)
33
+ node.update(:fixnum, nil, :constant => true)
34
+ end
35
+
36
+ # (rbx-push-literal l) -> l
37
+ def on_rbx_push_literal(node)
38
+ node.update(:literal, nil, :constant => true)
39
+ end
40
+
41
+ # (rbx-push-nil) -> (nil)
42
+ def on_rbx_push_nil(node)
43
+ node.update(:nil, nil, :constant => true)
44
+ end
45
+
46
+ # (rbx-string-dup s) -> (dup s)
47
+ def on_rbx_string_dup(node)
48
+ node.update(:dup)
49
+ end
50
+
51
+ # (rbx-pop x) -> x
52
+ def on_rbx_pop(node)
53
+ child = node.children.first
54
+ child.update(:nop) if child.metadata[:constant]
55
+
56
+ node.update(:expand, [
57
+ child,
58
+ AST::Node.new(:jump_target, [], node.metadata)
59
+ ], nil)
60
+ end
61
+
62
+ # (rbx-*) -> .
63
+ def make_jump_target(node)
64
+ node.update(:jump_target)
65
+ end
66
+ alias :on_rbx_check_interrupts :make_jump_target
67
+ alias :on_rbx_allow_private :make_jump_target
68
+
69
+ # (rbx-push-self) -> (self)
70
+ def on_rbx_push_self(node)
71
+ node.update(:self)
72
+ end
73
+
74
+ # (rbx-push-local n) -> (get-local n)
75
+ def on_rbx_push_local(node)
76
+ node.update(:get_lvar, [ @locals[node.children.first] ])
77
+ end
78
+
79
+ # (rbx-set-local n v) -> (set-local n v)
80
+ def on_rbx_set_local(node)
81
+ node.update(:set_lvar, [ @locals[node.children.first], node.children.last ])
82
+ end
83
+
84
+ # (rbx-push-ivar n) -> (get-ivar n)
85
+ def on_rbx_push_ivar(node)
86
+ node.update(:get_ivar, [ @literals[node.children.first] ])
87
+ end
88
+
89
+ # (rbx-set-ivar n v) -> (set-ivar n v)
90
+ def on_rbx_set_ivar(node)
91
+ node.update(:set_ivar, [ @literals[node.children.first], node.children.last ])
92
+ end
93
+
94
+ # (rbx-push-const-fast n x) -> (const n)
95
+ def on_rbx_push_const_fast(node)
96
+ node.update(:const, [ node.children.first ])
97
+ end
98
+
99
+ # (rbx-find-const n c) -> (const n c)
100
+ def on_rbx_find_const(node)
101
+ node.update(:const, [ @literals[node.children.first], node.children.last ])
102
+ end
103
+
104
+ # (rbx-ret x) -> (return x)
105
+ def on_rbx_ret(node)
106
+ node.update(:return)
107
+ end
108
+
109
+ # (rbx-send-method msg receiver) -> (send msg receiver)
110
+ def on_rbx_send_method(node)
111
+ node.update(:send, [
112
+ AST::MethodName.new(node.children[0]),
113
+ node.children[1]
114
+ ])
115
+ end
116
+
117
+ # (rbx-send-stack msg count receiver args...) -> (send msg receiver args...)
118
+ def on_rbx_send_stack(node)
119
+ node.update(:send, [
120
+ AST::MethodName.new(node.children[0]), # message
121
+ node.children[2], # receiver
122
+ *node.children[3..-1] # args
123
+ ])
124
+ end
125
+
126
+ # (rbx-send-stack-with-block msg count receiver args... block) -> (send-with-block msg receiver args... block)
127
+ def on_rbx_send_stack_with_block(node)
128
+ node.update(:send_with_block, [
129
+ AST::MethodName.new(node.children[0]), # message
130
+ node.children[2], # receiver
131
+ *node.children[3..-1] # args
132
+ ])
133
+ end
134
+
135
+ # (rbx-create-block block) -> (lambda block)
136
+ def on_rbx_create_block(node)
137
+ $block = node.children.first
138
+ node.update(:lambda, [ "FAIL" ])
139
+ end
140
+
141
+ # (rbx-meta-* op receiver arg) -> (send op receiver arg)
142
+ def on_rbx_send_op_any(node)
143
+ node.update(:send, [
144
+ AST::MethodName.new(node.children[0]),
145
+ *node.children[1..-1]
146
+ ])
147
+ end
148
+ alias :on_rbx_meta_send_op_plus :on_rbx_send_op_any
149
+ alias :on_rbx_meta_send_op_minus :on_rbx_send_op_any
150
+ alias :on_rbx_meta_send_op_gt :on_rbx_send_op_any
151
+ alias :on_rbx_meta_send_op_lt :on_rbx_send_op_any
152
+ alias :on_rbx_meta_to_s :on_rbx_send_op_any
153
+
154
+ # (rbx-goto-if-* block condition) -> (jump-if block value condition)
155
+ def on_rbx_goto_if_false(node)
156
+ node.update(:jump_if, [ node.children[0], false, node.children[1] ])
157
+ end
158
+
159
+ def on_rbx_goto_if_true(node)
160
+ node.update(:jump_if, [ node.children[0], true, node.children[1] ])
161
+ end
162
+
163
+ # (rbx-goto block) -> (jump block)
164
+ def on_rbx_goto(node)
165
+ node.update(:jump)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,3 @@
1
+ module Furnace
2
+ VERSION = "0.0.1"
3
+ end
data/lib/furnace.rb ADDED
@@ -0,0 +1,37 @@
1
+ require "furnace/version"
2
+
3
+ require "set"
4
+
5
+ require "furnace/ast/node"
6
+ require "furnace/ast/symbolic_node"
7
+ require "furnace/ast/visitor"
8
+
9
+ require "furnace/cfg/node"
10
+ require "furnace/cfg/edge"
11
+ require "furnace/cfg/graph"
12
+
13
+ require "furnace/anf/node"
14
+ require "furnace/anf/edge"
15
+ require "furnace/anf/let_node"
16
+ require "furnace/anf/in_node"
17
+ require "furnace/anf/if_node"
18
+ require "furnace/anf/return_node"
19
+ require "furnace/anf/graph"
20
+
21
+ require "furnace/transform/pipeline"
22
+
23
+ require "furnace/transform/rubinius/ast_build"
24
+ require "furnace/transform/rubinius/ast_normalize"
25
+
26
+ require "furnace/transform/generic/label_normalize"
27
+ require "furnace/transform/generic/cfg_build"
28
+ require "furnace/transform/generic/cfg_normalize"
29
+ require "furnace/transform/generic/anf_build"
30
+
31
+ require "furnace/transform/optimizing/fold_constants"
32
+
33
+ require "furnace/graphviz"
34
+
35
+ if RUBY_ENGINE != "rbx"
36
+ raise "Sorry, Furnace only works on Rubinius."
37
+ end