In Files

  • typeprof-0.12.0/lib/typeprof/iseq.rb

Class/Module Index [+]

Quicksearch

TypeProf::ISeq

Constants

FRESH_ID

Attributes

absolute_path[R]
catch_table[R]
fargs_format[R]
id[R]
insns[R]
linenos[R]
locals[R]
name[R]
path[R]
start_lineno[R]
type[R]

Public Class Methods

compile(file) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 5
def self.compile(file)
  opt = RubyVM::InstructionSequence.compile_option
  opt[:inline_const_cache] = false
  opt[:peephole_optimization] = false
  opt[:specialized_instruction] = false
  opt[:operands_unification] = false
  opt[:coverage_enabled] = false
  new(RubyVM::InstructionSequence.compile_file(file, **opt).to_a)
end
            
compile_str(str, path = nil) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 15
def self.compile_str(str, path = nil)
  opt = RubyVM::InstructionSequence.compile_option
  opt[:inline_const_cache] = false
  opt[:peephole_optimization] = false
  opt[:specialized_instruction] = false
  opt[:operands_unification] = false
  opt[:coverage_enabled] = false
  new(RubyVM::InstructionSequence.compile(str, path, **opt).to_a)
end
            
new(iseq) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 27
def initialize(iseq)
  @id = FRESH_ID[0]
  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

  case @type
  when :method, :block
    if @fargs_format[:opt]
      label = @fargs_format[:opt].last
      i = insns.index(label) + 1
    else
      i = insns.find_index {|insn| insn.is_a?(Array) }
    end
    # skip keyword initialization
    while insns[i][0] == :checkkeyword
      raise if insns[i + 1][0] != :branchif
      label = insns[i + 1][1]
      i = insns.index(label) + 1
    end
    insns[i, 0] = [[:_iseq_body_start]]
  end

  # 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
  special_labels = {}
  catch_table.map do |type, iseq, first, last, cont, stack_depth|
    special_labels[cont] = true if type == :rescue || type == :ensure
  end

  @insns = []
  @linenos = []

  labels = setup_iseq(insns, special_labels)

  # checkmatch->branch
  # send->branch

  @catch_table = []
  catch_table.map do |type, iseq, first, last, cont, stack_depth|
    iseq = iseq ? ISeq.new(iseq) : nil
    target = labels[special_labels[cont] ? :"#{ cont }_special" : cont]
    entry = [type, iseq, target, stack_depth]
    labels[first].upto(labels[last]) do |i|
      @catch_table[i] ||= []
      @catch_table[i] << entry
    end
  end

  merge_branches

  analyze_stack
end
            

Public Instance Methods

<=>(other) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 84
def <=>(other)
  @id <=> other.id
end
            
