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