#!/usr/bin/env ruby # Copyright (C) 2012-2014, 2016 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. require 'rubygems' require 'readline' begin require 'json' require 'highline' rescue LoadError $stderr.puts "Error: some required gems are not installed!" $stderr.puts $stderr.puts "Try running:" $stderr.puts $stderr.puts "sudo gem install json" $stderr.puts "sudo gem install highline" exit 1 end class Bytecode attr_accessor :bytecodes, :bytecodeIndex, :opcode, :description, :topCounts, :bottomCounts, :machineInlinees, :osrExits def initialize(bytecodes, bytecodeIndex, opcode, description) @bytecodes = bytecodes @bytecodeIndex = bytecodeIndex @opcode = opcode @description = description @topCounts = [] # "source" counts @bottomCounts = {} # "machine" counts, maps compilations to counts @machineInlinees = {} # maps my compilation to a set of inlinees @osrExits = [] end def shouldHaveCounts? @opcode != "op_call_put_result" end def addTopCount(count) @topCounts << count end def addBottomCountForCompilation(count, compilation) @bottomCounts[compilation] = [] unless @bottomCounts[compilation] @bottomCounts[compilation] << count end def addMachineInlinee(compilation, inlinee) @machineInlinees[compilation] = {} unless @machineInlinees[compilation] @machineInlinees[compilation][inlinee] = true end def totalTopExecutionCount sum = 0 @topCounts.each { | value | sum += value.count } sum end def topExecutionCount(engine) sum = 0 @topCounts.each { | value | if value.engine == engine sum += value.count end } sum end def totalBottomExecutionCount sum = 0 @bottomCounts.each_value { | counts | max = 0 counts.each { | value | max = [max, value.count].max } sum += max } sum end def bottomExecutionCount(engine) sum = 0 @bottomCounts.each_pair { | compilation, counts | if compilation.engine == engine max = 0 counts.each { | value | max = [max, value.count].max } sum += max end } sum end def totalExitCount sum = 0 @osrExits.each { | exit | sum += exit.count } sum end end class Bytecodes attr_accessor :codeHash, :inferredName, :source, :instructionCount, :machineInlineSites, :compilations def initialize(json) @codeHash = json["hash"].to_s @inferredName = json["inferredName"].to_s @source = json["sourceCode"].to_s @instructionCount = json["instructionCount"].to_i @bytecode = {} json["bytecode"].each { | subJson | index = subJson["bytecodeIndex"].to_i @bytecode[index] = Bytecode.new(self, index, subJson["opcode"].to_s, subJson["description"].to_s) } @machineInlineSites = {} # maps compilation to a set of origins @compilations = [] end def name(limit) if to_s.size > limit "\##{@codeHash}" else to_s end end def to_s "#{@inferredName}\##{@codeHash}" end def matches(pattern) if pattern =~ /^#/ $~.post_match == @codeHash elsif pattern =~ /#/ pattern == to_s else pattern == @inferredName or pattern == @codeHash end end def each @bytecode.values.sort{|a, b| a.bytecodeIndex <=> b.bytecodeIndex}.each { | value | yield value } end def bytecode(bytecodeIndex) @bytecode[bytecodeIndex] end def addMachineInlineSite(compilation, origin) @machineInlineSites[compilation] = {} unless @machineInlineSites[compilation] @machineInlineSites[compilation][origin] = true end def totalMachineInlineSites sum = 0 @machineInlineSites.each_value { | set | sum += set.size } sum end def sourceMachineInlineSites set = {} @machineInlineSites.each_value { | mySet | set.merge!(mySet) } set.size end def totalMaxTopExecutionCount max = 0 @bytecode.each_value { | bytecode | max = [max, bytecode.totalTopExecutionCount].max } max end def maxTopExecutionCount(engine) max = 0 @bytecode.each_value { | bytecode | max = [max, bytecode.topExecutionCount(engine)].max } max end def totalMaxBottomExecutionCount max = 0 @bytecode.each_value { | bytecode | max = [max, bytecode.totalBottomExecutionCount].max } max end def maxBottomExecutionCount(engine) max = 0 @bytecode.each_value { | bytecode | max = [max, bytecode.bottomExecutionCount(engine)].max } max end def totalExitCount sum = 0 each { | bytecode | sum += bytecode.totalExitCount } sum end def codeHashSortKey codeHash end end class ProfiledBytecode attr_reader :bytecodeIndex, :description def initialize(json) @bytecodeIndex = json["bytecodeIndex"].to_i @description = json["description"].to_s end end class ProfiledBytecodes attr_reader :header, :bytecodes def initialize(json) @header = json["header"] @bytecodes = $bytecodes[json["bytecodesID"].to_i] @sequence = json["bytecode"].map { | subJson | ProfiledBytecode.new(subJson) } end def each @sequence.each { | description | yield description } end end def originStackFromJSON(json) json.map { | subJson | $bytecodes[subJson["bytecodesID"].to_i].bytecode(subJson["bytecodeIndex"].to_i) } end class CompiledBytecode attr_accessor :origin, :description def initialize(json) @origin = originStackFromJSON(json["origin"]) @description = json["description"].to_s end end class ExecutionCounter attr_accessor :origin, :engine, :count def initialize(origin, engine, count) @origin = origin @engine = engine @count = count end end class OSRExit attr_reader :compilation, :origin, :codeAddresses, :exitKind, :isWatchpoint, :count def initialize(compilation, origin, codeAddresses, exitKind, isWatchpoint, count) @compilation = compilation @origin = origin @codeAddresses = codeAddresses @exitKind = exitKind @isWatchpoint = isWatchpoint @count = count end def dumpForDisplay(prefix) puts(prefix + "EXIT: due to #{@exitKind}, #{@count} times") end end class Compilation attr_accessor :bytecode, :engine, :descriptions, :counters, :compilationIndex attr_accessor :osrExits, :profiledBytecodes, :numInlinedGetByIds, :numInlinedPutByIds attr_accessor :numInlinedCalls, :jettisonReason, :additionalJettisonReason, :uid def initialize(json) @bytecode = $bytecodes[json["bytecodesID"].to_i] @bytecode.compilations << self @compilationIndex = @bytecode.compilations.size @engine = json["compilationKind"] @descriptions = json["descriptions"].map { | subJson | CompiledBytecode.new(subJson) } @descriptions.each { | description | next if description.origin.empty? description.origin[1..-1].each_with_index { | inlinee, index | description.origin[0].addMachineInlinee(self, inlinee.bytecodes) inlinee.bytecodes.addMachineInlineSite(self, description.origin[0...index]) } } @counters = {} json["counters"].each { | subJson | origin = originStackFromJSON(subJson["origin"]) counter = ExecutionCounter.new(origin, @engine, subJson["executionCount"].to_i) @counters[origin] = counter origin[-1].addTopCount(counter) origin[0].addBottomCountForCompilation(counter, self) } @osrExits = {} json["osrExits"].each { | subJson | osrExit = OSRExit.new(self, originStackFromJSON(subJson["origin"]), json["osrExitSites"][subJson["id"]].map { | value | value.hex }, subJson["exitKind"], subJson["isWatchpoint"], subJson["count"]) osrExit.codeAddresses.each { | codeAddress | osrExits[codeAddress] = [] unless osrExits[codeAddress] osrExits[codeAddress] << osrExit } osrExit.origin[-1].osrExits << osrExit } @profiledBytecodes = [] json["profiledBytecodes"].each { | subJson | @profiledBytecodes << ProfiledBytecodes.new(subJson) } @numInlinedGetByIds = json["numInlinedGetByIds"] @numInlinedPutByIds = json["numInlinedPutByIds"] @numInlinedCalls = json["numInlinedCalls"] @jettisonReason = json["jettisonReason"] @additionalJettisonReason = json["additionalJettisonReason"] @uid = json["uid"] end def codeHashSortKey bytecode.codeHashSortKey + "-" + compilationIndex.to_s end def counter(origin) @counters[origin] end def totalCount sum = 0 @counters.values.each { | value | sum += value.count } sum end def maxCount max = 0 @counters.values.each { | value | max = [max, value.count].max } max end def to_s "#{bytecode}-#{compilationIndex}-#{engine}" end end class DescriptionLine attr_reader :actualCountsString, :sourceCountsString, :disassembly, :shouldShow def initialize(actualCountsString, sourceCountsString, disassembly, shouldShow) @actualCountsString = actualCountsString @sourceCountsString = sourceCountsString @disassembly = disassembly @shouldShow = shouldShow end def codeAddress if @disassembly =~ /^\s*(0x[0-9a-fA-F]+):/ $1.hex else nil end end end class Event attr_reader :time, :bytecode, :compilation, :summary, :detail def initialize(json) @time = json["time"].to_f @bytecode = $bytecodes[json["bytecodesID"].to_i] if json["compilationUID"] @compilation = $compilationMap[json["compilationUID"]] end @summary = json["summary"] @detail = json["detail"] end end def originToPrintStack(origin) (0...(origin.size - 1)).map { | index | "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}" } end def originToString(origin) (originToPrintStack(origin) + ["bc\##{origin[-1].bytecodeIndex}"]).join(" ") end if ARGV.length != 1 $stderr.puts "Usage: display-profiler-output " $stderr.puts $stderr.puts "The typical usage pattern for the profiler currently looks something like:" $stderr.puts $stderr.puts "Path/To/jsc -p profile.json myprogram.js" $stderr.puts "display-profiler-output profile.json" exit 1 end $json = JSON::parse(IO::read(ARGV[0])) $bytecodes = $json["bytecodes"].map { | subJson | Bytecodes.new(subJson) } $compilations = $json["compilations"].map { | subJson | Compilation.new(subJson) } $compilationMap = {} $compilations.each { | compilation | $compilationMap[compilation.uid] = compilation } $events = $json["events"].map { | subJson | Event.new(subJson) } $engines = ["Baseline", "DFG", "FTL", "FTLForOSREntry"] def isOptimizing(engine) engine == "DFG" or engine == "FTL" or engine == "FTLForOSREntry" end $showCounts = true $sortMode = :time def lpad(str, chars) str = str.to_s if str.length > chars str else "%#{chars}s"%(str) end end def rpad(str, chars) str = str.to_s while str.length < chars str += " " end str end def center(str, chars) str = str.to_s while str.length < chars str += " " if str.length < chars str = " " + str end end str end def mayBeHash(hash) hash =~ /#/ or hash.size == 6 end def sourceOnOneLine(source, limit) source.gsub(/\s+/, ' ')[0...limit] end def screenWidth if $stdin.tty? HighLine::SystemExtensions.terminal_size[0] else 200 end end def sortByMode(list) if list.size == 1 return list end case $sortMode when :time list when :hash puts "Will sort output by code hash instead of compilation time." puts "Use 'sort time' to change back to the default." puts list.sort { | a, b | a.codeHashSortKey <=> b.codeHashSortKey } else raise end end def summary(mode, order) remaining = screenWidth # Figure out how many columns we need for the code block names, and for counts maxCount = 0 maxName = 0 $bytecodes.each { | bytecodes | maxCount = ([maxCount] + $engines.map { | engine | bytecodes.maxTopExecutionCount(engine) } + $engines.map { | engine | bytecodes.maxBottomExecutionCount(engine) }).max maxName = [bytecodes.to_s.size, maxName].max } maxCountDigits = maxCount.to_s.size hashCols = [[maxName, 30].min, "CodeBlock".size].max remaining -= hashCols + 1 countCols = [maxCountDigits * $engines.size + ($engines.size - 1), "Source Counts".size].max remaining -= countCols + 1 if mode == :full instructionCountCols = 6 remaining -= instructionCountCols + 1 machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max remaining -= machineCountCols + 1 compilationsCols = 7 remaining -= compilationsCols + 1 inlinesCols = 9 remaining -= inlinesCols + 1 exitCountCols = 7 remaining -= exitCountCols + 1 recentOptsCols = 12 remaining -= recentOptsCols + 1 end if remaining > 0 sourceCols = remaining else sourceCols = nil end print(center("CodeBlock", hashCols)) if mode == :full print(" " + center("#Instr", instructionCountCols)) end print(" " + center("Source Counts", countCols)) if mode == :full print(" " + center("Machine Counts", machineCountCols)) print(" " + center("#Compil", compilationsCols)) print(" " + center("Inlines", inlinesCols)) print(" " + center("#Exits", exitCountCols)) print(" " + center("Last Opts", recentOptsCols)) end if sourceCols print(" " + center("Source", sourceCols)) end puts print(center("", hashCols)) if mode == :full print(" " + (" " * instructionCountCols)) end print(" " + center("Base/DFG/FTL/FTLOSR", countCols)) if mode == :full print(" " + center("Base/DFG/FTL/FTLOSR", machineCountCols)) print(" " + (" " * compilationsCols)) print(" " + center("Src/Total", inlinesCols)) print(" " + (" " * exitCountCols)) print(" " + center("Get/Put/Call", recentOptsCols)) end puts $bytecodes.sort { | a, b | case order when :bytecode b.totalMaxTopExecutionCount <=> a.totalMaxTopExecutionCount when :machine b.totalMaxBottomExecutionCount <=> a.totalMaxBottomExecutionCount else raise end }.each { | bytecode | print(center(bytecode.name(hashCols), hashCols)) if mode == :full print(" " + center(bytecode.instructionCount.to_s, instructionCountCols)) end print(" " + center($engines.map { | engine | bytecode.maxTopExecutionCount(engine).to_s }.join("/"), countCols)) if mode == :full print(" " + center($engines.map { | engine | bytecode.maxBottomExecutionCount(engine).to_s }.join("/"), machineCountCols)) print(" " + center(bytecode.compilations.size.to_s, compilationsCols)) print(" " + center(bytecode.sourceMachineInlineSites.to_s + "/" + bytecode.totalMachineInlineSites.to_s, inlinesCols)) print(" " + center(bytecode.totalExitCount.to_s, exitCountCols)) lastCompilation = bytecode.compilations[-1] if lastCompilation optData = [lastCompilation.numInlinedGetByIds, lastCompilation.numInlinedPutByIds, lastCompilation.numInlinedCalls] else optData = ["N/A"] end print(" " + center(optData.join('/'), recentOptsCols)) end if sourceCols print(" " + sourceOnOneLine(bytecode.source, sourceCols)) end puts } end def queryCompilations(command, args) compilationIndex = nil case args.length when 1 hash = args[0] engine = nil when 2 if mayBeHash(args[0]) hash = args[0] engine = args[1] else engine = args[0] hash = args[1] end else puts "Usage: #{command} " return end if hash and hash =~ /-(-?[0-9]+)-/ hash = $~.pre_match engine = $~.post_match compilationIndex = $1.to_i end if engine and not $engines.index(engine) pattern = Regexp.new(Regexp.escape(engine), "i") trueEngine = nil $engines.each { | myEngine | if myEngine =~ pattern trueEngine = myEngine break end } unless trueEngine puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}." return end engine = trueEngine end if hash == "*" hash = nil end sortByMode($compilations).each { | compilation | next if hash and not compilation.bytecode.matches(hash) next if engine and compilation.engine != engine if compilationIndex if compilationIndex < 0 next unless compilation.bytecode.compilations[compilationIndex] == compilation else next unless compilation.compilationIndex == compilationIndex end end yield compilation } end def executeCommand(*commandArray) command = commandArray[0] args = commandArray[1..-1] case command when "help", "h", "?" puts "summary (s) Print a summary of code block execution rates." puts "full (f) Same as summary, but prints more information." puts "source Show the source for a code block." puts "bytecode (b) Show the bytecode for a code block, with counts." puts "profiling (p) Show the (internal) profiling data for a code block." puts "log (l) List the compilations, exits, and jettisons involving this code block." puts "events (e) List of events involving this code block." puts "display (d) Display details for a code block." puts "inlines Show all inlining stacks that the code block was on." puts "counts Set whether to show counts for 'bytecode' and 'display'." puts "sort Set how to sort compilations before display." puts "help (h) Print this message." puts "quit (q) Quit." when "quit", "q", "exit" exit 0 when "summary", "s" summary(:summary, :bytecode) when "full", "f" if args[0] and (args[0] == "m" or args[0] == "machine") summary(:full, :machine) else summary(:full, :bytecode) end when "source" if args.length != 1 puts "Usage: source " return end $bytecodes.each { | bytecode | if bytecode.matches(args[0]) puts bytecode.source end } when "bytecode", "b" if args.length != 1 puts "Usage: source " return end hash = args[0] countCols = 10 * $engines.size machineCols = 10 * $engines.size pad = 1 while (countCols + 1 + machineCols + pad) % 8 != 0 pad += 1 end sortByMode($bytecodes).each { | bytecodes | next unless bytecodes.matches(hash) if $showCounts puts(center("Source Counts", countCols) + " " + center("Machine Counts", machineCols) + (" " * pad) + center("Bytecode for #{bytecodes}", screenWidth - pad - countCols - 1 - machineCols)) puts(center("Base/DFG/FTL/FTLOSR", countCols) + " " + center("Base/DFG/FTL/FTLOSR", countCols)) else puts("Bytecode for #{bytecodes}:") end bytecodes.each { | bytecode | if $showCounts if bytecode.shouldHaveCounts? countsString = $engines.map { | myEngine | bytecode.topExecutionCount(myEngine) }.join("/") machineString = $engines.map { | myEngine | bytecode.bottomExecutionCount(myEngine) }.join("/") else countsString = "" machineString = "" end print(center(countsString, countCols) + " " + center(machineString, machineCols) + (" " * pad)) end puts(bytecode.description.chomp) bytecode.osrExits.each { | exit | if $showCounts print(center("!!!!!", countCols) + " " + center("!!!!!", machineCols) + (" " * pad)) end print(" " * 10) puts("EXIT: in #{exit.compilation} due to #{exit.exitKind}, #{exit.count} times") } } } when "profiling", "p" if args.length != 1 puts "Usage: profiling " return end hash = args[0] first = true sortByMode($compilations).each { | compilation | compilation.profiledBytecodes.each { | profiledBytecodes | if profiledBytecodes.bytecodes.matches(hash) if first first = false else puts end puts "Compilation #{compilation}:" profiledBytecodes.header.each { | header | puts(" " * 6 + header) } profiledBytecodes.each { | bytecode | puts(" " * 8 + bytecode.description) profiledBytecodes.bytecodes.bytecode(bytecode.bytecodeIndex).osrExits.each { | exit | if exit.compilation == compilation puts(" !!!!! EXIT: due to #{exit.exitKind}, #{exit.count} times") end } } end } } when "log", "l" queryCompilations("log", args) { | compilation | puts "Compilation #{compilation}:" puts " Total count: #{compilation.totalCount} Max count: #{compilation.maxCount}" compilation.osrExits.values.each { | exits | exits.each { | exit | puts " EXIT: at #{originToString(exit.origin)} due to #{exit.exitKind}, #{exit.count} times" } } if compilation.jettisonReason != "NotJettisoned" puts " Jettisoned due to #{compilation.jettisonReason}" if compilation.additionalJettisonReason puts " #{compilation.additionalJettisonReason}" end end } when "events", "e" if args.length != 1 puts "Usage: inlines " return end hash = Regexp.new(Regexp.escape(args[0])) events = [] $events.each { | event | if event.bytecode.to_s =~ hash events << event end } timeCols = 0 hashCols = 0 compilationCols = 0 summaryCols = 0 events.each { | event | timeCols = [event.time.to_s.size, timeCols].max hashCols = [event.bytecode.to_s.size, hashCols].max if event.compilation compilationCols = [event.compilation.to_s.size, compilationCols].max end summaryCols = [event.summary.size, summaryCols].max } events.each { | event | print rpad(event.time.to_s, timeCols) print " " print rpad(event.bytecode.to_s, hashCols) print " " compilationStr = "" if event.compilation compilationStr = event.compilation.to_s end print rpad(compilationStr, compilationCols) print " " print rpad(event.summary, summaryCols) print " " puts event.detail } when "inlines" if args.length != 1 puts "Usage: inlines " return end hash = args[0] sortByMode($bytecodes).each { | bytecodes | next unless bytecodes.matches(hash) # FIXME: print something useful to say more about which code block this is. $compilations.each { | compilation | myOrigins = [] compilation.descriptions.each { | description | if description.origin.index { | myBytecode | bytecodes == myBytecode.bytecodes } myOrigins << description.origin end } myOrigins.uniq! myOrigins.sort! { | a, b | result = 0 [a.size, b.size].min.times { | index | result = a[index].bytecodeIndex <=> b[index].bytecodeIndex break if result != 0 } result } next if myOrigins.empty? printArray = [] lastPrintStack = [] def printStack(printArray, stack, lastStack) stillCommon = true stack.each_with_index { | entry, index | next if stillCommon and entry == lastStack[index] printArray << (" " * (index + 1) + entry) stillCommon = false } end myOrigins.each { | origin | currentPrintStack = originToPrintStack(origin) printStack(printArray, currentPrintStack, lastPrintStack) lastPrintStack = currentPrintStack } next if printArray.empty? puts "Compilation #{compilation}:" printArray.each { | entry | puts entry } } } when "display", "d" actualCountCols = 13 sourceCountCols = 10 * $engines.size first = true queryCompilations("display", args) { | compilation | if first first = false else puts end puts("Compilation #{compilation}:") if $showCounts puts(center("Actual Counts", actualCountCols) + " " + center("Source Counts", sourceCountCols) + " " + center("Disassembly in #{compilation.engine}", screenWidth - 1 - sourceCountCols - 1 - actualCountCols)) puts((" " * actualCountCols) + " " + center("Base/DFG/FTL/FTLOSR", sourceCountCols)) else puts("Disassembly in #{compilation.engine}") end lines = [] compilation.descriptions.each { | description | # FIXME: We should have a better way of detecting things like CountExecution nodes # and slow path entries in the baseline JIT. if description.description =~ /CountExecution\(/ and isOptimizing(compilation.engine) shouldShow = false else shouldShow = true end if not description.origin.empty? and not description.origin[-1] p description.origin p description.description end if description.origin.empty? or not description.origin[-1].shouldHaveCounts? or (compilation.engine == "Baseline" and description.description =~ /^\s*\(S\)/) actualCountsString = "" sourceCountsString = "" else actualCountsString = compilation.counter(description.origin).count.to_s sourceCountsString = $engines.map { | myEngine | description.origin[-1].topExecutionCount(myEngine) }.join("/") end description.description.split("\n").each { | line | lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow) } } exitPrefix = "" if $showCounts exitPrefix += center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 15) else exitPrefix += " !!!!!" end exitPrefix += " " * 10 lines.each_with_index { | line, index | codeAddress = line.codeAddress if codeAddress list = compilation.osrExits[codeAddress] if list list.each { | exit | if exit.isWatchpoint exit.dumpForDisplay(exitPrefix) end } end end if line.shouldShow if $showCounts print(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " ") end puts(line.disassembly) end if codeAddress # Find the next disassembly address. endIndex = index + 1 endAddress = nil while endIndex < lines.size myAddress = lines[endIndex].codeAddress if myAddress endAddress = myAddress break end endIndex += 1 end if endAddress list = compilation.osrExits[endAddress] if list list.each { | exit | unless exit.isWatchpoint exit.dumpForDisplay(exitPrefix) end } end end end } } when "counts" if args.length != 1 puts "Usage: counts on|off|toggle" else case args[0].downcase when 'on' $showCounts = true when 'off' $showCounts = false when 'toggle' $showCounts = !$showCounts else puts "Usage: counts on|off|toggle" end end puts "Current value: #{$showCounts ? 'on' : 'off'}" when "sort" if args.length != 1 puts "Usage: sort time|hash" puts puts "sort time: Sorts by the timestamp of when the code was compiled." puts " This is the default." puts puts "sort hash: Sorts by the code hash. This is more deterministic," puts " and is useful for diffs." puts else case args[0].downcase when 'time' $sortMode = :time when 'hash' $sortMode = :hash else puts "Usage: sort time|hash" end end puts "Current value: #{$sortMode}" else puts "Invalid command: #{command}" end end if $stdin.tty? executeCommand("full") end while commandLine = Readline.readline("> ", true) executeCommand(*commandLine.split) end