summaryrefslogtreecommitdiff
path: root/chromium/v8/tools/turbolizer
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/v8/tools/turbolizer
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/v8/tools/turbolizer')
-rw-r--r--chromium/v8/tools/turbolizer/README.md7
-rw-r--r--chromium/v8/tools/turbolizer/down-arrow.pngbin0 -> 3670 bytes
-rw-r--r--chromium/v8/tools/turbolizer/index.html7
-rw-r--r--chromium/v8/tools/turbolizer/src/constants.ts3
-rw-r--r--chromium/v8/tools/turbolizer/src/graphmultiview.ts9
-rw-r--r--chromium/v8/tools/turbolizer/src/range-view.ts938
-rw-r--r--chromium/v8/tools/turbolizer/src/resizer.ts131
-rw-r--r--chromium/v8/tools/turbolizer/src/sequence-view.ts177
-rw-r--r--chromium/v8/tools/turbolizer/src/source-resolver.ts43
-rw-r--r--chromium/v8/tools/turbolizer/src/turbo-visualizer.ts5
-rw-r--r--chromium/v8/tools/turbolizer/src/util.ts7
-rw-r--r--chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css238
-rw-r--r--chromium/v8/tools/turbolizer/turbo-visualizer.css28
-rw-r--r--chromium/v8/tools/turbolizer/up-arrow.pngbin0 -> 3579 bytes
14 files changed, 1543 insertions, 50 deletions
diff --git a/chromium/v8/tools/turbolizer/README.md b/chromium/v8/tools/turbolizer/README.md
index c5ee729d64d..fa804f65e94 100644
--- a/chromium/v8/tools/turbolizer/README.md
+++ b/chromium/v8/tools/turbolizer/README.md
@@ -74,7 +74,6 @@ well as '--cpu' to specify which CPU to sample.
Turbolizer build process
------------------------
-Turbolizer is currently migrating to TypeScript. The typescript sources reside in
-tools/turbolizer/src, and the typescript compiler will put the JavaScript output
-into tools/turbolizer/build/. The index.html file is set up to load the JavaScript
-from that directory.
+The typescript sources reside in tools/turbolizer/src, and the typescript
+compiler will put the JavaScript output into tools/turbolizer/build/. The
+index.html file is set up to load the JavaScript from that directory.
diff --git a/chromium/v8/tools/turbolizer/down-arrow.png b/chromium/v8/tools/turbolizer/down-arrow.png
new file mode 100644
index 00000000000..39339f289a3
--- /dev/null
+++ b/chromium/v8/tools/turbolizer/down-arrow.png
Binary files differ
diff --git a/chromium/v8/tools/turbolizer/index.html b/chromium/v8/tools/turbolizer/index.html
index 268e51e0200..ea1b0b74d27 100644
--- a/chromium/v8/tools/turbolizer/index.html
+++ b/chromium/v8/tools/turbolizer/index.html
@@ -8,6 +8,7 @@ code is governed by a BSD-style license that can be found in the LICENSE file.
<meta charset="utf-8">
<title>V8 Turbolizer</title>
<link rel="stylesheet" href="turbo-visualizer.css">
+ <link rel="stylesheet" href="turbo-visualizer-ranges.css">
<link rel="stylesheet" href="tabs.css">
<link rel="icon" type="image/png" href="turbolizer.png">
</head>
@@ -21,6 +22,12 @@ code is governed by a BSD-style license that can be found in the LICENSE file.
<input id="upload-helper" type="file">
<input id="upload" type="image" title="load graph" class="button-input" src="upload-icon.png" alt="upload graph">
</div>
+ <div id="resizer-ranges" class="resizer" style="visibility:hidden;"></div>
+ <div id="ranges" class="content" style="visibility:hidden;"></div>
+ <div id="show-hide-ranges" class="show-hide-pane" style="visibility: hidden">
+ <input id="ranges-expand" type="image" title="show ranges" src="up-arrow.png" class="button-input invisible">
+ <input id="ranges-shrink" type="image" title="hide ranges" src="down-arrow.png" class="button-input">
+ </div>
</div>
<div id="resizer-right" class="resizer"></div>
<div id="right" class="content"></div>
diff --git a/chromium/v8/tools/turbolizer/src/constants.ts b/chromium/v8/tools/turbolizer/src/constants.ts
index ada39ae6b3b..47dee8547ff 100644
--- a/chromium/v8/tools/turbolizer/src/constants.ts
+++ b/chromium/v8/tools/turbolizer/src/constants.ts
@@ -14,6 +14,9 @@ export const GENERATED_PANE_ID = 'right';
export const DISASSEMBLY_PANE_ID = 'disassembly';
export const DISASSEMBLY_COLLAPSE_ID = 'disassembly-shrink';
export const DISASSEMBLY_EXPAND_ID = 'disassembly-expand';
+export const RANGES_PANE_ID = "ranges";
+export const RANGES_COLLAPSE_ID = "ranges-shrink";
+export const RANGES_EXPAND_ID = "ranges-expand";
export const UNICODE_BLOCK = '&#9611;';
export const PROF_COLS = [
{ perc: 0, col: { r: 255, g: 255, b: 255 } },
diff --git a/chromium/v8/tools/turbolizer/src/graphmultiview.ts b/chromium/v8/tools/turbolizer/src/graphmultiview.ts
index 380f7df77db..4f8f6339199 100644
--- a/chromium/v8/tools/turbolizer/src/graphmultiview.ts
+++ b/chromium/v8/tools/turbolizer/src/graphmultiview.ts
@@ -38,6 +38,11 @@ export class GraphMultiView extends View {
return pane;
}
+ hide() {
+ this.hideCurrentPhase();
+ super.hide();
+ }
+
constructor(id, selectionBroker, sourceResolver) {
super(id);
const view = this;
@@ -86,7 +91,9 @@ export class GraphMultiView extends View {
}
show() {
- super.show();
+ // Insert before is used so that the display is inserted before the
+ // resizer for the RangeView.
+ this.container.insertBefore(this.divNode, this.container.firstChild);
this.initializeSelect();
const lastPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase");
const initialPhaseIndex = this.sourceResolver.repairPhaseId(lastPhaseIndex);
diff --git a/chromium/v8/tools/turbolizer/src/range-view.ts b/chromium/v8/tools/turbolizer/src/range-view.ts
new file mode 100644
index 00000000000..17058e4f3b2
--- /dev/null
+++ b/chromium/v8/tools/turbolizer/src/range-view.ts
@@ -0,0 +1,938 @@
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import { createElement } from "../src/util";
+import { SequenceView } from "../src/sequence-view";
+import { RegisterAllocation, Range, ChildRange, Interval } from "../src/source-resolver";
+
+class Constants {
+ // Determines how many rows each div group holds for the purposes of
+ // hiding by syncHidden.
+ static readonly ROW_GROUP_SIZE = 20;
+ static readonly POSITIONS_PER_INSTRUCTION = 4;
+ static readonly FIXED_REGISTER_LABEL_WIDTH = 6;
+
+ static readonly INTERVAL_TEXT_FOR_NONE = "none";
+ static readonly INTERVAL_TEXT_FOR_CONST = "const";
+ static readonly INTERVAL_TEXT_FOR_STACK = "stack:";
+}
+
+// This class holds references to the HTMLElements that represent each cell.
+class Grid {
+ elements: Array<Array<HTMLElement>>;
+
+ constructor() {
+ this.elements = [];
+ }
+
+ setRow(row: number, elementsRow: Array<HTMLElement>) {
+ this.elements[row] = elementsRow;
+ }
+
+ getCell(row: number, column: number) {
+ return this.elements[row][column];
+ }
+
+ getInterval(row: number, column: number) {
+ // The cell is within an inner wrapper div which is within the interval div.
+ return this.getCell(row, column).parentElement.parentElement;
+ }
+}
+
+// This class is used as a wrapper to hide the switch between the
+// two different Grid objects used, one for each phase,
+// before and after register allocation.
+class GridAccessor {
+ sequenceView: SequenceView;
+ grids: Map<number, Grid>;
+
+ constructor(sequenceView: SequenceView) {
+ this.sequenceView = sequenceView;
+ this.grids = new Map<number, Grid>();
+ }
+
+ private currentGrid() {
+ return this.grids.get(this.sequenceView.currentPhaseIndex);
+ }
+
+ getAnyGrid() {
+ return this.grids.values().next().value;
+ }
+
+ hasGrid() {
+ return this.grids.has(this.sequenceView.currentPhaseIndex);
+ }
+
+ addGrid(grid: Grid) {
+ if (this.hasGrid()) console.warn("Overwriting existing Grid.");
+ this.grids.set(this.sequenceView.currentPhaseIndex, grid);
+ }
+
+ getCell(row: number, column: number) {
+ return this.currentGrid().getCell(row, column);
+ }
+
+ getInterval(row: number, column: number) {
+ return this.currentGrid().getInterval(row, column);
+ }
+}
+
+// This class is used as a wrapper to access the interval HTMLElements
+class IntervalElementsAccessor {
+ sequenceView: SequenceView;
+ map: Map<number, Array<HTMLElement>>;
+
+ constructor(sequenceView: SequenceView) {
+ this.sequenceView = sequenceView;
+ this.map = new Map<number, Array<HTMLElement>>();
+ }
+
+ private currentIntervals() {
+ const intervals = this.map.get(this.sequenceView.currentPhaseIndex);
+ if (intervals == undefined) {
+ this.map.set(this.sequenceView.currentPhaseIndex, new Array<HTMLElement>());
+ return this.currentIntervals();
+ }
+ return intervals;
+ }
+
+ addInterval(interval: HTMLElement) {
+ this.currentIntervals().push(interval);
+ }
+
+ forEachInterval(callback: (phase: number, interval: HTMLElement) => void) {
+ for (const phase of this.map.keys()) {
+ for (const interval of this.map.get(phase)) {
+ callback(phase, interval);
+ }
+ }
+ }
+}
+
+// A simple class used to hold two Range objects. This is used to allow the two fixed register live
+// ranges of normal and deferred to be easily combined into a single row.
+class RangePair {
+ ranges: [Range, Range];
+
+ constructor(ranges: [Range, Range]) {
+ this.ranges = ranges;
+ }
+
+ forEachRange(callback: (range: Range) => void) {
+ this.ranges.forEach((range: Range) => { if (range) callback(range); });
+ }
+}
+
+// A number of css variables regarding dimensions of HTMLElements are required by RangeView.
+class CSSVariables {
+ positionWidth: number;
+ blockBorderWidth: number;
+
+ constructor() {
+ const getNumberValue = varName => {
+ return parseFloat(getComputedStyle(document.body)
+ .getPropertyValue(varName).match(/[+-]?\d+(\.\d+)?/g)[0]);
+ };
+ this.positionWidth = getNumberValue("--range-position-width");
+ this.blockBorderWidth = getNumberValue("--range-block-border");
+ }
+}
+
+// Store the required data from the blocks JSON.
+class BlocksData {
+ blockBorders: Set<number>;
+ blockInstructionCountMap: Map<number, number>;
+
+ constructor(blocks: Array<any>) {
+ this.blockBorders = new Set<number>();
+ this.blockInstructionCountMap = new Map<number, number>();
+ for (const block of blocks) {
+ this.blockInstructionCountMap.set(block.id, block.instructions.length);
+ const maxInstructionInBlock = block.instructions[block.instructions.length - 1].id;
+ this.blockBorders.add(maxInstructionInBlock);
+ }
+ }
+
+ isInstructionBorder(position: number) {
+ return ((position + 1) % Constants.POSITIONS_PER_INSTRUCTION) == 0;
+ }
+
+ isBlockBorder(position: number) {
+ return this.isInstructionBorder(position)
+ && this.blockBorders.has(Math.floor(position / Constants.POSITIONS_PER_INSTRUCTION));
+ }
+}
+
+class Divs {
+ // Already existing.
+ container: HTMLElement;
+ resizerBar: HTMLElement;
+ snapper: HTMLElement;
+
+ // Created by constructor.
+ content: HTMLElement;
+ // showOnLoad contains all content that may change depending on the JSON.
+ showOnLoad: HTMLElement;
+ xAxisLabel: HTMLElement;
+ yAxisLabel: HTMLElement;
+ registerHeaders: HTMLElement;
+ registers: HTMLElement;
+
+ // Assigned from RangeView.
+ wholeHeader: HTMLElement;
+ positionHeaders: HTMLElement;
+ yAxis: HTMLElement;
+ grid: HTMLElement;
+
+ constructor() {
+ this.container = document.getElementById("ranges");
+ this.resizerBar = document.getElementById("resizer-ranges");
+ this.snapper = document.getElementById("show-hide-ranges");
+
+ this.content = document.createElement("div");
+ this.content.appendChild(this.elementForTitle());
+
+ this.showOnLoad = document.createElement("div");
+ this.showOnLoad.style.visibility = "hidden";
+ this.content.appendChild(this.showOnLoad);
+
+ this.xAxisLabel = createElement("div", "range-header-label-x");
+ this.xAxisLabel.innerText = "Blocks, Instructions, and Positions";
+ this.showOnLoad.appendChild(this.xAxisLabel);
+ this.yAxisLabel = createElement("div", "range-header-label-y");
+ this.yAxisLabel.innerText = "Registers";
+ this.showOnLoad.appendChild(this.yAxisLabel);
+
+ this.registerHeaders = createElement("div", "range-register-labels");
+ this.registers = createElement("div", "range-registers");
+ this.registerHeaders.appendChild(this.registers);
+ }
+
+ elementForTitle() {
+ const titleEl = createElement("div", "range-title-div");
+ const titleBar = createElement("div", "range-title");
+ titleBar.appendChild(createElement("div", "", "Live Ranges"));
+ const titleHelp = createElement("div", "range-title-help", "?");
+ titleHelp.title = "Each row represents a single TopLevelLiveRange (or two if deferred exists)."
+ + "\nEach interval belongs to a LiveRange contained within that row's TopLevelLiveRange."
+ + "\nAn interval is identified by i, the index of the LiveRange within the TopLevelLiveRange,"
+ + "\nand j, the index of the interval within the LiveRange, to give i:j.";
+ titleEl.appendChild(titleBar);
+ titleEl.appendChild(titleHelp);
+ return titleEl;
+ }
+}
+
+class Helper {
+ static virtualRegisterName(registerIndex: string) {
+ return "v" + registerIndex;
+ }
+
+ static fixedRegisterName(range: Range) {
+ return range.child_ranges[0].op.text;
+ }
+
+ static getPositionElementsFromInterval(interval: HTMLElement) {
+ return interval.children[1].children;
+ }
+
+ static forEachFixedRange(source: RegisterAllocation, row: number,
+ callback: (registerIndex: string, row: number, registerName: string,
+ ranges: RangePair) => void) {
+
+ const forEachRangeInMap = (rangeMap: Map<string, Range>) => {
+ // There are two fixed live ranges for each register, one for normal, another for deferred.
+ // These are combined into a single row.
+ const fixedRegisterMap = new Map<string, {ranges: [Range, Range], registerIndex: number}>();
+ for (const [registerIndex, range] of rangeMap) {
+ const registerName = this.fixedRegisterName(range);
+ if (fixedRegisterMap.has(registerName)) {
+ const entry = fixedRegisterMap.get(registerName);
+ entry.ranges[1] = range;
+ // Only use the deferred register index if no normal index exists.
+ if (!range.is_deferred) {
+ entry.registerIndex = parseInt(registerIndex, 10);
+ }
+ } else {
+ fixedRegisterMap.set(registerName, {ranges: [range, undefined],
+ registerIndex: parseInt(registerIndex, 10)});
+ }
+ }
+ // Sort the registers by number.
+ const sortedMap = new Map([...fixedRegisterMap.entries()].sort(([nameA, _], [nameB, __]) => {
+ // Larger numbers create longer strings.
+ if (nameA.length > nameB.length) return 1;
+ if (nameA.length < nameB.length) return -1;
+ // Sort lexicographically if same length.
+ if (nameA > nameB) return 1;
+ if (nameA < nameB) return -1;
+ return 0;
+ }));
+ for (const [registerName, {ranges, registerIndex}] of sortedMap) {
+ callback("" + (-registerIndex - 1), row, registerName, new RangePair(ranges));
+ ++row;
+ }
+ };
+
+ forEachRangeInMap(source.fixedLiveRanges);
+ forEachRangeInMap(source.fixedDoubleLiveRanges);
+
+ return row;
+ }
+}
+
+class RowConstructor {
+ view: RangeView;
+
+ constructor(view: RangeView) {
+ this.view = view;
+ }
+
+ // Constructs the row of HTMLElements for grid while providing a callback for each position
+ // depending on whether that position is the start of an interval or not.
+ // RangePair is used to allow the two fixed register live ranges of normal and deferred to be
+ // easily combined into a single row.
+ construct(grid: Grid, row: number, registerIndex: string, ranges: RangePair,
+ getElementForEmptyPosition: (position: number) => HTMLElement,
+ callbackForInterval: (position: number, interval: HTMLElement) => void) {
+ const positionArray = new Array<HTMLElement>(this.view.numPositions);
+ // Construct all of the new intervals.
+ const intervalMap = this.elementsForIntervals(registerIndex, ranges);
+ for (let position = 0; position < this.view.numPositions; ++position) {
+ const interval = intervalMap.get(position);
+ if (interval == undefined) {
+ positionArray[position] = getElementForEmptyPosition(position);
+ } else {
+ callbackForInterval(position, interval);
+ this.view.intervalsAccessor.addInterval(interval);
+ const intervalPositionElements = Helper.getPositionElementsFromInterval(interval);
+ for (let j = 0; j < intervalPositionElements.length; ++j) {
+ // Point positionsArray to the new elements.
+ positionArray[position + j] = (intervalPositionElements[j] as HTMLElement);
+ }
+ position += intervalPositionElements.length - 1;
+ }
+ }
+ grid.setRow(row, positionArray);
+ ranges.forEachRange((range: Range) => this.setUses(grid, row, range));
+ }
+
+ // This is the main function used to build new intervals.
+ // Returns a map of LifeTimePositions to intervals.
+ private elementsForIntervals(registerIndex: string, ranges: RangePair) {
+ const intervalMap = new Map<number, HTMLElement>();
+ let tooltip = "";
+ ranges.forEachRange((range: Range) => {
+ for (const childRange of range.child_ranges) {
+ switch (childRange.type) {
+ case "none":
+ tooltip = Constants.INTERVAL_TEXT_FOR_NONE;
+ break;
+ case "spill_range":
+ tooltip = Constants.INTERVAL_TEXT_FOR_STACK + registerIndex;
+ break;
+ default:
+ if (childRange.op.type == "constant") {
+ tooltip = Constants.INTERVAL_TEXT_FOR_CONST;
+ } else {
+ if (childRange.op.text) {
+ tooltip = childRange.op.text;
+ } else {
+ tooltip = childRange.op;
+ }
+ }
+ break;
+ }
+ childRange.intervals.forEach((intervalNums, index) => {
+ const interval = new Interval(intervalNums);
+ const intervalEl = this.elementForInterval(childRange, interval, tooltip,
+ index, range.is_deferred);
+ intervalMap.set(interval.start, intervalEl);
+ });
+ }
+ });
+ return intervalMap;
+ }
+
+ private elementForInterval(childRange: ChildRange, interval: Interval,
+ tooltip: string, index: number, isDeferred: boolean): HTMLElement {
+ const intervalEl = createElement("div", "range-interval");
+ const title = childRange.id + ":" + index + " " + tooltip;
+ intervalEl.setAttribute("title", isDeferred ? "deferred: " + title : title);
+ this.setIntervalColor(intervalEl, tooltip);
+ const intervalInnerWrapper = createElement("div", "range-interval-wrapper");
+ intervalEl.style.gridColumn = (interval.start + 1) + " / " + (interval.end + 1);
+ intervalInnerWrapper.style.gridTemplateColumns = "repeat(" + (interval.end - interval.start)
+ + ",calc(" + this.view.cssVariables.positionWidth + "ch + "
+ + this.view.cssVariables.blockBorderWidth + "px)";
+ const intervalTextEl = this.elementForIntervalString(tooltip, interval.end - interval.start);
+ intervalEl.appendChild(intervalTextEl);
+ for (let i = interval.start; i < interval.end; ++i) {
+ const classes = "range-position range-interval-position range-empty"
+ + (this.view.blocksData.isBlockBorder(i) ? " range-block-border" :
+ this.view.blocksData.isInstructionBorder(i) ? " range-instr-border" : "");
+ const positionEl = createElement("div", classes, "_");
+ positionEl.style.gridColumn = (i - interval.start + 1) + "";
+ intervalInnerWrapper.appendChild(positionEl);
+ }
+ intervalEl.appendChild(intervalInnerWrapper);
+ return intervalEl;
+ }
+
+ private setIntervalColor(interval: HTMLElement, tooltip: string) {
+ if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_NONE)) return;
+ if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_STACK + "-")) {
+ interval.style.backgroundColor = "rgb(250, 158, 168)";
+ } else if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_STACK)) {
+ interval.style.backgroundColor = "rgb(250, 158, 100)";
+ } else if (tooltip.includes(Constants.INTERVAL_TEXT_FOR_CONST)) {
+ interval.style.backgroundColor = "rgb(153, 158, 230)";
+ } else {
+ interval.style.backgroundColor = "rgb(153, 220, 168)";
+ }
+ }
+
+ private elementForIntervalString(tooltip: string, numCells: number) {
+ const spanEl = createElement("span", "range-interval-text");
+ this.setIntervalString(spanEl, tooltip, numCells);
+ return spanEl;
+ }
+
+ // Each interval displays a string of information about it.
+ private setIntervalString(spanEl: HTMLElement, tooltip: string, numCells: number) {
+ const spacePerCell = this.view.cssVariables.positionWidth;
+ // One character space is removed to accommodate for padding.
+ const spaceAvailable = (numCells * spacePerCell) - 0.5;
+ let str = tooltip + "";
+ const length = tooltip.length;
+ spanEl.style.width = null;
+ let paddingLeft = null;
+ // Add padding if possible
+ if (length <= spaceAvailable) {
+ paddingLeft = (length == spaceAvailable) ? "0.5ch" : "1ch";
+ } else {
+ str = "";
+ }
+ spanEl.style.paddingTop = null;
+ spanEl.style.paddingLeft = paddingLeft;
+ spanEl.innerHTML = str;
+ }
+
+ private setUses(grid: Grid, row: number, range: Range) {
+ for (const liveRange of range.child_ranges) {
+ if (liveRange.uses) {
+ for (const use of liveRange.uses) {
+ grid.getCell(row, use).classList.toggle("range-use", true);
+ }
+ }
+ }
+ }
+}
+
+class RangeViewConstructor {
+ view: RangeView;
+ gridTemplateColumns: string;
+ grid: Grid;
+
+ // Group the rows in divs to make hiding/showing divs more efficient.
+ currentGroup: HTMLElement;
+ currentPlaceholderGroup: HTMLElement;
+
+ constructor(rangeView: RangeView) {
+ this.view = rangeView;
+ }
+
+ construct() {
+ this.gridTemplateColumns = "repeat(" + this.view.numPositions
+ + ",calc(" + this.view.cssVariables.positionWidth + "ch + "
+ + this.view.cssVariables.blockBorderWidth + "px)";
+
+ this.grid = new Grid();
+ this.view.gridAccessor.addGrid(this.grid);
+
+ this.view.divs.wholeHeader = this.elementForHeader();
+ this.view.divs.showOnLoad.appendChild(this.view.divs.wholeHeader);
+
+ const gridContainer = document.createElement("div");
+ this.view.divs.grid = this.elementForGrid();
+ this.view.divs.yAxis = createElement("div", "range-y-axis");
+ this.view.divs.yAxis.appendChild(this.view.divs.registerHeaders);
+ this.view.divs.yAxis.onscroll = () => {
+ this.view.scrollHandler.syncScroll(ToSync.TOP, this.view.divs.yAxis, this.view.divs.grid);
+ this.view.scrollHandler.saveScroll();
+ };
+ gridContainer.appendChild(this.view.divs.yAxis);
+ gridContainer.appendChild(this.view.divs.grid);
+ this.view.divs.showOnLoad.appendChild(gridContainer);
+
+ this.resetGroups();
+ let row = 0;
+ row = this.addVirtualRanges(row);
+ this.addFixedRanges(row);
+ }
+
+ // The following three functions are for constructing the groups which the rows are contained
+ // within and which make up the grid. This is so as to allow groups of rows to easily be displayed
+ // and hidden for performance reasons. As rows are constructed, they are added to the currentGroup
+ // div. Each row in currentGroup is matched with an equivalent placeholder row in
+ // currentPlaceholderGroup that will be shown when currentGroup is hidden so as to maintain the
+ // dimensions and scroll positions of the grid.
+
+ private resetGroups () {
+ this.currentGroup = createElement("div", "range-positions-group range-hidden");
+ this.currentPlaceholderGroup = createElement("div", "range-positions-group");
+ }
+
+ private appendGroupsToGrid() {
+ this.view.divs.grid.appendChild(this.currentPlaceholderGroup);
+ this.view.divs.grid.appendChild(this.currentGroup);
+ }
+
+ private addRowToGroup(row: number, rowEl: HTMLElement) {
+ this.currentGroup.appendChild(rowEl);
+ this.currentPlaceholderGroup
+ .appendChild(createElement("div", "range-positions range-positions-placeholder", "_"));
+ if ((row + 1) % Constants.ROW_GROUP_SIZE == 0) {
+ this.appendGroupsToGrid();
+ this.resetGroups();
+ }
+ }
+
+ private addVirtualRanges(row: number) {
+ const source = this.view.sequenceView.sequence.register_allocation;
+ for (const [registerIndex, range] of source.liveRanges) {
+ const registerName = Helper.virtualRegisterName(registerIndex);
+ const registerEl = this.elementForVirtualRegister(registerName);
+ this.addRowToGroup(row, this.elementForRow(row, registerIndex,
+ new RangePair([range, undefined])));
+ this.view.divs.registers.appendChild(registerEl);
+ ++row;
+ }
+ return row;
+ }
+
+ private addFixedRanges(row: number) {
+ row = Helper.forEachFixedRange(this.view.sequenceView.sequence.register_allocation, row,
+ (registerIndex: string, row: number,
+ registerName: string, ranges: RangePair) => {
+ const registerEl = this.elementForFixedRegister(registerName);
+ this.addRowToGroup(row, this.elementForRow(row, registerIndex, ranges));
+ this.view.divs.registers.appendChild(registerEl);
+ });
+ if (row % Constants.ROW_GROUP_SIZE != 0) {
+ this.appendGroupsToGrid();
+ }
+ }
+
+ // Each row of positions and intervals associated with a register is contained in a single
+ // HTMLElement. RangePair is used to allow the two fixed register live ranges of normal and
+ // deferred to be easily combined into a single row.
+ private elementForRow(row: number, registerIndex: string, ranges: RangePair) {
+ const rowEl = createElement("div", "range-positions");
+ rowEl.style.gridTemplateColumns = this.gridTemplateColumns;
+
+ const getElementForEmptyPosition = (position: number) => {
+ const blockBorder = this.view.blocksData.isBlockBorder(position);
+ const classes = "range-position range-empty "
+ + (blockBorder ? "range-block-border" :
+ this.view.blocksData.isInstructionBorder(position) ? "range-instr-border"
+ : "range-position-border");
+ const positionEl = createElement("div", classes, "_");
+ positionEl.style.gridColumn = (position + 1) + "";
+ rowEl.appendChild(positionEl);
+ return positionEl;
+ };
+
+ const callbackForInterval = (_, interval: HTMLElement) => {
+ rowEl.appendChild(interval);
+ };
+
+ this.view.rowConstructor.construct(this.grid, row, registerIndex, ranges,
+ getElementForEmptyPosition, callbackForInterval);
+ return rowEl;
+ }
+
+ private elementForVirtualRegister(registerName: string) {
+ const regEl = createElement("div", "range-reg", registerName);
+ regEl.setAttribute("title", registerName);
+ return regEl;
+ }
+
+ private elementForFixedRegister(registerName: string) {
+ let text = registerName;
+ const span = "".padEnd(Constants.FIXED_REGISTER_LABEL_WIDTH - text.length, "_");
+ text = "HW - <span class='range-transparent'>" + span + "</span>" + text;
+ const regEl = createElement("div", "range-reg");
+ regEl.innerHTML = text;
+ regEl.setAttribute("title", registerName);
+ return regEl;
+ }
+
+ // The header element contains the three headers for the LifeTimePosition axis.
+ private elementForHeader() {
+ const headerEl = createElement("div", "range-header");
+ this.view.divs.positionHeaders = createElement("div", "range-position-labels");
+
+ this.view.divs.positionHeaders.appendChild(this.elementForBlockHeader());
+ this.view.divs.positionHeaders.appendChild(this.elementForInstructionHeader());
+ this.view.divs.positionHeaders.appendChild(this.elementForPositionHeader());
+
+ headerEl.appendChild(this.view.divs.positionHeaders);
+ headerEl.onscroll = () => {
+ this.view.scrollHandler.syncScroll(ToSync.LEFT,
+ this.view.divs.wholeHeader, this.view.divs.grid);
+ this.view.scrollHandler.saveScroll();
+ };
+ return headerEl;
+ }
+
+ // The LifeTimePosition axis shows three headers, for positions, instructions, and blocks.
+
+ private elementForBlockHeader() {
+ const headerEl = createElement("div", "range-block-ids");
+ headerEl.style.gridTemplateColumns = this.gridTemplateColumns;
+
+ const elementForBlockIndex = (index: number, firstInstruction: number, instrCount: number) => {
+ const str = "B" + index;
+ const element =
+ createElement("div", "range-block-id range-header-element range-block-border", str);
+ element.setAttribute("title", str);
+ const firstGridCol = (firstInstruction * Constants.POSITIONS_PER_INSTRUCTION) + 1;
+ const lastGridCol = firstGridCol + (instrCount * Constants.POSITIONS_PER_INSTRUCTION);
+ element.style.gridColumn = firstGridCol + " / " + lastGridCol;
+ return element;
+ };
+
+ let blockIndex = 0;
+ for (let i = 0; i < this.view.sequenceView.numInstructions;) {
+ const instrCount = this.view.blocksData.blockInstructionCountMap.get(blockIndex);
+ headerEl.appendChild(elementForBlockIndex(blockIndex, i, instrCount));
+ ++blockIndex;
+ i += instrCount;
+ }
+ return headerEl;
+ }
+
+ private elementForInstructionHeader() {
+ const headerEl = createElement("div", "range-instruction-ids");
+ headerEl.style.gridTemplateColumns = this.gridTemplateColumns;
+
+ const elementForInstructionIndex = (index: number, isBlockBorder: boolean) => {
+ const classes = "range-instruction-id range-header-element "
+ + (isBlockBorder ? "range-block-border" : "range-instr-border");
+ const element = createElement("div", classes, "" + index);
+ element.setAttribute("title", "" + index);
+ const firstGridCol = (index * Constants.POSITIONS_PER_INSTRUCTION) + 1;
+ element.style.gridColumn = firstGridCol + " / "
+ + (firstGridCol + Constants.POSITIONS_PER_INSTRUCTION);
+ return element;
+ };
+
+ for (let i = 0; i < this.view.sequenceView.numInstructions; ++i) {
+ const blockBorder = this.view.blocksData.blockBorders.has(i);
+ headerEl.appendChild(elementForInstructionIndex(i, blockBorder));
+ }
+ return headerEl;
+ }
+
+ private elementForPositionHeader() {
+ const headerEl = createElement("div", "range-positions range-positions-header");
+ headerEl.style.gridTemplateColumns = this.gridTemplateColumns;
+
+ const elementForPositionIndex = (index: number, isBlockBorder: boolean) => {
+ const classes = "range-position range-header-element " +
+ (isBlockBorder ? "range-block-border"
+ : this.view.blocksData.isInstructionBorder(index) ? "range-instr-border"
+ : "range-position-border");
+ const element = createElement("div", classes, "" + index);
+ element.setAttribute("title", "" + index);
+ return element;
+ };
+
+ for (let i = 0; i < this.view.numPositions; ++i) {
+ headerEl.appendChild(elementForPositionIndex(i, this.view.blocksData.isBlockBorder(i)));
+ }
+ return headerEl;
+ }
+
+ private elementForGrid() {
+ const gridEl = createElement("div", "range-grid");
+ gridEl.onscroll = () => {
+ this.view.scrollHandler.syncScroll(ToSync.TOP, this.view.divs.grid, this.view.divs.yAxis);
+ this.view.scrollHandler.syncScroll(ToSync.LEFT,
+ this.view.divs.grid, this.view.divs.wholeHeader);
+ this.view.scrollHandler.saveScroll();
+ };
+ return gridEl;
+ }
+}
+
+// Handles the work required when the phase is changed.
+// Between before and after register allocation for example.
+class PhaseChangeHandler {
+ view: RangeView;
+
+ constructor(view: RangeView) {
+ this.view = view;
+ }
+
+ // Called when the phase view is switched between before and after register allocation.
+ phaseChange() {
+ if (!this.view.gridAccessor.hasGrid()) {
+ // If this phase view has not been seen yet then the intervals need to be constructed.
+ this.addNewIntervals();
+ }
+ // Show all intervals pertaining to the current phase view.
+ this.view.intervalsAccessor.forEachInterval((phase, interval) => {
+ interval.classList.toggle("range-hidden", phase != this.view.sequenceView.currentPhaseIndex);
+ });
+ }
+
+ private addNewIntervals() {
+ // All Grids should point to the same HTMLElement for empty cells in the grid,
+ // so as to avoid duplication. The current Grid is used to retrieve these elements.
+ const currentGrid = this.view.gridAccessor.getAnyGrid();
+ const newGrid = new Grid();
+ this.view.gridAccessor.addGrid(newGrid);
+ const source = this.view.sequenceView.sequence.register_allocation;
+ let row = 0;
+ for (const [registerIndex, range] of source.liveRanges) {
+ this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex,
+ new RangePair([range, undefined]));
+ ++row;
+ }
+ Helper.forEachFixedRange(this.view.sequenceView.sequence.register_allocation, row,
+ (registerIndex, row, _, ranges) => {
+ this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, ranges);
+ });
+ }
+
+ private addnewIntervalsInRange(currentGrid: Grid, newGrid: Grid, row: number,
+ registerIndex: string, ranges: RangePair) {
+ const numReplacements = new Map<HTMLElement, number>();
+
+ const getElementForEmptyPosition = (position: number) => {
+ return currentGrid.getCell(row, position);
+ };
+
+ // Inserts new interval beside existing intervals.
+ const callbackForInterval = (position: number, interval: HTMLElement) => {
+ // Overlapping intervals are placed beside each other and the relevant ones displayed.
+ let currentInterval = currentGrid.getInterval(row, position);
+ // The number of intervals already inserted is tracked so that the inserted intervals
+ // are ordered correctly.
+ const intervalsAlreadyInserted = numReplacements.get(currentInterval);
+ numReplacements.set(currentInterval, intervalsAlreadyInserted ? intervalsAlreadyInserted + 1
+ : 1);
+ if (intervalsAlreadyInserted) {
+ for (let j = 0; j < intervalsAlreadyInserted; ++j) {
+ currentInterval = (currentInterval.nextElementSibling as HTMLElement);
+ }
+ }
+ interval.classList.add("range-hidden");
+ currentInterval.insertAdjacentElement('afterend', interval);
+ };
+
+ this.view.rowConstructor.construct(newGrid, row, registerIndex, ranges,
+ getElementForEmptyPosition, callbackForInterval);
+ }
+}
+
+enum ToSync { LEFT, TOP }
+
+// Handles saving and syncing the scroll positions of the grid.
+class ScrollHandler {
+ divs: Divs;
+ scrollTop: number;
+ scrollLeft: number;
+ scrollTopTimeout: NodeJS.Timeout;
+ scrollLeftTimeout: NodeJS.Timeout;
+ scrollTopFunc: (this: GlobalEventHandlers, ev: Event) => any;
+ scrollLeftFunc: (this: GlobalEventHandlers, ev: Event) => any;
+
+ constructor(divs: Divs) {
+ this.divs = divs;
+ }
+
+ // This function is used to hide the rows which are not currently in view and
+ // so reduce the performance cost of things like hit tests and scrolling.
+ syncHidden() {
+
+ const getOffset = (rowEl: HTMLElement, placeholderRowEl: HTMLElement, isHidden: boolean) => {
+ return isHidden ? placeholderRowEl.offsetTop : rowEl.offsetTop;
+ };
+
+ const toHide = new Array<[HTMLElement, HTMLElement]>();
+
+ const sampleCell = this.divs.registers.children[1] as HTMLElement;
+ const buffer = 2 * sampleCell.clientHeight;
+ const min = this.divs.grid.offsetTop + this.divs.grid.scrollTop - buffer;
+ const max = min + this.divs.grid.clientHeight + buffer;
+
+ // The rows are grouped by being contained within a group div. This is so as to allow
+ // groups of rows to easily be displayed and hidden with less of a performance cost.
+ // Each row in the mainGroup div is matched with an equivalent placeholder row in
+ // the placeholderGroup div that will be shown when mainGroup is hidden so as to maintain
+ // the dimensions and scroll positions of the grid.
+
+ const rangeGroups = this.divs.grid.children;
+ for (let i = 1; i < rangeGroups.length; i += 2) {
+ const mainGroup = rangeGroups[i] as HTMLElement;
+ const placeholderGroup = rangeGroups[i - 1] as HTMLElement;
+ const isHidden = mainGroup.classList.contains("range-hidden");
+ // The offsets are used to calculate whether the group is in view.
+ const offsetMin = getOffset(mainGroup.firstChild as HTMLElement,
+ placeholderGroup.firstChild as HTMLElement, isHidden);
+ const offsetMax = getOffset(mainGroup.lastChild as HTMLElement,
+ placeholderGroup.lastChild as HTMLElement, isHidden);
+ if (offsetMax > min && offsetMin < max) {
+ if (isHidden) {
+ // Show the rows, hide the placeholders.
+ mainGroup.classList.toggle("range-hidden", false);
+ placeholderGroup.classList.toggle("range-hidden", true);
+ }
+ } else if (!isHidden) {
+ // Only hide the rows once the new rows are shown so that scrollLeft is not lost.
+ toHide.push([mainGroup, placeholderGroup]);
+ }
+ }
+ for (const [mainGroup, placeholderGroup] of toHide) {
+ // Hide the rows, show the placeholders.
+ mainGroup.classList.toggle("range-hidden", true);
+ placeholderGroup.classList.toggle("range-hidden", false);
+ }
+ }
+
+ // This function is required to keep the axes labels in line with the grid
+ // content when scrolling.
+ syncScroll(toSync: ToSync, source: HTMLElement, target: HTMLElement) {
+ // Continually delay timeout until scrolling has stopped.
+ toSync == ToSync.TOP ? clearTimeout(this.scrollTopTimeout)
+ : clearTimeout(this.scrollLeftTimeout);
+ if (target.onscroll) {
+ if (toSync == ToSync.TOP) this.scrollTopFunc = target.onscroll;
+ else this.scrollLeftFunc = target.onscroll;
+ }
+ // Clear onscroll to prevent the target syncing back with the source.
+ target.onscroll = null;
+
+ if (toSync == ToSync.TOP) target.scrollTop = source.scrollTop;
+ else target.scrollLeft = source.scrollLeft;
+
+ // Only show / hide the grid content once scrolling has stopped.
+ if (toSync == ToSync.TOP) {
+ this.scrollTopTimeout = setTimeout(() => {
+ target.onscroll = this.scrollTopFunc;
+ this.syncHidden();
+ }, 500);
+ } else {
+ this.scrollLeftTimeout = setTimeout(() => {
+ target.onscroll = this.scrollLeftFunc;
+ this.syncHidden();
+ }, 500);
+ }
+ }
+
+ saveScroll() {
+ this.scrollLeft = this.divs.grid.scrollLeft;
+ this.scrollTop = this.divs.grid.scrollTop;
+ }
+
+ restoreScroll() {
+ if (this.scrollLeft) {
+ this.divs.grid.scrollLeft = this.scrollLeft;
+ this.divs.grid.scrollTop = this.scrollTop;
+ }
+ }
+}
+
+// RangeView displays the live range data as passed in by SequenceView.
+// The data is displayed in a grid format, with the fixed and virtual registers
+// along one axis, and the LifeTimePositions along the other. Each LifeTimePosition
+// is part of an Instruction in SequenceView, which itself is part of an Instruction
+// Block. The live ranges are displayed as intervals, each belonging to a register,
+// and spanning across a certain range of LifeTimePositions.
+// When the phase being displayed changes between before register allocation and
+// after register allocation, only the intervals need to be changed.
+export class RangeView {
+ sequenceView: SequenceView;
+
+ initialized: boolean;
+ isShown: boolean;
+ numPositions: number;
+ cssVariables: CSSVariables;
+ divs: Divs;
+ rowConstructor: RowConstructor;
+ phaseChangeHandler: PhaseChangeHandler;
+ scrollHandler: ScrollHandler;
+ blocksData: BlocksData;
+ intervalsAccessor: IntervalElementsAccessor;
+ gridAccessor: GridAccessor;
+
+ constructor(sequence: SequenceView) {
+ this.initialized = false;
+ this.isShown = false;
+ this.sequenceView = sequence;
+ }
+
+ initializeContent(blocks: Array<any>) {
+ if (!this.initialized) {
+ this.gridAccessor = new GridAccessor(this.sequenceView);
+ this.intervalsAccessor = new IntervalElementsAccessor(this.sequenceView);
+ this.cssVariables = new CSSVariables();
+ this.blocksData = new BlocksData(blocks);
+ this.divs = new Divs();
+ this.scrollHandler = new ScrollHandler(this.divs);
+ this.numPositions = this.sequenceView.numInstructions * Constants.POSITIONS_PER_INSTRUCTION;
+ this.rowConstructor = new RowConstructor(this);
+ const constructor = new RangeViewConstructor(this);
+ constructor.construct();
+ this.phaseChangeHandler = new PhaseChangeHandler(this);
+ this.initialized = true;
+ } else {
+ // If the RangeView has already been initialized then the phase must have
+ // been changed.
+ this.phaseChangeHandler.phaseChange();
+ }
+ }
+
+ show() {
+ if (!this.isShown) {
+ this.isShown = true;
+ this.divs.container.appendChild(this.divs.content);
+ this.divs.resizerBar.style.visibility = "visible";
+ this.divs.container.style.visibility = "visible";
+ this.divs.snapper.style.visibility = "visible";
+ // Dispatch a resize event to ensure that the
+ // panel is shown.
+ window.dispatchEvent(new Event('resize'));
+
+ setTimeout(() => {
+ this.scrollHandler.restoreScroll();
+ this.scrollHandler.syncHidden();
+ this.divs.showOnLoad.style.visibility = "visible";
+ }, 100);
+ }
+ }
+
+ hide() {
+ if (this.initialized) {
+ this.isShown = false;
+ this.divs.container.removeChild(this.divs.content);
+ this.divs.resizerBar.style.visibility = "hidden";
+ this.divs.container.style.visibility = "hidden";
+ this.divs.snapper.style.visibility = "hidden";
+ this.divs.showOnLoad.style.visibility = "hidden";
+ } else {
+ window.document.getElementById('ranges').style.visibility = "hidden";
+ }
+ // Dispatch a resize event to ensure that the
+ // panel is hidden.
+ window.dispatchEvent(new Event('resize'));
+ }
+
+ onresize() {
+ if (this.isShown) this.scrollHandler.syncHidden();
+ }
+}
diff --git a/chromium/v8/tools/turbolizer/src/resizer.ts b/chromium/v8/tools/turbolizer/src/resizer.ts
index 4bd771f7313..ce0519398bb 100644
--- a/chromium/v8/tools/turbolizer/src/resizer.ts
+++ b/chromium/v8/tools/turbolizer/src/resizer.ts
@@ -11,6 +11,8 @@ class Snapper {
sourceCollapse: HTMLElement;
disassemblyExpand: HTMLElement;
disassemblyCollapse: HTMLElement;
+ rangesExpand: HTMLElement;
+ rangesCollapse: HTMLElement;
constructor(resizer: Resizer) {
this.resizer = resizer;
@@ -18,6 +20,8 @@ class Snapper {
this.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID);
this.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID);
this.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID);
+ this.rangesExpand = document.getElementById(C.RANGES_EXPAND_ID);
+ this.rangesCollapse = document.getElementById(C.RANGES_COLLAPSE_ID);
document.getElementById("show-hide-source").addEventListener("click", () => {
this.resizer.resizerLeft.classed("snapped", !this.resizer.resizerLeft.classed("snapped"));
@@ -29,13 +33,20 @@ class Snapper {
this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible"));
this.resizer.updatePanes();
});
+ document.getElementById("show-hide-ranges").addEventListener("click", () => {
+ this.resizer.resizerRanges.classed("snapped", !this.resizer.resizerRanges.classed("snapped"));
+ this.setRangesExpanded(!this.rangesExpand.classList.contains("invisible"));
+ this.resizer.updatePanes();
+ });
}
restoreExpandedState(): void {
this.resizer.resizerLeft.classed("snapped", window.sessionStorage.getItem("expandedState-source") == "false");
this.resizer.resizerRight.classed("snapped", window.sessionStorage.getItem("expandedState-disassembly") == "false");
+ this.resizer.resizerRanges.classed("snapped", window.sessionStorage.getItem("expandedState-ranges") == "false");
this.setSourceExpanded(this.getLastExpandedState("source", true));
this.setDisassemblyExpanded(this.getLastExpandedState("disassembly", true));
+ this.setRangesExpanded(this.getLastExpandedState("ranges", true));
}
getLastExpandedState(type: string, defaultState: boolean): boolean {
@@ -48,6 +59,7 @@ class Snapper {
window.sessionStorage.setItem("expandedState-source", `${isSourceExpanded}`);
this.sourceExpand.classList.toggle("invisible", isSourceExpanded);
this.sourceCollapse.classList.toggle("invisible", !isSourceExpanded);
+ document.getElementById("show-hide-ranges").style.marginLeft = isSourceExpanded ? null : "40px";
}
setSourceExpanded(isSourceExpanded: boolean): void {
@@ -65,30 +77,53 @@ class Snapper {
this.disassemblyUpdate(isDisassemblyExpanded);
this.resizer.updateRightWidth();
}
+
+ rangesUpdate(isRangesExpanded: boolean): void {
+ window.sessionStorage.setItem("expandedState-ranges", `${isRangesExpanded}`);
+ this.rangesExpand.classList.toggle("invisible", isRangesExpanded);
+ this.rangesCollapse.classList.toggle("invisible", !isRangesExpanded);
+ }
+
+ setRangesExpanded(isRangesExpanded: boolean): void {
+ this.rangesUpdate(isRangesExpanded);
+ this.resizer.updateRanges();
+ }
}
export class Resizer {
snapper: Snapper;
deadWidth: number;
+ deadHeight: number;
left: HTMLElement;
right: HTMLElement;
+ ranges: HTMLElement;
+ middle: HTMLElement;
sepLeft: number;
sepRight: number;
+ sepRangesHeight: number;
panesUpdatedCallback: () => void;
resizerRight: d3.Selection<HTMLDivElement, any, any, any>;
resizerLeft: d3.Selection<HTMLDivElement, any, any, any>;
+ resizerRanges: d3.Selection<HTMLDivElement, any, any, any>;
private readonly SOURCE_PANE_DEFAULT_PERCENT = 1 / 4;
private readonly DISASSEMBLY_PANE_DEFAULT_PERCENT = 3 / 4;
+ private readonly RANGES_PANE_HEIGHT_DEFAULT_PERCENT = 3 / 4;
+ private readonly RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE = 5;
+ private readonly RESIZER_SIZE = document.getElementById("resizer-ranges").offsetHeight;
- constructor(panesUpdatedCallback: () => void, deadWidth: number) {
+ constructor(panesUpdatedCallback: () => void, deadWidth: number, deadHeight: number) {
const resizer = this;
resizer.panesUpdatedCallback = panesUpdatedCallback;
resizer.deadWidth = deadWidth;
+ resizer.deadHeight = deadHeight;
resizer.left = document.getElementById(C.SOURCE_PANE_ID);
resizer.right = document.getElementById(C.GENERATED_PANE_ID);
+ resizer.ranges = document.getElementById(C.RANGES_PANE_ID);
+ resizer.middle = document.getElementById("middle");
resizer.resizerLeft = d3.select('#resizer-left');
resizer.resizerRight = d3.select('#resizer-right');
+ resizer.resizerRanges = d3.select('#resizer-ranges');
// Set default sizes, if they weren't set.
if (window.sessionStorage.getItem("source-pane-percent") === null) {
window.sessionStorage.setItem("source-pane-percent", `${this.SOURCE_PANE_DEFAULT_PERCENT}`);
@@ -96,8 +131,11 @@ export class Resizer {
if (window.sessionStorage.getItem("disassembly-pane-percent") === null) {
window.sessionStorage.setItem("disassembly-pane-percent", `${this.DISASSEMBLY_PANE_DEFAULT_PERCENT}`);
}
+ if (window.sessionStorage.getItem("ranges-pane-height-percent") === null) {
+ window.sessionStorage.setItem("ranges-pane-height-percent", `${this.RANGES_PANE_HEIGHT_DEFAULT_PERCENT}`);
+ }
- this.updateWidths();
+ this.updateSizes();
const dragResizeLeft = d3.drag()
.on('drag', function () {
@@ -151,8 +189,35 @@ export class Resizer {
resizer.resizerRight.classed("dragged", false);
});
resizer.resizerRight.call(dragResizeRight);
+
+ const dragResizeRanges = d3.drag()
+ .on('drag', function () {
+ const y = d3.mouse(this.parentElement)[1];
+ resizer.sepRangesHeight = Math.max(100, Math.min(y, window.innerHeight) - resizer.RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE);
+ resizer.updatePanes();
+ })
+ .on('start', function () {
+ resizer.resizerRanges.classed("dragged", true);
+ })
+ .on('end', function () {
+ // If the panel is close enough to the bottom, treat it as if it was pulled all the way to the bottom.
+ const y = d3.mouse(this.parentElement)[1];
+ if (y >= (window.innerHeight - deadHeight)) {
+ resizer.sepRangesHeight = window.innerHeight;
+ resizer.updatePanes();
+ }
+ // Snap if dragged all the way to the bottom.
+ resizer.resizerRanges.classed("snapped", resizer.sepRangesHeight >= window.innerHeight - 1);
+ if (!resizer.isRangesSnapped()) {
+ window.sessionStorage.setItem("ranges-pane-height-percent", `${resizer.sepRangesHeight / window.innerHeight}`);
+ }
+ resizer.snapper.setRangesExpanded(!resizer.isRangesSnapped());
+ resizer.resizerRanges.classed("dragged", false);
+ });
+ resizer.resizerRanges.call(dragResizeRanges);
+
window.onresize = function () {
- resizer.updateWidths();
+ resizer.updateSizes();
resizer.updatePanes();
};
resizer.snapper = new Snapper(resizer);
@@ -167,15 +232,70 @@ export class Resizer {
return this.resizerRight.classed("snapped");
}
+ isRangesSnapped() {
+ return this.resizerRanges.classed("snapped");
+ }
+
+ updateRangesPane() {
+ const clientHeight = window.innerHeight;
+ const rangesIsHidden = this.ranges.style.visibility == "hidden";
+ let resizerSize = this.RESIZER_SIZE;
+ if (rangesIsHidden) {
+ resizerSize = 0;
+ this.sepRangesHeight = clientHeight;
+ }
+
+ const rangeHeight = clientHeight - this.sepRangesHeight;
+ this.ranges.style.height = rangeHeight + 'px';
+ const panelWidth = this.sepRight - this.sepLeft - (2 * resizerSize);
+ this.ranges.style.width = panelWidth + 'px';
+ const multiview = document.getElementById("multiview");
+ if (multiview && multiview.style) {
+ multiview.style.height = (this.sepRangesHeight - resizerSize) + 'px';
+ multiview.style.width = panelWidth + 'px';
+ }
+
+ // Resize the range grid and labels.
+ const rangeGrid = (this.ranges.getElementsByClassName("range-grid")[0] as HTMLElement);
+ if (rangeGrid) {
+ const yAxis = (this.ranges.getElementsByClassName("range-y-axis")[0] as HTMLElement);
+ const rangeHeader = (this.ranges.getElementsByClassName("range-header")[0] as HTMLElement);
+
+ const gridWidth = panelWidth - yAxis.clientWidth;
+ rangeGrid.style.width = Math.floor(gridWidth - 1) + 'px';
+ // Take live ranges' right scrollbar into account.
+ rangeHeader.style.width = (gridWidth - rangeGrid.offsetWidth + rangeGrid.clientWidth - 1) + 'px';
+ // Set resizer to horizontal.
+ this.resizerRanges.style('width', panelWidth + 'px');
+
+ const rangeTitle = (this.ranges.getElementsByClassName("range-title-div")[0] as HTMLElement);
+ const rangeHeaderLabel = (this.ranges.getElementsByClassName("range-header-label-x")[0] as HTMLElement);
+ const gridHeight = rangeHeight - rangeHeader.clientHeight - rangeTitle.clientHeight - rangeHeaderLabel.clientHeight;
+ rangeGrid.style.height = gridHeight + 'px';
+ // Take live ranges' bottom scrollbar into account.
+ yAxis.style.height = (gridHeight - rangeGrid.offsetHeight + rangeGrid.clientHeight) + 'px';
+ }
+ this.resizerRanges.style('ranges', this.ranges.style.height);
+ }
+
updatePanes() {
this.left.style.width = this.sepLeft + 'px';
this.resizerLeft.style('left', this.sepLeft + 'px');
this.right.style.width = (document.body.getBoundingClientRect().width - this.sepRight) + 'px';
this.resizerRight.style('right', (document.body.getBoundingClientRect().width - this.sepRight - 1) + 'px');
-
+ this.updateRangesPane();
this.panesUpdatedCallback();
}
+ updateRanges() {
+ if (this.isRangesSnapped()) {
+ this.sepRangesHeight = window.innerHeight;
+ } else {
+ const sepRangesHeight = window.sessionStorage.getItem("ranges-pane-height-percent");
+ this.sepRangesHeight = window.innerHeight * Number.parseFloat(sepRangesHeight);
+ }
+ }
+
updateLeftWidth() {
if (this.isLeftSnapped()) {
this.sepLeft = 0;
@@ -194,8 +314,9 @@ export class Resizer {
}
}
- updateWidths() {
+ updateSizes() {
this.updateLeftWidth();
this.updateRightWidth();
+ this.updateRanges();
}
}
diff --git a/chromium/v8/tools/turbolizer/src/sequence-view.ts b/chromium/v8/tools/turbolizer/src/sequence-view.ts
index 49b7e9f7b2a..187b162b1cd 100644
--- a/chromium/v8/tools/turbolizer/src/sequence-view.ts
+++ b/chromium/v8/tools/turbolizer/src/sequence-view.ts
@@ -3,12 +3,21 @@
// found in the LICENSE file.
import { Sequence } from "../src/source-resolver";
-import { isIterable } from "../src/util";
+import { createElement } from "../src/util";
import { TextView } from "../src/text-view";
+import { RangeView } from "../src/range-view";
export class SequenceView extends TextView {
sequence: Sequence;
searchInfo: Array<any>;
+ phaseSelect: HTMLSelectElement;
+ numInstructions: number;
+ currentPhaseIndex: number;
+ phaseIndexes: Set<number>;
+ isShown: boolean;
+ rangeView: RangeView;
+ showRangeView: boolean;
+ toggleRangeViewEl: HTMLElement;
createViewElement() {
const pane = document.createElement('div');
@@ -20,6 +29,12 @@ export class SequenceView extends TextView {
constructor(parentId, broker) {
super(parentId, broker);
+ this.numInstructions = 0;
+ this.phaseIndexes = new Set<number>();
+ this.isShown = false;
+ this.showRangeView = false;
+ this.rangeView = null;
+ this.toggleRangeViewEl = this.elementForToggleRangeView();
}
attachSelection(s) {
@@ -37,34 +52,58 @@ export class SequenceView extends TextView {
return this.selection.detachSelection();
}
+ show() {
+ this.currentPhaseIndex = this.phaseSelect.selectedIndex;
+ if (!this.isShown) {
+ this.isShown = true;
+ this.phaseIndexes.add(this.currentPhaseIndex);
+ this.container.appendChild(this.divNode);
+ this.container.getElementsByClassName("graph-toolbox")[0].appendChild(this.toggleRangeViewEl);
+ }
+ if (this.showRangeView) this.rangeView.show();
+ }
+
+ hide() {
+ // A single SequenceView object is used for two phases (i.e before and after
+ // register allocation), tracking the indexes lets the redundant hides and
+ // shows be avoided when switching between the two.
+ this.currentPhaseIndex = this.phaseSelect.selectedIndex;
+ if (!this.phaseIndexes.has(this.currentPhaseIndex)) {
+ this.isShown = false;
+ this.container.removeChild(this.divNode);
+ this.container.getElementsByClassName("graph-toolbox")[0].removeChild(this.toggleRangeViewEl);
+ if (this.showRangeView) this.rangeView.hide();
+ }
+ }
+
+ onresize() {
+ if (this.showRangeView) this.rangeView.onresize();
+ }
+
initializeContent(data, rememberedSelection) {
this.divNode.innerHTML = '';
this.sequence = data.sequence;
this.searchInfo = [];
- this.divNode.addEventListener('click', (e: MouseEvent) => {
+ this.divNode.onclick = (e: MouseEvent) => {
if (!(e.target instanceof HTMLElement)) return;
const instructionId = Number.parseInt(e.target.dataset.instructionId, 10);
if (!instructionId) return;
if (!e.shiftKey) this.broker.broadcastClear(null);
this.broker.broadcastInstructionSelect(null, [instructionId], true);
- });
+ };
+ this.phaseSelect = (document.getElementById('phase-select') as HTMLSelectElement);
+ this.currentPhaseIndex = this.phaseSelect.selectedIndex;
+
this.addBlocks(this.sequence.blocks);
+ const lastBlock = this.sequence.blocks[this.sequence.blocks.length - 1];
+ this.numInstructions = lastBlock.instructions[lastBlock.instructions.length - 1].id + 1;
+ this.addRangeView();
this.attachSelection(rememberedSelection);
this.show();
}
elementForBlock(block) {
const view = this;
- function createElement(tag: string, cls: string | Array<string>, content?: string) {
- const el = document.createElement(tag);
- if (isIterable(cls)) {
- for (const c of cls) el.classList.add(c);
- } else {
- el.classList.add(cls);
- }
- if (content != undefined) el.innerHTML = content;
- return el;
- }
function mkLinkHandler(id, handler) {
return function (e) {
@@ -84,16 +123,33 @@ export class SequenceView extends TextView {
return mkLinkHandler(text, view.selectionHandler);
}
+ function elementForOperandWithSpan(span, text, searchInfo, isVirtual) {
+ const selectionText = isVirtual ? "virt_" + text : text;
+ span.onclick = mkOperandLinkHandler(selectionText);
+ searchInfo.push(text);
+ view.addHtmlElementForNodeId(selectionText, span);
+ const container = createElement("div", "");
+ container.appendChild(span);
+ return container;
+ }
+
function elementForOperand(operand, searchInfo) {
- const text = operand.text;
- const operandEl = createElement("div", ["parameter", "tag", "clickable", operand.type], text);
+ let isVirtual = false;
+ let className = "parameter tag clickable " + operand.type;
+ if (operand.text[0] == 'v' && !(operand.tooltip && operand.tooltip.includes("Float"))) {
+ isVirtual = true;
+ className += " virtual-reg";
+ }
+ const span = createElement("span", className, operand.text);
if (operand.tooltip) {
- operandEl.setAttribute("title", operand.tooltip);
+ span.setAttribute("title", operand.tooltip);
}
- operandEl.onclick = mkOperandLinkHandler(text);
- searchInfo.push(text);
- view.addHtmlElementForNodeId(text, operandEl);
- return operandEl;
+ return elementForOperandWithSpan(span, operand.text, searchInfo, isVirtual);
+ }
+
+ function elementForPhiOperand(text, searchInfo) {
+ const span = createElement("span", "parameter tag clickable virtual-reg", text);
+ return elementForOperandWithSpan(span, text, searchInfo, true);
}
function elementForInstruction(instruction, searchInfo) {
@@ -115,7 +171,7 @@ export class SequenceView extends TextView {
const gapEl = createElement("div", "gap", "gap");
let hasGaps = false;
for (const gap of instruction.gaps) {
- const moves = createElement("div", ["comma-sep-list", "gap-move"]);
+ const moves = createElement("div", "comma-sep-list gap-move");
for (const move of gap) {
hasGaps = true;
const moveEl = createElement("div", "move");
@@ -137,7 +193,7 @@ export class SequenceView extends TextView {
instContentsEl.appendChild(instEl);
if (instruction.outputs.length > 0) {
- const outputs = createElement("div", ["comma-sep-list", "input-output-list"]);
+ const outputs = createElement("div", "comma-sep-list input-output-list");
for (const output of instruction.outputs) {
const outputEl = elementForOperand(output, searchInfo);
outputs.appendChild(outputEl);
@@ -147,8 +203,8 @@ export class SequenceView extends TextView {
instEl.appendChild(assignEl);
}
- let text = instruction.opcode + instruction.flags;
- const instLabel = createElement("div", "node-label", text)
+ const text = instruction.opcode + instruction.flags;
+ const instLabel = createElement("div", "node-label", text);
if (instruction.opcode == "ArchNop" && instruction.outputs.length == 1 && instruction.outputs[0].tooltip) {
instLabel.innerText = instruction.outputs[0].tooltip;
}
@@ -158,7 +214,7 @@ export class SequenceView extends TextView {
instEl.appendChild(instLabel);
if (instruction.inputs.length > 0) {
- const inputs = createElement("div", ["comma-sep-list", "input-output-list"]);
+ const inputs = createElement("div", "comma-sep-list input-output-list");
for (const input of instruction.inputs) {
const inputEl = elementForOperand(input, searchInfo);
inputs.appendChild(inputEl);
@@ -167,7 +223,7 @@ export class SequenceView extends TextView {
}
if (instruction.temps.length > 0) {
- const temps = createElement("div", ["comma-sep-list", "input-output-list", "temps"]);
+ const temps = createElement("div", "comma-sep-list input-output-list temps");
for (const temp of instruction.temps) {
const tempEl = elementForOperand(temp, searchInfo);
temps.appendChild(tempEl);
@@ -181,12 +237,12 @@ export class SequenceView extends TextView {
const sequenceBlock = createElement("div", "schedule-block");
sequenceBlock.classList.toggle("deferred", block.deferred);
- const blockId = createElement("div", ["block-id", "com", "clickable"], block.id);
+ const blockId = createElement("div", "block-id com clickable", block.id);
blockId.onclick = mkBlockLinkHandler(block.id);
sequenceBlock.appendChild(blockId);
- const blockPred = createElement("div", ["predecessor-list", "block-list", "comma-sep-list"]);
+ const blockPred = createElement("div", "predecessor-list block-list comma-sep-list");
for (const pred of block.predecessors) {
- const predEl = createElement("div", ["block-id", "com", "clickable"], pred);
+ const predEl = createElement("div", "block-id com clickable", pred);
predEl.onclick = mkBlockLinkHandler(pred);
blockPred.appendChild(predEl);
}
@@ -211,7 +267,7 @@ export class SequenceView extends TextView {
phiEl.appendChild(assignEl);
for (const input of phi.operands) {
- const inputEl = createElement("div", ["parameter", "tag", "clickable"], input);
+ const inputEl = elementForPhiOperand(input, this.searchInfo);
phiEl.appendChild(inputEl);
}
}
@@ -221,9 +277,9 @@ export class SequenceView extends TextView {
instructions.appendChild(elementForInstruction(instruction, this.searchInfo));
}
sequenceBlock.appendChild(instructions);
- const blockSucc = createElement("div", ["successor-list", "block-list", "comma-sep-list"]);
+ const blockSucc = createElement("div", "successor-list block-list comma-sep-list");
for (const succ of block.successors) {
- const succEl = createElement("div", ["block-id", "com", "clickable"], succ);
+ const succEl = createElement("div", "block-id com clickable", succ);
succEl.onclick = mkBlockLinkHandler(succ);
blockSucc.appendChild(succEl);
}
@@ -239,6 +295,63 @@ export class SequenceView extends TextView {
}
}
+ addRangeView() {
+ const preventRangeView = reason => {
+ const toggleRangesInput = this.toggleRangeViewEl.firstChild as HTMLInputElement;
+ if (this.rangeView) {
+ toggleRangesInput.checked = false;
+ this.toggleRangeView(toggleRangesInput);
+ }
+ toggleRangesInput.disabled = true;
+ this.toggleRangeViewEl.style.textDecoration = "line-through";
+ this.toggleRangeViewEl.setAttribute("title", reason);
+ };
+
+ if (this.sequence.register_allocation) {
+ if (!this.rangeView) {
+ this.rangeView = new RangeView(this);
+ }
+ const source = this.sequence.register_allocation;
+ if (source.fixedLiveRanges.size == 0 && source.liveRanges.size == 0) {
+ preventRangeView("No live ranges to show");
+ } else if (this.numInstructions >= 249) {
+ // This is due to the css grid-column being limited to 1000 columns.
+ // Performance issues would otherwise impose some limit.
+ // TODO(george.wort@arm.com): Allow the user to specify an instruction range
+ // to display that spans less than 249 instructions.
+ preventRangeView(
+ "Live range display is only supported for sequences with less than 249 instructions");
+ }
+ if (this.showRangeView) {
+ this.rangeView.initializeContent(this.sequence.blocks);
+ }
+ } else {
+ preventRangeView("No live range data provided");
+ }
+ }
+
+ elementForToggleRangeView() {
+ const toggleRangeViewEl = createElement("label", "", "show live ranges");
+ const toggleRangesInput = createElement("input", "range-toggle-show") as HTMLInputElement;
+ toggleRangesInput.setAttribute("type", "checkbox");
+ toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput);
+ toggleRangeViewEl.insertBefore(toggleRangesInput, toggleRangeViewEl.firstChild);
+ return toggleRangeViewEl;
+ }
+
+ toggleRangeView(toggleRangesInput: HTMLInputElement) {
+ toggleRangesInput.disabled = true;
+ this.showRangeView = toggleRangesInput.checked;
+ if (this.showRangeView) {
+ this.rangeView.initializeContent(this.sequence.blocks);
+ this.rangeView.show();
+ } else {
+ this.rangeView.hide();
+ }
+ window.dispatchEvent(new Event('resize'));
+ toggleRangesInput.disabled = false;
+ }
+
searchInputAction(searchBar, e) {
e.stopPropagation();
this.selectionHandler.clear();
diff --git a/chromium/v8/tools/turbolizer/src/source-resolver.ts b/chromium/v8/tools/turbolizer/src/source-resolver.ts
index 588eea5b995..085b44f3a75 100644
--- a/chromium/v8/tools/turbolizer/src/source-resolver.ts
+++ b/chromium/v8/tools/turbolizer/src/source-resolver.ts
@@ -83,7 +83,7 @@ interface InstructionsPhase {
instructionOffsetToPCOffset?: any;
blockIdtoInstructionRange?: any;
nodeIdToInstructionRange?: any;
- codeOffsetsInfo?: CodeOffsetsInfo
+ codeOffsetsInfo?: CodeOffsetsInfo;
}
interface GraphPhase {
@@ -100,8 +100,44 @@ export interface Schedule {
nodes: Array<any>;
}
+export class Interval {
+ start: number;
+ end: number;
+
+ constructor(numbers: [number, number]) {
+ this.start = numbers[0];
+ this.end = numbers[1];
+ }
+}
+
+export interface ChildRange {
+ id: string;
+ type: string;
+ op: any;
+ intervals: Array<[number, number]>;
+ uses: Array<number>;
+}
+
+export interface Range {
+ child_ranges: Array<ChildRange>;
+ is_deferred: boolean;
+}
+
+export class RegisterAllocation {
+ fixedDoubleLiveRanges: Map<string, Range>;
+ fixedLiveRanges: Map<string, Range>;
+ liveRanges: Map<string, Range>;
+
+ constructor(registerAllocation) {
+ this.fixedDoubleLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_double_live_ranges));
+ this.fixedLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_live_ranges));
+ this.liveRanges = new Map<string, Range>(Object.entries(registerAllocation.live_ranges));
+ }
+}
+
export interface Sequence {
blocks: Array<any>;
+ register_allocation: RegisterAllocation;
}
class CodeOffsetsInfo {
@@ -720,8 +756,11 @@ export class SourceResolver {
phase.schedule = state;
return phase;
}
+
parseSequence(phase) {
- phase.sequence = { blocks: phase.blocks };
+ phase.sequence = { blocks: phase.blocks,
+ register_allocation: phase.register_allocation ? new RegisterAllocation(phase.register_allocation)
+ : undefined };
return phase;
}
}
diff --git a/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts b/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts
index 22753cdda50..2dd01c28f74 100644
--- a/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts
+++ b/chromium/v8/tools/turbolizer/src/turbo-visualizer.ts
@@ -18,7 +18,7 @@ window.onload = function () {
let sourceViews: Array<CodeView> = [];
let selectionBroker: SelectionBroker = null;
let sourceResolver: SourceResolver = null;
- const resizer = new Resizer(panesUpdatedCallback, 75);
+ const resizer = new Resizer(panesUpdatedCallback, 75, 75);
const sourceTabsContainer = document.getElementById(C.SOURCE_PANE_ID);
const sourceTabs = new Tabs(sourceTabsContainer);
sourceTabs.addTab("&#x2b;").classList.add("last-tab", "persistent-tab");
@@ -48,6 +48,9 @@ window.onload = function () {
sourceViews.forEach(sv => sv.hide());
if (multiview) multiview.hide();
multiview = null;
+ document.getElementById("ranges").innerHTML = '';
+ document.getElementById('ranges').style.visibility = "hidden";
+ document.getElementById('show-hide-ranges').style.visibility = "hidden";
if (disassemblyView) disassemblyView.hide();
sourceViews = [];
sourceResolver = new SourceResolver();
diff --git a/chromium/v8/tools/turbolizer/src/util.ts b/chromium/v8/tools/turbolizer/src/util.ts
index d9c8dcdce05..8d2fc845115 100644
--- a/chromium/v8/tools/turbolizer/src/util.ts
+++ b/chromium/v8/tools/turbolizer/src/util.ts
@@ -91,3 +91,10 @@ export function measureText(text: string) {
export function interpolate(val: number, max: number, start: number, end: number) {
return start + (end - start) * (val / max);
}
+
+export function createElement(tag: string, cls: string, content?: string) {
+ const el = document.createElement(tag);
+ el.className = cls;
+ if (content != undefined) el.innerText = content;
+ return el;
+}
diff --git a/chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css b/chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css
new file mode 100644
index 00000000000..03976e2ec54
--- /dev/null
+++ b/chromium/v8/tools/turbolizer/turbo-visualizer-ranges.css
@@ -0,0 +1,238 @@
+/* CSS specific to the live ranges div associated with
+ the RangeView typescript class in src/range-view.ts. */
+
+:root {
+ --range-y-axis-width: 18ch;
+ --range-position-width: 3.5ch;
+ --range-block-border: 6px;
+ --range-instr-border: 3px;
+ --range-position-border: 1px;
+}
+
+.range-bold {
+ font-weight: bold;
+ color: black;
+}
+
+#ranges {
+ font-family: monospace;
+ min-height: auto;
+ overflow: hidden;
+}
+
+#resizer-ranges {
+ height: 10px;
+}
+
+.range-title-div {
+ padding: 2ch 2ch 2ch 2ch;
+ white-space: nowrap;
+ overflow: auto;
+}
+
+.range-title {
+ text-decoration: underline;
+ font-weight: bold;
+ font-size: large;
+ display: inline-block;
+}
+
+.range-title-help {
+ margin-left: 2ch;
+ width: 1ch;
+ padding: 0 0.25ch;
+ border: 1px dotted black;
+ color: slategray;
+ display: inline-block;
+}
+
+input.range-toggle-show {
+ vertical-align: middle;
+}
+
+.range-header-label-x {
+ text-align: center;
+ margin-left: 13ch;
+}
+
+.range-header-label-y {
+ width: 11ch;
+ float: left;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ margin-left: 6ch;
+ margin-top: 4ch;
+}
+
+.range-y-axis {
+ display: inline-block;
+ width: var(--range-y-axis-width);
+ overflow: hidden;
+ white-space: nowrap;
+ vertical-align: top;
+}
+
+.range-header {
+ display: flex;
+ overflow: hidden;
+ height: 8ch;
+ margin-left: var(--range-y-axis-width);
+}
+
+.range-position-labels,
+.range-register-labels {
+ background-color: lightgray;
+}
+
+.range-register-labels {
+ float: right;
+}
+
+.range-position-labels {
+ margin-top: auto;
+}
+
+.range-registers {
+ float: right;
+ overflow: hidden;
+ text-align: right;
+}
+
+.range-positions-header,
+.range-instruction-ids,
+.range-block-ids {
+ overflow: hidden;
+ white-space: nowrap;
+ display: grid;
+ grid-gap: 0;
+}
+
+.range-reg {
+ width: 13ch;
+ text-align: right;
+}
+
+.range-reg::after {
+ content: ":";
+}
+
+.range-grid {
+ overflow: auto;
+ display: inline-block;
+ white-space: nowrap;
+}
+
+.range-block-id {
+ display: inline-block;
+ text-align: center;
+}
+
+.range-instruction-id {
+ display: inline-block;
+ text-align: center;
+}
+
+.range-position {
+ display: inline-block;
+ text-align: center;
+ z-index: 1;
+}
+
+.range-transparent,
+.range-position.range-empty {
+ color: transparent;
+}
+
+.range-block-id:hover,
+.range-instruction-id:hover,
+.range-reg:hover,
+.range-position:hover {
+ background-color: rgba(0, 0, 255, 0.10);
+}
+
+.range-position.range-header-element {
+ border-bottom: 2px solid rgb(109, 107, 107);
+}
+
+.range-block-id,
+.range-instruction-id,
+.range-reg,
+.range-interval,
+.range-position {
+ position: relative;
+ border: var(--range-position-border) solid rgb(109, 107, 107);
+}
+
+.range-block-id,
+.range-instruction-id,
+.range-interval,
+.range-position {
+ border-left: 0;
+}
+
+.range-block-ids > .range-block-id:first-child,
+.range-instruction-ids > .range-instruction-id:first-child,
+.range-positions > .range-position:first-child {
+ border-left: var(--range-position-border) solid rgb(109, 107, 107);
+}
+
+.range-position.range-interval-position {
+ border: none;
+}
+
+.range-interval-text {
+ position: absolute;
+ padding-left: 0.5ch;
+ z-index: 2;
+ pointer-events: none
+}
+
+.range-position.range-use {
+ border-left: var(--range-instr-border) solid red;
+}
+
+.range-block-border,
+.range-block-border.range-position.range-interval-position:last-child {
+ border-right: var(--range-block-border) solid rgb(109, 107, 107);
+}
+
+.range-block-border.range-position.range-interval-position {
+ border-right: var(--range-block-border) solid transparent;
+}
+
+.range-instr-border,
+.range-instr-border.range-position.range-interval-position:last-child {
+ border-right: var(--range-instr-border) solid rgb(109, 107, 107);
+}
+
+.range-instr-border.range-position.range-interval-position {
+ border-right: var(--range-instr-border) solid transparent;
+}
+
+.range,
+.range-interval,
+.range-interval-wrapper,
+.range-positions {
+ white-space: nowrap;
+ display: inline-block;
+}
+
+.range-interval-wrapper,
+.range-positions {
+ display: grid;
+ grid-gap: 0;
+}
+
+.range-interval {
+ background-color: rgb(153, 158, 168);
+}
+
+.range-hidden {
+ display: none !important;
+}
+
+.range-positions-placeholder {
+ width: 100%;
+ border: var(--range-position-border) solid transparent;
+ color: transparent;
+} \ No newline at end of file
diff --git a/chromium/v8/tools/turbolizer/turbo-visualizer.css b/chromium/v8/tools/turbolizer/turbo-visualizer.css
index 6fb6da3b794..c7da769eb5d 100644
--- a/chromium/v8/tools/turbolizer/turbo-visualizer.css
+++ b/chromium/v8/tools/turbolizer/turbo-visualizer.css
@@ -342,6 +342,13 @@ input:hover,
background-color: #F8F8F8;
user-select: none;
flex: 1;
+ z-index: 7;
+}
+
+#middle.display-inline-flex,
+#middle.display-inline-flex #multiview,
+#middle.display-inline-flex #ranges {
+ display: inline-flex;
}
.viewpane {
@@ -351,11 +358,6 @@ input:hover,
flex-direction: column;
}
-.multiview {
- width: 100%;
-}
-
-
#show-hide-disassembly {
right: 0;
}
@@ -423,6 +425,10 @@ text {
dominant-baseline: text-before-edge;
}
+.tab-content {
+ z-index: 6;
+}
+
.resizer {
z-index: 10;
width: 10px;
@@ -595,6 +601,10 @@ text {
padding-right: .5ex;
}
+.instruction span {
+ padding-right: 0;
+}
+
.phi-label,
.instruction-id {
display: inline-block;
@@ -626,6 +636,10 @@ text {
display: inline-block;
}
+.phi span {
+ padding-right: 0;
+}
+
.gap .gap-move {
padding-left: .5ex;
padding-right: .5ex;
@@ -639,6 +653,10 @@ text {
content: ")";
}
+.virtual-reg {
+ outline: 1px dotted blue;
+}
+
.parameter.constant {
outline: 1px dotted red;
}
diff --git a/chromium/v8/tools/turbolizer/up-arrow.png b/chromium/v8/tools/turbolizer/up-arrow.png
new file mode 100644
index 00000000000..68cb14e80b3
--- /dev/null
+++ b/chromium/v8/tools/turbolizer/up-arrow.png
Binary files differ