analyze_stack() click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 196
def analyze_stack
  # gather branch targets
  # TODO: catch_table should be also considered
  branch_targets = {}
  @insns.each do |insn, operands|
    case insn
    when :branch
      branch_targets[operands[1]] = true
    when :jump
      branch_targets[operands[0]] = true
    end
  end

  # find a pattern: getlocal, ..., send (is_a?, respond_to?), branch
  getlocal_send_branch_list = []
  (@insns.size - 1).times do |i|
    insn, operands = @insns[i]
    if insn == :getlocal && operands[1] == 0
      j = i + 1
      sp = 1
      while @insns[j]
        sp = check_send_branch(sp, j)
        if sp == :match
          getlocal_send_branch_list << [i, j]
          break
        end
        break if !sp
        j += 1
      end
    end
  end
  getlocal_send_branch_list.each do |i, j|
    next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
    _insn, getlocal_operands = @insns[i]
    _insn, send_operands = @insns[j]
    _insn, branch_operands = @insns[j + 1]
    @insns[j] = [:nop]
    @insns[j + 1] = [: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, _operands = @insns[i]
    if insn == :send
      insn, _operands = @insns[i + 1]
      if insn == :branch
        send_branch_list << i
      end
    end
  end
  send_branch_list.each do |i|
    next if branch_targets[i + 1]
    _insn, send_operands = @insns[i]
    _insn, branch_operands = @insns[i + 1]
    @insns[i] = [:nop]
    @insns[i + 1] = [: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, getlocal_operands = @insns[i]
    insn1, dup_operands = @insns[i + 1]
    insn2, branch_operands = @insns[i + 2]
    if insn0 == :getlocal && insn1 == :dup && insn2 == :branch && getlocal_operands[1] == 0
      @insns[i    ] = [:nop]
      @insns[i + 1] = [:nop]
      @insns[i + 2] = [:getlocal_dup_branch, [getlocal_operands, dup_operands, branch_operands]]
    end
  end

  # find a pattern: dup, branch
  (@insns.size - 1).times do |i|
    next if branch_targets[i + 1]
    insn0, dup_operands = @insns[i]
    insn1, branch_operands = @insns[i + 1]
    if insn0 == :dup && insn1 == :branch
      @insns[i    ] = [:nop]
      @insns[i + 1] = [: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, getlocal_operands = @insns[i]
    insn1, branch_operands = @insns[i + 1]
    if [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0 && insn1 == :branch
      @insns[i    ] = [:nop]
      @insns[i + 1] = [:getlocal_branch, [getlocal_operands, branch_operands]]
    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)*
  case_branch_list = []
  (@insns.size - 1).times do |i|
    insn0, getlocal_operands = @insns[i]
    next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
    nops = [i]
    new_insns = []
    j = i + 1
    while true
      case @insns[j]
      when [:dup, []]
        break unless @insns[j + 1] == [:putnil, []]
        break unless @insns[j + 2] == [:putobject, [true]]
        break unless @insns[j + 3][0] == :getconstant # TODO: support A::B::C
        break unless @insns[j + 4] == [:checkmatch, [2]]
        break unless @insns[j + 5][0] == :branch
        target_pc = @insns[j + 5][1][1]
        break unless @insns[target_pc] == [:pop, []]
        nops << j << (j + 4) << target_pc
        new_insns << [j + 5, [:getlocal_checkmatch_branch, [getlocal_operands, @insns[j + 5][1]]]]
        j += 6
      when [:pop, []]
        nops << j
        case_branch_list << [nops, new_insns]
        break
      else
        break
      end
    end
  end
  case_branch_list.each do |nops, new_insns|
    nops.each {|i| @insns[i] = [:nop, []] }
    new_insns.each {|i, insn| @insns[i] = insn }
  end
end
            
check_send_branch(sp, j) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 327
def check_send_branch(sp, j)
  insn, operands = @insns[j]

  case 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
    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 == :send && sp == 0 && @insns[j + 1][0] == :branch
    sp += 1
  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
    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
            
merge_branches() click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 143
def merge_branches
  @insns.size.times do |i|
    insn, operands = @insns[i]
    case insn
    when :branchif
      @insns[i] = [:branch, [:if] + operands]
    when :branchunless
      @insns[i] = [:branch, [:unless] + operands]
    when :branchnil
      @insns[i] = [:branch, [:nil] + operands]
    end
  end
end
            
pretty_print(q) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 164
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
            
setup_iseq(insns, special_labels) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 88
def setup_iseq(insns, special_labels)
  i = 0
  labels = {}
  ninsns = []
  insns.each do |e|
    if e.is_a?(Symbol) && e.to_s.start_with?("label")
      if special_labels[e]
        labels[:"#{ e }_special"] = i
        ninsns << [:nop]
        i += 1
      end
      labels[e] = i
    else
      i += 1 if e.is_a?(Array)
      ninsns << e
    end
  end

  lineno = 0
  ninsns.each do |e|
    case e
    when Integer # lineno
      lineno = e
    when Symbol # label or trace
      nil
    when Array
      insn, *operands = e
      operands = (INSN_TABLE[insn] || []).zip(operands).map do |type, operand|
        case type
        when "ISEQ"
          operand && ISeq.new(operand)
        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

      @insns << [insn, operands]
      @linenos << lineno
    else
      raise "unknown iseq entry: #{ e }"
    end
  end

  @fargs_format[:opt] = @fargs_format[:opt].map {|l| labels[l] } if @fargs_format[:opt]

  labels
end
            
source_location(pc) click to toggle source
 
               # File typeprof-0.12.0/lib/typeprof/iseq.rb, line 157
def source_location(pc)
  "#{ @path }:#{ @linenos[pc] }"
end