typeprof 0.21.11 → 0.30.0

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