furnace 0.0.1

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