Home | History | Annotate | Download | only in sodium
      1 // Copyright 2013 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 var Sodium = (function() {
     29   "use strict";
     30 
     31   var kinds = ["FUNCTION", "OPTIMIZED_FUNCTION", "STUB", "BUILTIN",
     32                "LOAD_IC", "KEYED_LOAD_IC", "CALL_IC", "KEYED_CALL_IC",
     33                "STORE_IC", "KEYED_STORE_IC", "BINARY_OP_IC", "COMPARE_IC",
     34                "COMPARE_NIL_IC", "TO_BOOLEAN_IC"];
     35   var kindsWithSource = {
     36     'FUNCTION': true,
     37     'OPTIMIZED_FUNCTION': true
     38   };
     39 
     40   var addressRegEx = "0x[0-9a-f]{8,16}";
     41   var nameFinder = new RegExp("^name = (.+)$");
     42   var kindFinder = new RegExp("^kind = (.+)$");
     43   var firstPositionFinder = new RegExp("^source_position = (\\d+)$");
     44   var separatorFilter = new RegExp("^--- (.)+ ---$");
     45   var rawSourceFilter = new RegExp("^--- Raw source ---$");
     46   var codeEndFinder = new RegExp("^--- End code ---$");
     47   var whiteSpaceLineFinder = new RegExp("^\\W*$");
     48   var instructionBeginFinder =
     49     new RegExp("^Instructions\\W+\\(size = \\d+\\)");
     50   var instructionFinder =
     51     new RegExp("^\(" + addressRegEx + "\)\(\\W+\\d+\\W+.+\)");
     52   var positionFinder =
     53     new RegExp("^(" + addressRegEx + ")\\W+position\\W+\\((\\d+)\\)");
     54   var addressFinder = new RegExp("\(" + addressRegEx + "\)");
     55   var addressReplacer = new RegExp("\(" + addressRegEx + "\)", "gi");
     56 
     57   var fileContent = "";
     58   var selectedFunctionKind = "";
     59   var currentFunctionKind = "";
     60 
     61   var currentFunctionName = "";
     62   var firstSourcePosition = 0;
     63   var startAddress = "";
     64   var readingSource = false;
     65   var readingAsm = false;
     66   var sourceBegin = -1;
     67   var sourceEnd = -1;
     68   var asmBegin = -1;
     69   var asmEnd = -1;
     70   var codeObjects = [];
     71   var selectedAsm = null;
     72   var selectedSource = null;
     73   var selectedSourceClass = "";
     74 
     75   function Code(name, kind, sourceBegin, sourceEnd, asmBegin, asmEnd,
     76                 firstSourcePosition, startAddress) {
     77     this.name = name;
     78     this.kind = kind;
     79     this.sourceBegin = sourceBegin;
     80     this.sourceEnd = sourceEnd;
     81     this.asmBegin = asmBegin;
     82     this.asmEnd = asmEnd;
     83     this.firstSourcePosition = firstSourcePosition;
     84     this.startAddress = startAddress;
     85   }
     86 
     87   function getCurrentCodeObject() {
     88     var functionSelect = document.getElementById('function-selector-id');
     89     return functionSelect.options[functionSelect.selectedIndex].codeObject;
     90   }
     91 
     92   function getCurrentSourceText() {
     93     var code = getCurrentCodeObject();
     94     if (code.sourceBegin == -1 || code.sourceEnd == -1) return "";
     95     return fileContent.substring(code.sourceBegin, code.sourceEnd);
     96   }
     97 
     98   function getCurrentAsmText() {
     99     var code = getCurrentCodeObject();
    100     if (code.asmBegin == -1 || code.asmEnd == -1) return "";
    101     return fileContent.substring(code.asmBegin, code.asmEnd);
    102   }
    103 
    104   function setKindByIndex(index) {
    105     selectedFunctionKind = kinds[index];
    106   }
    107 
    108   function processLine(text, begin, end) {
    109     var line = text.substring(begin, end);
    110     if (readingSource) {
    111       if (separatorFilter.exec(line) != null) {
    112         readingSource = false;
    113       } else {
    114         if (sourceBegin == -1) {
    115           sourceBegin = begin;
    116         }
    117         sourceEnd = end;
    118       }
    119     } else {
    120       if (readingAsm) {
    121         if (codeEndFinder.exec(line) != null) {
    122           readingAsm = false;
    123           asmEnd = begin;
    124           var newCode =
    125             new Code(currentFunctionName, currentFunctionKind,
    126                      sourceBegin, sourceEnd, asmBegin, asmEnd,
    127                      firstSourcePosition, startAddress);
    128           codeObjects.push(newCode);
    129           currentFunctionKind = null;
    130         } else {
    131           if (asmBegin == -1) {
    132             matches = instructionBeginFinder.exec(line);
    133             if (matches != null) {
    134               asmBegin = begin;
    135             }
    136           }
    137           if (startAddress == "") {
    138             matches = instructionFinder.exec(line);
    139             if (matches != null) {
    140               startAddress = matches[1];
    141             }
    142           }
    143         }
    144       } else {
    145         var matches = kindFinder.exec(line);
    146         if (matches != null) {
    147           currentFunctionKind = matches[1];
    148           if (!kindsWithSource[currentFunctionKind]) {
    149             sourceBegin = -1;
    150             sourceEnd = -1;
    151           }
    152         } else if (currentFunctionKind != null) {
    153           matches = nameFinder.exec(line);
    154           if (matches != null) {
    155             readingAsm = true;
    156             asmBegin = -1;
    157             currentFunctionName = matches[1];
    158           }
    159         } else if (rawSourceFilter.exec(line) != null) {
    160           readingSource = true;
    161           sourceBegin = -1;
    162         } else {
    163           var matches = firstPositionFinder.exec(line);
    164           if (matches != null) {
    165             firstSourcePosition = parseInt(matches[1]);
    166           }
    167         }
    168       }
    169     }
    170   }
    171 
    172   function processLines(source, size, processLine) {
    173     var firstChar = 0;
    174     for (var x = 0; x < size; x++) {
    175       var curChar = source[x];
    176       if (curChar == '\n' || curChar == '\r') {
    177         processLine(source, firstChar, x);
    178         firstChar = x + 1;
    179       }
    180     }
    181     if (firstChar != size - 1) {
    182       processLine(source, firstChar, size - 1);
    183     }
    184   }
    185 
    186   function processFileContent() {
    187     document.getElementById('source-text-pre').innerHTML = '';
    188     sourceBegin = -1;
    189     codeObjects = [];
    190     processLines(fileContent, fileContent.length, processLine);
    191     var functionSelectElement = document.getElementById('function-selector-id');
    192     functionSelectElement.innerHTML = '';
    193     var length = codeObjects.length;
    194     for (var i = 0; i < codeObjects.length; ++i) {
    195       var code = codeObjects[i];
    196       if (code.kind == selectedFunctionKind) {
    197         var optionElement = document.createElement("option");
    198         optionElement.codeObject = code;
    199         optionElement.text = code.name;
    200         functionSelectElement.add(optionElement, null);
    201       }
    202     }
    203   }
    204 
    205   function asmClick(element) {
    206     if (element == selectedAsm) return;
    207     if (selectedAsm != null) {
    208       selectedAsm.classList.remove('highlight-yellow');
    209     }
    210     selectedAsm = element;
    211     selectedAsm.classList.add('highlight-yellow');
    212 
    213     var pc = element.firstChild.innerText;
    214     var sourceLine = null;
    215     if (addressFinder.exec(pc) != null) {
    216       var position = findSourcePosition(pc);
    217       var line = findSourceLine(position);
    218       sourceLine = document.getElementById('source-line-' + line);
    219       var sourceLineTop = sourceLine.offsetTop;
    220       makeSourcePosVisible(sourceLineTop);
    221     }
    222     if (selectedSource == sourceLine) return;
    223     if (selectedSource != null) {
    224       selectedSource.classList.remove('highlight-yellow');
    225       selectedSource.classList.add(selectedSourceClass);
    226     }
    227     if (sourceLine != null) {
    228       selectedSourceClass = sourceLine.classList[0];
    229       sourceLine.classList.remove(selectedSourceClass);
    230       sourceLine.classList.add('highlight-yellow');
    231     }
    232     selectedSource = sourceLine;
    233   }
    234 
    235   function makeContainerPosVisible(container, newTop) {
    236     var height = container.offsetHeight;
    237     var margin = Math.floor(height / 4);
    238     if (newTop < container.scrollTop + margin) {
    239       newTop -= margin;
    240       if (newTop < 0) newTop = 0;
    241       container.scrollTop = newTop;
    242       return;
    243     }
    244     if (newTop > (container.scrollTop + 3 * margin)) {
    245       newTop = newTop - 3 * margin;
    246       container.scrollTop = newTop;
    247     }
    248   }
    249 
    250   function makeAsmPosVisible(newTop) {
    251     var asmContainer = document.getElementById('asm-container');
    252     makeContainerPosVisible(asmContainer, newTop);
    253   }
    254 
    255   function makeSourcePosVisible(newTop) {
    256     var sourceContainer = document.getElementById('source-container');
    257     makeContainerPosVisible(sourceContainer, newTop);
    258   }
    259 
    260   function addressClick(element, event) {
    261     event.stopPropagation();
    262     var asmLineId = 'address-' + element.innerText;
    263     var asmLineElement = document.getElementById(asmLineId);
    264     if (asmLineElement != null) {
    265       var asmLineTop = asmLineElement.parentNode.offsetTop;
    266       makeAsmPosVisible(asmLineTop);
    267       asmLineElement.classList.add('highlight-flash-blue');
    268       window.setTimeout(function() {
    269         asmLineElement.classList.remove('highlight-flash-blue');
    270       }, 1500);
    271     }
    272   }
    273 
    274   function prepareAsm(originalSource) {
    275     var newSource = "";
    276     var lineNumber = 1;
    277     var functionProcessLine = function(text, begin, end) {
    278       var currentLine = text.substring(begin, end);
    279       var matches = instructionFinder.exec(currentLine);
    280       var clickHandler = "";
    281       if (matches != null) {
    282         var restOfLine = matches[2];
    283         restOfLine = restOfLine.replace(
    284           addressReplacer,
    285           '<span class="hover-underline" ' +
    286             'onclick="Sodium.addressClick(this, event);">\$1</span>');
    287         currentLine = '<span id="address-' + matches[1] + '" >' +
    288           matches[1] + '</span>' + restOfLine;
    289         clickHandler = 'onclick=\'Sodium.asmClick(this)\' ';
    290       } else if (whiteSpaceLineFinder.exec(currentLine)) {
    291         currentLine = "<br>";
    292       }
    293       newSource += '<pre style=\'margin-bottom: -12px;\' ' + clickHandler + '>' +
    294         currentLine + '</pre>';
    295       lineNumber++;
    296     }
    297     processLines(originalSource, originalSource.length, functionProcessLine);
    298     return newSource;
    299   }
    300 
    301   function findSourcePosition(pcToSearch) {
    302     var position = 0;
    303     var distance = 0x7FFFFFFF;
    304     var pcToSearchOffset = parseInt(pcToSearch);
    305     var processOneLine = function(text, begin, end) {
    306       var currentLine = text.substring(begin, end);
    307       var matches = positionFinder.exec(currentLine);
    308       if (matches != null) {
    309         var pcOffset = parseInt(matches[1]);
    310         if (pcOffset <= pcToSearchOffset) {
    311           var dist =  pcToSearchOffset - pcOffset;
    312           var pos = parseInt(matches[2]);
    313           if ((dist < distance) || (dist == distance && pos > position)) {
    314             position = pos;
    315             distance = dist;
    316           }
    317         }
    318       }
    319     }
    320     var asmText = getCurrentAsmText();
    321     processLines(asmText, asmText.length, processOneLine);
    322     var code = getCurrentCodeObject();
    323     if (position == 0) return 0;
    324     return position - code.firstSourcePosition;
    325   }
    326 
    327   function findSourceLine(position) {
    328     if (position == 0) return 1;
    329     var line = 0;
    330     var processOneLine = function(text, begin, end) {
    331       if (begin < position) {
    332         line++;
    333       }
    334     }
    335     var sourceText = getCurrentSourceText();
    336     processLines(sourceText, sourceText.length, processOneLine);
    337     return line;
    338   }
    339 
    340   function functionChangedHandler() {
    341     var functionSelect = document.getElementById('function-selector-id');
    342     var source = getCurrentSourceText();
    343     var sourceDivElement = document.getElementById('source-text');
    344     var code = getCurrentCodeObject();
    345     var newHtml = "<pre class=\"prettyprint linenums\" id=\"source-text\">"
    346       + 'function ' + code.name + source + "</pre>";
    347     sourceDivElement.innerHTML = newHtml;
    348     try {
    349       // Wrap in try to work when offline.
    350       PR.prettyPrint();
    351     } catch (e) {
    352     }
    353     var sourceLineContainer = sourceDivElement.firstChild.firstChild;
    354     var lineCount = sourceLineContainer.childElementCount;
    355     var current = sourceLineContainer.firstChild;
    356     for (var i = 1; i < lineCount; ++i) {
    357       current.id = "source-line-" + i;
    358       current = current.nextElementSibling;
    359     }
    360 
    361     var asm = getCurrentAsmText();
    362     document.getElementById('asm-text').innerHTML = prepareAsm(asm);
    363   }
    364 
    365   function kindChangedHandler(element) {
    366     setKindByIndex(element.selectedIndex);
    367     processFileContent();
    368     functionChangedHandler();
    369   }
    370 
    371   function readLog(evt) {
    372     //Retrieve the first (and only!) File from the FileList object
    373     var f = evt.target.files[0];
    374     if (f) {
    375       var r = new FileReader();
    376       r.onload = function(e) {
    377         var file = evt.target.files[0];
    378         currentFunctionKind = "";
    379         fileContent = e.target.result;
    380         processFileContent();
    381         functionChangedHandler();
    382       }
    383       r.readAsText(f);
    384     } else {
    385       alert("Failed to load file");
    386     }
    387   }
    388 
    389   function buildFunctionKindSelector(kindSelectElement) {
    390     for (var x = 0; x < kinds.length; ++x) {
    391       var optionElement = document.createElement("option");
    392       optionElement.value = x;
    393       optionElement.text = kinds[x];
    394       kindSelectElement.add(optionElement, null);
    395     }
    396     kindSelectElement.selectedIndex = 1;
    397     setKindByIndex(1);
    398   }
    399 
    400   return {
    401     buildFunctionKindSelector: buildFunctionKindSelector,
    402     kindChangedHandler: kindChangedHandler,
    403     functionChangedHandler: functionChangedHandler,
    404     asmClick: asmClick,
    405     addressClick: addressClick,
    406     readLog: readLog
    407   };
    408 
    409 })();
    410