blob: 100544dcf7f26d61ca33e818fe6d5202b8efda5e [file] [log] [blame]
#!/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 <path to profiler output file>"
$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?
begin
HighLine::SystemExtensions.terminal_size[0] - 3
rescue
HighLine::default_instance.terminal.terminal_size[0] - 3
end
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
when :exits
b.totalExitCount <=> a.totalExitCount
when :compiles
b.compilations.size <=> a.compilations.size
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} <code block hash> <engine>"
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)
elsif args[0] and (args[0] == "e" or args[0] == "exits")
summary(:full, :exits)
elsif args[0] and (args[0] == "c" or args[0] == "compiles")
summary(:full, :compiles)
else
summary(:full, :bytecode)
end
when "source"
if args.length != 1
puts "Usage: source <code block hash>"
return
end
$bytecodes.each {
| bytecode |
if bytecode.matches(args[0])
puts bytecode.source
end
}
when "bytecode", "b"
if args.length != 1
puts "Usage: source <code block hash>"
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 <code block hash>"
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 <code block hash>"
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 <code block hash>"
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