// 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 }; })();