├── OWNERS
├── live.png
├── types.png
├── search.png
├── search2.png
├── expand-all.jpg
├── left-arrow.png
├── layout-icon.png
├── right-arrow.png
├── upload-icon.png
├── hide-selected.png
├── hide-unselected.png
├── assets
└── turbolizer.gif
├── .gitignore
├── .npmignore
├── monkey.js
├── lib
├── client-loader.js
├── turbolizer.server.js
└── turbolizer.js
├── lang-disassembly.js
├── empty-view.js
├── package.json
├── view.js
├── constants.js
├── bin
└── turbolizer
├── util.js
├── edge.js
├── selection.js
├── selection-broker.js
├── README.md
├── schedule-view.js
├── index.html
├── node.js
├── code-view.js
├── turbo-visualizer.css
├── text-view.js
├── disassembly-view.js
├── turbo-visualizer.js
├── graph-layout.js
└── graph-view.js
/OWNERS:
--------------------------------------------------------------------------------
1 | danno@chromium.org
2 |
--------------------------------------------------------------------------------
/live.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/live.png
--------------------------------------------------------------------------------
/types.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/types.png
--------------------------------------------------------------------------------
/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/search.png
--------------------------------------------------------------------------------
/search2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/search2.png
--------------------------------------------------------------------------------
/expand-all.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/expand-all.jpg
--------------------------------------------------------------------------------
/left-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/left-arrow.png
--------------------------------------------------------------------------------
/layout-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/layout-icon.png
--------------------------------------------------------------------------------
/right-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/right-arrow.png
--------------------------------------------------------------------------------
/upload-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/upload-icon.png
--------------------------------------------------------------------------------
/hide-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/hide-selected.png
--------------------------------------------------------------------------------
/hide-unselected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/hide-unselected.png
--------------------------------------------------------------------------------
/assets/turbolizer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thlorenz/turbolizer/HEAD/assets/turbolizer.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 | assets
17 |
--------------------------------------------------------------------------------
/monkey.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | Math.alignUp = function(raw, multiple) {
6 | return Math.floor((raw + multiple - 1) / multiple) * multiple;
7 | }
8 |
--------------------------------------------------------------------------------
/lib/client-loader.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /* global location fetch */
4 |
5 | async function preload() {
6 | try {
7 | if (!(/^\?preload=/).test(location.search)) return
8 | const fileUrl = location.search.replace(/^\?preload=/, '')
9 | const fileData = await fetch(fileUrl, { mode: 'no-cors' })
10 | const txt = await fileData.text()
11 | window.renderTurbolizerData(txt)
12 | } catch (err) {
13 | console.error(err)
14 | }
15 | }
16 |
17 | preload()
18 |
--------------------------------------------------------------------------------
/lang-disassembly.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | PR.registerLangHandler(
6 | PR.createSimpleLexer(
7 | [
8 | [PR.PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$))/, null, '\''],
9 | [PR.PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']
10 | ],
11 | [ // fallthroughStylePatterns
12 | [PR.PR_COMMENT, /;; debug: position \d+/, null],
13 | ]),
14 | ['disassembly']);
15 |
--------------------------------------------------------------------------------
/empty-view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class EmptyView extends View {
8 | constructor(id, broker) {
9 | super(id, broker);
10 | this.svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
11 | }
12 |
13 | initializeContent(data, rememberedSelection) {
14 | this.svg.attr("height", document.documentElement.clientHeight + "px");
15 | }
16 |
17 | deleteContent() {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/turbolizer.server.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const ecstatic = require('ecstatic')
5 | const connect = require('connect')
6 | const opener = require('opener')
7 |
8 | function createServer(turboFilesDir, PORT = null) {
9 | const appDir = path.join(__dirname, '..')
10 | const ecstaticApp = ecstatic({ root: appDir })
11 | const ecstaticFiles = ecstatic({ root: turboFilesDir })
12 | const app = connect()
13 | .use('/', ecstaticApp)
14 | .use('/data/', ecstaticFiles)
15 |
16 | const server = app.listen(PORT)
17 | const { port } = server.address()
18 | const address = `http://localhost:${port}`
19 | return { server, address }
20 | }
21 |
22 | function openWithFile({ address, file }) {
23 | const url = `${address}?preload=${address}/data/${file}`
24 | opener(url)
25 | }
26 |
27 | module.exports = { createServer, openWithFile }
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "turbolizer",
3 | "version": "0.2.0",
4 | "description": "Turbolizer tool from the v8 repository with added support to preload a profile",
5 | "main": "lib/turbolizer.js",
6 | "bin": "bin/turbolizer",
7 | "repository": {
8 | "type": "git",
9 | "url": "git://github.com/thlorenz/turbolizer.git"
10 | },
11 | "homepage": "https://github.com/thlorenz/turbolizer",
12 | "dependencies": {
13 | "connect": "~3.6.6",
14 | "ecstatic": "~4.1.4",
15 | "opener": "~1.4.3",
16 | "promptly": "~3.0.3"
17 | },
18 | "devDependencies": {},
19 | "keywords": [],
20 | "author": {
21 | "name": "Thorsten Lorenz",
22 | "email": "thlorenz@gmx.de",
23 | "url": "http://thlorenz.com"
24 | },
25 | "license": {
26 | "type": "MIT",
27 | "url": "https://github.com/thlorenz/turbolizer/blob/master/LICENSE"
28 | },
29 | "engine": {
30 | "node": ">=8"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class View {
8 | constructor(id, broker) {
9 | this.divElement = d3.select("#" + id);
10 | this.divNode = this.divElement[0][0];
11 | this.parentNode = this.divNode.parentNode;
12 | }
13 |
14 | isScrollable() {
15 | return false;
16 | }
17 |
18 | show(data, rememberedSelection) {
19 | this.parentNode.appendChild(this.divElement[0][0]);
20 | this.initializeContent(data, rememberedSelection);
21 | this.divElement.attr(VISIBILITY, 'visible');
22 | }
23 |
24 | hide() {
25 | this.divElement.attr(VISIBILITY, 'hidden');
26 | this.deleteContent();
27 | this.parentNode.removeChild(this.divNode);
28 | }
29 |
30 | detachSelection() {
31 | return null;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/constants.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | var MAX_RANK_SENTINEL = 0;
6 | var GRAPH_MARGIN = 250;
7 | var WIDTH = 'width';
8 | var HEIGHT = 'height';
9 | var VISIBILITY = 'visibility';
10 | var SOURCE_PANE_ID = 'left';
11 | var SOURCE_COLLAPSE_ID = 'source-shrink';
12 | var SOURCE_EXPAND_ID = 'source-expand';
13 | var INTERMEDIATE_PANE_ID = 'middle';
14 | var EMPTY_PANE_ID = 'empty';
15 | var GRAPH_PANE_ID = 'graph';
16 | var SCHEDULE_PANE_ID = 'schedule';
17 | var GENERATED_PANE_ID = 'right';
18 | var DISASSEMBLY_PANE_ID = 'disassembly';
19 | var DISASSEMBLY_COLLAPSE_ID = 'disassembly-shrink';
20 | var DISASSEMBLY_EXPAND_ID = 'disassembly-expand';
21 | var COLLAPSE_PANE_BUTTON_VISIBLE = 'button-input';
22 | var COLLAPSE_PANE_BUTTON_INVISIBLE = 'button-input-invisible';
23 | var UNICODE_BLOCK = '▋';
24 | var PROF_COLS = [
25 | { perc: 0, col: { r: 255, g: 255, b: 255 } },
26 | { perc: 0.5, col: { r: 255, g: 255, b: 128 } },
27 | { perc: 5, col: { r: 255, g: 128, b: 0 } },
28 | { perc: 15, col: { r: 255, g: 0, b: 0 } },
29 | { perc: 100, col: { r: 0, g: 0, b: 0 } }
30 | ];
31 |
--------------------------------------------------------------------------------
/lib/turbolizer.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { promisify } = require('util')
4 | const path = require('path')
5 | const fs = require('fs')
6 | const readdir = promisify(fs.readdir)
7 | const stat = promisify(fs.stat)
8 | const access = promisify(fs.access)
9 |
10 | async function canRead(p) {
11 | try {
12 | await access(p, fs.constants.R_OK)
13 | return true
14 | } catch (err) {
15 | return false
16 | }
17 | }
18 |
19 | async function findAllTurboFiles(root) {
20 | const allEntries = await readdir(root)
21 | const turboFiles = []
22 | for (const entry of allEntries) {
23 | if (!(/^turbo-.+\.json$/).test(entry)) continue
24 |
25 | const fullPath = path.join(root, entry)
26 | if (!(await canRead(fullPath))) continue
27 | if (!(await stat(fullPath)).isFile()) continue
28 | turboFiles.push({ fullPath, entry })
29 | }
30 |
31 | return turboFiles
32 | }
33 |
34 | function makeSelectable(arr) {
35 | const map = new Map()
36 | for (var i = 0; i < arr.length; i++) {
37 | map.set(`${i + 1}`, arr[i])
38 | }
39 | return map
40 | }
41 |
42 | async function mapAllTurboFiles(root) {
43 | const arr = await findAllTurboFiles(root)
44 | return makeSelectable(arr)
45 | }
46 |
47 | module.exports = {
48 | findAllTurboFiles
49 | , mapAllTurboFiles
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/bin/turbolizer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 |
4 | const { prompt } = require('promptly')
5 | const { mapAllTurboFiles } = require('../lib/turbolizer')
6 | const { createServer, openWithFile } = require('../lib/turbolizer.server')
7 |
8 | function createPromptMsg(map) {
9 | let msg = 'Turbolizer - please select a file to view:\n\n'
10 | for (const [ selector, { entry } ] of map) {
11 | msg += `\t${selector}: ${entry}\n`
12 | }
13 | msg += '\t0: View ALL'
14 | return msg + '\n\nYour choice: '
15 | }
16 |
17 | function createValidator(map) {
18 | return val => {
19 | if (val === '0') return val
20 | if (map.has(val)) return val
21 | throw new Error(`Invalid choice: '${val}', please select one of the given numbers`)
22 | }
23 | }
24 |
25 | const root = process.cwd()
26 | ;(async () => {
27 | try {
28 | const map = await mapAllTurboFiles(root)
29 | if (map.size === 0) {
30 | console.error('Turbolizer - Problem:\n')
31 | console.error(' Unable to find any "turbo-*.json" files in the current directory.\n')
32 | console.error(' Please run "node --trace-turbo app.js" in order to create them or follow the')
33 | console.error(' instructions at https://github.com/thlorenz/turbolizer/blob/master/README.md.')
34 | return
35 | }
36 | const msg = createPromptMsg(map)
37 | const result = await prompt(msg, { validator: createValidator(map) })
38 | const { server, address } = createServer(root)
39 |
40 | server.on('listening', () => {
41 | const choice = result.trim()
42 | if (choice === '0') {
43 | for (const val of map.values()) {
44 | openWithFile({ address, file: val.entry })
45 | }
46 | } else {
47 | openWithFile({ address, file: map.get(choice).entry })
48 | }
49 | })
50 | } catch (err) {
51 | console.error(err)
52 | }
53 | })()
54 |
--------------------------------------------------------------------------------
/util.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | function makeContainerPosVisible(container, pos) {
8 | var height = container.offsetHeight;
9 | var margin = Math.floor(height / 4);
10 | if (pos < container.scrollTop + margin) {
11 | pos -= margin;
12 | if (pos < 0) pos = 0;
13 | container.scrollTop = pos;
14 | return;
15 | }
16 | if (pos > (container.scrollTop + 3 * margin)) {
17 | pos = pos - 3 * margin;
18 | if (pos < 0) pos = 0;
19 | container.scrollTop = pos;
20 | }
21 | }
22 |
23 |
24 | function lowerBound(a, value, compare, lookup) {
25 | let first = 0;
26 | let count = a.length;
27 | while (count > 0) {
28 | let step = Math.floor(count / 2);
29 | let middle = first + step;
30 | let middle_value = (lookup === undefined) ? a[middle] : lookup(a, middle);
31 | let result = (compare === undefined) ? (middle_value < value) : compare(middle_value, value);
32 | if (result) {
33 | first = middle + 1;
34 | count -= step + 1;
35 | } else {
36 | count = step;
37 | }
38 | }
39 | return first;
40 | }
41 |
42 |
43 | function upperBound(a, value, compare, lookup) {
44 | let first = 0;
45 | let count = a.length;
46 | while (count > 0) {
47 | let step = Math.floor(count / 2);
48 | let middle = first + step;
49 | let middle_value = (lookup === undefined) ? a[middle] : lookup(a, middle);
50 | let result = (compare === undefined) ? (value < middle_value) : compare(value, middle_value);
51 | if (!result) {
52 | first = middle + 1;
53 | count -= step + 1;
54 | } else {
55 | count = step;
56 | }
57 | }
58 | return first;
59 | }
60 |
61 |
62 | function sortUnique(arr, f) {
63 | arr = arr.sort(f);
64 | let ret = [arr[0]];
65 | for (var i = 1; i < arr.length; i++) {
66 | if (arr[i-1] !== arr[i]) {
67 | ret.push(arr[i]);
68 | }
69 | }
70 | return ret;
71 | }
72 |
73 | // Partial application without binding the receiver
74 | function partial(f) {
75 | var arguments1 = Array.prototype.slice.call(arguments, 1);
76 | return function() {
77 | var arguments2 = Array.from(arguments);
78 | f.apply(this, arguments1.concat(arguments2));
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/edge.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | var MINIMUM_EDGE_SEPARATION = 20;
6 |
7 | function isEdgeInitiallyVisible(target, index, source, type) {
8 | return type == "control" && (target.cfg || source.cfg);
9 | }
10 |
11 | var Edge = function(target, index, source, type) {
12 | this.target = target;
13 | this.source = source;
14 | this.index = index;
15 | this.type = type;
16 | this.backEdgeNumber = 0;
17 | this.visible = isEdgeInitiallyVisible(target, index, source, type);
18 | };
19 |
20 | Edge.prototype.stringID = function() {
21 | return this.source.id + "," + this.index + "," + this.target.id;
22 | };
23 |
24 | Edge.prototype.isVisible = function() {
25 | return this.visible && this.source.visible && this.target.visible;
26 | };
27 |
28 | Edge.prototype.getInputHorizontalPosition = function(graph) {
29 | if (this.backEdgeNumber > 0) {
30 | return graph.maxGraphNodeX + this.backEdgeNumber * MINIMUM_EDGE_SEPARATION;
31 | }
32 | var source = this.source;
33 | var target = this.target;
34 | var index = this.index;
35 | var input_x = target.x + target.getInputX(index);
36 | var inputApproach = target.getInputApproach(this.index);
37 | var outputApproach = source.getOutputApproach(graph);
38 | if (inputApproach > outputApproach) {
39 | return input_x;
40 | } else {
41 | var inputOffset = MINIMUM_EDGE_SEPARATION * (index + 1);
42 | return (target.x < source.x)
43 | ? (target.x + target.getTotalNodeWidth() + inputOffset)
44 | : (target.x - inputOffset)
45 | }
46 | }
47 |
48 | Edge.prototype.generatePath = function(graph) {
49 | var target = this.target;
50 | var source = this.source;
51 | var input_x = target.x + target.getInputX(this.index);
52 | var arrowheadHeight = 7;
53 | var input_y = target.y - 2 * DEFAULT_NODE_BUBBLE_RADIUS - arrowheadHeight;
54 | var output_x = source.x + source.getOutputX();
55 | var output_y = source.y + graph.getNodeHeight(source) + DEFAULT_NODE_BUBBLE_RADIUS;
56 | var inputApproach = target.getInputApproach(this.index);
57 | var outputApproach = source.getOutputApproach(graph);
58 | var horizontalPos = this.getInputHorizontalPosition(graph);
59 |
60 | var result = "M" + output_x + "," + output_y +
61 | "L" + output_x + "," + outputApproach +
62 | "L" + horizontalPos + "," + outputApproach;
63 |
64 | if (horizontalPos != input_x) {
65 | result += "L" + horizontalPos + "," + inputApproach;
66 | } else {
67 | if (inputApproach < outputApproach) {
68 | inputApproach = outputApproach;
69 | }
70 | }
71 |
72 | result += "L" + input_x + "," + inputApproach +
73 | "L" + input_x + "," + input_y;
74 | return result;
75 | }
76 |
77 | Edge.prototype.isBackEdge = function() {
78 | return this.target.hasBackEdges() && (this.target.rank < this.source.rank);
79 | }
80 |
--------------------------------------------------------------------------------
/selection.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | var Selection = function(handler) {
6 | this.handler = handler;
7 | this.selectionBase = null;
8 | this.lastSelection = null;
9 | this.selection = new Set();
10 | }
11 |
12 |
13 | Selection.prototype.isEmpty = function() {
14 | return this.selection.size == 0;
15 | }
16 |
17 |
18 | Selection.prototype.clear = function() {
19 | var handler = this.handler;
20 | this.selectionBase = null;
21 | this.lastSelection = null;
22 | handler.select(this.selection, false);
23 | handler.clear();
24 | this.selection = new Set();
25 | }
26 |
27 |
28 | count = 0;
29 |
30 | Selection.prototype.select = function(s, isSelected) {
31 | var handler = this.handler;
32 | if (!(Symbol.iterator in Object(s))) { s = [s]; }
33 | if (isSelected) {
34 | let first = true;
35 | for (let i of s) {
36 | if (first) {
37 | this.selectionBase = i;
38 | this.lastSelection = i;
39 | first = false;
40 | }
41 | this.selection.add(i);
42 | }
43 | handler.select(this.selection, true);
44 | } else {
45 | let unselectSet = new Set();
46 | for (let i of s) {
47 | if (this.selection.has(i)) {
48 | unselectSet.add(i);
49 | this.selection.delete(i);
50 | }
51 | }
52 | handler.select(unselectSet, false);
53 | }
54 | }
55 |
56 |
57 | Selection.prototype.extendTo = function(pos) {
58 | if (pos == this.lastSelection || this.lastSelection === null) return;
59 |
60 | var handler = this.handler;
61 | var pos_diff = handler.selectionDifference(pos, true, this.lastSelection, false);
62 | var unselect_diff = [];
63 | if (pos_diff.length == 0) {
64 | pos_diff = handler.selectionDifference(this.selectionBase, false, pos, true);
65 | if (pos_diff.length != 0) {
66 | unselect_diff = handler.selectionDifference(this.lastSelection, true, this.selectionBase, false);
67 | this.selection = new Set();
68 | this.selection.add(this.selectionBase);
69 | for (var d of pos_diff) {
70 | this.selection.add(d);
71 | }
72 | } else {
73 | unselect_diff = handler.selectionDifference(this.lastSelection, true, pos, false);
74 | for (var d of unselect_diff) {
75 | this.selection.delete(d);
76 | }
77 | }
78 | } else {
79 | unselect_diff = handler.selectionDifference(this.selectionBase, false, this.lastSelection, true);
80 | if (unselect_diff != 0) {
81 | pos_diff = handler.selectionDifference(pos, true, this.selectionBase, false);
82 | if (pos_diff.length == 0) {
83 | unselect_diff = handler.selectionDifference(pos, false, this.lastSelection, true);
84 | }
85 | for (var d of unselect_diff) {
86 | this.selection.delete(d);
87 | }
88 | }
89 | if (pos_diff.length != 0) {
90 | for (var d of pos_diff) {
91 | this.selection.add(d);
92 | }
93 | }
94 | }
95 | handler.select(unselect_diff, false);
96 | handler.select(pos_diff, true);
97 | this.lastSelection = pos;
98 | }
99 |
100 |
101 | Selection.prototype.detachSelection = function() {
102 | var result = new Set();
103 | for (var i of this.selection) {
104 | result.add(i);
105 | }
106 | this.clear();
107 | return result;
108 | }
109 |
--------------------------------------------------------------------------------
/selection-broker.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | var SelectionBroker = function() {
6 | this.brokers = [];
7 | this.dispatching = false;
8 | this.lastDispatchingHandler = null;
9 | this.nodePositionMap = [];
10 | this.sortedPositionList = [];
11 | this.positionNodeMap = [];
12 | };
13 |
14 | SelectionBroker.prototype.addSelectionHandler = function(handler) {
15 | this.brokers.push(handler);
16 | }
17 |
18 | SelectionBroker.prototype.setNodePositionMap = function(map) {
19 | let broker = this;
20 | if (!map) return;
21 | broker.nodePositionMap = map;
22 | broker.positionNodeMap = [];
23 | broker.sortedPositionList = [];
24 | let next = 0;
25 | for (let i in broker.nodePositionMap) {
26 | broker.sortedPositionList[next] = Number(broker.nodePositionMap[i]);
27 | broker.positionNodeMap[next++] = i;
28 | }
29 | broker.sortedPositionList = sortUnique(broker.sortedPositionList,
30 | function(a,b) { return a - b; });
31 | this.positionNodeMap.sort(function(a,b) {
32 | let result = broker.nodePositionMap[a] - broker.nodePositionMap[b];
33 | if (result != 0) return result;
34 | return a - b;
35 | });
36 | }
37 |
38 | SelectionBroker.prototype.select = function(from, locations, selected) {
39 | let broker = this;
40 | if (!broker.dispatching) {
41 | broker.lastDispatchingHandler = from;
42 | try {
43 | broker.dispatching = true;
44 | let enrichLocations = function(locations) {
45 | result = [];
46 | for (let location of locations) {
47 | let newLocation = {};
48 | if (location.pos_start != undefined) {
49 | newLocation.pos_start = location.pos_start;
50 | }
51 | if (location.pos_end != undefined) {
52 | newLocation.pos_end = location.pos_end;
53 | }
54 | if (location.node_id != undefined) {
55 | newLocation.node_id = location.node_id;
56 | }
57 | if (location.block_id != undefined) {
58 | newLocation.block_id = location.block_id;
59 | }
60 | if (newLocation.pos_start == undefined &&
61 | newLocation.pos_end == undefined &&
62 | newLocation.node_id != undefined) {
63 | if (broker.nodePositionMap && broker.nodePositionMap[location.node_id]) {
64 | newLocation.pos_start = broker.nodePositionMap[location.node_id];
65 | newLocation.pos_end = location.pos_start + 1;
66 | }
67 | }
68 | result.push(newLocation);
69 | }
70 | return result;
71 | }
72 | locations = enrichLocations(locations);
73 | for (var b of this.brokers) {
74 | if (b != from) {
75 | b.brokeredSelect(locations, selected);
76 | }
77 | }
78 | }
79 | finally {
80 | broker.dispatching = false;
81 | }
82 | }
83 | }
84 |
85 | SelectionBroker.prototype.clear = function(from) {
86 | this.lastDispatchingHandler = null;
87 | if (!this.dispatching) {
88 | try {
89 | this.dispatching = true;
90 | this.brokers.forEach(function(b) {
91 | if (b != from) {
92 | b.brokeredClear();
93 | }
94 | });
95 | } finally {
96 | this.dispatching = false;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Turbolizer
2 |
3 | Turbolizer tool derived from the one included with `v8/tools`.
4 |
5 | 
6 |
7 | ## Installation
8 |
9 | ```
10 | npm install -g turbolizer
11 | ```
12 |
13 | ## Usage
14 |
15 | Run your app with the `--trace-turbo` flag, i.e. `node --trace-turbo app.js` to produce `turbo-*.json` files.
16 |
17 | Then just run `turbolizer` in the same directory and select which file (or all) you want to
18 | load and the turbolizer application will open in the browser with it preloaded.
19 |
20 | ## Alternatives
21 |
22 | If you don't want to install anything, as an alternative can then either load them one by one
23 | via the hosted browser version of this repo at [thlorenz.github.io/turbolizer](https://thlorenz.github.io/turbolizer).
24 |
25 | * * *
26 |
27 | _Original Readme from the [v8 repository](https://github.com/v8/v8)_
28 |
29 | Turbolizer
30 | ==========
31 |
32 | Turbolizer is a HTML-based tool that visualizes optimized code along the various
33 | phases of Turbofan's optimization pipeline, allowing easy navigation between
34 | source code, Turbofan IR graphs, scheduled IR nodes and generated assembly code.
35 |
36 | Turbolizer consumes .json files that are generated per-function by d8 by passing
37 | the '--trace-turbo' command-line flag.
38 |
39 | Host the turbolizer locally by starting a web server that serves the contents of
40 | the turbolizer directory, e.g.:
41 |
42 | cd src/tools/turbolizer
43 | python -m SimpleHTTPServer 8000
44 |
45 | Optionally, profiling data generated by the perf tools in linux can be merged
46 | with the .json files using the turbolizer-perf.py file included. The following
47 | command is an example of using the perf script:
48 |
49 | perf script -i perf.data.jitted -s turbolizer-perf.py turbo-main.json
50 |
51 | The output of the above command is a json object that can be piped to a file
52 | which, when uploaded to turbolizer, will display the event counts from perf next
53 | to each instruction in the disassembly. Further detail can be found in the
54 | bottom of this document under "Using Perf with Turbo."
55 |
56 | Using the python interface in perf script requires python-dev to be installed
57 | and perf be recompiled with python support enabled. Once recompiled, the
58 | variable PERF_EXEC_PATH must be set to the location of the recompiled perf
59 | binaries.
60 |
61 | Graph visualization and manipulation based on Mike Bostock's sample code for an
62 | interactive tool for creating directed graphs. Original source is at
63 | https://github.com/metacademy/directed-graph-creator and released under the
64 | MIT/X license.
65 |
66 | Icons derived from the "White Olive Collection" created by Breezi released under
67 | the Creative Commons BY license.
68 |
69 | Using Perf with Turbo
70 | ---------------------
71 |
72 | In order to generate perf data that matches exactly with the turbofan trace, you
73 | must use either a debug build of v8 or a release build with the flag
74 | 'disassembler=on'. This flag ensures that the '--trace-turbo' will output the
75 | necessary disassembly for linking with the perf profile.
76 |
77 | The basic example of generating the required data is as follows:
78 |
79 | perf record -k mono /path/to/d8 --trace-turbo --perf-prof main.js
80 | perf inject -j -i perf.data -o perf.data.jitted
81 | perf script -i perf.data.jitted -s turbolizer-perf.py turbo-main.json
82 |
83 | These commands combined will run and profile d8, merge the output into a single
84 | 'perf.data.jitted' file, then take the event data from that and link them to the
85 | disassembly in the 'turbo-main.json'. Note that, as above, the output of the
86 | script command must be piped to a file for uploading to turbolizer.
87 |
88 | There are many options that can be added to the first command, for example '-e'
89 | can be used to specify the counting of specific events (default: cycles), as
90 | well as '--cpu' to specify which CPU to sample.
91 |
--------------------------------------------------------------------------------
/schedule-view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class ScheduleView extends TextView {
8 | constructor(id, broker) {
9 | super(id, broker, null, false);
10 | let view = this;
11 | let BLOCK_STYLE = {
12 | css: 'tag'
13 | };
14 | const BLOCK_HEADER_STYLE = {
15 | css: 'com',
16 | block_id: -1,
17 | location: function(text) {
18 | let matches = /\d+/.exec(text);
19 | if (!matches) return undefined;
20 | BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
21 | return {
22 | block_id: BLOCK_HEADER_STYLE.block_id
23 | };
24 | },
25 | };
26 | const BLOCK_LINK_STYLE = {
27 | css: 'tag',
28 | link: function(text) {
29 | let id = Number(text.substr(1));
30 | view.select(function(location) { return location.block_id == id; }, true, true);
31 | }
32 | };
33 | const ID_STYLE = {
34 | css: 'tag',
35 | location: function(text) {
36 | let matches = /\d+/.exec(text);
37 | return {
38 | node_id: Number(matches[0]),
39 | block_id: BLOCK_HEADER_STYLE.block_id
40 | };
41 | },
42 | };
43 | const ID_LINK_STYLE = {
44 | css: 'tag',
45 | link: function(text) {
46 | let id = Number(text);
47 | view.select(function(location) { return location.node_id == id; }, true, true);
48 | }
49 | };
50 | const NODE_STYLE = { css: 'kwd' };
51 | const GOTO_STYLE = { css: 'kwd',
52 | goto_id: -2,
53 | location: function(text) {
54 | return {
55 | node_id: GOTO_STYLE.goto_id--,
56 | block_id: BLOCK_HEADER_STYLE.block_id
57 | };
58 | }
59 | }
60 | const ARROW_STYLE = { css: 'kwd' };
61 | let patterns = [
62 | [
63 | [/^--- BLOCK B\d+/, BLOCK_HEADER_STYLE, 1],
64 | [/^\s+\d+: /, ID_STYLE, 2],
65 | [/^\s+Goto/, GOTO_STYLE, 6],
66 | [/^.*/, null, -1]
67 | ],
68 | [
69 | [/^ +/, null],
70 | [/^\(deferred\)/, BLOCK_HEADER_STYLE],
71 | [/^B\d+/, BLOCK_LINK_STYLE],
72 | [/^<-/, ARROW_STYLE],
73 | [/^->/, ARROW_STYLE],
74 | [/^,/, null],
75 | [/^---/, BLOCK_HEADER_STYLE, -1]
76 | ],
77 | // Parse opcode including []
78 | [
79 | [/^[A-Za-z0-9_]+(\[.*\])?$/, NODE_STYLE, -1],
80 | [/^[A-Za-z0-9_]+(\[(\[.*?\]|.)*?\])?/, NODE_STYLE, 3]
81 | ],
82 | // Parse optional parameters
83 | [
84 | [/^ /, null, 4],
85 | [/^\(/, null],
86 | [/^\d+/, ID_LINK_STYLE],
87 | [/^, /, null],
88 | [/^\)$/, null, -1],
89 | [/^\)/, null, 4],
90 | ],
91 | [
92 | [/^ -> /, ARROW_STYLE, 5],
93 | [/^.*/, null, -1]
94 | ],
95 | [
96 | [/^B\d+$/, BLOCK_LINK_STYLE, -1],
97 | [/^B\d+/, BLOCK_LINK_STYLE],
98 | [/^, /, null]
99 | ],
100 | [
101 | [/^ -> /, ARROW_STYLE],
102 | [/^B\d+$/, BLOCK_LINK_STYLE, -1]
103 | ]
104 | ];
105 | this.setPatterns(patterns);
106 | }
107 |
108 | initializeContent(data, rememberedSelection) {
109 | super.initializeContent(data, rememberedSelection);
110 | var graph = this;
111 | var locations = [];
112 | for (var id of rememberedSelection) {
113 | locations.push({ node_id : id });
114 | }
115 | this.selectLocations(locations, true, true);
116 | }
117 |
118 | detachSelection() {
119 | var selection = this.selection.detachSelection();
120 | var s = new Set();
121 | for (var i of selection) {
122 | if (i.location.node_id != undefined && i.location.node_id > 0) {
123 | s.add(i.location.node_id);
124 | }
125 | };
126 | return s;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Turbolizer
5 |
6 |
7 |
8 |
13 |
14 |
57 |
58 |
66 |
67 |
69 |
71 |
72 |
73 |
75 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/node.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | var TYPE_HEIGHT = 25;
6 | var DEFAULT_NODE_BUBBLE_RADIUS = 12;
7 | var NODE_INPUT_WIDTH = 50;
8 | var MINIMUM_NODE_INPUT_APPROACH = 15 + 2 * DEFAULT_NODE_BUBBLE_RADIUS;
9 | var MINIMUM_NODE_OUTPUT_APPROACH = 15;
10 |
11 | function isNodeInitiallyVisible(node) {
12 | return node.cfg;
13 | }
14 |
15 | var Node = {
16 | isControl: function() {
17 | return this.control;
18 | },
19 | isInput: function() {
20 | return this.opcode == 'Parameter' || this.opcode.endsWith('Constant');
21 | },
22 | isLive: function() {
23 | return this.live !== false;
24 | },
25 | isJavaScript: function() {
26 | return this.opcode.startsWith('JS');
27 | },
28 | isSimplified: function() {
29 | if (this.isJavaScript()) return false;
30 | return this.opcode.endsWith('Phi') ||
31 | this.opcode.startsWith('Boolean') ||
32 | this.opcode.startsWith('Number') ||
33 | this.opcode.startsWith('String') ||
34 | this.opcode.startsWith('Change') ||
35 | this.opcode.startsWith('Object') ||
36 | this.opcode.startsWith('Reference') ||
37 | this.opcode.startsWith('Any') ||
38 | this.opcode.endsWith('ToNumber') ||
39 | (this.opcode == 'AnyToBoolean') ||
40 | (this.opcode.startsWith('Load') && this.opcode.length > 4) ||
41 | (this.opcode.startsWith('Store') && this.opcode.length > 5);
42 | },
43 | isMachine: function() {
44 | return !(this.isControl() || this.isInput() ||
45 | this.isJavaScript() || this.isSimplified());
46 | },
47 | getTotalNodeWidth: function() {
48 | var inputWidth = this.inputs.length * NODE_INPUT_WIDTH;
49 | return Math.max(inputWidth, this.width);
50 | },
51 | getTitle: function() {
52 | var propsString;
53 | if (this.properties === undefined) {
54 | propsString = "";
55 | } else if (this.properties === "") {
56 | propsString = "no properties";
57 | } else {
58 | propsString = "[" + this.properties + "]";
59 | }
60 | return this.title + "\n" + propsString + "\n" + this.opinfo;
61 | },
62 | getDisplayLabel: function() {
63 | var result = this.id + ":" + this.label;
64 | if (result.length > 40) {
65 | return this.id + ":" + this.opcode;
66 | } else {
67 | return result;
68 | }
69 | },
70 | getType: function() {
71 | return this.type;
72 | },
73 | getDisplayType: function() {
74 | var type_string = this.type;
75 | if (type_string == undefined) return "";
76 | if (type_string.length > 24) {
77 | type_string = type_string.substr(0, 25) + "...";
78 | }
79 | return type_string;
80 | },
81 | deepestInputRank: function() {
82 | var deepestRank = 0;
83 | this.inputs.forEach(function(e) {
84 | if (e.isVisible() && !e.isBackEdge()) {
85 | if (e.source.rank > deepestRank) {
86 | deepestRank = e.source.rank;
87 | }
88 | }
89 | });
90 | return deepestRank;
91 | },
92 | areAnyOutputsVisible: function() {
93 | var visibleCount = 0;
94 | this.outputs.forEach(function(e) { if (e.isVisible()) ++visibleCount; });
95 | if (this.outputs.length == visibleCount) return 2;
96 | if (visibleCount != 0) return 1;
97 | return 0;
98 | },
99 | setOutputVisibility: function(v) {
100 | var result = false;
101 | this.outputs.forEach(function(e) {
102 | e.visible = v;
103 | if (v) {
104 | if (!e.target.visible) {
105 | e.target.visible = true;
106 | result = true;
107 | }
108 | }
109 | });
110 | return result;
111 | },
112 | setInputVisibility: function(i, v) {
113 | var edge = this.inputs[i];
114 | edge.visible = v;
115 | if (v) {
116 | if (!edge.source.visible) {
117 | edge.source.visible = true;
118 | return true;
119 | }
120 | }
121 | return false;
122 | },
123 | getInputApproach: function(index) {
124 | return this.y - MINIMUM_NODE_INPUT_APPROACH -
125 | (index % 4) * MINIMUM_EDGE_SEPARATION - DEFAULT_NODE_BUBBLE_RADIUS
126 | },
127 | getOutputApproach: function(graph, index) {
128 | return this.y + this.outputApproach + graph.getNodeHeight(this) +
129 | + DEFAULT_NODE_BUBBLE_RADIUS;
130 | },
131 | getInputX: function(index) {
132 | var result = this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2) +
133 | (index - this.inputs.length + 1) * NODE_INPUT_WIDTH;
134 | return result;
135 | },
136 | getOutputX: function() {
137 | return this.getTotalNodeWidth() - (NODE_INPUT_WIDTH / 2);
138 | },
139 | getFunctionRelativeSourcePosition: function(graph) {
140 | return this.pos - graph.sourcePosition;
141 | },
142 | hasBackEdges: function() {
143 | return (this.opcode == "Loop") ||
144 | ((this.opcode == "Phi" || this.opcode == "EffectPhi") &&
145 | this.inputs[this.inputs.length - 1].source.opcode == "Loop");
146 | }
147 | };
148 |
--------------------------------------------------------------------------------
/code-view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class CodeView extends View {
8 | constructor(divID, PR, sourceText, sourcePosition, broker) {
9 | super(divID, broker, null, false);
10 | let view = this;
11 | view.PR = PR;
12 | view.mouseDown = false;
13 | view.broker = broker;
14 | view.allSpans = [];
15 |
16 | var selectionHandler = {
17 | clear: function() { broker.clear(selectionHandler); },
18 | select: function(items, selected) {
19 | var handler = this;
20 | var broker = view.broker;
21 | for (let span of items) {
22 | if (selected) {
23 | span.classList.add("selected");
24 | } else {
25 | span.classList.remove("selected");
26 | }
27 | }
28 | var locations = [];
29 | for (var span of items) {
30 | locations.push({pos_start: span.start, pos_end: span.end});
31 | }
32 | broker.clear(selectionHandler);
33 | broker.select(selectionHandler, locations, selected);
34 | },
35 | selectionDifference: function(span1, inclusive1, span2, inclusive2) {
36 | var pos1 = span1.start;
37 | var pos2 = span2.start;
38 | var result = [];
39 | var lineListDiv = view.divNode.firstChild.firstChild.childNodes;
40 | for (var i = 0; i < lineListDiv.length; i++) {
41 | var currentLineElement = lineListDiv[i];
42 | var spans = currentLineElement.childNodes;
43 | for (var j = 0; j < spans.length; ++j) {
44 | var currentSpan = spans[j];
45 | if (currentSpan.start > pos1 ||
46 | (inclusive1 && currentSpan.start == pos1)) {
47 | if (currentSpan.start < pos2 ||
48 | (inclusive2 && currentSpan.start == pos2)) {
49 | result.push(currentSpan);
50 | }
51 | }
52 | }
53 | }
54 | return result;
55 | },
56 | brokeredSelect: function(locations, selected) {
57 | let firstSelect = view.selection.isEmpty();
58 | for (let location of locations) {
59 | let start = location.pos_start;
60 | let end = location.pos_end;
61 | if (start && end) {
62 | let lower = 0;
63 | let upper = view.allSpans.length;
64 | if (upper > 0) {
65 | while ((upper - lower) > 1) {
66 | var middle = Math.floor((upper + lower) / 2);
67 | var lineStart = view.allSpans[middle].start;
68 | if (lineStart < start) {
69 | lower = middle;
70 | } else if (lineStart > start) {
71 | upper = middle;
72 | } else {
73 | lower = middle;
74 | break;
75 | }
76 | }
77 | var currentSpan = view.allSpans[lower];
78 | var currentLineElement = currentSpan.parentNode;
79 | if ((currentSpan.start <= start && start < currentSpan.end) ||
80 | (currentSpan.start <= end && end < currentSpan.end)) {
81 | if (firstSelect) {
82 | makeContainerPosVisible(
83 | view.divNode, currentLineElement.offsetTop);
84 | firstSelect = false;
85 | }
86 | view.selection.select(currentSpan, selected);
87 | }
88 | }
89 | }
90 | }
91 | },
92 | brokeredClear: function() { view.selection.clear(); },
93 | };
94 | view.selection = new Selection(selectionHandler);
95 | broker.addSelectionHandler(selectionHandler);
96 |
97 | view.handleSpanMouseDown = function(e) {
98 | e.stopPropagation();
99 | if (!e.shiftKey) {
100 | view.selection.clear();
101 | }
102 | view.selection.select(this, true);
103 | view.mouseDown = true;
104 | }
105 |
106 | view.handleSpanMouseMove = function(e) {
107 | if (view.mouseDown) {
108 | view.selection.extendTo(this);
109 | }
110 | }
111 |
112 | view.handleCodeMouseDown = function(e) { view.selection.clear(); }
113 |
114 | document.addEventListener('mouseup', function(e) {
115 | view.mouseDown = false;
116 | }, false);
117 |
118 | view.initializeCode(sourceText, sourcePosition);
119 | }
120 |
121 | initializeContent(data, rememberedSelection) { this.data = data; }
122 |
123 | initializeCode(sourceText, sourcePosition) {
124 | var view = this;
125 | var codePre = document.createElement("pre");
126 | codePre.classList.add("prettyprint");
127 | view.divNode.innerHTML = "";
128 | view.divNode.appendChild(codePre);
129 | if (sourceText) {
130 | codePre.classList.add("linenums");
131 | codePre.textContent = sourceText;
132 | try {
133 | // Wrap in try to work when offline.
134 | view.PR.prettyPrint();
135 | } catch (e) {
136 | }
137 |
138 | view.divNode.onmousedown = this.handleCodeMouseDown;
139 |
140 | var base = sourcePosition;
141 | var current = 0;
142 | var lineListDiv = view.divNode.firstChild.firstChild.childNodes;
143 | for (let i = 0; i < lineListDiv.length; i++) {
144 | var currentLineElement = lineListDiv[i];
145 | currentLineElement.id = "li" + i;
146 | var pos = base + current;
147 | currentLineElement.pos = pos;
148 | var spans = currentLineElement.childNodes;
149 | for (let j = 0; j < spans.length; ++j) {
150 | var currentSpan = spans[j];
151 | if (currentSpan.nodeType == 1) {
152 | currentSpan.start = pos;
153 | currentSpan.end = pos + currentSpan.textContent.length;
154 | currentSpan.onmousedown = this.handleSpanMouseDown;
155 | currentSpan.onmousemove = this.handleSpanMouseMove;
156 | view.allSpans.push(currentSpan);
157 | }
158 | current += currentSpan.textContent.length;
159 | pos = base + current;
160 | }
161 | while ((current < sourceText.length) &&
162 | (sourceText[current] == '\n' || sourceText[current] == '\r')) {
163 | ++current;
164 | }
165 | }
166 | }
167 | }
168 |
169 | deleteContent() {}
170 | }
171 |
--------------------------------------------------------------------------------
/turbo-visualizer.css:
--------------------------------------------------------------------------------
1 | .visible-transition {
2 | transition-delay: 0s;
3 | transition-duration: 1s;
4 | transition-property: all;
5 | transition-timing-function: ease;
6 | }
7 |
8 | .collapse-pane {
9 | background: #A0A0A0;
10 | bottom: 0;
11 | position: absolute;
12 | margin-bottom: 0.5em;
13 | margin-right: 0.5em;
14 | margin-left: 0.5em;
15 | border-radius: 5px;
16 | padding: 0.5em;
17 | z-index: 5;
18 | opacity: 0.7;
19 | cursor: pointer;
20 | }
21 |
22 | .search-input {
23 | vertical-align: middle;
24 | width: 145px;
25 | opacity: 1;
26 | }
27 |
28 | .button-input {
29 | vertical-align: middle;
30 | width: 24px;
31 | opacity: 0.4;
32 | cursor: pointer;
33 | }
34 |
35 | .button-input-toggled {
36 | border-radius: 5px;
37 | background-color: #505050;
38 | }
39 |
40 | .button-input:focus {
41 | outline: none;
42 | }
43 |
44 | .invisible {
45 | display: none;
46 | }
47 |
48 |
49 | .selected {
50 | background-color: #FFFF33;
51 | }
52 |
53 | .prettyprint ol.linenums > li {
54 | list-style-type: decimal;
55 | !important
56 | }
57 |
58 |
59 | body {
60 | margin: 0;
61 | padding: 0;
62 | height: 100vh;
63 | width: 100vw;
64 | overflow:hidden;
65 | -webkit-touch-callout: none;
66 | -webkit-user-select: none;
67 | -khtml-user-select: none;
68 | -moz-user-select: none;
69 | -ms-user-select: none;
70 | user-select: none;
71 | }
72 |
73 | p {
74 | text-align: center;
75 | overflow: overlay;
76 | position: relative;
77 | }
78 |
79 | marker {
80 | fill: #080808;
81 | }
82 |
83 | g rect {
84 | fill: #F0F0F0;
85 | stroke: #080808;
86 | stroke-width: 2px;
87 | }
88 |
89 | g.dead {
90 | opacity: .5;
91 | }
92 |
93 | g.unsorted rect {
94 | opacity: 0.5;
95 | }
96 |
97 | div.scrollable {
98 | overflow-y: _croll; overflow-x: hidden;
99 | }
100 |
101 | g.turbonode[relToHover="input"] rect {
102 | stroke: #67e62c;
103 | stroke-width: 16px;
104 | }
105 |
106 | g.turbonode[relToHover="output"] rect {
107 | stroke: #d23b14;
108 | stroke-width: 16px;
109 | }
110 |
111 | path[relToHover="input"] {
112 | stroke: #67e62c;
113 | stroke-width: 16px;
114 | }
115 |
116 | path[relToHover="output"] {
117 | stroke: #d23b14;
118 | stroke-width: 16px;
119 | }
120 |
121 |
122 | g.turbonode:hover rect {
123 | stroke: #000000;
124 | stroke-width: 7px;
125 | }
126 |
127 | g.control rect {
128 | fill: #EFCC00;
129 | stroke: #080808;
130 | stroke-width: 5px;
131 | }
132 |
133 | g.javascript rect {
134 | fill: #DD7E6B;
135 | }
136 |
137 | g.simplified rect {
138 | fill: #3C78D8;
139 | }
140 |
141 | g.machine rect {
142 | fill: #6AA84F;
143 | }
144 |
145 | g.input rect {
146 | fill: #CFE2F3;
147 | }
148 |
149 | g.selected rect {
150 | fill: #FFFF33;
151 | }
152 |
153 | circle.bubbleStyle {
154 | fill: #080808;
155 | fill-opacity: 0.0;
156 | stroke: #080808;
157 | stroke-width: 2px;
158 | }
159 |
160 | circle.bubbleStyle:hover {
161 | stroke-width: 3px;
162 | }
163 |
164 | circle.filledBubbleStyle {
165 | fill: #080808;
166 | stroke: #080808;
167 | stroke-width: 2px;
168 | }
169 |
170 | circle.filledBubbleStyle:hover {
171 | fill: #080808;
172 | stroke-width: 3px;
173 | }
174 |
175 | circle.halfFilledBubbleStyle {
176 | fill: #808080;
177 | stroke: #101010;
178 | stroke-width: 2px;
179 | }
180 |
181 | circle.halfFilledBubbleStyle:hover {
182 | fill: #808080;
183 | stroke-width: 3px;
184 | }
185 |
186 | path {
187 | fill: none;
188 | stroke: #080808;
189 | stroke-width: 4px;
190 | cursor: default;
191 | }
192 |
193 | path:hover {
194 | stroke-width: 6px;
195 | }
196 |
197 | path.hidden {
198 | fill: none;
199 | stroke-width: 0;
200 | }
201 |
202 | path.link.selected {
203 | stroke: #FFFF33;
204 | }
205 |
206 | pre.prettyprint {
207 | border: none !important;
208 | padding: 0px;
209 | }
210 |
211 | li.L1,
212 | li.L3,
213 | li.L5,
214 | li.L7,
215 | li.L9 {
216 | background: none !important
217 | }
218 |
219 | li.nolinenums {
220 | list-style-type:none;
221 | }
222 |
223 | ul.noindent {
224 | -webkit-padding-start: 0px;
225 | -webkit-margin-before: 0px;
226 | -webkit-margin-after: 0px;
227 | }
228 |
229 | input:hover, .collapse-pane:hover input {
230 | opacity: 1;
231 | cursor: pointer;
232 | }
233 |
234 | span.linkable-text {
235 | text-decoration: underline;
236 | }
237 |
238 | span.linkable-text:hover {
239 | cursor: pointer;
240 | font-weight: bold;
241 | }
242 |
243 |
244 | #left {
245 | float: left;
246 | }
247 |
248 | #middle {
249 | float:left; background-color: #F8F8F8;
250 | }
251 |
252 | #right {
253 | float: right;
254 | }
255 |
256 | .viewpane {
257 | height: 100vh;
258 | background-color: #FFFFFF;
259 | }
260 |
261 |
262 | #disassembly-collapse {
263 | right: 0;
264 | }
265 |
266 | #source-collapse {
267 | left: 0;
268 | }
269 |
270 | #graph-toolbox-anchor {
271 | height: 0px;
272 | }
273 |
274 | #graph-toolbox {
275 | position: relative;
276 | top: 1em;
277 | left: 25px;
278 | border: 2px solid #eee8d5;
279 | border-radius: 5px;
280 | padding: 0.7em;
281 | z-index: 5;
282 | background: rgba(100%, 100%, 100%, 0.7);
283 | }
284 |
285 | #disassembly-toolbox {
286 | position: relative;
287 | top: 1em;
288 | left: 0.7em;
289 | border: 2px solid #eee8d5;
290 | border-radius: 5px;
291 | padding: 0.7em;
292 | z-index: 5;
293 | }
294 |
295 | #load-file {
296 | position: absolute;
297 | top: 0;
298 | right: 0;
299 | margin-top: 0.5em;
300 | margin-right: 0.5em;
301 | z-index: 5;
302 | opacity: 0.7;
303 | }
304 |
305 | #load-file input {
306 | background: #A0A0A0;
307 | border-radius: 5px;
308 | padding: 0.5em;
309 | }
310 |
311 | #hidden-file-upload {
312 | display: none;
313 | }
314 |
315 | .prof {
316 | cursor: default;
317 | }
318 |
319 | tspan {
320 | font-size: 500%;
321 | font-family: sans-serif;
322 | }
323 |
324 | text {
325 | dominant-baseline: text-before-edge;
326 | }
327 |
328 | .resizer-left {
329 | position:absolute;
330 | width: 4px;
331 | height:100%;
332 | background: #a0a0a0;
333 | cursor: pointer;
334 | }
335 |
336 | .resizer-left.snapped {
337 | width: 12px;
338 | }
339 |
340 | .resizer-left:hover {
341 | background: orange;
342 | }
343 |
344 | .resizer-left.dragged {
345 | background: orange;
346 | }
347 |
348 | .resizer-right {
349 | position:absolute;
350 | width: 4px;
351 | height:100%;
352 | background: #a0a0a0;
353 | cursor: pointer;
354 | }
355 |
356 | .resizer-right.snapped {
357 | width: 12px;
358 | }
359 |
360 | .resizer-right:hover {
361 | background: orange;
362 | }
363 |
364 | .resizer-right.dragged {
365 | background: orange;
366 | }
--------------------------------------------------------------------------------
/text-view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class TextView extends View {
8 | constructor(id, broker, patterns, allowSpanSelection) {
9 | super(id, broker);
10 | let view = this;
11 | view.hide();
12 | view.textListNode = view.divNode.getElementsByTagName('ul')[0];
13 | view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0");
14 | view.patterns = patterns;
15 | view.allowSpanSelection = allowSpanSelection;
16 | view.nodeToLineMap = [];
17 | var selectionHandler = {
18 | clear: function() {
19 | broker.clear(selectionHandler);
20 | },
21 | select: function(items, selected) {
22 | for (let i of items) {
23 | if (selected) {
24 | i.classList.add("selected");
25 | } else {
26 | i.classList.remove("selected");
27 | }
28 | }
29 | broker.clear(selectionHandler);
30 | broker.select(selectionHandler, view.getLocations(items), selected);
31 | },
32 | selectionDifference: function(span1, inclusive1, span2, inclusive2) {
33 | return null;
34 | },
35 | brokeredSelect: function(locations, selected) {
36 | view.selectLocations(locations, selected, true);
37 | },
38 | brokeredClear: function() {
39 | view.selection.clear();
40 | }
41 | };
42 | view.selection = new Selection(selectionHandler);
43 | broker.addSelectionHandler(selectionHandler);
44 | }
45 |
46 | setPatterns(patterns) {
47 | let view = this;
48 | view.patterns = patterns;
49 | }
50 |
51 | clearText() {
52 | let view = this;
53 | while (view.textListNode.firstChild) {
54 | view.textListNode.removeChild(view.textListNode.firstChild);
55 | }
56 | }
57 |
58 | sameLocation(l1, l2) {
59 | let view = this;
60 | if (l1.block_id != undefined && l2.block_id != undefined &&
61 | l1.block_id == l2.block_id && l1.node_id === undefined) {
62 | return true;
63 | }
64 |
65 | if (l1.address != undefined && l1.address == l2.address) {
66 | return true;
67 | }
68 |
69 | let node1 = l1.node_id;
70 | let node2 = l2.node_id;
71 |
72 | if (node1 === undefined || node2 == undefined) {
73 | if (l1.pos_start === undefined || l2.pos_start == undefined) {
74 | return false;
75 | }
76 | if (l1.pos_start == -1 || l2.pos_start == -1) {
77 | return false;
78 | }
79 | if (l1.pos_start < l2.pos_start) {
80 | return l1.pos_end > l2.pos_start;
81 | } {
82 | return l1.pos_start < l2.pos_end;
83 | }
84 | }
85 |
86 | return l1.node_id == l2.node_id;
87 | }
88 |
89 | selectLocations(locations, selected, makeVisible) {
90 | let view = this;
91 | let s = new Set();
92 | for (let l of locations) {
93 | for (let i = 0; i < view.textListNode.children.length; ++i) {
94 | let child = view.textListNode.children[i];
95 | if (child.location != undefined && view.sameLocation(l, child.location)) {
96 | s.add(child);
97 | }
98 | }
99 | }
100 | view.selectCommon(s, selected, makeVisible);
101 | }
102 |
103 | getLocations(items) {
104 | let result = [];
105 | let lastObject = null;
106 | for (let i of items) {
107 | if (i.location) {
108 | result.push(i.location);
109 | }
110 | }
111 | return result;
112 | }
113 |
114 | createFragment(text, style) {
115 | let view = this;
116 | let span = document.createElement("SPAN");
117 | span.onmousedown = function(e) {
118 | view.mouseDownSpan(span, e);
119 | }
120 | if (style != undefined) {
121 | span.classList.add(style);
122 | }
123 | span.innerHTML = text;
124 | return span;
125 | }
126 |
127 | appendFragment(li, fragment) {
128 | li.appendChild(fragment);
129 | }
130 |
131 | processLine(line) {
132 | let view = this;
133 | let result = [];
134 | let patternSet = 0;
135 | while (true) {
136 | let beforeLine = line;
137 | for (let pattern of view.patterns[patternSet]) {
138 | let matches = line.match(pattern[0]);
139 | if (matches != null) {
140 | if (matches[0] != '') {
141 | let style = pattern[1] != null ? pattern[1] : {};
142 | let text = matches[0];
143 | if (text != '') {
144 | let fragment = view.createFragment(matches[0], style.css);
145 | if (style.link) {
146 | fragment.classList.add('linkable-text');
147 | fragment.link = style.link;
148 | }
149 | result.push(fragment);
150 | if (style.location != undefined) {
151 | let location = style.location(text);
152 | if (location != undefined) {
153 | fragment.location = location;
154 | }
155 | }
156 | }
157 | line = line.substr(matches[0].length);
158 | }
159 | let nextPatternSet = patternSet;
160 | if (pattern.length > 2) {
161 | nextPatternSet = pattern[2];
162 | }
163 | if (line == "") {
164 | if (nextPatternSet != -1) {
165 | throw("illegal parsing state in text-view in patternSet" + patternSet);
166 | }
167 | return result;
168 | }
169 | patternSet = nextPatternSet;
170 | break;
171 | }
172 | }
173 | if (beforeLine == line) {
174 | throw("input not consumed in text-view in patternSet" + patternSet);
175 | }
176 | }
177 | }
178 |
179 | select(s, selected, makeVisible) {
180 | let view = this;
181 | view.selection.clear();
182 | view.selectCommon(s, selected, makeVisible);
183 | }
184 |
185 | selectCommon(s, selected, makeVisible) {
186 | let view = this;
187 | let firstSelect = makeVisible && view.selection.isEmpty();
188 | if ((typeof s) === 'function') {
189 | for (let i = 0; i < view.textListNode.children.length; ++i) {
190 | let child = view.textListNode.children[i];
191 | if (child.location && s(child.location)) {
192 | if (firstSelect) {
193 | makeContainerPosVisible(view.parentNode, child.offsetTop);
194 | firstSelect = false;
195 | }
196 | view.selection.select(child, selected);
197 | }
198 | }
199 | } else if (typeof s[Symbol.iterator] === 'function') {
200 | if (firstSelect) {
201 | for (let i of s) {
202 | makeContainerPosVisible(view.parentNode, i.offsetTop);
203 | break;
204 | }
205 | }
206 | view.selection.select(s, selected);
207 | } else {
208 | if (firstSelect) {
209 | makeContainerPosVisible(view.parentNode, s.offsetTop);
210 | }
211 | view.selection.select(s, selected);
212 | }
213 | }
214 |
215 | mouseDownLine(li, e) {
216 | let view = this;
217 | e.stopPropagation();
218 | if (!e.shiftKey) {
219 | view.selection.clear();
220 | }
221 | if (li.location != undefined) {
222 | view.selectLocations([li.location], true, false);
223 | }
224 | }
225 |
226 | mouseDownSpan(span, e) {
227 | let view = this;
228 | if (view.allowSpanSelection) {
229 | e.stopPropagation();
230 | if (!e.shiftKey) {
231 | view.selection.clear();
232 | }
233 | select(li, true);
234 | } else if (span.link) {
235 | span.link(span.textContent);
236 | e.stopPropagation();
237 | }
238 | }
239 |
240 | processText(text) {
241 | let view = this;
242 | let textLines = text.split(/[\n]/);
243 | let lineNo = 0;
244 | for (let line of textLines) {
245 | let li = document.createElement("LI");
246 | li.onmousedown = function(e) {
247 | view.mouseDownLine(li, e);
248 | }
249 | li.className = "nolinenums";
250 | li.lineNo = lineNo++;
251 | let fragments = view.processLine(line);
252 | for (let fragment of fragments) {
253 | view.appendFragment(li, fragment);
254 | }
255 | let lineLocation = view.lineLocation(li);
256 | if (lineLocation != undefined) {
257 | li.location = lineLocation;
258 | }
259 | view.textListNode.appendChild(li);
260 | }
261 | }
262 |
263 | initializeContent(data, rememberedSelection) {
264 | let view = this;
265 | view.selection.clear();
266 | view.clearText();
267 | view.processText(data);
268 | var fillerSize = document.documentElement.clientHeight -
269 | view.textListNode.clientHeight;
270 | if (fillerSize < 0) {
271 | fillerSize = 0;
272 | }
273 | view.fillerSvgElement.attr("height", fillerSize);
274 | }
275 |
276 | deleteContent() {
277 | }
278 |
279 | isScrollable() {
280 | return true;
281 | }
282 |
283 | detachSelection() {
284 | return null;
285 | }
286 |
287 | lineLocation(li) {
288 | let view = this;
289 | for (let i = 0; i < li.children.length; ++i) {
290 | let fragment = li.children[i];
291 | if (fragment.location != undefined && !view.allowSpanSelection) {
292 | return fragment.location;
293 | }
294 | }
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/disassembly-view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class DisassemblyView extends TextView {
8 | constructor(id, broker) {
9 | super(id, broker, null, false);
10 |
11 | let view = this;
12 | let ADDRESS_STYLE = {
13 | css: 'tag',
14 | location: function(text) {
15 | ADDRESS_STYLE.last_address = text;
16 | return undefined;
17 | }
18 | };
19 | let ADDRESS_LINK_STYLE = {
20 | css: 'tag',
21 | link: function(text) {
22 | view.select(function(location) { return location.address == text; }, true, true);
23 | }
24 | };
25 | let UNCLASSIFIED_STYLE = {
26 | css: 'com'
27 | };
28 | let NUMBER_STYLE = {
29 | css: 'lit'
30 | };
31 | let COMMENT_STYLE = {
32 | css: 'com'
33 | };
34 | let POSITION_STYLE = {
35 | css: 'com',
36 | location: function(text) {
37 | view.pos_start = Number(text);
38 | }
39 | };
40 | let OPCODE_STYLE = {
41 | css: 'kwd',
42 | location: function(text) {
43 | if (BLOCK_HEADER_STYLE.block_id != undefined) {
44 | return {
45 | address: ADDRESS_STYLE.last_address,
46 | block_id: BLOCK_HEADER_STYLE.block_id
47 | };
48 | } else {
49 | return {
50 | address: ADDRESS_STYLE.last_address
51 | };
52 | }
53 | }
54 | };
55 | const BLOCK_HEADER_STYLE = {
56 | css: 'com',
57 | block_id: -1,
58 | location: function(text) {
59 | let matches = /\d+/.exec(text);
60 | if (!matches) return undefined;
61 | BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
62 | return {
63 | block_id: BLOCK_HEADER_STYLE.block_id
64 | };
65 | },
66 | };
67 | const SOURCE_POSITION_HEADER_STYLE = {
68 | css: 'com',
69 | location: function(text) {
70 | let matches = /(\d+):(\d+)/.exec(text);
71 | if (!matches) return undefined;
72 | let li = Number(matches[1]);
73 | if (view.pos_lines === null) return undefined;
74 | let pos = view.pos_lines[li-1] + Number(matches[2]);
75 | return {
76 | pos_start: pos,
77 | pos_end: pos + 1
78 | };
79 | },
80 | };
81 | view.SOURCE_POSITION_HEADER_REGEX = /^(\s*-- .+:)(\d+:\d+)( --)/;
82 | let patterns = [
83 | [
84 | [/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1],
85 | [view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1],
86 | [/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1],
87 | [/^.*/, UNCLASSIFIED_STYLE, -1]
88 | ],
89 | [
90 | [/^\s+[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2],
91 | [/^.*/, null, -1]
92 | ],
93 | [
94 | [/^\S+\s+/, OPCODE_STYLE, 3],
95 | [/^\S+$/, OPCODE_STYLE, -1],
96 | [/^.*/, null, -1]
97 | ],
98 | [
99 | [/^\s+/, null],
100 | [/^[^\(;]+$/, null, -1],
101 | [/^[^\(;]+/, null],
102 | [/^\(/, null, 4],
103 | [/^;/, COMMENT_STYLE, 5]
104 | ],
105 | [
106 | [/^0x[0-9a-f]{8,16}/, ADDRESS_LINK_STYLE],
107 | [/^[^\)]/, null],
108 | [/^\)$/, null, -1],
109 | [/^\)/, null, 3]
110 | ],
111 | [
112 | [/^; debug\: position /, COMMENT_STYLE, 6],
113 | [/^.+$/, COMMENT_STYLE, -1]
114 | ],
115 | [
116 | [/^\d+$/, POSITION_STYLE, -1],
117 | ]
118 | ];
119 | view.setPatterns(patterns);
120 | }
121 |
122 | lineLocation(li) {
123 | let view = this;
124 | let result = undefined;
125 | for (let i = 0; i < li.children.length; ++i) {
126 | let fragment = li.children[i];
127 | let location = fragment.location;
128 | if (location != null) {
129 | if (location.block_id != undefined) {
130 | if (result === undefined) result = {};
131 | result.block_id = location.block_id;
132 | }
133 | if (location.address != undefined) {
134 | if (result === undefined) result = {};
135 | result.address = location.address;
136 | }
137 | if (location.pos_start != undefined && location.pos_end != undefined) {
138 | if (result === undefined) result = {};
139 | result.pos_start = location.pos_start;
140 | result.pos_end = location.pos_end;
141 | }
142 | else if (view.pos_start != -1) {
143 | if (result === undefined) result = {};
144 | result.pos_start = view.pos_start;
145 | result.pos_end = result.pos_start + 1;
146 | }
147 | }
148 | }
149 | return result;
150 | }
151 |
152 | initializeContent(data, rememberedSelection) {
153 | this.data = data;
154 | super.initializeContent(data, rememberedSelection);
155 | }
156 |
157 | initializeCode(sourceText, sourcePosition) {
158 | let view = this;
159 | view.pos_start = -1;
160 | view.addr_event_counts = null;
161 | view.total_event_counts = null;
162 | view.max_event_counts = null;
163 | view.pos_lines = new Array();
164 | // Comment lines for line 0 include sourcePosition already, only need to
165 | // add sourcePosition for lines > 0.
166 | view.pos_lines[0] = sourcePosition;
167 | if (sourceText) {
168 | let base = sourcePosition;
169 | let current = 0;
170 | let source_lines = sourceText.split("\n");
171 | for (let i = 1; i < source_lines.length; i++) {
172 | // Add 1 for newline character that is split off.
173 | current += source_lines[i-1].length + 1;
174 | view.pos_lines[i] = base + current;
175 | }
176 | }
177 | }
178 |
179 | initializePerfProfile(eventCounts) {
180 | let view = this;
181 | if (eventCounts !== undefined) {
182 | view.addr_event_counts = eventCounts;
183 |
184 | view.total_event_counts = {};
185 | view.max_event_counts = {};
186 | for (let ev_name in view.addr_event_counts) {
187 | let keys = Object.keys(view.addr_event_counts[ev_name]);
188 | let values = keys.map(key => view.addr_event_counts[ev_name][key]);
189 | view.total_event_counts[ev_name] = values.reduce((a, b) => a + b);
190 | view.max_event_counts[ev_name] = values.reduce((a, b) => Math.max(a, b));
191 | }
192 | }
193 | else {
194 | view.addr_event_counts = null;
195 | view.total_event_counts = null;
196 | view.max_event_counts = null;
197 | }
198 | }
199 |
200 | // Shorten decimals and remove trailing zeroes for readability.
201 | humanize(num) {
202 | return num.toFixed(3).replace(/\.?0+$/, "") + "%";
203 | }
204 |
205 | // Interpolate between the given start and end values by a fraction of val/max.
206 | interpolate(val, max, start, end) {
207 | return start + (end - start) * (val / max);
208 | }
209 |
210 | processLine(line) {
211 | let view = this;
212 | let func = function(match, p1, p2, p3) {
213 | let nums = p2.split(":");
214 | let li = Number(nums[0]);
215 | let pos = Number(nums[1]);
216 | if(li === 0)
217 | pos -= view.pos_lines[0];
218 | li++;
219 | return p1 + li + ":" + pos + p3;
220 | };
221 | line = line.replace(view.SOURCE_POSITION_HEADER_REGEX, func);
222 | let fragments = super.processLine(line);
223 |
224 | // Add profiling data per instruction if available.
225 | if (view.total_event_counts) {
226 | let matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line);
227 | if (matches) {
228 | let newFragments = [];
229 | for (let event in view.addr_event_counts) {
230 | let count = view.addr_event_counts[event][matches[1]];
231 | let str = " ";
232 | let css_cls = "prof";
233 | if(count !== undefined) {
234 | let perc = count / view.total_event_counts[event] * 100;
235 |
236 | let col = { r: 255, g: 255, b: 255 };
237 | for (let i = 0; i < PROF_COLS.length; i++) {
238 | if (perc === PROF_COLS[i].perc) {
239 | col = PROF_COLS[i].col;
240 | break;
241 | }
242 | else if (perc > PROF_COLS[i].perc && perc < PROF_COLS[i + 1].perc) {
243 | let col1 = PROF_COLS[i].col;
244 | let col2 = PROF_COLS[i + 1].col;
245 |
246 | let val = perc - PROF_COLS[i].perc;
247 | let max = PROF_COLS[i + 1].perc - PROF_COLS[i].perc;
248 |
249 | col.r = Math.round(view.interpolate(val, max, col1.r, col2.r));
250 | col.g = Math.round(view.interpolate(val, max, col1.g, col2.g));
251 | col.b = Math.round(view.interpolate(val, max, col1.b, col2.b));
252 | break;
253 | }
254 | }
255 |
256 | str = UNICODE_BLOCK;
257 |
258 | let fragment = view.createFragment(str, css_cls);
259 | fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")";
260 | fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")";
261 |
262 | newFragments.push(fragment);
263 | }
264 | else
265 | newFragments.push(view.createFragment(str, css_cls));
266 |
267 | }
268 | fragments = newFragments.concat(fragments);
269 | }
270 | }
271 | return fragments;
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/turbo-visualizer.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | class Snapper {
6 |
7 | constructor(resizer) {
8 | let snapper = this;
9 | snapper.resizer = resizer;
10 | snapper.sourceExpand = d3.select("#" + SOURCE_EXPAND_ID);
11 | snapper.sourceCollapse = d3.select("#" + SOURCE_COLLAPSE_ID);
12 | snapper.disassemblyExpand = d3.select("#" + DISASSEMBLY_EXPAND_ID);
13 | snapper.disassemblyCollapse = d3.select("#" + DISASSEMBLY_COLLAPSE_ID);
14 |
15 | d3.select("#source-collapse").on("click", function(){
16 | resizer.snapper.toggleSourceExpanded();
17 | });
18 | d3.select("#disassembly-collapse").on("click", function(){
19 | resizer.snapper.toggleDisassemblyExpanded();
20 | });
21 | }
22 |
23 | getLastExpandedState(type, default_state) {
24 | var state = window.sessionStorage.getItem("expandedState-"+type);
25 | if (state === null) return default_state;
26 | return state === 'true';
27 | }
28 |
29 | setLastExpandedState(type, state) {
30 | window.sessionStorage.setItem("expandedState-"+type, state);
31 | }
32 |
33 | toggleSourceExpanded() {
34 | this.setSourceExpanded(!this.sourceExpand.classed("invisible"));
35 | }
36 |
37 | sourceExpandUpdate(newState) {
38 | this.setLastExpandedState("source", newState);
39 | this.sourceExpand.classed("invisible", newState);
40 | this.sourceCollapse.classed("invisible", !newState);
41 | }
42 |
43 | setSourceExpanded(newState) {
44 | if (this.sourceExpand.classed("invisible") === newState) return;
45 | this.sourceExpandUpdate(newState);
46 | let resizer = this.resizer;
47 | if (newState) {
48 | resizer.sep_left = resizer.sep_left_snap;
49 | resizer.sep_left_snap = 0;
50 | } else {
51 | resizer.sep_left_snap = resizer.sep_left;
52 | resizer.sep_left = 0;
53 | }
54 | resizer.updatePanes();
55 | }
56 |
57 | toggleDisassemblyExpanded() {
58 | this.setDisassemblyExpanded(!this.disassemblyExpand.classed("invisible"));
59 | }
60 |
61 | disassemblyExpandUpdate(newState) {
62 | this.setLastExpandedState("disassembly", newState);
63 | this.disassemblyExpand.classed("invisible", newState);
64 | this.disassemblyCollapse.classed("invisible", !newState);
65 | }
66 |
67 | setDisassemblyExpanded(newState) {
68 | if (this.disassemblyExpand.classed("invisible") === newState) return;
69 | this.disassemblyExpandUpdate(newState);
70 | let resizer = this.resizer;
71 | if (newState) {
72 | resizer.sep_right = resizer.sep_right_snap;
73 | resizer.sep_right_snap = resizer.client_width;
74 | } else {
75 | resizer.sep_right_snap = resizer.sep_right;
76 | resizer.sep_right = resizer.client_width;
77 | }
78 | resizer.updatePanes();
79 | }
80 |
81 | panesUpated() {
82 | this.sourceExpandUpdate(this.resizer.sep_left > this.resizer.dead_width);
83 | this.disassemblyExpandUpdate(this.resizer.sep_right <
84 | (this.resizer.client_width - this.resizer.dead_width));
85 | }
86 | }
87 |
88 | class Resizer {
89 | constructor(panes_updated_callback, dead_width) {
90 | let resizer = this;
91 | resizer.snapper = new Snapper(resizer)
92 | resizer.panes_updated_callback = panes_updated_callback;
93 | resizer.dead_width = dead_width
94 | resizer.client_width = d3.select("body").node().getBoundingClientRect().width;
95 | resizer.left = d3.select("#" + SOURCE_PANE_ID);
96 | resizer.middle = d3.select("#" + INTERMEDIATE_PANE_ID);
97 | resizer.right = d3.select("#" + GENERATED_PANE_ID);
98 | resizer.resizer_left = d3.select('.resizer-left');
99 | resizer.resizer_right = d3.select('.resizer-right');
100 | resizer.sep_left = resizer.client_width/3;
101 | resizer.sep_right = resizer.client_width/3*2;
102 | resizer.sep_left_snap = 0;
103 | resizer.sep_right_snap = 0;
104 | // Offset to prevent resizers from sliding slightly over one another.
105 | resizer.sep_width_offset = 7;
106 |
107 | let dragResizeLeft = d3.behavior.drag()
108 | .on('drag', function() {
109 | let x = d3.mouse(this.parentElement)[0];
110 | resizer.sep_left = Math.min(Math.max(0,x), resizer.sep_right-resizer.sep_width_offset);
111 | resizer.updatePanes();
112 | })
113 | .on('dragstart', function() {
114 | resizer.resizer_left.classed("dragged", true);
115 | let x = d3.mouse(this.parentElement)[0];
116 | if (x > dead_width) {
117 | resizer.sep_left_snap = resizer.sep_left;
118 | }
119 | })
120 | .on('dragend', function() {
121 | resizer.resizer_left.classed("dragged", false);
122 | });
123 | resizer.resizer_left.call(dragResizeLeft);
124 |
125 | let dragResizeRight = d3.behavior.drag()
126 | .on('drag', function() {
127 | let x = d3.mouse(this.parentElement)[0];
128 | resizer.sep_right = Math.max(resizer.sep_left+resizer.sep_width_offset, Math.min(x, resizer.client_width));
129 | resizer.updatePanes();
130 | })
131 | .on('dragstart', function() {
132 | resizer.resizer_right.classed("dragged", true);
133 | let x = d3.mouse(this.parentElement)[0];
134 | if (x < (resizer.client_width-dead_width)) {
135 | resizer.sep_right_snap = resizer.sep_right;
136 | }
137 | })
138 | .on('dragend', function() {
139 | resizer.resizer_right.classed("dragged", false);
140 | });;
141 | resizer.resizer_right.call(dragResizeRight);
142 | window.onresize = function(){
143 | resizer.updateWidths();
144 | /*fitPanesToParents();*/
145 | resizer.updatePanes();
146 | };
147 | }
148 |
149 | updatePanes() {
150 | let left_snapped = this.sep_left === 0;
151 | let right_snapped = this.sep_right >= this.client_width - 1;
152 | this.resizer_left.classed("snapped", left_snapped);
153 | this.resizer_right.classed("snapped", right_snapped);
154 | this.left.style('width', this.sep_left + 'px');
155 | this.middle.style('width', (this.sep_right-this.sep_left) + 'px');
156 | this.right.style('width', (this.client_width - this.sep_right) + 'px');
157 | this.resizer_left.style('left', this.sep_left + 'px');
158 | this.resizer_right.style('right', (this.client_width - this.sep_right - 1) + 'px');
159 |
160 | this.snapper.panesUpated();
161 | this.panes_updated_callback();
162 | }
163 |
164 | updateWidths() {
165 | this.client_width = d3.select("body").node().getBoundingClientRect().width;
166 | this.sep_right = Math.min(this.sep_right, this.client_width);
167 | this.sep_left = Math.min(Math.max(0, this.sep_left), this.sep_right);
168 | }
169 | }
170 |
171 | document.onload = (function(d3){
172 | "use strict";
173 | var jsonObj;
174 | var svg = null;
175 | var graph = null;
176 | var schedule = null;
177 | var empty = null;
178 | var currentPhaseView = null;
179 | var disassemblyView = null;
180 | var sourceView = null;
181 | var selectionBroker = null;
182 | let resizer = new Resizer(panesUpdatedCallback, 100);
183 |
184 | function renderTurbolizerData(txtRes) {
185 | // If the JSON isn't properly terminated, assume compiler crashed and
186 | // add best-guess empty termination
187 | if (txtRes[txtRes.length-2] == ',') {
188 | txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}';
189 | }
190 | try{
191 | jsonObj = JSON.parse(txtRes);
192 |
193 | hideCurrentPhase();
194 |
195 | selectionBroker.setNodePositionMap(jsonObj.nodePositions);
196 |
197 | sourceView.initializeCode(jsonObj.source, jsonObj.sourcePosition);
198 | disassemblyView.initializeCode(jsonObj.source);
199 |
200 | var selectMenu = document.getElementById('display-selector');
201 | var disassemblyPhase = null;
202 | selectMenu.innerHTML = '';
203 | for (var i = 0; i < jsonObj.phases.length; ++i) {
204 | var optionElement = document.createElement("option");
205 | optionElement.text = jsonObj.phases[i].name;
206 | if (optionElement.text == 'disassembly') {
207 | disassemblyPhase = jsonObj.phases[i];
208 | } else {
209 | selectMenu.add(optionElement, null);
210 | }
211 | }
212 |
213 | disassemblyView.initializePerfProfile(jsonObj.eventCounts);
214 | disassemblyView.show(disassemblyPhase.data, null);
215 |
216 | var initialPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase");
217 | if (!(initialPhaseIndex in jsonObj.phases)) {
218 | initialPhaseIndex = 0;
219 | }
220 |
221 | // We wish to show the remembered phase {lastSelectedPhase}, but
222 | // this will crash if the first view we switch to is a
223 | // ScheduleView. So we first switch to the first phase, which
224 | // should never be a ScheduleView.
225 | displayPhase(jsonObj.phases[0]);
226 | displayPhase(jsonObj.phases[initialPhaseIndex]);
227 | selectMenu.selectedIndex = initialPhaseIndex;
228 |
229 | selectMenu.onchange = function(item) {
230 | window.sessionStorage.setItem("lastSelectedPhase", selectMenu.selectedIndex);
231 | displayPhase(jsonObj.phases[selectMenu.selectedIndex]);
232 | }
233 |
234 | fitPanesToParents();
235 |
236 | d3.select("#search-input").attr("value", window.sessionStorage.getItem("lastSearch") || "");
237 |
238 | }
239 | catch(err) {
240 | window.console.log("caught exception, clearing session storage just in case");
241 | window.sessionStorage.clear(); // just in case
242 | window.console.log("showing error");
243 | window.alert("Invalid TurboFan JSON file\n" +
244 | "error: " + err.message);
245 | return;
246 | }
247 | }
248 |
249 | window.renderTurbolizerData = renderTurbolizerData
250 |
251 | function panesUpdatedCallback() {
252 | graph.fitGraphViewToWindow();
253 | }
254 |
255 | function hideCurrentPhase() {
256 | var rememberedSelection = null;
257 | if (currentPhaseView != null) {
258 | rememberedSelection = currentPhaseView.detachSelection();
259 | currentPhaseView.hide();
260 | currentPhaseView = null;
261 | }
262 | return rememberedSelection;
263 | }
264 |
265 | function displayPhaseView(view, data) {
266 | var rememberedSelection = hideCurrentPhase();
267 | view.show(data, rememberedSelection);
268 | d3.select("#middle").classed("scrollable", view.isScrollable());
269 | currentPhaseView = view;
270 | }
271 |
272 | function displayPhase(phase) {
273 | if (phase.type == 'graph') {
274 | displayPhaseView(graph, phase.data);
275 | } else if (phase.type == 'schedule') {
276 | displayPhaseView(schedule, phase.data);
277 | } else {
278 | displayPhaseView(empty, null);
279 | }
280 | }
281 |
282 | function fitPanesToParents() {
283 | d3.select("#left").classed("scrollable", false)
284 | d3.select("#right").classed("scrollable", false);
285 |
286 | graph.fitGraphViewToWindow();
287 |
288 | d3.select("#left").classed("scrollable", true);
289 | d3.select("#right").classed("scrollable", true);
290 | }
291 |
292 | selectionBroker = new SelectionBroker();
293 |
294 | function initializeHandlers(g) {
295 | d3.select("#hidden-file-upload").on("change", function() {
296 | if (window.File && window.FileReader && window.FileList) {
297 | var uploadFile = this.files[0];
298 | var filereader = new window.FileReader();
299 | var consts = Node.consts;
300 | filereader.onload = function(){
301 | var txtRes = filereader.result;
302 | renderTurbolizerData(txtRes);
303 | };
304 | filereader.readAsText(uploadFile);
305 | } else {
306 | alert("Can't load graph");
307 | }
308 | });
309 | }
310 |
311 | sourceView = new CodeView(SOURCE_PANE_ID, PR, "", 0, selectionBroker);
312 | disassemblyView = new DisassemblyView(DISASSEMBLY_PANE_ID, selectionBroker);
313 | graph = new GraphView(d3, GRAPH_PANE_ID, [], [], selectionBroker);
314 | schedule = new ScheduleView(SCHEDULE_PANE_ID, selectionBroker);
315 | empty = new EmptyView(EMPTY_PANE_ID, selectionBroker);
316 |
317 | initializeHandlers(graph);
318 |
319 | resizer.snapper.setSourceExpanded(resizer.snapper.getLastExpandedState("source", true));
320 | resizer.snapper.setDisassemblyExpanded(resizer.snapper.getLastExpandedState("disassembly", false));
321 |
322 | displayPhaseView(empty, null);
323 | fitPanesToParents();
324 | resizer.updatePanes();
325 |
326 | })(window.d3);
327 |
--------------------------------------------------------------------------------
/graph-layout.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | var DEFAULT_NODE_ROW_SEPARATION = 130
6 |
7 | var traceLayout = false;
8 |
9 | function newGraphOccupation(graph){
10 | var isSlotFilled = [];
11 | var maxSlot = 0;
12 | var minSlot = 0;
13 | var nodeOccupation = [];
14 |
15 | function slotToIndex(slot) {
16 | if (slot >= 0) {
17 | return slot * 2;
18 | } else {
19 | return slot * 2 + 1;
20 | }
21 | }
22 |
23 | function indexToSlot(index) {
24 | if ((index % 0) == 0) {
25 | return index / 2;
26 | } else {
27 | return -((index - 1) / 2);
28 | }
29 | }
30 |
31 | function positionToSlot(pos) {
32 | return Math.floor(pos / NODE_INPUT_WIDTH);
33 | }
34 |
35 | function slotToLeftPosition(slot) {
36 | return slot * NODE_INPUT_WIDTH
37 | }
38 |
39 | function slotToRightPosition(slot) {
40 | return (slot + 1) * NODE_INPUT_WIDTH
41 | }
42 |
43 | function findSpace(pos, width, direction) {
44 | var widthSlots = Math.floor((width + NODE_INPUT_WIDTH - 1) /
45 | NODE_INPUT_WIDTH);
46 | var currentSlot = positionToSlot(pos + width / 2);
47 | var currentScanSlot = currentSlot;
48 | var widthSlotsRemainingLeft = widthSlots;
49 | var widthSlotsRemainingRight = widthSlots;
50 | var slotsChecked = 0;
51 | while (true) {
52 | var mod = slotsChecked++ % 2;
53 | currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
54 | if (!isSlotFilled[slotToIndex(currentScanSlot)]) {
55 | if (mod) {
56 | if (direction <= 0) --widthSlotsRemainingLeft
57 | } else {
58 | if (direction >= 0) --widthSlotsRemainingRight
59 | }
60 | if (widthSlotsRemainingLeft == 0 ||
61 | widthSlotsRemainingRight == 0 ||
62 | (widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
63 | (widthSlots == slotsChecked)) {
64 | if (mod) {
65 | return [currentScanSlot, widthSlots];
66 | } else {
67 | return [currentScanSlot - widthSlots + 1, widthSlots];
68 | }
69 | }
70 | } else {
71 | if (mod) {
72 | widthSlotsRemainingLeft = widthSlots;
73 | } else {
74 | widthSlotsRemainingRight = widthSlots;
75 | }
76 | }
77 | }
78 | }
79 |
80 | function setIndexRange(from, to, value) {
81 | if (to < from) {
82 | throw("illegal slot range");
83 | }
84 | while (from <= to) {
85 | if (from > maxSlot) {
86 | maxSlot = from;
87 | }
88 | if (from < minSlot) {
89 | minSlot = from;
90 | }
91 | isSlotFilled[slotToIndex(from++)] = value;
92 | }
93 | }
94 |
95 | function occupySlotRange(from, to) {
96 | if (traceLayout) {
97 | console.log("Occupied [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
98 | }
99 | setIndexRange(from, to, true);
100 | }
101 |
102 | function clearSlotRange(from, to) {
103 | if (traceLayout) {
104 | console.log("Cleared [" + slotToLeftPosition(from) + " " + slotToLeftPosition(to + 1) + ")");
105 | }
106 | setIndexRange(from, to, false);
107 | }
108 |
109 | function occupyPositionRange(from, to) {
110 | occupySlotRange(positionToSlot(from), positionToSlot(to - 1));
111 | }
112 |
113 | function clearPositionRange(from, to) {
114 | clearSlotRange(positionToSlot(from), positionToSlot(to - 1));
115 | }
116 |
117 | function occupyPositionRangeWithMargin(from, to, margin) {
118 | var fromMargin = from - Math.floor(margin);
119 | var toMargin = to + Math.floor(margin);
120 | occupyPositionRange(fromMargin, toMargin);
121 | }
122 |
123 | function clearPositionRangeWithMargin(from, to, margin) {
124 | var fromMargin = from - Math.floor(margin);
125 | var toMargin = to + Math.floor(margin);
126 | clearPositionRange(fromMargin, toMargin);
127 | }
128 |
129 | var occupation = {
130 | occupyNodeInputs: function(node) {
131 | for (var i = 0; i < node.inputs.length; ++i) {
132 | if (node.inputs[i].isVisible()) {
133 | var edge = node.inputs[i];
134 | if (!edge.isBackEdge()) {
135 | var source = edge.source;
136 | var horizontalPos = edge.getInputHorizontalPosition(graph);
137 | if (traceLayout) {
138 | console.log("Occupying input " + i + " of " + node.id + " at " + horizontalPos);
139 | }
140 | occupyPositionRangeWithMargin(horizontalPos,
141 | horizontalPos,
142 | NODE_INPUT_WIDTH / 2);
143 | }
144 | }
145 | }
146 | },
147 | occupyNode: function(node) {
148 | var getPlacementHint = function(n) {
149 | var pos = 0;
150 | var direction = -1;
151 | var outputEdges = 0;
152 | var inputEdges = 0;
153 | for (var k = 0; k < n.outputs.length; ++k) {
154 | var outputEdge = n.outputs[k];
155 | if (outputEdge.isVisible()) {
156 | var output = n.outputs[k].target;
157 | for (var l = 0; l < output.inputs.length; ++l) {
158 | if (output.rank > n.rank) {
159 | var inputEdge = output.inputs[l];
160 | if (inputEdge.isVisible()) {
161 | ++inputEdges;
162 | }
163 | if (output.inputs[l].source == n) {
164 | pos += output.x + output.getInputX(l) + NODE_INPUT_WIDTH / 2;
165 | outputEdges++;
166 | if (l >= (output.inputs.length / 2)) {
167 | direction = 1;
168 | }
169 | }
170 | }
171 | }
172 | }
173 | }
174 | if (outputEdges != 0) {
175 | pos = pos / outputEdges;
176 | }
177 | if (outputEdges > 1 || inputEdges == 1) {
178 | direction = 0;
179 | }
180 | return [direction, pos];
181 | }
182 | var width = node.getTotalNodeWidth();
183 | var margin = MINIMUM_EDGE_SEPARATION;
184 | var paddedWidth = width + 2 * margin;
185 | var placementHint = getPlacementHint(node);
186 | var x = placementHint[1] - paddedWidth + margin;
187 | if (traceLayout) {
188 | console.log("Node " + node.id + " placement hint [" + x + ", " + (x + paddedWidth) + ")");
189 | }
190 | var placement = findSpace(x, paddedWidth, placementHint[0]);
191 | var firstSlot = placement[0];
192 | var slotWidth = placement[1];
193 | var endSlotExclusive = firstSlot + slotWidth - 1;
194 | occupySlotRange(firstSlot, endSlotExclusive);
195 | nodeOccupation.push([firstSlot, endSlotExclusive]);
196 | if (placementHint[0] < 0) {
197 | return slotToLeftPosition(firstSlot + slotWidth) - width - margin;
198 | } else if (placementHint[0] > 0) {
199 | return slotToLeftPosition(firstSlot) + margin;
200 | } else {
201 | return slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
202 | }
203 | },
204 | clearOccupiedNodes: function() {
205 | nodeOccupation.forEach(function(o) {
206 | clearSlotRange(o[0], o[1]);
207 | });
208 | nodeOccupation = [];
209 | },
210 | clearNodeOutputs: function(source) {
211 | source.outputs.forEach(function(edge) {
212 | if (edge.isVisible()) {
213 | var target = edge.target;
214 | for (var i = 0; i < target.inputs.length; ++i) {
215 | if (target.inputs[i].source === source) {
216 | var horizontalPos = edge.getInputHorizontalPosition(graph);
217 | clearPositionRangeWithMargin(horizontalPos,
218 | horizontalPos,
219 | NODE_INPUT_WIDTH / 2);
220 | }
221 | }
222 | }
223 | });
224 | },
225 | print: function() {
226 | var s = "";
227 | for (var currentSlot = -40; currentSlot < 40; ++currentSlot) {
228 | if (currentSlot != 0) {
229 | s += " ";
230 | } else {
231 | s += "|";
232 | }
233 | }
234 | console.log(s);
235 | s = "";
236 | for (var currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
237 | if (isSlotFilled[slotToIndex(currentSlot2)]) {
238 | s += "*";
239 | } else {
240 | s += " ";
241 | }
242 | }
243 | console.log(s);
244 | }
245 | }
246 | return occupation;
247 | }
248 |
249 | function layoutNodeGraph(graph) {
250 | // First determine the set of nodes that have no outputs. Those are the
251 | // basis for bottom-up DFS to determine rank and node placement.
252 | var endNodesHasNoOutputs = [];
253 | var startNodesHasNoInputs = [];
254 | graph.nodes.forEach(function(n, i){
255 | endNodesHasNoOutputs[n.id] = true;
256 | startNodesHasNoInputs[n.id] = true;
257 | });
258 | graph.edges.forEach(function(e, i){
259 | endNodesHasNoOutputs[e.source.id] = false;
260 | startNodesHasNoInputs[e.target.id] = false;
261 | });
262 |
263 | // Finialize the list of start and end nodes.
264 | var endNodes = [];
265 | var startNodes = [];
266 | var visited = [];
267 | var rank = [];
268 | graph.nodes.forEach(function(n, i){
269 | if (endNodesHasNoOutputs[n.id]) {
270 | endNodes.push(n);
271 | }
272 | if (startNodesHasNoInputs[n.id]) {
273 | startNodes.push(n);
274 | }
275 | visited[n.id] = false;
276 | rank[n.id] = -1;
277 | n.rank = 0;
278 | n.visitOrderWithinRank = 0;
279 | n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
280 | });
281 |
282 |
283 | var maxRank = 0;
284 | var visited = [];
285 | var dfsStack = [];
286 | var visitOrderWithinRank = 0;
287 |
288 | var worklist = startNodes.slice();
289 | while (worklist.length != 0) {
290 | var n = worklist.pop();
291 | var changed = false;
292 | if (n.rank == MAX_RANK_SENTINEL) {
293 | n.rank = 1;
294 | changed = true;
295 | }
296 | var begin = 0;
297 | var end = n.inputs.length;
298 | if (n.opcode == 'Phi' || n.opcode == 'EffectPhi') {
299 | // Keep with merge or loop node
300 | begin = n.inputs.length - 1;
301 | } else if (n.hasBackEdges()) {
302 | end = 1;
303 | }
304 | for (var l = begin; l < end; ++l) {
305 | var input = n.inputs[l].source;
306 | if (input.visible && input.rank >= n.rank) {
307 | n.rank = input.rank + 1;
308 | changed = true;
309 | }
310 | }
311 | if (changed) {
312 | var hasBackEdges = n.hasBackEdges();
313 | for (var l = n.outputs.length - 1; l >= 0; --l) {
314 | if (hasBackEdges && (l != 0)) {
315 | worklist.unshift(n.outputs[l].target);
316 | } else {
317 | worklist.push(n.outputs[l].target);
318 | }
319 | }
320 | }
321 | if (n.rank > maxRank) {
322 | maxRank = n.rank;
323 | }
324 | }
325 |
326 | visited = [];
327 | function dfsFindRankLate(n) {
328 | if (visited[n.id]) return;
329 | visited[n.id] = true;
330 | var originalRank = n.rank;
331 | var newRank = n.rank;
332 | var firstInput = true;
333 | for (var l = 0; l < n.outputs.length; ++l) {
334 | var output = n.outputs[l].target;
335 | dfsFindRankLate(output);
336 | var outputRank = output.rank;
337 | if (output.visible && (firstInput || outputRank <= newRank) &&
338 | (outputRank > originalRank)) {
339 | newRank = outputRank - 1;
340 | }
341 | firstInput = false;
342 | }
343 | if (n.opcode != "Start" && n.opcode != "Phi" && n.opcode != "EffectPhi") {
344 | n.rank = newRank;
345 | }
346 | }
347 |
348 | startNodes.forEach(dfsFindRankLate);
349 |
350 | visited = [];
351 | function dfsRankOrder(n) {
352 | if (visited[n.id]) return;
353 | visited[n.id] = true;
354 | for (var l = 0; l < n.outputs.length; ++l) {
355 | var edge = n.outputs[l];
356 | if (edge.isVisible()) {
357 | var output = edge.target;
358 | dfsRankOrder(output);
359 | }
360 | }
361 | if (n.visitOrderWithinRank == 0) {
362 | n.visitOrderWithinRank = ++visitOrderWithinRank;
363 | }
364 | }
365 | startNodes.forEach(dfsRankOrder);
366 |
367 | endNodes.forEach(function(n) {
368 | n.rank = maxRank + 1;
369 | });
370 |
371 | var rankSets = [];
372 | // Collect sets for each rank.
373 | graph.nodes.forEach(function(n, i){
374 | n.y = n.rank * (DEFAULT_NODE_ROW_SEPARATION + graph.getNodeHeight(n) +
375 | 2 * DEFAULT_NODE_BUBBLE_RADIUS);
376 | if (n.visible) {
377 | if (rankSets[n.rank] === undefined) {
378 | rankSets[n.rank] = [n];
379 | } else {
380 | rankSets[n.rank].push(n);
381 | }
382 | }
383 | });
384 |
385 | // Iterate backwards from highest to lowest rank, placing nodes so that they
386 | // spread out from the "center" as much as possible while still being
387 | // compact and not overlapping live input lines.
388 | var occupation = newGraphOccupation(graph);
389 | var rankCount = 0;
390 |
391 | rankSets.reverse().forEach(function(rankSet) {
392 |
393 | for (var i = 0; i < rankSet.length; ++i) {
394 | occupation.clearNodeOutputs(rankSet[i]);
395 | }
396 |
397 | if (traceLayout) {
398 | console.log("After clearing outputs");
399 | occupation.print();
400 | }
401 |
402 | var placedCount = 0;
403 | rankSet = rankSet.sort(function(a,b) {
404 | return a.visitOrderWithinRank < b.visitOrderWithinRank;
405 | });
406 | for (var i = 0; i < rankSet.length; ++i) {
407 | var nodeToPlace = rankSet[i];
408 | if (nodeToPlace.visible) {
409 | nodeToPlace.x = occupation.occupyNode(nodeToPlace);
410 | if (traceLayout) {
411 | console.log("Node " + nodeToPlace.id + " is placed between [" + nodeToPlace.x + ", " + (nodeToPlace.x + nodeToPlace.getTotalNodeWidth()) + ")");
412 | }
413 | var staggeredFlooredI = Math.floor(placedCount++ % 3);
414 | var delta = MINIMUM_EDGE_SEPARATION * staggeredFlooredI
415 | nodeToPlace.outputApproach += delta;
416 | } else {
417 | nodeToPlace.x = 0;
418 | }
419 | }
420 |
421 | if (traceLayout) {
422 | console.log("Before clearing nodes");
423 | occupation.print();
424 | }
425 |
426 | occupation.clearOccupiedNodes();
427 |
428 | if (traceLayout) {
429 | console.log("After clearing nodes");
430 | occupation.print();
431 | }
432 |
433 | for (var i = 0; i < rankSet.length; ++i) {
434 | var node = rankSet[i];
435 | occupation.occupyNodeInputs(node);
436 | }
437 |
438 | if (traceLayout) {
439 | console.log("After occupying inputs");
440 | occupation.print();
441 | }
442 |
443 | if (traceLayout) {
444 | console.log("After determining bounding box");
445 | occupation.print();
446 | }
447 | });
448 |
449 | graph.maxBackEdgeNumber = 0;
450 | graph.visibleEdges.each(function (e) {
451 | if (e.isBackEdge()) {
452 | e.backEdgeNumber = ++graph.maxBackEdgeNumber;
453 | } else {
454 | e.backEdgeNumber = 0;
455 | }
456 | });
457 |
458 | redetermineGraphBoundingBox(graph);
459 |
460 | }
461 |
462 | function redetermineGraphBoundingBox(graph) {
463 | graph.minGraphX = 0;
464 | graph.maxGraphNodeX = 1;
465 | graph.maxGraphX = undefined; // see below
466 | graph.minGraphY = 0;
467 | graph.maxGraphY = 1;
468 |
469 | for (var i = 0; i < graph.nodes.length; ++i) {
470 | var node = graph.nodes[i];
471 |
472 | if (!node.visible) {
473 | continue;
474 | }
475 |
476 | if (node.x < graph.minGraphX) {
477 | graph.minGraphX = node.x;
478 | }
479 | if ((node.x + node.getTotalNodeWidth()) > graph.maxGraphNodeX) {
480 | graph.maxGraphNodeX = node.x + node.getTotalNodeWidth();
481 | }
482 | if ((node.y - 50) < graph.minGraphY) {
483 | graph.minGraphY = node.y - 50;
484 | }
485 | if ((node.y + graph.getNodeHeight(node) + 50) > graph.maxGraphY) {
486 | graph.maxGraphY = node.y + graph.getNodeHeight(node) + 50;
487 | }
488 | }
489 |
490 | graph.maxGraphX = graph.maxGraphNodeX +
491 | graph.maxBackEdgeNumber * MINIMUM_EDGE_SEPARATION;
492 |
493 | }
494 |
--------------------------------------------------------------------------------
/graph-view.js:
--------------------------------------------------------------------------------
1 | // Copyright 2015 the V8 project authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | class GraphView extends View {
8 | constructor (d3, id, nodes, edges, broker) {
9 | super(id, broker);
10 | var graph = this;
11 |
12 | var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
13 | graph.svg = svg;
14 |
15 | graph.nodes = nodes || [];
16 | graph.edges = edges || [];
17 |
18 | graph.minGraphX = 0;
19 | graph.maxGraphX = 1;
20 | graph.minGraphY = 0;
21 | graph.maxGraphY = 1;
22 |
23 | graph.state = {
24 | selection: null,
25 | mouseDownNode: null,
26 | justDragged: false,
27 | justScaleTransGraph: false,
28 | lastKeyDown: -1,
29 | showTypes: false
30 | };
31 |
32 | var selectionHandler = {
33 | clear: function() {
34 | broker.clear(selectionHandler);
35 | },
36 | select: function(items, selected) {
37 | var locations = [];
38 | for (var d of items) {
39 | if (selected) {
40 | d.classList.add("selected");
41 | } else {
42 | d.classList.remove("selected");
43 | }
44 | var data = d.__data__;
45 | locations.push({ pos_start: data.pos, pos_end: data.pos + 1, node_id: data.id});
46 | }
47 | broker.select(selectionHandler, locations, selected);
48 | },
49 | selectionDifference: function(span1, inclusive1, span2, inclusive2) {
50 | // Should not be called
51 | },
52 | brokeredSelect: function(locations, selected) {
53 | var test = [].entries().next();
54 | var selection = graph.nodes
55 | .filter(function(n) {
56 | var pos = n.pos;
57 | for (var location of locations) {
58 | var start = location.pos_start;
59 | var end = location.pos_end;
60 | var id = location.node_id;
61 | if (end != undefined) {
62 | if (pos >= start && pos < end) {
63 | return true;
64 | }
65 | } else if (start != undefined) {
66 | if (pos === start) {
67 | return true;
68 | }
69 | } else {
70 | if (n.id === id) {
71 | return true;
72 | }
73 | }
74 | }
75 | return false;
76 | });
77 | var newlySelected = new Set();
78 | selection.forEach(function(n) {
79 | newlySelected.add(n);
80 | if (!n.visible) {
81 | n.visible = true;
82 | }
83 | });
84 | graph.updateGraphVisibility();
85 | graph.visibleNodes.each(function(n) {
86 | if (newlySelected.has(n)) {
87 | graph.state.selection.select(this, selected);
88 | }
89 | });
90 | graph.updateGraphVisibility();
91 | graph.viewSelection();
92 | },
93 | brokeredClear: function() {
94 | graph.state.selection.clear();
95 | }
96 | };
97 | broker.addSelectionHandler(selectionHandler);
98 |
99 | graph.state.selection = new Selection(selectionHandler);
100 |
101 | var defs = svg.append('svg:defs');
102 | defs.append('svg:marker')
103 | .attr('id', 'end-arrow')
104 | .attr('viewBox', '0 -4 8 8')
105 | .attr('refX', 2)
106 | .attr('markerWidth', 2.5)
107 | .attr('markerHeight', 2.5)
108 | .attr('orient', 'auto')
109 | .append('svg:path')
110 | .attr('d', 'M0,-4L8,0L0,4');
111 |
112 | this.graphElement = svg.append("g");
113 | graph.visibleEdges = this.graphElement.append("g").selectAll("g");
114 | graph.visibleNodes = this.graphElement.append("g").selectAll("g");
115 |
116 | graph.drag = d3.behavior.drag()
117 | .origin(function(d){
118 | return {x: d.x, y: d.y};
119 | })
120 | .on("drag", function(args){
121 | graph.state.justDragged = true;
122 | graph.dragmove.call(graph, args);
123 | })
124 |
125 | d3.select("#upload").on("click", partial(this.uploadAction, graph));
126 | d3.select("#layout").on("click", partial(this.layoutAction, graph));
127 | d3.select("#show-all").on("click", partial(this.showAllAction, graph));
128 | d3.select("#hide-dead").on("click", partial(this.hideDeadAction, graph));
129 | d3.select("#hide-unselected").on("click", partial(this.hideUnselectedAction, graph));
130 | d3.select("#hide-selected").on("click", partial(this.hideSelectedAction, graph));
131 | d3.select("#zoom-selection").on("click", partial(this.zoomSelectionAction, graph));
132 | d3.select("#toggle-types").on("click", partial(this.toggleTypesAction, graph));
133 | d3.select("#search-input").on("keydown", partial(this.searchInputAction, graph));
134 |
135 | // listen for key events
136 | d3.select(window).on("keydown", function(e){
137 | graph.svgKeyDown.call(graph);
138 | })
139 | .on("keyup", function(){
140 | graph.svgKeyUp.call(graph);
141 | });
142 | svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);});
143 | svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);});
144 |
145 | graph.dragSvg = d3.behavior.zoom()
146 | .on("zoom", function(){
147 | if (d3.event.sourceEvent.shiftKey){
148 | return false;
149 | } else{
150 | graph.zoomed.call(graph);
151 | }
152 | return true;
153 | })
154 | .on("zoomstart", function(){
155 | if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
156 | })
157 | .on("zoomend", function(){
158 | d3.select('body').style("cursor", "auto");
159 | });
160 |
161 | svg.call(graph.dragSvg).on("dblclick.zoom", null);
162 | }
163 |
164 | static get selectedClass() {
165 | return "selected";
166 | }
167 | static get rectClass() {
168 | return "nodeStyle";
169 | }
170 | static get activeEditId() {
171 | return "active-editing";
172 | }
173 | static get nodeRadius() {
174 | return 50;
175 | }
176 |
177 | getNodeHeight(d) {
178 | if (this.state.showTypes) {
179 | return d.normalheight + d.labelbbox.height;
180 | } else {
181 | return d.normalheight;
182 | }
183 | }
184 |
185 | getEdgeFrontier(nodes, inEdges, edgeFilter) {
186 | let frontier = new Set();
187 | nodes.forEach(function(element) {
188 | var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
189 | var edgeNumber = 0;
190 | edges.forEach(function(edge) {
191 | if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
192 | frontier.add(edge);
193 | }
194 | ++edgeNumber;
195 | });
196 | });
197 | return frontier;
198 | }
199 |
200 | getNodeFrontier(nodes, inEdges, edgeFilter) {
201 | let graph = this;
202 | var frontier = new Set();
203 | var newState = true;
204 | var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
205 | // Control key toggles edges rather than just turning them on
206 | if (d3.event.ctrlKey) {
207 | edgeFrontier.forEach(function(edge) {
208 | if (edge.visible) {
209 | newState = false;
210 | }
211 | });
212 | }
213 | edgeFrontier.forEach(function(edge) {
214 | edge.visible = newState;
215 | if (newState) {
216 | var node = inEdges ? edge.source : edge.target;
217 | node.visible = true;
218 | frontier.add(node);
219 | }
220 | });
221 | graph.updateGraphVisibility();
222 | if (newState) {
223 | return graph.visibleNodes.filter(function(n) {
224 | return frontier.has(n);
225 | });
226 | } else {
227 | return undefined;
228 | }
229 | }
230 |
231 | dragmove(d) {
232 | var graph = this;
233 | d.x += d3.event.dx;
234 | d.y += d3.event.dy;
235 | graph.updateGraphVisibility();
236 | }
237 |
238 | initializeContent(data, rememberedSelection) {
239 | this.createGraph(data, rememberedSelection);
240 | if (rememberedSelection != null) {
241 | this.attachSelection(rememberedSelection);
242 | this.connectVisibleSelectedNodes();
243 | this.viewSelection();
244 | }
245 | this.updateGraphVisibility();
246 | }
247 |
248 | deleteContent() {
249 | if (this.visibleNodes) {
250 | this.nodes = [];
251 | this.edges = [];
252 | this.nodeMap = [];
253 | this.updateGraphVisibility();
254 | }
255 | };
256 |
257 | measureText(text) {
258 | var textMeasure = document.getElementById('text-measure');
259 | textMeasure.textContent = text;
260 | return {
261 | width: textMeasure.getBBox().width,
262 | height: textMeasure.getBBox().height,
263 | };
264 | }
265 |
266 | createGraph(data, initiallyVisibileIds) {
267 | var g = this;
268 | g.nodes = data.nodes;
269 | g.nodeMap = [];
270 | g.nodes.forEach(function(n, i){
271 | n.__proto__ = Node;
272 | n.visible = false;
273 | n.x = 0;
274 | n.y = 0;
275 | n.rank = MAX_RANK_SENTINEL;
276 | n.inputs = [];
277 | n.outputs = [];
278 | n.rpo = -1;
279 | n.outputApproach = MINIMUM_NODE_OUTPUT_APPROACH;
280 | n.cfg = n.control;
281 | g.nodeMap[n.id] = n;
282 | n.displayLabel = n.getDisplayLabel();
283 | n.labelbbox = g.measureText(n.displayLabel);
284 | n.typebbox = g.measureText(n.getDisplayType());
285 | var innerwidth = Math.max(n.labelbbox.width, n.typebbox.width);
286 | n.width = Math.alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
287 | NODE_INPUT_WIDTH);
288 | var innerheight = Math.max(n.labelbbox.height, n.typebbox.height);
289 | n.normalheight = innerheight + 20;
290 | });
291 | g.edges = [];
292 | data.edges.forEach(function(e, i){
293 | var t = g.nodeMap[e.target];
294 | var s = g.nodeMap[e.source];
295 | var newEdge = new Edge(t, e.index, s, e.type);
296 | t.inputs.push(newEdge);
297 | s.outputs.push(newEdge);
298 | g.edges.push(newEdge);
299 | if (e.type == 'control') {
300 | s.cfg = true;
301 | }
302 | });
303 | g.nodes.forEach(function(n, i) {
304 | n.visible = isNodeInitiallyVisible(n);
305 | if (initiallyVisibileIds != undefined) {
306 | if (initiallyVisibileIds.has(n.id)) {
307 | n.visible = true;
308 | }
309 | }
310 | });
311 | g.fitGraphViewToWindow();
312 | g.updateGraphVisibility();
313 | g.layoutGraph();
314 | g.updateGraphVisibility();
315 | g.viewWholeGraph();
316 | }
317 |
318 | connectVisibleSelectedNodes() {
319 | var graph = this;
320 | graph.state.selection.selection.forEach(function(element) {
321 | var edgeNumber = 0;
322 | element.__data__.inputs.forEach(function(edge) {
323 | if (edge.source.visible && edge.target.visible) {
324 | edge.visible = true;
325 | }
326 | });
327 | element.__data__.outputs.forEach(function(edge) {
328 | if (edge.source.visible && edge.target.visible) {
329 | edge.visible = true;
330 | }
331 | });
332 | });
333 | }
334 |
335 | updateInputAndOutputBubbles() {
336 | var g = this;
337 | var s = g.visibleBubbles;
338 | s.classed("filledBubbleStyle", function(c) {
339 | var components = this.id.split(',');
340 | if (components[0] == "ib") {
341 | var edge = g.nodeMap[components[3]].inputs[components[2]];
342 | return edge.isVisible();
343 | } else {
344 | return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
345 | }
346 | }).classed("halfFilledBubbleStyle", function(c) {
347 | var components = this.id.split(',');
348 | if (components[0] == "ib") {
349 | var edge = g.nodeMap[components[3]].inputs[components[2]];
350 | return false;
351 | } else {
352 | return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
353 | }
354 | }).classed("bubbleStyle", function(c) {
355 | var components = this.id.split(',');
356 | if (components[0] == "ib") {
357 | var edge = g.nodeMap[components[3]].inputs[components[2]];
358 | return !edge.isVisible();
359 | } else {
360 | return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
361 | }
362 | });
363 | s.each(function(c) {
364 | var components = this.id.split(',');
365 | if (components[0] == "ob") {
366 | var from = g.nodeMap[components[1]];
367 | var x = from.getOutputX();
368 | var y = g.getNodeHeight(from) + DEFAULT_NODE_BUBBLE_RADIUS;
369 | var transform = "translate(" + x + "," + y + ")";
370 | this.setAttribute('transform', transform);
371 | }
372 | });
373 | }
374 |
375 | attachSelection(s) {
376 | var graph = this;
377 | if (s.size != 0) {
378 | this.visibleNodes.each(function(n) {
379 | if (s.has(this.__data__.id)) {
380 | graph.state.selection.select(this, true);
381 | }
382 | });
383 | }
384 | }
385 |
386 | detachSelection() {
387 | var selection = this.state.selection.detachSelection();
388 | var s = new Set();
389 | for (var i of selection) {
390 | s.add(i.__data__.id);
391 | };
392 | return s;
393 | }
394 |
395 | pathMouseDown(path, d) {
396 | d3.event.stopPropagation();
397 | this.state.selection.clear();
398 | this.state.selection.add(path);
399 | };
400 |
401 | nodeMouseDown(node, d) {
402 | d3.event.stopPropagation();
403 | this.state.mouseDownNode = d;
404 | }
405 |
406 | nodeMouseUp(d3node, d) {
407 | var graph = this,
408 | state = graph.state,
409 | consts = graph.consts;
410 |
411 | var mouseDownNode = state.mouseDownNode;
412 |
413 | if (!mouseDownNode) return;
414 |
415 | if (state.justDragged) {
416 | // dragged, not clicked
417 | redetermineGraphBoundingBox(graph);
418 | state.justDragged = false;
419 | } else{
420 | // clicked, not dragged
421 | var extend = d3.event.shiftKey;
422 | var selection = graph.state.selection;
423 | if (!extend) {
424 | selection.clear();
425 | }
426 | selection.select(d3node[0][0], true);
427 | }
428 | }
429 |
430 | selectSourcePositions(start, end, selected) {
431 | var graph = this;
432 | var map = [];
433 | var sel = graph.nodes.filter(function(n) {
434 | var pos = (n.pos === undefined)
435 | ? -1
436 | : n.getFunctionRelativeSourcePosition(graph);
437 | if (pos >= start && pos < end) {
438 | map[n.id] = true;
439 | n.visible = true;
440 | }
441 | });
442 | graph.updateGraphVisibility();
443 | graph.visibleNodes.filter(function(n) { return map[n.id]; })
444 | .each(function(n) {
445 | var selection = graph.state.selection;
446 | selection.select(d3.select(this), selected);
447 | });
448 | }
449 |
450 | selectAllNodes(inEdges, filter) {
451 | var graph = this;
452 | if (!d3.event.shiftKey) {
453 | graph.state.selection.clear();
454 | }
455 | graph.state.selection.select(graph.visibleNodes[0], true);
456 | graph.updateGraphVisibility();
457 | }
458 |
459 | uploadAction(graph) {
460 | document.getElementById("hidden-file-upload").click();
461 | }
462 |
463 | layoutAction(graph) {
464 | graph.updateGraphVisibility();
465 | graph.layoutGraph();
466 | graph.updateGraphVisibility();
467 | graph.viewWholeGraph();
468 | }
469 |
470 | showAllAction(graph) {
471 | graph.nodes.filter(function(n) { n.visible = true; })
472 | graph.edges.filter(function(e) { e.visible = true; })
473 | graph.updateGraphVisibility();
474 | graph.viewWholeGraph();
475 | }
476 |
477 | hideDeadAction(graph) {
478 | graph.nodes.filter(function(n) { if (!n.isLive()) n.visible = false; })
479 | graph.updateGraphVisibility();
480 | }
481 |
482 | hideUnselectedAction(graph) {
483 | var unselected = graph.visibleNodes.filter(function(n) {
484 | return !this.classList.contains("selected");
485 | });
486 | unselected.each(function(n) {
487 | n.visible = false;
488 | });
489 | graph.updateGraphVisibility();
490 | }
491 |
492 | hideSelectedAction(graph) {
493 | var selected = graph.visibleNodes.filter(function(n) {
494 | return this.classList.contains("selected");
495 | });
496 | selected.each(function(n) {
497 | n.visible = false;
498 | });
499 | graph.state.selection.clear();
500 | graph.updateGraphVisibility();
501 | }
502 |
503 | zoomSelectionAction(graph) {
504 | graph.viewSelection();
505 | }
506 |
507 | toggleTypesAction(graph) {
508 | graph.toggleTypes();
509 | }
510 |
511 | searchInputAction(graph) {
512 | if (d3.event.keyCode == 13) {
513 | graph.state.selection.clear();
514 | var query = this.value;
515 | window.sessionStorage.setItem("lastSearch", query);
516 |
517 | var reg = new RegExp(query);
518 | var filterFunction = function(n) {
519 | return (reg.exec(n.getDisplayLabel()) != null ||
520 | (graph.state.showTypes && reg.exec(n.getDisplayType())) ||
521 | reg.exec(n.opcode) != null);
522 | };
523 | if (d3.event.ctrlKey) {
524 | graph.nodes.forEach(function(n, i) {
525 | if (filterFunction(n)) {
526 | n.visible = true;
527 | }
528 | });
529 | graph.updateGraphVisibility();
530 | }
531 | var selected = graph.visibleNodes.each(function(n) {
532 | if (filterFunction(n)) {
533 | graph.state.selection.select(this, true);
534 | }
535 | });
536 | graph.connectVisibleSelectedNodes();
537 | graph.updateGraphVisibility();
538 | this.blur();
539 | graph.viewSelection();
540 | }
541 | d3.event.stopPropagation();
542 | }
543 |
544 | svgMouseDown() {
545 | this.state.graphMouseDown = true;
546 | }
547 |
548 | svgMouseUp() {
549 | var graph = this,
550 | state = graph.state;
551 | if (state.justScaleTransGraph) {
552 | // Dragged
553 | state.justScaleTransGraph = false;
554 | } else {
555 | // Clicked
556 | if (state.mouseDownNode == null) {
557 | graph.state.selection.clear();
558 | }
559 | }
560 | state.mouseDownNode = null;
561 | state.graphMouseDown = false;
562 | }
563 |
564 | svgKeyDown() {
565 | var state = this.state;
566 | var graph = this;
567 |
568 | // Don't handle key press repetition
569 | if(state.lastKeyDown !== -1) return;
570 |
571 | var showSelectionFrontierNodes = function(inEdges, filter, select) {
572 | var frontier = graph.getNodeFrontier(state.selection.selection, inEdges, filter);
573 | if (frontier != undefined) {
574 | if (select) {
575 | if (!d3.event.shiftKey) {
576 | state.selection.clear();
577 | }
578 | state.selection.select(frontier[0], true);
579 | }
580 | graph.updateGraphVisibility();
581 | }
582 | allowRepetition = false;
583 | }
584 |
585 | var allowRepetition = true;
586 | var eventHandled = true; // unless the below switch defaults
587 | switch(d3.event.keyCode) {
588 | case 49:
589 | case 50:
590 | case 51:
591 | case 52:
592 | case 53:
593 | case 54:
594 | case 55:
595 | case 56:
596 | case 57:
597 | // '1'-'9'
598 | showSelectionFrontierNodes(true,
599 | (edge, index) => { return index == (d3.event.keyCode - 49); },
600 | false);
601 | break;
602 | case 97:
603 | case 98:
604 | case 99:
605 | case 100:
606 | case 101:
607 | case 102:
608 | case 103:
609 | case 104:
610 | case 105:
611 | // 'numpad 1'-'numpad 9'
612 | showSelectionFrontierNodes(true,
613 | (edge, index) => { return index == (d3.event.keyCode - 97); },
614 | false);
615 | break;
616 | case 67:
617 | // 'c'
618 | showSelectionFrontierNodes(true,
619 | (edge, index) => { return edge.type == 'control'; },
620 | false);
621 | break;
622 | case 69:
623 | // 'e'
624 | showSelectionFrontierNodes(true,
625 | (edge, index) => { return edge.type == 'effect'; },
626 | false);
627 | break;
628 | case 79:
629 | // 'o'
630 | showSelectionFrontierNodes(false, undefined, false);
631 | break;
632 | case 73:
633 | // 'i'
634 | showSelectionFrontierNodes(true, undefined, false);
635 | break;
636 | case 65:
637 | // 'a'
638 | graph.selectAllNodes();
639 | allowRepetition = false;
640 | break;
641 | case 38:
642 | case 40: {
643 | showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
644 | break;
645 | }
646 | case 82:
647 | // 'r'
648 | if (!d3.event.ctrlKey) {
649 | this.layoutAction(this);
650 | } else {
651 | eventHandled = false;
652 | }
653 | break;
654 | case 191:
655 | // '/'
656 | document.getElementById("search-input").focus();
657 | document.getElementById("search-input").select();
658 | break;
659 | default:
660 | eventHandled = false;
661 | break;
662 | }
663 | if (eventHandled) {
664 | d3.event.preventDefault();
665 | }
666 | if (!allowRepetition) {
667 | state.lastKeyDown = d3.event.keyCode;
668 | }
669 | }
670 |
671 | svgKeyUp() {
672 | this.state.lastKeyDown = -1
673 | };
674 |
675 | layoutEdges() {
676 | var graph = this;
677 | graph.maxGraphX = graph.maxGraphNodeX;
678 | this.visibleEdges.attr("d", function(edge){
679 | return edge.generatePath(graph);
680 | });
681 | }
682 |
683 | layoutGraph() {
684 | layoutNodeGraph(this);
685 | }
686 |
687 | // call to propagate changes to graph
688 | updateGraphVisibility() {
689 |
690 | var graph = this,
691 | state = graph.state;
692 |
693 | var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); });
694 | var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) {
695 | return edge.stringID();
696 | });
697 |
698 | // add new paths
699 | visibleEdges.enter()
700 | .append('path')
701 | .style('marker-end','url(#end-arrow)')
702 | .classed('hidden', function(e) {
703 | return !e.isVisible();
704 | })
705 | .attr("id", function(edge){ return "e," + edge.stringID(); })
706 | .on("mousedown", function(d){
707 | graph.pathMouseDown.call(graph, d3.select(this), d);
708 | })
709 | .attr("adjacentToHover", "false");
710 |
711 | // Set the correct styles on all of the paths
712 | visibleEdges.classed('value', function(e) {
713 | return e.type == 'value' || e.type == 'context';
714 | }).classed('control', function(e) {
715 | return e.type == 'control';
716 | }).classed('effect', function(e) {
717 | return e.type == 'effect';
718 | }).classed('frame-state', function(e) {
719 | return e.type == 'frame-state';
720 | }).attr('stroke-dasharray', function(e) {
721 | if (e.type == 'frame-state') return "10,10";
722 | return (e.type == 'effect') ? "5,5" : "";
723 | });
724 |
725 | // remove old links
726 | visibleEdges.exit().remove();
727 |
728 | graph.visibleEdges = visibleEdges;
729 |
730 | // update existing nodes
731 | var filteredNodes = graph.nodes.filter(function(n) { return n.visible; });
732 | graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) {
733 | return d.id;
734 | });
735 | graph.visibleNodes.attr("transform", function(n){
736 | return "translate(" + n.x + "," + n.y + ")";
737 | }).select('rect').
738 | attr(HEIGHT, function(d) { return graph.getNodeHeight(d); });
739 |
740 | // add new nodes
741 | var newGs = graph.visibleNodes.enter()
742 | .append("g");
743 |
744 | newGs.classed("turbonode", function(n) { return true; })
745 | .classed("control", function(n) { return n.isControl(); })
746 | .classed("live", function(n) { return n.isLive(); })
747 | .classed("dead", function(n) { return !n.isLive(); })
748 | .classed("javascript", function(n) { return n.isJavaScript(); })
749 | .classed("input", function(n) { return n.isInput(); })
750 | .classed("simplified", function(n) { return n.isSimplified(); })
751 | .classed("machine", function(n) { return n.isMachine(); })
752 | .attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";})
753 | .on("mousedown", function(d){
754 | graph.nodeMouseDown.call(graph, d3.select(this), d);
755 | })
756 | .on("mouseup", function(d){
757 | graph.nodeMouseUp.call(graph, d3.select(this), d);
758 | })
759 | .on('mouseover', function(d){
760 | var nodeSelection = d3.select(this);
761 | let node = graph.nodeMap[d.id];
762 | let adjInputEdges = graph.visibleEdges.filter(e => { return e.target === node; });
763 | let adjOutputEdges = graph.visibleEdges.filter(e => { return e.source === node; });
764 | adjInputEdges.attr('relToHover', "input");
765 | adjOutputEdges.attr('relToHover', "output");
766 | let adjInputNodes = adjInputEdges.data().map(e => e.source);
767 | graph.visibleNodes.data(adjInputNodes, function(d) {
768 | return d.id;
769 | }).attr('relToHover', "input");
770 | let adjOutputNodes = adjOutputEdges.data().map(e => e.target);
771 | graph.visibleNodes.data(adjOutputNodes, function(d) {
772 | return d.id;
773 | }).attr('relToHover', "output");
774 | graph.updateGraphVisibility();
775 | })
776 | .on('mouseout', function(d){
777 | var nodeSelection = d3.select(this);
778 | let node = graph.nodeMap[d.id];
779 | let adjEdges = graph.visibleEdges.filter(e => { return e.target === node || e.source === node; });
780 | adjEdges.attr('relToHover', "none");
781 | let adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
782 | let nodes = graph.visibleNodes.data(adjNodes, function(d) {
783 | return d.id;
784 | }).attr('relToHover', "none");
785 | graph.updateGraphVisibility();
786 | })
787 | .call(graph.drag);
788 |
789 | newGs.append("rect")
790 | .attr("rx", 10)
791 | .attr("ry", 10)
792 | .attr(WIDTH, function(d) {
793 | return d.getTotalNodeWidth();
794 | })
795 | .attr(HEIGHT, function(d) {
796 | return graph.getNodeHeight(d);
797 | })
798 |
799 | function appendInputAndOutputBubbles(g, d) {
800 | for (var i = 0; i < d.inputs.length; ++i) {
801 | var x = d.getInputX(i);
802 | var y = -DEFAULT_NODE_BUBBLE_RADIUS;
803 | var s = g.append('circle')
804 | .classed("filledBubbleStyle", function(c) {
805 | return d.inputs[i].isVisible();
806 | } )
807 | .classed("bubbleStyle", function(c) {
808 | return !d.inputs[i].isVisible();
809 | } )
810 | .attr("id", "ib," + d.inputs[i].stringID())
811 | .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
812 | .attr("transform", function(d) {
813 | return "translate(" + x + "," + y + ")";
814 | })
815 | .on("mousedown", function(d){
816 | var components = this.id.split(',');
817 | var node = graph.nodeMap[components[3]];
818 | var edge = node.inputs[components[2]];
819 | var visible = !edge.isVisible();
820 | node.setInputVisibility(components[2], visible);
821 | d3.event.stopPropagation();
822 | graph.updateGraphVisibility();
823 | });
824 | }
825 | if (d.outputs.length != 0) {
826 | var x = d.getOutputX();
827 | var y = graph.getNodeHeight(d) + DEFAULT_NODE_BUBBLE_RADIUS;
828 | var s = g.append('circle')
829 | .classed("filledBubbleStyle", function(c) {
830 | return d.areAnyOutputsVisible() == 2;
831 | } )
832 | .classed("halFilledBubbleStyle", function(c) {
833 | return d.areAnyOutputsVisible() == 1;
834 | } )
835 | .classed("bubbleStyle", function(c) {
836 | return d.areAnyOutputsVisible() == 0;
837 | } )
838 | .attr("id", "ob," + d.id)
839 | .attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
840 | .attr("transform", function(d) {
841 | return "translate(" + x + "," + y + ")";
842 | })
843 | .on("mousedown", function(d) {
844 | d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
845 | d3.event.stopPropagation();
846 | graph.updateGraphVisibility();
847 | });
848 | }
849 | }
850 |
851 | newGs.each(function(d){
852 | appendInputAndOutputBubbles(d3.select(this), d);
853 | });
854 |
855 | newGs.each(function(d){
856 | d3.select(this).append("text")
857 | .classed("label", true)
858 | .attr("text-anchor","right")
859 | .attr("dx", 5)
860 | .attr("dy", 5)
861 | .append('tspan')
862 | .text(function(l) {
863 | return d.getDisplayLabel();
864 | })
865 | .append("title")
866 | .text(function(l) {
867 | return d.getTitle();
868 | })
869 | if (d.type != undefined) {
870 | d3.select(this).append("text")
871 | .classed("label", true)
872 | .classed("type", true)
873 | .attr("text-anchor","right")
874 | .attr("dx", 5)
875 | .attr("dy", d.labelbbox.height + 5)
876 | .append('tspan')
877 | .text(function(l) {
878 | return d.getDisplayType();
879 | })
880 | .append("title")
881 | .text(function(l) {
882 | return d.getType();
883 | })
884 | }
885 | });
886 |
887 | graph.visibleNodes.select('.type').each(function (d) {
888 | this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
889 | });
890 |
891 | // remove old nodes
892 | graph.visibleNodes.exit().remove();
893 |
894 | graph.visibleBubbles = d3.selectAll('circle');
895 |
896 | graph.updateInputAndOutputBubbles();
897 |
898 | graph.layoutEdges();
899 |
900 | graph.svg.style.height = '100%';
901 | }
902 |
903 | getVisibleTranslation(translate, scale) {
904 | var graph = this;
905 | var height = (graph.maxGraphY - graph.minGraphY + 2 * GRAPH_MARGIN) * scale;
906 | var width = (graph.maxGraphX - graph.minGraphX + 2 * GRAPH_MARGIN) * scale;
907 |
908 | var dimensions = this.getSvgViewDimensions();
909 |
910 | var baseY = translate[1];
911 | var minY = (graph.minGraphY - GRAPH_MARGIN) * scale;
912 | var maxY = (graph.maxGraphY + GRAPH_MARGIN) * scale;
913 |
914 | var adjustY = 0;
915 | var adjustYCandidate = 0;
916 | if ((maxY + baseY) < dimensions[1]) {
917 | adjustYCandidate = dimensions[1] - (maxY + baseY);
918 | if ((minY + baseY + adjustYCandidate) > 0) {
919 | adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
920 | } else {
921 | adjustY = adjustYCandidate;
922 | }
923 | } else if (-baseY < minY) {
924 | adjustYCandidate = -(baseY + minY);
925 | if ((maxY + baseY + adjustYCandidate) < dimensions[1]) {
926 | adjustY = (dimensions[1] / 2) - (maxY - (height / 2)) - baseY;
927 | } else {
928 | adjustY = adjustYCandidate;
929 | }
930 | }
931 | translate[1] += adjustY;
932 |
933 | var baseX = translate[0];
934 | var minX = (graph.minGraphX - GRAPH_MARGIN) * scale;
935 | var maxX = (graph.maxGraphX + GRAPH_MARGIN) * scale;
936 |
937 | var adjustX = 0;
938 | var adjustXCandidate = 0;
939 | if ((maxX + baseX) < dimensions[0]) {
940 | adjustXCandidate = dimensions[0] - (maxX + baseX);
941 | if ((minX + baseX + adjustXCandidate) > 0) {
942 | adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
943 | } else {
944 | adjustX = adjustXCandidate;
945 | }
946 | } else if (-baseX < minX) {
947 | adjustXCandidate = -(baseX + minX);
948 | if ((maxX + baseX + adjustXCandidate) < dimensions[0]) {
949 | adjustX = (dimensions[0] / 2) - (maxX - (width / 2)) - baseX;
950 | } else {
951 | adjustX = adjustXCandidate;
952 | }
953 | }
954 | translate[0] += adjustX;
955 | return translate;
956 | }
957 |
958 | translateClipped(translate, scale, transition) {
959 | var graph = this;
960 | var graphNode = this.graphElement[0][0];
961 | var translate = this.getVisibleTranslation(translate, scale);
962 | if (transition) {
963 | graphNode.classList.add('visible-transition');
964 | clearTimeout(graph.transitionTimout);
965 | graph.transitionTimout = setTimeout(function(){
966 | graphNode.classList.remove('visible-transition');
967 | }, 1000);
968 | }
969 | var translateString = "translate(" + translate[0] + "px," + translate[1] + "px) scale(" + scale + ")";
970 | graphNode.style.transform = translateString;
971 | graph.dragSvg.translate(translate);
972 | graph.dragSvg.scale(scale);
973 | }
974 |
975 | zoomed(){
976 | this.state.justScaleTransGraph = true;
977 | var scale = this.dragSvg.scale();
978 | this.translateClipped(d3.event.translate, scale);
979 | }
980 |
981 |
982 | getSvgViewDimensions() {
983 | var canvasWidth = this.parentNode.clientWidth;
984 | var documentElement = document.documentElement;
985 | var canvasHeight = documentElement.clientHeight;
986 | return [canvasWidth, canvasHeight];
987 | }
988 |
989 |
990 | minScale() {
991 | var graph = this;
992 | var dimensions = this.getSvgViewDimensions();
993 | var width = graph.maxGraphX - graph.minGraphX;
994 | var height = graph.maxGraphY - graph.minGraphY;
995 | var minScale = dimensions[0] / (width + GRAPH_MARGIN * 2);
996 | var minScaleYCandidate = dimensions[1] / (height + GRAPH_MARGIN * 2);
997 | if (minScaleYCandidate < minScale) {
998 | minScale = minScaleYCandidate;
999 | }
1000 | this.dragSvg.scaleExtent([minScale, 1.5]);
1001 | return minScale;
1002 | }
1003 |
1004 | fitGraphViewToWindow() {
1005 | this.svg.attr("height", document.documentElement.clientHeight + "px");
1006 | this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale());
1007 | }
1008 |
1009 | toggleTypes() {
1010 | var graph = this;
1011 | graph.state.showTypes = !graph.state.showTypes;
1012 | var element = document.getElementById('toggle-types');
1013 | if (graph.state.showTypes) {
1014 | element.classList.add('button-input-toggled');
1015 | } else {
1016 | element.classList.remove('button-input-toggled');
1017 | }
1018 | graph.updateGraphVisibility();
1019 | }
1020 |
1021 | viewSelection() {
1022 | var graph = this;
1023 | var minX, maxX, minY, maxY;
1024 | var hasSelection = false;
1025 | graph.visibleNodes.each(function(n) {
1026 | if (this.classList.contains("selected")) {
1027 | hasSelection = true;
1028 | minX = minX ? Math.min(minX, n.x) : n.x;
1029 | maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
1030 | n.x + n.getTotalNodeWidth();
1031 | minY = minY ? Math.min(minY, n.y) : n.y;
1032 | maxY = maxY ? Math.max(maxY, n.y + graph.getNodeHeight(n)) :
1033 | n.y + graph.getNodeHeight(n);
1034 | }
1035 | });
1036 | if (hasSelection) {
1037 | graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
1038 | maxX + NODE_INPUT_WIDTH, maxY + 60,
1039 | true);
1040 | }
1041 | }
1042 |
1043 | viewGraphRegion(minX, minY, maxX, maxY, transition) {
1044 | var graph = this;
1045 | var dimensions = this.getSvgViewDimensions();
1046 | var width = maxX - minX;
1047 | var height = maxY - minY;
1048 | var scale = Math.min(dimensions[0] / width, dimensions[1] / height);
1049 | scale = Math.min(1.5, scale);
1050 | scale = Math.max(graph.minScale(), scale);
1051 | var translation = [-minX*scale, -minY*scale];
1052 | translation = graph.getVisibleTranslation(translation, scale);
1053 | graph.translateClipped(translation, scale, transition);
1054 | }
1055 |
1056 | viewWholeGraph() {
1057 | var graph = this;
1058 | var minScale = graph.minScale();
1059 | var translation = [0, 0];
1060 | translation = graph.getVisibleTranslation(translation, minScale);
1061 | graph.translateClipped(translation, minScale);
1062 | }
1063 | }
1064 |
--------------------------------------------------------------------------------