furnace 0.3.1 → 0.4.0.beta.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.
- data/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +1 -2
- data/{LICENSE → LICENSE.MIT} +14 -14
- data/Rakefile +1 -5
- data/furnace.gemspec +7 -3
- data/lib/furnace/ast/node.rb +14 -0
- data/lib/furnace/ast/processor.rb +7 -7
- data/lib/furnace/ssa/argument.rb +23 -0
- data/lib/furnace/ssa/basic_block.rb +117 -0
- data/lib/furnace/ssa/builder.rb +100 -0
- data/lib/furnace/ssa/constant.rb +43 -0
- data/lib/furnace/ssa/function.rb +191 -0
- data/lib/furnace/ssa/generic_instruction.rb +14 -0
- data/lib/furnace/ssa/generic_type.rb +16 -0
- data/lib/furnace/ssa/instruction.rb +92 -0
- data/lib/furnace/ssa/instruction_syntax.rb +109 -0
- data/lib/furnace/ssa/instructions/branch.rb +11 -0
- data/lib/furnace/ssa/instructions/phi.rb +63 -0
- data/lib/furnace/ssa/instructions/return.rb +11 -0
- data/lib/furnace/ssa/module.rb +52 -0
- data/lib/furnace/ssa/named_value.rb +25 -0
- data/lib/furnace/ssa/pretty_printer.rb +113 -0
- data/lib/furnace/ssa/terminator_instruction.rb +24 -0
- data/lib/furnace/ssa/type.rb +27 -0
- data/lib/furnace/ssa/types/basic_block.rb +11 -0
- data/lib/furnace/ssa/types/function.rb +11 -0
- data/lib/furnace/ssa/types/void.rb +15 -0
- data/lib/furnace/ssa/user.rb +84 -0
- data/lib/furnace/ssa/value.rb +62 -0
- data/lib/furnace/ssa.rb +66 -0
- data/lib/furnace/transform/iterative.rb +27 -0
- data/lib/furnace/transform/pipeline.rb +3 -3
- data/lib/furnace/version.rb +1 -1
- data/lib/furnace.rb +3 -3
- data/test/ast_test.rb +32 -3
- data/test/ssa_test.rb +1129 -0
- data/test/test_helper.rb +17 -28
- data/test/transform_test.rb +74 -0
- metadata +136 -58
- data/lib/furnace/cfg/algorithms.rb +0 -193
- data/lib/furnace/cfg/graph.rb +0 -99
- data/lib/furnace/cfg/node.rb +0 -78
- data/lib/furnace/cfg.rb +0 -7
- data/lib/furnace/transform/iterative_process.rb +0 -26
@@ -0,0 +1,92 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::Instruction < SSA::User
|
3
|
+
def self.opcode
|
4
|
+
@opcode ||= SSA.class_name_to_opcode(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.syntax(&block)
|
8
|
+
SSA::InstructionSyntax.new(self).evaluate(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :basic_block
|
12
|
+
|
13
|
+
def initialize(basic_block, operands=[], name=nil)
|
14
|
+
super(basic_block.function, operands, name)
|
15
|
+
@basic_block = basic_block
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_copy(original)
|
19
|
+
super
|
20
|
+
|
21
|
+
@operands = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def opcode
|
25
|
+
self.class.opcode
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove
|
29
|
+
@basic_block.remove self
|
30
|
+
detach
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace_with(value)
|
34
|
+
replace_all_uses_with value
|
35
|
+
|
36
|
+
if value.is_a? SSA::Instruction
|
37
|
+
@basic_block.replace self, value
|
38
|
+
detach
|
39
|
+
else
|
40
|
+
remove
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_side_effects?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def terminator?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def pretty_print(p=SSA::PrettyPrinter.new)
|
53
|
+
unless type == SSA.void
|
54
|
+
p.type type
|
55
|
+
p.name name
|
56
|
+
p.text '='
|
57
|
+
end
|
58
|
+
|
59
|
+
if valid?
|
60
|
+
p.keyword opcode
|
61
|
+
else
|
62
|
+
p.keyword_invalid opcode
|
63
|
+
end
|
64
|
+
|
65
|
+
pretty_parameters(p)
|
66
|
+
pretty_operands(p)
|
67
|
+
|
68
|
+
p
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect_as_value(p=SSA::PrettyPrinter.new)
|
72
|
+
if type == SSA.void
|
73
|
+
p.type type
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def pretty_parameters(p)
|
82
|
+
end
|
83
|
+
|
84
|
+
def pretty_operands(p)
|
85
|
+
if @operands
|
86
|
+
p.values @operands
|
87
|
+
else
|
88
|
+
p.text '<DETACHED>'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::InstructionSyntax
|
3
|
+
def initialize(klass)
|
4
|
+
@klass = klass
|
5
|
+
@operands = {}
|
6
|
+
@splat = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def evaluate
|
10
|
+
yield self
|
11
|
+
|
12
|
+
codegen
|
13
|
+
end
|
14
|
+
|
15
|
+
def operand(name, type=nil)
|
16
|
+
check_for_splat
|
17
|
+
|
18
|
+
type = type.to_type unless type.nil?
|
19
|
+
@operands[name.to_sym] = type
|
20
|
+
end
|
21
|
+
|
22
|
+
def splat(name)
|
23
|
+
check_for_splat
|
24
|
+
|
25
|
+
@splat = name.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def check_for_splat
|
31
|
+
if @splat
|
32
|
+
raise ArgumentError, "There should be at most one splat operand in tail position"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def codegen
|
37
|
+
operands, splat = @operands, @splat
|
38
|
+
|
39
|
+
@klass.class_eval do
|
40
|
+
operands.each_with_index do |(operand, type), index|
|
41
|
+
define_method(operand) do
|
42
|
+
@operands[index]
|
43
|
+
end
|
44
|
+
|
45
|
+
define_method(:"#{operand}=") do |value|
|
46
|
+
value = value.to_value
|
47
|
+
|
48
|
+
@operands[index].remove_use self if @operands[index]
|
49
|
+
@operands[index] = value
|
50
|
+
value.add_use self if value
|
51
|
+
|
52
|
+
value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if splat
|
57
|
+
define_method splat do
|
58
|
+
@operands[operands.size..-1]
|
59
|
+
end
|
60
|
+
|
61
|
+
define_method(:"#{splat}=") do |values|
|
62
|
+
values = values.map(&:to_value)
|
63
|
+
|
64
|
+
update_use_lists do
|
65
|
+
@operands[operands.size, @operands.size - operands.size] = values
|
66
|
+
end
|
67
|
+
|
68
|
+
values
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
define_method(:operands=) do |values|
|
73
|
+
if splat && values.size < operands.size
|
74
|
+
raise ArgumentError, "Not enough operands provided: #{values.size} for #{operands.size}"
|
75
|
+
elsif !splat && values.size != operands.size
|
76
|
+
raise ArgumentError, "Incorrect number of operands provided: #{values.size} for #{operands.size}"
|
77
|
+
end
|
78
|
+
|
79
|
+
values = values.map(&:to_value)
|
80
|
+
|
81
|
+
update_use_lists do
|
82
|
+
@operands = values
|
83
|
+
end
|
84
|
+
|
85
|
+
verify!
|
86
|
+
|
87
|
+
values
|
88
|
+
end
|
89
|
+
|
90
|
+
define_method(:verify!) do |ignore_nil_types=true|
|
91
|
+
return if @operands.nil?
|
92
|
+
|
93
|
+
operands.each_with_index do |(operand, type), index|
|
94
|
+
next if type.nil?
|
95
|
+
|
96
|
+
value = send operand
|
97
|
+
next if ignore_nil_types && value.type.nil?
|
98
|
+
|
99
|
+
if value.type.nil? || !value.type.subtype_of?(type)
|
100
|
+
raise TypeError, "Wrong type for operand ##{index + 1} `#{operand}': #{SSA.inspect_type type} is expected, #{SSA.inspect_type value.type} is present"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::PhiInsn < SSA::GenericInstruction
|
3
|
+
def initialize(basic_block, type, operands={}, name=nil)
|
4
|
+
super(basic_block, type, operands, name)
|
5
|
+
end
|
6
|
+
|
7
|
+
def each_operand(&block)
|
8
|
+
return to_enum(:each_operand) if block.nil?
|
9
|
+
|
10
|
+
if @operands
|
11
|
+
@operands.each do |basic_block, value|
|
12
|
+
yield basic_block
|
13
|
+
yield value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def operands=(operands)
|
19
|
+
update_use_lists do
|
20
|
+
@operands = operands
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def translate_operands(map)
|
25
|
+
Hash[@operands.map do |basic_block, value|
|
26
|
+
[ map[basic_block], map[value] ]
|
27
|
+
end]
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def pretty_operands(p)
|
33
|
+
@operands.each_with_index do |(basic_block, value), index|
|
34
|
+
p.name basic_block.name
|
35
|
+
p.text '=>'
|
36
|
+
value.inspect_as_value p
|
37
|
+
|
38
|
+
p << ',' if index < @operands.count - 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def replace_uses_of_operands(use, new_use)
|
43
|
+
if @operands.include? use
|
44
|
+
value = @operands[use]
|
45
|
+
@operands.delete use
|
46
|
+
@operands[new_use] = value
|
47
|
+
|
48
|
+
true
|
49
|
+
else
|
50
|
+
found = false
|
51
|
+
|
52
|
+
@operands.each do |basic_block, operand|
|
53
|
+
if operand == use
|
54
|
+
found = true
|
55
|
+
@operands[basic_block] = new_use
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
found
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::Module
|
3
|
+
def initialize
|
4
|
+
@functions = {}
|
5
|
+
@next_id = 0
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_a
|
9
|
+
@functions.values
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
@functions.values.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def include?(name)
|
17
|
+
@functions.include? name
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](name)
|
21
|
+
unless @functions.include? name
|
22
|
+
raise ArgumentError, "function #{name} is not found"
|
23
|
+
end
|
24
|
+
|
25
|
+
@functions[name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(function, name_prefix=nil)
|
29
|
+
if name_prefix ||
|
30
|
+
function.name.nil? ||
|
31
|
+
@functions.include?(function.name)
|
32
|
+
|
33
|
+
basename = name_prefix || function.name || 'function'
|
34
|
+
basename = basename.gsub /;\d+$/, ''
|
35
|
+
|
36
|
+
function.name = "#{basename};#{make_id}"
|
37
|
+
end
|
38
|
+
|
39
|
+
@functions[function.name] = function
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove(name)
|
43
|
+
@functions.delete name
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def make_id
|
49
|
+
@next_id += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::NamedValue < SSA::Value
|
3
|
+
attr_accessor :function
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(function, name)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@function = function
|
10
|
+
self.name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
def name=(name)
|
14
|
+
@name = @function.make_name(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect_as_value(p=SSA::PrettyPrinter.new)
|
18
|
+
p.name name
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
pretty_print
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'ansi'
|
2
|
+
|
3
|
+
module Furnace
|
4
|
+
class SSA::PrettyPrinter
|
5
|
+
@colorize = true
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :colorize
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(colorize=self.class.colorize)
|
12
|
+
@colorize = colorize
|
13
|
+
@buffer = ""
|
14
|
+
@need_space = false
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
@buffer
|
21
|
+
end
|
22
|
+
|
23
|
+
alias to_str to_s
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
to_s == other
|
27
|
+
end
|
28
|
+
|
29
|
+
def =~(other)
|
30
|
+
to_s =~ other
|
31
|
+
end
|
32
|
+
|
33
|
+
def <<(what)
|
34
|
+
@buffer << what
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def text(*what)
|
40
|
+
what = what.map(&:to_s)
|
41
|
+
return if what.all?(&:empty?)
|
42
|
+
|
43
|
+
ensure_space do
|
44
|
+
self << what.join
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def newline
|
49
|
+
@need_space = false
|
50
|
+
|
51
|
+
self << "\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
def name(what)
|
55
|
+
text '%', what
|
56
|
+
end
|
57
|
+
|
58
|
+
def type(what)
|
59
|
+
text with_ansi(:green) { Furnace::SSA.inspect_type(what) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def keyword(what)
|
63
|
+
text with_ansi(:bright, :white) { what.to_s }
|
64
|
+
end
|
65
|
+
|
66
|
+
def keyword_invalid(what)
|
67
|
+
if @colorize
|
68
|
+
text with_ansi(:bright, :red) { what.to_s }
|
69
|
+
else
|
70
|
+
text "!#{what}".to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def objects(objects, separator=",", printer=:pretty_print)
|
75
|
+
objects.each_with_index do |object, index|
|
76
|
+
object.send(printer, self)
|
77
|
+
|
78
|
+
self << separator if index < objects.count - 1
|
79
|
+
end
|
80
|
+
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def values(values, separator=",")
|
85
|
+
objects(values, separator, :inspect_as_value)
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def with_ansi(*colors)
|
91
|
+
string = yield
|
92
|
+
|
93
|
+
if @colorize
|
94
|
+
ANSI::Code.ansi(yield, *colors)
|
95
|
+
else
|
96
|
+
yield
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def ensure_space(need_space_after=true)
|
101
|
+
if @need_space
|
102
|
+
self << " "
|
103
|
+
@need_space = false
|
104
|
+
end
|
105
|
+
|
106
|
+
yield
|
107
|
+
|
108
|
+
@need_space = need_space_after
|
109
|
+
|
110
|
+
self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::TerminatorInstruction < SSA::Instruction
|
3
|
+
def has_side_effects?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
|
7
|
+
def terminator?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def exits?
|
12
|
+
raise NotImplementedError, "reimplement SSA::TerminatorInstruction#exits? in a subclass"
|
13
|
+
end
|
14
|
+
|
15
|
+
def successors
|
16
|
+
operands.
|
17
|
+
select do |value|
|
18
|
+
value.type == SSA::BasicBlockType.instance
|
19
|
+
end.map do |value|
|
20
|
+
value.name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::Type
|
3
|
+
def to_type
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
def monotype?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
other.instance_of?(self.class)
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
[self.class].hash
|
17
|
+
end
|
18
|
+
|
19
|
+
def eql?(other)
|
20
|
+
self == other
|
21
|
+
end
|
22
|
+
|
23
|
+
def subtype_of?(other)
|
24
|
+
self == other
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Furnace
|
2
|
+
class SSA::User < SSA::NamedValue
|
3
|
+
attr_reader :operands
|
4
|
+
|
5
|
+
def initialize(function, operands=[], name=nil)
|
6
|
+
super(function, name)
|
7
|
+
|
8
|
+
self.operands = operands
|
9
|
+
end
|
10
|
+
|
11
|
+
def each_operand(&block)
|
12
|
+
@operands.each &block if @operands
|
13
|
+
end
|
14
|
+
|
15
|
+
def operands=(operands)
|
16
|
+
update_use_lists do
|
17
|
+
@operands = operands.map(&:to_value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def detach
|
22
|
+
update_use_lists do
|
23
|
+
@operands = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def translate_operands(map)
|
28
|
+
@operands.map do |operand|
|
29
|
+
map[operand]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace_uses_of(value, new_value)
|
34
|
+
if replace_uses_of_operands(value, new_value)
|
35
|
+
value.remove_use(self)
|
36
|
+
new_value.add_use(self)
|
37
|
+
else
|
38
|
+
raise ArgumentError, "#{value.inspect} is not used in #{self.inspect}"
|
39
|
+
end
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid?(*args)
|
45
|
+
verify!(*args)
|
46
|
+
true
|
47
|
+
rescue TypeError
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def verify!
|
52
|
+
# do nothing
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def update_use_lists
|
58
|
+
each_operand do |operand|
|
59
|
+
operand.remove_use(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
value = yield
|
63
|
+
|
64
|
+
each_operand do |operand|
|
65
|
+
operand.add_use(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
value
|
69
|
+
end
|
70
|
+
|
71
|
+
def replace_uses_of_operands(value, new_value)
|
72
|
+
found = false
|
73
|
+
|
74
|
+
@operands.each_with_index do |operand, index|
|
75
|
+
if operand == value
|
76
|
+
found = true
|
77
|
+
@operands[index] = new_value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
found
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|