| Index: tools/ic-explorer.html | 
| diff --git a/tools/ICE.html b/tools/ic-explorer.html | 
| similarity index 60% | 
| rename from tools/ICE.html | 
| rename to tools/ic-explorer.html | 
| index a060a4e3eb98403d14808a24cf274b4e16238b71..43b486a50cdd33a407cdc70af3e83ab0025b14a3 100644 | 
| --- a/tools/ICE.html | 
| +++ b/tools/ic-explorer.html | 
| @@ -22,6 +22,10 @@ | 
| .key { | 
| padding-left: 1em; | 
| } | 
| +  .drilldown-group-title { | 
| +    font-weight: bold; | 
| +    padding: 0.5em 0 0.2em 0; | 
| +  } | 
| </style> | 
| <script> | 
| "use strict" | 
| @@ -30,7 +34,9 @@ var entries = []; | 
| class Entry { | 
| constructor(id, line) { | 
| this.id = id; | 
| +    this.line = line; | 
| var parts = line.split(" "); | 
| +    if (parts.length < 6) return | 
| this.isValid = false; | 
| if (parts[0][0] !== "[") return; | 
| if (parts[1] === "patching") return; | 
| @@ -44,32 +50,27 @@ class Entry { | 
| if (this.type.length == 0) return; | 
| if (this.type.indexOf('BinaryOpIC(') === 0) { | 
| this.type = "BinaryOpIC"; | 
| -      this.isNative = parts[8] == "native" | 
| -      var offset = this.isNative ? 1 : 0; | 
| var split = parts[0].split('('); | 
| this.state = "(" + split[1] + " => " + parts[2]; | 
| -      this.position = parts[6]; | 
| -      this.file = parts[8 + offset]; | 
| +      var offset = this.parsePositionAndFile(parts, 6); | 
| +      if (offset == -1) return | 
| +      if (this.file === undefined) return | 
| this.file = this.file.slice(0,-1); | 
| - | 
| } else { | 
| -      this.position = parts[2]; | 
| -      this.isNative = parts[4] == "native" | 
| -      var offset = this.isNative ? 1 : 0; | 
| -      this.file = parts[4 + offset]; | 
| -      this.state = parts[5 + offset]; | 
| +      var offset = this.parsePositionAndFile(parts, 2); | 
| +      if (offset == -1) return | 
| +      this.state = parts[++offset]; | 
| if (this.type !== "CompareIC") { | 
| // if there is no address we have a smi key | 
| -        var address = parts[6 + offset]; | 
| +        var address = parts[++offset]; | 
| if (address !== undefined && address.indexOf("0x") === 0) { | 
| -          this.key = parts.slice(7 + offset).join(" "); | 
| +          this.key = parts.slice(++offset).join(" "); | 
| } else { | 
| this.key = address; | 
| } | 
| } | 
| } | 
| this.filePosition = this.file + " " + this.position; | 
| -    this.isValid = true; | 
| if (this.key) { | 
| var isStringKey = false | 
| if (this.key.indexOf("<String[") === 0) { | 
| @@ -87,10 +88,27 @@ class Entry { | 
| this.key = this.key + "\""; | 
| } | 
| } | 
| +    this.isValid = true; | 
| +  } | 
| + | 
| +  parsePositionAndFile(parts, start) { | 
| +    // find the position of 'at' in the parts array. | 
| +    var offset = start; | 
| +    for (var i = start+1; i<parts.length; i++) { | 
| +      offset++; | 
| +      if (parts[i] == 'at') break; | 
| +    } | 
| +    if (parts[offset] !== 'at') return -1; | 
| +    this.position = parts.slice(start, offset).join(' '); | 
| +    offset += 1; | 
| +    this.isNative = parts[offset] == "native" | 
| +    offset += this.isNative ? 1 : 0; | 
| +    this.file = parts[offset]; | 
| +    return offset; | 
| } | 
| } | 
|  | 
| -function updateSize() { | 
| +function loadFile() { | 
| var files = document.getElementById("uploadInput").files; | 
|  | 
| var file = files[0]; | 
| @@ -126,7 +144,32 @@ function updateSize() { | 
|  | 
| var properties = ['type', 'category', 'file', 'filePosition', 'state' , 'key', 'isNative'] | 
|  | 
| -function groupBy(entries, property, subGroup) { | 
| +class Group { | 
| +  constructor(property, key, entry) { | 
| +    this.property = property; | 
| +    this.key = key; | 
| +    this.count = 1; | 
| +    this.entries = [entry]; | 
| +    this.percentage = undefined; | 
| +    this.groups = undefined; | 
| +  } | 
| + | 
| +  add(entry) { | 
| +    this.count ++; | 
| +    this.entries.push(entry) | 
| +  } | 
| + | 
| +  createSubGroups() { | 
| +    this.groups = {}; | 
| +    for (var i=0; i<properties.length; i++) { | 
| +      var subProperty = properties[i]; | 
| +      if (this.property == subProperty) continue; | 
| +      this.groups[subProperty] = groupBy(this.entries, subProperty); | 
| +    } | 
| +  } | 
| +} | 
| + | 
| +function groupBy(entries, property) { | 
| var accumulator = {}; | 
| accumulator.__proto__ = null; | 
| var length = entries.length; | 
| @@ -134,33 +177,23 @@ function groupBy(entries, property, subGroup) { | 
| var entry = entries[i]; | 
| var key = entry[property]; | 
| if (accumulator[key] == undefined) { | 
| -      accumulator[key] = { key: key, count: 1, entries: [ entry ]}; | 
| +      accumulator[key] = new Group(property, key, entry) | 
| } else { | 
| -      var record = accumulator[key]; | 
| -      if (record.entries == undefined) console.log([record, entry]); | 
| -      record.count ++; | 
| -      record.entries.push(entry) | 
| +      var group = accumulator[key]; | 
| +      if (group.entries == undefined) console.log([group, entry]); | 
| +      group.add(entry) | 
| } | 
| } | 
| var result = [] | 
| for (var key in accumulator) { | 
| -    var record = accumulator[key]; | 
| -    record.percentage = Math.round(record.count / length * 100 * 100) / 100; | 
| -    if (subGroup) subGroupBy(record, property); | 
| -    result.push(record); | 
| +    var group = accumulator[key]; | 
| +    group.percentage = Math.round(group.count / length * 100 * 100) / 100; | 
| +    result.push(group); | 
| } | 
| result.sort((a,b) => { return b.count - a.count }); | 
| return result; | 
| } | 
|  | 
| -function subGroupBy(record, originalProperty) { | 
| -  record.groups = {}; | 
| -  for (var i=0; i<properties.length; i++) { | 
| -    var property = properties[i]; | 
| -    if (property == originalProperty) continue; | 
| -    record.groups[property] = groupBy(record.entries, property, false); | 
| -  } | 
| -} | 
|  | 
|  | 
|  | 
| @@ -170,7 +203,8 @@ function updateTable() { | 
| console.log(key); | 
| var tableBody = document.getElementById("table-body"); | 
| removeAllChildren(tableBody); | 
| -  display(groupBy(entries, key, true), tableBody, true); | 
| +  var groups = groupBy(entries, key, true); | 
| +  display(groups, tableBody); | 
| } | 
|  | 
| function selecedOption(node) { | 
| @@ -183,10 +217,7 @@ function removeAllChildren(node) { | 
| } | 
| } | 
|  | 
| -function display(entries, parent, showDetails) { | 
| -  if (showDetails) { | 
| -    console.log(entries) | 
| -  } | 
| +function display(entries, parent) { | 
| var fragment = document.createDocumentFragment(); | 
|  | 
| function td(tr, content, className) { | 
| @@ -194,60 +225,67 @@ function display(entries, parent, showDetails) { | 
| td.innerHTML = content; | 
| td.className = className | 
| tr.appendChild(td); | 
| -    return tr | 
| +    return td | 
| } | 
| - | 
| -  function drillDown(entry, tr) { | 
| -    tr.id = "details-" + i; | 
| -    tr.className = "entry-details"; | 
| -    tr.style.display = "none"; | 
| - | 
| -    tr.appendChild(document.createElement("td")); | 
| - | 
| -    var td = document.createElement("td"); | 
| -    td.colSpan = 3; | 
| -    for (var key in entry.groups) { | 
| -      td.appendChild(drillDownGroup(entry, key)); | 
| -    } | 
| -    tr.appendChild(td); | 
| -  } | 
| - | 
| -  function drillDownGroup(entry, key) { | 
| -    var group = entry.groups[key]; | 
| -    var div = document.createElement("div") | 
| -    div.innerHTML = key; | 
| -    var table = document.createElement("table"); | 
| -    display(group.slice(0, 20), table, false) | 
| -    div.appendChild(table); | 
| -    return div; | 
| -  } | 
| - | 
| -  for (var i = 0; i<entries.length; i++) { | 
| +  var max = Math.min(1000, entries.length) | 
| +  for (var i = 0; i<max; i++) { | 
| var entry = entries[i]; | 
| var tr = document.createElement("tr"); | 
| -    tr.id = "row-" + i; | 
| -    tr.dataset.id = i; | 
| -    if (showDetails) { | 
| -      td(tr, '<span onclick="toggleDetails(this)">details</a>', 'details'); | 
| -    } | 
| +    tr.entry = entry; | 
| +    td(tr, '<span onclick="toggleDetails(this)">details</a>', 'details'); | 
| td(tr, entry.percentage +"%", 'percentage'); | 
| td(tr, entry.count, 'count'); | 
| td(tr, entry.key, 'key'); | 
| fragment.appendChild(tr); | 
| - | 
| -    if (showDetails) { | 
| -      tr = document.createElement("tr"); | 
| -      drillDown(entry, tr); | 
| -      fragment.appendChild(tr); | 
| -    } | 
| +  } | 
| +  var omitted = entries.length - max; | 
| +  if (omitted > 0) { | 
| +    var tr = document.createElement("tr"); | 
| +    var td = td(tr, 'Omitted ' + omitted + " entries."); | 
| +    td.colSpan = 4; | 
| +    fragment.appendChild(tr); | 
| } | 
| parent.appendChild(fragment); | 
| } | 
|  | 
| +function displayDrilldown(entry, previousSibling) { | 
| +  var tr = document.createElement('tr'); | 
| +  tr.className = "entry-details"; | 
| +  tr.style.display = "none"; | 
| +  // indent by one td. | 
| +  tr.appendChild(document.createElement("td")); | 
| +  var td = document.createElement("td"); | 
| +  td.colSpan = 3; | 
| +  for (var key in entry.groups) { | 
| +    td.appendChild(displayDrilldownGroup(entry, key)); | 
| +  } | 
| +  tr.appendChild(td); | 
| +  // Append the new TR after previousSibling. | 
| +  previousSibling.parentNode.insertBefore(tr, previousSibling.nextSibling) | 
| +} | 
| + | 
| +function displayDrilldownGroup(entry, key) { | 
| +  var max = 20; | 
| +  var group = entry.groups[key]; | 
| +  var div = document.createElement("div") | 
| +  div.className = 'drilldown-group-title' | 
| +  div.innerHTML = key + ' [top ' + max + ']'; | 
| +  var table = document.createElement("table"); | 
| +  display(group.slice(0, max), table, false) | 
| +  div.appendChild(table); | 
| +  return div; | 
| +} | 
| + | 
| function toggleDetails(node) { | 
| var tr = node.parentNode.parentNode; | 
| -  var id = 'details-'+tr.dataset.id; | 
| -  var details = document.getElementById(id); | 
| +  var entry = tr.entry; | 
| + | 
| +  // Create subgroup in-place if the don't exist yet. | 
| +  if (entry.groups === undefined) { | 
| +    entry.createSubGroups(); | 
| +    displayDrilldown(entry, tr); | 
| +  } | 
| +  var details = tr.nextSibling; | 
| var display = details.style.display; | 
| if (display != "none") { | 
| display = "none"; | 
| @@ -281,7 +319,7 @@ function initGroupKeySelect() { | 
| <h2>Data</h2> | 
| <form name="fileForm"> | 
| <p> | 
| -        <input id="uploadInput" type="file" name="files" onchange="updateSize();" > | 
| +        <input id="uploadInput" type="file" name="files" onchange="loadFile();" > | 
| trace entries: <span id="count">0</span> | 
| </p> | 
| </form> | 
|  |