BasicObject
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 103
def initialize(iseq, file_info)
file_info.created_iseqs << self
@id = (ISEQ_FRESH_ID[0] += 1)
_magic, _major_version, _minor_version, _format_type, misc,
@name, @path, @absolute_path, @start_lineno, @type,
@locals, @fargs_format, catch_table, insns = *iseq
fl, fc, ll, lc = misc[:code_location]
@iseq_code_range = CodeRange.new(CodeLocation.new(fl, fc), CodeLocation.new(ll, lc))
convert_insns(insns, misc[:node_ids] || [], file_info)
add_body_start_marker(insns)
add_exception_cont_marker(insns, catch_table)
labels = create_label_table(insns)
@insns = setup_insns(insns, labels, file_info)
@fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]
@catch_table = []
catch_table.map do |type, iseq, first, last, cont, stack_depth|
iseq = iseq ? ISeq.new(iseq, file_info) : nil
target = labels[cont]
entry = [type, iseq, target, stack_depth]
labels[first].upto(labels[last]) do |i|
@catch_table[i] ||= []
@catch_table[i] << entry
end
end
def_node_id = misc[:def_node_id]
if def_node_id && file_info.node_id2node[def_node_id] && (@type == :method || @type == :block)
def_node = file_info.node_id2node[def_node_id]
method_name_token_range = extract_method_name_token_range(def_node)
if method_name_token_range
@callers = Utils::MutableSet.new
file_info.caller_table[method_name_token_range] = @callers
end
end
rename_insn_types
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 243
def <=>(other)
@id <=> other.id
end
Insert a dummy instruction “_iseq_body_start”
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 286
def add_body_start_marker(insns)
case @type
when :method, :block
# skip initialization code of optional arguments
if @fargs_format[:opt]
label = @fargs_format[:opt].last
i = insns.index(label) + 1
else
i = insns.find_index {|insn| insn.is_a?(Insn) }
end
# skip initialization code of keyword arguments
while insns[i][0] == :checkkeyword
raise if insns[i + 1].insn != :branchif
label = insns[i + 1].operands[0]
i = insns.index(label) + 1
end
insns.insert(i, Insn.new(:_iseq_body_start, [], @start_lineno, nil, nil))
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 193
def add_called_iseq(pc, callee_iseq)
if callee_iseq && @insns[pc].definitions
@insns[pc].definitions << [callee_iseq.path, callee_iseq.iseq_code_range]
end
if callee_iseq.callers
callee_iseq.callers << [@path, @insns[pc].code_range]
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 202
def add_def_loc(pc, detailed_loc)
if detailed_loc && @insns[pc].definitions
@insns[pc].definitions << detailed_loc
end
end
Insert “nop” instruction to continuation point of exception handlers
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 309
def add_exception_cont_marker(insns, catch_table)
# rescue/ensure clauses need to have a dedicated return addresses
# because they requires to be virtually called.
# So, this preprocess adds "nop" to make a new insn for their return addresses
exception_cont_labels = {}
catch_table.map! do |type, iseq, first, last, cont, stack_depth|
if type == :rescue || type == :ensure
exception_cont_labels[cont] = true
cont = :"#{ cont }_exception_cont"
end
[type, iseq, first, last, cont, stack_depth]
end
i = 0
while i < insns.size
e = insns[i]
if exception_cont_labels[e]
insns.insert(i, :"#{ e }_exception_cont", Insn.new(:nop, []))
i += 2
end
i += 1
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 692
def check_send_branch(sp, j)
insn = @insns[j]
operands = insn.operands
case insn.insn
when :putspecialobject, :putnil, :putobject, :duparray, :putstring,
:putself
sp += 1
when :newarray, :newarraykwsplat, :newhash, :concatstrings
len, = operands
sp =- len
return nil if sp <= 0
sp += 1
when :newhashfromarray
raise NotImplementedError, "newhashfromarray"
when :newrange, :tostring, :objtostring, :anytostring
sp -= 2
return nil if sp <= 0
sp += 1
when :freezestring
# XXX: should leverage this information?
when :toregexp
_regexp_opt, len = operands
sp -= len
return nil if sp <= 0
sp += 1
when :intern
sp -= 1
return nil if sp <= 0
sp += 1
when :definemethod, :definesmethod
when :defineclass
sp -= 2
when :send, :invokesuper
opt, = operands
_flags = opt[:flag]
_mid = opt[:mid]
kw_arg = opt[:kw_arg]
argc = opt[:orig_argc]
argc += 1 # receiver
argc += kw_arg.size if kw_arg
sp -= argc
return :match if insn.insn == :send && sp == 0 && @insns[j + 1].insn == :branch
sp += 1
when :arg_getlocal_send_branch
return # not implemented
when :invokeblock
opt, = operands
sp -= opt[:orig_argc]
return nil if sp <= 0
sp += 1
when :invokebuiltin
raise NotImplementedError
when :leave, :throw
return
when :once
return # not implemented
when :branch, :jump
return # not implemented
when :setinstancevariable, :setclassvariable, :setglobal
sp -= 1
when :setlocal, :setblockparam
return # conservative
when :getinstancevariable, :getclassvariable, :getglobal,
:getlocal, :getblockparam, :getblockparamproxy, :getlocal_checkmatch_branch
sp += 1
when :getconstant
sp -= 2
return nil if sp <= 0
sp += 1
when :setconstant
sp -= 2
when :getspecial
sp += 1
when :setspecial
# flip-flop
raise NotImplementedError, "setspecial"
when :dup
sp += 1
when :duphash
sp += 1
when :dupn
n, = operands
sp += n
when :pop
sp -= 1
when :swap
sp -= 2
return nil if sp <= 0
sp += 2
when :reverse
n, = operands
sp -= n
return nil if sp <= 0
sp += n
when :defined
sp -= 1
return nil if sp <= 0
sp += 1
when :checkmatch
sp -= 2
return nil if sp <= 0
sp += 1
when :checkkeyword
sp += 1
when :adjuststack
n, = operands
sp -= n
when :nop
when :setn
return nil # not implemented
when :topn
sp += 1
when :splatarray
sp -= 1
return nil if sp <= 0
sp += 1
when :expandarray
num, flag = operands
splat = flag & 1 == 1
sp -= 1
return nil if sp <= 0
sp += num + (splat ? 1 : 0)
when :concatarray
sp -= 2
return nil if sp <= 0
sp += 1
when :checktype
sp -= 1
return nil if sp <= 0
sp += 1
else
raise "Unknown insn: #{ insn }"
end
return nil if sp <= 0
sp
end
Collect local variable use and definition info recursively
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 392
def collect_local_variable_info(file_info, absolute_level = 0, parent_variable_tables = {})
# e.g.
# variable_tables[abs_level][idx] = [[path, code_range]]
current_variables = []
variable_tables = parent_variable_tables.merge({
absolute_level => current_variables
})
dummy_def_range = CodeRange.new(
CodeLocation.new(@start_lineno, 0),
CodeLocation.new(@start_lineno, 1),
)
# Fill tail elements with parameters
(@fargs_format[:lead_num] || 0).times do |offset|
current_variables[VM_ENV_DATA_SIZE + @locals.length - offset - 1] ||= Utils::MutableSet.new
current_variables[VM_ENV_DATA_SIZE + @locals.length - offset - 1] << [@path, dummy_def_range]
end
@insns.each do |insn|
next unless insn.insn == :getlocal || insn.insn == :setlocal
idx = insn.operands[0]
# note: level is relative value to the current level
level = insn.operands[1]
target_abs_level = absolute_level - level
variable_tables[target_abs_level] ||= {}
variable_tables[target_abs_level][idx] ||= Utils::MutableSet.new
case insn.insn
when :setlocal
variable_tables[target_abs_level][idx] << [path, insn.code_range]
when :getlocal
file_info.definition_table[insn.code_range] = variable_tables[target_abs_level][idx]
end
end
@insns.each do |insn|
insn.operands.each do |operand|
next unless operand.is_a?(ISeq)
operand.collect_local_variable_info(
file_info, absolute_level + 1,
variable_tables
)
end
end
end
Remove lineno entry and convert instructions to Insn instances
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 248
def convert_insns(insns, node_ids, file_info)
ninsns = []
lineno = 0
insns.each do |e|
case e
when Integer # lineno
lineno = e
when Symbol # label or trace
ninsns << e
when Array
insn, *operands = e
node_id = node_ids.shift
node = file_info.node_id2node[node_id]
if node
code_range = ISeq.code_range_from_node(node)
case insn
when :send, :invokesuper
opt, blk_iseq = operands
opt[:node_id] = node_id
if blk_iseq
misc = blk_iseq[4] # iseq's "misc" field
misc[:def_node_id] = node_id
end
when :definemethod, :definesmethod
iseq = operands[1]
misc = iseq[4] # iseq's "misc" field
misc[:def_node_id] = node_id
end
end
ninsns << Insn.new(insn, operands, lineno, code_range, nil)
else
raise "unknown iseq entry: #{ e }"
end
end
insns.replace(ninsns)
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 333
def create_label_table(insns)
pc = 0
labels = {}
insns.each do |e|
if e.is_a?(Symbol)
labels[e] = pc
else
pc += 1
end
end
labels
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 184
def detailed_source_location(pc)
code_range = @insns[pc].code_range
if code_range
[@path, code_range]
else
[@path]
end
end
# File typeprof-0.21.2/lib/typeprof/analyzer.rb, line 2452
def do_define_iseq_method(ep, env, mid, iseq, outer_ep)
cref = ep.ctx.cref
if cref.klass.is_a?(Type::Class)
typed_mdef = check_typed_method(cref.klass, mid, ep.ctx.cref.singleton)
recv = cref.klass
recv = Type::Instance.new(recv) unless ep.ctx.cref.singleton
if typed_mdef
mdef = ISeqMethodDef.new(iseq, cref, outer_ep, env.static_env.pub_meth)
typed_mdef.each do |typed_mdef|
typed_mdef.do_match_iseq_mdef(mdef, recv, mid, env, ep, self)
end
else
if ep.ctx.cref.singleton
meth = add_singleton_iseq_method(cref.klass, mid, iseq, cref, outer_ep, true)
else
meth = add_iseq_method(cref.klass, mid, iseq, cref, outer_ep, env.static_env.pub_meth)
if env.static_env.mod_func
add_singleton_iseq_method(cref.klass, mid, iseq, cref, outer_ep, true)
end
end
end
pend_method_execution(iseq, meth, recv, mid, ep.ctx.cref, outer_ep)
else
# XXX: what to do?
end
end
# File typeprof-0.21.2/lib/typeprof/analyzer.rb, line 2441
def do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, replace_cref: nil, &ctn)
blk.each_child do |blk|
if blk.is_a?(Type::Proc)
blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, replace_cref: replace_cref, &ctn)
else
warn(ep, "non-proc is passed as a block") if blk != Type.any
ctn[Type.any, ep, env]
end
end
end
# File typeprof-0.21.2/lib/typeprof/analyzer.rb, line 2383
def do_send(recvs, mid, aargs, ep, env, &ctn)
if mid == :__typeprof_lsp_completion
names = {}
recvs.each_child do |recv|
case recv
when Type::Void, Type::Any
else
klass, singleton, include_subclasses = recv.method_dispatch_info
names.merge!(get_all_methods(klass, singleton, include_subclasses)) if klass
end
end
@lsp_completion = names
return ctn[Type.any, ep, env]
end
recvs.each_child do |recv|
case recv
when Type::Void
error(ep, "void's method is called: #{ globalize_type(recv, env, ep).screen_name(self) }##{ mid }")
ctn[Type.any, ep, env]
when Type::Any
ctn[Type.any, ep, env]
else
klass, singleton, include_subclasses = recv.method_dispatch_info
meths = get_method(klass, singleton, include_subclasses, mid) if klass
if meths
path, loc = Config.current.options[:signature_help_loc]
if path && path == ep.ctx.iseq.path && mid != :inherited # XXX: too ad-hoc!!!
path, code_range = ep&.detailed_source_location
if path && code_range&.contain_loc?(loc)
@lsp_signature_help[code_range] = {
recv: recv,
mid: mid,
singleton: singleton,
mdefs: meths,
node_id: aargs.node_id,
}
end
end
meths.each do |meth|
meth.do_send(recv, mid, aargs, ep, env, self, &ctn)
end
else
meths = get_method(klass, singleton, include_subclasses, :method_missing) if klass
if meths
aargs = aargs.for_method_missing(Type::Symbol.new(mid, Type::Instance.new(Type::Builtin[:sym])))
meths.each do |meth|
meth.do_send(recv, :method_missing, aargs, ep, env, self, &ctn)
end
else
error(ep, "undefined method: #{ globalize_type(recv, env, ep).screen_name(self) }##{ mid }")
ctn[Type.any, ep, env]
end
end
end
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 151
def extract_method_name_token_range(node)
case @type
when :method
regex = if node.type == :DEFS
/^def\s+(?:\w+)\s*\.\s*(\w+)/
else
/^def\s+(\w+)/
end
return nil unless node.source =~ regex
zero_loc = CodeLocation.new(1, 0)
name_start = $~.begin(1)
name_length = $~.end(1) - name_start
name_head_loc = zero_loc.advance_cursor(name_start, node.source)
name_tail_loc = name_head_loc.advance_cursor(name_length, node.source)
return CodeRange.new(
CodeLocation.new(
node.first_lineno + (name_head_loc.lineno - 1),
name_head_loc.lineno == 1 ? node.first_column + name_head_loc.column : name_head_loc.column
),
CodeLocation.new(
node.first_lineno + (name_tail_loc.lineno - 1),
name_tail_loc.lineno == 1 ? node.first_column + name_tail_loc.column : name_tail_loc.column
),
)
when :block
return ISeq.code_range_from_node(node)
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 211
def pretty_print(q)
q.text "ISeq["
q.group do
q.nest(1) do
q.breakable ""
q.text "@type= #{ @type }"
q.breakable ", "
q.text "@name= #{ @name }"
q.breakable ", "
q.text "@path= #{ @path }"
q.breakable ", "
q.text "@absolute_path= #{ @absolute_path }"
q.breakable ", "
q.text "@start_lineno= #{ @start_lineno }"
q.breakable ", "
q.text "@fargs_format= #{ @fargs_format.inspect }"
q.breakable ", "
q.text "@insns="
q.group(2) do
@insns.each_with_index do |(insn, *operands), i|
q.breakable
q.group(2, "#{ i }: #{ insn.to_s }", "") do
q.pp operands
end
end
end
end
q.breakable
end
q.text "]"
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 439
def rename_insn_types
@insns.each do |insn|
case insn.insn
when :branchif
insn.insn, insn.operands = :branch, [:if] + insn.operands
when :branchunless
insn.insn, insn.operands = :branch, [:unless] + insn.operands
when :branchnil
insn.insn, insn.operands = :branch, [:nil] + insn.operands
when :getblockparam, :getblockparamproxy
insn.insn = :getlocal
end
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 346
def setup_insns(insns, labels, file_info)
ninsns = []
insns.each do |e|
case e
when Symbol # label or trace
nil
when Insn
operands = (INSN_TABLE[e.insn] || []).zip(e.operands).map do |type, operand|
case type
when "ISEQ"
operand && ISeq.new(operand, file_info)
when "lindex_t", "rb_num_t", "VALUE", "ID", "GENTRY", "CALL_DATA"
operand
when "OFFSET"
labels[operand] || raise("unknown label: #{ operand }")
when "IVC", "ISE"
raise unless operand.is_a?(Integer)
:_cache_operand
else
raise "unknown operand type: #{ type }"
end
end
if e.code_range && should_collect_defs(e.insn)
definition = Utils::MutableSet.new
file_info.definition_table[e.code_range] = definition
end
ninsns << Insn.new(e.insn, operands, e.lineno, e.code_range, definition)
else
raise "unknown iseq entry: #{ e }"
end
end
ninsns
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 382
def should_collect_defs(insn_kind)
case insn_kind
when :send, :getinstancevariable, :getconstant
return true
else
return false
end
end
# File typeprof-0.21.2/lib/typeprof/analyzer.rb, line 2480
def show_block_signature(blks)
bsig = nil
ret_ty = Type.bot
blks.each do |blk|
blk.each_child_global do |blk|
bsig0 = @block_signatures[blk.block_body]
if bsig0
if bsig
bsig = bsig.merge(bsig0)
else
bsig = bsig0
end
end
@block_to_ctx[blk.block_body]&.each do |blk_ctx|
ret_ty = ret_ty.union(@return_values[blk_ctx]) if @return_values[blk_ctx]
end
end
end
bsig ||= BlockSignature.new([], [], nil, Type.nil)
bsig, = bsig.screen_name(nil, self)
ret_ty = ret_ty.screen_name(self)
ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
bsig = bsig + " " if bsig != ""
"{ #{ bsig }-> #{ ret_ty } }"
end
# File typeprof-0.21.2/lib/typeprof/analyzer.rb, line 2549
def show_method_signature(ctx)
farg_tys = @method_signatures[ctx]
return nil unless farg_tys
ret_ty = ctx.mid == :initialize ? Type::Void.new : @return_values[ctx] || Type.bot
untyped = farg_tys.include_untyped?(self) || ret_ty.include_untyped?(self)
farg_tys, ranges = farg_tys.screen_name(ctx.iseq, self)
ret_ty = ret_ty.screen_name(self)
ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
return "#{ (farg_tys.empty? ? "" : "#{ farg_tys } ") }-> #{ ret_ty }", untyped, ranges
end
# File typeprof-0.21.2/lib/typeprof/analyzer.rb, line 2511
def show_proc_signature(blks)
farg_tys, ret_ty = nil, Type.bot
blks.each do |blk|
blk.each_child_global do |blk|
next if blk.block_body.is_a?(TypedBlock) # XXX: Support TypedBlock
next unless @block_to_ctx[blk.block_body] # this occurs when screen_name is called before type-profiling finished (e.g., error message)
@block_to_ctx[blk.block_body].each do |blk_ctx|
if farg_tys
if @method_signatures[blk_ctx]
farg_tys = farg_tys.merge_as_block_arguments(@method_signatures[blk_ctx])
else
# this occurs when screen_name is called before type-profiling finished (e.g., error message)
end
else
farg_tys = @method_signatures[blk_ctx]
end
ret_ty = ret_ty.union(@return_values[blk_ctx]) if @return_values[blk_ctx]
end
end
end
return Type.any.screen_name(self) if @types_being_shown.include?(farg_tys) || @types_being_shown.include?(ret_ty)
begin
@types_being_shown << farg_tys << ret_ty
farg_tys, = farg_tys ? farg_tys.screen_name(nil, self) : ["(unknown)"]
ret_ty = ret_ty.screen_name(self)
ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
farg_tys = farg_tys + " " if farg_tys != ""
"^#{ farg_tys }-> #{ ret_ty }"
ensure
@types_being_shown.pop(2)
end
end
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 180
def source_location(pc)
"#{ @path }:#{ @insns[pc].lineno }"
end
Unify some instructions for flow-sensitive analysis
# File typeprof-0.21.2/lib/typeprof/iseq.rb, line 455
def unify_instructions
# This method rewrites instructions to enable flow-sensitive analysis.
#
# Consider `if x; ...; else; ... end`.
# When the variable `x` is of type "Integer | nil",
# we want to make sure that `x` is "Integer" in then clause.
# So, we need to split the environment to two ones:
# one is that `x` is of type "Integer", and the other is that
# `x` is type "nil".
#
# However, `if x` is compiled to "getlocal; branch".
# TypeProf evaluates them as follows:
#
# * "getlocal" pushes the value of `x` to the stack, amd
# * "branch" checks the value on the top of the stack
#
# TypeProf does not keep where the value comes from, so
# it is difficult to split the environment when evaluating "branch".
#
# This method rewrites "getlocal; branch" to "nop; getlocal_branch".
# The two instructions are unified to "getlocal_branch" instruction,
# so TypeProf can split the environment.
#
# This is a very fragile appoach because it highly depends on the compiler of Ruby.
# gather branch targets
# TODO: catch_table should be also considered
branch_targets = {}
@insns.each do |insn|
case insn.insn
when :branch
branch_targets[insn.operands[1]] = true
when :jump
branch_targets[insn.operands[0]] = true
end
end
# flow-sensitive analysis for `case var; when A; when B; when C; end`
# find a pattern: getlocal, (dup, putobject(true), getconstant(class name), checkmatch, branch)* for ..Ruby 3.0
# find a pattern: getlocal, (putobject(true), getconstant(class name), top(1), send(===), branch)* for Ruby 3.1..
case_branch_list = []
if CASE_WHEN_CHECKMATCH
(@insns.size - 1).times do |i|
insn = @insns[i]
next unless insn.insn == :getlocal && insn.operands[1] == 0
getlocal_operands = insn.operands
nops = [i]
new_insns = []
j = i + 1
while true
case @insns[j].insn
when :dup
break unless @insns[j + 1].check?(:putnil, [])
break unless @insns[j + 2].check?(:putobject, [true])
break unless @insns[j + 3].check?(:getconstant) # TODO: support A::B::C
break unless @insns[j + 4].check?(:checkmatch, [2])
break unless @insns[j + 5].check?(:branch)
target_pc = @insns[j + 5].operands[1]
break unless @insns[target_pc].check?(:pop, [])
nops << j << (j + 4) << target_pc
branch_operands = @insns[j + 5][1]
new_insns << [j + 5, Insn.new(:getlocal_checkmatch_branch, [getlocal_operands, branch_operands])]
j += 6
when :pop
nops << j
case_branch_list << [nops, new_insns]
break
else
break
end
end
end
else
(@insns.size - 1).times do |i|
insn = @insns[i]
next unless insn.insn == :getlocal && insn.operands[1] == 0
getlocal_operands = insn.operands
nops = []
new_insns = []
j = i + 1
while true
insn = @insns[j]
if insn.check?(:putnil, [])
break unless @insns[j + 1].check?(:putobject, [true])
break unless @insns[j + 2].check?(:getconstant) # TODO: support A::B::C
break unless @insns[j + 3].check?(:topn, [1])
break unless @insns[j + 4].check?(:send) && @insns[j + 4].operands[0].slice(:mid, :flag, :orig_argc) == {:mid=>:===, :flag=>20, :orig_argc=>1}
break unless @insns[j + 5].check?(:branch)
target_pc = @insns[j + 5].operands[1]
break unless @insns[target_pc].check?(:pop, [])
nops << (j + 4) #<< target_pc
send_operands = @insns[j + 4][1]
branch_operands = @insns[j + 5][1]
new_insns << [j + 5, Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])]
j += 6
elsif insn.check?(:pop, [])
#nops << j
case_branch_list << [nops, new_insns]
break
else
break
end
end
end
end
case_branch_list.each do |nops, new_insns|
nops.each {|i| @insns[i] = Insn.new(:nop, []) }
new_insns.each {|i, insn| @insns[i] = insn }
end
# find a pattern: getlocal(recv), ..., send (is_a?, respond_to?), branch
recv_getlocal_send_branch_list = []
(@insns.size - 1).times do |i|
insn = @insns[i]
if insn.insn == :getlocal && insn.operands[1] == 0
j = i + 1
sp = 1
while @insns[j]
sp = check_send_branch(sp, j)
if sp == :match
recv_getlocal_send_branch_list << [i, j]
break
end
break if !sp
j += 1
end
end
end
recv_getlocal_send_branch_list.each do |i, j|
next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
getlocal_operands = @insns[i].operands
send_operands = @insns[j].operands
branch_operands = @insns[j + 1].operands
@insns[j] = Insn.new(:nop, [])
@insns[j + 1] = Insn.new(:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
end
# find a pattern: getlocal, send (===), branch
arg_getlocal_send_branch_list = []
(@insns.size - 1).times do |i|
insn1 = @insns[i]
next unless insn1.insn == :getlocal && insn1.operands[1] == 0
insn2 = @insns[i + 1]
next unless insn2.insn == :send
send_operands = insn2.operands[0]
next unless send_operands[:flag] == 16 && send_operands[:orig_argc] == 1
insn3 = @insns[i + 2]
next unless insn3.insn == :branch
arg_getlocal_send_branch_list << i
end
arg_getlocal_send_branch_list.each do |i|
next if (i .. i + 2).any? {|i| branch_targets[i] }
getlocal_operands = @insns[i].operands
send_operands = @insns[i + 1].operands
branch_operands = @insns[i + 2].operands
@insns[i + 1] = Insn.new(:nop, [])
@insns[i + 2] = Insn.new(:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands])
end
# find a pattern: send (block_given?), branch
send_branch_list = []
(@insns.size - 1).times do |i|
insn = @insns[i]
if insn.insn == :send
insn = @insns[i + 1]
if insn.insn == :branch
send_branch_list << i
end
end
end
send_branch_list.each do |i|
next if branch_targets[i + 1]
send_operands = @insns[i].operands
branch_operands = @insns[i + 1].operands
@insns[i] = Insn.new(:nop, [])
@insns[i + 1] = Insn.new(:send_branch, [send_operands, branch_operands])
end
# find a pattern: getlocal, dup, branch
(@insns.size - 2).times do |i|
next if branch_targets[i + 1] || branch_targets[i + 2]
insn0 = @insns[i]
insn1 = @insns[i + 1]
insn2 = @insns[i + 2]
if insn0.insn == :getlocal && insn1.insn == :dup && insn2.insn == :branch && insn0.operands[1] == 0
getlocal_operands = insn0.operands
dup_operands = insn1.operands
branch_operands = insn2.operands
@insns[i ] = Insn.new(:nop, [])
@insns[i + 1] = Insn.new(:nop, [])
@insns[i + 2] = Insn.new(:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands])
end
end
# find a pattern: dup, setlocal, branch
(@insns.size - 2).times do |i|
next if branch_targets[i + 1] || branch_targets[i + 2]
insn0 = @insns[i]
insn1 = @insns[i + 1]
insn2 = @insns[i + 2]
if insn0.insn == :dup && insn1.insn == :setlocal && insn2.insn == :branch && insn1.operands[1] == 0
dup_operands = insn0.operands
setlocal_operands = insn1.operands
branch_operands = insn2.operands
@insns[i ] = Insn.new(:nop, [])
@insns[i + 1] = Insn.new(:nop, [])
@insns[i + 2] = Insn.new(:dup_setlocal_branch, [dup_operands, setlocal_operands, branch_operands])
end
end
# find a pattern: dup, branch
(@insns.size - 1).times do |i|
next if branch_targets[i + 1]
insn0 = @insns[i]
insn1 = @insns[i + 1]
if insn0.insn == :dup && insn1.insn == :branch
dup_operands = insn0.operands
branch_operands = insn1.operands
@insns[i ] = Insn.new(:nop, [])
@insns[i + 1] = Insn.new(:dup_branch, [dup_operands, branch_operands])
end
end
# find a pattern: getlocal, branch
(@insns.size - 1).times do |i|
next if branch_targets[i + 1]
insn0 = @insns[i]
insn1 = @insns[i + 1]
if insn0.insn == :getlocal && insn0.operands[1] == 0 && insn1.insn == :branch
getlocal_operands = insn0.operands
branch_operands = insn1.operands
@insns[i ] = Insn.new(:nop, [])
@insns[i + 1] = Insn.new(:getlocal_branch, [getlocal_operands, branch_operands])
end
end
end