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