Home | History | Annotate | Download | only in Scripts
      1 #!/usr/bin/env ruby
      2 
      3 # Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
      4 #
      5 # Redistribution and use in source and binary forms, with or without
      6 # modification, are permitted provided that the following conditions
      7 # are met:
      8 # 1. Redistributions of source code must retain the above copyright
      9 #    notice, this list of conditions and the following disclaimer.
     10 # 2. Redistributions in binary form must reproduce the above copyright
     11 #    notice, this list of conditions and the following disclaimer in the
     12 #    documentation and/or other materials provided with the distribution.
     13 #
     14 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     15 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     16 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     18 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     19 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     20 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     21 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     22 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     23 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     24 # THE POSSIBILITY OF SUCH DAMAGE.
     25 
     26 require 'rubygems'
     27 
     28 require 'readline'
     29 
     30 begin
     31     require 'json'
     32     require 'highline'
     33 rescue LoadError
     34     $stderr.puts "Error: some required gems are not installed!"
     35     $stderr.puts
     36     $stderr.puts "Try running:"
     37     $stderr.puts
     38     $stderr.puts "sudo gem install json"
     39     $stderr.puts "sudo gem install highline"
     40     exit 1
     41 end
     42 
     43 class Bytecode
     44     attr_accessor :bytecodes, :bytecodeIndex, :opcode, :description, :topCounts, :bottomCounts, :machineInlinees, :osrExits
     45 
     46     def initialize(bytecodes, bytecodeIndex, opcode, description)
     47         @bytecodes = bytecodes
     48         @bytecodeIndex = bytecodeIndex
     49         @opcode = opcode
     50         @description = description
     51         @topCounts = [] # "source" counts
     52         @bottomCounts = {} # "machine" counts, maps compilations to counts
     53         @machineInlinees = {} # maps my compilation to a set of inlinees
     54         @osrExits = []
     55     end
     56 
     57     def shouldHaveCounts?
     58         @opcode != "op_call_put_result"
     59     end
     60 
     61     def addTopCount(count)
     62         @topCounts << count
     63     end
     64 
     65     def addBottomCountForCompilation(count, compilation)
     66         @bottomCounts[compilation] = [] unless @bottomCounts[compilation]
     67         @bottomCounts[compilation] << count
     68     end
     69 
     70     def addMachineInlinee(compilation, inlinee)
     71         @machineInlinees[compilation] = {} unless @machineInlinees[compilation]
     72         @machineInlinees[compilation][inlinee] = true
     73     end
     74 
     75     def totalTopExecutionCount
     76         sum = 0
     77         @topCounts.each {
     78             | value |
     79             sum += value.count
     80         }
     81         sum
     82     end
     83 
     84     def topExecutionCount(engine)
     85         sum = 0
     86         @topCounts.each {
     87             | value |
     88             if value.engine == engine
     89                 sum += value.count
     90             end
     91         }
     92         sum
     93     end
     94 
     95     def totalBottomExecutionCount
     96         sum = 0
     97         @bottomCounts.each_value {
     98             | counts |
     99             max = 0
    100             counts.each {
    101                 | value |
    102                 max = [max, value.count].max
    103             }
    104             sum += max
    105         }
    106         sum
    107     end
    108 
    109     def bottomExecutionCount(engine)
    110         sum = 0
    111         @bottomCounts.each_pair {
    112             | compilation, counts |
    113             if compilation.engine == engine
    114                 max = 0
    115                 counts.each {
    116                     | value |
    117                     max = [max, value.count].max
    118                 }
    119                 sum += max
    120             end
    121         }
    122         sum
    123     end
    124 
    125     def totalExitCount
    126         sum = 0
    127         @osrExits.each {
    128             | exit |
    129             sum += exit.count
    130         }
    131         sum
    132     end
    133 end
    134 
    135 class Bytecodes
    136     attr_accessor :codeHash, :inferredName, :source, :instructionCount, :machineInlineSites, :compilations
    137 
    138     def initialize(json)
    139         @codeHash = json["hash"].to_s
    140         @inferredName = json["inferredName"].to_s
    141         @source = json["sourceCode"].to_s
    142         @instructionCount = json["instructionCount"].to_i
    143         @bytecode = {}
    144         json["bytecode"].each {
    145             | subJson |
    146             index = subJson["bytecodeIndex"].to_i
    147             @bytecode[index] = Bytecode.new(self, index, subJson["opcode"].to_s, subJson["description"].to_s)
    148         }
    149         @machineInlineSites = {} # maps compilation to a set of origins
    150         @compilations = []
    151     end
    152 
    153     def name(limit)
    154         if to_s.size > limit
    155             "\##{@codeHash}"
    156         else
    157             to_s
    158         end
    159     end
    160 
    161     def to_s
    162         "#{@inferredName}\##{@codeHash}"
    163     end
    164 
    165     def matches(pattern)
    166         if pattern =~ /^#/
    167             $~.post_match == @codeHash
    168         elsif pattern =~ /#/
    169             pattern == to_s
    170         else
    171             pattern == @inferredName or pattern == @codeHash
    172         end
    173     end
    174 
    175     def each
    176         @bytecode.values.sort{|a, b| a.bytecodeIndex <=> b.bytecodeIndex}.each {
    177             | value |
    178             yield value
    179         }
    180     end
    181 
    182     def bytecode(bytecodeIndex)
    183         @bytecode[bytecodeIndex]
    184     end
    185 
    186     def addMachineInlineSite(compilation, origin)
    187         @machineInlineSites[compilation] = {} unless @machineInlineSites[compilation]
    188         @machineInlineSites[compilation][origin] = true
    189     end
    190 
    191     def totalMachineInlineSites
    192         sum = 0
    193         @machineInlineSites.each_value {
    194             | set |
    195             sum += set.size
    196         }
    197         sum
    198     end
    199 
    200     def sourceMachineInlineSites
    201         set = {}
    202         @machineInlineSites.each_value {
    203             | mySet |
    204             set.merge!(mySet)
    205         }
    206         set.size
    207     end
    208 
    209     def totalMaxTopExecutionCount
    210         max = 0
    211         @bytecode.each_value {
    212             | bytecode |
    213             max = [max, bytecode.totalTopExecutionCount].max
    214         }
    215         max
    216     end
    217 
    218     def maxTopExecutionCount(engine)
    219         max = 0
    220         @bytecode.each_value {
    221             | bytecode |
    222             max = [max, bytecode.topExecutionCount(engine)].max
    223         }
    224         max
    225     end
    226 
    227     def totalMaxBottomExecutionCount
    228         max = 0
    229         @bytecode.each_value {
    230             | bytecode |
    231             max = [max, bytecode.totalBottomExecutionCount].max
    232         }
    233         max
    234     end
    235 
    236     def maxBottomExecutionCount(engine)
    237         max = 0
    238         @bytecode.each_value {
    239             | bytecode |
    240             max = [max, bytecode.bottomExecutionCount(engine)].max
    241         }
    242         max
    243     end
    244 
    245     def totalExitCount
    246         sum = 0
    247         each {
    248             | bytecode |
    249             sum += bytecode.totalExitCount
    250         }
    251         sum
    252     end
    253 end
    254 
    255 class ProfiledBytecode
    256     attr_reader :bytecodeIndex, :description
    257 
    258     def initialize(json)
    259         @bytecodeIndex = json["bytecodeIndex"].to_i
    260         @description = json["description"].to_s
    261     end
    262 end
    263 
    264 class ProfiledBytecodes
    265     attr_reader :header, :bytecodes
    266 
    267     def initialize(json)
    268         @header = json["header"]
    269         @bytecodes = $bytecodes[json["bytecodesID"].to_i]
    270         @sequence = json["bytecode"].map {
    271             | subJson |
    272             ProfiledBytecode.new(subJson)
    273         }
    274     end
    275 
    276     def each
    277         @sequence.each {
    278             | description |
    279             yield description
    280         }
    281     end
    282 end
    283 
    284 def originStackFromJSON(json)
    285     json.map {
    286         | subJson |
    287         $bytecodes[subJson["bytecodesID"].to_i].bytecode(subJson["bytecodeIndex"].to_i)
    288     }
    289 end
    290 
    291 class CompiledBytecode
    292     attr_accessor :origin, :description
    293 
    294     def initialize(json)
    295         @origin = originStackFromJSON(json["origin"])
    296         @description = json["description"].to_s
    297     end
    298 end
    299 
    300 class ExecutionCounter
    301     attr_accessor :origin, :engine, :count
    302 
    303     def initialize(origin, engine, count)
    304         @origin = origin
    305         @engine = engine
    306         @count = count
    307     end
    308 end
    309 
    310 class OSRExit
    311     attr_reader :compilation, :origin, :codeAddresses, :exitKind, :isWatchpoint, :count
    312 
    313     def initialize(compilation, origin, codeAddresses, exitKind, isWatchpoint, count)
    314         @compilation = compilation
    315         @origin = origin
    316         @codeAddresses = codeAddresses
    317         @exitKind = exitKind
    318         @isWatchpoint = isWatchpoint
    319         @count = count
    320     end
    321 
    322     def dumpForDisplay(prefix)
    323         puts(prefix + "EXIT: due to #{@exitKind}, #{@count} times")
    324     end
    325 end
    326 
    327 class Compilation
    328     attr_accessor :bytecode, :engine, :descriptions, :counters, :compilationIndex
    329     attr_accessor :osrExits, :profiledBytecodes, :numInlinedGetByIds, :numInlinedPutByIds
    330     attr_accessor :numInlinedCalls
    331 
    332     def initialize(json)
    333         @bytecode = $bytecodes[json["bytecodesID"].to_i]
    334         @bytecode.compilations << self
    335         @compilationIndex = @bytecode.compilations.size
    336         @engine = json["compilationKind"]
    337         @descriptions = json["descriptions"].map {
    338             | subJson |
    339             CompiledBytecode.new(subJson)
    340         }
    341         @descriptions.each {
    342             | description |
    343             next if description.origin.empty?
    344             description.origin[1..-1].each_with_index {
    345                 | inlinee, index |
    346                 description.origin[0].addMachineInlinee(self, inlinee.bytecodes)
    347                 inlinee.bytecodes.addMachineInlineSite(self, description.origin[0...index])
    348             }
    349         }
    350         @counters = {}
    351         json["counters"].each {
    352             | subJson |
    353             origin = originStackFromJSON(subJson["origin"])
    354             counter = ExecutionCounter.new(origin, @engine, subJson["executionCount"].to_i)
    355             @counters[origin] = counter
    356             origin[-1].addTopCount(counter)
    357             origin[0].addBottomCountForCompilation(counter, self)
    358         }
    359         @osrExits = {}
    360         json["osrExits"].each {
    361             | subJson |
    362             osrExit = OSRExit.new(self, originStackFromJSON(subJson["origin"]),
    363                                   json["osrExitSites"][subJson["id"]].map {
    364                                       | value |
    365                                       value.hex
    366                                   }, subJson["exitKind"], subJson["isWatchpoint"],
    367                                   subJson["count"])
    368             osrExit.codeAddresses.each {
    369                 | codeAddress |
    370                 osrExits[codeAddress] = [] unless osrExits[codeAddress]
    371                 osrExits[codeAddress] << osrExit
    372             }
    373             osrExit.origin[-1].osrExits << osrExit
    374         }
    375         @profiledBytecodes = []
    376         json["profiledBytecodes"].each {
    377             | subJson |
    378             @profiledBytecodes << ProfiledBytecodes.new(subJson)
    379         }
    380         @numInlinedGetByIds = json["numInlinedGetByIds"]
    381         @numInlinedPutByIds = json["numInlinedPutByIds"]
    382         @numInlinedCalls = json["numInlinedCalls"]
    383     end
    384 
    385     def counter(origin)
    386         @counters[origin]
    387     end
    388 
    389     def to_s
    390         "#{bytecode}-#{compilationIndex}-#{engine}"
    391     end
    392 end
    393 
    394 class DescriptionLine
    395     attr_reader :actualCountsString, :sourceCountsString, :disassembly, :shouldShow
    396 
    397     def initialize(actualCountsString, sourceCountsString, disassembly, shouldShow)
    398         @actualCountsString = actualCountsString
    399         @sourceCountsString = sourceCountsString
    400         @disassembly = disassembly
    401         @shouldShow = shouldShow
    402     end
    403 
    404     def codeAddress
    405         if @disassembly =~ /^\s*(0x[0-9a-fA-F]+):/
    406             $1.hex
    407         else
    408             nil
    409         end
    410     end
    411 end
    412 
    413 if ARGV.length != 1
    414     $stderr.puts "Usage: display-profiler-output <path to profiler output file>"
    415     $stderr.puts
    416     $stderr.puts "The typical usage pattern for the profiler currently looks something like:"
    417     $stderr.puts
    418     $stderr.puts "Path/To/jsc -p profile.json myprogram.js"
    419     $stderr.puts "display-profiler-output profile.json"
    420     exit 1
    421 end
    422 
    423 $json = JSON::parse(IO::read(ARGV[0]))
    424 $bytecodes = $json["bytecodes"].map {
    425     | subJson |
    426     Bytecodes.new(subJson)
    427 }
    428 $compilations = $json["compilations"].map {
    429     | subJson |
    430     Compilation.new(subJson)
    431 }
    432 $engines = ["Baseline", "DFG"]
    433 
    434 def lpad(str,chars)
    435   if str.length>chars
    436     str
    437   else
    438     "%#{chars}s"%(str)
    439   end
    440 end
    441 
    442 def rpad(str, chars)
    443     while str.length < chars
    444         str += " "
    445     end
    446     str
    447 end
    448 
    449 def center(str, chars)
    450     while str.length < chars
    451         str += " "
    452         if str.length < chars
    453             str = " " + str
    454         end
    455     end
    456     str
    457 end
    458 
    459 def mayBeHash(hash)
    460     hash =~ /#/ or hash.size == 6
    461 end
    462 
    463 def sourceOnOneLine(source, limit)
    464     source.gsub(/\s+/, ' ')[0...limit]
    465 end
    466 
    467 def screenWidth
    468     if $stdin.tty?
    469         HighLine::SystemExtensions.terminal_size[0]
    470     else
    471         200
    472     end
    473 end
    474 
    475 def summary(mode)
    476     remaining = screenWidth
    477 
    478     # Figure out how many columns we need for the code block names, and for counts
    479     maxCount = 0
    480     maxName = 0
    481     $bytecodes.each {
    482         | bytecodes |
    483         maxCount = ([maxCount] + $engines.map {
    484                         | engine |
    485                         bytecodes.maxTopExecutionCount(engine)
    486                     } + $engines.map {
    487                         | engine |
    488                         bytecodes.maxBottomExecutionCount(engine)
    489                     }).max
    490         maxName = [bytecodes.to_s.size, maxName].max
    491     }
    492     maxCountDigits = maxCount.to_s.size
    493 
    494     hashCols = [[maxName, 30].min, "CodeBlock".size].max
    495     remaining -= hashCols + 1
    496 
    497     countCols = [maxCountDigits * $engines.size, "Source Counts".size].max
    498     remaining -= countCols + 1
    499 
    500     if mode == :full
    501         instructionCountCols = 6
    502         remaining -= instructionCountCols + 1
    503 
    504         machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max
    505         remaining -= machineCountCols + 1
    506 
    507         compilationsCols = 7
    508         remaining -= compilationsCols + 1
    509 
    510         inlinesCols = 9
    511         remaining -= inlinesCols + 1
    512 
    513         exitCountCols = 7
    514         remaining -= exitCountCols + 1
    515 
    516         recentOptsCols = 12
    517         remaining -= recentOptsCols + 1
    518     end
    519 
    520     if remaining > 0
    521         sourceCols = remaining
    522     else
    523         sourceCols = nil
    524     end
    525 
    526     print(center("CodeBlock", hashCols))
    527     if mode == :full
    528         print(" " + center("#Instr", instructionCountCols))
    529     end
    530     print(" " + center("Source Counts", countCols))
    531     if mode == :full
    532         print(" " + center("Machine Counts", machineCountCols))
    533         print(" " + center("#Compil", compilationsCols))
    534         print(" " + center("Inlines", inlinesCols))
    535         print(" " + center("#Exits", exitCountCols))
    536         print(" " + center("Last Opts", recentOptsCols))
    537     end
    538     if sourceCols
    539         print(" " + center("Source", sourceCols))
    540     end
    541     puts
    542 
    543     print(center("", hashCols))
    544     if mode == :full
    545         print(" " + (" " * instructionCountCols))
    546     end
    547     print(" " + center("Base/DFG", countCols))
    548     if mode == :full
    549         print(" " + center("Base/DFG", machineCountCols))
    550         print(" " + (" " * compilationsCols))
    551         print(" " + center("Src/Total", inlinesCols))
    552         print(" " + (" " * exitCountCols))
    553         print(" " + center("Get/Put/Call", recentOptsCols))
    554     end
    555     puts
    556     $bytecodes.sort {
    557         | a, b |
    558         b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount
    559     }.each {
    560         | bytecode |
    561         print(center(bytecode.name(hashCols), hashCols))
    562         if mode == :full
    563             print(" " + center(bytecode.instructionCount.to_s, instructionCountCols))
    564         end
    565         print(" " +
    566               center($engines.map {
    567                          | engine |
    568                          bytecode.maxTopExecutionCount(engine).to_s
    569                      }.join("/"), countCols))
    570         if mode == :full
    571             print(" " + center($engines.map {
    572                                    | engine |
    573                                    bytecode.maxBottomExecutionCount(engine).to_s
    574                                }.join("/"), machineCountCols))
    575             print(" " + center(bytecode.compilations.size.to_s, compilationsCols))
    576             print(" " + center(bytecode.sourceMachineInlineSites.to_s + "/" + bytecode.totalMachineInlineSites.to_s, inlinesCols))
    577             print(" " + center(bytecode.totalExitCount.to_s, exitCountCols))
    578             lastCompilation = bytecode.compilations[-1]
    579             if lastCompilation
    580                 optData = [lastCompilation.numInlinedGetByIds,
    581                            lastCompilation.numInlinedPutByIds,
    582                            lastCompilation.numInlinedCalls]
    583             else
    584                 optData = ["N/A"]
    585             end
    586             print(" " + center(optData.join('/'), recentOptsCols))
    587         end
    588         if sourceCols
    589             print(" " + sourceOnOneLine(bytecode.source, sourceCols))
    590         end
    591         puts
    592     }
    593 end
    594 
    595 def executeCommand(*commandArray)
    596     command = commandArray[0]
    597     args = commandArray[1..-1]
    598     case command
    599     when "help", "h", "?"
    600         puts "summary (s)     Print a summary of code block execution rates."
    601         puts "full (f)        Same as summary, but prints more information."
    602         puts "source          Show the source for a code block."
    603         puts "bytecode (b)    Show the bytecode for a code block, with counts."
    604         puts "profiling (p)   Show the (internal) profiling data for a code block."
    605         puts "display (d)     Display details for a code block."
    606         puts "inlines         Show all inlining stacks that the code block was on."
    607         puts "help (h)        Print this message."
    608         puts "quit (q)        Quit."
    609     when "quit", "q", "exit"
    610         exit 0
    611     when "summary", "s"
    612         summary(:summary)
    613     when "full", "f"
    614         summary(:full)
    615     when "source"
    616         if args.length != 1
    617             puts "Usage: source <code block hash>"
    618             return
    619         end
    620         $bytecodes.each {
    621             | bytecode |
    622             if bytecode.matches(args[0])
    623                 puts bytecode.source
    624             end
    625         }
    626     when "bytecode", "b"
    627         if args.length != 1
    628             puts "Usage: source <code block hash>"
    629             return
    630         end
    631 
    632         hash = args[0]
    633 
    634         countCols = 10 * $engines.size
    635         machineCols = 10 * $engines.size
    636         pad = 1
    637         while (countCols + 1 + machineCols + pad) % 8 != 0
    638             pad += 1
    639         end
    640 
    641         $bytecodes.each {
    642             | bytecodes |
    643             next unless bytecodes.matches(hash)
    644             puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) +
    645                  (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols))
    646             puts(center("Base/DFG", countCols) + " " + center("Base/DFG", countCols))
    647             bytecodes.each {
    648                 | bytecode |
    649                 if bytecode.shouldHaveCounts?
    650                     countsString = $engines.map {
    651                         | myEngine |
    652                         bytecode.topExecutionCount(myEngine)
    653                     }.join("/")
    654                     machineString = $engines.map {
    655                         | myEngine |
    656                         bytecode.bottomExecutionCount(myEngine)
    657                     }.join("/")
    658                 else
    659                     countsString = ""
    660                     machineString = ""
    661                 end
    662                 puts(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad) + bytecode.description.chomp)
    663                 bytecode.osrExits.each {
    664                     | exit |
    665                     puts(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * (pad + 10)) +
    666                          "EXIT: in #{exit.compilation} due to #{exit.exitKind}, #{exit.count} times")
    667                 }
    668             }
    669         }
    670     when "profiling", "p"
    671         if args.length != 1
    672             puts "Usage: profiling <code block hash>"
    673             return
    674         end
    675 
    676         hash = args[0]
    677 
    678         first = true
    679         $compilations.each {
    680             | compilation |
    681 
    682             compilation.profiledBytecodes.each {
    683                 | profiledBytecodes |
    684                 if profiledBytecodes.bytecodes.matches(hash)
    685                     if first
    686                         first = false
    687                     else
    688                         puts
    689                     end
    690 
    691                     puts "Compilation #{compilation}:"
    692                     profiledBytecodes.header.each {
    693                         | header |
    694                         puts(" " * 6 + header)
    695                     }
    696                     profiledBytecodes.each {
    697                         | bytecode |
    698                         puts(" " * 8 + bytecode.description)
    699                         profiledBytecodes.bytecodes.bytecode(bytecode.bytecodeIndex).osrExits.each {
    700                             | exit |
    701                             if exit.compilation == compilation
    702                                 puts(" !!!!!           EXIT: due to #{exit.exitKind}, #{exit.count} times")
    703                             end
    704                         }
    705                     }
    706                 end
    707             }
    708         }
    709     when "inlines"
    710         if args.length != 1
    711             puts "Usage: inlines <code block hash>"
    712             return
    713         end
    714 
    715         hash = args[0]
    716 
    717         $bytecodes.each {
    718             | bytecodes |
    719             next unless bytecodes.matches(hash)
    720 
    721             # FIXME: print something useful to say more about which code block this is.
    722 
    723             $compilations.each {
    724                 | compilation |
    725                 myOrigins = []
    726                 compilation.descriptions.each {
    727                     | description |
    728                     if description.origin.index {
    729                             | myBytecode |
    730                             bytecodes == myBytecode.bytecodes
    731                         }
    732                         myOrigins << description.origin
    733                     end
    734                 }
    735                 myOrigins.uniq!
    736                 myOrigins.sort! {
    737                     | a, b |
    738                     result = 0
    739                     [a.size, b.size].min.times {
    740                         | index |
    741                         result = a[index].bytecodeIndex <=> b[index].bytecodeIndex
    742                         break if result != 0
    743                     }
    744                     result
    745                 }
    746 
    747                 next if myOrigins.empty?
    748 
    749                 printArray = []
    750                 lastPrintStack = []
    751 
    752                 def originToPrintStack(origin)
    753                     (0...(origin.size - 1)).map {
    754                         | index |
    755                         "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}"
    756                     }
    757                 end
    758 
    759                 def printStack(printArray, stack, lastStack)
    760                     stillCommon = true
    761                     stack.each_with_index {
    762                         | entry, index |
    763                         next if stillCommon and entry == lastStack[index]
    764                         printArray << ("    " * (index + 1) + entry)
    765                         stillCommon = false
    766                     }
    767                 end
    768 
    769                 myOrigins.each {
    770                     | origin |
    771                     currentPrintStack = originToPrintStack(origin)
    772                     printStack(printArray, currentPrintStack, lastPrintStack)
    773                     lastPrintStack = currentPrintStack
    774                 }
    775 
    776                 next if printArray.empty?
    777 
    778                 puts "Compilation #{compilation}:"
    779                 printArray.each {
    780                     | entry |
    781                     puts entry
    782                 }
    783             }
    784         }
    785     when "display", "d"
    786         compilationIndex = nil
    787 
    788         case args.length
    789         when 1
    790             if args[0] == "*"
    791                 hash = nil
    792             else
    793                 hash = args[0]
    794             end
    795             engine = nil
    796         when 2
    797             if mayBeHash(args[0])
    798                 hash = args[0]
    799                 engine = args[1]
    800             else
    801                 engine = args[0]
    802                 hash = args[1]
    803             end
    804         else
    805             puts "Usage: summary <code block hash> <engine>"
    806             return
    807         end
    808 
    809         if hash and hash =~ /-([0-9]+)-/
    810             hash = $~.pre_match
    811             engine = $~.post_match
    812             compilationIndex = $1.to_i
    813         end
    814 
    815         if engine and not $engines.index(engine)
    816             pattern = Regexp.new(Regexp.escape(engine), "i")
    817             trueEngine = nil
    818             $engines.each {
    819                 | myEngine |
    820                 if myEngine =~ pattern
    821                     trueEngine = myEngine
    822                     break
    823                 end
    824             }
    825             unless trueEngine
    826                 puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}."
    827                 return
    828             end
    829             engine = trueEngine
    830         end
    831 
    832         actualCountCols = 13
    833         sourceCountCols = 10 * $engines.size
    834 
    835         first = true
    836         $compilations.each {
    837             | compilation |
    838             next if hash and not compilation.bytecode.matches(hash)
    839             next if engine and compilation.engine != engine
    840             next if compilationIndex and compilation.compilationIndex != compilationIndex
    841 
    842             if first
    843                 first = false
    844             else
    845                 puts
    846             end
    847 
    848             puts("Compilation #{compilation}:")
    849             puts("    Num inlined: GetByIds: #{compilation.numInlinedGetByIds}  PutByIds: #{compilation.numInlinedPutByIds}  Calls: #{compilation.numInlinedCalls}")
    850             puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols))
    851             puts((" " * actualCountCols) + " " + center("Base/DFG", sourceCountCols))
    852 
    853             lines = []
    854 
    855             compilation.descriptions.each {
    856                 | description |
    857                 # FIXME: We should have a better way of detecting things like CountExecution nodes
    858                 # and slow path entries in the baseline JIT.
    859                 if description.description =~ /CountExecution\(/ and compilation.engine == "DFG"
    860                     shouldShow = false
    861                 else
    862                     shouldShow = true
    863                 end
    864                 if description.origin.empty? or not description.origin[-1].shouldHaveCounts? or (compilation.engine == "Baseline" and description.description =~ /^\s*\(S\)/)
    865                     actualCountsString = ""
    866                     sourceCountsString = ""
    867                 else
    868                     actualCountsString = compilation.counter(description.origin).count.to_s
    869                     sourceCountsString = $engines.map {
    870                         | myEngine |
    871                         description.origin[-1].topExecutionCount(myEngine)
    872                     }.join("/")
    873                 end
    874                 description.description.split("\n").each {
    875                     | line |
    876                     lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow)
    877                 }
    878             }
    879 
    880             exitPrefix = center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 25)
    881 
    882             lines.each_with_index {
    883                 | line, index |
    884                 codeAddress = line.codeAddress
    885                 if codeAddress
    886                     list = compilation.osrExits[codeAddress]
    887                     if list
    888                         list.each {
    889                             | exit |
    890                             if exit.isWatchpoint
    891                                 exit.dumpForDisplay(exitPrefix)
    892                             end
    893                         }
    894                     end
    895                 end
    896                 if line.shouldShow
    897                     puts(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " " + line.disassembly)
    898                 end
    899                 if codeAddress
    900                     # Find the next disassembly address.
    901                     endIndex = index + 1
    902                     endAddress = nil
    903                     while endIndex < lines.size
    904                         myAddress = lines[endIndex].codeAddress
    905                         if myAddress
    906                             endAddress = myAddress
    907                             break
    908                         end
    909                         endIndex += 1
    910                     end
    911 
    912                     if endAddress
    913                         list = compilation.osrExits[endAddress]
    914                         if list
    915                             list.each {
    916                                 | exit |
    917                                 unless exit.isWatchpoint
    918                                     exit.dumpForDisplay(exitPrefix)
    919                                 end
    920                             }
    921                         end
    922                     end
    923                 end
    924             }
    925         }
    926     else
    927         puts "Invalid command: #{command}"
    928     end
    929 end
    930 
    931 if $stdin.tty?
    932     executeCommand("full")
    933 end
    934 
    935 while commandLine = Readline.readline("> ", true)
    936     executeCommand(*commandLine.split)
    937 end
    938 
    939