From 22ec1bc787ef0ed0015493d7ebe5d6b5fb21eb03 Mon Sep 17 00:00:00 2001 From: George Wort Date: Fri, 16 Dec 2022 17:01:07 +0000 Subject: [PATCH] [turbolizer] Allow live range view to be narrowed by instructions Allow the user to specify the range of instructions that are shown on the live ranges grid so as to ease performance issues. Bug: v8:7327 Change-Id: I431e4464155427f59adf3a2229806c6f11c471be Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4110973 Commit-Queue: George Wort Reviewed-by: Nico Hartmann Cr-Commit-Position: refs/heads/main@{#85186} --- src/compiler/graph-visualizer.cc | 13 +- .../css/turbo-visualizer-ranges.css | 11 +- tools/turbolizer/css/turbo-visualizer.css | 11 +- tools/turbolizer/src/common/constants.ts | 4 + tools/turbolizer/src/phases/sequence-phase.ts | 53 +-- tools/turbolizer/src/views/range-view.ts | 450 ++++++++++++++---- tools/turbolizer/src/views/sequence-view.ts | 53 ++- tools/turbolizer/src/views/text-view.ts | 20 + 8 files changed, 454 insertions(+), 161 deletions(-) diff --git a/src/compiler/graph-visualizer.cc b/src/compiler/graph-visualizer.cc index 2cd220eb86..a4ee044515 100644 --- a/src/compiler/graph-visualizer.cc +++ b/src/compiler/graph-visualizer.cc @@ -1047,6 +1047,7 @@ std::ostream& operator<<( const TopLevelLiveRangeAsJSON& top_level_live_range_json) { int vreg = top_level_live_range_json.range_.vreg(); bool first = true; + int instruction_range[2] = {INT32_MAX, -1}; os << "\"" << (vreg > 0 ? vreg : -vreg) << "\":{ \"child_ranges\":["; for (const LiveRange* child = &(top_level_live_range_json.range_); child != nullptr; child = child->next()) { @@ -1057,6 +1058,15 @@ std::ostream& operator<<( os << ","; } os << LiveRangeAsJSON{*child, top_level_live_range_json.code_}; + // Record the minimum and maximum positions observed within this + // TopLevelLiveRange + for (const UseInterval* interval = child->first_interval(); + interval != nullptr; interval = interval->next()) { + if (interval->start().value() < instruction_range[0]) + instruction_range[0] = interval->start().value(); + if (interval->end().value() > instruction_range[1]) + instruction_range[1] = interval->end().value(); + } } } os << "]"; @@ -1065,7 +1075,8 @@ std::ostream& operator<<( << (top_level_live_range_json.range_.IsDeferredFixed() ? "true" : "false"); } - os << "}"; + os << ", \"instruction_range\": [" << instruction_range[0] << "," + << instruction_range[1] << "]}"; return os; } diff --git a/tools/turbolizer/css/turbo-visualizer-ranges.css b/tools/turbolizer/css/turbo-visualizer-ranges.css index 4103e7983c..f1ac7302dc 100644 --- a/tools/turbolizer/css/turbo-visualizer-ranges.css +++ b/tools/turbolizer/css/turbo-visualizer-ranges.css @@ -58,6 +58,10 @@ input.range-toggle-setting { vertical-align: middle; } +.range-toggle-form { + display: inline; +} + .range-header-label-x { text-align: center; margin-left: 13ch; @@ -181,11 +185,10 @@ input.range-toggle-setting { display: inline-block; text-align: center; z-index: 1; - color: transparent; } -#ranges.flipped .range-position { - color: inherit; +#ranges.not_flipped .range-position { + color: transparent; } .range-transparent, @@ -224,7 +227,7 @@ input.range-toggle-setting { display: grid; grid-gap: 0; writing-mode: vertical-lr; - grid-template-rows: repeat(3, calc(3.5ch + (2 * var(--range-position-border)))); + grid-template-rows: repeat(3, calc(5.5ch + (2 * var(--range-position-border)))); border-bottom: 2px solid white; } diff --git a/tools/turbolizer/css/turbo-visualizer.css b/tools/turbolizer/css/turbo-visualizer.css index 1ec293e0ee..353b180871 100644 --- a/tools/turbolizer/css/turbo-visualizer.css +++ b/tools/turbolizer/css/turbo-visualizer.css @@ -25,7 +25,8 @@ cursor: pointer; } -.search-input { +.search-input, +.instruction-range-input { vertical-align: middle; width: 145px; opacity: 1; @@ -33,6 +34,14 @@ height: 1.5em; } +.instruction-range-input { + width: 13ch; +} + +.instruction-range-submit { + margin-left: 1ch; +} + #phase-select { box-sizing: border-box; height: 1.5em; diff --git a/tools/turbolizer/src/common/constants.ts b/tools/turbolizer/src/common/constants.ts index 001ad91a37..bf0ae727da 100644 --- a/tools/turbolizer/src/common/constants.ts +++ b/tools/turbolizer/src/common/constants.ts @@ -33,6 +33,10 @@ export const ROW_GROUP_SIZE = 20; export const POSITIONS_PER_INSTRUCTION = 4; export const FIXED_REGISTER_LABEL_WIDTH = 6; export const FLIPPED_REGISTER_WIDTH_BUFFER = 5; +// Required due to the css grid-template-rows and grid-template-columns being limited +// to 1000 places. Regardless of this, a limit is required at some point due +// to performance issues. +export const MAX_NUM_POSITIONS = 999; export const SESSION_STORAGE_PREFIX = "ranges-setting-"; export const INTERVAL_TEXT_FOR_NONE = "none"; export const INTERVAL_TEXT_FOR_CONST = "const"; diff --git a/tools/turbolizer/src/phases/sequence-phase.ts b/tools/turbolizer/src/phases/sequence-phase.ts index 209404ca23..8674c98aa7 100644 --- a/tools/turbolizer/src/phases/sequence-phase.ts +++ b/tools/turbolizer/src/phases/sequence-phase.ts @@ -88,7 +88,7 @@ export class SequencePhase extends Phase { if (!rangesJSON) return null; const parsedRanges = new Array(); for (const [idx, range] of Object.entries(rangesJSON)) { - const newRange = new Range(range.isDeferred); + const newRange = new Range(range.isDeferred, range.instructionRange); for (const childRange of range.childRanges) { let operand: SequenceBlockOperand | string = null; if (childRange.op) { @@ -176,61 +176,16 @@ export class RegisterAllocation { this.fixedLiveRanges = new Array(); this.liveRanges = new Array(); } - - public forEachFixedRange(row: number, callback: (registerIndex: number, row: number, - registerName: string, - ranges: [Range, Range]) => void): number { - const forEachRangeInMap = (rangeMap: Array) => { - // 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(); - for (const [registerIndex, range] of rangeMap.entries()) { - if (!range) continue; - const registerName = range.fixedRegisterName(); - 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.isDeferred) { - entry.registerIndex = registerIndex; - } - } else { - fixedRegisterMap.set(registerName, {registerIndex, ranges: [range, undefined]}); - } - } - // Sort the registers by number. - const sortedMap = new Map([...fixedRegisterMap.entries()].sort(([nameA, _], [nameB, __]) => { - if (nameA.length > nameB.length) { - return 1; - } else if (nameA.length < nameB.length) { - return -1; - } else if (nameA > nameB) { - return 1; - } else if (nameA < nameB) { - return -1; - } - return 0; - })); - - for (const [registerName, {ranges, registerIndex}] of sortedMap) { - callback(-registerIndex - 1, row, registerName, ranges); - ++row; - } - }; - - forEachRangeInMap(this.fixedLiveRanges); - forEachRangeInMap(this.fixedDoubleLiveRanges); - - return row; - } } export class Range { isDeferred: boolean; + instructionRange: [number, number]; childRanges: Array; - constructor(isDeferred: boolean) { + constructor(isDeferred: boolean, instructionRange: [number, number]) { this.isDeferred = isDeferred; + this.instructionRange = instructionRange; this.childRanges = new Array(); } diff --git a/tools/turbolizer/src/views/range-view.ts b/tools/turbolizer/src/views/range-view.ts index ba390887e2..1a39cf9777 100644 --- a/tools/turbolizer/src/views/range-view.ts +++ b/tools/turbolizer/src/views/range-view.ts @@ -195,10 +195,12 @@ class UserSettings { // Store the required data from the blocks JSON. class BlocksData { + view: RangeView; blockBorders: Set; blockInstructionCountMap: Map; - constructor(blocks: Array) { + constructor(view: RangeView, blocks: Array) { + this.view = view; this.blockBorders = new Set(); this.blockInstructionCountMap = new Map(); for (const block of blocks) { @@ -212,9 +214,23 @@ class BlocksData { return ((position + 1) % C.POSITIONS_PER_INSTRUCTION) == 0; } - public isBlockBorder(position: number): boolean { + public isInstructionIdOnBlockBorder(instrId: number) { + return this.view.instructionRangeHandler.isLastInstruction(instrId) + || this.blockBorders.has(instrId); + } + + public isBlockBorder(position: number) { const border = Math.floor(position / C.POSITIONS_PER_INSTRUCTION); - return this.isInstructionBorder(position) && this.blockBorders.has(border); + return this.view.instructionRangeHandler.isLastPosition(position) + || (this.isInstructionBorder(position) && this.blockBorders.has(border)); + } + + public isIndexInstructionBorder(index: number) { + return this.isInstructionBorder(this.view.instructionRangeHandler.getPositionFromIndex(index)); + } + + public isIndexBlockBorder(index: number) { + return this.isBlockBorder(this.view.instructionRangeHandler.getPositionFromIndex(index)); } } @@ -240,13 +256,14 @@ class Divs { yAxis: HTMLElement; grid: HTMLElement; - constructor(userSettings: UserSettings) { + constructor(userSettings: UserSettings, instructionRangeString: string) { this.container = document.getElementById(C.RANGES_PANE_ID); this.resizerBar = document.getElementById(C.RESIZER_RANGES_ID); this.snapper = document.getElementById(C.SHOW_HIDE_RANGES_ID); this.content = document.createElement("div"); - this.content.appendChild(this.elementForTitle(userSettings)); + this.content.id = "ranges-content"; + this.content.appendChild(this.elementForTitle(userSettings, instructionRangeString)); this.showOnLoad = document.createElement("div"); this.showOnLoad.style.visibility = "hidden"; @@ -268,10 +285,10 @@ class Divs { this.registerHeaders.appendChild(this.registers); } - public elementForTitle(userSettings: UserSettings): HTMLElement { + public elementForTitle(userSettings: UserSettings, instructionRangeString: string): HTMLElement { const titleEl = createElement("div", "range-title-div"); const titleBar = createElement("div", "range-title"); - titleBar.appendChild(createElement("div", "", "Live Ranges")); + titleBar.appendChild(createElement("div", "", "Live Ranges for " + instructionRangeString)); 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." @@ -310,23 +327,25 @@ class RowConstructor { // easily combined into a single row. construct(grid: Grid, row: number, registerIndex: number, ranges: [Range, Range], getElementForEmptyPosition: (position: number) => HTMLElement, - callbackForInterval: (position: number, interval: HTMLElement) => void): void { - const positions = new Array(this.view.numPositions); + callbackForInterval: (position: number, interval: HTMLElement) => void): boolean { // 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 (intervalMap.size == 0) return false; + const positions = new Array(this.view.instructionRangeHandler.numPositions); + for (let column = 0; column < this.view.instructionRangeHandler.numPositions; ++column) { + const interval = intervalMap.get(column); if (interval === undefined) { - positions[position] = getElementForEmptyPosition(position); + positions[column] = getElementForEmptyPosition(column); } else { - callbackForInterval(position, interval); + callbackForInterval(column, interval); this.view.intervalsAccessor.addInterval(interval); const intervalPositionElements = this.view.getPositionElementsFromInterval(interval); for (let j = 0; j < intervalPositionElements.length; ++j) { - // Point positionsArray to the new elements. - positions[position + j] = (intervalPositionElements[j] as HTMLElement); + const intervalColumn = column + j; + // Point positions to the new elements. + positions[intervalColumn] = (intervalPositionElements[j] as HTMLElement); } - position += intervalPositionElements.length - 1; + column += intervalPositionElements.length - 1; } } @@ -336,6 +355,7 @@ class RowConstructor { if (!range) continue; this.setUses(grid, row, range); } + return true; } // This is the main function used to build new intervals. @@ -348,7 +368,12 @@ class RowConstructor { for (const childRange of range.childRanges) { const tooltip = childRange.getTooltip(registerIndex); for (const [index, intervalNums] of childRange.intervals.entries()) { - const interval = new Interval(intervalNums); + let interval = new Interval(intervalNums); + if (!this.view.instructionRangeHandler.showAllPositions) { + if (!this.view.instructionRangeHandler.isIntervalInRange(interval)) continue; + interval = + this.view.instructionRangeHandler.convertIntervalPositionsToIndexes(interval); + } const intervalEl = this.elementForInterval(childRange, interval, tooltip, index, range.isDeferred); intervalMap.set(interval.start, intervalEl); @@ -377,9 +402,9 @@ class RowConstructor { 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" : ""); - + (this.view.blocksData.isIndexBlockBorder(i) + ? " range-block-border" + : this.view.blocksData.isIndexInstructionBorder(i) ? " range-instr-border" : ""); const positionEl = createElement("div", classes, "_"); positionEl.style.gridColumn = String(i - interval.start + 1); intervalInnerWrapper.appendChild(positionEl); @@ -408,13 +433,17 @@ class RowConstructor { // Allows a cleaner display of the interval text when displayed vertically. const spanElBehind = createElement("span", "range-interval-text range-interval-text-behind"); this.view.stringConstructor.setIntervalString(spanEl, spanElBehind, tooltip, numCells); - return {main: spanEl, behind: spanElBehind}; + return { main: spanEl, behind: spanElBehind}; } private setUses(grid: Grid, row: number, range: Range): void { for (const liveRange of range.childRanges) { if (!liveRange.uses) continue; - for (const use of liveRange.uses) { + for (let use of liveRange.uses) { + if (!this.view.instructionRangeHandler.showAllPositions) { + if (!this.view.instructionRangeHandler.isPositionInRange(use)) continue; + use = this.view.instructionRangeHandler.getIndexFromPosition(use); + } grid.getCell(row, use).classList.toggle("range-use", true); } } @@ -613,30 +642,32 @@ class RangeViewConstructor { } private addVirtualRanges(row: number): number { - const source = this.view.sequenceView.sequence.registerAllocation; - for (const [registerIndex, range] of source.liveRanges.entries()) { - if (!range) continue; - const registerName = this.virtualRegisterName(registerIndex); - const registerEl = this.elementForRegister(row, registerName, true); - this.addRowToGroup(row, this.elementForRow(row, registerIndex, [range, undefined])); - this.view.divs.registers.appendChild(registerEl); - ++(this.registerTypeHeaderData.virtualCount); - ++row; - } - return row; - } - - private virtualRegisterName(registerIndex: number): string { - return `v${registerIndex}`; + return this.view.instructionRangeHandler.forEachLiveRange(row, + (registerIndex: number, row: number, registerName: string, range: Range) => { + const rowEl = this.elementForRow(row, registerIndex, [range, undefined]); + if (rowEl) { + const registerEl = this.elementForRegister(row, registerName, true); + this.addRowToGroup(row, rowEl); + this.view.divs.registers.appendChild(registerEl); + ++(this.registerTypeHeaderData.virtualCount); + return true; + } + return false; + }); } private addFixedRanges(row: number): void { - row = this.view.sequenceView.sequence.registerAllocation.forEachFixedRange(row, + row = this.view.instructionRangeHandler.forEachFixedRange(row, (registerIndex: number, row: number, registerName: string, ranges: [Range, Range]) => { - this.registerTypeHeaderData.countFixedRegister(registerName, ranges); - const registerEl = this.elementForRegister(row, registerName, false); - this.addRowToGroup(row, this.elementForRow(row, registerIndex, ranges)); - this.view.divs.registers.appendChild(registerEl); + const rowEl = this.elementForRow(row, registerIndex, ranges); + if (rowEl) { + this.registerTypeHeaderData.countFixedRegister(registerName, ranges); + const registerEl = this.elementForRegister(row, registerName, false); + this.addRowToGroup(row, rowEl); + this.view.divs.registers.appendChild(registerEl); + return true; + } + return false; }); if (row % C.ROW_GROUP_SIZE != 0) { @@ -650,14 +681,15 @@ class RangeViewConstructor { private elementForRow(row: number, registerIndex: number, ranges: [Range, Range]): HTMLElement { const rowEl = createElement("div", "range-positions"); - const getElementForEmptyPosition = (position: number) => { + const getElementForEmptyPosition = (column: number) => { + const position = this.view.instructionRangeHandler.getPositionFromIndex(column); 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 = String(position + 1); + positionEl.style.gridColumn = String(column + 1); rowEl.appendChild(positionEl); return positionEl; }; @@ -666,10 +698,12 @@ class RangeViewConstructor { rowEl.appendChild(interval); }; - this.view.rowConstructor.construct(this.grid, row, registerIndex, ranges, - getElementForEmptyPosition, callbackForInterval); - - return rowEl; + // Only construct the row if it has any intervals. + if (this.view.rowConstructor.construct(this.grid, row, registerIndex, ranges, + getElementForEmptyPosition, callbackForInterval)) { + return rowEl; + } + return undefined; } private elementForRegister(row: number, registerName: string, isVirtual: boolean) { @@ -735,29 +769,42 @@ class RangeViewConstructor { private elementForBlockHeader(): HTMLElement { const headerEl = createElement("div", "range-block-ids"); - let blockIndex = 0; - for (let i = 0; i < this.view.sequenceView.numInstructions;) { - const instrCount = this.view.blocksData.blockInstructionCountMap.get(blockIndex); - headerEl.appendChild(this.elementForBlockIndex(blockIndex, i, instrCount)); - ++blockIndex; - i += instrCount; + let blockId = 0; + const lastPos = this.view.instructionRangeHandler.getLastPosition(); + for (let position = 0; position <= lastPos;) { + const instrCount = this.view.blocksData.blockInstructionCountMap.get(blockId); + if (this.view.instructionRangeHandler.showAllPositions) { + headerEl.appendChild(this.elementForBlock(blockId, position, instrCount)); + } else { + let blockInterval = + new Interval([position, position + (C.POSITIONS_PER_INSTRUCTION * instrCount)]); + if (this.view.instructionRangeHandler.isIntervalInRange(blockInterval)) { + blockInterval = this.view.instructionRangeHandler + .convertBlockPositionsToIndexes(blockId, blockInterval); + headerEl.appendChild(this.elementForBlock(blockId, blockInterval.start, + (blockInterval.end - blockInterval.start) / C.POSITIONS_PER_INSTRUCTION)); + } + } + ++blockId; + position += instrCount * C.POSITIONS_PER_INSTRUCTION; } return headerEl; } - private elementForBlockIndex(index: number, firstInstruction: number, instrCount: number): + private elementForBlock(blockId: number, firstColumn: number, instrCount: number): HTMLElement { const element = createElement("div", "range-block-id range-header-element range-block-border"); - const str = `B${index}`; + const str = `B${blockId}`; const idEl = createElement("span", "range-block-id-number", str); const centre = instrCount * C.POSITIONS_PER_INSTRUCTION; idEl.style.gridRow = `${centre} / ${centre + 1}`; element.appendChild(idEl); element.setAttribute("title", str); element.dataset.instrCount = String(instrCount); - const firstGridCol = (firstInstruction * C.POSITIONS_PER_INSTRUCTION) + 1; + // gridColumns start at 1 rather than 0. + const firstGridCol = firstColumn + 1; const lastGridCol = firstGridCol + (instrCount * C.POSITIONS_PER_INSTRUCTION); element.style.gridColumn = `${firstGridCol} / ${lastGridCol}`; element.style.gridTemplateRows = `repeat(${8 * instrCount}, @@ -768,23 +815,24 @@ class RangeViewConstructor { private elementForInstructionHeader(): HTMLElement { const headerEl = createElement("div", "range-instruction-ids"); - - for (let i = 0; i < this.view.sequenceView.numInstructions; ++i) { - headerEl.appendChild(this.elementForInstructionIndex(i)); + let instrId = this.view.instructionRangeHandler.getInstructionIdFromIndex(0); + const instrLimit = instrId + this.view.instructionRangeHandler.numInstructions; + for (; instrId < instrLimit; ++instrId) { + headerEl.appendChild(this.elementForInstruction(instrId)); } return headerEl; } - private elementForInstructionIndex(index: number): HTMLElement { - const isBlockBorder = this.view.blocksData.blockBorders.has(index); + private elementForInstruction(instrId: number): HTMLElement { + const isBlockBorder = this.view.blocksData.isInstructionIdOnBlockBorder(instrId); const classes = "range-instruction-id range-header-element " + (isBlockBorder ? "range-block-border" : "range-instr-border"); - const element = createElement("div", classes); - element.appendChild(createElement("span", "range-instruction-id-number", String(index))); - element.setAttribute("title", String(index)); - const firstGridCol = (index * C.POSITIONS_PER_INSTRUCTION) + 1; + element.appendChild(createElement("span", "range-instruction-id-number", String(instrId))); + element.setAttribute("title", String(instrId)); + const instrIndex = this.view.instructionRangeHandler.getInstructionIndex(instrId); + const firstGridCol = (instrIndex * C.POSITIONS_PER_INSTRUCTION) + 1; element.style.gridColumn = `${firstGridCol} / ${(firstGridCol + C.POSITIONS_PER_INSTRUCTION)}`; return element; } @@ -792,22 +840,24 @@ class RangeViewConstructor { private elementForPositionHeader(): HTMLElement { const headerEl = createElement("div", "range-positions range-positions-header"); - for (let i = 0; i < this.view.numPositions; ++i) { - headerEl.appendChild(this.elementForPositionIndex(i)); + let position = this.view.instructionRangeHandler.getPositionFromIndex(0); + const lastPos = this.view.instructionRangeHandler.getLastPosition(); + for (; position <= lastPos; ++position) { + const isBlockBorder = this.view.blocksData.isBlockBorder(position); + headerEl.appendChild(this.elementForPosition(position, isBlockBorder)); } return headerEl; } - private elementForPositionIndex(index: number): HTMLElement { - const isBlockBorder = this.view.blocksData.isBlockBorder(index); + private elementForPosition(position: number, isBlockBorder: boolean): HTMLElement { const classes = "range-position range-header-element " + (isBlockBorder ? "range-block-border" - : this.view.blocksData.isInstructionBorder(index) ? "range-instr-border" + : this.view.blocksData.isInstructionBorder(position) ? "range-instr-border" : "range-position-border"); - const element = createElement("div", classes, String(index)); - element.setAttribute("title", String(index)); + const element = createElement("div", classes, String(position)); + element.setAttribute("title", String(position)); return element; } @@ -850,18 +900,18 @@ class PhaseChangeHandler { const currentGrid = this.view.gridAccessor.getAnyGrid(); const newGrid = new Grid(); this.view.gridAccessor.addGrid(newGrid); - const source = this.view.sequenceView.sequence.registerAllocation; let row = 0; - for (const [registerIndex, range] of source.liveRanges.entries()) { - if (!range) continue; + row = this.view.instructionRangeHandler.forEachLiveRange(row, (registerIndex: number, + row: number, _: string, range: Range) => { this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, [range, undefined]); - ++row; - } + return true; + }); - this.view.sequenceView.sequence.registerAllocation.forEachFixedRange(row, + this.view.instructionRangeHandler.forEachFixedRange(row, (registerIndex, row, _, ranges) => { this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, ranges); + return true; }); } @@ -869,14 +919,14 @@ class PhaseChangeHandler { registerIndex: number, ranges: [Range, Range]): void { const numReplacements = new Map(); - const getElementForEmptyPosition = (position: number) => { - return currentGrid.getCell(row, position); + const getElementForEmptyPosition = (column: number) => { + return currentGrid.getCell(row, column); }; // Inserts new interval beside existing intervals. - const callbackForInterval = (position: number, interval: HTMLElement) => { + const callbackForInterval = (column: number, interval: HTMLElement) => { // Overlapping intervals are placed beside each other and the relevant ones displayed. - let currentInterval = currentGrid.getInterval(row, position); + let currentInterval = currentGrid.getInterval(row, column); // The number of intervals already inserted is tracked so that the inserted intervals // are ordered correctly. const intervalsAlreadyInserted = numReplacements.get(currentInterval); @@ -898,6 +948,210 @@ class PhaseChangeHandler { } } +// Manages the limitation of how many instructions are shown in the grid. +class InstructionRangeHandler { + view: RangeView; + + numPositions: number; + numInstructions: number; + showAllPositions: boolean; + + private positionRange: [number, number]; + private instructionRange: [number, number]; + private blockRange: [number, number]; + + constructor(view: RangeView, firstInstr: number, lastInstr: number) { + this.view = view; + this.showAllPositions = false; + this.blockRange = [0, -1]; + this.instructionRange = this.getValidRange(firstInstr, lastInstr); + if (this.instructionRange[0] == 0 + && this.instructionRange[1] == this.view.sequenceView.numInstructions) { + this.showAllPositions = true; + } + this.updateInstructionRange(); + } + + public isNewRangeViewRequired(firstInstr: number, lastInstr: number): boolean { + const validRange = this.getValidRange(firstInstr, lastInstr); + return (this.instructionRange[0] != validRange[0]) + || (this.instructionRange[1] != validRange[1]); + } + + public getValidRange(firstInstr: number, lastInstr: number): [number, number] { + const maxInstructions = Math.floor(C.MAX_NUM_POSITIONS / C.POSITIONS_PER_INSTRUCTION); + const validRange = [firstInstr, lastInstr + 1] as [number, number]; + if (isNaN(lastInstr)) { + validRange[1] = this.view.sequenceView.numInstructions; + } + if (isNaN(firstInstr)) { + validRange[0] = (isNaN(lastInstr) || validRange[1] < maxInstructions) + ? 0 : validRange[1] - maxInstructions; + } + if (!this.isValidRange(validRange[0], validRange[1])) { + console.warn("Invalid range: displaying default view."); + validRange[0] = 0; + validRange[1] = this.view.sequenceView.numInstructions; + } + const rangeLength = validRange[1] - validRange[0]; + if (C.POSITIONS_PER_INSTRUCTION * rangeLength > C.MAX_NUM_POSITIONS) { + validRange[1] = validRange[0] + maxInstructions; + console.warn("Cannot display more than " + maxInstructions + + " instructions in the live ranges grid at one time."); + } + return validRange; + } + + public isValidRange(firstInstr: number, instrLimit: number): boolean { + return 0 <= firstInstr && firstInstr < instrLimit + && instrLimit <= this.view.sequenceView.numInstructions; + } + + public updateInstructionRange(): void { + this.numInstructions = this.showAllPositions + ? this.view.sequenceView.numInstructions + : this.instructionRange[1] - this.instructionRange[0]; + this.numPositions = this.numInstructions * C.POSITIONS_PER_INSTRUCTION; + this.positionRange = [C.POSITIONS_PER_INSTRUCTION * this.instructionRange[0], + C.POSITIONS_PER_INSTRUCTION * this.instructionRange[1]]; + } + + public getInstructionRangeString(): string { + if (this.showAllPositions) { + return "all instructions"; + } else { + return "instructions [" + this.instructionRange[0] + + ", " + (this.instructionRange[1] - 1) + "]"; + } + } + + public getLastPosition(): number { + return this.positionRange[1] - 1; + } + + public getPositionFromIndex(index: number): number { + return index + this.positionRange[0]; + } + + public getIndexFromPosition(position: number): number { + return position - this.positionRange[0]; + } + + public getInstructionIdFromIndex(index: number): number { + return index + this.instructionRange[0]; + } + + public getInstructionIndex(id: number): number { + return id - this.instructionRange[0]; + } + + public getBlockIdFromIndex(index: number): number { + return index + this.blockRange[0]; + } + + public getBlockIndex(id: number): number { + return id - this.blockRange[0]; + } + + public isPositionInRange(position: number): boolean { + return position >= this.positionRange[0] && position < this.positionRange[1]; + } + + public isIntervalInRange(interval: Interval): boolean { + return interval.start < this.positionRange[1] && interval.end > this.positionRange[0]; + } + + public convertIntervalPositionsToIndexes(interval: Interval): Interval { + return new Interval([Math.max(0, interval.start - this.positionRange[0]), + Math.min(this.numPositions, interval.end - this.positionRange[0])]); + } + + public convertBlockPositionsToIndexes(blockIndex: number, interval: Interval): Interval { + if (this.blockRange[1] < 0) this.blockRange[0] = blockIndex; + this.blockRange[1] = blockIndex + 1; + return this.convertIntervalPositionsToIndexes(interval); + } + + public isLastPosition(position: number): boolean { + return !this.showAllPositions && (position == this.getLastPosition()); + } + + public isLastInstruction(instrId: number): boolean { + return !this.showAllPositions && (instrId == this.instructionRange[1] - 1); + } + + public forEachLiveRange(row: number, callback: (registerIndex: number, row: number, + registerName: string, range: Range) => boolean): number { + const source = this.view.sequenceView.sequence.registerAllocation; + for (const [registerIndex, range] of source.liveRanges.entries()) { + if (!range || + (!this.showAllPositions && + (range.instructionRange[0] >= this.positionRange[1] + || this.positionRange[0] >= range.instructionRange[1]))) { + continue; + } + if (callback(registerIndex, row, `v${registerIndex}`, range)) { + ++row; + } + } + return row; + } + + public forEachFixedRange(row: number, callback: (registerIndex: number, row: number, + registerName: string, + ranges: [Range, Range]) => boolean): number { + const forEachRangeInMap = (rangeMap: Array) => { + // 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(); + for (const [registerIndex, range] of rangeMap.entries()) { + if (!range || + (!this.showAllPositions && + (range.instructionRange[0] >= this.positionRange[1] + || this.positionRange[0] >= range.instructionRange[1]))) { + continue; + } + const registerName = range.fixedRegisterName(); + 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.isDeferred) { + entry.registerIndex = registerIndex; + } + } else { + fixedRegisterMap.set(registerName, {registerIndex, ranges: [range, undefined]}); + } + } + // Sort the registers by number. + const sortedMap = new Map([...fixedRegisterMap.entries()].sort(([nameA, _], [nameB, __]) => { + if (nameA.length > nameB.length) { + return 1; + } else if (nameA.length < nameB.length) { + return -1; + } else if (nameA > nameB) { + return 1; + } else if (nameA < nameB) { + return -1; + } + return 0; + })); + + for (const [registerName, {ranges, registerIndex}] of sortedMap) { + if (callback(-registerIndex - 1, row, registerName, ranges)) { + ++row; + } + } + }; + + const source = this.view.sequenceView.sequence.registerAllocation; + forEachRangeInMap(source.fixedLiveRanges); + forEachRangeInMap(source.fixedDoubleLiveRanges); + + return row; + } +} + class DisplayResetter { view: RangeView; isFlipped: boolean; @@ -1135,18 +1389,19 @@ export class RangeView { divs: Divs; scrollHandler: ScrollHandler; phaseChangeHandler: PhaseChangeHandler; + instructionRangeHandler: InstructionRangeHandler; displayResetter: DisplayResetter; rowConstructor: RowConstructor; stringConstructor: StringConstructor; initialized: boolean; isShown: boolean; - numPositions: number; maxLengthVirtualRegisterNumber: number; - constructor(sequence: SequenceView) { + constructor(sequence: SequenceView, firstInstr: number, lastInstr: number) { this.sequenceView = sequence; this.initialized = false; this.isShown = false; + this.instructionRangeHandler = new InstructionRangeHandler(this, firstInstr, lastInstr); } public initializeContent(blocks: Array): void { @@ -1162,16 +1417,17 @@ export class RangeView { // Indicates whether the grid axes are switched. this.userSettings.addSetting("flipped", false, this.displayResetter.resetFlipped.bind(this.displayResetter)); - this.blocksData = new BlocksData(blocks); - this.divs = new Divs(this.userSettings); + this.blocksData = new BlocksData(this, blocks); + this.divs = new Divs(this.userSettings, + this.instructionRangeHandler.getInstructionRangeString()); this.displayResetter.updateClassesOnContainer(); this.scrollHandler = new ScrollHandler(this); - this.numPositions = this.sequenceView.numInstructions * C.POSITIONS_PER_INSTRUCTION; this.rowConstructor = new RowConstructor(this); this.stringConstructor = new StringConstructor(this); const constructor = new RangeViewConstructor(this); constructor.construct(); - this.cssVariables.setVariables(this.numPositions, this.divs.registers.children.length); + this.cssVariables.setVariables(this.instructionRangeHandler.numPositions, + this.divs.registers.children.length); this.phaseChangeHandler = new PhaseChangeHandler(this); let maxVirtualRegisterNumber = 0; for (const register of this.divs.registers.children) { @@ -1199,12 +1455,14 @@ export class RangeView { // panel is shown. window.dispatchEvent(new Event("resize")); - setTimeout(() => { - this.userSettings.resetFromSessionStorage(); - this.scrollHandler.restoreScroll(); - this.scrollHandler.syncHidden(); - this.divs.showOnLoad.style.visibility = "visible"; - }, 100); + if (this.divs.registers.children.length) { + setTimeout(() => { + this.userSettings.resetFromSessionStorage(); + this.scrollHandler.restoreScroll(); + this.scrollHandler.syncHidden(); + this.divs.showOnLoad.style.visibility = "visible"; + }, 100); + } } } @@ -1225,7 +1483,7 @@ export class RangeView { } public onresize(): void { - if (this.isShown) this.scrollHandler.syncHidden(); + if (this.divs.registers.children.length && this.isShown) this.scrollHandler.syncHidden(); } public getPositionElementsFromInterval(interval: HTMLElement): HTMLCollection { diff --git a/tools/turbolizer/src/views/sequence-view.ts b/tools/turbolizer/src/views/sequence-view.ts index 6ea5aad03e..4c201aa13f 100644 --- a/tools/turbolizer/src/views/sequence-view.ts +++ b/tools/turbolizer/src/views/sequence-view.ts @@ -26,6 +26,8 @@ export class SequenceView extends TextView { showRangeView: boolean; phaseSelectEl: HTMLSelectElement; toggleRangeViewEl: HTMLElement; + firstInstrInput: HTMLInputElement; + lastInstrInput: HTMLInputElement; constructor(parent: HTMLElement, broker: SelectionBroker) { super(parent, broker); @@ -307,19 +309,12 @@ export class SequenceView extends TextView { private addRangeView(): void { if (this.sequence.registerAllocation) { if (!this.rangeView) { - this.rangeView = new RangeView(this); + this.rangeView = new RangeView(this, parseInt(this.firstInstrInput.value, 10), + parseInt(this.lastInstrInput.value, 10)); } const source = this.sequence.registerAllocation; if (source.fixedLiveRanges.length == 0 && source.liveRanges.length == 0) { this.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. - this.preventRangeView( - "Live range display is only supported for sequences with less than 249 instructions" - ); } if (this.showRangeView) { this.rangeView.initializeContent(this.sequence.blocks); @@ -362,8 +357,37 @@ export class SequenceView extends TextView { }; } + private elementForRangeViewInputElement(form: HTMLElement, text: string): HTMLInputElement { + const instrInputEl = createElement("input", "instruction-range-input") as HTMLInputElement; + instrInputEl.type = "text"; + instrInputEl.title = text; + instrInputEl.placeholder = text; + instrInputEl.alt = text; + form.appendChild(instrInputEl); + return instrInputEl; + } + private elementForToggleRangeView(): HTMLElement { - const toggleRangeViewEl = createElement("label", "", "show live ranges"); + const toggleRangeViewEl = createElement("label", "", "show live ranges from "); + + const form = createElement("form", "range-toggle-form"); + toggleRangeViewEl.appendChild(form); + + this.firstInstrInput = this.elementForRangeViewInputElement(form, "first instruction"); + form.appendChild(createElement("span", "", " to ")); + this.lastInstrInput = this.elementForRangeViewInputElement(form, "last instruction"); + + const submit = createElement("input", "instruction-range-submit") as HTMLInputElement; + submit.type = "submit"; + submit.value = "Refresh Ranges"; + submit.onclick = (e: MouseEvent) => { + e.preventDefault(); + // Single click if not shown, double click to refresh if shown. + this.toggleRangeViewEl.click(); + if (!this.showRangeView) this.toggleRangeViewEl.click(); + }; + form.appendChild(submit); + const toggleRangesInput = createElement("input", "range-toggle-show") as HTMLInputElement; toggleRangesInput.setAttribute("type", "checkbox"); toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput); @@ -375,6 +399,15 @@ export class SequenceView extends TextView { toggleRangesInput.disabled = true; this.showRangeView = toggleRangesInput.checked; if (this.showRangeView) { + const firstInstr = parseInt(this.firstInstrInput.value, 10); + const lastInstr = parseInt(this.lastInstrInput.value, 10); + if (this.rangeView.instructionRangeHandler.isNewRangeViewRequired(firstInstr, lastInstr)) { + // Remove current RangeView's selection nodes and blocks from SelectionHandlers. + this.removeHtmlElementFromAllMapsIf((e: HTMLElement) => + e.closest("#ranges-content") != null); + this.rangeView = new RangeView(this, firstInstr, lastInstr); + this.addRangeView(); + } this.rangeView.initializeContent(this.sequence.blocks); this.rangeView.show(); } else { diff --git a/tools/turbolizer/src/views/text-view.ts b/tools/turbolizer/src/views/text-view.ts index 428abd85cd..3dfe4e7811 100644 --- a/tools/turbolizer/src/views/text-view.ts +++ b/tools/turbolizer/src/views/text-view.ts @@ -161,6 +161,26 @@ export abstract class TextView extends PhaseView { public onresize(): void {} + private removeHtmlElementFromMapIf(condition: (e: HTMLElement) => boolean, + map: Map>): void { + for (const [nodeId, elements] of map) { + let i = elements.length; + while (i--) { + if (condition(elements[i])) { + elements.splice(i, 1); + } + } + if (elements.length == 0) { + map.delete(nodeId); + } + } + } + + public removeHtmlElementFromAllMapsIf(condition: (e: HTMLElement) => boolean): void { + this.removeHtmlElementFromMapIf(condition, this.nodeIdToHtmlElementsMap); + this.removeHtmlElementFromMapIf(condition, this.blockIdToHtmlElementsMap); + } + // instruction-id are the divs for the register allocator phase protected addHtmlElementForInstructionId(anyInstructionId: any, htmlElement: HTMLElement): void { const instructionId = String(anyInstructionId);