summaryrefslogtreecommitdiffstats
path: root/chromium/v8/tools/turbolizer/src/range-view.ts
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/v8/tools/turbolizer/src/range-view.ts')
-rw-r--r--chromium/v8/tools/turbolizer/src/range-view.ts938
1 files changed, 938 insertions, 0 deletions
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();
+ }
+}