[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 <george.wort@arm.com>
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85186}
This commit is contained in:
George Wort 2022-12-16 17:01:07 +00:00 committed by V8 LUCI CQ
parent 40f3d61836
commit 22ec1bc787
8 changed files with 454 additions and 161 deletions

View File

@ -1047,6 +1047,7 @@ std::ostream& operator<<(
const TopLevelLiveRangeAsJSON& top_level_live_range_json) { const TopLevelLiveRangeAsJSON& top_level_live_range_json) {
int vreg = top_level_live_range_json.range_.vreg(); int vreg = top_level_live_range_json.range_.vreg();
bool first = true; bool first = true;
int instruction_range[2] = {INT32_MAX, -1};
os << "\"" << (vreg > 0 ? vreg : -vreg) << "\":{ \"child_ranges\":["; os << "\"" << (vreg > 0 ? vreg : -vreg) << "\":{ \"child_ranges\":[";
for (const LiveRange* child = &(top_level_live_range_json.range_); for (const LiveRange* child = &(top_level_live_range_json.range_);
child != nullptr; child = child->next()) { child != nullptr; child = child->next()) {
@ -1057,6 +1058,15 @@ std::ostream& operator<<(
os << ","; os << ",";
} }
os << LiveRangeAsJSON{*child, top_level_live_range_json.code_}; 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 << "]"; os << "]";
@ -1065,7 +1075,8 @@ std::ostream& operator<<(
<< (top_level_live_range_json.range_.IsDeferredFixed() ? "true" << (top_level_live_range_json.range_.IsDeferredFixed() ? "true"
: "false"); : "false");
} }
os << "}"; os << ", \"instruction_range\": [" << instruction_range[0] << ","
<< instruction_range[1] << "]}";
return os; return os;
} }

View File

@ -58,6 +58,10 @@ input.range-toggle-setting {
vertical-align: middle; vertical-align: middle;
} }
.range-toggle-form {
display: inline;
}
.range-header-label-x { .range-header-label-x {
text-align: center; text-align: center;
margin-left: 13ch; margin-left: 13ch;
@ -181,11 +185,10 @@ input.range-toggle-setting {
display: inline-block; display: inline-block;
text-align: center; text-align: center;
z-index: 1; z-index: 1;
color: transparent;
} }
#ranges.flipped .range-position { #ranges.not_flipped .range-position {
color: inherit; color: transparent;
} }
.range-transparent, .range-transparent,
@ -224,7 +227,7 @@ input.range-toggle-setting {
display: grid; display: grid;
grid-gap: 0; grid-gap: 0;
writing-mode: vertical-lr; 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; border-bottom: 2px solid white;
} }

View File

@ -25,7 +25,8 @@
cursor: pointer; cursor: pointer;
} }
.search-input { .search-input,
.instruction-range-input {
vertical-align: middle; vertical-align: middle;
width: 145px; width: 145px;
opacity: 1; opacity: 1;
@ -33,6 +34,14 @@
height: 1.5em; height: 1.5em;
} }
.instruction-range-input {
width: 13ch;
}
.instruction-range-submit {
margin-left: 1ch;
}
#phase-select { #phase-select {
box-sizing: border-box; box-sizing: border-box;
height: 1.5em; height: 1.5em;

View File

@ -33,6 +33,10 @@ export const ROW_GROUP_SIZE = 20;
export const POSITIONS_PER_INSTRUCTION = 4; export const POSITIONS_PER_INSTRUCTION = 4;
export const FIXED_REGISTER_LABEL_WIDTH = 6; export const FIXED_REGISTER_LABEL_WIDTH = 6;
export const FLIPPED_REGISTER_WIDTH_BUFFER = 5; 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 SESSION_STORAGE_PREFIX = "ranges-setting-";
export const INTERVAL_TEXT_FOR_NONE = "none"; export const INTERVAL_TEXT_FOR_NONE = "none";
export const INTERVAL_TEXT_FOR_CONST = "const"; export const INTERVAL_TEXT_FOR_CONST = "const";

View File

