typeprof 0.14.1 → 0.15.3
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/Gemfile.lock +5 -5
- data/lib/typeprof/analyzer.rb +154 -85
- data/lib/typeprof/builtin.rb +22 -12
- data/lib/typeprof/cli.rb +1 -0
- data/lib/typeprof/config.rb +2 -1
- data/lib/typeprof/container-type.rb +7 -1
- data/lib/typeprof/export.rb +9 -5
- data/lib/typeprof/import.rb +60 -21
- data/lib/typeprof/iseq.rb +303 -201
- data/lib/typeprof/method.rb +2 -2
- data/lib/typeprof/type.rb +97 -47
- data/lib/typeprof/version.rb +1 -1
- data/smoke/alias2.rb +1 -1
- data/smoke/array15.rb +1 -1
- data/smoke/array3.rb +1 -1
- data/smoke/array6.rb +2 -2
- data/smoke/array8.rb +1 -1
- data/smoke/attr-module.rb +1 -4
- data/smoke/attr-vis.rb +1 -1
- data/smoke/attr.rb +1 -1
- data/smoke/block-args2.rb +3 -3
- data/smoke/block-args3.rb +4 -4
- data/smoke/break2.rb +1 -1
- data/smoke/gvar2.rb +0 -3
- data/smoke/hash-bot.rb +1 -1
- data/smoke/hash4.rb +1 -1
- data/smoke/huge_union.rb +86 -0
- data/smoke/identifier_keywords.rb +17 -0
- data/smoke/initialize.rb +1 -1
- data/smoke/ivar2.rb +1 -1
- data/smoke/ivar3.rb +1 -1
- data/smoke/kwrest.rb +2 -2
- data/smoke/kwrest.rbs +1 -1
- data/smoke/method_missing.rb +1 -1
- data/smoke/next2.rb +1 -1
- data/smoke/noname.rb +9 -0
- data/smoke/or_raise.rb +18 -0
- data/smoke/parameterizedd-self.rb +2 -2
- data/smoke/pattern-match1.rb +1 -6
- data/smoke/proc6.rb +13 -0
- data/smoke/proc7.rb +32 -0
- data/smoke/rbs-tyvar4.rb +1 -1
- data/smoke/rbs-vars.rb +0 -3
- data/smoke/require1.rb +13 -0
- data/smoke/require2.rb +13 -0
- data/smoke/struct5.rb +1 -1
- data/smoke/struct6.rb +1 -1
- data/smoke/struct7.rb +1 -1
- data/smoke/super3.rb +1 -1
- data/smoke/symbol-proc-attr.rb +1 -1
- data/smoke/symbol-proc-attr2.rb +1 -1
- data/testbed/ao.rb +1 -1
- data/typeprof.gemspec +1 -1
- metadata +12 -4
data/lib/typeprof/builtin.rb
CHANGED
@@ -73,9 +73,9 @@ module TypeProf
|
|
73
73
|
else
|
74
74
|
ty = Type::Instance.new(recv)
|
75
75
|
end
|
76
|
-
meths = scratch.get_method(recv, false, :initialize)
|
76
|
+
meths = scratch.get_method(recv, false, false, :initialize)
|
77
77
|
meths.flat_map do |meth|
|
78
|
-
meth.do_send(ty, :initialize, aargs, ep, env, scratch) do |
|
78
|
+
meth.do_send(ty, :initialize, aargs, ep, env, scratch) do |_ret_ty, ep, env|
|
79
79
|
ctn[ty, ep, env]
|
80
80
|
end
|
81
81
|
end
|
@@ -104,7 +104,7 @@ module TypeProf
|
|
104
104
|
sym = get_sym("respond_to?", aargs.lead_tys[0], ep, scratch)
|
105
105
|
if sym
|
106
106
|
klass, singleton = recv.method_dispatch_info
|
107
|
-
if scratch.get_method(klass, singleton, sym)
|
107
|
+
if scratch.get_method(klass, singleton, false, sym)
|
108
108
|
true_val = Type::Instance.new(Type::Builtin[:true])
|
109
109
|
ctn[true_val, ep, env]
|
110
110
|
else
|
@@ -334,7 +334,7 @@ module TypeProf
|
|
334
334
|
else
|
335
335
|
aargs.lead_tys.each do |aarg|
|
336
336
|
sym = get_sym("module_function", aarg, ep, scratch) or next
|
337
|
-
meths = scratch.get_method(recv, false, sym)
|
337
|
+
meths = scratch.get_method(recv, false, false, sym)
|
338
338
|
meths.each do |mdef|
|
339
339
|
scratch.add_method(recv, sym, true, mdef)
|
340
340
|
end
|
@@ -350,7 +350,7 @@ module TypeProf
|
|
350
350
|
if recv.is_a?(Type::Class)
|
351
351
|
aargs.lead_tys.each do |aarg|
|
352
352
|
sym = get_sym("public", aarg, ep, scratch) or next
|
353
|
-
meths = scratch.get_method(recv, false, sym)
|
353
|
+
meths = scratch.get_method(recv, false, false, sym)
|
354
354
|
next unless meths
|
355
355
|
meths.each do |mdef|
|
356
356
|
mdef.pub_meth = true if mdef.respond_to?(:pub_meth=)
|
@@ -370,7 +370,7 @@ module TypeProf
|
|
370
370
|
if recv.is_a?(Type::Class)
|
371
371
|
aargs.lead_tys.each do |aarg|
|
372
372
|
sym = get_sym("private", aarg, ep, scratch) or next
|
373
|
-
meths = scratch.get_method(recv, false, sym)
|
373
|
+
meths = scratch.get_method(recv, false, false, sym)
|
374
374
|
next unless meths
|
375
375
|
meths.each do |mdef|
|
376
376
|
mdef.pub_meth = false if mdef.respond_to?(:pub_meth=)
|
@@ -635,9 +635,6 @@ module TypeProf
|
|
635
635
|
end
|
636
636
|
|
637
637
|
def self.file_require(feature, scratch)
|
638
|
-
return :done, :false if scratch.loaded_features[feature]
|
639
|
-
scratch.loaded_features[feature] = true
|
640
|
-
|
641
638
|
# XXX: dynamic RBS load is really needed?? Another idea:
|
642
639
|
#
|
643
640
|
# * RBS should be loaded in advance of analysis
|
@@ -653,7 +650,11 @@ module TypeProf
|
|
653
650
|
|
654
651
|
begin
|
655
652
|
filetype, path = $LOAD_PATH.resolve_feature_path(feature)
|
653
|
+
|
656
654
|
if filetype == :rb
|
655
|
+
return :done, :false if scratch.loaded_files[path]
|
656
|
+
scratch.loaded_files[path] = true
|
657
|
+
|
657
658
|
return :do, path if File.readable?(path)
|
658
659
|
|
659
660
|
return :error, "failed to load: #{ path }"
|
@@ -714,13 +715,14 @@ module TypeProf
|
|
714
715
|
return ctn[Type.any, ep, env]
|
715
716
|
end
|
716
717
|
|
717
|
-
|
718
|
+
path = File.join(File.dirname(ep.ctx.iseq.absolute_path), feature) + ".rb" # XXX
|
719
|
+
|
720
|
+
if scratch.loaded_files[path]
|
718
721
|
result = Type::Instance.new(Type::Builtin[:false])
|
719
722
|
return ctn[result, ep, env]
|
720
723
|
end
|
721
|
-
scratch.
|
724
|
+
scratch.loaded_files[path] = true
|
722
725
|
|
723
|
-
path = File.join(File.dirname(ep.ctx.iseq.path), feature) + ".rb" # XXX
|
724
726
|
return Builtin.file_load(path, ep, env, scratch, &ctn) if File.readable?(path)
|
725
727
|
|
726
728
|
scratch.warn(ep, "failed to load: #{ path }")
|
@@ -811,6 +813,7 @@ module TypeProf
|
|
811
813
|
Type::Builtin[:exc] = scratch.get_constant(klass_obj, :Exception)
|
812
814
|
Type::Builtin[:encoding] = scratch.get_constant(klass_obj, :Encoding)
|
813
815
|
Type::Builtin[:enumerator] = scratch.get_constant(klass_obj, :Enumerator)
|
816
|
+
Type::Builtin[:kernel] = scratch.get_constant(klass_obj, :Kernel)
|
814
817
|
|
815
818
|
klass_vmcore = Type::Builtin[:vmcore]
|
816
819
|
klass_ary = Type::Builtin[:ary]
|
@@ -823,6 +826,7 @@ module TypeProf
|
|
823
826
|
scratch.set_custom_method(klass_vmcore, :"core#undef_method", Builtin.method(:vmcore_undef_method))
|
824
827
|
scratch.set_custom_method(klass_vmcore, :"core#hash_merge_kwd", Builtin.method(:vmcore_hash_merge_kwd))
|
825
828
|
scratch.set_custom_method(klass_vmcore, :"core#raise", Builtin.method(:vmcore_raise))
|
829
|
+
|
826
830
|
scratch.set_custom_method(klass_vmcore, :lambda, Builtin.method(:lambda))
|
827
831
|
scratch.set_singleton_custom_method(klass_obj, :"new", Builtin.method(:object_s_new))
|
828
832
|
scratch.set_custom_method(klass_obj, :p, Builtin.method(:kernel_p), false)
|
@@ -878,6 +882,12 @@ module TypeProf
|
|
878
882
|
str_ty = Type::Instance.new(Type::Builtin[:str])
|
879
883
|
env_ty = Type.gen_hash {|h| h[str_ty] = Type.optional(str_ty) }
|
880
884
|
scratch.add_constant(klass_obj, :ENV, env_ty, false)
|
885
|
+
|
886
|
+
scratch.search_method(Type::Builtin[:kernel], false, :sprintf) do |mdefs,|
|
887
|
+
mdefs.each do |mdef|
|
888
|
+
scratch.add_method(klass_vmcore, :"core#sprintf", false, mdef)
|
889
|
+
end
|
890
|
+
end
|
881
891
|
end
|
882
892
|
end
|
883
893
|
end
|
data/lib/typeprof/cli.rb
CHANGED
@@ -64,6 +64,7 @@ module TypeProf
|
|
64
64
|
opt.separator "Advanced options:"
|
65
65
|
opt.on("--[no-]stub-execution", "Force to call all unreachable methods with \"untyped\" arguments") {|v| options[:stub_execution] = v }
|
66
66
|
opt.on("--type-depth-limit DEPTH", Integer, "Limit the maximum depth of nested types") {|v| options[:type_depth_limit] = v }
|
67
|
+
opt.on("--union-width-limit WIDTH", Integer, "Limit the maximum count of class instances in one union type") {|v| options[:union_width_limit] = v }
|
67
68
|
opt.on("--debug", "Display analysis log (for debugging purpose)") { verbose = 2 }
|
68
69
|
opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| options[:stackprof] = v.to_sym }
|
69
70
|
|
data/lib/typeprof/config.rb
CHANGED
@@ -40,6 +40,7 @@ module TypeProf
|
|
40
40
|
show_source_locations: false,
|
41
41
|
stub_execution: true,
|
42
42
|
type_depth_limit: 5,
|
43
|
+
union_width_limit: 10,
|
43
44
|
stackprof: nil,
|
44
45
|
}.merge(opt[:options])
|
45
46
|
super(**opt)
|
@@ -98,7 +99,7 @@ module TypeProf
|
|
98
99
|
if rb.is_a?(Array) # [String name, String content]
|
99
100
|
iseq = ISeq.compile_str(*rb.reverse)
|
100
101
|
else
|
101
|
-
iseq =
|
102
|
+
iseq = rb
|
102
103
|
end
|
103
104
|
scratch.add_entrypoint(iseq)
|
104
105
|
end
|
@@ -307,7 +307,11 @@ module TypeProf
|
|
307
307
|
def screen_name(scratch)
|
308
308
|
if @rest_ty == Type.bot
|
309
309
|
if @lead_tys.empty?
|
310
|
-
|
310
|
+
# This is heuristic: in general, an empty array is a wrong guess.
|
311
|
+
# Note that an empty array is representable as "[ ]" in RBS, but
|
312
|
+
# users often have to modify it to "Array[something]".
|
313
|
+
# In this term, "Array[untyped]" is considered more useful than "[ ]".
|
314
|
+
return "Array[untyped]"
|
311
315
|
end
|
312
316
|
s = @lead_tys.map do |ty|
|
313
317
|
ty.screen_name(scratch)
|
@@ -673,6 +677,8 @@ module TypeProf
|
|
673
677
|
k_ty = k_ty.union(k)
|
674
678
|
v_ty = v_ty.union(v)
|
675
679
|
end
|
680
|
+
k_ty = Type.any if k_ty == Type.bot
|
681
|
+
v_ty = Type.any if v_ty == Type.bot
|
676
682
|
k_ty = k_ty.screen_name(scratch)
|
677
683
|
v_ty = v_ty.screen_name(scratch)
|
678
684
|
"Hash[#{ k_ty }, #{ v_ty }]"
|
data/lib/typeprof/export.rb
CHANGED
@@ -69,14 +69,17 @@ module TypeProf
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def show_gvars(scratch, gvars, output)
|
72
|
+
gvars = gvars.dump.filter_map do |gvar_name, entry|
|
73
|
+
if entry.type != Type.bot && !entry.rbs_declared
|
74
|
+
[gvar_name, entry]
|
75
|
+
end
|
76
|
+
end
|
72
77
|
# A signature for global variables is not supported in RBS
|
73
|
-
return if gvars.
|
78
|
+
return if gvars.empty?
|
74
79
|
|
75
80
|
output.puts "# Global variables"
|
76
|
-
gvars.
|
77
|
-
|
78
|
-
s = entry.rbs_declared ? "#" : ""
|
79
|
-
output.puts s + "#{ gvar_name }: #{ entry.type.screen_name(scratch) }"
|
81
|
+
gvars.each do |gvar_name, entry|
|
82
|
+
output.puts "#{ gvar_name }: #{ entry.type.screen_name(scratch) }"
|
80
83
|
end
|
81
84
|
output.puts
|
82
85
|
end
|
@@ -149,6 +152,7 @@ module TypeProf
|
|
149
152
|
source_locations[key] ||= ctx.iseq.source_location(0)
|
150
153
|
(methods[key] ||= []) << @scratch.show_method_signature(ctx)
|
151
154
|
when AliasMethodDef
|
155
|
+
next if mdef.def_ep && Config.check_dir_filter(mdef.def_ep.source_location) == :exclude
|
152
156
|
alias_name, orig_name = mid, mdef.orig_mid
|
153
157
|
if singleton
|
154
158
|
alias_name = "self.#{ alias_name }"
|
data/lib/typeprof/import.rb
CHANGED
@@ -373,25 +373,30 @@ module TypeProf
|
|
373
373
|
end
|
374
374
|
|
375
375
|
def conv_block(rbs_block)
|
376
|
-
|
376
|
+
blk = rbs_block.type
|
377
377
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
conv_type(type.type)
|
387
|
-
end
|
388
|
-
opt_tys = type.optional_positionals.map do |type|
|
389
|
-
conv_type(type.type)
|
390
|
-
end
|
378
|
+
lead_tys = blk.required_positionals.map {|type| conv_type(type.type) }
|
379
|
+
opt_tys = blk.optional_positionals.map {|type| conv_type(type.type) }
|
380
|
+
rest_ty = blk.rest_positionals
|
381
|
+
rest_ty = conv_type(rest_ty.type) if rest_ty
|
382
|
+
opt_kw_tys = blk.optional_keywords.to_h {|key, type| [key, conv_type(type.type)] }
|
383
|
+
req_kw_tys = blk.required_keywords.to_h {|key, type| [key, conv_type(type.type)] }
|
384
|
+
rest_kw_ty = blk.rest_keywords
|
385
|
+
rest_kw_ty = conv_type(rest_kw_ty.type) if rest_kw_ty
|
391
386
|
|
392
|
-
ret_ty = conv_type(
|
387
|
+
ret_ty = conv_type(blk.return_type)
|
393
388
|
|
394
|
-
|
389
|
+
{
|
390
|
+
required_block: rbs_block.required,
|
391
|
+
lead_tys: lead_tys,
|
392
|
+
opt_tys: opt_tys,
|
393
|
+
rest_ty: rest_ty,
|
394
|
+
req_kw_tys: req_kw_tys,
|
395
|
+
opt_kw_tys: opt_kw_tys,
|
396
|
+
rest_kw_ty: rest_kw_ty,
|
397
|
+
blk: blk,
|
398
|
+
ret_ty: ret_ty,
|
399
|
+
}
|
395
400
|
end
|
396
401
|
|
397
402
|
def conv_type(ty)
|
@@ -454,6 +459,8 @@ module TypeProf
|
|
454
459
|
end
|
455
460
|
when RBS::Types::Union
|
456
461
|
[:union, ty.types.map {|ty2| conv_type(ty2) }.compact]
|
462
|
+
when RBS::Types::Intersection
|
463
|
+
[:intersection, ty.types.map {|ty2| conv_type(ty2) }.compact]
|
457
464
|
when RBS::Types::Optional
|
458
465
|
[:optional, conv_type(ty.type)]
|
459
466
|
when RBS::Types::Interface
|
@@ -630,7 +637,13 @@ module TypeProf
|
|
630
637
|
kw_tys = []
|
631
638
|
req_kw_tys.each {|key, ty| kw_tys << [true, key, conv_type(ty)] }
|
632
639
|
opt_kw_tys.each {|key, ty| kw_tys << [false, key, conv_type(ty)] }
|
633
|
-
|
640
|
+
if rest_kw_ty
|
641
|
+
ty = conv_type(rest_kw_ty)
|
642
|
+
kw_rest_ty = Type.gen_hash do |h|
|
643
|
+
k_ty = Type::Instance.new(Type::Builtin[:sym])
|
644
|
+
h[k_ty] = ty
|
645
|
+
end
|
646
|
+
end
|
634
647
|
|
635
648
|
blks = conv_block(blk)
|
636
649
|
|
@@ -643,13 +656,36 @@ module TypeProf
|
|
643
656
|
|
644
657
|
def conv_block(blk)
|
645
658
|
return [Type.nil] unless blk
|
646
|
-
|
659
|
+
|
660
|
+
required_block = blk[:required_block]
|
661
|
+
lead_tys = blk[:lead_tys]
|
662
|
+
opt_tys = blk[:opt_tys]
|
663
|
+
rest_ty = blk[:rest_ty]
|
664
|
+
req_kw_tys = blk[:req_kw_tys]
|
665
|
+
opt_kw_tys = blk[:opt_kw_tys]
|
666
|
+
rest_kw_ty = blk[:rest_kw_ty]
|
667
|
+
ret_ty = blk[:ret_ty]
|
668
|
+
|
647
669
|
lead_tys = lead_tys.map {|ty| conv_type(ty) }
|
648
670
|
opt_tys = opt_tys.map {|ty| conv_type(ty) }
|
649
|
-
|
671
|
+
rest_ty = conv_type(rest_ty) if rest_ty
|
672
|
+
kw_tys = []
|
673
|
+
req_kw_tys.each {|key, ty| kw_tys << [true, key, conv_type(ty)] }
|
674
|
+
opt_kw_tys.each {|key, ty| kw_tys << [false, key, conv_type(ty)] }
|
675
|
+
if rest_kw_ty
|
676
|
+
ty = conv_type(rest_kw_ty)
|
677
|
+
kw_rest_ty = Type.gen_hash do |h|
|
678
|
+
k_ty = Type::Instance.new(Type::Builtin[:sym])
|
679
|
+
h[k_ty] = ty
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
msig = MethodSignature.new(lead_tys, opt_tys, rest_ty, [], kw_tys, kw_rest_ty, Type.nil)
|
684
|
+
|
650
685
|
ret_ty = conv_type(ret_ty)
|
686
|
+
|
651
687
|
ret = [Type::Proc.new(TypedBlock.new(msig, ret_ty), Type::Builtin[:proc])]
|
652
|
-
ret << Type.nil unless
|
688
|
+
ret << Type.nil unless required_block
|
653
689
|
ret
|
654
690
|
end
|
655
691
|
|
@@ -694,7 +730,10 @@ module TypeProf
|
|
694
730
|
end
|
695
731
|
when :union
|
696
732
|
tys = ty[1]
|
697
|
-
Type::Union.
|
733
|
+
Type::Union.create(Utils::Set[*tys.map {|ty2| conv_type(ty2) }], nil) # XXX: Array and Hash support
|
734
|
+
when :intersection
|
735
|
+
tys = ty[1]
|
736
|
+
conv_type(tys.first) # XXX: This is wrong! We need to support intersection type
|
698
737
|
when :var
|
699
738
|
Type::Var.new(ty[1])
|
700
739
|
when :proc
|
data/lib/typeprof/iseq.rb
CHANGED
@@ -3,75 +3,65 @@ module TypeProf
|
|
3
3
|
# https://github.com/ruby/ruby/pull/4468
|
4
4
|
CASE_WHEN_CHECKMATCH = RubyVM::InstructionSequence.compile("case 1; when Integer; end").to_a.last.any? {|insn,| insn == :checkmatch }
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
class << self
|
7
|
+
def compile(file)
|
8
|
+
compile_core(nil, file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile_str(str, path = nil)
|
12
|
+
compile_core(str, path)
|
13
|
+
end
|
14
|
+
|
15
|
+
private def compile_core(str, path)
|
16
|
+
opt = RubyVM::InstructionSequence.compile_option
|
17
|
+
opt[:inline_const_cache] = false
|
18
|
+
opt[:peephole_optimization] = false
|
19
|
+
opt[:specialized_instruction] = false
|
20
|
+
opt[:operands_unification] = false
|
21
|
+
opt[:coverage_enabled] = false
|
22
|
+
|
23
|
+
if str
|
24
|
+
iseq = RubyVM::InstructionSequence.compile(str, path, **opt)
|
25
|
+
else
|
26
|
+
iseq = RubyVM::InstructionSequence.compile_file(path, **opt)
|
27
|
+
end
|
28
|
+
|
29
|
+
return new(iseq.to_a)
|
30
|
+
end
|
16
31
|
end
|
17
32
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
opt[:operands_unification] = false
|
24
|
-
opt[:coverage_enabled] = false
|
25
|
-
new(RubyVM::InstructionSequence.compile(str, path, **opt).to_a)
|
33
|
+
Insn = Struct.new(:insn, :operands, :lineno)
|
34
|
+
class Insn
|
35
|
+
def check?(insn_cmp, operands_cmp = nil)
|
36
|
+
return insn == insn_cmp && (!operands_cmp || operands == operands_cmp)
|
37
|
+
end
|
26
38
|
end
|
27
39
|
|
28
|
-
|
40
|
+
ISEQ_FRESH_ID = [0]
|
29
41
|
|
30
42
|
def initialize(iseq)
|
31
|
-
@id =
|
32
|
-
FRESH_ID[0] += 1
|
43
|
+
@id = (ISEQ_FRESH_ID[0] += 1)
|
33
44
|
|
34
45
|
_magic, _major_version, _minor_version, _format_type, _misc,
|
35
46
|
@name, @path, @absolute_path, @start_lineno, @type,
|
36
47
|
@locals, @fargs_format, catch_table, insns = *iseq
|
37
48
|
|
38
|
-
|
39
|
-
when :method, :block
|
40
|
-
if @fargs_format[:opt]
|
41
|
-
label = @fargs_format[:opt].last
|
42
|
-
i = insns.index(label) + 1
|
43
|
-
else
|
44
|
-
i = insns.find_index {|insn| insn.is_a?(Array) }
|
45
|
-
end
|
46
|
-
# skip keyword initialization
|
47
|
-
while insns[i][0] == :checkkeyword
|
48
|
-
raise if insns[i + 1][0] != :branchif
|
49
|
-
label = insns[i + 1][1]
|
50
|
-
i = insns.index(label) + 1
|
51
|
-
end
|
52
|
-
insns[i, 0] = [[:_iseq_body_start]]
|
53
|
-
end
|
49
|
+
convert_insns(insns)
|
54
50
|
|
55
|
-
|
56
|
-
# because they requires to be virtually called.
|
57
|
-
# So, this preprocess adds "nop" to make a new insn for their return addresses
|
58
|
-
special_labels = {}
|
59
|
-
catch_table.map do |type, iseq, first, last, cont, stack_depth|
|
60
|
-
special_labels[cont] = true if type == :rescue || type == :ensure
|
61
|
-
end
|
51
|
+
add_body_start_marker(insns)
|
62
52
|
|
63
|
-
|
64
|
-
@linenos = []
|
53
|
+
add_exception_cont_marker(insns, catch_table)
|
65
54
|
|
66
|
-
labels =
|
55
|
+
labels = create_label_table(insns)
|
67
56
|
|
68
|
-
|
69
|
-
|
57
|
+
@insns = setup_insns(insns, labels)
|
58
|
+
|
59
|
+
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
|
70
60
|
|
71
61
|
@catch_table = []
|
72
62
|
catch_table.map do |type, iseq, first, last, cont, stack_depth|
|
73
63
|
iseq = iseq ? ISeq.new(iseq) : nil
|
74
|
-
target = labels[
|
64
|
+
target = labels[cont]
|
75
65
|
entry = [type, iseq, target, stack_depth]
|
76
66
|
labels[first].upto(labels[last]) do |i|
|
77
67
|
@catch_table[i] ||= []
|
@@ -79,43 +69,143 @@ module TypeProf
|
|
79
69
|
end
|
80
70
|
end
|
81
71
|
|
82
|
-
|
72
|
+
rename_insn_types
|
73
|
+
|
74
|
+
unify_instructions
|
75
|
+
end
|
76
|
+
|
77
|
+
def source_location(pc)
|
78
|
+
"#{ @path }:#{ @insns[pc].lineno }"
|
79
|
+
end
|
83
80
|
|
84
|
-
|
81
|
+
attr_reader :name, :path, :absolute_path, :start_lineno, :type, :locals, :fargs_format, :catch_table, :insns
|
82
|
+
attr_reader :id
|
83
|
+
|
84
|
+
def pretty_print(q)
|
85
|
+
q.text "ISeq["
|
86
|
+
q.group do
|
87
|
+
q.nest(1) do
|
88
|
+
q.breakable ""
|
89
|
+
q.text "@type= #{ @type }"
|
90
|
+
q.breakable ", "
|
91
|
+
q.text "@name= #{ @name }"
|
92
|
+
q.breakable ", "
|
93
|
+
q.text "@path= #{ @path }"
|
94
|
+
q.breakable ", "
|
95
|
+
q.text "@absolute_path= #{ @absolute_path }"
|
96
|
+
q.breakable ", "
|
97
|
+
q.text "@start_lineno= #{ @start_lineno }"
|
98
|
+
q.breakable ", "
|
99
|
+
q.text "@fargs_format= #{ @fargs_format.inspect }"
|
100
|
+
q.breakable ", "
|
101
|
+
q.text "@insns="
|
102
|
+
q.group(2) do
|
103
|
+
@insns.each_with_index do |(insn, *operands), i|
|
104
|
+
q.breakable
|
105
|
+
q.group(2, "#{ i }: #{ insn.to_s }", "") do
|
106
|
+
q.pp operands
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
q.breakable
|
112
|
+
end
|
113
|
+
q.text "]"
|
85
114
|
end
|
86
115
|
|
87
116
|
def <=>(other)
|
88
117
|
@id <=> other.id
|
89
118
|
end
|
90
119
|
|
91
|
-
|
120
|
+
# Remove lineno entry and convert instructions to Insn instances
|
121
|
+
def convert_insns(insns)
|
122
|
+
ninsns = []
|
123
|
+
lineno = 0
|
124
|
+
insns.each do |e|
|
125
|
+
case e
|
126
|
+
when Integer # lineno
|
127
|
+
lineno = e
|
128
|
+
when Symbol # label or trace
|
129
|
+
ninsns << e
|
130
|
+
when Array
|
131
|
+
insn, *operands = e
|
132
|
+
ninsns << Insn.new(insn, operands, lineno)
|
133
|
+
else
|
134
|
+
raise "unknown iseq entry: #{ e }"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
insns.replace(ninsns)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Insert a dummy instruction "_iseq_body_start"
|
141
|
+
def add_body_start_marker(insns)
|
142
|
+
case @type
|
143
|
+
when :method, :block
|
144
|
+
# skip initialization code of optional arguments
|
145
|
+
if @fargs_format[:opt]
|
146
|
+
label = @fargs_format[:opt].last
|
147
|
+
i = insns.index(label) + 1
|
148
|
+
else
|
149
|
+
i = insns.find_index {|insn| insn.is_a?(Insn) }
|
150
|
+
end
|
151
|
+
|
152
|
+
# skip initialization code of keyword arguments
|
153
|
+
while insns[i][0] == :checkkeyword
|
154
|
+
raise if insns[i + 1].insn != :branchif
|
155
|
+
label = insns[i + 1].operands[0]
|
156
|
+
i = insns.index(label) + 1
|
157
|
+
end
|
158
|
+
|
159
|
+
insns.insert(i, Insn.new(:_iseq_body_start, [], @start_lineno))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Insert "nop" instruction to continuation point of exception handlers
|
164
|
+
def add_exception_cont_marker(insns, catch_table)
|
165
|
+
# rescue/ensure clauses need to have a dedicated return addresses
|
166
|
+
# because they requires to be virtually called.
|
167
|
+
# So, this preprocess adds "nop" to make a new insn for their return addresses
|
168
|
+
exception_cont_labels = {}
|
169
|
+
catch_table.map! do |type, iseq, first, last, cont, stack_depth|
|
170
|
+
if type == :rescue || type == :ensure
|
171
|
+
exception_cont_labels[cont] = true
|
172
|
+
cont = :"#{ cont }_exception_cont"
|
173
|
+
end
|
174
|
+
[type, iseq, first, last, cont, stack_depth]
|
175
|
+
end
|
176
|
+
|
92
177
|
i = 0
|
178
|
+
while i < insns.size
|
179
|
+
e = insns[i]
|
180
|
+
if exception_cont_labels[e]
|
181
|
+
insns.insert(i, :"#{ e }_exception_cont", Insn.new(:nop, []))
|
182
|
+
i += 2
|
183
|
+
end
|
184
|
+
i += 1
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def create_label_table(insns)
|
189
|
+
pc = 0
|
93
190
|
labels = {}
|
94
|
-
ninsns = []
|
95
191
|
insns.each do |e|
|
96
|
-
if e.is_a?(Symbol)
|
97
|
-
|
98
|
-
labels[:"#{ e }_special"] = i
|
99
|
-
ninsns << [:nop]
|
100
|
-
i += 1
|
101
|
-
end
|
102
|
-
labels[e] = i
|
192
|
+
if e.is_a?(Symbol)
|
193
|
+
labels[e] = pc
|
103
194
|
else
|
104
|
-
|
105
|
-
ninsns << e
|
195
|
+
pc += 1
|
106
196
|
end
|
107
197
|
end
|
198
|
+
labels
|
199
|
+
end
|
108
200
|
|
109
|
-
|
110
|
-
ninsns
|
201
|
+
def setup_insns(insns, labels)
|
202
|
+
ninsns = []
|
203
|
+
insns.each do |e|
|
111
204
|
case e
|
112
|
-
when Integer # lineno
|
113
|
-
lineno = e
|
114
205
|
when Symbol # label or trace
|
115
206
|
nil
|
116
|
-
when
|
117
|
-
insn
|
118
|
-
operands = (INSN_TABLE[insn] || []).zip(operands).map do |type, operand|
|
207
|
+
when Insn
|
208
|
+
operands = (INSN_TABLE[e.insn] || []).zip(e.operands).map do |type, operand|
|
119
209
|
case type
|
120
210
|
when "ISEQ"
|
121
211
|
operand && ISeq.new(operand)
|
@@ -131,81 +221,64 @@ module TypeProf
|
|
131
221
|
end
|
132
222
|
end
|
133
223
|
|
134
|
-
|
135
|
-
@linenos << lineno
|
224
|
+
ninsns << Insn.new(e.insn, operands, e.lineno)
|
136
225
|
else
|
137
226
|
raise "unknown iseq entry: #{ e }"
|
138
227
|
end
|
139
228
|
end
|
140
|
-
|
141
|
-
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
|
142
|
-
|
143
|
-
labels
|
229
|
+
ninsns
|
144
230
|
end
|
145
231
|
|
146
|
-
def
|
147
|
-
@insns.
|
148
|
-
insn
|
149
|
-
case insn
|
232
|
+
def rename_insn_types
|
233
|
+
@insns.each do |insn|
|
234
|
+
case insn.insn
|
150
235
|
when :branchif
|
151
|
-
|
236
|
+
insn.insn, insn.operands = :branch, [:if] + insn.operands
|
152
237
|
when :branchunless
|
153
|
-
|
238
|
+
insn.insn, insn.operands = :branch, [:unless] + insn.operands
|
154
239
|
when :branchnil
|
155
|
-
|
240
|
+
insn.insn, insn.operands = :branch, [:nil] + insn.operands
|
241
|
+
when :getblockparam, :getblockparamproxy
|
242
|
+
insn.insn = :getlocal
|
156
243
|
end
|
157
244
|
end
|
158
245
|
end
|
159
246
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
q.group(2) do
|
186
|
-
@insns.each_with_index do |(insn, *operands), i|
|
187
|
-
q.breakable
|
188
|
-
q.group(2, "#{ i }: #{ insn.to_s }", "") do
|
189
|
-
q.pp operands
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
q.breakable
|
195
|
-
end
|
196
|
-
q.text "]"
|
197
|
-
end
|
247
|
+
# Unify some instructions for flow-sensitive analysis
|
248
|
+
def unify_instructions
|
249
|
+
# This method rewrites instructions to enable flow-sensitive analysis.
|
250
|
+
#
|
251
|
+
# Consider `if x; ...; else; ... end`.
|
252
|
+
# When the variable `x` is of type "Integer | nil",
|
253
|
+
# we want to make sure that `x` is "Integer" in then clause.
|
254
|
+
# So, we need to split the environment to two ones:
|
255
|
+
# one is that `x` is of type "Integer", and the other is that
|
256
|
+
# `x` is type "nil".
|
257
|
+
#
|
258
|
+
# However, `if x` is compiled to "getlocal; branch".
|
259
|
+
# TypeProf evaluates them as follows:
|
260
|
+
#
|
261
|
+
# * "getlocal" pushes the value of `x` to the stack, amd
|
262
|
+
# * "branch" checks the value on the top of the stack
|
263
|
+
#
|
264
|
+
# TypeProf does not keep where the value comes from, so
|
265
|
+
# it is difficult to split the environment when evaluating "branch".
|
266
|
+
#
|
267
|
+
# This method rewrites "getlocal; branch" to "nop; getlocal_branch".
|
268
|
+
# The two instructions are unified to "getlocal_branch" instruction,
|
269
|
+
# so TypeProf can split the environment.
|
270
|
+
#
|
271
|
+
# This is a very fragile appoach because it highly depends on the compiler of Ruby.
|
198
272
|
|
199
|
-
def analyze_stack
|
200
273
|
# gather branch targets
|
201
274
|
# TODO: catch_table should be also considered
|
202
275
|
branch_targets = {}
|
203
|
-
@insns.each do |insn
|
204
|
-
case insn
|
276
|
+
@insns.each do |insn|
|
277
|
+
case insn.insn
|
205
278
|
when :branch
|
206
|
-
branch_targets[operands[1]] = true
|
279
|
+
branch_targets[insn.operands[1]] = true
|
207
280
|
when :jump
|
208
|
-
branch_targets[operands[0]] = true
|
281
|
+
branch_targets[insn.operands[0]] = true
|
209
282
|
end
|
210
283
|
end
|
211
284
|
|
@@ -215,25 +288,27 @@ module TypeProf
|
|
215
288
|
case_branch_list = []
|
216
289
|
if CASE_WHEN_CHECKMATCH
|
217
290
|
(@insns.size - 1).times do |i|
|
218
|
-
|
219
|
-
next unless
|
291
|
+
insn = @insns[i]
|
292
|
+
next unless insn.insn == :getlocal && insn.operands[1] == 0
|
293
|
+
getlocal_operands = insn.operands
|
220
294
|
nops = [i]
|
221
295
|
new_insns = []
|
222
296
|
j = i + 1
|
223
297
|
while true
|
224
|
-
case @insns[j]
|
225
|
-
when
|
226
|
-
break unless @insns[j + 1]
|
227
|
-
break unless @insns[j + 2]
|
228
|
-
break unless @insns[j + 3]
|
229
|
-
break unless @insns[j + 4]
|
230
|
-
break unless @insns[j + 5]
|
231
|
-
target_pc = @insns[j + 5][1]
|
232
|
-
break unless @insns[target_pc]
|
298
|
+
case @insns[j].insn
|
299
|
+
when :dup
|
300
|
+
break unless @insns[j + 1].check?(:putnil, [])
|
301
|
+
break unless @insns[j + 2].check?(:putobject, [true])
|
302
|
+
break unless @insns[j + 3].check?(:getconstant) # TODO: support A::B::C
|
303
|
+
break unless @insns[j + 4].check?(:checkmatch, [2])
|
304
|
+
break unless @insns[j + 5].check?(:branch)
|
305
|
+
target_pc = @insns[j + 5].operands[1]
|
306
|
+
break unless @insns[target_pc].check?(:pop, [])
|
233
307
|
nops << j << (j + 4) << target_pc
|
234
|
-
|
308
|
+
branch_operands = @insns[j + 5][1]
|
309
|
+
new_insns << [j + 5, Insn.new(:getlocal_checkmatch_branch, [getlocal_operands, branch_operands])]
|
235
310
|
j += 6
|
236
|
-
when
|
311
|
+
when :pop
|
237
312
|
nops << j
|
238
313
|
case_branch_list << [nops, new_insns]
|
239
314
|
break
|
@@ -244,25 +319,28 @@ module TypeProf
|
|
244
319
|
end
|
245
320
|
else
|
246
321
|
(@insns.size - 1).times do |i|
|
247
|
-
|
248
|
-
next unless
|
322
|
+
insn = @insns[i]
|
323
|
+
next unless insn.insn == :getlocal && insn.operands[1] == 0
|
324
|
+
getlocal_operands = insn.operands
|
249
325
|
nops = []
|
250
326
|
new_insns = []
|
251
327
|
j = i + 1
|
252
328
|
while true
|
253
|
-
|
254
|
-
|
255
|
-
break unless @insns[j + 1]
|
256
|
-
break unless @insns[j + 2]
|
257
|
-
break unless @insns[j + 3]
|
258
|
-
break unless @insns[j + 4]
|
259
|
-
break unless @insns[j + 5]
|
260
|
-
target_pc = @insns[j + 5][1]
|
261
|
-
break unless @insns[target_pc]
|
329
|
+
insn = @insns[j]
|
330
|
+
if insn.check?(:putnil, [])
|
331
|
+
break unless @insns[j + 1].check?(:putobject, [true])
|
332
|
+
break unless @insns[j + 2].check?(:getconstant) # TODO: support A::B::C
|
333
|
+
break unless @insns[j + 3].check?(:topn, [1])
|
334
|
+
break unless @insns[j + 4].check?(:send, [{:mid=>:===, :flag=>20, :orig_argc=>1}, nil])
|
335
|
+
break unless @insns[j + 5].check?(:branch)
|
336
|
+
target_pc = @insns[j + 5].operands[1]
|
337
|
+
break unless @insns[target_pc].check?(:pop, [])
|
262
338
|
nops << (j + 4) #<< target_pc
|
263
|
-
|
339
|
+
send_operands = @insns[j + 4][1]
|
340
|
+
branch_operands = @insns[j + 5][1]
|
341
|
+
new_insns << [j + 5, Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])]
|
264
342
|
j += 6
|
265
|
-
|
343
|
+
elsif insn.check?(:pop, [])
|
266
344
|
#nops << j
|
267
345
|
case_branch_list << [nops, new_insns]
|
268
346
|
break
|
@@ -273,15 +351,15 @@ module TypeProf
|
|
273
351
|
end
|
274
352
|
end
|
275
353
|
case_branch_list.each do |nops, new_insns|
|
276
|
-
nops.each {|i| @insns[i] =
|
354
|
+
nops.each {|i| @insns[i] = Insn.new(:nop, []) }
|
277
355
|
new_insns.each {|i, insn| @insns[i] = insn }
|
278
356
|
end
|
279
357
|
|
280
358
|
# find a pattern: getlocal(recv), ..., send (is_a?, respond_to?), branch
|
281
359
|
recv_getlocal_send_branch_list = []
|
282
360
|
(@insns.size - 1).times do |i|
|
283
|
-
insn
|
284
|
-
if insn == :getlocal && operands[1] == 0
|
361
|
+
insn = @insns[i]
|
362
|
+
if insn.insn == :getlocal && insn.operands[1] == 0
|
285
363
|
j = i + 1
|
286
364
|
sp = 1
|
287
365
|
while @insns[j]
|
@@ -297,94 +375,118 @@ module TypeProf
|
|
297
375
|
end
|
298
376
|
recv_getlocal_send_branch_list.each do |i, j|
|
299
377
|
next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
@insns[j] =
|
304
|
-
@insns[j + 1] =
|
378
|
+
getlocal_operands = @insns[i].operands
|
379
|
+
send_operands = @insns[j].operands
|
380
|
+
branch_operands = @insns[j + 1].operands
|
381
|
+
@insns[j] = Insn.new(:nop, [])
|
382
|
+
@insns[j + 1] = Insn.new(:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
|
305
383
|
end
|
306
384
|
|
307
385
|
# find a pattern: getlocal, send (===), branch
|
308
386
|
arg_getlocal_send_branch_list = []
|
309
387
|
(@insns.size - 1).times do |i|
|
310
|
-
insn1
|
311
|
-
next unless insn1 == :getlocal &&
|
312
|
-
insn2
|
313
|
-
next unless insn2 == :send
|
314
|
-
|
315
|
-
next unless
|
316
|
-
insn3
|
317
|
-
next unless insn3 == :branch
|
388
|
+
insn1 = @insns[i]
|
389
|
+
next unless insn1.insn == :getlocal && insn1.operands[1] == 0
|
390
|
+
insn2 = @insns[i + 1]
|
391
|
+
next unless insn2.insn == :send
|
392
|
+
send_operands = insn2.operands[0]
|
393
|
+
next unless send_operands[:flag] == 16 && send_operands[:orig_argc] == 1
|
394
|
+
insn3 = @insns[i + 2]
|
395
|
+
next unless insn3.insn == :branch
|
318
396
|
arg_getlocal_send_branch_list << i
|
319
397
|
end
|
320
398
|
arg_getlocal_send_branch_list.each do |i|
|
321
399
|
next if (i .. i + 2).any? {|i| branch_targets[i] }
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
@insns[i + 1] =
|
326
|
-
@insns[i + 2] =
|
400
|
+
getlocal_operands = @insns[i].operands
|
401
|
+
send_operands = @insns[i + 1].operands
|
402
|
+
branch_operands = @insns[i + 2].operands
|
403
|
+
@insns[i + 1] = Insn.new(:nop, [])
|
404
|
+
@insns[i + 2] = Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
|
327
405
|
end
|
328
406
|
|
329
407
|
# find a pattern: send (block_given?), branch
|
330
408
|
send_branch_list = []
|
331
409
|
(@insns.size - 1).times do |i|
|
332
|
-
insn
|
333
|
-
if insn == :send
|
334
|
-
insn
|
335
|
-
if insn == :branch
|
410
|
+
insn = @insns[i]
|
411
|
+
if insn.insn == :send
|
412
|
+
insn = @insns[i + 1]
|
413
|
+
if insn.insn == :branch
|
336
414
|
send_branch_list << i
|
337
415
|
end
|
338
416
|
end
|
339
417
|
end
|
340
418
|
send_branch_list.each do |i|
|
341
419
|
next if branch_targets[i + 1]
|
342
|
-
|
343
|
-
|
344
|
-
@insns[i] =
|
345
|
-
@insns[i + 1] =
|
420
|
+
send_operands = @insns[i].operands
|
421
|
+
branch_operands = @insns[i + 1].operands
|
422
|
+
@insns[i] = Insn.new(:nop, [])
|
423
|
+
@insns[i + 1] = Insn.new(:send_branch, [send_operands, branch_operands])
|
346
424
|
end
|
347
425
|
|
348
426
|
# find a pattern: getlocal, dup, branch
|
349
427
|
(@insns.size - 2).times do |i|
|
350
428
|
next if branch_targets[i + 1] || branch_targets[i + 2]
|
351
|
-
insn0
|
352
|
-
insn1
|
353
|
-
insn2
|
354
|
-
if insn0 == :getlocal && insn1 == :dup && insn2 == :branch &&
|
355
|
-
|
356
|
-
|
357
|
-
|
429
|
+
insn0 = @insns[i]
|
430
|
+
insn1 = @insns[i + 1]
|
431
|
+
insn2 = @insns[i + 2]
|
432
|
+
if insn0.insn == :getlocal && insn1.insn == :dup && insn2.insn == :branch && insn0.operands[1] == 0
|
433
|
+
getlocal_operands = insn0.operands
|
434
|
+
dup_operands = insn1.operands
|
435
|
+
branch_operands = insn2.operands
|
436
|
+
@insns[i ] = Insn.new(:nop, [])
|
437
|
+
@insns[i + 1] = Insn.new(:nop, [])
|
438
|
+
@insns[i + 2] = Insn.new(:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands])
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# find a pattern: dup, setlocal, branch
|
443
|
+
(@insns.size - 2).times do |i|
|
444
|
+
next if branch_targets[i + 1] || branch_targets[i + 2]
|
445
|
+
insn0 = @insns[i]
|
446
|
+
insn1 = @insns[i + 1]
|
447
|
+
insn2 = @insns[i + 2]
|
448
|
+
if insn0.insn == :dup && insn1.insn == :setlocal && insn2.insn == :branch && insn1.operands[1] == 0
|
449
|
+
dup_operands = insn0.operands
|
450
|
+
setlocal_operands = insn1.operands
|
451
|
+
branch_operands = insn2.operands
|
452
|
+
@insns[i ] = Insn.new(:nop, [])
|
453
|
+
@insns[i + 1] = Insn.new(:nop, [])
|
454
|
+
@insns[i + 2] = Insn.new(:dup_setlocal_branch, [dup_operands, setlocal_operands, branch_operands])
|
358
455
|
end
|
359
456
|
end
|
360
457
|
|
361
458
|
# find a pattern: dup, branch
|
362
459
|
(@insns.size - 1).times do |i|
|
363
460
|
next if branch_targets[i + 1]
|
364
|
-
insn0
|
365
|
-
insn1
|
366
|
-
if insn0 == :dup && insn1 == :branch
|
367
|
-
|
368
|
-
|
461
|
+
insn0 = @insns[i]
|
462
|
+
insn1 = @insns[i + 1]
|
463
|
+
if insn0.insn == :dup && insn1.insn == :branch
|
464
|
+
dup_operands = insn0.operands
|
465
|
+
branch_operands = insn1.operands
|
466
|
+
@insns[i ] = Insn.new(:nop, [])
|
467
|
+
@insns[i + 1] = Insn.new(:dup_branch, [dup_operands, branch_operands])
|
369
468
|
end
|
370
469
|
end
|
371
470
|
|
372
471
|
# find a pattern: getlocal, branch
|
373
472
|
(@insns.size - 1).times do |i|
|
374
473
|
next if branch_targets[i + 1]
|
375
|
-
insn0
|
376
|
-
insn1
|
377
|
-
if
|
378
|
-
|
379
|
-
|
474
|
+
insn0 = @insns[i]
|
475
|
+
insn1 = @insns[i + 1]
|
476
|
+
if insn0.insn == :getlocal && insn0.operands[1] == 0 && insn1.insn == :branch
|
477
|
+
getlocal_operands = insn0.operands
|
478
|
+
branch_operands = insn1.operands
|
479
|
+
@insns[i ] = Insn.new(:nop, [])
|
480
|
+
@insns[i + 1] = Insn.new(:getlocal_branch, [getlocal_operands, branch_operands])
|
380
481
|
end
|
381
482
|
end
|
382
483
|
end
|
383
484
|
|
384
485
|
def check_send_branch(sp, j)
|
385
|
-
insn
|
486
|
+
insn = @insns[j]
|
487
|
+
operands = insn.operands
|
386
488
|
|
387
|
-
case insn
|
489
|
+
case insn.insn
|
388
490
|
when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
|
389
491
|
:putself
|
390
492
|
sp += 1
|
@@ -422,7 +524,7 @@ module TypeProf
|
|
422
524
|
argc += 1 # receiver
|
423
525
|
argc += kw_arg.size if kw_arg
|
424
526
|
sp -= argc
|
425
|
-
return :match if insn == :send && sp == 0 && @insns[j + 1]
|
527
|
+
return :match if insn.insn == :send && sp == 0 && @insns[j + 1].insn == :branch
|
426
528
|
sp += 1
|
427
529
|
when :arg_getlocal_send_branch
|
428
530
|
return # not implemented
|