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
26require 'rubygems'
27
28require 'readline'
29
30begin
31    require 'json'
32    require 'highline'
33rescue 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
41end
42
43class 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
133end
134
135class 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
253end
254
255class 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
262end
263
264class 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
282end
283
284def originStackFromJSON(json)
285    json.map {
286        | subJson |
287        $bytecodes[subJson["bytecodesID"].to_i].bytecode(subJson["bytecodeIndex"].to_i)
288    }
289end
290
291class CompiledBytecode
292    attr_accessor :origin, :description
293
294    def initialize(json)
295        @origin = originStackFromJSON(json["origin"])
296        @description = json["description"].to_s
297    end
298end
299
300class 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
308end
309
310class 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
325end
326
327class 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
392end
393
394class 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
411end
412
413if 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
421end
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
434def lpad(str,chars)
435  if str.length>chars
436    str
437  else
438    "%#{chars}s"%(str)
439  end
440end
441
442def rpad(str, chars)
443    while str.length < chars
444        str += " "
445    end
446    str
447end
448
449def center(str, chars)
450    while str.length < chars
451        str += " "
452        if str.length < chars
453            str = " " + str
454        end
455    end
456    str
457end
458
459def mayBeHash(hash)
460    hash =~ /#/ or hash.size == 6
461end
462
463def sourceOnOneLine(source, limit)
464    source.gsub(/\s+/, ' ')[0...limit]
465end
466
467def screenWidth
468    if $stdin.tty?
469        HighLine::SystemExtensions.terminal_size[0]
470    else
471        200
472    end
473end
474
475def 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    }
593end
594
595def 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
929end
930
931if $stdin.tty?
932    executeCommand("full")
933end
934
935while commandLine = Readline.readline("> ", true)
936    executeCommand(*commandLine.split)
937end
938
939