blob: 44475a177f77151e6fddf78438ce03d899e71cc4 [file] [log] [blame]
// Copyright 2013 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
var Sodium = (function() {
"use strict";
var kinds = ["FUNCTION", "OPTIMIZED_FUNCTION", "STUB", "BUILTIN",
"LOAD_IC", "KEYED_LOAD_IC", "CALL_IC", "KEYED_CALL_IC",
"STORE_IC", "KEYED_STORE_IC", "BINARY_OP_IC", "COMPARE_IC",
"COMPARE_NIL_IC", "TO_BOOLEAN_IC"];
var kindsWithSource = {
'FUNCTION': true,
'OPTIMIZED_FUNCTION': true
};
var addressRegEx = "0x[0-9a-f]{8,16}";
var nameFinder = new RegExp("^name = (.+)$");
var kindFinder = new RegExp("^kind = (.+)$");
var firstPositionFinder = new RegExp("^source_position = (\\d+)$");
var separatorFilter = new RegExp("^--- (.)+ ---$");
var rawSourceFilter = new RegExp("^--- Raw source ---$");
var codeEndFinder = new RegExp("^--- End code ---$");
var whiteSpaceLineFinder = new RegExp("^\\W*$");
var instructionBeginFinder =
new RegExp("^Instructions\\W+\\(size = \\d+\\)");
var instructionFinder =
new RegExp("^\(" + addressRegEx + "\)\(\\W+\\d+\\W+.+\)");
var positionFinder =
new RegExp("^(" + addressRegEx + ")\\W+position\\W+\\((\\d+)\\)");
var addressFinder = new RegExp("\(" + addressRegEx + "\)");
var addressReplacer = new RegExp("\(" + addressRegEx + "\)", "gi");
var fileContent = "";
var selectedFunctionKind = "";
var currentFunctionKind = "";
var currentFunctionName = "";
var firstSourcePosition = 0;
var startAddress = "";
var readingSource = false;
var readingAsm = false;
var sourceBegin = -1;
var sourceEnd = -1;
var asmBegin = -1;
var asmEnd = -1;
var codeObjects = [];
var selectedAsm = null;
var selectedSource = null;
var selectedSourceClass = "";
function Code(name, kind, sourceBegin, sourceEnd, asmBegin, asmEnd,
firstSourcePosition, startAddress) {
this.name = name;
this.kind = kind;
this.sourceBegin = sourceBegin;
this.sourceEnd = sourceEnd;
this.asmBegin = asmBegin;
this.asmEnd = asmEnd;
this.firstSourcePosition = firstSourcePosition;
this.startAddress = startAddress;
}
function getCurrentCodeObject() {
var functionSelect = document.getElementById('function-selector-id');
return functionSelect.options[functionSelect.selectedIndex].codeObject;
}
function getCurrentSourceText() {
var code = getCurrentCodeObject();
if (code.sourceBegin == -1 || code.sourceEnd == -1) return "";
return fileContent.substring(code.sourceBegin, code.sourceEnd);
}
function getCurrentAsmText() {
var code = getCurrentCodeObject();
if (code.asmBegin == -1 || code.asmEnd == -1) return "";
return fileContent.substring(code.asmBegin, code.asmEnd);
}
function setKindByIndex(index) {
selectedFunctionKind = kinds[index];
}
function processLine(text, begin, end) {
var line = text.substring(begin, end);
if (readingSource) {
if (separatorFilter.exec(line) != null) {
readingSource = false;
} else {
if (sourceBegin == -1) {
sourceBegin = begin;
}
sourceEnd = end;
}
} else {
if (readingAsm) {
if (codeEndFinder.exec(line) != null) {
readingAsm = false;
asmEnd = begin;
var newCode =
new Code(currentFunctionName, currentFunctionKind,
sourceBegin, sourceEnd, asmBegin, asmEnd,
firstSourcePosition, startAddress);
codeObjects.push(newCode);
currentFunctionKind = null;
} else {
if (asmBegin == -1) {
matches = instructionBeginFinder.exec(line);
if (matches != null) {
asmBegin = begin;
}
}
if (startAddress == "") {
matches = instructionFinder.exec(line);
if (matches != null) {
startAddress = matches[1];
}
}
}
} else {
var matches = kindFinder.exec(line);
if (matches != null) {
currentFunctionKind = matches[1];
if (!kindsWithSource[currentFunctionKind]) {
sourceBegin = -1;
sourceEnd = -1;
}
} else if (currentFunctionKind != null) {
matches = nameFinder.exec(line);
if (matches != null) {
readingAsm = true;
asmBegin = -1;
currentFunctionName = matches[1];
}
} else if (rawSourceFilter.exec(line) != null) {
readingSource = true;
sourceBegin = -1;
} else {
var matches = firstPositionFinder.exec(line);
if (matches != null) {
firstSourcePosition = parseInt(matches[1]);
}
}
}
}
}
function processLines(source, size, processLine) {
var firstChar = 0;
for (var x = 0; x < size; x++) {
var curChar = source[x];
if (curChar == '\n' || curChar == '\r') {
processLine(source, firstChar, x);
firstChar = x + 1;
}
}
if (firstChar != size - 1) {
processLine(source, firstChar, size - 1);
}
}
function processFileContent() {
document.getElementById('source-text-pre').innerHTML = '';
sourceBegin = -1;
codeObjects = [];
processLines(fileContent, fileContent.length, processLine);
var functionSelectElement = document.getElementById('function-selector-id');
functionSelectElement.innerHTML = '';
var length = codeObjects.length;
for (var i = 0; i < codeObjects.length; ++i) {
var code = codeObjects[i];
if (code.kind == selectedFunctionKind) {
var optionElement = document.createElement("option");
optionElement.codeObject = code;
optionElement.text = code.name;
functionSelectElement.add(optionElement, null);
}
}
}
function asmClick(element) {
if (element == selectedAsm) return;
if (selectedAsm != null) {
selectedAsm.classList.remove('highlight-yellow');
}
selectedAsm = element;
selectedAsm.classList.add('highlight-yellow');
var pc = element.firstChild.innerText;
var sourceLine = null;
if (addressFinder.exec(pc) != null) {
var position = findSourcePosition(pc);
var line = findSourceLine(position);
sourceLine = document.getElementById('source-line-' + line);
var sourceLineTop = sourceLine.offsetTop;
makeSourcePosVisible(sourceLineTop);
}
if (selectedSource == sourceLine) return;
if (selectedSource != null) {
selectedSource.classList.remove('highlight-yellow');
selectedSource.classList.add(selectedSourceClass);
}
if (sourceLine != null) {
selectedSourceClass = sourceLine.classList[0];
sourceLine.classList.remove(selectedSourceClass);
sourceLine.classList.add('highlight-yellow');
}
selectedSource = sourceLine;
}
function makeContainerPosVisible(container, newTop) {
var height = container.offsetHeight;
var margin = Math.floor(height / 4);
if (newTop < container.scrollTop + margin) {
newTop -= margin;
if (newTop < 0) newTop = 0;
container.scrollTop = newTop;
return;
}
if (newTop > (container.scrollTop + 3 * margin)) {
newTop = newTop - 3 * margin;
container.scrollTop = newTop;
}
}
function makeAsmPosVisible(newTop) {
var asmContainer = document.getElementById('asm-container');
makeContainerPosVisible(asmContainer, newTop);
}
function makeSourcePosVisible(newTop) {
var sourceContainer = document.getElementById('source-container');
makeContainerPosVisible(sourceContainer, newTop);
}
function addressClick(element, event) {
event.stopPropagation();
var asmLineId = 'address-' + element.innerText;
var asmLineElement = document.getElementById(asmLineId);
if (asmLineElement != null) {
var asmLineTop = asmLineElement.parentNode.offsetTop;
makeAsmPosVisible(asmLineTop);
asmLineElement.classList.add('highlight-flash-blue');
window.setTimeout(function() {
asmLineElement.classList.remove('highlight-flash-blue');
}, 1500);
}
}
function prepareAsm(originalSource) {
var newSource = "";
var lineNumber = 1;
var functionProcessLine = function(text, begin, end) {
var currentLine = text.substring(begin, end);
var matches = instructionFinder.exec(currentLine);
var clickHandler = "";
if (matches != null) {
var restOfLine = matches[2];
restOfLine = restOfLine.replace(
addressReplacer,
'<span class="hover-underline" ' +
'onclick="Sodium.addressClick(this, event);">\$1</span>');
currentLine = '<span id="address-' + matches[1] + '" >' +
matches[1] + '</span>' + restOfLine;
clickHandler = 'onclick=\'Sodium.asmClick(this)\' ';
} else if (whiteSpaceLineFinder.exec(currentLine)) {
currentLine = "<br>";
}
newSource += '<pre style=\'margin-bottom: -12px;\' ' + clickHandler + '>' +
currentLine + '</pre>';
lineNumber++;
}
processLines(originalSource, originalSource.length, functionProcessLine);
return newSource;
}
function findSourcePosition(pcToSearch) {
var position = 0;
var distance = 0x7FFFFFFF;
var pcToSearchOffset = parseInt(pcToSearch);
var processOneLine = function(text, begin, end) {
var currentLine = text.substring(begin, end);
var matches = positionFinder.exec(currentLine);
if (matches != null) {
var pcOffset = parseInt(matches[1]);
if (pcOffset <= pcToSearchOffset) {
var dist = pcToSearchOffset - pcOffset;
var pos = parseInt(matches[2]);
if ((dist < distance) || (dist == distance && pos > position)) {
position = pos;
distance = dist;
}
}
}
}
var asmText = getCurrentAsmText();
processLines(asmText, asmText.length, processOneLine);
var code = getCurrentCodeObject();
if (position == 0) return 0;
return position - code.firstSourcePosition;
}
function findSourceLine(position) {
if (position == 0) return 1;
var line = 0;
var processOneLine = function(text, begin, end) {
if (begin < position) {
line++;
}
}
var sourceText = getCurrentSourceText();
processLines(sourceText, sourceText.length, processOneLine);
return line;
}
function functionChangedHandler() {
var functionSelect = document.getElementById('function-selector-id');
var source = getCurrentSourceText();
var sourceDivElement = document.getElementById('source-text');
var code = getCurrentCodeObject();
var newHtml = "<pre class=\"prettyprint linenums\" id=\"source-text\">"
+ 'function ' + code.name + source + "</pre>";
sourceDivElement.innerHTML = newHtml;
try {
// Wrap in try to work when offline.
PR.prettyPrint();
} catch (e) {
}
var sourceLineContainer = sourceDivElement.firstChild.firstChild;
var lineCount = sourceLineContainer.childElementCount;
var current = sourceLineContainer.firstChild;
for (var i = 1; i < lineCount; ++i) {
current.id = "source-line-" + i;
current = current.nextElementSibling;
}
var asm = getCurrentAsmText();
document.getElementById('asm-text').innerHTML = prepareAsm(asm);
}
function kindChangedHandler(element) {
setKindByIndex(element.selectedIndex);
processFileContent();
functionChangedHandler();
}
function readLog(evt) {
//Retrieve the first (and only!) File from the FileList object
var f = evt.target.files[0];
if (f) {
var r = new FileReader();
r.onload = function(e) {
var file = evt.target.files[0];
currentFunctionKind = "";
fileContent = e.target.result;
processFileContent();
functionChangedHandler();
}
r.readAsText(f);
} else {
alert("Failed to load file");
}
}
function buildFunctionKindSelector(kindSelectElement) {
for (var x = 0; x < kinds.length; ++x) {
var optionElement = document.createElement("option");
optionElement.value = x;
optionElement.text = kinds[x];
kindSelectElement.add(optionElement, null);
}
kindSelectElement.selectedIndex = 1;
setKindByIndex(1);
}
return {
buildFunctionKindSelector: buildFunctionKindSelector,
kindChangedHandler: kindChangedHandler,
functionChangedHandler: functionChangedHandler,
asmClick: asmClick,
addressClick: addressClick,
readLog: readLog
};
})();