In Files

  • typeprof-0.21.2/lib/typeprof/analyzer.rb
  • typeprof-0.21.2/lib/typeprof/iseq.rb
  • typeprof-0.21.2/tools/setup-insns-def.rb

Class/Module Index [+]

Quicksearch

Object

Public Class Methods

new(iseq, file_info) click to toggle source
 
               # 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
            

Public Instance Methods

<=>(other) click to toggle source
 
               # File typeprof-0.21.2/lib/typeprof/iseq.rb, line 243
def <=>(other)
  @id <=> other.id
end
            
add_body_start_marker(insns) click to toggle source

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
            
add_called_iseq(pc, callee_iseq) click to toggle source
 
               # 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
            
add_def_loc(pc, detailed_loc) click to toggle source
 
               # 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
            
add_exception_cont_marker(insns, catch_table) click to toggle source

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
            
check_send_branch(sp, j) click to toggle source
 
               # 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_info(file_info, absolute_level = 0, parent_variable_tables = {}) click to toggle source

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
            
convert_insns(insns, node_ids, file_info) click to toggle source

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
            
create_label_table(insns) click to toggle source
 
               # 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
            
detailed_source_location(pc) click to toggle source
 
               # 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
            
do_define_iseq_method(ep, env, mid, iseq, outer_ep) click to toggle source
 
               # 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
            
do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, replace_cref: nil, &ctn) click to toggle source
 
               # 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
            
do_send(recvs, mid, aargs, ep, env, &ctn) click to toggle source
 
               # 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
            
extract_method_name_token_range(node) click to toggle source
 
               # 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
            
pretty_print(q) click to toggle source
 
               # 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
            
rename_insn_types() click to toggle source
 
               # 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
            
setup_insns(insns, labels, file_info) click to toggle source
 
               # 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
            
should_collect_defs(insn_kind) click to toggle source
 
               # 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
            
show_block_signature(blks) click to toggle source
 
               # 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
            
show_method_signature(ctx) click to toggle source
 
               # 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
            
show_proc_signature(blks) click to toggle source
 
               # 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
            
source_location(pc) click to toggle source
 
               # File typeprof-0.21.2/lib/typeprof/iseq.rb, line 180
def source_location(pc)
  "#{ @path }:#{ @insns[pc].lineno }"
end
            
unify_instructions() click to toggle source

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
            
There is an updated format of the API docs for this version here.