typeprof 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +26 -0
- data/.gitignore +7 -0
- data/.gitmodules +6 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +41 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/doc/doc.ja.md +415 -0
- data/doc/doc.md +429 -0
- data/doc/ppl2019.pdf +0 -0
- data/exe/typeprof +5 -0
- data/lib/typeprof.rb +13 -0
- data/lib/typeprof/analyzer.rb +1911 -0
- data/lib/typeprof/builtin.rb +554 -0
- data/lib/typeprof/cli.rb +110 -0
- data/lib/typeprof/container-type.rb +626 -0
- data/lib/typeprof/export.rb +203 -0
- data/lib/typeprof/import.rb +546 -0
- data/lib/typeprof/insns-def.rb +61 -0
- data/lib/typeprof/iseq.rb +387 -0
- data/lib/typeprof/method.rb +267 -0
- data/lib/typeprof/type.rb +1092 -0
- data/lib/typeprof/utils.rb +209 -0
- data/run.sh +3 -0
- data/smoke/alias.rb +30 -0
- data/smoke/alias2.rb +19 -0
- data/smoke/any-cbase.rb +5 -0
- data/smoke/any1.rb +15 -0
- data/smoke/any2.rb +17 -0
- data/smoke/arguments.rb +16 -0
- data/smoke/array-each.rb +14 -0
- data/smoke/array-each2.rb +15 -0
- data/smoke/array-each3.rb +15 -0
- data/smoke/array-ltlt.rb +13 -0
- data/smoke/array-ltlt2.rb +16 -0
- data/smoke/array-map.rb +11 -0
- data/smoke/array-map2.rb +10 -0
- data/smoke/array-map3.rb +22 -0
- data/smoke/array-mul.rb +17 -0
- data/smoke/array-plus1.rb +10 -0
- data/smoke/array-plus2.rb +15 -0
- data/smoke/array-pop.rb +11 -0
- data/smoke/array-replace.rb +12 -0
- data/smoke/array-s-aref.rb +11 -0
- data/smoke/array1.rb +26 -0
- data/smoke/array10.rb +14 -0
- data/smoke/array11.rb +13 -0
- data/smoke/array12.rb +24 -0
- data/smoke/array13.rb +30 -0
- data/smoke/array14.rb +13 -0
- data/smoke/array2.rb +27 -0
- data/smoke/array3.rb +25 -0
- data/smoke/array4.rb +14 -0
- data/smoke/array5.rb +13 -0
- data/smoke/array6.rb +14 -0
- data/smoke/array7.rb +13 -0
- data/smoke/array8.rb +13 -0
- data/smoke/array9.rb +12 -0
- data/smoke/attr.rb +28 -0
- data/smoke/backtrace.rb +32 -0
- data/smoke/block1.rb +22 -0
- data/smoke/block10.rb +14 -0
- data/smoke/block11.rb +39 -0
- data/smoke/block12.rb +22 -0
- data/smoke/block2.rb +14 -0
- data/smoke/block3.rb +38 -0
- data/smoke/block4.rb +18 -0
- data/smoke/block5.rb +18 -0
- data/smoke/block6.rb +20 -0
- data/smoke/block7.rb +20 -0
- data/smoke/block8.rb +27 -0
- data/smoke/block9.rb +12 -0
- data/smoke/blown.rb +12 -0
- data/smoke/break1.rb +18 -0
- data/smoke/break2.rb +15 -0
- data/smoke/case.rb +16 -0
- data/smoke/case2.rb +17 -0
- data/smoke/class.rb +5 -0
- data/smoke/class_instance_var.rb +9 -0
- data/smoke/class_method.rb +25 -0
- data/smoke/class_method2.rb +21 -0
- data/smoke/class_method3.rb +27 -0
- data/smoke/constant1.rb +38 -0
- data/smoke/constant2.rb +33 -0
- data/smoke/constant3.rb +9 -0
- data/smoke/constant4.rb +11 -0
- data/smoke/context-sensitive1.rb +12 -0
- data/smoke/cvar.rb +28 -0
- data/smoke/cvar2.rb +17 -0
- data/smoke/demo.rb +80 -0
- data/smoke/demo1.rb +16 -0
- data/smoke/demo10.rb +20 -0
- data/smoke/demo11.rb +11 -0
- data/smoke/demo2.rb +14 -0
- data/smoke/demo3.rb +16 -0
- data/smoke/demo4.rb +27 -0
- data/smoke/demo5.rb +13 -0
- data/smoke/demo6.rb +21 -0
- data/smoke/demo7.rb +14 -0
- data/smoke/demo8.rb +18 -0
- data/smoke/demo9.rb +18 -0
- data/smoke/dummy-execution1.rb +14 -0
- data/smoke/dummy-execution2.rb +16 -0
- data/smoke/ensure1.rb +20 -0
- data/smoke/enumerator.rb +15 -0
- data/smoke/expandarray1.rb +22 -0
- data/smoke/expandarray2.rb +23 -0
- data/smoke/fib.rb +28 -0
- data/smoke/flow1.rb +16 -0
- data/smoke/flow2.rb +14 -0
- data/smoke/flow3.rb +14 -0
- data/smoke/flow4.rb +5 -0
- data/smoke/flow5.rb +19 -0
- data/smoke/flow6.rb +19 -0
- data/smoke/flow7.rb +26 -0
- data/smoke/for.rb +9 -0
- data/smoke/freeze.rb +11 -0
- data/smoke/function.rb +16 -0
- data/smoke/gvar.rb +13 -0
- data/smoke/hash-fetch.rb +27 -0
- data/smoke/hash1.rb +18 -0
- data/smoke/hash2.rb +12 -0
- data/smoke/hash3.rb +13 -0
- data/smoke/hash4.rb +10 -0
- data/smoke/hash5.rb +14 -0
- data/smoke/inheritance.rb +34 -0
- data/smoke/inheritance2.rb +29 -0
- data/smoke/initialize.rb +26 -0
- data/smoke/instance_eval.rb +18 -0
- data/smoke/int_times.rb +14 -0
- data/smoke/integer.rb +10 -0
- data/smoke/ivar.rb +29 -0
- data/smoke/ivar2.rb +30 -0
- data/smoke/kernel-class.rb +12 -0
- data/smoke/keyword1.rb +11 -0
- data/smoke/keyword2.rb +11 -0
- data/smoke/keyword3.rb +12 -0
- data/smoke/keyword4.rb +11 -0
- data/smoke/keyword5.rb +15 -0
- data/smoke/kwsplat1.rb +42 -0
- data/smoke/kwsplat2.rb +12 -0
- data/smoke/manual-rbs.rb +27 -0
- data/smoke/manual-rbs.rbs +3 -0
- data/smoke/manual-rbs2.rb +20 -0
- data/smoke/manual-rbs2.rbs +8 -0
- data/smoke/masgn1.rb +13 -0
- data/smoke/masgn2.rb +17 -0
- data/smoke/masgn3.rb +12 -0
- data/smoke/method_in_branch.rb +22 -0
- data/smoke/module1.rb +29 -0
- data/smoke/module2.rb +28 -0
- data/smoke/module3.rb +33 -0
- data/smoke/module4.rb +29 -0
- data/smoke/module_function1.rb +28 -0
- data/smoke/module_function2.rb +28 -0
- data/smoke/multiple-include.rb +14 -0
- data/smoke/multiple-superclass.rb +16 -0
- data/smoke/next1.rb +20 -0
- data/smoke/next2.rb +16 -0
- data/smoke/object-send1.rb +22 -0
- data/smoke/once.rb +12 -0
- data/smoke/optional1.rb +13 -0
- data/smoke/optional2.rb +15 -0
- data/smoke/parameterizedd-self.rb +18 -0
- data/smoke/pathname1.rb +13 -0
- data/smoke/pathname2.rb +13 -0
- data/smoke/printf.rb +20 -0
- data/smoke/proc.rb +19 -0
- data/smoke/proc2.rb +16 -0
- data/smoke/proc3.rb +14 -0
- data/smoke/proc4.rb +11 -0
- data/smoke/range.rb +13 -0
- data/smoke/redo1.rb +21 -0
- data/smoke/redo2.rb +22 -0
- data/smoke/req-keyword.rb +12 -0
- data/smoke/rescue1.rb +20 -0
- data/smoke/rescue2.rb +22 -0
- data/smoke/respond_to.rb +22 -0
- data/smoke/rest-farg.rb +10 -0
- data/smoke/rest1.rb +25 -0
- data/smoke/rest2.rb +30 -0
- data/smoke/rest3.rb +36 -0
- data/smoke/rest4.rb +18 -0
- data/smoke/rest5.rb +10 -0
- data/smoke/rest6.rb +11 -0
- data/smoke/retry1.rb +20 -0
- data/smoke/return.rb +13 -0
- data/smoke/reveal.rb +13 -0
- data/smoke/singleton_class.rb +8 -0
- data/smoke/singleton_method.rb +9 -0
- data/smoke/step.rb +17 -0
- data/smoke/string-split.rb +11 -0
- data/smoke/struct.rb +9 -0
- data/smoke/struct2.rb +24 -0
- data/smoke/super1.rb +50 -0
- data/smoke/super2.rb +16 -0
- data/smoke/super3.rb +19 -0
- data/smoke/svar1.rb +12 -0
- data/smoke/tap1.rb +17 -0
- data/smoke/toplevel.rb +12 -0
- data/smoke/two-map.rb +17 -0
- data/smoke/type_var.rb +10 -0
- data/smoke/typed_method.rb +15 -0
- data/smoke/union-recv.rb +29 -0
- data/smoke/variadic1.rb.notyet +5 -0
- data/smoke/wrong-extend.rb +25 -0
- data/smoke/wrong-include.rb +26 -0
- data/smoke/wrong-rbs.rb +15 -0
- data/smoke/wrong-rbs.rbs +7 -0
- data/testbed/ao.rb +297 -0
- data/testbed/diff-lcs-entrypoint.rb +4 -0
- data/testbed/goodcheck-Gemfile.lock +51 -0
- data/tools/coverage.rb +14 -0
- data/tools/setup-insns-def.rb +30 -0
- data/tools/stackprof-wrapper.rb +10 -0
- data/typeprof.gemspec +24 -0
- metadata +262 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
TypeProf::INSN_TABLE = {:nop=>[],
|
2
|
+
:getlocal=>["lindex_t", "rb_num_t"],
|
3
|
+
:setlocal=>["lindex_t", "rb_num_t"],
|
4
|
+
:getblockparam=>["lindex_t", "rb_num_t"],
|
5
|
+
:setblockparam=>["lindex_t", "rb_num_t"],
|
6
|
+
:getblockparamproxy=>["lindex_t", "rb_num_t"],
|
7
|
+
:getspecial=>["rb_num_t", "rb_num_t"],
|
8
|
+
:setspecial=>["rb_num_t"],
|
9
|
+
:getinstancevariable=>["ID", "IVC"],
|
10
|
+
:setinstancevariable=>["ID", "IVC"],
|
11
|
+
:getclassvariable=>["ID"],
|
12
|
+
:setclassvariable=>["ID"],
|
13
|
+
:getconstant=>["ID"],
|
14
|
+
:setconstant=>["ID"],
|
15
|
+
:getglobal=>["GENTRY"],
|
16
|
+
:setglobal=>["GENTRY"],
|
17
|
+
:putnil=>[],
|
18
|
+
:putself=>[],
|
19
|
+
:putobject=>["VALUE"],
|
20
|
+
:putspecialobject=>["rb_num_t"],
|
21
|
+
:putstring=>["VALUE"],
|
22
|
+
:concatstrings=>["rb_num_t"],
|
23
|
+
:tostring=>[],
|
24
|
+
:freezestring=>["VALUE"],
|
25
|
+
:toregexp=>["rb_num_t", "rb_num_t"],
|
26
|
+
:intern=>[],
|
27
|
+
:newarray=>["rb_num_t"],
|
28
|
+
:newarraykwsplat=>["rb_num_t"],
|
29
|
+
:duparray=>["VALUE"],
|
30
|
+
:duphash=>["VALUE"],
|
31
|
+
:expandarray=>["rb_num_t", "rb_num_t"],
|
32
|
+
:concatarray=>[],
|
33
|
+
:splatarray=>["VALUE"],
|
34
|
+
:newhash=>["rb_num_t"],
|
35
|
+
:newrange=>["rb_num_t"],
|
36
|
+
:pop=>[],
|
37
|
+
:dup=>[],
|
38
|
+
:dupn=>["rb_num_t"],
|
39
|
+
:swap=>[],
|
40
|
+
:reverse=>["rb_num_t"],
|
41
|
+
:topn=>["rb_num_t"],
|
42
|
+
:setn=>["rb_num_t"],
|
43
|
+
:adjuststack=>["rb_num_t"],
|
44
|
+
:defined=>["rb_num_t", "VALUE", "VALUE"],
|
45
|
+
:checkmatch=>["rb_num_t"],
|
46
|
+
:checkkeyword=>["lindex_t", "lindex_t"],
|
47
|
+
:checktype=>["rb_num_t"],
|
48
|
+
:defineclass=>["ID", "ISEQ", "rb_num_t"],
|
49
|
+
:definemethod=>["ID", "ISEQ"],
|
50
|
+
:definesmethod=>["ID", "ISEQ"],
|
51
|
+
:send=>["CALL_DATA", "ISEQ"],
|
52
|
+
:invokesuper=>["CALL_DATA", "ISEQ"],
|
53
|
+
:invokeblock=>["CALL_DATA"],
|
54
|
+
:leave=>[],
|
55
|
+
:throw=>["rb_num_t"],
|
56
|
+
:jump=>["OFFSET"],
|
57
|
+
:branchif=>["OFFSET"],
|
58
|
+
:branchunless=>["OFFSET"],
|
59
|
+
:branchnil=>["OFFSET"],
|
60
|
+
:once=>["ISEQ", "ISE"],
|
61
|
+
:invokebuiltin=>["RB_BUILTIN"]}
|
@@ -0,0 +1,387 @@
|
|
1
|
+
module TypeProf
|
2
|
+
class ISeq
|
3
|
+
include Utils::StructuralEquality
|
4
|
+
|
5
|
+
def self.compile(file)
|
6
|
+
opt = RubyVM::InstructionSequence.compile_option
|
7
|
+
opt[:inline_const_cache] = false
|
8
|
+
opt[:peephole_optimization] = false
|
9
|
+
opt[:specialized_instruction] = false
|
10
|
+
opt[:operands_unification] = false
|
11
|
+
opt[:coverage_enabled] = false
|
12
|
+
new(RubyVM::InstructionSequence.compile_file(file, **opt).to_a)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.compile_str(str, path = nil)
|
16
|
+
opt = RubyVM::InstructionSequence.compile_option
|
17
|
+
opt[:inline_const_cache] = false
|
18
|
+
opt[:peephole_optimization] = false
|
19
|
+
opt[:specialized_instruction] = false
|
20
|
+
opt[:operands_unification] = false
|
21
|
+
opt[:coverage_enabled] = false
|
22
|
+
new(RubyVM::InstructionSequence.compile(str, path, **opt).to_a)
|
23
|
+
end
|
24
|
+
|
25
|
+
FRESH_ID = [0]
|
26
|
+
|
27
|
+
def initialize(iseq)
|
28
|
+
@id = FRESH_ID[0]
|
29
|
+
FRESH_ID[0] += 1
|
30
|
+
|
31
|
+
_magic, _major_version, _minor_version, _format_type, _misc,
|
32
|
+
@name, @path, @absolute_path, @start_lineno, @type,
|
33
|
+
@locals, @fargs_format, catch_table, insns = *iseq
|
34
|
+
|
35
|
+
@insns = []
|
36
|
+
@linenos = []
|
37
|
+
|
38
|
+
labels = setup_iseq(insns)
|
39
|
+
|
40
|
+
# checkmatch->branch
|
41
|
+
# send->branch
|
42
|
+
|
43
|
+
@catch_table = []
|
44
|
+
catch_table.map do |type, iseq, first, last, cont, stack_depth|
|
45
|
+
iseq = iseq ? ISeq.new(iseq) : nil
|
46
|
+
entry = [type, iseq, labels[cont], stack_depth]
|
47
|
+
labels[first].upto(labels[last]) do |i|
|
48
|
+
@catch_table[i] ||= []
|
49
|
+
@catch_table[i] << entry
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
merge_branches
|
54
|
+
|
55
|
+
analyze_stack
|
56
|
+
end
|
57
|
+
|
58
|
+
def <=>(other)
|
59
|
+
@id <=> other.id
|
60
|
+
end
|
61
|
+
|
62
|
+
def setup_iseq(insns)
|
63
|
+
i = 0
|
64
|
+
labels = {}
|
65
|
+
insns.each do |e|
|
66
|
+
if e.is_a?(Symbol) && e.to_s.start_with?("label")
|
67
|
+
labels[e] = i
|
68
|
+
elsif e.is_a?(Array)
|
69
|
+
i += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
lineno = 0
|
74
|
+
insns.each do |e|
|
75
|
+
case e
|
76
|
+
when Integer # lineno
|
77
|
+
lineno = e
|
78
|
+
when Symbol # label or trace
|
79
|
+
nil
|
80
|
+
when Array
|
81
|
+
insn, *operands = e
|
82
|
+
operands = INSN_TABLE[insn].zip(operands).map do |type, operand|
|
83
|
+
case type
|
84
|
+
when "ISEQ"
|
85
|
+
operand && ISeq.new(operand)
|
86
|
+
when "lindex_t", "rb_num_t", "VALUE", "ID", "GENTRY", "CALL_DATA"
|
87
|
+
operand
|
88
|
+
when "OFFSET"
|
89
|
+
labels[operand] || raise("unknown label: #{ operand }")
|
90
|
+
when "IVC", "ISE"
|
91
|
+
raise unless operand.is_a?(Integer)
|
92
|
+
:_cache_operand
|
93
|
+
else
|
94
|
+
raise "unknown operand type: #{ type }"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@insns << [insn, operands]
|
99
|
+
@linenos << lineno
|
100
|
+
else
|
101
|
+
raise "unknown iseq entry: #{ e }"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
|
106
|
+
|
107
|
+
labels
|
108
|
+
end
|
109
|
+
|
110
|
+
def merge_branches
|
111
|
+
@insns.size.times do |i|
|
112
|
+
insn, operands = @insns[i]
|
113
|
+
case insn
|
114
|
+
when :branchif
|
115
|
+
@insns[i] = [:branch, [:if] + operands]
|
116
|
+
when :branchunless
|
117
|
+
@insns[i] = [:branch, [:unless] + operands]
|
118
|
+
when :branchnil
|
119
|
+
@insns[i] = [:branch, [:nil] + operands]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def source_location(pc)
|
125
|
+
"#{ @path }:#{ @linenos[pc] }"
|
126
|
+
end
|
127
|
+
|
128
|
+
attr_reader :name, :path, :abolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns, :linenos
|
129
|
+
attr_reader :id
|
130
|
+
|
131
|
+
def pretty_print(q)
|
132
|
+
q.text "ISeq["
|
133
|
+
q.group do
|
134
|
+
q.nest(1) do
|
135
|
+
q.breakable ""
|
136
|
+
q.text "@type= #{ @type }"
|
137
|
+
q.breakable ", "
|
138
|
+
q.text "@name= #{ @name }"
|
139
|
+
q.breakable ", "
|
140
|
+
q.text "@path= #{ @path }"
|
141
|
+
q.breakable ", "
|
142
|
+
q.text "@absolute_path= #{ @absolute_path }"
|
143
|
+
q.breakable ", "
|
144
|
+
q.text "@start_lineno= #{ @start_lineno }"
|
145
|
+
q.breakable ", "
|
146
|
+
q.text "@fargs_format= #{ @fargs_format.inspect }"
|
147
|
+
q.breakable ", "
|
148
|
+
q.text "@insns="
|
149
|
+
q.group(2) do
|
150
|
+
@insns.each_with_index do |(insn, *operands), i|
|
151
|
+
q.breakable
|
152
|
+
q.group(2, "#{ i }: #{ insn.to_s }", "") do
|
153
|
+
q.pp operands
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
q.breakable
|
159
|
+
end
|
160
|
+
q.text "]"
|
161
|
+
end
|
162
|
+
|
163
|
+
def analyze_stack
|
164
|
+
# gather branch targets
|
165
|
+
# TODO: catch_table should be also considered
|
166
|
+
branch_targets = {}
|
167
|
+
@insns.each do |insn, operands|
|
168
|
+
case insn
|
169
|
+
when :branch
|
170
|
+
branch_targets[operands[1]] = true
|
171
|
+
when :jump
|
172
|
+
branch_targets[operands[0]] = true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# find a pattern: getlocal, ..., send (is_a?, respond_to?), branch
|
177
|
+
send_branch_list = []
|
178
|
+
(@insns.size - 1).times do |i|
|
179
|
+
insn, operands = @insns[i]
|
180
|
+
if insn == :getlocal && operands[1] == 0
|
181
|
+
j = i + 1
|
182
|
+
sp = 1
|
183
|
+
while @insns[j]
|
184
|
+
sp = check_send_branch(sp, j)
|
185
|
+
if sp == :match
|
186
|
+
send_branch_list << [i, j]
|
187
|
+
break
|
188
|
+
end
|
189
|
+
break if !sp
|
190
|
+
j += 1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
send_branch_list.each do |i, j|
|
195
|
+
next if (i + 1 .. j).any? {|i| branch_targets[i] }
|
196
|
+
_insn, getlocal_operands = @insns[i]
|
197
|
+
_insn, send_operands = @insns[j]
|
198
|
+
_insn, branch_operands = @insns[j + 1]
|
199
|
+
@insns[j] = [:nop]
|
200
|
+
@insns[j + 1] = [:send_branch, [getlocal_operands, send_operands, branch_operands]]
|
201
|
+
end
|
202
|
+
|
203
|
+
# find a pattern: getlocal, branch
|
204
|
+
(@insns.size - 1).times do |i|
|
205
|
+
next if branch_targets[i + 1]
|
206
|
+
insn0, getlocal_operands = @insns[i]
|
207
|
+
insn1, branch_operands = @insns[i + 1]
|
208
|
+
if [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0 && insn1 == :branch
|
209
|
+
@insns[i ] = [:nop]
|
210
|
+
@insns[i + 1] = [:getlocal_branch, [getlocal_operands, branch_operands]]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# flow-sensitive analysis for `case var; when A; when B; when C; end`
|
215
|
+
# find a pattern: getlocal, (dup, putobject(true), getconstant(class name), checkmatch, branch)*
|
216
|
+
case_branch_list = []
|
217
|
+
(@insns.size - 1).times do |i|
|
218
|
+
insn0, getlocal_operands = @insns[i]
|
219
|
+
next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
|
220
|
+
nops = [i]
|
221
|
+
new_insns = []
|
222
|
+
j = i + 1
|
223
|
+
while true
|
224
|
+
case @insns[j]
|
225
|
+
when [:dup, []]
|
226
|
+
break unless @insns[j + 1] == [:putnil, []]
|
227
|
+
break unless @insns[j + 2] == [:putobject, [true]]
|
228
|
+
break unless @insns[j + 3][0] == :getconstant # TODO: support A::B::C
|
229
|
+
break unless @insns[j + 4] == [:checkmatch, [2]]
|
230
|
+
break unless @insns[j + 5][0] == :branch
|
231
|
+
target_pc = @insns[j + 5][1][1]
|
232
|
+
break unless @insns[target_pc] == [:pop, []]
|
233
|
+
nops << j << (j + 4) << target_pc
|
234
|
+
new_insns << [j + 5, [:getlocal_checkmatch_branch, [getlocal_operands, @insns[j + 5][1]]]]
|
235
|
+
j += 6
|
236
|
+
when [:pop, []]
|
237
|
+
nops << j
|
238
|
+
case_branch_list << [nops, new_insns]
|
239
|
+
break
|
240
|
+
else
|
241
|
+
break
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
case_branch_list.each do |nops, new_insns|
|
246
|
+
nops.each {|i| @insns[i] = [:nop, []] }
|
247
|
+
new_insns.each {|i, insn| @insns[i] = insn }
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def check_send_branch(sp, j)
|
252
|
+
insn, operands = @insns[j]
|
253
|
+
|
254
|
+
case insn
|
255
|
+
when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
|
256
|
+
:putself
|
257
|
+
sp += 1
|
258
|
+
when :newarray, :newarraykwsplat, :newhash, :concatstrings
|
259
|
+
len, = operands
|
260
|
+
sp =- len
|
261
|
+
return nil if sp <= 0
|
262
|
+
sp += 1
|
263
|
+
when :newhashfromarray
|
264
|
+
raise NotImplementedError, "newhashfromarray"
|
265
|
+
when :newrange, :tostring
|
266
|
+
sp -= 2
|
267
|
+
return nil if sp <= 0
|
268
|
+
sp += 1
|
269
|
+
when :freezestring
|
270
|
+
# XXX: should leverage this information?
|
271
|
+
when :toregexp
|
272
|
+
_regexp_opt, len = operands
|
273
|
+
sp -= len
|
274
|
+
return nil if sp <= 0
|
275
|
+
sp += 1
|
276
|
+
when :intern
|
277
|
+
sp -= 1
|
278
|
+
return nil if sp <= 0
|
279
|
+
sp += 1
|
280
|
+
when :definemethod, :definesmethod
|
281
|
+
when :defineclass
|
282
|
+
sp -= 2
|
283
|
+
when :send, :invokesuper
|
284
|
+
opt, = operands
|
285
|
+
_flags = opt[:flag]
|
286
|
+
_mid = opt[:mid]
|
287
|
+
kw_arg = opt[:kw_arg]
|
288
|
+
argc = opt[:orig_argc]
|
289
|
+
argc += 1 # receiver
|
290
|
+
argc += kw_arg.size if kw_arg
|
291
|
+
sp -= argc
|
292
|
+
return :match if insn == :send && sp == 0 && @insns[j + 1][0] == :branch
|
293
|
+
sp += 1
|
294
|
+
when :invokeblock
|
295
|
+
opt, = operands
|
296
|
+
sp -= opt[:orig_argc]
|
297
|
+
return nil if sp <= 0
|
298
|
+
sp += 1
|
299
|
+
when :invokebuiltin
|
300
|
+
raise NotImplementedError
|
301
|
+
when :leave, :throw
|
302
|
+
return
|
303
|
+
when :once
|
304
|
+
return # not implemented
|
305
|
+
when :branch, :jump
|
306
|
+
return # not implemented
|
307
|
+
when :setinstancevariable, :setclassvariable, :setglobal
|
308
|
+
sp -= 1
|
309
|
+
when :setlocal, :setblockparam
|
310
|
+
return # conservative
|
311
|
+
when :getinstancevariable, :getclassvariable, :getglobal,
|
312
|
+
:getlocal, :getblockparam, :getblockparamproxy
|
313
|
+
sp += 1
|
314
|
+
when :getconstant
|
315
|
+
sp -= 2
|
316
|
+
return nil if sp <= 0
|
317
|
+
sp += 1
|
318
|
+
when :setconstant
|
319
|
+
sp -= 2
|
320
|
+
when :getspecial
|
321
|
+
sp += 1
|
322
|
+
when :setspecial
|
323
|
+
# flip-flop
|
324
|
+
raise NotImplementedError, "setspecial"
|
325
|
+
when :dup
|
326
|
+
sp += 1
|
327
|
+
when :duphash
|
328
|
+
sp += 1
|
329
|
+
when :dupn
|
330
|
+
n, = operands
|
331
|
+
sp += n
|
332
|
+
when :pop
|
333
|
+
sp -= 1
|
334
|
+
when :swap
|
335
|
+
sp -= 2
|
336
|
+
return nil if sp <= 0
|
337
|
+
sp += 2
|
338
|
+
when :reverse
|
339
|
+
n, = operands
|
340
|
+
sp -= n
|
341
|
+
return nil if sp <= 0
|
342
|
+
sp += n
|
343
|
+
when :defined
|
344
|
+
sp -= 1
|
345
|
+
return nil if sp <= 0
|
346
|
+
sp += 1
|
347
|
+
when :checkmatch
|
348
|
+
sp -= 2
|
349
|
+
return nil if sp <= 0
|
350
|
+
sp += 1
|
351
|
+
when :checkkeyword
|
352
|
+
sp += 1
|
353
|
+
when :adjuststack
|
354
|
+
n, = operands
|
355
|
+
sp -= n
|
356
|
+
when :nop
|
357
|
+
when :setn
|
358
|
+
return nil # not implemented
|
359
|
+
when :topn
|
360
|
+
sp += 1
|
361
|
+
when :splatarray
|
362
|
+
sp -= 1
|
363
|
+
return nil if sp <= 0
|
364
|
+
sp += 1
|
365
|
+
when :expandarray
|
366
|
+
num, flag = operands
|
367
|
+
splat = flag & 1 == 1
|
368
|
+
sp -= 1
|
369
|
+
return nil if sp <= 0
|
370
|
+
sp += num + (splat ? 1 : 0)
|
371
|
+
when :concatarray
|
372
|
+
sp -= 2
|
373
|
+
return nil if sp <= 0
|
374
|
+
sp += 1
|
375
|
+
when :checktype
|
376
|
+
sp -= 1
|
377
|
+
return nil if sp <= 0
|
378
|
+
sp += 1
|
379
|
+
else
|
380
|
+
raise "Unknown insn: #{ insn }"
|
381
|
+
end
|
382
|
+
|
383
|
+
return nil if sp <= 0
|
384
|
+
sp
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
module TypeProf
|
2
|
+
class MethodDef
|
3
|
+
include Utils::StructuralEquality
|
4
|
+
end
|
5
|
+
|
6
|
+
class ISeqMethodDef < MethodDef
|
7
|
+
def initialize(iseq, cref)
|
8
|
+
@iseq = iseq
|
9
|
+
raise if iseq.nil?
|
10
|
+
@cref = cref
|
11
|
+
end
|
12
|
+
|
13
|
+
def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
|
14
|
+
recv = scratch.globalize_type(recv, caller_env, caller_ep)
|
15
|
+
aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
|
16
|
+
|
17
|
+
aargs.each_formal_arguments(@iseq.fargs_format) do |fargs, start_pc|
|
18
|
+
if fargs.is_a?(String)
|
19
|
+
scratch.error(caller_ep, fargs)
|
20
|
+
ctn[Type.any, caller_ep, caller_env]
|
21
|
+
next
|
22
|
+
end
|
23
|
+
|
24
|
+
callee_ep = do_send_core(fargs, start_pc, recv, mid, scratch)
|
25
|
+
|
26
|
+
scratch.add_iseq_method_call!(self, callee_ep.ctx)
|
27
|
+
scratch.add_callsite!(callee_ep.ctx, fargs, caller_ep, caller_env, &ctn)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_send_core(fargs, start_pc, recv, mid, scratch)
|
32
|
+
lead_num = @iseq.fargs_format[:lead_num] || 0
|
33
|
+
post_start = @iseq.fargs_format[:post_start]
|
34
|
+
rest_start = @iseq.fargs_format[:rest_start]
|
35
|
+
kw_start = @iseq.fargs_format[:kwbits]
|
36
|
+
kw_start -= @iseq.fargs_format[:keyword].size if kw_start
|
37
|
+
block_start = @iseq.fargs_format[:block_start]
|
38
|
+
|
39
|
+
# XXX: need to check .rbs fargs and .rb fargs
|
40
|
+
|
41
|
+
ctx = Context.new(@iseq, @cref, mid) # XXX: to support opts, rest, etc
|
42
|
+
callee_ep = ExecutionPoint.new(ctx, start_pc, nil)
|
43
|
+
|
44
|
+
locals = [Type.nil] * @iseq.locals.size
|
45
|
+
nenv = Env.new(StaticEnv.new(recv, fargs.blk_ty, false), locals, [], Utils::HashWrapper.new({}))
|
46
|
+
alloc_site = AllocationSite.new(callee_ep)
|
47
|
+
idx = 0
|
48
|
+
fargs.lead_tys.each_with_index do |ty, i|
|
49
|
+
alloc_site2 = alloc_site.add_id(idx += 1)
|
50
|
+
# nenv is top-level, so it is okay to call Type#localize directly
|
51
|
+
nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
|
52
|
+
nenv = nenv.local_update(i, ty)
|
53
|
+
end
|
54
|
+
if fargs.opt_tys
|
55
|
+
fargs.opt_tys.each_with_index do |ty, i|
|
56
|
+
alloc_site2 = alloc_site.add_id(idx += 1)
|
57
|
+
nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
|
58
|
+
nenv = nenv.local_update(lead_num + i, ty)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
if fargs.rest_ty
|
62
|
+
alloc_site2 = alloc_site.add_id(idx += 1)
|
63
|
+
ty = Type::Array.new(Type::Array::Elements.new([], fargs.rest_ty), Type::Instance.new(Type::Builtin[:ary]))
|
64
|
+
nenv, rest_ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
|
65
|
+
nenv = nenv.local_update(rest_start, rest_ty)
|
66
|
+
end
|
67
|
+
if fargs.post_tys
|
68
|
+
fargs.post_tys.each_with_index do |ty, i|
|
69
|
+
alloc_site2 = alloc_site.add_id(idx += 1)
|
70
|
+
nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
|
71
|
+
nenv = nenv.local_update(post_start + i, ty)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
if fargs.kw_tys
|
75
|
+
fargs.kw_tys.each_with_index do |(_, _, ty), i|
|
76
|
+
alloc_site2 = alloc_site.add_id(idx += 1)
|
77
|
+
nenv, ty = ty.localize(nenv, alloc_site2, Config.options[:type_depth_limit])
|
78
|
+
nenv = nenv.local_update(kw_start + i, ty)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
# kwrest
|
82
|
+
nenv = nenv.local_update(block_start, fargs.blk_ty) if block_start
|
83
|
+
|
84
|
+
scratch.merge_env(callee_ep, nenv)
|
85
|
+
|
86
|
+
callee_ep
|
87
|
+
end
|
88
|
+
|
89
|
+
def do_check_send_core(fargs, recv, mid, ep, scratch)
|
90
|
+
lead_num = @iseq.fargs_format[:lead_num] || 0
|
91
|
+
post_num = @iseq.fargs_format[:post_num] || 0
|
92
|
+
rest_start = @iseq.fargs_format[:rest_start]
|
93
|
+
opt = @iseq.fargs_format[:opt] || [0]
|
94
|
+
|
95
|
+
# TODO: check keywords
|
96
|
+
if rest_start
|
97
|
+
# almost ok
|
98
|
+
else
|
99
|
+
if fargs.lead_tys.size + fargs.post_tys.size < lead_num + post_num
|
100
|
+
scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at least %d arguments" % [fargs.lead_tys.size + fargs.post_tys.size, lead_num + post_num])
|
101
|
+
return
|
102
|
+
end
|
103
|
+
if fargs.lead_tys.size + fargs.opt_tys.size + fargs.post_tys.size > lead_num + opt.size - 1 + post_num
|
104
|
+
scratch.error(ep, "RBS says that the arity may be %d, but the method definition requires at most %d arguments" % [fargs.lead_tys.size + fargs.opt_tys.size + fargs.post_tys.size, lead_num + opt.size - 1 + post_num])
|
105
|
+
return
|
106
|
+
end
|
107
|
+
end
|
108
|
+
do_send_core(fargs, 0, recv, mid, scratch)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class AttrMethodDef < MethodDef
|
113
|
+
def initialize(ivar, kind)
|
114
|
+
@ivar = ivar
|
115
|
+
@kind = kind # :reader | :writer
|
116
|
+
end
|
117
|
+
|
118
|
+
attr_reader :ivar, :kind
|
119
|
+
|
120
|
+
def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
|
121
|
+
case @kind
|
122
|
+
when :reader
|
123
|
+
if aargs.lead_tys.size == 0
|
124
|
+
scratch.get_instance_variable(recv, @ivar, caller_ep, caller_env) do |ty, nenv|
|
125
|
+
ctn[ty, caller_ep, nenv]
|
126
|
+
end
|
127
|
+
else
|
128
|
+
ctn[Type.any, caller_ep, caller_env]
|
129
|
+
end
|
130
|
+
when :writer
|
131
|
+
if aargs.lead_tys.size == 1
|
132
|
+
ty = aargs.lead_tys[0]
|
133
|
+
scratch.set_instance_variable(recv, @ivar, ty, caller_ep, caller_env)
|
134
|
+
ctn[ty, caller_ep, caller_env]
|
135
|
+
else
|
136
|
+
ctn[Type.any, caller_ep, caller_env]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class TypedMethodDef < MethodDef
|
143
|
+
def initialize(sigs, rbs_source) # sigs: Array<[FormalArguments, (return)Type]>
|
144
|
+
@sigs = sigs
|
145
|
+
@rbs_source = rbs_source
|
146
|
+
end
|
147
|
+
|
148
|
+
attr_reader :rbs_source
|
149
|
+
|
150
|
+
def do_send(recv_orig, mid, aargs, caller_ep, caller_env, scratch, &ctn)
|
151
|
+
recv = scratch.globalize_type(recv_orig, caller_env, caller_ep)
|
152
|
+
found = false
|
153
|
+
aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
|
154
|
+
@sigs.each do |fargs, ret_ty|
|
155
|
+
ncaller_env = caller_env
|
156
|
+
# XXX: need to interpret args more correctly
|
157
|
+
#pp [mid, aargs, fargs]
|
158
|
+
# XXX: support self type in fargs
|
159
|
+
subst = { Type::Var.new(:self) => recv }
|
160
|
+
next unless aargs.consistent_with_formal_arguments?(fargs, subst)
|
161
|
+
if recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
|
162
|
+
tyvar_elem = Type::Var.new(:Elem)
|
163
|
+
if subst[tyvar_elem]
|
164
|
+
ty = subst[tyvar_elem]
|
165
|
+
alloc_site = AllocationSite.new(caller_ep).add_id(self)
|
166
|
+
ncaller_env, ty = scratch.localize_type(ty, ncaller_env, caller_ep)
|
167
|
+
ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id) do |elems|
|
168
|
+
elems.update(nil, ty)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
subst.merge!({ tyvar_elem => recv.elems.squash })
|
172
|
+
elsif recv.is_a?(Type::Hash) && recv_orig.is_a?(Type::LocalHash)
|
173
|
+
tyvar_k = Type::Var.new(:K)
|
174
|
+
tyvar_v = Type::Var.new(:V)
|
175
|
+
# XXX: need to support destructive operation
|
176
|
+
k_ty, v_ty = recv.elems.squash
|
177
|
+
# XXX: need to heuristically replace ret type Hash[K, V] with self, instead of conversative type?
|
178
|
+
subst.merge!({ tyvar_k => k_ty, tyvar_v => v_ty })
|
179
|
+
end
|
180
|
+
ret_ty = ret_ty.substitute(subst, Config.options[:type_depth_limit])
|
181
|
+
found = true
|
182
|
+
if aargs.blk_ty.is_a?(Type::ISeqProc)
|
183
|
+
dummy_ctx = TypedContext.new(caller_ep, mid)
|
184
|
+
dummy_ep = ExecutionPoint.new(dummy_ctx, -1, caller_ep)
|
185
|
+
dummy_env = Env.new(StaticEnv.new(recv, fargs.blk_ty, false), [], [], Utils::HashWrapper.new({}))
|
186
|
+
if fargs.blk_ty.is_a?(Type::TypedProc)
|
187
|
+
scratch.add_callsite!(dummy_ctx, nil, caller_ep, ncaller_env, &ctn)
|
188
|
+
nfargs = fargs.blk_ty.fargs
|
189
|
+
alloc_site = AllocationSite.new(caller_ep).add_id(self)
|
190
|
+
nfargs = nfargs.map.with_index do |nfarg, i|
|
191
|
+
if recv.is_a?(Type::Array)
|
192
|
+
tyvar_elem = Type::Var.new(:Elem)
|
193
|
+
nfarg = nfarg.substitute(subst.merge({ tyvar_elem => recv.elems.squash }), Config.options[:type_depth_limit])
|
194
|
+
else
|
195
|
+
nfarg = nfarg.substitute(subst, Config.options[:type_depth_limit])
|
196
|
+
end
|
197
|
+
nfarg = nfarg.remove_type_vars
|
198
|
+
alloc_site2 = alloc_site.add_id(i)
|
199
|
+
dummy_env, nfarg = scratch.localize_type(nfarg, dummy_env, dummy_ep, alloc_site2)
|
200
|
+
nfarg
|
201
|
+
end
|
202
|
+
naargs = ActualArguments.new(nfargs, nil, nil, Type.nil) # XXX: support block to block?
|
203
|
+
scratch.do_invoke_block(false, aargs.blk_ty, naargs, dummy_ep, dummy_env) do |blk_ret_ty, _ep, _env|
|
204
|
+
subst2 = {}
|
205
|
+
if blk_ret_ty.consistent?(fargs.blk_ty.ret_ty, subst2)
|
206
|
+
if recv.is_a?(Type::Array) && recv_orig.is_a?(Type::LocalArray)
|
207
|
+
tyvar_elem = Type::Var.new(:Elem)
|
208
|
+
if subst2[tyvar_elem]
|
209
|
+
ncaller_env = scratch.update_container_elem_types(ncaller_env, caller_ep, recv_orig.id) do |elems|
|
210
|
+
elems.update(nil, subst2[tyvar_elem])
|
211
|
+
end
|
212
|
+
scratch.merge_return_env(caller_ep) {|env| env ? env.merge(ncaller_env) : ncaller_env }
|
213
|
+
end
|
214
|
+
ret_ty = ret_ty.substitute(subst2, Config.options[:type_depth_limit])
|
215
|
+
else
|
216
|
+
ret_ty = ret_ty.substitute(subst2, Config.options[:type_depth_limit])
|
217
|
+
end
|
218
|
+
else
|
219
|
+
# raise "???"
|
220
|
+
# XXX: need warning
|
221
|
+
ret_ty = Type.any
|
222
|
+
end
|
223
|
+
ret_ty = ret_ty.remove_type_vars
|
224
|
+
# XXX: check the return type from the block
|
225
|
+
# sig.blk_ty.ret_ty.eql?(_ret_ty) ???
|
226
|
+
scratch.add_return_type!(dummy_ctx, ret_ty)
|
227
|
+
end
|
228
|
+
# scratch.add_return_type!(dummy_ctx, ret_ty) ?
|
229
|
+
# This makes `def foo; 1.times { return "str" }; end` return Integer|String
|
230
|
+
else
|
231
|
+
# XXX: a block is passed to a method that does not accept block.
|
232
|
+
# Should we call the passed block with any arguments?
|
233
|
+
ret_ty = ret_ty.remove_type_vars
|
234
|
+
ctn[ret_ty, caller_ep, ncaller_env]
|
235
|
+
end
|
236
|
+
else
|
237
|
+
ret_ty = ret_ty.remove_type_vars
|
238
|
+
ctn[ret_ty, caller_ep, ncaller_env]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
unless found
|
243
|
+
scratch.error(caller_ep, "failed to resolve overload: #{ recv.screen_name(scratch) }##{ mid }")
|
244
|
+
ctn[Type.any, caller_ep, caller_env]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def do_match_iseq_mdef(iseq_mdef, recv, mid, env, ep, scratch)
|
249
|
+
recv = scratch.globalize_type(recv, env, ep)
|
250
|
+
@sigs.each do |fargs, _ret_ty|
|
251
|
+
iseq_mdef.do_check_send_core(fargs, recv, mid, ep, scratch)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class CustomMethodDef < MethodDef
|
257
|
+
def initialize(impl)
|
258
|
+
@impl = impl
|
259
|
+
end
|
260
|
+
|
261
|
+
def do_send(recv, mid, aargs, caller_ep, caller_env, scratch, &ctn)
|
262
|
+
# XXX: ctn?
|
263
|
+
scratch.merge_return_env(caller_ep) {|env| env ? env.merge(caller_env) : caller_env } # for Kernel#lambda
|
264
|
+
@impl[recv, mid, aargs, caller_ep, caller_env, scratch, &ctn]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|