typeprof 0.21.11 → 0.30.0
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.
- checksums.yaml +4 -4
- data/README.md +15 -31
- data/bin/typeprof +5 -0
- data/doc/doc.ja.md +134 -0
- data/doc/doc.md +136 -0
- data/lib/typeprof/cli/cli.rb +180 -0
- data/lib/typeprof/cli.rb +2 -133
- data/lib/typeprof/code_range.rb +112 -0
- data/lib/typeprof/core/ast/base.rb +263 -0
- data/lib/typeprof/core/ast/call.rb +251 -0
- data/lib/typeprof/core/ast/const.rb +126 -0
- data/lib/typeprof/core/ast/control.rb +432 -0
- data/lib/typeprof/core/ast/meta.rb +150 -0
- data/lib/typeprof/core/ast/method.rb +335 -0
- data/lib/typeprof/core/ast/misc.rb +263 -0
- data/lib/typeprof/core/ast/module.rb +123 -0
- data/lib/typeprof/core/ast/pattern.rb +140 -0
- data/lib/typeprof/core/ast/sig_decl.rb +471 -0
- data/lib/typeprof/core/ast/sig_type.rb +663 -0
- data/lib/typeprof/core/ast/value.rb +319 -0
- data/lib/typeprof/core/ast/variable.rb +315 -0
- data/lib/typeprof/core/ast.rb +472 -0
- data/lib/typeprof/core/builtin.rb +146 -0
- data/lib/typeprof/core/env/method.rb +137 -0
- data/lib/typeprof/core/env/method_entity.rb +55 -0
- data/lib/typeprof/core/env/module_entity.rb +408 -0
- data/lib/typeprof/core/env/static_read.rb +155 -0
- data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
- data/lib/typeprof/core/env/value_entity.rb +32 -0
- data/lib/typeprof/core/env.rb +360 -0
- data/lib/typeprof/core/graph/box.rb +991 -0
- data/lib/typeprof/core/graph/change_set.rb +224 -0
- data/lib/typeprof/core/graph/filter.rb +155 -0
- data/lib/typeprof/core/graph/vertex.rb +222 -0
- data/lib/typeprof/core/graph.rb +3 -0
- data/lib/typeprof/core/service.rb +522 -0
- data/lib/typeprof/core/type.rb +348 -0
- data/lib/typeprof/core/util.rb +81 -0
- data/lib/typeprof/core.rb +32 -0
- data/lib/typeprof/diagnostic.rb +35 -0
- data/lib/typeprof/lsp/messages.rb +430 -0
- data/lib/typeprof/lsp/server.rb +177 -0
- data/lib/typeprof/lsp/text.rb +69 -0
- data/lib/typeprof/lsp/util.rb +61 -0
- data/lib/typeprof/lsp.rb +4 -907
- data/lib/typeprof/version.rb +1 -1
- data/lib/typeprof.rb +4 -18
- data/typeprof.gemspec +5 -7
- metadata +48 -35
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/main.yml +0 -39
- data/.gitignore +0 -9
- data/Gemfile +0 -17
- data/Gemfile.lock +0 -41
- data/Rakefile +0 -10
- data/exe/typeprof +0 -10
- data/lib/typeprof/analyzer.rb +0 -2598
- data/lib/typeprof/arguments.rb +0 -414
- data/lib/typeprof/block.rb +0 -176
- data/lib/typeprof/builtin.rb +0 -893
- data/lib/typeprof/code-range.rb +0 -177
- data/lib/typeprof/config.rb +0 -158
- data/lib/typeprof/container-type.rb +0 -912
- data/lib/typeprof/export.rb +0 -589
- data/lib/typeprof/import.rb +0 -852
- data/lib/typeprof/insns-def.rb +0 -65
- data/lib/typeprof/iseq.rb +0 -864
- data/lib/typeprof/method.rb +0 -355
- data/lib/typeprof/type.rb +0 -1140
- data/lib/typeprof/utils.rb +0 -212
- data/tools/coverage.rb +0 -14
- data/tools/setup-insns-def.rb +0 -30
- data/typeprof-lsp +0 -3
data/lib/typeprof/iseq.rb
DELETED
@@ -1,864 +0,0 @@
|
|
1
|
-
module TypeProf
|
2
|
-
class ISeq
|
3
|
-
if defined?(RubyVM::InstructionSequence)
|
4
|
-
# https://github.com/ruby/ruby/pull/4468
|
5
|
-
CASE_WHEN_CHECKMATCH = RubyVM::InstructionSequence.compile("case 1; when Integer; end").to_a.last.any? {|insn,| insn == :checkmatch }
|
6
|
-
# https://github.com/ruby/ruby/blob/v3_0_2/vm_core.h#L1206
|
7
|
-
VM_ENV_DATA_SIZE = 3
|
8
|
-
# Check if Ruby 3.1 or later
|
9
|
-
RICH_AST = begin RubyVM::AbstractSyntaxTree.parse("1", keep_script_lines: true).node_id; true; rescue; false; end
|
10
|
-
end
|
11
|
-
|
12
|
-
FileInfo = Struct.new(
|
13
|
-
:node_id2node,
|
14
|
-
:definition_table,
|
15
|
-
:caller_table,
|
16
|
-
:created_iseqs,
|
17
|
-
)
|
18
|
-
|
19
|
-
class << self
|
20
|
-
def compile(file)
|
21
|
-
compile_core(nil, file)
|
22
|
-
end
|
23
|
-
|
24
|
-
def compile_str(str, path = nil)
|
25
|
-
compile_core(str, path)
|
26
|
-
end
|
27
|
-
|
28
|
-
private def compile_core(str, path)
|
29
|
-
opt = RubyVM::InstructionSequence.compile_option
|
30
|
-
opt[:inline_const_cache] = false
|
31
|
-
opt[:peephole_optimization] = false
|
32
|
-
opt[:specialized_instruction] = false
|
33
|
-
opt[:operands_unification] = false
|
34
|
-
opt[:coverage_enabled] = false
|
35
|
-
|
36
|
-
parse_opts = {}
|
37
|
-
parse_opts[:keep_script_lines] = true if RICH_AST
|
38
|
-
|
39
|
-
unless defined?(RubyVM::InstructionSequence)
|
40
|
-
puts "Currently, TypeProf can work on a Ruby implementation that supports RubyVM::InstructionSequence, such as CRuby."
|
41
|
-
exit 1
|
42
|
-
end
|
43
|
-
|
44
|
-
if str
|
45
|
-
node = RubyVM::AbstractSyntaxTree.parse(str, **parse_opts)
|
46
|
-
iseq = RubyVM::InstructionSequence.compile(str, path, **opt)
|
47
|
-
else
|
48
|
-
node = RubyVM::AbstractSyntaxTree.parse_file(path, **parse_opts)
|
49
|
-
iseq = RubyVM::InstructionSequence.compile_file(path, **opt)
|
50
|
-
end
|
51
|
-
|
52
|
-
node_id2node = {}
|
53
|
-
build_ast_node_id_table(node, node_id2node) if RICH_AST
|
54
|
-
|
55
|
-
file_info = FileInfo.new(node_id2node, CodeRangeTable.new, CodeRangeTable.new, [])
|
56
|
-
iseq_rb = new(iseq.to_a, file_info)
|
57
|
-
iseq_rb.collect_local_variable_info(file_info) if RICH_AST
|
58
|
-
file_info.created_iseqs.each do |iseq|
|
59
|
-
iseq.unify_instructions
|
60
|
-
end
|
61
|
-
|
62
|
-
return iseq_rb, file_info.definition_table, file_info.caller_table
|
63
|
-
end
|
64
|
-
|
65
|
-
private def build_ast_node_id_table(node, tbl = {})
|
66
|
-
tbl[node.node_id] = node
|
67
|
-
node.children.each do |child|
|
68
|
-
build_ast_node_id_table(child, tbl) if child.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
69
|
-
end
|
70
|
-
tbl
|
71
|
-
end
|
72
|
-
|
73
|
-
def code_range_from_node(node)
|
74
|
-
CodeRange.new(
|
75
|
-
CodeLocation.new(node.first_lineno, node.first_column),
|
76
|
-
CodeLocation.new(node.last_lineno, node.last_column),
|
77
|
-
)
|
78
|
-
end
|
79
|
-
|
80
|
-
def find_node_by_id(node, id)
|
81
|
-
node = RubyVM::AbstractSyntaxTree.parse(node) if node.is_a?(String)
|
82
|
-
|
83
|
-
return node if id == node.node_id
|
84
|
-
|
85
|
-
node.children.each do |child|
|
86
|
-
if child.is_a?(RubyVM::AbstractSyntaxTree::Node)
|
87
|
-
ret = find_node_by_id(child, id)
|
88
|
-
return ret if ret
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
nil
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
Insn = Struct.new(:insn, :operands, :lineno, :code_range, :definitions)
|
97
|
-
class Insn
|
98
|
-
def check?(insn_cmp, operands_cmp = nil)
|
99
|
-
return insn == insn_cmp && (!operands_cmp || operands == operands_cmp)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
ISEQ_FRESH_ID = [0]
|
104
|
-
|
105
|
-
def initialize(iseq, file_info)
|
106
|
-
file_info.created_iseqs << self
|
107
|
-
|
108
|
-
@id = (ISEQ_FRESH_ID[0] += 1)
|
109
|
-
|
110
|
-
_magic, _major_version, _minor_version, _format_type, misc,
|
111
|
-
@name, @path, @absolute_path, @start_lineno, @type,
|
112
|
-
@locals, @fargs_format, catch_table, insns = *iseq
|
113
|
-
|
114
|
-
fl, fc, ll, lc = misc[:code_location]
|
115
|
-
@iseq_code_range = CodeRange.new(CodeLocation.new(fl, fc), CodeLocation.new(ll, lc))
|
116
|
-
|
117
|
-
convert_insns(insns, misc[:node_ids] || [], file_info)
|
118
|
-
|
119
|
-
add_body_start_marker(insns)
|
120
|
-
|
121
|
-
add_exception_cont_marker(insns, catch_table)
|
122
|
-
|
123
|
-
labels = create_label_table(insns)
|
124
|
-
|
125
|
-
@insns = setup_insns(insns, labels, file_info)
|
126
|
-
|
127
|
-
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
|
128
|
-
|
129
|
-
@catch_table = []
|
130
|
-
catch_table.map do |type, iseq, first, last, cont, stack_depth|
|
131
|
-
iseq = iseq ? ISeq.new(iseq, file_info) : nil
|
132
|
-
target = labels[cont]
|
133
|
-
entry = [type, iseq, target, stack_depth]
|
134
|
-
labels[first].upto(labels[last]) do |i|
|
135
|
-
@catch_table[i] ||= []
|
136
|
-
@catch_table[i] << entry
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def_node_id = misc[:def_node_id]
|
141
|
-
if def_node_id && file_info.node_id2node[def_node_id] && (@type == :method || @type == :block)
|
142
|
-
def_node = file_info.node_id2node[def_node_id]
|
143
|
-
method_name_token_range = extract_method_name_token_range(def_node)
|
144
|
-
if method_name_token_range
|
145
|
-
@callers = Utils::MutableSet.new
|
146
|
-
file_info.caller_table[method_name_token_range] = @callers
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
rename_insn_types
|
151
|
-
end
|
152
|
-
|
153
|
-
def extract_method_name_token_range(node)
|
154
|
-
case @type
|
155
|
-
when :method
|
156
|
-
regex = if node.type == :DEFS
|
157
|
-
/^def\s+(?:\w+)\s*\.\s*(\w+)/
|
158
|
-
else
|
159
|
-
/^def\s+(\w+)/
|
160
|
-
end
|
161
|
-
return nil unless node.source =~ regex
|
162
|
-
zero_loc = CodeLocation.new(1, 0)
|
163
|
-
name_start = $~.begin(1)
|
164
|
-
name_length = $~.end(1) - name_start
|
165
|
-
name_head_loc = zero_loc.advance_cursor(name_start, node.source)
|
166
|
-
name_tail_loc = name_head_loc.advance_cursor(name_length, node.source)
|
167
|
-
return CodeRange.new(
|
168
|
-
CodeLocation.new(
|
169
|
-
node.first_lineno + (name_head_loc.lineno - 1),
|
170
|
-
name_head_loc.lineno == 1 ? node.first_column + name_head_loc.column : name_head_loc.column
|
171
|
-
),
|
172
|
-
CodeLocation.new(
|
173
|
-
node.first_lineno + (name_tail_loc.lineno - 1),
|
174
|
-
name_tail_loc.lineno == 1 ? node.first_column + name_tail_loc.column : name_tail_loc.column
|
175
|
-
),
|
176
|
-
)
|
177
|
-
when :block
|
178
|
-
return ISeq.code_range_from_node(node)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
def source_location(pc)
|
183
|
-
"#{ @path }:#{ @insns[pc].lineno }"
|
184
|
-
end
|
185
|
-
|
186
|
-
def detailed_source_location(pc)
|
187
|
-
code_range = @insns[pc].code_range
|
188
|
-
if code_range
|
189
|
-
[@path, code_range]
|
190
|
-
else
|
191
|
-
[@path]
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def add_called_iseq(pc, callee_iseq)
|
196
|
-
if callee_iseq && @insns[pc].definitions
|
197
|
-
@insns[pc].definitions << [callee_iseq.path, callee_iseq.iseq_code_range]
|
198
|
-
end
|
199
|
-
if callee_iseq.callers
|
200
|
-
callee_iseq.callers << [@path, @insns[pc].code_range]
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def add_def_loc(pc, detailed_loc)
|
205
|
-
if detailed_loc && @insns[pc].definitions
|
206
|
-
@insns[pc].definitions << detailed_loc
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
attr_reader :name, :path, :absolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns
|
211
|
-
attr_reader :id, :iseq_code_range, :callers
|
212
|
-
|
213
|
-
def pretty_print(q)
|
214
|
-
q.text "ISeq["
|
215
|
-
q.group do
|
216
|
-
q.nest(1) do
|
217
|
-
q.breakable ""
|
218
|
-
q.text "@type= #{ @type }"
|
219
|
-
q.breakable ", "
|
220
|
-
q.text "@name= #{ @name }"
|
221
|
-
q.breakable ", "
|
222
|
-
q.text "@path= #{ @path }"
|
223
|
-
q.breakable ", "
|
224
|
-
q.text "@absolute_path= #{ @absolute_path }"
|
225
|
-
q.breakable ", "
|
226
|
-
q.text "@start_lineno= #{ @start_lineno }"
|
227
|
-
q.breakable ", "
|
228
|
-
q.text "@fargs_format= #{ @fargs_format.inspect }"
|
229
|
-
q.breakable ", "
|
230
|
-
q.text "@insns="
|
231
|
-
q.group(2) do
|
232
|
-
@insns.each_with_index do |(insn, *operands), i|
|
233
|
-
q.breakable
|
234
|
-
q.group(2, "#{ i }: #{ insn.to_s }", "") do
|
235
|
-
q.pp operands
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
q.breakable
|
241
|
-
end
|
242
|
-
q.text "]"
|
243
|
-
end
|
244
|
-
|
245
|
-
def <=>(other)
|
246
|
-
@id <=> other.id
|
247
|
-
end
|
248
|
-
|
249
|
-
# Remove lineno entry and convert instructions to Insn instances
|
250
|
-
def convert_insns(insns, node_ids, file_info)
|
251
|
-
ninsns = []
|
252
|
-
lineno = 0
|
253
|
-
insns.each do |e|
|
254
|
-
case e
|
255
|
-
when Integer # lineno
|
256
|
-
lineno = e
|
257
|
-
when Symbol # label or trace
|
258
|
-
ninsns << e
|
259
|
-
when Array
|
260
|
-
insn, *operands = e
|
261
|
-
node_id = node_ids.shift
|
262
|
-
node = file_info.node_id2node[node_id]
|
263
|
-
if node
|
264
|
-
code_range = ISeq.code_range_from_node(node)
|
265
|
-
case insn
|
266
|
-
when :send, :invokesuper
|
267
|
-
opt, blk_iseq = operands
|
268
|
-
opt[:node_id] = node_id
|
269
|
-
if blk_iseq
|
270
|
-
misc = blk_iseq[4] # iseq's "misc" field
|
271
|
-
misc[:def_node_id] = node_id
|
272
|
-
end
|
273
|
-
when :definemethod, :definesmethod
|
274
|
-
iseq = operands[1]
|
275
|
-
misc = iseq[4] # iseq's "misc" field
|
276
|
-
misc[:def_node_id] = node_id
|
277
|
-
end
|
278
|
-
end
|
279
|
-
ninsns << i = Insn.new(insn, operands, lineno, code_range, nil)
|
280
|
-
else
|
281
|
-
raise "unknown iseq entry: #{ e }"
|
282
|
-
end
|
283
|
-
end
|
284
|
-
insns.replace(ninsns)
|
285
|
-
end
|
286
|
-
|
287
|
-
# Insert a dummy instruction "_iseq_body_start"
|
288
|
-
def add_body_start_marker(insns)
|
289
|
-
case @type
|
290
|
-
when :method, :block
|
291
|
-
# skip initialization code of optional arguments
|
292
|
-
if @fargs_format[:opt]
|
293
|
-
label = @fargs_format[:opt].last
|
294
|
-
i = insns.index(label) + 1
|
295
|
-
else
|
296
|
-
i = insns.find_index {|insn| insn.is_a?(Insn) }
|
297
|
-
end
|
298
|
-
|
299
|
-
# skip initialization code of keyword arguments
|
300
|
-
while insns[i][0] == :checkkeyword
|
301
|
-
raise if insns[i + 1].insn != :branchif
|
302
|
-
label = insns[i + 1].operands[0]
|
303
|
-
i = insns.index(label) + 1
|
304
|
-
end
|
305
|
-
|
306
|
-
insns.insert(i, Insn.new(:_iseq_body_start, [], @start_lineno, nil, nil))
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
# Insert "nop" instruction to continuation point of exception handlers
|
311
|
-
def add_exception_cont_marker(insns, catch_table)
|
312
|
-
# rescue/ensure clauses need to have a dedicated return addresses
|
313
|
-
# because they requires to be virtually called.
|
314
|
-
# So, this preprocess adds "nop" to make a new insn for their return addresses
|
315
|
-
exception_cont_labels = {}
|
316
|
-
catch_table.map! do |type, iseq, first, last, cont, stack_depth|
|
317
|
-
if type == :rescue || type == :ensure
|
318
|
-
exception_cont_labels[cont] = true
|
319
|
-
cont = :"#{ cont }_exception_cont"
|
320
|
-
end
|
321
|
-
[type, iseq, first, last, cont, stack_depth]
|
322
|
-
end
|
323
|
-
|
324
|
-
i = 0
|
325
|
-
while i < insns.size
|
326
|
-
e = insns[i]
|
327
|
-
if exception_cont_labels[e]
|
328
|
-
insns.insert(i, :"#{ e }_exception_cont", Insn.new(:nop, []))
|
329
|
-
i += 2
|
330
|
-
end
|
331
|
-
i += 1
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
|
-
def create_label_table(insns)
|
336
|
-
pc = 0
|
337
|
-
labels = {}
|
338
|
-
insns.each do |e|
|
339
|
-
if e.is_a?(Symbol)
|
340
|
-
labels[e] = pc
|
341
|
-
else
|
342
|
-
pc += 1
|
343
|
-
end
|
344
|
-
end
|
345
|
-
labels
|
346
|
-
end
|
347
|
-
|
348
|
-
def setup_insns(insns, labels, file_info)
|
349
|
-
ninsns = []
|
350
|
-
insns.each do |e|
|
351
|
-
case e
|
352
|
-
when Symbol # label or trace
|
353
|
-
nil
|
354
|
-
when Insn
|
355
|
-
operands = (INSN_TABLE[e.insn] || []).zip(e.operands).map do |type, operand|
|
356
|
-
case type
|
357
|
-
when "ISEQ"
|
358
|
-
operand && ISeq.new(operand, file_info)
|
359
|
-
when "lindex_t", "rb_num_t", "VALUE", "ID", "GENTRY", "CALL_DATA"
|
360
|
-
operand
|
361
|
-
when "OFFSET"
|
362
|
-
labels[operand] || raise("unknown label: #{ operand }")
|
363
|
-
when "IVC", "ISE"
|
364
|
-
raise unless operand.is_a?(Integer)
|
365
|
-
:_cache_operand
|
366
|
-
else
|
367
|
-
raise "unknown operand type: #{ type }"
|
368
|
-
end
|
369
|
-
end
|
370
|
-
|
371
|
-
if e.code_range && should_collect_defs(e.insn)
|
372
|
-
definition = Utils::MutableSet.new
|
373
|
-
file_info.definition_table[e.code_range] = definition
|
374
|
-
end
|
375
|
-
|
376
|
-
ninsns << Insn.new(e.insn, operands, e.lineno, e.code_range, definition)
|
377
|
-
else
|
378
|
-
raise "unknown iseq entry: #{ e }"
|
379
|
-
end
|
380
|
-
end
|
381
|
-
ninsns
|
382
|
-
end
|
383
|
-
|
384
|
-
def should_collect_defs(insn_kind)
|
385
|
-
case insn_kind
|
386
|
-
when :send, :getinstancevariable, :getconstant
|
387
|
-
return true
|
388
|
-
else
|
389
|
-
return false
|
390
|
-
end
|
391
|
-
end
|
392
|
-
|
393
|
-
# Collect local variable use and definition info recursively
|
394
|
-
def collect_local_variable_info(file_info, absolute_level = 0, parent_variable_tables = {})
|
395
|
-
# e.g.
|
396
|
-
# variable_tables[abs_level][idx] = [[path, code_range]]
|
397
|
-
current_variables = []
|
398
|
-
variable_tables = parent_variable_tables.merge({
|
399
|
-
absolute_level => current_variables
|
400
|
-
})
|
401
|
-
|
402
|
-
dummy_def_range = CodeRange.new(
|
403
|
-
CodeLocation.new(@start_lineno, 0),
|
404
|
-
CodeLocation.new(@start_lineno, 1),
|
405
|
-
)
|
406
|
-
# Fill tail elements with parameters
|
407
|
-
(@fargs_format[:lead_num] || 0).times do |offset|
|
408
|
-
current_variables[VM_ENV_DATA_SIZE + @locals.length - offset - 1] ||= Utils::MutableSet.new
|
409
|
-
current_variables[VM_ENV_DATA_SIZE + @locals.length - offset - 1] << [@path, dummy_def_range]
|
410
|
-
end
|
411
|
-
|
412
|
-
@insns.each do |insn|
|
413
|
-
next unless insn.insn == :getlocal || insn.insn == :setlocal
|
414
|
-
|
415
|
-
idx = insn.operands[0]
|
416
|
-
# note: level is relative value to the current level
|
417
|
-
level = insn.operands[1]
|
418
|
-
target_abs_level = absolute_level - level
|
419
|
-
variable_tables[target_abs_level] ||= {}
|
420
|
-
variable_tables[target_abs_level][idx] ||= Utils::MutableSet.new
|
421
|
-
|
422
|
-
case insn.insn
|
423
|
-
when :setlocal
|
424
|
-
variable_tables[target_abs_level][idx] << [path, insn.code_range]
|
425
|
-
when :getlocal
|
426
|
-
file_info.definition_table[insn.code_range] = variable_tables[target_abs_level][idx]
|
427
|
-
end
|
428
|
-
end
|
429
|
-
|
430
|
-
@insns.each do |insn|
|
431
|
-
insn.operands.each do |operand|
|
432
|
-
next unless operand.is_a?(ISeq)
|
433
|
-
operand.collect_local_variable_info(
|
434
|
-
file_info, absolute_level + 1,
|
435
|
-
variable_tables
|
436
|
-
)
|
437
|
-
end
|
438
|
-
end
|
439
|
-
end
|
440
|
-
|
441
|
-
def rename_insn_types
|
442
|
-
@insns.each do |insn|
|
443
|
-
case insn.insn
|
444
|
-
when :branchif
|
445
|
-
insn.insn, insn.operands = :branch, [:if] + insn.operands
|
446
|
-
when :branchunless
|
447
|
-
insn.insn, insn.operands = :branch, [:unless] + insn.operands
|
448
|
-
when :branchnil
|
449
|
-
insn.insn, insn.operands = :branch, [:nil] + insn.operands
|
450
|
-
when :getblockparam, :getblockparamproxy
|
451
|
-
insn.insn = :getlocal
|
452
|
-
when :getglobal
|
453
|
-
if insn.operands == [:$typeprof]
|
454
|
-
insn.insn = :putobject
|
455
|
-
insn.operands = [true]
|
456
|
-
end
|
457
|
-
end
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
# Unify some instructions for flow-sensitive analysis
|
462
|
-
def unify_instructions
|
463
|
-
# This method rewrites instructions to enable flow-sensitive analysis.
|
464
|
-
#
|
465
|
-
# Consider `if x; ...; else; ... end`.
|
466
|
-
# When the variable `x` is of type "Integer | nil",
|
467
|
-
# we want to make sure that `x` is "Integer" in then clause.
|
468
|
-
# So, we need to split the environment to two ones:
|
469
|
-
# one is that `x` is of type "Integer", and the other is that
|
470
|
-
# `x` is type "nil".
|
471
|
-
#
|
472
|
-
# However, `if x` is compiled to "getlocal; branch".
|
473
|
-
# TypeProf evaluates them as follows:
|
474
|
-
#
|
475
|
-
# * "getlocal" pushes the value of `x` to the stack, amd
|
476
|
-
# * "branch" checks the value on the top of the stack
|
477
|
-
#
|
478
|
-
# TypeProf does not keep where the value comes from, so
|
479
|
-
# it is difficult to split the environment when evaluating "branch".
|
480
|
-
#
|
481
|
-
# This method rewrites "getlocal; branch" to "nop; getlocal_branch".
|
482
|
-
# The two instructions are unified to "getlocal_branch" instruction,
|
483
|
-
# so TypeProf can split the environment.
|
484
|
-
#
|
485
|
-
# This is a very fragile appoach because it highly depends on the compiler of Ruby.
|
486
|
-
|
487
|
-
# gather branch targets
|
488
|
-
# TODO: catch_table should be also considered
|
489
|
-
branch_targets = {}
|
490
|
-
@insns.each do |insn|
|
491
|
-
case insn.insn
|
492
|
-
when :branch
|
493
|
-
branch_targets[insn.operands[1]] = true
|
494
|
-
when :jump
|
495
|
-
branch_targets[insn.operands[0]] = true
|
496
|
-
end
|
497
|
-
end
|
498
|
-
|
499
|
-
# flow-sensitive analysis for `case var; when A; when B; when C; end`
|
500
|
-
# find a pattern: getlocal, (dup, putobject(true), getconstant(class name), checkmatch, branch)* for ..Ruby 3.0
|
501
|
-
# find a pattern: getlocal, (putobject(true), getconstant(class name), top(1), send(===), branch)* for Ruby 3.1..
|
502
|
-
case_branch_list = []
|
503
|
-
if CASE_WHEN_CHECKMATCH
|
504
|
-
(@insns.size - 1).times do |i|
|
505
|
-
insn = @insns[i]
|
506
|
-
next unless insn.insn == :getlocal && insn.operands[1] == 0
|
507
|
-
getlocal_operands = insn.operands
|
508
|
-
nops = [i]
|
509
|
-
new_insns = []
|
510
|
-
j = i + 1
|
511
|
-
while true
|
512
|
-
case @insns[j].insn
|
513
|
-
when :dup
|
514
|
-
break unless @insns[j + 1].check?(:putnil, [])
|
515
|
-
break unless @insns[j + 2].check?(:putobject, [true])
|
516
|
-
break unless @insns[j + 3].check?(:getconstant) # TODO: support A::B::C
|
517
|
-
break unless @insns[j + 4].check?(:checkmatch, [2])
|
518
|
-
break unless @insns[j + 5].check?(:branch)
|
519
|
-
target_pc = @insns[j + 5].operands[1]
|
520
|
-
break unless @insns[target_pc].check?(:pop, [])
|
521
|
-
nops << j << (j + 4) << target_pc
|
522
|
-
branch_operands = @insns[j + 5][1]
|
523
|
-
new_insns << [j + 5, Insn.new(:getlocal_checkmatch_branch, [getlocal_operands, branch_operands])]
|
524
|
-
j += 6
|
525
|
-
when :pop
|
526
|
-
nops << j
|
527
|
-
case_branch_list << [nops, new_insns]
|
528
|
-
break
|
529
|
-
else
|
530
|
-
break
|
531
|
-
end
|
532
|
-
end
|
533
|
-
end
|
534
|
-
else
|
535
|
-
(@insns.size - 1).times do |i|
|
536
|
-
insn = @insns[i]
|
537
|
-
next unless insn.insn == :getlocal && insn.operands[1] == 0
|
538
|
-
getlocal_operands = insn.operands
|
539
|
-
nops = []
|
540
|
-
new_insns = []
|
541
|
-
j = i + 1
|
542
|
-
while true
|
543
|
-
insn = @insns[j]
|
544
|
-
if insn.check?(:putnil, [])
|
545
|
-
break unless @insns[j + 1].check?(:putobject, [true])
|
546
|
-
break unless @insns[j + 2].check?(:getconstant) # TODO: support A::B::C
|
547
|
-
break unless @insns[j + 3].check?(:topn, [1])
|
548
|
-
break unless @insns[j + 4].check?(:send) && @insns[j + 4].operands[0].slice(:mid, :flag, :orig_argc) == {:mid=>:===, :flag=>20, :orig_argc=>1}
|
549
|
-
break unless @insns[j + 5].check?(:branch)
|
550
|
-
target_pc = @insns[j + 5].operands[1]
|
551
|
-
break unless @insns[target_pc].check?(:pop, [])
|
552
|
-
nops << (j + 4) #<< target_pc
|
553
|
-
send_operands = @insns[j + 4][1]
|
554
|
-
branch_operands = @insns[j + 5][1]
|
555
|
-
new_insns << [j + 5, Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])]
|
556
|
-
j += 6
|
557
|
-
elsif insn.check?(:pop, [])
|
558
|
-
#nops << j
|
559
|
-
case_branch_list << [nops, new_insns]
|
560
|
-
break
|
561
|
-
else
|
562
|
-
break
|
563
|
-
end
|
564
|
-
end
|
565
|
-
end
|
566
|
-
end
|
567
|
-
case_branch_list.each do |nops, new_insns|
|
568
|
-
nops.each {|i| @insns[i] = Insn.new(:nop, []) }
|
569
|
-
new_insns.each {|i, insn| @insns[i] = insn }
|
570
|
-
end
|
571
|
-
|
572
|
-
# find a pattern: getlocal(recv), ..., send (is_a?, respond_to?), branch
|
573
|
-
recv_getlocal_send_branch_list = []
|
574
|
-
(@insns.size - 1).times do |i|
|
575
|
-
insn = @insns[i]
|
576
|
-
if insn.insn == :getlocal && insn.operands[1] == 0
|
577
|
-
j = i + 1
|
578
|
-
sp = 1
|
579
|
-
while @insns[j]
|
580
|
-
sp = check_send_branch(sp, j)
|
581
|
-
if sp == :match
|
582
|
-
recv_getlocal_send_branch_list << [i, j]
|
583
|
-
break
|
584
|
-
end
|
585
|
-
break if !sp
|
586
|
-
j += 1
|
587
|
-
end
|
588
|
-
end
|
589
|
-
end
|
590
|
-
recv_getlocal_send_branch_list.each do |i, j|
|
591
|
-
next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
|
592
|
-
getlocal_operands = @insns[i].operands
|
593
|
-
send_operands = @insns[j].operands
|
594
|
-
branch_operands = @insns[j + 1].operands
|
595
|
-
@insns[j] = Insn.new(:nop, [])
|
596
|
-
@insns[j + 1] = Insn.new(:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
|
597
|
-
end
|
598
|
-
|
599
|
-
# find a pattern: getlocal, send (===), branch
|
600
|
-
arg_getlocal_send_branch_list = []
|
601
|
-
(@insns.size - 1).times do |i|
|
602
|
-
insn1 = @insns[i]
|
603
|
-
next unless insn1.insn == :getlocal && insn1.operands[1] == 0
|
604
|
-
insn2 = @insns[i + 1]
|
605
|
-
next unless insn2.insn == :send
|
606
|
-
send_operands = insn2.operands[0]
|
607
|
-
next unless send_operands[:flag] == 16 && send_operands[:orig_argc] == 1
|
608
|
-
insn3 = @insns[i + 2]
|
609
|
-
next unless insn3.insn == :branch
|
610
|
-
arg_getlocal_send_branch_list << i
|
611
|
-
end
|
612
|
-
arg_getlocal_send_branch_list.each do |i|
|
613
|
-
next if (i .. i + 2).any? {|i| branch_targets[i] }
|
614
|
-
getlocal_operands = @insns[i].operands
|
615
|
-
send_operands = @insns[i + 1].operands
|
616
|
-
branch_operands = @insns[i + 2].operands
|
617
|
-
@insns[i + 1] = Insn.new(:nop, [])
|
618
|
-
@insns[i + 2] = Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
|
619
|
-
end
|
620
|
-
|
621
|
-
# find a pattern: send (block_given?), branch
|
622
|
-
send_branch_list = []
|
623
|
-
(@insns.size - 1).times do |i|
|
624
|
-
insn = @insns[i]
|
625
|
-
if insn.insn == :send
|
626
|
-
insn = @insns[i + 1]
|
627
|
-
if insn.insn == :branch
|
628
|
-
send_branch_list << i
|
629
|
-
end
|
630
|
-
end
|
631
|
-
end
|
632
|
-
send_branch_list.each do |i|
|
633
|
-
next if branch_targets[i + 1]
|
634
|
-
send_operands = @insns[i].operands
|
635
|
-
branch_operands = @insns[i + 1].operands
|
636
|
-
@insns[i] = Insn.new(:nop, [])
|
637
|
-
@insns[i + 1] = Insn.new(:send_branch, [send_operands, branch_operands])
|
638
|
-
end
|
639
|
-
|
640
|
-
# find a pattern: getlocal, dup, branch
|
641
|
-
(@insns.size - 2).times do |i|
|
642
|
-
next if branch_targets[i + 1] || branch_targets[i + 2]
|
643
|
-
insn0 = @insns[i]
|
644
|
-
insn1 = @insns[i + 1]
|
645
|
-
insn2 = @insns[i + 2]
|
646
|
-
if insn0.insn == :getlocal && insn1.insn == :dup && insn2.insn == :branch && insn0.operands[1] == 0
|
647
|
-
getlocal_operands = insn0.operands
|
648
|
-
dup_operands = insn1.operands
|
649
|
-
branch_operands = insn2.operands
|
650
|
-
@insns[i ] = Insn.new(:nop, [])
|
651
|
-
@insns[i + 1] = Insn.new(:nop, [])
|
652
|
-
@insns[i + 2] = Insn.new(:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands])
|
653
|
-
end
|
654
|
-
end
|
655
|
-
|
656
|
-
# find a pattern: dup, setlocal, branch
|
657
|
-
(@insns.size - 2).times do |i|
|
658
|
-
next if branch_targets[i + 1] || branch_targets[i + 2]
|
659
|
-
insn0 = @insns[i]
|
660
|
-
insn1 = @insns[i + 1]
|
661
|
-
insn2 = @insns[i + 2]
|
662
|
-
if insn0.insn == :dup && insn1.insn == :setlocal && insn2.insn == :branch && insn1.operands[1] == 0
|
663
|
-
dup_operands = insn0.operands
|
664
|
-
setlocal_operands = insn1.operands
|
665
|
-
branch_operands = insn2.operands
|
666
|
-
@insns[i ] = Insn.new(:nop, [])
|
667
|
-
@insns[i + 1] = Insn.new(:nop, [])
|
668
|
-
@insns[i + 2] = Insn.new(:dup_setlocal_branch, [dup_operands, setlocal_operands, branch_operands])
|
669
|
-
end
|
670
|
-
end
|
671
|
-
|
672
|
-
# find a pattern: dup, branch
|
673
|
-
(@insns.size - 1).times do |i|
|
674
|
-
next if branch_targets[i + 1]
|
675
|
-
insn0 = @insns[i]
|
676
|
-
insn1 = @insns[i + 1]
|
677
|
-
if insn0.insn == :dup && insn1.insn == :branch
|
678
|
-
dup_operands = insn0.operands
|
679
|
-
branch_operands = insn1.operands
|
680
|
-
@insns[i ] = Insn.new(:nop, [])
|
681
|
-
@insns[i + 1] = Insn.new(:dup_branch, [dup_operands, branch_operands])
|
682
|
-
end
|
683
|
-
end
|
684
|
-
|
685
|
-
# find a pattern: getlocal, branch
|
686
|
-
(@insns.size - 1).times do |i|
|
687
|
-
next if branch_targets[i + 1]
|
688
|
-
insn0 = @insns[i]
|
689
|
-
insn1 = @insns[i + 1]
|
690
|
-
if insn0.insn == :getlocal && insn0.operands[1] == 0 && insn1.insn == :branch
|
691
|
-
getlocal_operands = insn0.operands
|
692
|
-
branch_operands = insn1.operands
|
693
|
-
@insns[i ] = Insn.new(:nop, [])
|
694
|
-
@insns[i + 1] = Insn.new(:getlocal_branch, [getlocal_operands, branch_operands])
|
695
|
-
end
|
696
|
-
end
|
697
|
-
|
698
|
-
# find a pattern: putobject, branch
|
699
|
-
(@insns.size - 1).times do |i|
|
700
|
-
next if branch_targets[i + 1]
|
701
|
-
insn0 = @insns[i]
|
702
|
-
insn1 = @insns[i + 1]
|
703
|
-
if insn0.insn == :putobject && insn1.insn == :branch
|
704
|
-
putobject_operands = insn0.operands
|
705
|
-
branch_operands = insn1.operands
|
706
|
-
obj = putobject_operands[0]
|
707
|
-
branch_type = branch_operands[0]
|
708
|
-
branch_target = branch_operands[1]
|
709
|
-
case branch_type
|
710
|
-
when :if then jump = !!obj
|
711
|
-
when :unless then jump = !obj
|
712
|
-
when :nil then jump = obj == nil
|
713
|
-
end
|
714
|
-
@insns[i ] = Insn.new(:nop, [])
|
715
|
-
@insns[i + 1] = jump ? Insn.new(:jump, [branch_target]) : Insn.new(:nop, [])
|
716
|
-
end
|
717
|
-
end
|
718
|
-
end
|
719
|
-
|
720
|
-
def check_send_branch(sp, j)
|
721
|
-
insn = @insns[j]
|
722
|
-
operands = insn.operands
|
723
|
-
|
724
|
-
case insn.insn
|
725
|
-
when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
|
726
|
-
:putself
|
727
|
-
sp += 1
|
728
|
-
when :newarray, :newarraykwsplat, :newhash, :concatstrings
|
729
|
-
len, = operands
|
730
|
-
sp =- len
|
731
|
-
return nil if sp <= 0
|
732
|
-
sp += 1
|
733
|
-
when :newhashfromarray
|
734
|
-
raise NotImplementedError, "newhashfromarray"
|
735
|
-
when :newrange, :tostring, :objtostring, :anytostring
|
736
|
-
sp -= 2
|
737
|
-
return nil if sp <= 0
|
738
|
-
sp += 1
|
739
|
-
when :freezestring
|
740
|
-
# XXX: should leverage this information?
|
741
|
-
when :toregexp
|
742
|
-
_regexp_opt, len = operands
|
743
|
-
sp -= len
|
744
|
-
return nil if sp <= 0
|
745
|
-
sp += 1
|
746
|
-
when :intern
|
747
|
-
sp -= 1
|
748
|
-
return nil if sp <= 0
|
749
|
-
sp += 1
|
750
|
-
when :definemethod, :definesmethod
|
751
|
-
when :defineclass
|
752
|
-
sp -= 2
|
753
|
-
when :send, :invokesuper
|
754
|
-
opt, = operands
|
755
|
-
_flags = opt[:flag]
|
756
|
-
_mid = opt[:mid]
|
757
|
-
kw_arg = opt[:kw_arg]
|
758
|
-
argc = opt[:orig_argc]
|
759
|
-
argc += 1 # receiver
|
760
|
-
argc += kw_arg.size if kw_arg
|
761
|
-
sp -= argc
|
762
|
-
return :match if insn.insn == :send && sp == 0 && @insns[j + 1].insn == :branch
|
763
|
-
sp += 1
|
764
|
-
when :arg_getlocal_send_branch
|
765
|
-
return # not implemented
|
766
|
-
when :invokeblock
|
767
|
-
opt, = operands
|
768
|
-
sp -= opt[:orig_argc]
|
769
|
-
return nil if sp <= 0
|
770
|
-
sp += 1
|
771
|
-
when :invokebuiltin
|
772
|
-
raise NotImplementedError
|
773
|
-
when :leave, :throw
|
774
|
-
return
|
775
|
-
when :once
|
776
|
-
return # not implemented
|
777
|
-
when :branch, :jump
|
778
|
-
return # not implemented
|
779
|
-
when :setinstancevariable, :setclassvariable, :setglobal
|
780
|
-
sp -= 1
|
781
|
-
when :setlocal, :setblockparam
|
782
|
-
return # conservative
|
783
|
-
when :getinstancevariable, :getclassvariable, :getglobal,
|
784
|
-
:getlocal, :getblockparam, :getblockparamproxy, :getlocal_checkmatch_branch
|
785
|
-
sp += 1
|
786
|
-
when :getconstant
|
787
|
-
sp -= 2
|
788
|
-
return nil if sp <= 0
|
789
|
-
sp += 1
|
790
|
-
when :setconstant
|
791
|
-
sp -= 2
|
792
|
-
when :getspecial
|
793
|
-
sp += 1
|
794
|
-
when :setspecial
|
795
|
-
# flip-flop
|
796
|
-
raise NotImplementedError, "setspecial"
|
797
|
-
when :dup
|
798
|
-
sp += 1
|
799
|
-
when :duphash
|
800
|
-
sp += 1
|
801
|
-
when :dupn
|
802
|
-
n, = operands
|
803
|
-
sp += n
|
804
|
-
when :pop
|
805
|
-
sp -= 1
|
806
|
-
when :swap
|
807
|
-
sp -= 2
|
808
|
-
return nil if sp <= 0
|
809
|
-
sp += 2
|
810
|
-
when :reverse
|
811
|
-
n, = operands
|
812
|
-
sp -= n
|
813
|
-
return nil if sp <= 0
|
814
|
-
sp += n
|
815
|
-
when :defined
|
816
|
-
sp -= 1
|
817
|
-
return nil if sp <= 0
|
818
|
-
sp += 1
|
819
|
-
when :checkmatch
|
820
|
-
sp -= 2
|
821
|
-
return nil if sp <= 0
|
822
|
-
sp += 1
|
823
|
-
when :checkkeyword
|
824
|
-
sp += 1
|
825
|
-
when :adjuststack
|
826
|
-
n, = operands
|
827
|
-
sp -= n
|
828
|
-
when :nop
|
829
|
-
when :setn
|
830
|
-
return nil # not implemented
|
831
|
-
when :topn
|
832
|
-
sp += 1
|
833
|
-
when :splatarray
|
834
|
-
sp -= 1
|
835
|
-
return nil if sp <= 0
|
836
|
-
sp += 1
|
837
|
-
when :expandarray
|
838
|
-
num, flag = operands
|
839
|
-
splat = flag & 1 == 1
|
840
|
-
sp -= 1
|
841
|
-
return nil if sp <= 0
|
842
|
-
sp += num + (splat ? 1 : 0)
|
843
|
-
when :concatarray, :concattoarray
|
844
|
-
sp -= 2
|
845
|
-
return nil if sp <= 0
|
846
|
-
sp += 1
|
847
|
-
when :pushtoarray
|
848
|
-
num, = operands
|
849
|
-
sp -= num + 1
|
850
|
-
return nil if sp <= 0
|
851
|
-
sp += 1
|
852
|
-
when :checktype
|
853
|
-
sp -= 1
|
854
|
-
return nil if sp <= 0
|
855
|
-
sp += 1
|
856
|
-
else
|
857
|
-
raise "Unknown insn: #{ insn }"
|
858
|
-
end
|
859
|
-
|
860
|
-
return nil if sp <= 0
|
861
|
-
sp
|
862
|
-
end
|
863
|
-
end
|
864
|
-
end
|