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