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