Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Tools / Scripts / display-profiler-output
blob0a8c15e9c69fc799cb32e0547612134fe5382a08
1 #!/usr/bin/env ruby
3 # Copyright (C) 2012, 2013 Apple Inc. All rights reserved.
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.
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.
26 require 'rubygems'
28 require 'readline'
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
43 class Bytecode
44 attr_accessor :bytecodes, :bytecodeIndex, :opcode, :description, :topCounts, :bottomCounts, :machineInlinees, :osrExits
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
57 def shouldHaveCounts?
58 @opcode != "op_call_put_result"
59 end
61 def addTopCount(count)
62 @topCounts << count
63 end
65 def addBottomCountForCompilation(count, compilation)
66 @bottomCounts[compilation] = [] unless @bottomCounts[compilation]
67 @bottomCounts[compilation] << count
68 end
70 def addMachineInlinee(compilation, inlinee)
71 @machineInlinees[compilation] = {} unless @machineInlinees[compilation]
72 @machineInlinees[compilation][inlinee] = true
73 end
75 def totalTopExecutionCount
76 sum = 0
77 @topCounts.each {
78 | value |
79 sum += value.count
81 sum
82 end
84 def topExecutionCount(engine)
85 sum = 0
86 @topCounts.each {
87 | value |
88 if value.engine == engine
89 sum += value.count
90 end
92 sum
93 end
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
104 sum += max
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
119 sum += max
125 def totalExitCount
126 sum = 0
127 @osrExits.each {
128 | exit |
129 sum += exit.count
135 class Bytecodes
136 attr_accessor :codeHash, :inferredName, :source, :instructionCount, :machineInlineSites, :compilations
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)
149 @machineInlineSites = {} # maps compilation to a set of origins
150 @compilations = []
153 def name(limit)
154 if to_s.size > limit
155 "\##{@codeHash}"
156 else
157 to_s
161 def to_s
162 "#{@inferredName}\##{@codeHash}"
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
175 def each
176 @bytecode.values.sort{|a, b| a.bytecodeIndex <=> b.bytecodeIndex}.each {
177 | value |
178 yield value
182 def bytecode(bytecodeIndex)
183 @bytecode[bytecodeIndex]
186 def addMachineInlineSite(compilation, origin)
187 @machineInlineSites[compilation] = {} unless @machineInlineSites[compilation]
188 @machineInlineSites[compilation][origin] = true
191 def totalMachineInlineSites
192 sum = 0
193 @machineInlineSites.each_value {
194 | set |
195 sum += set.size
200 def sourceMachineInlineSites
201 set = {}
202 @machineInlineSites.each_value {
203 | mySet |
204 set.merge!(mySet)
206 set.size
209 def totalMaxTopExecutionCount
210 max = 0
211 @bytecode.each_value {
212 | bytecode |
213 max = [max, bytecode.totalTopExecutionCount].max
218 def maxTopExecutionCount(engine)
219 max = 0
220 @bytecode.each_value {
221 | bytecode |
222 max = [max, bytecode.topExecutionCount(engine)].max
227 def totalMaxBottomExecutionCount
228 max = 0
229 @bytecode.each_value {
230 | bytecode |
231 max = [max, bytecode.totalBottomExecutionCount].max
236 def maxBottomExecutionCount(engine)
237 max = 0
238 @bytecode.each_value {
239 | bytecode |
240 max = [max, bytecode.bottomExecutionCount(engine)].max
245 def totalExitCount
246 sum = 0
247 each {
248 | bytecode |
249 sum += bytecode.totalExitCount
255 class ProfiledBytecode
256 attr_reader :bytecodeIndex, :description
258 def initialize(json)
259 @bytecodeIndex = json["bytecodeIndex"].to_i
260 @description = json["description"].to_s
264 class ProfiledBytecodes
265 attr_reader :header, :bytecodes
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)
276 def each
277 @sequence.each {
278 | description |
279 yield description
284 def originStackFromJSON(json)
285 json.map {
286 | subJson |
287 $bytecodes[subJson["bytecodesID"].to_i].bytecode(subJson["bytecodeIndex"].to_i)
291 class CompiledBytecode
292 attr_accessor :origin, :description
294 def initialize(json)
295 @origin = originStackFromJSON(json["origin"])
296 @description = json["description"].to_s
300 class ExecutionCounter
301 attr_accessor :origin, :engine, :count
303 def initialize(origin, engine, count)
304 @origin = origin
305 @engine = engine
306 @count = count
310 class OSRExit
311 attr_reader :compilation, :origin, :codeAddresses, :exitKind, :isWatchpoint, :count
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
322 def dumpForDisplay(prefix)
323 puts(prefix + "EXIT: due to #{@exitKind}, #{@count} times")
327 class Compilation
328 attr_accessor :bytecode, :engine, :descriptions, :counters, :compilationIndex
329 attr_accessor :osrExits, :profiledBytecodes, :numInlinedGetByIds, :numInlinedPutByIds
330 attr_accessor :numInlinedCalls
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)
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])
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)
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
373 osrExit.origin[-1].osrExits << osrExit
375 @profiledBytecodes = []
376 json["profiledBytecodes"].each {
377 | subJson |
378 @profiledBytecodes << ProfiledBytecodes.new(subJson)
380 @numInlinedGetByIds = json["numInlinedGetByIds"]
381 @numInlinedPutByIds = json["numInlinedPutByIds"]
382 @numInlinedCalls = json["numInlinedCalls"]
385 def counter(origin)
386 @counters[origin]
389 def to_s
390 "#{bytecode}-#{compilationIndex}-#{engine}"
394 class DescriptionLine
395 attr_reader :actualCountsString, :sourceCountsString, :disassembly, :shouldShow
397 def initialize(actualCountsString, sourceCountsString, disassembly, shouldShow)
398 @actualCountsString = actualCountsString
399 @sourceCountsString = sourceCountsString
400 @disassembly = disassembly
401 @shouldShow = shouldShow
404 def codeAddress
405 if @disassembly =~ /^\s*(0x[0-9a-fA-F]+):/
406 $1.hex
407 else
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
423 $json = JSON::parse(IO::read(ARGV[0]))
424 $bytecodes = $json["bytecodes"].map {
425 | subJson |
426 Bytecodes.new(subJson)
428 $compilations = $json["compilations"].map {
429 | subJson |
430 Compilation.new(subJson)
432 $engines = ["Baseline", "DFG"]
434 def lpad(str,chars)
435 if str.length>chars
437 else
438 "%#{chars}s"%(str)
442 def rpad(str, chars)
443 while str.length < chars
444 str += " "
449 def center(str, chars)
450 while str.length < chars
451 str += " "
452 if str.length < chars
453 str = " " + str
459 def mayBeHash(hash)
460 hash =~ /#/ or hash.size == 6
463 def sourceOnOneLine(source, limit)
464 source.gsub(/\s+/, ' ')[0...limit]
467 def screenWidth
468 if $stdin.tty?
469 HighLine::SystemExtensions.terminal_size[0]
470 else
475 def summary(mode)
476 remaining = screenWidth
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
492 maxCountDigits = maxCount.to_s.size
494 hashCols = [[maxName, 30].min, "CodeBlock".size].max
495 remaining -= hashCols + 1
497 countCols = [maxCountDigits * $engines.size, "Source Counts".size].max
498 remaining -= countCols + 1
500 if mode == :full
501 instructionCountCols = 6
502 remaining -= instructionCountCols + 1
504 machineCountCols = [maxCountDigits * $engines.size, "Machine Counts".size].max
505 remaining -= machineCountCols + 1
507 compilationsCols = 7
508 remaining -= compilationsCols + 1
510 inlinesCols = 9
511 remaining -= inlinesCols + 1
513 exitCountCols = 7
514 remaining -= exitCountCols + 1
516 recentOptsCols = 12
517 remaining -= recentOptsCols + 1
520 if remaining > 0
521 sourceCols = remaining
522 else
523 sourceCols = nil
526 print(center("CodeBlock", hashCols))
527 if mode == :full
528 print(" " + center("#Instr", instructionCountCols))
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))
538 if sourceCols
539 print(" " + center("Source", sourceCols))
541 puts
543 print(center("", hashCols))
544 if mode == :full
545 print(" " + (" " * instructionCountCols))
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))
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))
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"]
586 print(" " + center(optData.join('/'), recentOptsCols))
588 if sourceCols
589 print(" " + sourceOnOneLine(bytecode.source, sourceCols))
591 puts
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
620 $bytecodes.each {
621 | bytecode |
622 if bytecode.matches(args[0])
623 puts bytecode.source
626 when "bytecode", "b"
627 if args.length != 1
628 puts "Usage: source <code block hash>"
629 return
632 hash = args[0]
634 countCols = 10 * $engines.size
635 machineCols = 10 * $engines.size
636 pad = 1
637 while (countCols + 1 + machineCols + pad) % 8 != 0
638 pad += 1
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 = ""
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")
670 when "profiling", "p"
671 if args.length != 1
672 puts "Usage: profiling <code block hash>"
673 return
676 hash = args[0]
678 first = true
679 $compilations.each {
680 | compilation |
682 compilation.profiledBytecodes.each {
683 | profiledBytecodes |
684 if profiledBytecodes.bytecodes.matches(hash)
685 if first
686 first = false
687 else
688 puts
691 puts "Compilation #{compilation}:"
692 profiledBytecodes.header.each {
693 | header |
694 puts(" " * 6 + header)
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")
709 when "inlines"
710 if args.length != 1
711 puts "Usage: inlines <code block hash>"
712 return
715 hash = args[0]
717 $bytecodes.each {
718 | bytecodes |
719 next unless bytecodes.matches(hash)
721 # FIXME: print something useful to say more about which code block this is.
723 $compilations.each {
724 | compilation |
725 myOrigins = []
726 compilation.descriptions.each {
727 | description |
728 if description.origin.index {
729 | myBytecode |
730 bytecodes == myBytecode.bytecodes
732 myOrigins << description.origin
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
744 result
747 next if myOrigins.empty?
749 printArray = []
750 lastPrintStack = []
752 def originToPrintStack(origin)
753 (0...(origin.size - 1)).map {
754 | index |
755 "bc\##{origin[index].bytecodeIndex} --> #{origin[index + 1].bytecodes}"
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
769 myOrigins.each {
770 | origin |
771 currentPrintStack = originToPrintStack(origin)
772 printStack(printArray, currentPrintStack, lastPrintStack)
773 lastPrintStack = currentPrintStack
776 next if printArray.empty?
778 puts "Compilation #{compilation}:"
779 printArray.each {
780 | entry |
781 puts entry
785 when "display", "d"
786 compilationIndex = nil
788 case args.length
789 when 1
790 if args[0] == "*"
791 hash = nil
792 else
793 hash = args[0]
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]
804 else
805 puts "Usage: summary <code block hash> <engine>"
806 return
809 if hash and hash =~ /-([0-9]+)-/
810 hash = $~.pre_match
811 engine = $~.post_match
812 compilationIndex = $1.to_i
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
825 unless trueEngine
826 puts "#{engine} is not a valid engine, try #{$engines.join(' or ')}."
827 return
829 engine = trueEngine
832 actualCountCols = 13
833 sourceCountCols = 10 * $engines.size
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
842 if first
843 first = false
844 else
845 puts
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))
853 lines = []
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
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("/")
874 description.description.split("\n").each {
875 | line |
876 lines << DescriptionLine.new(actualCountsString, sourceCountsString, line.chomp, shouldShow)
880 exitPrefix = center("!!!!!", actualCountCols) + " " + center("!!!!!", sourceCountCols) + (" " * 25)
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)
896 if line.shouldShow
897 puts(center(line.actualCountsString, actualCountCols) + " " + center(line.sourceCountsString, sourceCountCols) + " " + line.disassembly)
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
909 endIndex += 1
912 if endAddress
913 list = compilation.osrExits[endAddress]
914 if list
915 list.each {
916 | exit |
917 unless exit.isWatchpoint
918 exit.dumpForDisplay(exitPrefix)
926 else
927 puts "Invalid command: #{command}"
931 if $stdin.tty?
932 executeCommand("full")
935 while commandLine = Readline.readline("> ", true)
936 executeCommand(*commandLine.split)