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
28var 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