# 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
# 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
# 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
# File typeprof-0.12.0/lib/typeprof/iseq.rb, line 84 def <=>(other) @id <=> other.id end
# 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
# 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
# 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
# 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
# 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