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);