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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -31
  3. data/bin/typeprof +5 -0
  4. data/doc/doc.ja.md +134 -0
  5. data/doc/doc.md +136 -0
  6. data/lib/typeprof/cli/cli.rb +180 -0
  7. data/lib/typeprof/cli.rb +2 -133
  8. data/lib/typeprof/code_range.rb +112 -0
  9. data/lib/typeprof/core/ast/base.rb +263 -0
  10. data/lib/typeprof/core/ast/call.rb +251 -0
  11. data/lib/typeprof/core/ast/const.rb +126 -0
  12. data/lib/typeprof/core/ast/control.rb +432 -0
  13. data/lib/typeprof/core/ast/meta.rb +150 -0
  14. data/lib/typeprof/core/ast/method.rb +335 -0
  15. data/lib/typeprof/core/ast/misc.rb +263 -0
  16. data/lib/typeprof/core/ast/module.rb +123 -0
  17. data/lib/typeprof/core/ast/pattern.rb +140 -0
  18. data/lib/typeprof/core/ast/sig_decl.rb +471 -0
  19. data/lib/typeprof/core/ast/sig_type.rb +663 -0
  20. data/lib/typeprof/core/ast/value.rb +319 -0
  21. data/lib/typeprof/core/ast/variable.rb +315 -0
  22. data/lib/typeprof/core/ast.rb +472 -0
  23. data/lib/typeprof/core/builtin.rb +146 -0
  24. data/lib/typeprof/core/env/method.rb +137 -0
  25. data/lib/typeprof/core/env/method_entity.rb +55 -0
  26. data/lib/typeprof/core/env/module_entity.rb +408 -0
  27. data/lib/typeprof/core/env/static_read.rb +155 -0
  28. data/lib/typeprof/core/env/type_alias_entity.rb +27 -0
  29. data/lib/typeprof/core/env/value_entity.rb +32 -0
  30. data/lib/typeprof/core/env.rb +360 -0
  31. data/lib/typeprof/core/graph/box.rb +991 -0
  32. data/lib/typeprof/core/graph/change_set.rb +224 -0
  33. data/lib/typeprof/core/graph/filter.rb +155 -0
  34. data/lib/typeprof/core/graph/vertex.rb +222 -0
  35. data/lib/typeprof/core/graph.rb +3 -0
  36. data/lib/typeprof/core/service.rb +522 -0
  37. data/lib/typeprof/core/type.rb +348 -0
  38. data/lib/typeprof/core/util.rb +81 -0
  39. data/lib/typeprof/core.rb +32 -0
  40. data/lib/typeprof/diagnostic.rb +35 -0
  41. data/lib/typeprof/lsp/messages.rb +430 -0
  42. data/lib/typeprof/lsp/server.rb +177 -0
  43. data/lib/typeprof/lsp/text.rb +69 -0
  44. data/lib/typeprof/lsp/util.rb +61 -0
  45. data/lib/typeprof/lsp.rb +4 -907
  46. data/lib/typeprof/version.rb +1 -1
  47. data/lib/typeprof.rb +4 -18
  48. data/typeprof.gemspec +5 -7
  49. metadata +48 -35
  50. data/.github/dependabot.yml +0 -6
  51. data/.github/workflows/main.yml +0 -39
  52. data/.gitignore +0 -9
  53. data/Gemfile +0 -17
  54. data/Gemfile.lock +0 -41
  55. data/Rakefile +0 -10
  56. data/exe/typeprof +0 -10
  57. data/lib/typeprof/analyzer.rb +0 -2598
  58. data/lib/typeprof/arguments.rb +0 -414
  59. data/lib/typeprof/block.rb +0 -176
  60. data/lib/typeprof/builtin.rb +0 -893
  61. data/lib/typeprof/code-range.rb +0 -177
  62. data/lib/typeprof/config.rb +0 -158
  63. data/lib/typeprof/container-type.rb +0 -912
  64. data/lib/typeprof/export.rb +0 -589
  65. data/lib/typeprof/import.rb +0 -852
  66. data/lib/typeprof/insns-def.rb +0 -65
  67. data/lib/typeprof/iseq.rb +0 -864
  68. data/lib/typeprof/method.rb +0 -355
  69. data/lib/typeprof/type.rb +0 -1140
  70. data/lib/typeprof/utils.rb +0 -212
  71. data/tools/coverage.rb +0 -14
  72. data/tools/setup-insns-def.rb +0 -30
  73. 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