Repository: thlorenz/turbolizer
Branch: master
Commit: fa3377527f03
Files: 28
Total size: 116.3 KB
Directory structure:
gitextract_t6om2rjh/
├── .gitignore
├── .npmignore
├── OWNERS
├── README.md
├── bin/
│ └── turbolizer
├── code-view.js
├── constants.js
├── disassembly-view.js
├── edge.js
├── empty-view.js
├── graph-layout.js
├── graph-view.js
├── index.html
├── lang-disassembly.js
├── lib/
│ ├── client-loader.js
│ ├── turbolizer.js
│ └── turbolizer.server.js
├── monkey.js
├── node.js
├── package.json
├── schedule-view.js
├── selection-broker.js
├── selection.js
├── text-view.js
├── turbo-visualizer.css
├── turbo-visualizer.js
├── util.js
└── view.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules
================================================
FILE: .npmignore
================================================
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules
assets
================================================
FILE: OWNERS
================================================
danno@chromium.org
================================================
FILE: README.md
================================================
# Turbolizer
Turbolizer tool derived from the one included with `v8/tools`.

## Installation
```
npm install -g turbolizer
```
## Usage
Run your app with the `--trace-turbo` flag, i.e. `node --trace-turbo app.js` to produce `turbo-*.json` files.
Then just run `turbolizer` in the same directory and select which file (or all) you want to
load and the turbolizer application will open in the browser with it preloaded.
## Alternatives
If you don't want to install anything, as an alternative can then either load them one by one
via the hosted browser version of this repo at [thlorenz.github.io/turbolizer](https://thlorenz.github.io/turbolizer).
* * *
_Original Readme from the [v8 repository](https://github.com/v8/v8)_
Turbolizer
==========
Turbolizer is a HTML-based tool that visualizes optimized code along the various
phases of Turbofan's optimization pipeline, allowing easy navigation between
source code, Turbofan IR graphs, scheduled IR nodes and generated assembly code.
Turbolizer consumes .json files that are generated per-function by d8 by passing
the '--trace-turbo' command-line flag.
Host the turbolizer locally by starting a web server that serves the contents of
the turbolizer directory, e.g.:
cd src/tools/turbolizer
python -m SimpleHTTPServer 8000
Optionally, profiling data generated by the perf tools in linux can be merged
with the .json files using the turbolizer-perf.py file included. The following
command is an example of using the perf script:
perf script -i perf.data.jitted -s turbolizer-perf.py turbo-main.json
The output of the above command is a json object that can be piped to a file
which, when uploaded to turbolizer, will display the event counts from perf next
to each instruction in the disassembly. Further detail can be found in the
bottom of this document under "Using Perf with Turbo."
Using the python interface in perf script requires python-dev to be installed
and perf be recompiled with python support enabled. Once recompiled, the
variable PERF_EXEC_PATH must be set to the location of the recompiled perf
binaries.
Graph visualization and manipulation based on Mike Bostock's sample code for an
interactive tool for creating directed graphs. Original source is at
https://github.com/metacademy/directed-graph-creator and released under the
MIT/X license.
Icons derived from the "White Olive Collection" created by Breezi released under
the Creative Commons BY license.
Using Perf with Turbo
---------------------
In order to generate perf data that matches exactly with the turbofan trace, you
must use either a debug build of v8 or a release build with the flag
'disassembler=on'. This flag ensures that the '--trace-turbo' will output the
necessary disassembly for linking with the perf profile.
The basic example of generating the required data is as follows:
perf record -k mono /path/to/d8 --trace-turbo --perf-prof main.js
perf inject -j -i perf.data -o perf.data.jitted
perf script -i perf.data.jitted -s turbolizer-perf.py turbo-main.json
These commands combined will run and profile d8, merge the output into a single
'perf.data.jitted' file, then take the event data from that and link them to the
disassembly in the 'turbo-main.json'. Note that, as above, the output of the
script command must be piped to a file for uploading to turbolizer.
There are many options that can be added to the first command, for example '-e'
can be used to specify the counting of specific events (default: cycles), as
well as '--cpu' to specify which CPU to sample.
================================================
FILE: bin/turbolizer
================================================
#!/usr/bin/env node
'use strict'
const { prompt } = require('promptly')
const { mapAllTurboFiles } = require('../lib/turbolizer')
const { createServer, openWithFile } = require('../lib/turbolizer.server')
function createPromptMsg(map) {
let msg = 'Turbolizer - please select a file to view:\n\n'
for (const [ selector, { entry } ] of map) {
msg += `\t${selector}: ${entry}\n`
}
msg += '\t0: View ALL'
return msg + '\n\nYour choice: '
}
function createValidator(map) {
return val => {
if (val === '0') return val
if (map.has(val)) return val
throw new Error(`Invalid choice: '${val}', please select one of the given numbers`)
}
}
const root = process.cwd()
;(async () => {
try {
const map = await mapAllTurboFiles(root)
if (map.size === 0) {
console.error('Turbolizer - Problem:\n')
console.error(' Unable to find any "turbo-*.json" files in the current directory.\n')
console.error(' Please run "node --trace-turbo app.js" in order to create them or follow the')
console.error(' instructions at https://github.com/thlorenz/turbolizer/blob/master/README.md.')
return
}
const msg = createPromptMsg(map)
const result = await prompt(msg, { validator: createValidator(map) })
const { server, address } = createServer(root)
server.on('listening', () => {
const choice = result.trim()
if (choice === '0') {
for (const val of map.values()) {
openWithFile({ address, file: val.entry })
}
} else {
openWithFile({ address, file: map.get(choice).entry })
}
})
} catch (err) {
console.error(err)
}
})()
================================================
FILE: code-view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class CodeView extends View {
constructor(divID, PR, sourceText, sourcePosition, broker) {
super(divID, broker, null, false);
let view = this;
view.PR = PR;
view.mouseDown = false;
view.broker = broker;
view.allSpans = [];
var selectionHandler = {
clear: function() { broker.clear(selectionHandler); },
select: function(items, selected) {
var handler = this;
var broker = view.broker;
for (let span of items) {
if (selected) {
span.classList.add("selected");
} else {
span.classList.remove("selected");
}
}
var locations = [];
for (var span of items) {
locations.push({pos_start: span.start, pos_end: span.end});
}
broker.clear(selectionHandler);
broker.select(selectionHandler, locations, selected);
},
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
var pos1 = span1.start;
var pos2 = span2.start;
var result = [];
var lineListDiv = view.divNode.firstChild.firstChild.childNodes;
for (var i = 0; i < lineListDiv.length; i++) {
var currentLineElement = lineListDiv[i];
var spans = currentLineElement.childNodes;
for (var j = 0; j < spans.length; ++j) {
var currentSpan = spans[j];
if (currentSpan.start > pos1 ||
(inclusive1 && currentSpan.start == pos1)) {
if (currentSpan.start < pos2 ||
(inclusive2 && currentSpan.start == pos2)) {
result.push(currentSpan);
}
}
}
}
return result;
},
brokeredSelect: function(locations, selected) {
let firstSelect = view.selection.isEmpty();
for (let location of locations) {
let start = location.pos_start;
let end = location.pos_end;
if (start && end) {
let lower = 0;
let upper = view.allSpans.length;
if (upper > 0) {
while ((upper - lower) > 1) {
var middle = Math.floor((upper + lower) / 2);
var lineStart = view.allSpans[middle].start;
if (lineStart < start) {
lower = middle;
} else if (lineStart > start) {
upper = middle;
} else {
lower = middle;
break;
}
}
var currentSpan = view.allSpans[lower];
var currentLineElement = currentSpan.parentNode;
if ((currentSpan.start <= start && start < currentSpan.end) ||
(currentSpan.start <= end && end < currentSpan.end)) {
if (firstSelect) {
makeContainerPosVisible(
view.divNode, currentLineElement.offsetTop);
firstSelect = false;
}
view.selection.select(currentSpan, selected);
}
}
}
}
},
brokeredClear: function() { view.selection.clear(); },
};
view.selection = new Selection(selectionHandler);
broker.addSelectionHandler(selectionHandler);
view.handleSpanMouseDown = function(e) {
e.stopPropagation();
if (!e.shiftKey) {
view.selection.clear();
}
view.selection.select(this, true);
view.mouseDown = true;
}
view.handleSpanMouseMove = function(e) {
if (view.mouseDown) {
view.selection.extendTo(this);
}
}
view.handleCodeMouseDown = function(e) { view.selection.clear(); }
document.addEventListener('mouseup', function(e) {
view.mouseDown = false;
}, false);
view.initializeCode(sourceText, sourcePosition);
}
initializeContent(data, rememberedSelection) { this.data = data; }
initializeCode(sourceText, sourcePosition) {
var view = this;
var codePre = document.createElement("pre");
codePre.classList.add("prettyprint");
view.divNode.innerHTML = "";
view.divNode.appendChild(codePre);
if (sourceText) {
codePre.classList.add("linenums");
codePre.textContent = sourceText;
try {
// Wrap in try to work when offline.
view.PR.prettyPrint();
} catch (e) {
}
view.divNode.onmousedown = this.handleCodeMouseDown;
var base = sourcePosition;
var current = 0;
var lineListDiv = view.divNode.firstChild.firstChild.childNodes;
for (let i = 0; i < lineListDiv.length; i++) {
var currentLineElement = lineListDiv[i];
currentLineElement.id = "li" + i;
var pos = base + current;
currentLineElement.pos = pos;
var spans = currentLineElement.childNodes;
for (let j = 0; j < spans.length; ++j) {
var currentSpan = spans[j];
if (currentSpan.nodeType == 1) {
currentSpan.start = pos;
currentSpan.end = pos + currentSpan.textContent.length;
currentSpan.onmousedown = this.handleSpanMouseDown;
currentSpan.onmousemove = this.handleSpanMouseMove;
view.allSpans.push(currentSpan);
}
current += currentSpan.textContent.length;
pos = base + current;
}
while ((current < sourceText.length) &&
(sourceText[current] == '\n' || sourceText[current] == '\r')) {
++current;
}
}
}
}
deleteContent() {}
}
================================================
FILE: constants.js
================================================
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var MAX_RANK_SENTINEL = 0;
var GRAPH_MARGIN = 250;
var WIDTH = 'width';
var HEIGHT = 'height';
var VISIBILITY = 'visibility';
var SOURCE_PANE_ID = 'left';
var SOURCE_COLLAPSE_ID = 'source-shrink';
var SOURCE_EXPAND_ID = 'source-expand';
var INTERMEDIATE_PANE_ID = 'middle';
var EMPTY_PANE_ID = 'empty';
var GRAPH_PANE_ID = 'graph';
var SCHEDULE_PANE_ID = 'schedule';
var GENERATED_PANE_ID = 'right';
var DISASSEMBLY_PANE_ID = 'disassembly';
var DISASSEMBLY_COLLAPSE_ID = 'disassembly-shrink';
var DISASSEMBLY_EXPAND_ID = 'disassembly-expand';
var COLLAPSE_PANE_BUTTON_VISIBLE = 'button-input';
var COLLAPSE_PANE_BUTTON_INVISIBLE = 'button-input-invisible';
var UNICODE_BLOCK = '▋';
var PROF_COLS = [
{ perc: 0, col: { r: 255, g: 255, b: 255 } },
{ perc: 0.5, col: { r: 255, g: 255, b: 128 } },
{ perc: 5, col: { r: 255, g: 128, b: 0 } },
{ perc: 15, col: { r: 255, g: 0, b: 0 } },
{ perc: 100, col: { r: 0, g: 0, b: 0 } }
];
================================================
FILE: disassembly-view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class DisassemblyView extends TextView {
constructor(id, broker) {
super(id, broker, null, false);
let view = this;
let ADDRESS_STYLE = {
css: 'tag',
location: function(text) {
ADDRESS_STYLE.last_address = text;
return undefined;
}
};
let ADDRESS_LINK_STYLE = {
css: 'tag',
link: function(text) {
view.select(function(location) { return location.address == text; }, true, true);
}
};
let UNCLASSIFIED_STYLE = {
css: 'com'
};
let NUMBER_STYLE = {
css: 'lit'
};
let COMMENT_STYLE = {
css: 'com'
};
let POSITION_STYLE = {
css: 'com',
location: function(text) {
view.pos_start = Number(text);
}
};
let OPCODE_STYLE = {
css: 'kwd',
location: function(text) {
if (BLOCK_HEADER_STYLE.block_id != undefined) {
return {
address: ADDRESS_STYLE.last_address,
block_id: BLOCK_HEADER_STYLE.block_id
};
} else {
return {
address: ADDRESS_STYLE.last_address
};
}
}
};
const BLOCK_HEADER_STYLE = {
css: 'com',
block_id: -1,
location: function(text) {
let matches = /\d+/.exec(text);
if (!matches) return undefined;
BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
return {
block_id: BLOCK_HEADER_STYLE.block_id
};
},
};
const SOURCE_POSITION_HEADER_STYLE = {
css: 'com',
location: function(text) {
let matches = /(\d+):(\d+)/.exec(text);
if (!matches) return undefined;
let li = Number(matches[1]);
if (view.pos_lines === null) return undefined;
let pos = view.pos_lines[li-1] + Number(matches[2]);
return {
pos_start: pos,
pos_end: pos + 1
};
},
};
view.SOURCE_POSITION_HEADER_REGEX = /^(\s*-- .+:)(\d+:\d+)( --)/;
let patterns = [
[
[/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1],
[view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1],
[/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1],
[/^.*/, UNCLASSIFIED_STYLE, -1]
],
[
[/^\s+[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2],
[/^.*/, null, -1]
],
[
[/^\S+\s+/, OPCODE_STYLE, 3],
[/^\S+$/, OPCODE_STYLE, -1],
[/^.*/, null, -1]
],
[
[/^\s+/, null],
[/^[^\(;]+$/, null, -1],
[/^[^\(;]+/, null],
[/^\(/, null, 4],
[/^;/, COMMENT_STYLE, 5]
],
[
[/^0x[0-9a-f]{8,16}/, ADDRESS_LINK_STYLE],
[/^[^\)]/, null],
[/^\)$/, null, -1],
[/^\)/, null, 3]
],
[
[/^; debug\: position /, COMMENT_STYLE, 6],
[/^.+$/, COMMENT_STYLE, -1]
],
[
[/^\d+$/, POSITION_STYLE, -1],
]
];
view.setPatterns(patterns);
}
lineLocation(li) {
let view = this;
let result = undefined;
for (let i = 0; i < li.children.length; ++i) {
let fragment = li.children[i];
let location = fragment.location;
if (location != null) {
if (location.block_id != undefined) {
if (result === undefined) result = {};
result.block_id = location.block_id;
}
if (location.address != undefined) {
if (result === undefined) result = {};
result.address = location.address;
}
if (location.pos_start != undefined && location.pos_end != undefined) {
if (result === undefined) result = {};
result.pos_start = location.pos_start;
result.pos_end = location.pos_end;
}
else if (view.pos_start != -1) {
if (result === undefined) result = {};
result.pos_start = view.pos_start;
result.pos_end = result.pos_start + 1;
}
}
}
return result;
}
initializeContent(data, rememberedSelection) {
this.data = data;
super.initializeContent(data, rememberedSelection);
}
initializeCode(sourceText, sourcePosition) {
let view = this;
view.pos_start = -1;
view.addr_event_counts = null;
view.total_event_counts = null;
view.max_event_counts = null;
view.pos_lines = new Array();
// Comment lines for line 0 include sourcePosition already, only need to
// add sourcePosition for lines > 0.
view.pos_lines[0] = sourcePosition;
if (sourceText) {
let base = sourcePosition;
let current = 0;
let source_lines = sourceText.split("\n");
for (let i = 1; i < source_lines.length; i++) {
// Add 1 for newline character that is split off.
current += source_lines[i-1].length + 1;
view.pos_lines[i] = base + current;
}
}
}
initializePerfProfile(eventCounts) {
let view = this;
if (eventCounts !== undefined) {
view.addr_event_counts = eventCounts;
view.total_event_counts = {};
view.max_event_counts = {};
for (let ev_name in view.addr_event_counts) {
let keys = Object.keys(view.addr_event_counts[ev_name]);
let values = keys.map(key => view.addr_event_counts[ev_name][key]);
view.total_event_counts[ev_name] = values.reduce((a, b) => a + b);
view.max_event_counts[ev_name] = values.reduce((a, b) => Math.max(a, b));
}
}
else {
view.addr_event_counts = null;
view.total_event_counts = null;
view.max_event_counts = null;
}
}
// Shorten decimals and remove trailing zeroes for readability.
humanize(num) {
return num.toFixed(3).replace(/\.?0+$/, "") + "%";
}
// Interpolate between the given start and end values by a fraction of val/max.
interpolate(val, max, start, end) {
return start + (end - start) * (val / max);
}
processLine(line) {
let view = this;
let func = function(match, p1, p2, p3) {
let nums = p2.split(":");
let li = Number(nums[0]);
let pos = Number(nums[1]);
if(li === 0)
pos -= view.pos_lines[0];
li++;
return p1 + li + ":" + pos + p3;
};
line = line.replace(view.SOURCE_POSITION_HEADER_REGEX, func);
let fragments = super.processLine(line);
// Add profiling data per instruction if available.
if (view.total_event_counts) {
let matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line);
if (matches) {
let newFragments = [];
for (let event in view.addr_event_counts) {
let count = view.addr_event_counts[event][matches[1]];
let str = " ";
let css_cls = "prof";
if(count !== undefined) {
let perc = count / view.total_event_counts[event] * 100;
let col = { r: 255, g: 255, b: 255 };
for (let i = 0; i < PROF_COLS.length; i++) {
if (perc === PROF_COLS[i].perc) {
col = PROF_COLS[i].col;
break;
}
else if (perc > PROF_COLS[i].perc && perc < PROF_COLS[i + 1].perc) {
let col1 = PROF_COLS[i].col;
let col2 = PROF_COLS[i + 1].col;
let val = perc - PROF_COLS[i].perc;
let max = PROF_COLS[i + 1].perc - PROF_COLS[i].perc;
col.r = Math.round(view.interpolate(val, max, col1.r, col2.r));
col.g = Math.round(view.interpolate(val, max, col1.g, col2.g));
col.b = Math.round(view.interpolate(val, max, col1.b, col2.b));
break;
}
}
str = UNICODE_BLOCK;
let fragment = view.createFragment(str, css_cls);
fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")";
fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")";
newFragments.push(fragment);
}
else
newFragments.push(view.createFragment(str, css_cls));
}
fragments = newFragments.concat(fragments);
}
}
return fragments;
}
}
================================================
FILE: edge.js
================================================
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var MINIMUM_EDGE_SEPARATION = 20;
function isEdgeInitiallyVisible(target, index, source, type) {
return type == "control" && (target.cfg || source.cfg);
}
var Edge = function(target, index, source, type) {
this.target = target;
this.source = source;
this.index = index;
this.type = type;
this.backEdgeNumber = 0;
this.visible = isEdgeInitiallyVisible(target, index, source, type);
};
Edge.prototype.stringID = function() {
return this.source.id + "," + this.index + "," + this.target.id;
};
Edge.prototype.isVisible = function() {
return this.visible && this.source.visible && this.target.visible;
};
Edge.prototype.getInputHorizontalPosition = function(graph) {
if (this.backEdgeNumber > 0) {
return graph.maxGraphNodeX + this.backEdgeNumber * MINIMUM_EDGE_SEPARATION;
}
var source = this.source;
var target = this.target;
var index = this.index;
var input_x = target.x + target.getInputX(index);
var inputApproach = target.getInputApproach(this.index);
var outputApproach = source.getOutputApproach(graph);
if (inputApproach > outputApproach) {
return input_x;
} else {
var inputOffset = MINIMUM_EDGE_SEPARATION * (index + 1);
return (target.x < source.x)
? (target.x + target.getTotalNodeWidth() + inputOffset)
: (target.x - inputOffset)
}
}
Edge.prototype.generatePath = function(graph) {
var target = this.target;
var source = this.source;
var input_x = target.x + target.getInputX(this.index);
var arrowheadHeight = 7;
var input_y = target.y - 2 * DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
var output_x = source.x + source.getOutputX();
var output_y = source.y + graph.getNodeHeight(source) + DEFAULT_NODE_BUBBLE_RADIUS;
var inputApproach = target.getInputApproach(this.index);
var outputApproach = source.getOutputApproach(graph);
var horizontalPos = this.getInputHorizontalPosition(graph);
var result = "M" + output_x + "," + output_y +
"L" + output_x + "," + outputApproach +
"L" + horizontalPos + "," + outputApproach;
if (horizontalPos != input_x) {
result += "L" + horizontalPos + "," + inputApproach;
} else {
if (inputApproach < outputApproach) {
inputApproach = outputApproach;
}
}
result += "L" + input_x + "," + inputApproach +
"L" + input_x + "," + input_y;
return result;
}
Edge.prototype.isBackEdge = function() {
return this.target.hasBackEdges() && (this.target.rank < this.source.rank);
}
================================================
FILE: empty-view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class EmptyView extends View {
constructor(id, broker) {
super(id, broker);
this.svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
}
initializeContent(data, rememberedSelection) {
this.svg.attr("height", document.documentElement.clientHeight + "px");
}
deleteContent() {
}
}
================================================
FILE: graph-layout.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var DEFAULT_NODE_ROW_SEPARATION = 130
var traceLayout = false;
function newGraphOccupation(graph){
var isSlotFilled = [];
var maxSlot = 0;
var minSlot = 0;
var nodeOccupation = [];
function slotToIndex(slot) {
if (slot >= 0) {
return slot * 2;
} else {
return slot * 2 + 1;
}
}
function indexToSlot(index) {
if ((index % 0) == 0) {
return index / 2;
} else {
return -((index - 1) / 2);
}
}
function positionToSlot(pos) {
return Math.floor(pos / NODE_INPUT_WIDTH);
}
function slotToLeftPosition(slot) {
return slot * NODE_INPUT_WIDTH
}
function slotToRightPosition(slot) {
return (slot + 1) * NODE_INPUT_WIDTH
}
function findSpace(pos, width, direction) {
var widthSlots = Math.floor((width + NODE_INPUT_WIDTH - 1) /
NODE_INPUT_WIDTH);
var currentSlot = positionToSlot(pos + width / 2);
var currentScanSlot = currentSlot;
var widthSlotsRemainingLeft = widthSlots;
var widthSlotsRemainingRight = widthSlots;
var slotsChecked = 0;
while (true) {
var mod = slotsChecked++ % 2;
currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
if (!isSlotFilled[slotToIndex(currentScanSlot)]) {
if (mod) {
if (direction <= 0) --widthSlotsRemainingLeft
} else {
if (direction >= 0) --widthSlotsRemainingRight
}
if (widthSlotsRemainingLeft == 0 ||
widthSlotsRemainingRight == 0 ||
(widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
(widthSlots == slotsChecked)) {
if (mod) {
return [currentScanSlot, widthSlots];
} else {
return [currentScanSlot - widthSlots + 1, widthSlots];
}
}
} else {
if (mod) {
widthSlotsRemainingLeft = widthSlots;
} else {
widthSlotsRemainingRight = widthSlots;
}
}
}
}
function setIndexRange(from, to, value) {
if (to < from) {
throw("illegal slot range");
}
while (from <= to) {
if (from > maxSlot) {
maxSlot = from;
}
if (from < minSlot) {
minSlot = from;
}
isSlotFilled[slotToIndex(from++)] = value;
}
}
function occupySlotRange(from, to) {
if (traceLayout) {
console.log("Occupied [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
}
setIndexRange(from, to, true);
}
function clearSlotRange(from, to) {
if (traceLayout) {
console.log("Cleared [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
}
setIndexRange(from, to, false);
}
function occupyPositionRange(from, to) {
occupySlotRange(positionToSlot(from), positionToSlot(to - 1));
}
function clearPositionRange(from, to) {
clearSlotRange(positionToSlot(from), positionToSlot(to - 1));
}
function occupyPositionRangeWithMargin(from, to, margin) {
var fromMargin = from - Math.floor(margin);
var toMargin = to + Math.floor(margin);
occupyPositionRange(fromMargin, toMargin);
}
function clearPositionRangeWithMargin(from, to, margin) {
var fromMargin = from - Math.floor(margin);
var toMargin = to + Math.floor(margin);
clearPositionRange(fromMargin, toMargin);
}
var occupation = {
occupyNodeInputs: function(node) {
for (var i = 0; i < node.inputs.length; ++i) {
if (node.inputs[i].isVisible()) {
var edge = node.inputs[i];
if (!edge.isBackEdge()) {
var source = edge.source;
var horizontalPos = edge.getInputHorizontalPosition(graph);
if (traceLayout) {
console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
}
occupyPositionRangeWithMargin(horizontalPos,
horizontalPos,
NODE_INPUT_WIDTH / 2);
}
}
}
},
occupyNode: function(node) {
var getPlacementHint = function(n) {
var pos = 0;
var direction = -1;
var outputEdges = 0;
var inputEdges = 0;
for (var k = 0; k < n.outputs.length; ++k) {
var outputEdge = n.outputs[k];
if (outputEdge.isVisible()) {
var output = n.outputs[k].target;
for (var l = 0; l < output.inputs.length; ++l) {
if (output.rank > n.rank) {
var inputEdge = output.inputs[l];
if (inputEdge.isVisible()) {
++inputEdges;
}
if (output.inputs[l].source == n) {
pos += output.x + output.getInputX(l) + NODE_INPUT_WIDTH / 2;
outputEdges++;
if (l >= (output.inputs.length / 2)) {
direction = 1;
}
}
}
}
}
}
if (outputEdges != 0) {
pos = pos / outputEdges;
}
if (outputEdges > 1 || inputEdges == 1) {
direction = 0;
}
return [direction, pos];
}
var width = node.getTotalNodeWidth();
var margin = MINIMUM_EDGE_SEPARATION;
var paddedWidth = width + 2 * margin;
var placementHint = getPlacementHint(node);
var x = placementHint[1] - paddedWidth + margin;
if (traceLayout) {
console.log("Node " + node.id + " placement hint [" + x + ", " + (x + paddedWidth) + ")");
}
var placement = findSpace(x, paddedWidth, placementHint[0]);
var firstSlot = placement[0];
var slotWidth = placement[1];
var endSlotExclusive = firstSlot + slotWidth - 1;
occupySlotRange(firstSlot, endSlotExclusive);
nodeOccupation.push([firstSlot, endSlotExclusive]);
if (placementHint[0] < 0) {
return slotToLeftPosition(firstSlot + slotWidth) - width - margin;
} else if (placementHint[0] > 0) {
return slotToLeftPosition(firstSlot) + margin;
} else {
return slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
}
},
clearOccupiedNodes: function() {
nodeOccupation.forEach(function(o) {
clearSlotRange(o[0], o[1]);
});
nodeOccupation = [];
},
clearNodeOutputs: function(source) {
source.outputs.forEach(function(edge) {
if (edge.isVisible()) {
var target = edge.target;
for (var i = 0; i < target.inputs.length; ++i) {
if (target.inputs[i].source === source) {
var horizontalPos = edge.getInputHorizontalPosition(graph);
clearPositionRangeWithMargin(horizontalPos,
horizontalPos,
NODE_INPUT_WIDTH / 2);
}
}
}
});
},
print: function() {
var s = "";
for (var currentSlot = -40; currentSlot < 40; ++currentSlot) {
if (currentSlot != 0) {
s += " ";
} else {
s += "|";
}
}
console.log(s);
s = "";
for (var currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
if (isSlotFilled[slotToIndex(currentSlot2)]) {
s += "*";
} else {
s += " ";
}
}
console.log(s);
}
}
return occupation;
}
function layoutNodeGraph(graph) {
// First determine the set of nodes that have no outputs. Those are the
// basis for bottom-up DFS to determine rank and node placement.
var endNodesHasNoOutputs = [];
var startNodesHasNoInputs = [];
graph.nodes.forEach(function(n, i){
endNodesHasNoOutputs[n.id] = true;
startNodesHasNoInputs[n.id] = true;
});
graph.edges.forEach(function(e, i){
endNodesHasNoOutputs[e.source.id] = false;
startNodesHasNoInputs[e.target.id] = false;
});
// Finialize the list of start and end nodes.
var endNodes = [];
var startNodes = [];
var visited = [];
var rank = [];
graph.nodes.forEach(function(n, i){
if (endNodesHasNoOutputs[n.id]) {
endNodes.push(n);
}
if (startNodesHasNoInputs[n.id]) {
startNodes.push(n);
}
visited[n.id] = false;
rank[n.id] = -1;
n.rank = 0;
n.visitOrderWithinRank = 0;
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
});
var maxRank = 0;
var visited = [];
var dfsStack = [];
var visitOrderWithinRank = 0;
var worklist = startNodes.slice();
while (worklist.length != 0) {
var n = worklist.pop();
var changed = false;
if (n.rank == MAX_RANK_SENTINEL) {
n.rank = 1;
changed = true;
}
var begin = 0;
var end = n.inputs.length;
if (n.opcode == 'Phi' || n.opcode == 'EffectPhi') {
// Keep with merge or loop node
begin = n.inputs.length - 1;
} else if (n.hasBackEdges()) {
end = 1;
}
for (var l = begin; l < end; ++l) {
var input = n.inputs[l].source;
if (input.visible && input.rank >= n.rank) {
n.rank = input.rank + 1;
changed = true;
}
}
if (changed) {
var hasBackEdges = n.hasBackEdges();
for (var l = n.outputs.length - 1; l >= 0; --l) {
if (hasBackEdges && (l != 0)) {
worklist.unshift(n.outputs[l].target);
} else {
worklist.push(n.outputs[l].target);
}
}
}
if (n.rank > maxRank) {
maxRank = n.rank;
}
}
visited = [];
function dfsFindRankLate(n) {
if (visited[n.id]) return;
visited[n.id] = true;
var originalRank = n.rank;
var newRank = n.rank;
var firstInput = true;
for (var l = 0; l < n.outputs.length; ++l) {
var output = n.outputs[l].target;
dfsFindRankLate(output);
var outputRank = output.rank;
if (output.visible && (firstInput || outputRank <= newRank) &&
(outputRank > originalRank)) {
newRank = outputRank - 1;
}
firstInput = false;
}
if (n.opcode != "Start" && n.opcode != "Phi" && n.opcode != "EffectPhi") {
n.rank = newRank;
}
}
startNodes.forEach(dfsFindRankLate);
visited = [];
function dfsRankOrder(n) {
if (visited[n.id]) return;
visited[n.id] = true;
for (var l = 0; l < n.outputs.length; ++l) {
var edge = n.outputs[l];
if (edge.isVisible()) {
var output = edge.target;
dfsRankOrder(output);
}
}
if (n.visitOrderWithinRank == 0) {
n.visitOrderWithinRank = ++visitOrderWithinRank;
}
}
startNodes.forEach(dfsRankOrder);
endNodes.forEach(function(n) {
n.rank = maxRank + 1;
});
var rankSets = [];
// Collect sets for each rank.
graph.nodes.forEach(function(n, i){
n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + graph.getNodeHeight(n) +
2 * DEFAULT_NODE_BUBBLE_RADIUS);
if (n.visible) {
if (rankSets[n.rank] === undefined) {
rankSets[n.rank] = [n];
} else {
rankSets[n.rank].push(n);
}
}
});
// Iterate backwards from highest to lowest rank, placing nodes so that they
// spread out from the "center" as much as possible while still being
// compact and not overlapping live input lines.
var occupation = newGraphOccupation(graph);
var rankCount = 0;
rankSets.reverse().forEach(function(rankSet) {
for (var i = 0; i < rankSet.length; ++i) {
occupation.clearNodeOutputs(rankSet[i]);
}
if (traceLayout) {
console.log("After clearing outputs");
occupation.print();
}
var placedCount = 0;
rankSet = rankSet.sort(function(a,b) {
return a.visitOrderWithinRank < b.visitOrderWithinRank;
});
for (var i = 0; i < rankSet.length; ++i) {
var nodeToPlace = rankSet[i];
if (nodeToPlace.visible) {
nodeToPlace.x = occupation.occupyNode(nodeToPlace);
if (traceLayout) {
console.log("Node " + nodeToPlace.id + " is placed between [" + nodeToPlace.x + ", " + (nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) + ")");
}
var staggeredFlooredI = Math.floor(placedCount++ % 3);
var delta = MINIMUM_EDGE_SEPARATION * staggeredFlooredI
nodeToPlace.outputApproach += delta;
} else {
nodeToPlace.x = 0;
}
}
if (traceLayout) {
console.log("Before clearing nodes");
occupation.print();
}
occupation.clearOccupiedNodes();
if (traceLayout) {
console.log("After clearing nodes");
occupation.print();
}
for (var i = 0; i < rankSet.length; ++i) {
var node = rankSet[i];
occupation.occupyNodeInputs(node);
}
if (traceLayout) {
console.log("After occupying inputs");
occupation.print();
}
if (traceLayout) {
console.log("After determining bounding box");
occupation.print();
}
});
graph.maxBackEdgeNumber = 0;
graph.visibleEdges.each(function (e) {
if (e.isBackEdge()) {
e.backEdgeNumber = ++graph.maxBackEdgeNumber;
} else {
e.backEdgeNumber = 0;
}
});
redetermineGraphBoundingBox(graph);
}
function redetermineGraphBoundingBox(graph) {
graph.minGraphX = 0;
graph.maxGraphNodeX = 1;
graph.maxGraphX = undefined; // see below
graph.minGraphY = 0;
graph.maxGraphY = 1;
for (var i = 0; i < graph.nodes.length; ++i) {
var node = graph.nodes[i];
if (!node.visible) {
continue;
}
if (node.x < graph.minGraphX) {
graph.minGraphX = node.x;
}
if ((node.x + node.getTotalNodeWidth()) > graph.maxGraphNodeX) {
graph.maxGraphNodeX = node.x + node.getTotalNodeWidth();
}
if ((node.y - 50) < graph.minGraphY) {
graph.minGraphY = node.y - 50;
}
if ((node.y + graph.getNodeHeight(node) + 50) > graph.maxGraphY) {
graph.maxGraphY = node.y + graph.getNodeHeight(node) + 50;
}
}
graph.maxGraphX = graph.maxGraphNodeX +
graph.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;
}
================================================
FILE: graph-view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class GraphView extends View {
constructor (d3, id, nodes, edges, broker) {
super(id, broker);
var graph = this;
var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
graph.svg = svg;
graph.nodes = nodes || [];
graph.edges = edges || [];
graph.minGraphX = 0;
graph.maxGraphX = 1;
graph.minGraphY = 0;
graph.maxGraphY = 1;
graph.state = {
selection: null,
mouseDownNode: null,
justDragged: false,
justScaleTransGraph: false,
lastKeyDown: -1,
showTypes: false
};
var selectionHandler = {
clear: function() {
broker.clear(selectionHandler);
},
select: function(items, selected) {
var locations = [];
for (var d of items) {
if (selected) {
d.classList.add("selected");
} else {
d.classList.remove("selected");
}
var data = d.__data__;
locations.push({ pos_start: data.pos, pos_end: data.pos + 1, node_id: data.id});
}
broker.select(selectionHandler, locations, selected);
},
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
// Should not be called
},
brokeredSelect: function(locations, selected) {
var test = [].entries().next();
var selection = graph.nodes
.filter(function(n) {
var pos = n.pos;
for (var location of locations) {
var start = location.pos_start;
var end = location.pos_end;
var id = location.node_id;
if (end != undefined) {
if (pos >= start && pos < end) {
return true;
}
} else if (start != undefined) {
if (pos === start) {
return true;
}
} else {
if (n.id === id) {
return true;
}
}
}
return false;
});
var newlySelected = new Set();
selection.forEach(function(n) {
newlySelected.add(n);
if (!n.visible) {
n.visible = true;
}
});
graph.updateGraphVisibility();
graph.visibleNodes.each(function(n) {
if (newlySelected.has(n)) {
graph.state.selection.select(this, selected);
}
});
graph.updateGraphVisibility();
graph.viewSelection();
},
brokeredClear: function() {
graph.state.selection.clear();
}
};
broker.addSelectionHandler(selectionHandler);
graph.state.selection = new Selection(selectionHandler);
var defs = svg.append('svg:defs');
defs.append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -4 8 8')
.attr('refX', 2)
.attr('markerWidth', 2.5)
.attr('markerHeight', 2.5)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-4L8,0L0,4');
this.graphElement = svg.append("g");
graph.visibleEdges = this.graphElement.append("g").selectAll("g");
graph.visibleNodes = this.graphElement.append("g").selectAll("g");
graph.drag = d3.behavior.drag()
.origin(function(d){
return {x: d.x, y: d.y};
})
.on("drag", function(args){
graph.state.justDragged = true;
graph.dragmove.call(graph, args);
})
d3.select("#upload").on("click", partial(this.uploadAction, graph));
d3.select("#layout").on("click", partial(this.layoutAction, graph));
d3.select("#show-all").on("click", partial(this.showAllAction, graph));
d3.select("#hide-dead").on("click", partial(this.hideDeadAction, graph));
d3.select("#hide-unselected").on("click", partial(this.hideUnselectedAction, graph));
d3.select("#hide-selected").on("click", partial(this.hideSelectedAction, graph));
d3.select("#zoom-selection").on("click", partial(this.zoomSelectionAction, graph));
d3.select("#toggle-types").on("click", partial(this.toggleTypesAction, graph));
d3.select("#search-input").on("keydown", partial(this.searchInputAction, graph));
// listen for key events
d3.select(window).on("keydown", function(e){
graph.svgKeyDown.call(graph);
})
.on("keyup", function(){
graph.svgKeyUp.call(graph);
});
svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);});
svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);});
graph.dragSvg = d3.behavior.zoom()
.on("zoom", function(){
if (d3.event.sourceEvent.shiftKey){
return false;
} else{
graph.zoomed.call(graph);
}
return true;
})
.on("zoomstart", function(){
if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
})
.on("zoomend", function(){
d3.select('body').style("cursor", "auto");
});
svg.call(graph.dragSvg).on("dblclick.zoom", null);
}
static get selectedClass() {
return "selected";
}
static get rectClass() {
return "nodeStyle";
}
static get activeEditId() {
return "active-editing";
}
static get nodeRadius() {
return 50;
}
getNodeHeight(d) {
if (this.state.showTypes) {
return d.normalheight + d.labelbbox.height;
} else {
return d.normalheight;
}
}
getEdgeFrontier(nodes, inEdges, edgeFilter) {
let frontier = new Set();
nodes.forEach(function(element) {
var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
var edgeNumber = 0;
edges.forEach(function(edge) {
if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
frontier.add(edge);
}
++edgeNumber;
});
});
return frontier;
}
getNodeFrontier(nodes, inEdges, edgeFilter) {
let graph = this;
var frontier = new Set();
var newState = true;
var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
// Control key toggles edges rather than just turning them on
if (d3.event.ctrlKey) {
edgeFrontier.forEach(function(edge) {
if (edge.visible) {
newState = false;
}
});
}
edgeFrontier.forEach(function(edge) {
edge.visible = newState;
if (newState) {
var node = inEdges ? edge.source : edge.target;
node.visible = true;
frontier.add(node);
}
});
graph.updateGraphVisibility();
if (newState) {
return graph.visibleNodes.filter(function(n) {
return frontier.has(n);
});
} else {
return undefined;
}
}
dragmove(d) {
var graph = this;
d.x += d3.event.dx;
d.y += d3.event.dy;
graph.updateGraphVisibility();
}
initializeContent(data, rememberedSelection) {
this.createGraph(data, rememberedSelection);
if (rememberedSelection != null) {
this.attachSelection(rememberedSelection);
this.connectVisibleSelectedNodes();
this.viewSelection();
}
this.updateGraphVisibility();
}
deleteContent() {
if (this.visibleNodes) {
this.nodes = [];
this.edges = [];
this.nodeMap = [];
this.updateGraphVisibility();
}
};
measureText(text) {
var textMeasure = document.getElementById('text-measure');
textMeasure.textContent = text;
return {
width: textMeasure.getBBox().width,
height: textMeasure.getBBox().height,
};
}
createGraph(data, initiallyVisibileIds) {
var g = this;
g.nodes = data.nodes;
g.nodeMap = [];
g.nodes.forEach(function(n, i){
n.__proto__ = Node;
n.visible = false;
n.x = 0;
n.y = 0;
n.rank = MAX_RANK_SENTINEL;
n.inputs = [];
n.outputs = [];
n.rpo = -1;
n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
n.cfg = n.control;
g.nodeMap[n.id] = n;
n.displayLabel = n.getDisplayLabel();
n.labelbbox = g.measureText(n.displayLabel);
n.typebbox = g.measureText(n.getDisplayType());
var innerwidth = Math.max(n.labelbbox.width, n.typebbox.width);
n.width = Math.alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
NODE_INPUT_WIDTH);
var innerheight = Math.max(n.labelbbox.height, n.typebbox.height);
n.normalheight = innerheight + 20;
});
g.edges = [];
data.edges.forEach(function(e, i){
var t = g.nodeMap[e.target];
var s = g.nodeMap[e.source];
var newEdge = new Edge(t, e.index, s, e.type);
t.inputs.push(newEdge);
s.outputs.push(newEdge);
g.edges.push(newEdge);
if (e.type == 'control') {
s.cfg = true;
}
});
g.nodes.forEach(function(n, i) {
n.visible = isNodeInitiallyVisible(n);
if (initiallyVisibileIds != undefined) {
if (initiallyVisibileIds.has(n.id)) {
n.visible = true;
}
}
});
g.fitGraphViewToWindow();
g.updateGraphVisibility();
g.layoutGraph();
g.updateGraphVisibility();
g.viewWholeGraph();
}
connectVisibleSelectedNodes() {
var graph = this;
graph.state.selection.selection.forEach(function(element) {
var edgeNumber = 0;
element.__data__.inputs.forEach(function(edge) {
if (edge.source.visible && edge.target.visible) {
edge.visible = true;
}
});
element.__data__.outputs.forEach(function(edge) {
if (edge.source.visible && edge.target.visible) {
edge.visible = true;
}
});
});
}
updateInputAndOutputBubbles() {
var g = this;
var s = g.visibleBubbles;
s.classed("filledBubbleStyle", function(c) {
var components = this.id.split(',');
if (components[0] == "ib") {
var edge = g.nodeMap[components[3]].inputs[components[2]];
return edge.isVisible();
} else {
return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
}
}).classed("halfFilledBubbleStyle", function(c) {
var components = this.id.split(',');
if (components[0] == "ib") {
var edge = g.nodeMap[components[3]].inputs[components[2]];
return false;
} else {
return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
}
}).classed("bubbleStyle", function(c) {
var components = this.id.split(',');
if (components[0] == "ib") {
var edge = g.nodeMap[components[3]].inputs[components[2]];
return !edge.isVisible();
} else {
return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
}
});
s.each(function(c) {
var components = this.id.split(',');
if (components[0] == "ob") {
var from = g.nodeMap[components[1]];
var x = from.getOutputX();
var y = g.getNodeHeight(from) + DEFAULT_NODE_BUBBLE_RADIUS;
var transform = "translate(" + x + "," + y + ")";
this.setAttribute('transform', transform);
}
});
}
attachSelection(s) {
var graph = this;
if (s.size != 0) {
this.visibleNodes.each(function(n) {
if (s.has(this.__data__.id)) {
graph.state.selection.select(this, true);
}
});
}
}
detachSelection() {
var selection = this.state.selection.detachSelection();
var s = new Set();
for (var i of selection) {
s.add(i.__data__.id);
};
return s;
}
pathMouseDown(path, d) {
d3.event.stopPropagation();
this.state.selection.clear();
this.state.selection.add(path);
};
nodeMouseDown(node, d) {
d3.event.stopPropagation();
this.state.mouseDownNode = d;
}
nodeMouseUp(d3node, d) {
var graph = this,
state = graph.state,
consts = graph.consts;
var mouseDownNode = state.mouseDownNode;
if (!mouseDownNode) return;
if (state.justDragged) {
// dragged, not clicked
redetermineGraphBoundingBox(graph);
state.justDragged = false;
} else{
// clicked, not dragged
var extend = d3.event.shiftKey;
var selection = graph.state.selection;
if (!extend) {
selection.clear();
}
selection.select(d3node[0][0], true);
}
}
selectSourcePositions(start, end, selected) {
var graph = this;
var map = [];
var sel = graph.nodes.filter(function(n) {
var pos = (n.pos === undefined)
? -1
: n.getFunctionRelativeSourcePosition(graph);
if (pos >= start && pos < end) {
map[n.id] = true;
n.visible = true;
}
});
graph.updateGraphVisibility();
graph.visibleNodes.filter(function(n) { return map[n.id]; })
.each(function(n) {
var selection = graph.state.selection;
selection.select(d3.select(this), selected);
});
}
selectAllNodes(inEdges, filter) {
var graph = this;
if (!d3.event.shiftKey) {
graph.state.selection.clear();
}
graph.state.selection.select(graph.visibleNodes[0], true);
graph.updateGraphVisibility();
}
uploadAction(graph) {
document.getElementById("hidden-file-upload").click();
}
layoutAction(graph) {
graph.updateGraphVisibility();
graph.layoutGraph();
graph.updateGraphVisibility();
graph.viewWholeGraph();
}
showAllAction(graph) {
graph.nodes.filter(function(n) { n.visible = true; })
graph.edges.filter(function(e) { e.visible = true; })
graph.updateGraphVisibility();
graph.viewWholeGraph();
}
hideDeadAction(graph) {
graph.nodes.filter(function(n) { if (!n.isLive()) n.visible = false; })
graph.updateGraphVisibility();
}
hideUnselectedAction(graph) {
var unselected = graph.visibleNodes.filter(function(n) {
return !this.classList.contains("selected");
});
unselected.each(function(n) {
n.visible = false;
});
graph.updateGraphVisibility();
}
hideSelectedAction(graph) {
var selected = graph.visibleNodes.filter(function(n) {
return this.classList.contains("selected");
});
selected.each(function(n) {
n.visible = false;
});
graph.state.selection.clear();
graph.updateGraphVisibility();
}
zoomSelectionAction(graph) {
graph.viewSelection();
}
toggleTypesAction(graph) {
graph.toggleTypes();
}
searchInputAction(graph) {
if (d3.event.keyCode == 13) {
graph.state.selection.clear();
var query = this.value;
window.sessionStorage.setItem("lastSearch", query);
var reg = new RegExp(query);
var filterFunction = function(n) {
return (reg.exec(n.getDisplayLabel()) != null ||
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
reg.exec(n.opcode) != null);
};
if (d3.event.ctrlKey) {
graph.nodes.forEach(function(n, i) {
if (filterFunction(n)) {
n.visible = true;
}
});
graph.updateGraphVisibility();
}
var selected = graph.visibleNodes.each(function(n) {
if (filterFunction(n)) {
graph.state.selection.select(this, true);
}
});
graph.connectVisibleSelectedNodes();
graph.updateGraphVisibility();
this.blur();
graph.viewSelection();
}
d3.event.stopPropagation();
}
svgMouseDown() {
this.state.graphMouseDown = true;
}
svgMouseUp() {
var graph = this,
state = graph.state;
if (state.justScaleTransGraph) {
// Dragged
state.justScaleTransGraph = false;
} else {
// Clicked
if (state.mouseDownNode == null) {
graph.state.selection.clear();
}
}
state.mouseDownNode = null;
state.graphMouseDown = false;
}
svgKeyDown() {
var state = this.state;
var graph = this;
// Don't handle key press repetition
if(state.lastKeyDown !== -1) return;
var showSelectionFrontierNodes = function(inEdges, filter, select) {
var frontier = graph.getNodeFrontier(state.selection.selection, inEdges, filter);
if (frontier != undefined) {
if (select) {
if (!d3.event.shiftKey) {
state.selection.clear();
}
state.selection.select(frontier[0], true);
}
graph.updateGraphVisibility();
}
allowRepetition = false;
}
var allowRepetition = true;
var eventHandled = true; // unless the below switch defaults
switch(d3.event.keyCode) {
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// '1'-'9'
showSelectionFrontierNodes(true,
(edge, index) => { return index == (d3.event.keyCode - 49); },
false);
break;
case 97:
case 98:
case 99:
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
// 'numpad 1'-'numpad 9'
showSelectionFrontierNodes(true,
(edge, index) => { return index == (d3.event.keyCode - 97); },
false);
break;
case 67:
// 'c'
showSelectionFrontierNodes(true,
(edge, index) => { return edge.type == 'control'; },
false);
break;
case 69:
// 'e'
showSelectionFrontierNodes(true,
(edge, index) => { return edge.type == 'effect'; },
false);
break;
case 79:
// 'o'
showSelectionFrontierNodes(false, undefined, false);
break;
case 73:
// 'i'
showSelectionFrontierNodes(true, undefined, false);
break;
case 65:
// 'a'
graph.selectAllNodes();
allowRepetition = false;
break;
case 38:
case 40: {
showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
break;
}
case 82:
// 'r'
if (!d3.event.ctrlKey) {
this.layoutAction(this);
} else {
eventHandled = false;
}
break;
case 191:
// '/'
document.getElementById("search-input").focus();
document.getElementById("search-input").select();
break;
default:
eventHandled = false;
break;
}
if (eventHandled) {
d3.event.preventDefault();
}
if (!allowRepetition) {
state.lastKeyDown = d3.event.keyCode;
}
}
svgKeyUp() {
this.state.lastKeyDown = -1
};
layoutEdges() {
var graph = this;
graph.maxGraphX = graph.maxGraphNodeX;
this.visibleEdges.attr("d", function(edge){
return edge.generatePath(graph);
});
}
layoutGraph() {
layoutNodeGraph(this);
}
// call to propagate changes to graph
updateGraphVisibility() {
var graph = this,
state = graph.state;
var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); });
var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) {
return edge.stringID();
});
// add new paths
visibleEdges.enter()
.append('path')
.style('marker-end','url(#end-arrow)')
.classed('hidden', function(e) {
return !e.isVisible();
})
.attr("id", function(edge){ return "e," + edge.stringID(); })
.on("mousedown", function(d){
graph.pathMouseDown.call(graph, d3.select(this), d);
})
.attr("adjacentToHover", "false");
// Set the correct styles on all of the paths
visibleEdges.classed('value', function(e) {
return e.type == 'value' || e.type == 'context';
}).classed('control', function(e) {
return e.type == 'control';
}).classed('effect', function(e) {
return e.type == 'effect';
}).classed('frame-state', function(e) {
return e.type == 'frame-state';
}).attr('stroke-dasharray', function(e) {
if (e.type == 'frame-state') return "10,10";
return (e.type == 'effect') ? "5,5" : "";
});
// remove old links
visibleEdges.exit().remove();
graph.visibleEdges = visibleEdges;
// update existing nodes
var filteredNodes = graph.nodes.filter(function(n) { return n.visible; });
graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) {
return d.id;
});
graph.visibleNodes.attr("transform", function(n){
return "translate(" + n.x + "," + n.y + ")";
}).select('rect').
attr(HEIGHT, function(d) { return graph.getNodeHeight(d); });
// add new nodes
var newGs = graph.visibleNodes.enter()
.append("g");
newGs.classed("turbonode", function(n) { return true; })
.classed("control", function(n) { return n.isControl(); })
.classed("live", function(n) { return n.isLive(); })
.classed("dead", function(n) { return !n.isLive(); })
.classed("javascript", function(n) { return n.isJavaScript(); })
.classed("input", function(n) { return n.isInput(); })
.classed("simplified", function(n) { return n.isSimplified(); })
.classed("machine", function(n) { return n.isMachine(); })
.attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";})
.on("mousedown", function(d){
graph.nodeMouseDown.call(graph, d3.select(this), d);
})
.on("mouseup", function(d){
graph.nodeMouseUp.call(graph, d3.select(this), d);
})
.on('mouseover', function(d){
var nodeSelection = d3.select(this);
let node = graph.nodeMap[d.id];
let adjInputEdges = graph.visibleEdges.filter(e => { return e.target === node; });
let adjOutputEdges = graph.visibleEdges.filter(e => { return e.source === node; });
adjInputEdges.attr('relToHover', "input");
adjOutputEdges.attr('relToHover', "output");
let adjInputNodes = adjInputEdges.data().map(e => e.source);
graph.visibleNodes.data(adjInputNodes, function(d) {
return d.id;
}).attr('relToHover', "input");
let adjOutputNodes = adjOutputEdges.data().map(e => e.target);
graph.visibleNodes.data(adjOutputNodes, function(d) {
return d.id;
}).attr('relToHover', "output");
graph.updateGraphVisibility();
})
.on('mouseout', function(d){
var nodeSelection = d3.select(this);
let node = graph.nodeMap[d.id];
let adjEdges = graph.visibleEdges.filter(e => { return e.target === node || e.source === node; });
adjEdges.attr('relToHover', "none");
let adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
let nodes = graph.visibleNodes.data(adjNodes, function(d) {
return d.id;
}).attr('relToHover', "none");
graph.updateGraphVisibility();
})
.call(graph.drag);
newGs.append("rect")
.attr("rx", 10)
.attr("ry", 10)
.attr(WIDTH, function(d) {
return d.getTotalNodeWidth();
})
.attr(HEIGHT, function(d) {
return graph.getNodeHeight(d);
})
function appendInputAndOutputBubbles(g, d) {
for (var i = 0; i < d.inputs.length; ++i) {
var x = d.getInputX(i);
var y = -DEFAULT_NODE_BUBBLE_RADIUS;
var s = g.append('circle')
.classed("filledBubbleStyle", function(c) {
return d.inputs[i].isVisible();
} )
.classed("bubbleStyle", function(c) {
return !d.inputs[i].isVisible();
} )
.attr("id", "ib," + d.inputs[i].stringID())
.attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
.attr("transform", function(d) {
return "translate(" + x + "," + y + ")";
})
.on("mousedown", function(d){
var components = this.id.split(',');
var node = graph.nodeMap[components[3]];
var edge = node.inputs[components[2]];
var visible = !edge.isVisible();
node.setInputVisibility(components[2], visible);
d3.event.stopPropagation();
graph.updateGraphVisibility();
});
}
if (d.outputs.length != 0) {
var x = d.getOutputX();
var y = graph.getNodeHeight(d) + DEFAULT_NODE_BUBBLE_RADIUS;
var s = g.append('circle')
.classed("filledBubbleStyle", function(c) {
return d.areAnyOutputsVisible() == 2;
} )
.classed("halFilledBubbleStyle", function(c) {
return d.areAnyOutputsVisible() == 1;
} )
.classed("bubbleStyle", function(c) {
return d.areAnyOutputsVisible() == 0;
} )
.attr("id", "ob," + d.id)
.attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
.attr("transform", function(d) {
return "translate(" + x + "," + y + ")";
})
.on("mousedown", function(d) {
d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
d3.event.stopPropagation();
graph.updateGraphVisibility();
});
}
}
newGs.each(function(d){
appendInputAndOutputBubbles(d3.select(this), d);
});
newGs.each(function(d){
d3.select(this).append("text")
.classed("label", true)
.attr("text-anchor","right")
.attr("dx", 5)
.attr("dy", 5)
.append('tspan')
.text(function(l) {
return d.getDisplayLabel();
})
.append("title")
.text(function(l) {
return d.getTitle();
})
if (d.type != undefined) {
d3.select(this).append("text")
.classed("label", true)
.classed("type", true)
.attr("text-anchor","right")
.attr("dx", 5)
.attr("dy", d.labelbbox.height + 5)
.append('tspan')
.text(function(l) {
return d.getDisplayType();
})
.append("title")
.text(function(l) {
return d.getType();
})
}
});
graph.visibleNodes.select('.type').each(function (d) {
this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
});
// remove old nodes
graph.visibleNodes.exit().remove();
graph.visibleBubbles = d3.selectAll('circle');
graph.updateInputAndOutputBubbles();
graph.layoutEdges();
graph.svg.style.height = '100%';
}
getVisibleTranslation(translate, scale) {
var graph = this;
var height = (graph.maxGraphY - graph.minGraphY + 2 * GRAPH_MARGIN) * scale;
var width = (graph.maxGraphX - graph.minGraphX + 2 * GRAPH_MARGIN) * scale;
var dimensions = this.getSvgViewDimensions();
var baseY = translate[1];
var minY = (graph.minGraphY - GRAPH_MARGIN) * scale;
var maxY = (graph.maxGraphY + GRAPH_MARGIN) * scale;
var adjustY = 0;
var adjustYCandidate = 0;
if ((maxY + baseY) < dimensions[1]) {
adjustYCandidate = dimensions[1] - (maxY + baseY);
if ((minY + baseY + adjustYCandidate) > 0) {
adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
} else {
adjustY = adjustYCandidate;
}
} else if (-baseY < minY) {
adjustYCandidate = -(baseY + minY);
if ((maxY + baseY + adjustYCandidate) < dimensions[1]) {
adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
} else {
adjustY = adjustYCandidate;
}
}
translate[1] += adjustY;
var baseX = translate[0];
var minX = (graph.minGraphX - GRAPH_MARGIN) * scale;
var maxX = (graph.maxGraphX + GRAPH_MARGIN) * scale;
var adjustX = 0;
var adjustXCandidate = 0;
if ((maxX + baseX) < dimensions[0]) {
adjustXCandidate = dimensions[0] - (maxX + baseX);
if ((minX + baseX + adjustXCandidate) > 0) {
adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
} else {
adjustX = adjustXCandidate;
}
} else if (-baseX < minX) {
adjustXCandidate = -(baseX + minX);
if ((maxX + baseX + adjustXCandidate) < dimensions[0]) {
adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
} else {
adjustX = adjustXCandidate;
}
}
translate[0] += adjustX;
return translate;
}
translateClipped(translate, scale, transition) {
var graph = this;
var graphNode = this.graphElement[0][0];
var translate = this.getVisibleTranslation(translate, scale);
if (transition) {
graphNode.classList.add('visible-transition');
clearTimeout(graph.transitionTimout);
graph.transitionTimout = setTimeout(function(){
graphNode.classList.remove('visible-transition');
}, 1000);
}
var translateString = "translate(" + translate[0] + "px," + translate[1] + "px) scale(" + scale + ")";
graphNode.style.transform = translateString;
graph.dragSvg.translate(translate);
graph.dragSvg.scale(scale);
}
zoomed(){
this.state.justScaleTransGraph = true;
var scale = this.dragSvg.scale();
this.translateClipped(d3.event.translate, scale);
}
getSvgViewDimensions() {
var canvasWidth = this.parentNode.clientWidth;
var documentElement = document.documentElement;
var canvasHeight = documentElement.clientHeight;
return [canvasWidth, canvasHeight];
}
minScale() {
var graph = this;
var dimensions = this.getSvgViewDimensions();
var width = graph.maxGraphX - graph.minGraphX;
var height = graph.maxGraphY - graph.minGraphY;
var minScale = dimensions[0] / (width + GRAPH_MARGIN * 2);
var minScaleYCandidate = dimensions[1] / (height + GRAPH_MARGIN * 2);
if (minScaleYCandidate < minScale) {
minScale = minScaleYCandidate;
}
this.dragSvg.scaleExtent([minScale, 1.5]);
return minScale;
}
fitGraphViewToWindow() {
this.svg.attr("height", document.documentElement.clientHeight + "px");
this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale());
}
toggleTypes() {
var graph = this;
graph.state.showTypes = !graph.state.showTypes;
var element = document.getElementById('toggle-types');
if (graph.state.showTypes) {
element.classList.add('button-input-toggled');
} else {
element.classList.remove('button-input-toggled');
}
graph.updateGraphVisibility();
}
viewSelection() {
var graph = this;
var minX, maxX, minY, maxY;
var hasSelection = false;
graph.visibleNodes.each(function(n) {
if (this.classList.contains("selected")) {
hasSelection = true;
minX = minX ? Math.min(minX, n.x) : n.x;
maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
n.x + n.getTotalNodeWidth();
minY = minY ? Math.min(minY, n.y) : n.y;
maxY = maxY ? Math.max(maxY, n.y + graph.getNodeHeight(n)) :
n.y + graph.getNodeHeight(n);
}
});
if (hasSelection) {
graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
maxX + NODE_INPUT_WIDTH, maxY + 60,
true);
}
}
viewGraphRegion(minX, minY, maxX, maxY, transition) {
var graph = this;
var dimensions = this.getSvgViewDimensions();
var width = maxX - minX;
var height = maxY - minY;
var scale = Math.min(dimensions[0] / width, dimensions[1] / height);
scale = Math.min(1.5, scale);
scale = Math.max(graph.minScale(), scale);
var translation = [-minX*scale, -minY*scale];
translation = graph.getVisibleTranslation(translation, scale);
graph.translateClipped(translation, scale, transition);
}
viewWholeGraph() {
var graph = this;
var minScale = graph.minScale();
var translation = [0, 0];
translation = graph.getVisibleTranslation(translation, minScale);
graph.translateClipped(translation, minScale);
}
}
================================================
FILE: index.html
================================================
<!DOCTYPE HTML>
<html>
<head>
<title>Turbolizer</title>
<link rel="stylesheet" href="turbo-visualizer.css" />
</head>
<body>
<div id="left" class="viewpane">
<div id='source-text'>
<pre id='source-text-pre'\>
</div>
</div>
<div class="resizer-left"></div>
<div id="middle" class="viewpane">
<div id="graph-toolbox-anchor">
<span id="graph-toolbox">
<input id="layout" type="image" title="layout graph" src="layout-icon.png"
alt="layout graph" class="button-input">
<input id="show-all" type="image" title="show all nodes" src="expand-all.jpg"
alt="show all nodes" class="button-input">
<input id="hide-dead" type="image" title="only live nodes" src="live.png"
alt="only live nodes" class="button-input">
<input id="hide-unselected" type="image" title="hide unselected nodes"
src="hide-unselected.png" alt="hide unselected nodes" class="button-input">
<input id="hide-selected" type="image" title="hide selected nodes"
src="hide-selected.png" alt="hide selected nodes" class="button-input">
<input id="zoom-selection" type="image" title="zoom to selection"
src="search.png" alt="zoom to selection" class="button-input">
<input id="toggle-types" type="image" title="show/hide types"
src="types.png" alt="show/hide types" class="button-input">
<input id="search-input" type="text" title="search nodes for regex"
alt="search node for regex" class="search-input"
placeholder="find with regexp…">
<select id="display-selector">
<option disabled selected>(please open a file)</option>
</select>
</span>
</div>
<div id="load-file">
<input type="file" id="hidden-file-upload">
<input id="upload" type="image" title="load graph" class="button-input"
src="upload-icon.png" alt="upload graph">
</div>
<div id="empty" width="100%" height="100%"></div>
<div id="graph" width="100%" height="100%"></div>
<div id="schedule" width="100%">
<pre id="schedule-text-pre" class='prettyprint prettyprinted'>
<ul id="schedule-list" class='nolinenums noindent'>
</ul>
</pre>
</div>
<div id='text-placeholder' width="0px" height="0px" style="position: absolute; top:100000px;" ><svg><text text-anchor="right">
<tspan white-space="inherit" id="text-measure"/>
</text></svg></div>
</div>
<div class="resizer-right"></div>
<div id="right" class="viewpane">
<div id='disassembly'>
<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
<ul id='disassembly-list' class='nolinenums noindent'>
</ul>
</pre>
</div>
</div>
<div id="source-collapse" class="collapse-pane">
<input id="source-expand" type="image" title="show source"
src="right-arrow.png" class="button-input invisible">
<input id="source-shrink" type="image" title="hide source"
src="left-arrow.png" class="button-input">
</div>
<div id="disassembly-collapse" class="collapse-pane">
<input id="disassembly-expand" type="image" title="show disassembly"
src="left-arrow.png" class="button-input invisible">
<input id="disassembly-shrink" type="image" title="hide disassembly"
src="right-arrow.png" class="button-input">
</div>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/filesaver.js/0.1/FileSaver.min.js"></script>
<script src="monkey.js"></script>
<script src="util.js"></script>
<script src="lang-disassembly.js"></script>
<script src="node.js"></script>
<script src="edge.js"></script>
<script src="selection.js"></script>
<script src="selection-broker.js"></script>
<script src="constants.js"></script>
<script src="view.js"></script>
<script src="text-view.js"></script>
<script src="empty-view.js"></script>
<script src="code-view.js"></script>
<script src="graph-layout.js"></script>
<script src="graph-view.js"></script>
<script src="schedule-view.js"></script>
<script src="disassembly-view.js"></script>
<script src="turbo-visualizer.js"></script>
<script src="lib/client-loader.js"></script>
</body>
</html>
================================================
FILE: lang-disassembly.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
PR.registerLangHandler(
PR.createSimpleLexer(
[
[PR.PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$))/, null, '\''],
[PR.PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']
],
[ // fallthroughStylePatterns
[PR.PR_COMMENT, /;; debug: position \d+/, null],
]),
['disassembly']);
================================================
FILE: lib/client-loader.js
================================================
'use strict'
/* global location fetch */
async function preload() {
try {
if (!(/^\?preload=/).test(location.search)) return
const fileUrl = location.search.replace(/^\?preload=/, '')
const fileData = await fetch(fileUrl, { mode: 'no-cors' })
const txt = await fileData.text()
window.renderTurbolizerData(txt)
} catch (err) {
console.error(err)
}
}
preload()
================================================
FILE: lib/turbolizer.js
================================================
'use strict'
const { promisify } = require('util')
const path = require('path')
const fs = require('fs')
const readdir = promisify(fs.readdir)
const stat = promisify(fs.stat)
const access = promisify(fs.access)
async function canRead(p) {
try {
await access(p, fs.constants.R_OK)
return true
} catch (err) {
return false
}
}
async function findAllTurboFiles(root) {
const allEntries = await readdir(root)
const turboFiles = []
for (const entry of allEntries) {
if (!(/^turbo-.+\.json$/).test(entry)) continue
const fullPath = path.join(root, entry)
if (!(await canRead(fullPath))) continue
if (!(await stat(fullPath)).isFile()) continue
turboFiles.push({ fullPath, entry })
}
return turboFiles
}
function makeSelectable(arr) {
const map = new Map()
for (var i = 0; i < arr.length; i++) {
map.set(`${i + 1}`, arr[i])
}
return map
}
async function mapAllTurboFiles(root) {
const arr = await findAllTurboFiles(root)
return makeSelectable(arr)
}
module.exports = {
findAllTurboFiles
, mapAllTurboFiles
}
================================================
FILE: lib/turbolizer.server.js
================================================
'use strict'
const path = require('path')
const ecstatic = require('ecstatic')
const connect = require('connect')
const opener = require('opener')
function createServer(turboFilesDir, PORT = null) {
const appDir = path.join(__dirname, '..')
const ecstaticApp = ecstatic({ root: appDir })
const ecstaticFiles = ecstatic({ root: turboFilesDir })
const app = connect()
.use('/', ecstaticApp)
.use('/data/', ecstaticFiles)
const server = app.listen(PORT)
const { port } = server.address()
const address = `http://localhost:${port}`
return { server, address }
}
function openWithFile({ address, file }) {
const url = `${address}?preload=${address}/data/${file}`
opener(url)
}
module.exports = { createServer, openWithFile }
================================================
FILE: monkey.js
================================================
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Math.alignUp = function(raw, multiple) {
return Math.floor((raw + multiple - 1) / multiple) * multiple;
}
================================================
FILE: node.js
================================================
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var TYPE_HEIGHT = 25;
var DEFAULT_NODE_BUBBLE_RADIUS = 12;
var NODE_INPUT_WIDTH = 50;
var MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS;
var MINIMUM_NODE_OUTPUT_APPROACH = 15;
function isNodeInitiallyVisible(node) {
return node.cfg;
}
var Node = {
isControl: function() {
return this.control;
},
isInput: function() {
return this.opcode == 'Parameter' || this.opcode.endsWith('Constant');
},
isLive: function() {
return this.live !== false;
},
isJavaScript: function() {
return this.opcode.startsWith('JS');
},
isSimplified: function() {
if (this.isJavaScript()) return false;
return this.opcode.endsWith('Phi') ||
this.opcode.startsWith('Boolean') ||
this.opcode.startsWith('Number') ||
this.opcode.startsWith('String') ||
this.opcode.startsWith('Change') ||
this.opcode.startsWith('Object') ||
this.opcode.startsWith('Reference') ||
this.opcode.startsWith('Any') ||
this.opcode.endsWith('ToNumber') ||
(this.opcode == 'AnyToBoolean') ||
(this.opcode.startsWith('Load') && this.opcode.length > 4) ||
(this.opcode.startsWith('Store') && this.opcode.length > 5);
},
isMachine: function() {
return !(this.isControl() || this.isInput() ||
this.isJavaScript() || this.isSimplified());
},
getTotalNodeWidth: function() {
var inputWidth = this.inputs.length * NODE_INPUT_WIDTH;
return Math.max(inputWidth, this.width);
},
getTitle: function() {
var propsString;
if (this.properties === undefined) {
propsString = "";
} else if (this.properties === "") {
propsString = "no properties";
} else {
propsString = "[" + this.properties + "]";
}
return this.title + "\n" + propsString + "\n" + this.opinfo;
},
getDisplayLabel: function() {
var result = this.id + ":" + this.label;
if (result.length > 40) {
return this.id + ":" + this.opcode;
} else {
return result;
}
},
getType: function() {
return this.type;
},
getDisplayType: function() {
var type_string = this.type;
if (type_string == undefined) return "";
if (type_string.length > 24) {
type_string = type_string.substr(0, 25) + "...";
}
return type_string;
},
deepestInputRank: function() {
var deepestRank = 0;
this.inputs.forEach(function(e) {
if (e.isVisible() && !e.isBackEdge()) {
if (e.source.rank > deepestRank) {
deepestRank = e.source.rank;
}
}
});
return deepestRank;
},
areAnyOutputsVisible: function() {
var visibleCount = 0;
this.outputs.forEach(function(e) { if (e.isVisible()) ++visibleCount; });
if (this.outputs.length == visibleCount) return 2;
if (visibleCount != 0) return 1;
return 0;
},
setOutputVisibility: function(v) {
var result = false;
this.outputs.forEach(function(e) {
e.visible = v;
if (v) {
if (!e.target.visible) {
e.target.visible = true;
result = true;
}
}
});
return result;
},
setInputVisibility: function(i, v) {
var edge = this.inputs[i];
edge.visible = v;
if (v) {
if (!edge.source.visible) {
edge.source.visible = true;
return true;
}
}
return false;
},
getInputApproach: function(index) {
return this.y - MINIMUM_NODE_INPUT_APPROACH -
(index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS
},
getOutputApproach: function(graph, index) {
return this.y + this.outputApproach + graph.getNodeHeight(this) +
+ DEFAULT_NODE_BUBBLE_RADIUS;
},
getInputX: function(index) {
var result = this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2) +
(index - this.inputs.length + 1) * NODE_INPUT_WIDTH;
return result;
},
getOutputX: function() {
return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2);
},
getFunctionRelativeSourcePosition: function(graph) {
return this.pos - graph.sourcePosition;
},
hasBackEdges: function() {
return (this.opcode == "Loop") ||
((this.opcode == "Phi" || this.opcode == "EffectPhi") &&
this.inputs[this.inputs.length - 1].source.opcode == "Loop");
}
};
================================================
FILE: package.json
================================================
{
"name": "turbolizer",
"version": "0.2.0",
"description": "Turbolizer tool from the v8 repository with added support to preload a profile",
"main": "lib/turbolizer.js",
"bin": "bin/turbolizer",
"repository": {
"type": "git",
"url": "git://github.com/thlorenz/turbolizer.git"
},
"homepage": "https://github.com/thlorenz/turbolizer",
"dependencies": {
"connect": "~3.6.6",
"ecstatic": "~4.1.4",
"opener": "~1.4.3",
"promptly": "~3.0.3"
},
"devDependencies": {},
"keywords": [],
"author": {
"name": "Thorsten Lorenz",
"email": "thlorenz@gmx.de",
"url": "http://thlorenz.com"
},
"license": {
"type": "MIT",
"url": "https://github.com/thlorenz/turbolizer/blob/master/LICENSE"
},
"engine": {
"node": ">=8"
}
}
================================================
FILE: schedule-view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class ScheduleView extends TextView {
constructor(id, broker) {
super(id, broker, null, false);
let view = this;
let BLOCK_STYLE = {
css: 'tag'
};
const BLOCK_HEADER_STYLE = {
css: 'com',
block_id: -1,
location: function(text) {
let matches = /\d+/.exec(text);
if (!matches) return undefined;
BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
return {
block_id: BLOCK_HEADER_STYLE.block_id
};
},
};
const BLOCK_LINK_STYLE = {
css: 'tag',
link: function(text) {
let id = Number(text.substr(1));
view.select(function(location) { return location.block_id == id; }, true, true);
}
};
const ID_STYLE = {
css: 'tag',
location: function(text) {
let matches = /\d+/.exec(text);
return {
node_id: Number(matches[0]),
block_id: BLOCK_HEADER_STYLE.block_id
};
},
};
const ID_LINK_STYLE = {
css: 'tag',
link: function(text) {
let id = Number(text);
view.select(function(location) { return location.node_id == id; }, true, true);
}
};
const NODE_STYLE = { css: 'kwd' };
const GOTO_STYLE = { css: 'kwd',
goto_id: -2,
location: function(text) {
return {
node_id: GOTO_STYLE.goto_id--,
block_id: BLOCK_HEADER_STYLE.block_id
};
}
}
const ARROW_STYLE = { css: 'kwd' };
let patterns = [
[
[/^--- BLOCK B\d+/, BLOCK_HEADER_STYLE, 1],
[/^\s+\d+: /, ID_STYLE, 2],
[/^\s+Goto/, GOTO_STYLE, 6],
[/^.*/, null, -1]
],
[
[/^ +/, null],
[/^\(deferred\)/, BLOCK_HEADER_STYLE],
[/^B\d+/, BLOCK_LINK_STYLE],
[/^<-/, ARROW_STYLE],
[/^->/, ARROW_STYLE],
[/^,/, null],
[/^---/, BLOCK_HEADER_STYLE, -1]
],
// Parse opcode including []
[
[/^[A-Za-z0-9_]+(\[.*\])?$/, NODE_STYLE, -1],
[/^[A-Za-z0-9_]+(\[(\[.*?\]|.)*?\])?/, NODE_STYLE, 3]
],
// Parse optional parameters
[
[/^ /, null, 4],
[/^\(/, null],
[/^\d+/, ID_LINK_STYLE],
[/^, /, null],
[/^\)$/, null, -1],
[/^\)/, null, 4],
],
[
[/^ -> /, ARROW_STYLE, 5],
[/^.*/, null, -1]
],
[
[/^B\d+$/, BLOCK_LINK_STYLE, -1],
[/^B\d+/, BLOCK_LINK_STYLE],
[/^, /, null]
],
[
[/^ -> /, ARROW_STYLE],
[/^B\d+$/, BLOCK_LINK_STYLE, -1]
]
];
this.setPatterns(patterns);
}
initializeContent(data, rememberedSelection) {
super.initializeContent(data, rememberedSelection);
var graph = this;
var locations = [];
for (var id of rememberedSelection) {
locations.push({ node_id : id });
}
this.selectLocations(locations, true, true);
}
detachSelection() {
var selection = this.selection.detachSelection();
var s = new Set();
for (var i of selection) {
if (i.location.node_id != undefined && i.location.node_id > 0) {
s.add(i.location.node_id);
}
};
return s;
}
}
================================================
FILE: selection-broker.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var SelectionBroker = function() {
this.brokers = [];
this.dispatching = false;
this.lastDispatchingHandler = null;
this.nodePositionMap = [];
this.sortedPositionList = [];
this.positionNodeMap = [];
};
SelectionBroker.prototype.addSelectionHandler = function(handler) {
this.brokers.push(handler);
}
SelectionBroker.prototype.setNodePositionMap = function(map) {
let broker = this;
if (!map) return;
broker.nodePositionMap = map;
broker.positionNodeMap = [];
broker.sortedPositionList = [];
let next = 0;
for (let i in broker.nodePositionMap) {
broker.sortedPositionList[next] = Number(broker.nodePositionMap[i]);
broker.positionNodeMap[next++] = i;
}
broker.sortedPositionList = sortUnique(broker.sortedPositionList,
function(a,b) { return a - b; });
this.positionNodeMap.sort(function(a,b) {
let result = broker.nodePositionMap[a] - broker.nodePositionMap[b];
if (result != 0) return result;
return a - b;
});
}
SelectionBroker.prototype.select = function(from, locations, selected) {
let broker = this;
if (!broker.dispatching) {
broker.lastDispatchingHandler = from;
try {
broker.dispatching = true;
let enrichLocations = function(locations) {
result = [];
for (let location of locations) {
let newLocation = {};
if (location.pos_start != undefined) {
newLocation.pos_start = location.pos_start;
}
if (location.pos_end != undefined) {
newLocation.pos_end = location.pos_end;
}
if (location.node_id != undefined) {
newLocation.node_id = location.node_id;
}
if (location.block_id != undefined) {
newLocation.block_id = location.block_id;
}
if (newLocation.pos_start == undefined &&
newLocation.pos_end == undefined &&
newLocation.node_id != undefined) {
if (broker.nodePositionMap && broker.nodePositionMap[location.node_id]) {
newLocation.pos_start = broker.nodePositionMap[location.node_id];
newLocation.pos_end = location.pos_start + 1;
}
}
result.push(newLocation);
}
return result;
}
locations = enrichLocations(locations);
for (var b of this.brokers) {
if (b != from) {
b.brokeredSelect(locations, selected);
}
}
}
finally {
broker.dispatching = false;
}
}
}
SelectionBroker.prototype.clear = function(from) {
this.lastDispatchingHandler = null;
if (!this.dispatching) {
try {
this.dispatching = true;
this.brokers.forEach(function(b) {
if (b != from) {
b.brokeredClear();
}
});
} finally {
this.dispatching = false;
}
}
}
================================================
FILE: selection.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var Selection = function(handler) {
this.handler = handler;
this.selectionBase = null;
this.lastSelection = null;
this.selection = new Set();
}
Selection.prototype.isEmpty = function() {
return this.selection.size == 0;
}
Selection.prototype.clear = function() {
var handler = this.handler;
this.selectionBase = null;
this.lastSelection = null;
handler.select(this.selection, false);
handler.clear();
this.selection = new Set();
}
count = 0;
Selection.prototype.select = function(s, isSelected) {
var handler = this.handler;
if (!(Symbol.iterator in Object(s))) { s = [s]; }
if (isSelected) {
let first = true;
for (let i of s) {
if (first) {
this.selectionBase = i;
this.lastSelection = i;
first = false;
}
this.selection.add(i);
}
handler.select(this.selection, true);
} else {
let unselectSet = new Set();
for (let i of s) {
if (this.selection.has(i)) {
unselectSet.add(i);
this.selection.delete(i);
}
}
handler.select(unselectSet, false);
}
}
Selection.prototype.extendTo = function(pos) {
if (pos == this.lastSelection || this.lastSelection === null) return;
var handler = this.handler;
var pos_diff = handler.selectionDifference(pos, true, this.lastSelection, false);
var unselect_diff = [];
if (pos_diff.length == 0) {
pos_diff = handler.selectionDifference(this.selectionBase, false, pos, true);
if (pos_diff.length != 0) {
unselect_diff = handler.selectionDifference(this.lastSelection, true, this.selectionBase, false);
this.selection = new Set();
this.selection.add(this.selectionBase);
for (var d of pos_diff) {
this.selection.add(d);
}
} else {
unselect_diff = handler.selectionDifference(this.lastSelection, true, pos, false);
for (var d of unselect_diff) {
this.selection.delete(d);
}
}
} else {
unselect_diff = handler.selectionDifference(this.selectionBase, false, this.lastSelection, true);
if (unselect_diff != 0) {
pos_diff = handler.selectionDifference(pos, true, this.selectionBase, false);
if (pos_diff.length == 0) {
unselect_diff = handler.selectionDifference(pos, false, this.lastSelection, true);
}
for (var d of unselect_diff) {
this.selection.delete(d);
}
}
if (pos_diff.length != 0) {
for (var d of pos_diff) {
this.selection.add(d);
}
}
}
handler.select(unselect_diff, false);
handler.select(pos_diff, true);
this.lastSelection = pos;
}
Selection.prototype.detachSelection = function() {
var result = new Set();
for (var i of this.selection) {
result.add(i);
}
this.clear();
return result;
}
================================================
FILE: text-view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class TextView extends View {
constructor(id, broker, patterns, allowSpanSelection) {
super(id, broker);
let view = this;
view.hide();
view.textListNode = view.divNode.getElementsByTagName('ul')[0];
view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0");
view.patterns = patterns;
view.allowSpanSelection = allowSpanSelection;
view.nodeToLineMap = [];
var selectionHandler = {
clear: function() {
broker.clear(selectionHandler);
},
select: function(items, selected) {
for (let i of items) {
if (selected) {
i.classList.add("selected");
} else {
i.classList.remove("selected");
}
}
broker.clear(selectionHandler);
broker.select(selectionHandler, view.getLocations(items), selected);
},
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
return null;
},
brokeredSelect: function(locations, selected) {
view.selectLocations(locations, selected, true);
},
brokeredClear: function() {
view.selection.clear();
}
};
view.selection = new Selection(selectionHandler);
broker.addSelectionHandler(selectionHandler);
}
setPatterns(patterns) {
let view = this;
view.patterns = patterns;
}
clearText() {
let view = this;
while (view.textListNode.firstChild) {
view.textListNode.removeChild(view.textListNode.firstChild);
}
}
sameLocation(l1, l2) {
let view = this;
if (l1.block_id != undefined && l2.block_id != undefined &&
l1.block_id == l2.block_id && l1.node_id === undefined) {
return true;
}
if (l1.address != undefined && l1.address == l2.address) {
return true;
}
let node1 = l1.node_id;
let node2 = l2.node_id;
if (node1 === undefined || node2 == undefined) {
if (l1.pos_start === undefined || l2.pos_start == undefined) {
return false;
}
if (l1.pos_start == -1 || l2.pos_start == -1) {
return false;
}
if (l1.pos_start < l2.pos_start) {
return l1.pos_end > l2.pos_start;
} {
return l1.pos_start < l2.pos_end;
}
}
return l1.node_id == l2.node_id;
}
selectLocations(locations, selected, makeVisible) {
let view = this;
let s = new Set();
for (let l of locations) {
for (let i = 0; i < view.textListNode.children.length; ++i) {
let child = view.textListNode.children[i];
if (child.location != undefined && view.sameLocation(l, child.location)) {
s.add(child);
}
}
}
view.selectCommon(s, selected, makeVisible);
}
getLocations(items) {
let result = [];
let lastObject = null;
for (let i of items) {
if (i.location) {
result.push(i.location);
}
}
return result;
}
createFragment(text, style) {
let view = this;
let span = document.createElement("SPAN");
span.onmousedown = function(e) {
view.mouseDownSpan(span, e);
}
if (style != undefined) {
span.classList.add(style);
}
span.innerHTML = text;
return span;
}
appendFragment(li, fragment) {
li.appendChild(fragment);
}
processLine(line) {
let view = this;
let result = [];
let patternSet = 0;
while (true) {
let beforeLine = line;
for (let pattern of view.patterns[patternSet]) {
let matches = line.match(pattern[0]);
if (matches != null) {
if (matches[0] != '') {
let style = pattern[1] != null ? pattern[1] : {};
let text = matches[0];
if (text != '') {
let fragment = view.createFragment(matches[0], style.css);
if (style.link) {
fragment.classList.add('linkable-text');
fragment.link = style.link;
}
result.push(fragment);
if (style.location != undefined) {
let location = style.location(text);
if (location != undefined) {
fragment.location = location;
}
}
}
line = line.substr(matches[0].length);
}
let nextPatternSet = patternSet;
if (pattern.length > 2) {
nextPatternSet = pattern[2];
}
if (line == "") {
if (nextPatternSet != -1) {
throw("illegal parsing state in text-view in patternSet" + patternSet);
}
return result;
}
patternSet = nextPatternSet;
break;
}
}
if (beforeLine == line) {
throw("input not consumed in text-view in patternSet" + patternSet);
}
}
}
select(s, selected, makeVisible) {
let view = this;
view.selection.clear();
view.selectCommon(s, selected, makeVisible);
}
selectCommon(s, selected, makeVisible) {
let view = this;
let firstSelect = makeVisible && view.selection.isEmpty();
if ((typeof s) === 'function') {
for (let i = 0; i < view.textListNode.children.length; ++i) {
let child = view.textListNode.children[i];
if (child.location && s(child.location)) {
if (firstSelect) {
makeContainerPosVisible(view.parentNode, child.offsetTop);
firstSelect = false;
}
view.selection.select(child, selected);
}
}
} else if (typeof s[Symbol.iterator] === 'function') {
if (firstSelect) {
for (let i of s) {
makeContainerPosVisible(view.parentNode, i.offsetTop);
break;
}
}
view.selection.select(s, selected);
} else {
if (firstSelect) {
makeContainerPosVisible(view.parentNode, s.offsetTop);
}
view.selection.select(s, selected);
}
}
mouseDownLine(li, e) {
let view = this;
e.stopPropagation();
if (!e.shiftKey) {
view.selection.clear();
}
if (li.location != undefined) {
view.selectLocations([li.location], true, false);
}
}
mouseDownSpan(span, e) {
let view = this;
if (view.allowSpanSelection) {
e.stopPropagation();
if (!e.shiftKey) {
view.selection.clear();
}
select(li, true);
} else if (span.link) {
span.link(span.textContent);
e.stopPropagation();
}
}
processText(text) {
let view = this;
let textLines = text.split(/[\n]/);
let lineNo = 0;
for (let line of textLines) {
let li = document.createElement("LI");
li.onmousedown = function(e) {
view.mouseDownLine(li, e);
}
li.className = "nolinenums";
li.lineNo = lineNo++;
let fragments = view.processLine(line);
for (let fragment of fragments) {
view.appendFragment(li, fragment);
}
let lineLocation = view.lineLocation(li);
if (lineLocation != undefined) {
li.location = lineLocation;
}
view.textListNode.appendChild(li);
}
}
initializeContent(data, rememberedSelection) {
let view = this;
view.selection.clear();
view.clearText();
view.processText(data);
var fillerSize = document.documentElement.clientHeight -
view.textListNode.clientHeight;
if (fillerSize < 0) {
fillerSize = 0;
}
view.fillerSvgElement.attr("height", fillerSize);
}
deleteContent() {
}
isScrollable() {
return true;
}
detachSelection() {
return null;
}
lineLocation(li) {
let view = this;
for (let i = 0; i < li.children.length; ++i) {
let fragment = li.children[i];
if (fragment.location != undefined && !view.allowSpanSelection) {
return fragment.location;
}
}
}
}
================================================
FILE: turbo-visualizer.css
================================================
.visible-transition {
transition-delay: 0s;
transition-duration: 1s;
transition-property: all;
transition-timing-function: ease;
}
.collapse-pane {
background: #A0A0A0;
bottom: 0;
position: absolute;
margin-bottom: 0.5em;
margin-right: 0.5em;
margin-left: 0.5em;
border-radius: 5px;
padding: 0.5em;
z-index: 5;
opacity: 0.7;
cursor: pointer;
}
.search-input {
vertical-align: middle;
width: 145px;
opacity: 1;
}
.button-input {
vertical-align: middle;
width: 24px;
opacity: 0.4;
cursor: pointer;
}
.button-input-toggled {
border-radius: 5px;
background-color: #505050;
}
.button-input:focus {
outline: none;
}
.invisible {
display: none;
}
.selected {
background-color: #FFFF33;
}
.prettyprint ol.linenums > li {
list-style-type: decimal;
!important
}
body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
overflow:hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
p {
text-align: center;
overflow: overlay;
position: relative;
}
marker {
fill: #080808;
}
g rect {
fill: #F0F0F0;
stroke: #080808;
stroke-width: 2px;
}
g.dead {
opacity: .5;
}
g.unsorted rect {
opacity: 0.5;
}
div.scrollable {
overflow-y: _croll; overflow-x: hidden;
}
g.turbonode[relToHover="input"] rect {
stroke: #67e62c;
stroke-width: 16px;
}
g.turbonode[relToHover="output"] rect {
stroke: #d23b14;
stroke-width: 16px;
}
path[relToHover="input"] {
stroke: #67e62c;
stroke-width: 16px;
}
path[relToHover="output"] {
stroke: #d23b14;
stroke-width: 16px;
}
g.turbonode:hover rect {
stroke: #000000;
stroke-width: 7px;
}
g.control rect {
fill: #EFCC00;
stroke: #080808;
stroke-width: 5px;
}
g.javascript rect {
fill: #DD7E6B;
}
g.simplified rect {
fill: #3C78D8;
}
g.machine rect {
fill: #6AA84F;
}
g.input rect {
fill: #CFE2F3;
}
g.selected rect {
fill: #FFFF33;
}
circle.bubbleStyle {
fill: #080808;
fill-opacity: 0.0;
stroke: #080808;
stroke-width: 2px;
}
circle.bubbleStyle:hover {
stroke-width: 3px;
}
circle.filledBubbleStyle {
fill: #080808;
stroke: #080808;
stroke-width: 2px;
}
circle.filledBubbleStyle:hover {
fill: #080808;
stroke-width: 3px;
}
circle.halfFilledBubbleStyle {
fill: #808080;
stroke: #101010;
stroke-width: 2px;
}
circle.halfFilledBubbleStyle:hover {
fill: #808080;
stroke-width: 3px;
}
path {
fill: none;
stroke: #080808;
stroke-width: 4px;
cursor: default;
}
path:hover {
stroke-width: 6px;
}
path.hidden {
fill: none;
stroke-width: 0;
}
path.link.selected {
stroke: #FFFF33;
}
pre.prettyprint {
border: none !important;
padding: 0px;
}
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
background: none !important
}
li.nolinenums {
list-style-type:none;
}
ul.noindent {
-webkit-padding-start: 0px;
-webkit-margin-before: 0px;
-webkit-margin-after: 0px;
}
input:hover, .collapse-pane:hover input {
opacity: 1;
cursor: pointer;
}
span.linkable-text {
text-decoration: underline;
}
span.linkable-text:hover {
cursor: pointer;
font-weight: bold;
}
#left {
float: left;
}
#middle {
float:left; background-color: #F8F8F8;
}
#right {
float: right;
}
.viewpane {
height: 100vh;
background-color: #FFFFFF;
}
#disassembly-collapse {
right: 0;
}
#source-collapse {
left: 0;
}
#graph-toolbox-anchor {
height: 0px;
}
#graph-toolbox {
position: relative;
top: 1em;
left: 25px;
border: 2px solid #eee8d5;
border-radius: 5px;
padding: 0.7em;
z-index: 5;
background: rgba(100%, 100%, 100%, 0.7);
}
#disassembly-toolbox {
position: relative;
top: 1em;
left: 0.7em;
border: 2px solid #eee8d5;
border-radius: 5px;
padding: 0.7em;
z-index: 5;
}
#load-file {
position: absolute;
top: 0;
right: 0;
margin-top: 0.5em;
margin-right: 0.5em;
z-index: 5;
opacity: 0.7;
}
#load-file input {
background: #A0A0A0;
border-radius: 5px;
padding: 0.5em;
}
#hidden-file-upload {
display: none;
}
.prof {
cursor: default;
}
tspan {
font-size: 500%;
font-family: sans-serif;
}
text {
dominant-baseline: text-before-edge;
}
.resizer-left {
position:absolute;
width: 4px;
height:100%;
background: #a0a0a0;
cursor: pointer;
}
.resizer-left.snapped {
width: 12px;
}
.resizer-left:hover {
background: orange;
}
.resizer-left.dragged {
background: orange;
}
.resizer-right {
position:absolute;
width: 4px;
height:100%;
background: #a0a0a0;
cursor: pointer;
}
.resizer-right.snapped {
width: 12px;
}
.resizer-right:hover {
background: orange;
}
.resizer-right.dragged {
background: orange;
}
================================================
FILE: turbo-visualizer.js
================================================
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class Snapper {
constructor(resizer) {
let snapper = this;
snapper.resizer = resizer;
snapper.sourceExpand = d3.select("#" + SOURCE_EXPAND_ID);
snapper.sourceCollapse = d3.select("#" + SOURCE_COLLAPSE_ID);
snapper.disassemblyExpand = d3.select("#" + DISASSEMBLY_EXPAND_ID);
snapper.disassemblyCollapse = d3.select("#" + DISASSEMBLY_COLLAPSE_ID);
d3.select("#source-collapse").on("click", function(){
resizer.snapper.toggleSourceExpanded();
});
d3.select("#disassembly-collapse").on("click", function(){
resizer.snapper.toggleDisassemblyExpanded();
});
}
getLastExpandedState(type, default_state) {
var state = window.sessionStorage.getItem("expandedState-"+type);
if (state === null) return default_state;
return state === 'true';
}
setLastExpandedState(type, state) {
window.sessionStorage.setItem("expandedState-"+type, state);
}
toggleSourceExpanded() {
this.setSourceExpanded(!this.sourceExpand.classed("invisible"));
}
sourceExpandUpdate(newState) {
this.setLastExpandedState("source", newState);
this.sourceExpand.classed("invisible", newState);
this.sourceCollapse.classed("invisible", !newState);
}
setSourceExpanded(newState) {
if (this.sourceExpand.classed("invisible") === newState) return;
this.sourceExpandUpdate(newState);
let resizer = this.resizer;
if (newState) {
resizer.sep_left = resizer.sep_left_snap;
resizer.sep_left_snap = 0;
} else {
resizer.sep_left_snap = resizer.sep_left;
resizer.sep_left = 0;
}
resizer.updatePanes();
}
toggleDisassemblyExpanded() {
this.setDisassemblyExpanded(!this.disassemblyExpand.classed("invisible"));
}
disassemblyExpandUpdate(newState) {
this.setLastExpandedState("disassembly", newState);
this.disassemblyExpand.classed("invisible", newState);
this.disassemblyCollapse.classed("invisible", !newState);
}
setDisassemblyExpanded(newState) {
if (this.disassemblyExpand.classed("invisible") === newState) return;
this.disassemblyExpandUpdate(newState);
let resizer = this.resizer;
if (newState) {
resizer.sep_right = resizer.sep_right_snap;
resizer.sep_right_snap = resizer.client_width;
} else {
resizer.sep_right_snap = resizer.sep_right;
resizer.sep_right = resizer.client_width;
}
resizer.updatePanes();
}
panesUpated() {
this.sourceExpandUpdate(this.resizer.sep_left > this.resizer.dead_width);
this.disassemblyExpandUpdate(this.resizer.sep_right <
(this.resizer.client_width - this.resizer.dead_width));
}
}
class Resizer {
constructor(panes_updated_callback, dead_width) {
let resizer = this;
resizer.snapper = new Snapper(resizer)
resizer.panes_updated_callback = panes_updated_callback;
resizer.dead_width = dead_width
resizer.client_width = d3.select("body").node().getBoundingClientRect().width;
resizer.left = d3.select("#" + SOURCE_PANE_ID);
resizer.middle = d3.select("#" + INTERMEDIATE_PANE_ID);
resizer.right = d3.select("#" + GENERATED_PANE_ID);
resizer.resizer_left = d3.select('.resizer-left');
resizer.resizer_right = d3.select('.resizer-right');
resizer.sep_left = resizer.client_width/3;
resizer.sep_right = resizer.client_width/3*2;
resizer.sep_left_snap = 0;
resizer.sep_right_snap = 0;
// Offset to prevent resizers from sliding slightly over one another.
resizer.sep_width_offset = 7;
let dragResizeLeft = d3.behavior.drag()
.on('drag', function() {
let x = d3.mouse(this.parentElement)[0];
resizer.sep_left = Math.min(Math.max(0,x), resizer.sep_right-resizer.sep_width_offset);
resizer.updatePanes();
})
.on('dragstart', function() {
resizer.resizer_left.classed("dragged", true);
let x = d3.mouse(this.parentElement)[0];
if (x > dead_width) {
resizer.sep_left_snap = resizer.sep_left;
}
})
.on('dragend', function() {
resizer.resizer_left.classed("dragged", false);
});
resizer.resizer_left.call(dragResizeLeft);
let dragResizeRight = d3.behavior.drag()
.on('drag', function() {
let x = d3.mouse(this.parentElement)[0];
resizer.sep_right = Math.max(resizer.sep_left+resizer.sep_width_offset, Math.min(x, resizer.client_width));
resizer.updatePanes();
})
.on('dragstart', function() {
resizer.resizer_right.classed("dragged", true);
let x = d3.mouse(this.parentElement)[0];
if (x < (resizer.client_width-dead_width)) {
resizer.sep_right_snap = resizer.sep_right;
}
})
.on('dragend', function() {
resizer.resizer_right.classed("dragged", false);
});;
resizer.resizer_right.call(dragResizeRight);
window.onresize = function(){
resizer.updateWidths();
/*fitPanesToParents();*/
resizer.updatePanes();
};
}
updatePanes() {
let left_snapped = this.sep_left === 0;
let right_snapped = this.sep_right >= this.client_width - 1;
this.resizer_left.classed("snapped", left_snapped);
this.resizer_right.classed("snapped", right_snapped);
this.left.style('width', this.sep_left + 'px');
this.middle.style('width', (this.sep_right-this.sep_left) + 'px');
this.right.style('width', (this.client_width - this.sep_right) + 'px');
this.resizer_left.style('left', this.sep_left + 'px');
this.resizer_right.style('right', (this.client_width - this.sep_right - 1) + 'px');
this.snapper.panesUpated();
this.panes_updated_callback();
}
updateWidths() {
this.client_width = d3.select("body").node().getBoundingClientRect().width;
this.sep_right = Math.min(this.sep_right, this.client_width);
this.sep_left = Math.min(Math.max(0, this.sep_left), this.sep_right);
}
}
document.onload = (function(d3){
"use strict";
var jsonObj;
var svg = null;
var graph = null;
var schedule = null;
var empty = null;
var currentPhaseView = null;
var disassemblyView = null;
var sourceView = null;
var selectionBroker = null;
let resizer = new Resizer(panesUpdatedCallback, 100);
function renderTurbolizerData(txtRes) {
// If the JSON isn't properly terminated, assume compiler crashed and
// add best-guess empty termination
if (txtRes[txtRes.length-2] == ',') {
txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}';
}
try{
jsonObj = JSON.parse(txtRes);
hideCurrentPhase();
selectionBroker.setNodePositionMap(jsonObj.nodePositions);
sourceView.initializeCode(jsonObj.source, jsonObj.sourcePosition);
disassemblyView.initializeCode(jsonObj.source);
var selectMenu = document.getElementById('display-selector');
var disassemblyPhase = null;
selectMenu.innerHTML = '';
for (var i = 0; i < jsonObj.phases.length; ++i) {
var optionElement = document.createElement("option");
optionElement.text = jsonObj.phases[i].name;
if (optionElement.text == 'disassembly') {
disassemblyPhase = jsonObj.phases[i];
} else {
selectMenu.add(optionElement, null);
}
}
disassemblyView.initializePerfProfile(jsonObj.eventCounts);
disassemblyView.show(disassemblyPhase.data, null);
var initialPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase");
if (!(initialPhaseIndex in jsonObj.phases)) {
initialPhaseIndex = 0;
}
// We wish to show the remembered phase {lastSelectedPhase}, but
// this will crash if the first view we switch to is a
// ScheduleView. So we first switch to the first phase, which
// should never be a ScheduleView.
displayPhase(jsonObj.phases[0]);
displayPhase(jsonObj.phases[initialPhaseIndex]);
selectMenu.selectedIndex = initialPhaseIndex;
selectMenu.onchange = function(item) {
window.sessionStorage.setItem("lastSelectedPhase", selectMenu.selectedIndex);
displayPhase(jsonObj.phases[selectMenu.selectedIndex]);
}
fitPanesToParents();
d3.select("#search-input").attr("value", window.sessionStorage.getItem("lastSearch") || "");
}
catch(err) {
window.console.log("caught exception, clearing session storage just in case");
window.sessionStorage.clear(); // just in case
window.console.log("showing error");
window.alert("Invalid TurboFan JSON file\n" +
"error: " + err.message);
return;
}
}
window.renderTurbolizerData = renderTurbolizerData
function panesUpdatedCallback() {
graph.fitGraphViewToWindow();
}
function hideCurrentPhase() {
var rememberedSelection = null;
if (currentPhaseView != null) {
rememberedSelection = currentPhaseView.detachSelection();
currentPhaseView.hide();
currentPhaseView = null;
}
return rememberedSelection;
}
function displayPhaseView(view, data) {
var rememberedSelection = hideCurrentPhase();
view.show(data, rememberedSelection);
d3.select("#middle").classed("scrollable", view.isScrollable());
currentPhaseView = view;
}
function displayPhase(phase) {
if (phase.type == 'graph') {
displayPhaseView(graph, phase.data);
} else if (phase.type == 'schedule') {
displayPhaseView(schedule, phase.data);
} else {
displayPhaseView(empty, null);
}
}
function fitPanesToParents() {
d3.select("#left").classed("scrollable", false)
d3.select("#right").classed("scrollable", false);
graph.fitGraphViewToWindow();
d3.select("#left").classed("scrollable", true);
d3.select("#right").classed("scrollable", true);
}
selectionBroker = new SelectionBroker();
function initializeHandlers(g) {
d3.select("#hidden-file-upload").on("change", function() {
if (window.File && window.FileReader && window.FileList) {
var uploadFile = this.files[0];
var filereader = new window.FileReader();
var consts = Node.consts;
filereader.onload = function(){
var txtRes = filereader.result;
renderTurbolizerData(txtRes);
};
filereader.readAsText(uploadFile);
} else {
alert("Can't load graph");
}
});
}
sourceView = new CodeView(SOURCE_PANE_ID, PR, "", 0, selectionBroker);
disassemblyView = new DisassemblyView(DISASSEMBLY_PANE_ID, selectionBroker);
graph = new GraphView(d3, GRAPH_PANE_ID, [], [], selectionBroker);
schedule = new ScheduleView(SCHEDULE_PANE_ID, selectionBroker);
empty = new EmptyView(EMPTY_PANE_ID, selectionBroker);
initializeHandlers(graph);
resizer.snapper.setSourceExpanded(resizer.snapper.getLastExpandedState("source", true));
resizer.snapper.setDisassemblyExpanded(resizer.snapper.getLastExpandedState("disassembly", false));
displayPhaseView(empty, null);
fitPanesToParents();
resizer.updatePanes();
})(window.d3);
================================================
FILE: util.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
function makeContainerPosVisible(container, pos) {
var height = container.offsetHeight;
var margin = Math.floor(height / 4);
if (pos < container.scrollTop + margin) {
pos -= margin;
if (pos < 0) pos = 0;
container.scrollTop = pos;
return;
}
if (pos > (container.scrollTop + 3 * margin)) {
pos = pos - 3 * margin;
if (pos < 0) pos = 0;
container.scrollTop = pos;
}
}
function lowerBound(a, value, compare, lookup) {
let first = 0;
let count = a.length;
while (count > 0) {
let step = Math.floor(count / 2);
let middle = first + step;
let middle_value = (lookup === undefined) ? a[middle] : lookup(a, middle);
let result = (compare === undefined) ? (middle_value < value) : compare(middle_value, value);
if (result) {
first = middle + 1;
count -= step + 1;
} else {
count = step;
}
}
return first;
}
function upperBound(a, value, compare, lookup) {
let first = 0;
let count = a.length;
while (count > 0) {
let step = Math.floor(count / 2);
let middle = first + step;
let middle_value = (lookup === undefined) ? a[middle] : lookup(a, middle);
let result = (compare === undefined) ? (value < middle_value) : compare(value, middle_value);
if (!result) {
first = middle + 1;
count -= step + 1;
} else {
count = step;
}
}
return first;
}
function sortUnique(arr, f) {
arr = arr.sort(f);
let ret = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i-1] !== arr[i]) {
ret.push(arr[i]);
}
}
return ret;
}
// Partial application without binding the receiver
function partial(f) {
var arguments1 = Array.prototype.slice.call(arguments, 1);
return function() {
var arguments2 = Array.from(arguments);
f.apply(this, arguments1.concat(arguments2));
}
}
================================================
FILE: view.js
================================================
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class View {
constructor(id, broker) {
this.divElement = d3.select("#" + id);
this.divNode = this.divElement[0][0];
this.parentNode = this.divNode.parentNode;
}
isScrollable() {
return false;
}
show(data, rememberedSelection) {
this.parentNode.appendChild(this.divElement[0][0]);
this.initializeContent(data, rememberedSelection);
this.divElement.attr(VISIBILITY, 'visible');
}
hide() {
this.divElement.attr(VISIBILITY, 'hidden');
this.deleteContent();
this.parentNode.removeChild(this.divNode);
}
detachSelection() {
return null;
}
}
gitextract_t6om2rjh/ ├── .gitignore ├── .npmignore ├── OWNERS ├── README.md ├── bin/ │ └── turbolizer ├── code-view.js ├── constants.js ├── disassembly-view.js ├── edge.js ├── empty-view.js ├── graph-layout.js ├── graph-view.js ├── index.html ├── lang-disassembly.js ├── lib/ │ ├── client-loader.js │ ├── turbolizer.js │ └── turbolizer.server.js ├── monkey.js ├── node.js ├── package.json ├── schedule-view.js ├── selection-broker.js ├── selection.js ├── text-view.js ├── turbo-visualizer.css ├── turbo-visualizer.js ├── util.js └── view.js
SYMBOL INDEX (136 symbols across 15 files)
FILE: code-view.js
class CodeView (line 7) | class CodeView extends View {
method constructor (line 8) | constructor(divID, PR, sourceText, sourcePosition, broker) {
method initializeContent (line 121) | initializeContent(data, rememberedSelection) { this.data = data; }
method initializeCode (line 123) | initializeCode(sourceText, sourcePosition) {
method deleteContent (line 169) | deleteContent() {}
FILE: disassembly-view.js
class DisassemblyView (line 7) | class DisassemblyView extends TextView {
method constructor (line 8) | constructor(id, broker) {
method lineLocation (line 122) | lineLocation(li) {
method initializeContent (line 152) | initializeContent(data, rememberedSelection) {
method initializeCode (line 157) | initializeCode(sourceText, sourcePosition) {
method initializePerfProfile (line 179) | initializePerfProfile(eventCounts) {
method humanize (line 201) | humanize(num) {
method interpolate (line 206) | interpolate(val, max, start, end) {
method processLine (line 210) | processLine(line) {
FILE: edge.js
function isEdgeInitiallyVisible (line 7) | function isEdgeInitiallyVisible(target, index, source, type) {
FILE: empty-view.js
class EmptyView (line 7) | class EmptyView extends View {
method constructor (line 8) | constructor(id, broker) {
method initializeContent (line 13) | initializeContent(data, rememberedSelection) {
method deleteContent (line 17) | deleteContent() {
FILE: graph-layout.js
function newGraphOccupation (line 9) | function newGraphOccupation(graph){
function layoutNodeGraph (line 249) | function layoutNodeGraph(graph) {
function redetermineGraphBoundingBox (line 462) | function redetermineGraphBoundingBox(graph) {
FILE: graph-view.js
class GraphView (line 7) | class GraphView extends View {
method constructor (line 8) | constructor (d3, id, nodes, edges, broker) {
method selectedClass (line 164) | static get selectedClass() {
method rectClass (line 167) | static get rectClass() {
method activeEditId (line 170) | static get activeEditId() {
method nodeRadius (line 173) | static get nodeRadius() {
method getNodeHeight (line 177) | getNodeHeight(d) {
method getEdgeFrontier (line 185) | getEdgeFrontier(nodes, inEdges, edgeFilter) {
method getNodeFrontier (line 200) | getNodeFrontier(nodes, inEdges, edgeFilter) {
method dragmove (line 231) | dragmove(d) {
method initializeContent (line 238) | initializeContent(data, rememberedSelection) {
method deleteContent (line 248) | deleteContent() {
method measureText (line 257) | measureText(text) {
method createGraph (line 266) | createGraph(data, initiallyVisibileIds) {
method connectVisibleSelectedNodes (line 318) | connectVisibleSelectedNodes() {
method updateInputAndOutputBubbles (line 335) | updateInputAndOutputBubbles() {
method attachSelection (line 375) | attachSelection(s) {
method detachSelection (line 386) | detachSelection() {
method pathMouseDown (line 395) | pathMouseDown(path, d) {
method nodeMouseDown (line 401) | nodeMouseDown(node, d) {
method nodeMouseUp (line 406) | nodeMouseUp(d3node, d) {
method selectSourcePositions (line 430) | selectSourcePositions(start, end, selected) {
method selectAllNodes (line 450) | selectAllNodes(inEdges, filter) {
method uploadAction (line 459) | uploadAction(graph) {
method layoutAction (line 463) | layoutAction(graph) {
method showAllAction (line 470) | showAllAction(graph) {
method hideDeadAction (line 477) | hideDeadAction(graph) {
method hideUnselectedAction (line 482) | hideUnselectedAction(graph) {
method hideSelectedAction (line 492) | hideSelectedAction(graph) {
method zoomSelectionAction (line 503) | zoomSelectionAction(graph) {
method toggleTypesAction (line 507) | toggleTypesAction(graph) {
method searchInputAction (line 511) | searchInputAction(graph) {
method svgMouseDown (line 544) | svgMouseDown() {
method svgMouseUp (line 548) | svgMouseUp() {
method svgKeyDown (line 564) | svgKeyDown() {
method svgKeyUp (line 671) | svgKeyUp() {
method layoutEdges (line 675) | layoutEdges() {
method layoutGraph (line 683) | layoutGraph() {
method updateGraphVisibility (line 688) | updateGraphVisibility() {
method getVisibleTranslation (line 903) | getVisibleTranslation(translate, scale) {
method translateClipped (line 958) | translateClipped(translate, scale, transition) {
method zoomed (line 975) | zoomed(){
method getSvgViewDimensions (line 982) | getSvgViewDimensions() {
method minScale (line 990) | minScale() {
method fitGraphViewToWindow (line 1004) | fitGraphViewToWindow() {
method toggleTypes (line 1009) | toggleTypes() {
method viewSelection (line 1021) | viewSelection() {
method viewGraphRegion (line 1043) | viewGraphRegion(minX, minY, maxX, maxY, transition) {
method viewWholeGraph (line 1056) | viewWholeGraph() {
FILE: lib/client-loader.js
function preload (line 5) | async function preload() {
FILE: lib/turbolizer.js
function canRead (line 10) | async function canRead(p) {
function findAllTurboFiles (line 19) | async function findAllTurboFiles(root) {
function makeSelectable (line 34) | function makeSelectable(arr) {
function mapAllTurboFiles (line 42) | async function mapAllTurboFiles(root) {
FILE: lib/turbolizer.server.js
function createServer (line 8) | function createServer(turboFilesDir, PORT = null) {
function openWithFile (line 22) | function openWithFile({ address, file }) {
FILE: node.js
function isNodeInitiallyVisible (line 11) | function isNodeInitiallyVisible(node) {
FILE: schedule-view.js
class ScheduleView (line 7) | class ScheduleView extends TextView {
method constructor (line 8) | constructor(id, broker) {
method initializeContent (line 108) | initializeContent(data, rememberedSelection) {
method detachSelection (line 118) | detachSelection() {
FILE: text-view.js
class TextView (line 7) | class TextView extends View {
method constructor (line 8) | constructor(id, broker, patterns, allowSpanSelection) {
method setPatterns (line 46) | setPatterns(patterns) {
method clearText (line 51) | clearText() {
method sameLocation (line 58) | sameLocation(l1, l2) {
method selectLocations (line 89) | selectLocations(locations, selected, makeVisible) {
method getLocations (line 103) | getLocations(items) {
method createFragment (line 114) | createFragment(text, style) {
method appendFragment (line 127) | appendFragment(li, fragment) {
method processLine (line 131) | processLine(line) {
method select (line 179) | select(s, selected, makeVisible) {
method selectCommon (line 185) | selectCommon(s, selected, makeVisible) {
method mouseDownLine (line 215) | mouseDownLine(li, e) {
method mouseDownSpan (line 226) | mouseDownSpan(span, e) {
method processText (line 240) | processText(text) {
method initializeContent (line 263) | initializeContent(data, rememberedSelection) {
method deleteContent (line 276) | deleteContent() {
method isScrollable (line 279) | isScrollable() {
method detachSelection (line 283) | detachSelection() {
method lineLocation (line 287) | lineLocation(li) {
FILE: turbo-visualizer.js
class Snapper (line 5) | class Snapper {
method constructor (line 7) | constructor(resizer) {
method getLastExpandedState (line 23) | getLastExpandedState(type, default_state) {
method setLastExpandedState (line 29) | setLastExpandedState(type, state) {
method toggleSourceExpanded (line 33) | toggleSourceExpanded() {
method sourceExpandUpdate (line 37) | sourceExpandUpdate(newState) {
method setSourceExpanded (line 43) | setSourceExpanded(newState) {
method toggleDisassemblyExpanded (line 57) | toggleDisassemblyExpanded() {
method disassemblyExpandUpdate (line 61) | disassemblyExpandUpdate(newState) {
method setDisassemblyExpanded (line 67) | setDisassemblyExpanded(newState) {
method panesUpated (line 81) | panesUpated() {
class Resizer (line 88) | class Resizer {
method constructor (line 89) | constructor(panes_updated_callback, dead_width) {
method updatePanes (line 149) | updatePanes() {
method updateWidths (line 164) | updateWidths() {
function renderTurbolizerData (line 184) | function renderTurbolizerData(txtRes) {
function panesUpdatedCallback (line 251) | function panesUpdatedCallback() {
function hideCurrentPhase (line 255) | function hideCurrentPhase() {
function displayPhaseView (line 265) | function displayPhaseView(view, data) {
function displayPhase (line 272) | function displayPhase(phase) {
function fitPanesToParents (line 282) | function fitPanesToParents() {
function initializeHandlers (line 294) | function initializeHandlers(g) {
FILE: util.js
function makeContainerPosVisible (line 7) | function makeContainerPosVisible(container, pos) {
function lowerBound (line 24) | function lowerBound(a, value, compare, lookup) {
function upperBound (line 43) | function upperBound(a, value, compare, lookup) {
function sortUnique (line 62) | function sortUnique(arr, f) {
function partial (line 74) | function partial(f) {
FILE: view.js
class View (line 7) | class View {
method constructor (line 8) | constructor(id, broker) {
method isScrollable (line 14) | isScrollable() {
method show (line 18) | show(data, rememberedSelection) {
method hide (line 24) | hide() {
method detachSelection (line 30) | detachSelection() {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (125K chars).
[
{
"path": ".gitignore",
"chars": 97,
"preview": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n\npids\nlogs\nresults\n\nnpm-debug.log\nnode_modules\n"
},
{
"path": ".npmignore",
"chars": 104,
"preview": "lib-cov\n*.seed\n*.log\n*.csv\n*.dat\n*.out\n*.pid\n*.gz\n\npids\nlogs\nresults\n\nnpm-debug.log\nnode_modules\nassets\n"
},
{
"path": "OWNERS",
"chars": 19,
"preview": "danno@chromium.org\n"
},
{
"path": "README.md",
"chars": 3604,
"preview": "# Turbolizer\n\nTurbolizer tool derived from the one included with `v8/tools`.\n\n\n\n##"
},
{
"path": "bin/turbolizer",
"chars": 1660,
"preview": "#!/usr/bin/env node\n'use strict'\n\nconst { prompt } = require('promptly')\nconst { mapAllTurboFiles } = require('../lib/tu"
},
{
"path": "code-view.js",
"chars": 5729,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "constants.js",
"chars": 1126,
"preview": "// Copyright 2014 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "disassembly-view.js",
"chars": 8290,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "edge.js",
"chars": 2627,
"preview": "// Copyright 2014 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "empty-view.js",
"chars": 515,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "graph-layout.js",
"chars": 14193,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "graph-view.js",
"chars": 31837,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "index.html",
"chars": 4638,
"preview": "<!DOCTYPE HTML>\n<html>\n <head>\n <title>Turbolizer</title>\n <link rel=\"stylesheet\" href=\"turbo-visualizer.css\" />\n"
},
{
"path": "lang-disassembly.js",
"chars": 507,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "lib/client-loader.js",
"chars": 392,
"preview": "'use strict'\n\n/* global location fetch */\n\nasync function preload() {\n try {\n if (!(/^\\?preload=/).test(location.sea"
},
{
"path": "lib/turbolizer.js",
"chars": 1079,
"preview": "'use strict'\n\nconst { promisify } = require('util')\nconst path = require('path')\nconst fs = require('fs')\nconst readdir "
},
{
"path": "lib/turbolizer.server.js",
"chars": 752,
"preview": "'use strict'\n\nconst path = require('path')\nconst ecstatic = require('ecstatic')\nconst connect = require('connect')\nconst"
},
{
"path": "monkey.js",
"chars": 276,
"preview": "// Copyright 2014 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "node.js",
"chars": 4413,
"preview": "// Copyright 2014 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "package.json",
"chars": 789,
"preview": "{\n \"name\": \"turbolizer\",\n \"version\": \"0.2.0\",\n \"description\": \"Turbolizer tool from the v8 repository with added supp"
},
{
"path": "schedule-view.js",
"chars": 3371,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "selection-broker.js",
"chars": 3041,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "selection.js",
"chars": 2937,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "text-view.js",
"chars": 8028,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "turbo-visualizer.css",
"chars": 5024,
"preview": ".visible-transition {\n transition-delay: 0s;\n transition-duration: 1s;\n transition-property: all;\n transitio"
},
{
"path": "turbo-visualizer.js",
"chars": 11185,
"preview": "// Copyright 2017 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "util.js",
"chars": 2025,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
},
{
"path": "view.js",
"chars": 786,
"preview": "// Copyright 2015 the V8 project authors. All rights reserved.\n// Use of this source code is governed by a BSD-style lic"
}
]
About this extraction
This page contains the full source code of the thlorenz/turbolizer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (116.3 KB), approximately 30.7k tokens, and a symbol index with 136 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.