@ -88,7 +88,7 @@ export class SequencePhase extends Phase {
if (!rangesJSON) return null; if (!rangesJSON) return null;
const parsedRanges = new Array<Range>(); const parsedRanges = new Array<Range>();
for (const [idx, range] of Object.entries<Range>(rangesJSON)) { for (const [idx, range] of Object.entries<Range>(rangesJSON)) {
const newRange = new Range(range.isDeferred); const newRange = new Range(range.isDeferred, range.instructionRange);
for (const childRange of range.childRanges) { for (const childRange of range.childRanges) {
let operand: SequenceBlockOperand | string = null; let operand: SequenceBlockOperand | string = null;
if (childRange.op) { if (childRange.op) {
@ -176,61 +176,16 @@ export class RegisterAllocation {
this.fixedLiveRanges = new Array<Range>(); this.fixedLiveRanges = new Array<Range>();
this.liveRanges = new Array<Range>(); this.liveRanges = new Array<Range>();
} }
public forEachFixedRange(row: number, callback: (registerIndex: number, row: number,
registerName: string,
ranges: [Range, Range]) => void): number {
const forEachRangeInMap = (rangeMap: Array<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, {registerIndex: number, ranges: [Range, Range]}>();
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 { export class Range {
isDeferred: boolean; isDeferred: boolean;
instructionRange: [number, number];
childRanges: Array<ChildRange>; childRanges: Array<ChildRange>;
constructor(isDeferred: boolean) { constructor(isDeferred: boolean, instructionRange: [number, number]) {
this.isDeferred = isDeferred; this.isDeferred = isDeferred;
this.instructionRange = instructionRange;
this.childRanges = new Array<ChildRange>(); this.childRanges = new Array<ChildRange>();
} }

View File

@ -195,10 +195,12 @@ class UserSettings {
// Store the required data from the blocks JSON. // Store the required data from the blocks JSON.
class BlocksData { class BlocksData {
view: RangeView;
blockBorders: Set<number>; blockBorders: Set<number>;
blockInstructionCountMap: Map<number, number>; blockInstructionCountMap: Map<number, number>;
constructor(blocks: Array<SequenceBlock>) { constructor(view: RangeView, blocks: Array<SequenceBlock>) {
this.view = view;
this.blockBorders = new Set<number>(); this.blockBorders = new Set<number>();
this.blockInstructionCountMap = new Map<number, number>(); this.blockInstructionCountMap = new Map<number, number>();
for (const block of blocks) { for (const block of blocks) {
@ -212,9 +214,23 @@ class BlocksData {
return ((position + 1) % C.POSITIONS_PER_INSTRUCTION) == 0; 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); 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; yAxis: HTMLElement;
grid: HTMLElement; grid: HTMLElement;
constructor(userSettings: UserSettings) { constructor(userSettings: UserSettings, instructionRangeString: string) {
this.container = document.getElementById(C.RANGES_PANE_ID); this.container = document.getElementById(C.RANGES_PANE_ID);
this.resizerBar = document.getElementById(C.RESIZER_RANGES_ID); this.resizerBar = document.getElementById(C.RESIZER_RANGES_ID);
this.snapper = document.getElementById(C.SHOW_HIDE_RANGES_ID); this.snapper = document.getElementById(C.SHOW_HIDE_RANGES_ID);
this.content = document.createElement("div"); 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 = document.createElement("div");
this.showOnLoad.style.visibility = "hidden"; this.showOnLoad.style.visibility = "hidden";
@ -268,10 +285,10 @@ class Divs {
this.registerHeaders.appendChild(this.registers); 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 titleEl = createElement("div", "range-title-div");
const titleBar = createElement("div", "range-title"); 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", "?"); const titleHelp = createElement("div", "range-title-help", "?");
titleHelp.title = "Each row represents a single TopLevelLiveRange (or two if deferred exists)." 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." + "\nEach interval belongs to a LiveRange contained within that row's TopLevelLiveRange."
@ -310,23 +327,25 @@ class RowConstructor {
// easily combined into a single row. // easily combined into a single row.
construct(grid: Grid, row: number, registerIndex: number, ranges: [Range, Range], construct(grid: Grid, row: number, registerIndex: number, ranges: [Range, Range],
getElementForEmptyPosition: (position: number) => HTMLElement, getElementForEmptyPosition: (position: number) => HTMLElement,
callbackForInterval: (position: number, interval: HTMLElement) => void): void { callbackForInterval: (position: number, interval: HTMLElement) => void): boolean {
const positions = new Array<HTMLElement>(this.view.numPositions);
// Construct all of the new intervals. // Construct all of the new intervals.
const intervalMap = this.elementsForIntervals(registerIndex, ranges); const intervalMap = this.elementsForIntervals(registerIndex, ranges);
for (let position = 0; position < this.view.numPositions; ++position) { if (intervalMap.size == 0) return false;
const interval = intervalMap.get(position); const positions = new Array<HTMLElement>(this.view.instructionRangeHandler.numPositions);
for (let column = 0; column < this.view.instructionRangeHandler.numPositions; ++column) {
const interval = intervalMap.get(column);
if (interval === undefined) { if (interval === undefined) {
positions[position] = getElementForEmptyPosition(position); positions[column] = getElementForEmptyPosition(column);
} else { } else {
callbackForInterval(position, interval); callbackForInterval(column, interval);
this.view.intervalsAccessor.addInterval(interval); this.view.intervalsAccessor.addInterval(interval);
const intervalPositionElements = this.view.getPositionElementsFromInterval(interval); const intervalPositionElements = this.view.getPositionElementsFromInterval(interval);
for (let j = 0; j < intervalPositionElements.length; ++j) { for (let j = 0; j < intervalPositionElements.length; ++j) {
// Point positionsArray to the new elements. const intervalColumn = column + j;
positions[position + j] = (intervalPositionElements[j] as HTMLElement); // 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; if (!range) continue;
this.setUses(grid, row, range); this.setUses(grid, row, range);
} }
return true;
} }
// This is the main function used to build new intervals. // This is the main function used to build new intervals.
@ -348,7 +368,12 @@ class RowConstructor {
for (const childRange of range.childRanges) { for (const childRange of range.childRanges) {
const tooltip = childRange.getTooltip(registerIndex); const tooltip = childRange.getTooltip(registerIndex);
for (const [index, intervalNums] of childRange.intervals.entries()) { 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, const intervalEl = this.elementForInterval(childRange, interval, tooltip,
index, range.isDeferred); index, range.isDeferred);
intervalMap.set(interval.start, intervalEl); intervalMap.set(interval.start, intervalEl);
@ -377,9 +402,9 @@ class RowConstructor {
for (let i = interval.start; i < interval.end; ++i) { for (let i = interval.start; i < interval.end; ++i) {
const classes = "range-position range-interval-position range-empty" + const classes = "range-position range-interval-position range-empty" +
(this.view.blocksData.isBlockBorder(i) ? " range-block-border" (this.view.blocksData.isIndexBlockBorder(i)
: this.view.blocksData.isInstructionBorder(i) ? " range-instr-border" : ""); ? " range-block-border"
: this.view.blocksData.isIndexInstructionBorder(i) ? " range-instr-border" : "");
const positionEl = createElement("div", classes, "_"); const positionEl = createElement("div", classes, "_");
positionEl.style.gridColumn = String(i - interval.start + 1); positionEl.style.gridColumn = String(i - interval.start + 1);
intervalInnerWrapper.appendChild(positionEl); intervalInnerWrapper.appendChild(positionEl);
@ -408,13 +433,17 @@ class RowConstructor {
// Allows a cleaner display of the interval text when displayed vertically. // Allows a cleaner display of the interval text when displayed vertically.
const spanElBehind = createElement("span", "range-interval-text range-interval-text-behind"); const spanElBehind = createElement("span", "range-interval-text range-interval-text-behind");
this.view.stringConstructor.setIntervalString(spanEl, spanElBehind, tooltip, numCells); 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 { private setUses(grid: Grid, row: number, range: Range): void {
for (const liveRange of range.childRanges) { for (const liveRange of range.childRanges) {
if (!liveRange.uses) continue; 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); grid.getCell(row, use).classList.toggle("range-use", true);
} }
} }
@ -613,30 +642,32 @@ class RangeViewConstructor {
} }
private addVirtualRanges(row: number): number { private addVirtualRanges(row: number): number {
const source = this.view.sequenceView.sequence.registerAllocation; return this.view.instructionRangeHandler.forEachLiveRange(row,
for (const [registerIndex, range] of source.liveRanges.entries()) { (registerIndex: number, row: number, registerName: string, range: Range) => {
if (!range) continue; const rowEl = this.elementForRow(row, registerIndex, [range, undefined]);
const registerName = this.virtualRegisterName(registerIndex); if (rowEl) {
const registerEl = this.elementForRegister(row, registerName, true); const registerEl = this.elementForRegister(row, registerName, true);
this.addRowToGroup(row, this.elementForRow(row, registerIndex, [range, undefined])); this.addRowToGroup(row, rowEl);
this.view.divs.registers.appendChild(registerEl); this.view.divs.registers.appendChild(registerEl);
++(this.registerTypeHeaderData.virtualCount); ++(this.registerTypeHeaderData.virtualCount);
++row; return true;
} }
return row; return false;
} });
private virtualRegisterName(registerIndex: number): string {
return `v${registerIndex}`;
} }
private addFixedRanges(row: number): void { 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]) => { (registerIndex: number, row: number, registerName: string, ranges: [Range, Range]) => {
this.registerTypeHeaderData.countFixedRegister(registerName, ranges); const rowEl = this.elementForRow(row, registerIndex, ranges);
const registerEl = this.elementForRegister(row, registerName, false); if (rowEl) {
this.addRowToGroup(row, this.elementForRow(row, registerIndex, ranges)); this.registerTypeHeaderData.countFixedRegister(registerName, ranges);
this.view.divs.registers.appendChild(registerEl); 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) { if (row % C.ROW_GROUP_SIZE != 0) {
@ -650,14 +681,15 @@ class RangeViewConstructor {
private elementForRow(row: number, registerIndex: number, ranges: [Range, Range]): HTMLElement { private elementForRow(row: number, registerIndex: number, ranges: [Range, Range]): HTMLElement {
const rowEl = createElement("div", "range-positions"); 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 blockBorder = this.view.blocksData.isBlockBorder(position);
const classes = "range-position range-empty " + (blockBorder const classes = "range-position range-empty " + (blockBorder
? "range-block-border" : this.view.blocksData.isInstructionBorder(position) ? "range-block-border" : this.view.blocksData.isInstructionBorder(position)
? "range-instr-border" : "range-position-border"); ? "range-instr-border" : "range-position-border");
const positionEl = createElement("div", classes, "_"); const positionEl = createElement("div", classes, "_");
positionEl.style.gridColumn = String(position + 1); positionEl.style.gridColumn = String(column + 1);
rowEl.appendChild(positionEl); rowEl.appendChild(positionEl);
return positionEl; return positionEl;
}; };
@ -666,10 +698,12 @@ class RangeViewConstructor {
rowEl.appendChild(interval); rowEl.appendChild(interval);
}; };
this.view.rowConstructor.construct(this.grid, row, registerIndex, ranges, // Only construct the row if it has any intervals.
getElementForEmptyPosition, callbackForInterval); if (this.view.rowConstructor.construct(this.grid, row, registerIndex, ranges,
getElementForEmptyPosition, callbackForInterval)) {
return rowEl; return rowEl;
}
return undefined;
} }
private elementForRegister(row: number, registerName: string, isVirtual: boolean) { private elementForRegister(row: number, registerName: string, isVirtual: boolean) {
@ -735,29 +769,42 @@ class RangeViewConstructor {
private elementForBlockHeader(): HTMLElement { private elementForBlockHeader(): HTMLElement {
const headerEl = createElement("div", "range-block-ids"); const headerEl = createElement("div", "range-block-ids");
let blockIndex = 0; let blockId = 0;
for (let i = 0; i < this.view.sequenceView.numInstructions;) { const lastPos = this.view.instructionRangeHandler.getLastPosition();
const instrCount = this.view.blocksData.blockInstructionCountMap.get(blockIndex); for (let position = 0; position <= lastPos;) {
headerEl.appendChild(this.elementForBlockIndex(blockIndex, i, instrCount)); const instrCount = this.view.blocksData.blockInstructionCountMap.get(blockId);
++blockIndex; if (this.view.instructionRangeHandler.showAllPositions) {
i += instrCount; 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; return headerEl;
} }
private elementForBlockIndex(index: number, firstInstruction: number, instrCount: number): private elementForBlock(blockId: number, firstColumn: number, instrCount: number):
HTMLElement { HTMLElement {
const element = const element =
createElement("div", "range-block-id range-header-element range-block-border"); 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 idEl = createElement("span", "range-block-id-number", str);
const centre = instrCount * C.POSITIONS_PER_INSTRUCTION; const centre = instrCount * C.POSITIONS_PER_INSTRUCTION;
idEl.style.gridRow = `${centre} / ${centre + 1}`; idEl.style.gridRow = `${centre} / ${centre + 1}`;
element.appendChild(idEl); element.appendChild(idEl);
element.setAttribute("title", str); element.setAttribute("title", str);
element.dataset.instrCount = String(instrCount); 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); const lastGridCol = firstGridCol + (instrCount * C.POSITIONS_PER_INSTRUCTION);
element.style.gridColumn = `${firstGridCol} / ${lastGridCol}`; element.style.gridColumn = `${firstGridCol} / ${lastGridCol}`;
element.style.gridTemplateRows = `repeat(${8 * instrCount}, element.style.gridTemplateRows = `repeat(${8 * instrCount},
@ -768,23 +815,24 @@ class RangeViewConstructor {
private elementForInstructionHeader(): HTMLElement { private elementForInstructionHeader(): HTMLElement {
const headerEl = createElement("div", "range-instruction-ids"); const headerEl = createElement("div", "range-instruction-ids");
let instrId = this.view.instructionRangeHandler.getInstructionIdFromIndex(0);
for (let i = 0; i < this.view.sequenceView.numInstructions; ++i) { const instrLimit = instrId + this.view.instructionRangeHandler.numInstructions;
headerEl.appendChild(this.elementForInstructionIndex(i)); for (; instrId < instrLimit; ++instrId) {
headerEl.appendChild(this.elementForInstruction(instrId));
} }
return headerEl; return headerEl;
} }
private elementForInstructionIndex(index: number): HTMLElement { private elementForInstruction(instrId: number): HTMLElement {
const isBlockBorder = this.view.blocksData.blockBorders.has(index); const isBlockBorder = this.view.blocksData.isInstructionIdOnBlockBorder(instrId);
const classes = "range-instruction-id range-header-element " const classes = "range-instruction-id range-header-element "
+ (isBlockBorder ? "range-block-border" : "range-instr-border"); + (isBlockBorder ? "range-block-border" : "range-instr-border");
const element = createElement("div", classes); const element = createElement("div", classes);
element.appendChild(createElement("span", "range-instruction-id-number", String(index))); element.appendChild(createElement("span", "range-instruction-id-number", String(instrId)));
element.setAttribute("title", String(index)); element.setAttribute("title", String(instrId));
const firstGridCol = (index * C.POSITIONS_PER_INSTRUCTION) + 1; const instrIndex = this.view.instructionRangeHandler.getInstructionIndex(instrId);
const firstGridCol = (instrIndex * C.POSITIONS_PER_INSTRUCTION) + 1;
element.style.gridColumn = `${firstGridCol} / ${(firstGridCol + C.POSITIONS_PER_INSTRUCTION)}`; element.style.gridColumn = `${firstGridCol} / ${(firstGridCol + C.POSITIONS_PER_INSTRUCTION)}`;
return element; return element;
} }
@ -792,22 +840,24 @@ class RangeViewConstructor {
private elementForPositionHeader(): HTMLElement { private elementForPositionHeader(): HTMLElement {
const headerEl = createElement("div", "range-positions range-positions-header"); const headerEl = createElement("div", "range-positions range-positions-header");
for (let i = 0; i < this.view.numPositions; ++i) { let position = this.view.instructionRangeHandler.getPositionFromIndex(0);
headerEl.appendChild(this.elementForPositionIndex(i)); 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; return headerEl;
} }
private elementForPositionIndex(index: number): HTMLElement { private elementForPosition(position: number, isBlockBorder: boolean): HTMLElement {
const isBlockBorder = this.view.blocksData.isBlockBorder(index);
const classes = "range-position range-header-element " + const classes = "range-position range-header-element " +
(isBlockBorder ? "range-block-border" (isBlockBorder ? "range-block-border"
: this.view.blocksData.isInstructionBorder(index) ? "range-instr-border" : this.view.blocksData.isInstructionBorder(position) ? "range-instr-border"
: "range-position-border"); : "range-position-border");
const element = createElement("div", classes, String(index)); const element = createElement("div", classes, String(position));
element.setAttribute("title", String(index)); element.setAttribute("title", String(position));
return element; return element;
} }
@ -850,18 +900,18 @@ class PhaseChangeHandler {
const currentGrid = this.view.gridAccessor.getAnyGrid(); const currentGrid = this.view.gridAccessor.getAnyGrid();
const newGrid = new Grid(); const newGrid = new Grid();
this.view.gridAccessor.addGrid(newGrid); this.view.gridAccessor.addGrid(newGrid);
const source = this.view.sequenceView.sequence.registerAllocation;
let row = 0; let row = 0;
for (const [registerIndex, range] of source.liveRanges.entries()) { row = this.view.instructionRangeHandler.forEachLiveRange(row, (registerIndex: number,
if (!range) continue; row: number, _: string, range: Range) => {
this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, [range, undefined]); 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) => { (registerIndex, row, _, ranges) => {
this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, ranges); this.addnewIntervalsInRange(currentGrid, newGrid, row, registerIndex, ranges);
return true;
}); });
} }
@ -869,14 +919,14 @@ class PhaseChangeHandler {
registerIndex: number, ranges: [Range, Range]): void { registerIndex: number, ranges: [Range, Range]): void {
const numReplacements = new Map<HTMLElement, number>(); const numReplacements = new Map<HTMLElement, number>();
const getElementForEmptyPosition = (position: number) => { const getElementForEmptyPosition = (column: number) => {
return currentGrid.getCell(row, position); return currentGrid.getCell(row, column);
}; };
// Inserts new interval beside existing intervals. // 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. // 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 // The number of intervals already inserted is tracked so that the inserted intervals
// are ordered correctly. // are ordered correctly.
const intervalsAlreadyInserted = numReplacements.get(currentInterval); 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<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, {registerIndex: number, ranges: [Range, Range]}>();
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 { class DisplayResetter {
view: RangeView; view: RangeView;
isFlipped: boolean; isFlipped: boolean;
@ -1135,18 +1389,19 @@ export class RangeView {
divs: Divs; divs: Divs;
scrollHandler: ScrollHandler; scrollHandler: ScrollHandler;
phaseChangeHandler: PhaseChangeHandler; phaseChangeHandler: PhaseChangeHandler;
instructionRangeHandler: InstructionRangeHandler;
displayResetter: DisplayResetter; displayResetter: DisplayResetter;
rowConstructor: RowConstructor; rowConstructor: RowConstructor;
stringConstructor: StringConstructor; stringConstructor: StringConstructor;
initialized: boolean; initialized: boolean;
isShown: boolean; isShown: boolean;
numPositions: number;
maxLengthVirtualRegisterNumber: number; maxLengthVirtualRegisterNumber: number;
constructor(sequence: SequenceView) { constructor(sequence: SequenceView, firstInstr: number, lastInstr: number) {
this.sequenceView = sequence; this.sequenceView = sequence;
this.initialized = false; this.initialized = false;
this.isShown = false; this.isShown = false;
this.instructionRangeHandler = new InstructionRangeHandler(this, firstInstr, lastInstr);
} }
public initializeContent(blocks: Array<SequenceBlock>): void { public initializeContent(blocks: Array<SequenceBlock>): void {
@ -1162,16 +1417,17 @@ export class RangeView {
// Indicates whether the grid axes are switched. // Indicates whether the grid axes are switched.
this.userSettings.addSetting("flipped", false, this.userSettings.addSetting("flipped", false,
this.displayResetter.resetFlipped.bind(this.displayResetter)); this.displayResetter.resetFlipped.bind(this.displayResetter));
this.blocksData = new BlocksData(blocks); this.blocksData = new BlocksData(this, blocks);
this.divs = new Divs(this.userSettings); this.divs = new Divs(this.userSettings,
this.instructionRangeHandler.getInstructionRangeString());
this.displayResetter.updateClassesOnContainer(); this.displayResetter.updateClassesOnContainer();
this.scrollHandler = new ScrollHandler(this); this.scrollHandler = new ScrollHandler(this);
this.numPositions = this.sequenceView.numInstructions * C.POSITIONS_PER_INSTRUCTION;
this.rowConstructor = new RowConstructor(this); this.rowConstructor = new RowConstructor(this);
this.stringConstructor = new StringConstructor(this); this.stringConstructor = new StringConstructor(this);
const constructor = new RangeViewConstructor(this); const constructor = new RangeViewConstructor(this);
constructor.construct(); 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); this.phaseChangeHandler = new PhaseChangeHandler(this);
let maxVirtualRegisterNumber = 0; let maxVirtualRegisterNumber = 0;
for (const register of this.divs.registers.children) { for (const register of this.divs.registers.children) {
@ -1199,12 +1455,14 @@ export class RangeView {
// panel is shown. // panel is shown.
window.dispatchEvent(new Event("resize")); window.dispatchEvent(new Event("resize"));
setTimeout(() => { if (this.divs.registers.children.length) {
this.userSettings.resetFromSessionStorage(); setTimeout(() => {
this.scrollHandler.restoreScroll(); this.userSettings.resetFromSessionStorage();
this.scrollHandler.syncHidden(); this.scrollHandler.restoreScroll();
this.divs.showOnLoad.style.visibility = "visible"; this.scrollHandler.syncHidden();
}, 100); this.divs.showOnLoad.style.visibility = "visible";
}, 100);
}
} }
} }
@ -1225,7 +1483,7 @@ export class RangeView {
} }
public onresize(): void { 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 { public getPositionElementsFromInterval(interval: HTMLElement): HTMLCollection {

View File

@ -26,6 +26,8 @@ export class SequenceView extends TextView {
showRangeView: boolean; showRangeView: boolean;
phaseSelectEl: HTMLSelectElement; phaseSelectEl: HTMLSelectElement;
toggleRangeViewEl: HTMLElement; toggleRangeViewEl: HTMLElement;
firstInstrInput: HTMLInputElement;
lastInstrInput: HTMLInputElement;
constructor(parent: HTMLElement, broker: SelectionBroker) { constructor(parent: HTMLElement, broker: SelectionBroker) {
super(parent, broker); super(parent, broker);
@ -307,19 +309,12 @@ export class SequenceView extends TextView {
private addRangeView(): void { private addRangeView(): void {
if (this.sequence.registerAllocation) { if (this.sequence.registerAllocation) {
if (!this.rangeView) { 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; const source = this.sequence.registerAllocation;
if (source.fixedLiveRanges.length == 0 && source.liveRanges.length == 0) { if (source.fixedLiveRanges.length == 0 && source.liveRanges.length == 0) {
this.preventRangeView("No live ranges to show"); 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) { if (this.showRangeView) {
this.rangeView.initializeContent(this.sequence.blocks); 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 { 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; const toggleRangesInput = createElement("input", "range-toggle-show") as HTMLInputElement;
toggleRangesInput.setAttribute("type", "checkbox"); toggleRangesInput.setAttribute("type", "checkbox");
toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput); toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput);
@ -375,6 +399,15 @@ export class SequenceView extends TextView {
toggleRangesInput.disabled = true; toggleRangesInput.disabled = true;
this.showRangeView = toggleRangesInput.checked; this.showRangeView = toggleRangesInput.checked;
if (this.showRangeView) { 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.initializeContent(this.sequence.blocks);
this.rangeView.show(); this.rangeView.show();
} else { } else {

View File

@ -161,6 +161,26 @@ export abstract class TextView extends PhaseView {
public onresize(): void {} public onresize(): void {}
private removeHtmlElementFromMapIf(condition: (e: HTMLElement) => boolean,
map: Map<string, Array<HTMLElement>>): 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 // instruction-id are the divs for the register allocator phase
protected addHtmlElementForInstructionId(anyInstructionId: any, htmlElement: HTMLElement): void { protected addHtmlElementForInstructionId(anyInstructionId: any, htmlElement: HTMLElement): void {
const instructionId = String(anyInstructionId); const instructionId = String(anyInstructionId);