[turbolizer] Views refactoring

Refactored views:
- code-view
- disassembly-view

Bug: v8:7327
Change-Id: I2020e288ace5b2706546b825620c147686dd310c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3757899
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Commit-Queue: Danylo Boiko <danielboyko02@gmail.com>
Cr-Commit-Position: refs/heads/main@{#81820}
This commit is contained in:
Danylo Boiko 2022-07-18 21:44:43 +03:00 committed by V8 LUCI CQ
parent 398f33fe7a
commit 7e93531493
5 changed files with 511 additions and 476 deletions

View File

@ -68,14 +68,14 @@ window.onload = function () {
sourceContainer.classList.add("viewpane", "scrollable");
sourceTabs.activateTab(sourceTab);
const sourceView = new CodeView(sourceContainer, selectionBroker, sourceResolver,
mainFunction, CodeMode.MainSource);
const sourceView = new CodeView(sourceContainer, selectionBroker, mainFunction,
sourceResolver, CodeMode.MainSource);
sourceView.show();
sourceViews.push(sourceView);
for (const source of sourceResolver.sources) {
const sourceView = new CodeView(sourceContainer, selectionBroker, sourceResolver,
source, CodeMode.InlinedSource);
const sourceView = new CodeView(sourceContainer, selectionBroker, source,
sourceResolver, CodeMode.InlinedSource);
sourceView.show();
sourceViews.push(sourceView);
}
@ -87,7 +87,7 @@ window.onload = function () {
disassemblyView.initializeCode(mainFunction.sourceText);
if (sourceResolver.disassemblyPhase) {
disassemblyView.initializePerfProfile(jsonObj.eventCounts);
disassemblyView.showContent(sourceResolver.disassemblyPhase.data);
disassemblyView.showContent(sourceResolver.disassemblyPhase);
disassemblyView.show();
}

View File

@ -8,7 +8,8 @@ import { SelectionBroker } from "../selection/selection-broker";
import { View } from "./view";
import { SelectionMap } from "../selection/selection-map";
import { ViewElements } from "../common/view-elements";
import { SelectionHandler } from "../selection/selection-handler";
import { ClearableHandler, SelectionHandler } from "../selection/selection-handler";
import { SourcePosition } from "../position";
interface PR {
prettyPrint(_: unknown, el: HTMLElement): void;
@ -30,117 +31,32 @@ export class CodeView extends View {
codeMode: CodeMode;
sourcePositionToHtmlElements: Map<string, Array<HTMLElement>>;
showAdditionalInliningPosition: boolean;
selectionHandler: SelectionHandler;
selection: SelectionMap;
selectionHandler: SelectionHandler & ClearableHandler;
createViewElement() {
constructor(parent: HTMLElement, broker: SelectionBroker, sourceFunction: Source,
sourceResolver: SourceResolver, codeMode: CodeMode) {
super(parent);
this.broker = broker;
this.source = sourceFunction;
this.sourceResolver = sourceResolver;
this.codeMode = codeMode;
this.sourcePositionToHtmlElements = new Map<string, Array<HTMLElement>>();
this.showAdditionalInliningPosition = false;
this.selection = new SelectionMap((gp: GenericPosition) => gp.toString());
this.selectionHandler = this.initializeSourcePositionHandler();
broker.addSourcePositionHandler(this.selectionHandler);
this.initializeCode();
}
public createViewElement(): HTMLDivElement {
const sourceContainer = document.createElement("div");
sourceContainer.classList.add("source-container");
return sourceContainer;
}
constructor(parent: HTMLElement, broker: SelectionBroker, sourceResolver: SourceResolver, sourceFunction: Source, codeMode: CodeMode) {
super(parent);
const view = this;
view.broker = broker;
view.sourceResolver = sourceResolver;
view.source = sourceFunction;
view.codeMode = codeMode;
this.sourcePositionToHtmlElements = new Map();
this.showAdditionalInliningPosition = false;
const selectionHandler = {
clear: function () {
view.selection.clear();
view.updateSelection();
broker.broadcastClear(this);
},
select: function (sourcePositions, selected) {
const locations = [];
for (const sourcePosition of sourcePositions) {
locations.push(sourcePosition);
sourceResolver.addInliningPositions(sourcePosition, locations);
}
if (locations.length == 0) return;
view.selection.select(locations, selected);
view.updateSelection();
broker.broadcastSourcePositionSelect(this, locations, selected);
},
brokeredSourcePositionSelect: function (locations, selected) {
const firstSelect = view.selection.isEmpty();
for (const location of locations) {
const translated = sourceResolver.translateToSourceId(view.source.sourceId, location);
if (!translated) continue;
view.selection.select([translated], selected);
}
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.selection.clear();
view.updateSelection();
},
};
view.selection = new SelectionMap((gp: GenericPosition) => gp.toString());
broker.addSourcePositionHandler(selectionHandler);
this.selectionHandler = selectionHandler;
this.initializeCode();
}
addHtmlElementToSourcePosition(sourcePosition, element) {
const key = sourcePosition.toString();
if (!this.sourcePositionToHtmlElements.has(key)) {
this.sourcePositionToHtmlElements.set(key, []);
}
this.sourcePositionToHtmlElements.get(key).push(element);
}
getHtmlElementForSourcePosition(sourcePosition) {
return this.sourcePositionToHtmlElements.get(sourcePosition.toString());
}
updateSelection(scrollIntoView: boolean = false): void {
const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
for (const [sp, els] of this.sourcePositionToHtmlElements.entries()) {
const isSelected = this.selection.isKeySelected(sp);
for (const el of els) {
mkVisible.consider(el, isSelected);
el.classList.toggle("selected", isSelected);
}
}
mkVisible.apply(scrollIntoView);
}
getCodeHtmlElementName() {
return `source-pre-${this.source.sourceId}`;
}
getCodeHeaderHtmlElementName() {
return `source-pre-${this.source.sourceId}-header`;
}
getHtmlCodeLines(): NodeListOf<HTMLElement> {
const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
return ordereList.childNodes as NodeListOf<HTMLElement>;
}
onSelectLine(lineNumber: number, doClear: boolean) {
if (doClear) {
this.selectionHandler.clear();
}
const positions = this.sourceResolver.lineToSourcePositions(lineNumber - 1);
if (positions !== undefined) {
this.selectionHandler.select(positions, undefined);
}
}
onSelectSourcePosition(sourcePosition, doClear: boolean) {
if (doClear) {
this.selectionHandler.clear();
}
this.selectionHandler.select([sourcePosition], undefined);
}
initializeCode() {
private initializeCode(): void {
const view = this;
const source = this.source;
const sourceText = source.sourceText;
@ -160,7 +76,7 @@ export class CodeView extends View {
codeHeader.appendChild(codeFileFunction);
const codeModeDiv = document.createElement("div");
codeModeDiv.classList.add("code-mode");
codeModeDiv.innerHTML = `${this.codeMode}`;
codeModeDiv.innerHTML = this.codeMode;
codeHeader.appendChild(codeModeDiv);
const clearDiv = document.createElement("div");
clearDiv.style.clear = "both";
@ -178,7 +94,7 @@ export class CodeView extends View {
codePre.style.display = "none";
}
};
if (sourceText != "") {
if (sourceText !== "") {
codePre.classList.add("linenums");
codePre.textContent = sourceText;
try {
@ -189,7 +105,7 @@ export class CodeView extends View {
}
view.divNode.onclick = function (e: MouseEvent) {
if (e.target instanceof Element && e.target.tagName == "DIV") {
if (e.target instanceof Element && e.target.tagName === "DIV") {
const targetDiv = e.target as HTMLDivElement;
if (targetDiv.classList.contains("line-number")) {
e.stopPropagation();
@ -208,8 +124,8 @@ export class CodeView extends View {
// Line numbers are not zero-based.
const lineNumber = i + 1;
const currentLineElement = lineListDiv[i];
currentLineElement.id = "li" + i;
currentLineElement.dataset.lineNumber = "" + lineNumber;
currentLineElement.id = `li${i}`;
currentLineElement.dataset.lineNumber = String(lineNumber);
const spans = currentLineElement.childNodes;
for (const currentSpan of spans) {
if (currentSpan instanceof HTMLSpanElement) {
@ -224,7 +140,7 @@ export class CodeView extends View {
this.insertLineNumber(currentLineElement, lineNumber);
while ((current < sourceText.length) &&
(sourceText[current] == '\n' || sourceText[current] == '\r')) {
(sourceText[current] === "\n" || sourceText[current] === "\r")) {
++current;
++newlineAdjust;
}
@ -232,7 +148,95 @@ export class CodeView extends View {
}
}
insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) {
private initializeSourcePositionHandler(): SelectionHandler & ClearableHandler {
const view = this;
const broker = this.broker;
const sourceResolver = this.sourceResolver;
return {
select: function (sourcePositions: Array<SourcePosition>, selected: boolean) {
const locations = new Array<SourcePosition>();
for (const sourcePosition of sourcePositions) {
locations.push(sourcePosition);
sourceResolver.addInliningPositions(sourcePosition, locations);
}
if (locations.length == 0) return;
view.selection.select(locations, selected);
view.updateSelection();
broker.broadcastSourcePositionSelect(this, locations, selected);
},
clear: function () {
view.selection.clear();
view.updateSelection();
broker.broadcastClear(this);
},
brokeredSourcePositionSelect: function (locations: Array<SourcePosition>, selected: boolean) {
const firstSelect = view.selection.isEmpty();
for (const location of locations) {
const translated = sourceResolver.translateToSourceId(view.source.sourceId, location);
if (!translated) continue;
view.selection.select([translated], selected);
}
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.selection.clear();
view.updateSelection();
},
};
}
private addHtmlElementToSourcePosition(sourcePosition, element): void {
const key = sourcePosition.toString();
if (!this.sourcePositionToHtmlElements.has(key)) {
this.sourcePositionToHtmlElements.set(key, new Array<HTMLElement>());
}
this.sourcePositionToHtmlElements.get(key).push(element);
}
private updateSelection(scrollIntoView: boolean = false): void {
const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
for (const [sp, els] of this.sourcePositionToHtmlElements.entries()) {
const isSelected = this.selection.isKeySelected(sp);
for (const el of els) {
mkVisible.consider(el, isSelected);
el.classList.toggle("selected", isSelected);
}
}
mkVisible.apply(scrollIntoView);
}
private getCodeHtmlElementName(): string {
return `source-pre-${this.source.sourceId}`;
}
private getCodeHeaderHtmlElementName(): string {
return `source-pre-${this.source.sourceId}-header`;
}
private getHtmlCodeLines(): NodeListOf<HTMLElement> {
const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
return ordereList.childNodes as NodeListOf<HTMLElement>;
}
private onSelectLine(lineNumber: number, doClear: boolean) {
if (doClear) {
this.selectionHandler.clear();
}
const positions = this.sourceResolver.lineToSourcePositions(lineNumber - 1);
if (positions !== undefined) {
this.selectionHandler.select(positions, undefined);
}
}
private onSelectSourcePosition(sourcePosition: SourcePosition, doClear: boolean) {
if (doClear) {
this.selectionHandler.clear();
}
this.selectionHandler.select([sourcePosition], undefined);
}
private insertSourcePositions(currentSpan: HTMLSpanElement, lineNumber: number,
pos: number, end: number, adjust: number): void {
const view = this;
const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end);
let offset = 0;
@ -240,43 +244,42 @@ export class CodeView extends View {
// Internally, line numbers are 0-based so we have to substract 1 from the line number. This
// path in only taken by non-Wasm code. Wasm code relies on setSourceLineToBytecodePosition.
this.sourceResolver.addAnyPositionToLine(lineNumber - 1, sourcePosition);
const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.lastChild : currentSpan;
if (!(textnode instanceof Text)) continue;
const textNode = currentSpan.tagName === "SPAN" ? currentSpan.lastChild : currentSpan;
if (!(textNode instanceof Text)) continue;
const splitLength = Math.max(0, sourcePosition.scriptOffset - pos - offset);
offset += splitLength;
const replacementNode = textnode.splitText(splitLength);
const span = document.createElement('span');
const replacementNode = textNode.splitText(splitLength);
const span = document.createElement("span");
span.setAttribute("scriptOffset", sourcePosition.scriptOffset.toString());
span.classList.add("source-position");
const marker = document.createElement('span');
const marker = document.createElement("span");
marker.classList.add("marker");
span.appendChild(marker);
const inlining = this.sourceResolver.getInliningForPosition(sourcePosition);
if (inlining != undefined && view.showAdditionalInliningPosition) {
if (inlining && view.showAdditionalInliningPosition) {
const sourceName = this.sourceResolver.getSourceName(inlining.sourceId);
const inliningMarker = document.createElement('span');
const inliningMarker = document.createElement("span");
inliningMarker.classList.add("inlining-marker");
inliningMarker.setAttribute("data-descr", `${sourceName} was inlined here`);
span.appendChild(inliningMarker);
}
span.onclick = function (e) {
span.onclick = function (e: MouseEvent) {
e.stopPropagation();
view.onSelectSourcePosition(sourcePosition, !e.shiftKey);
};
view.addHtmlElementToSourcePosition(sourcePosition, span);
textnode.parentNode.insertBefore(span, replacementNode);
textNode.parentNode.insertBefore(span, replacementNode);
}
}
insertLineNumber(lineElement: HTMLElement, lineNumber: number) {
const view = this;
private insertLineNumber(lineElement: HTMLElement, lineNumber: number): void {
const lineNumberElement = document.createElement("div");
lineNumberElement.classList.add("line-number");
lineNumberElement.dataset.lineNumber = `${lineNumber}`;
lineNumberElement.innerText = `${lineNumber}`;
lineNumberElement.dataset.lineNumber = String(lineNumber);
lineNumberElement.innerText = String(lineNumber);
lineElement.insertBefore(lineNumberElement, lineElement.firstChild);
for (const sourcePosition of this.sourceResolver.lineToSourcePositions(lineNumber - 1)) {
view.addHtmlElementToSourcePosition(sourcePosition, lineElement);
this.addHtmlElementToSourcePosition(sourcePosition, lineElement);
}
}
}

View File

@ -3,13 +3,14 @@
// found in the LICENSE file.
import * as C from "../common/constants";
import { interpolate } from "../common/util";
import { interpolate, storageGetItem, storageSetItem } from "../common/util";
import { SelectionBroker } from "../selection/selection-broker";
import { TextView } from "./text-view";
import { SelectionMap } from "../selection/selection-map";
import { InstructionSelectionHandler } from "../selection/selection-handler";
import { ClearableHandler, InstructionSelectionHandler } from "../selection/selection-handler";
import { TurbolizerInstructionStartInfo } from "../phases/instructions-phase";
import { SelectionStorage } from "../selection/selection-storage";
import { DisassemblyPhase } from "../phases/disassembly-phase";
const toolboxHTML =
`<div id="disassembly-toolbox">
@ -21,237 +22,27 @@ const toolboxHTML =
</div>`;
export class DisassemblyView extends TextView {
SOURCE_POSITION_HEADER_REGEX: any;
addrEventCounts: any;
totalEventCounts: any;
maxEventCounts: any;
posLines: Array<any>;
instructionSelectionHandler: InstructionSelectionHandler;
posLines: Array<number>;
instructionSelectionHandler: InstructionSelectionHandler & ClearableHandler;
offsetSelection: SelectionMap;
showInstructionAddressHandler: () => void;
showInstructionBinaryHandler: () => void;
highlightGapInstructionsHandler: () => void;
constructor(parentId, broker: SelectionBroker) {
super(parentId, broker);
const view = this;
const ADDRESS_STYLE = {
associateData: (text, fragment: HTMLElement) => {
const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/);
const offset = Number.parseInt(matches.groups["offset"], 16);
const instructionKind = view.sourceResolver.instructionsPhase
.getInstructionKindForPCOffset(offset);
fragment.dataset.instructionKind = instructionKind;
fragment.title = view.sourceResolver.instructionsPhase
.instructionKindToReadableName(instructionKind);
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
const blockIdElement = document.createElement("SPAN");
blockIdElement.className = "block-id com linkable-text";
blockIdElement.innerText = "";
if (blockIds && blockIds.length > 0) {
blockIds.forEach(blockId => view.addHtmlElementForBlockId(blockId, fragment));
blockIdElement.innerText = `B${blockIds.join(",")}:`;
blockIdElement.dataset.blockId = `${blockIds.join(",")}`;
}
fragment.appendChild(blockIdElement);
const addressElement = document.createElement("SPAN");
addressElement.className = "instruction-address";
addressElement.innerText = matches.groups["address"];
const offsetElement = document.createElement("SPAN");
offsetElement.innerText = matches.groups["offset"];
fragment.appendChild(addressElement);
fragment.appendChild(document.createTextNode(matches.groups["addressSpace"]));
fragment.appendChild(offsetElement);
fragment.appendChild(document.createTextNode(matches.groups["offsetSpace"]));
fragment.classList.add('tag');
constructor(parent: HTMLDivElement, broker: SelectionBroker) {
super(parent, broker);
this.patterns = this.initializePatterns();
if (!Number.isNaN(offset)) {
let pcOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
if (pcOffset == -1) pcOffset = Number(offset);
fragment.dataset.pcOffset = `${pcOffset}`;
addressElement.classList.add('linkable-text');
offsetElement.classList.add('linkable-text');
}
return true;
}
};
const UNCLASSIFIED_STYLE = {
css: 'com'
};
const NUMBER_STYLE = {
css: ['instruction-binary', 'lit']
};
const COMMENT_STYLE = {
css: 'com'
};
const OPCODE_ARGS = {
associateData: function (text, fragment) {
fragment.innerHTML = text;
const replacer = (match, hexOffset) => {
const offset = Number.parseInt(hexOffset, 16);
let keyOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
if (keyOffset == -1) keyOffset = Number(offset);
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
let block = "";
let blockIdData = "";
if (blockIds && blockIds.length > 0) {
block = `B${blockIds.join(",")} `;
blockIdData = `data-block-id="${blockIds.join(",")}"`;
}
return `<span class="tag linkable-text" data-pc-offset="${keyOffset}" ${blockIdData}>${block}${match}</span>`;
};
const html = text.replace(/<.0?x?([0-9a-fA-F]+)>/g, replacer);
fragment.innerHTML = html;
return true;
}
};
const OPCODE_STYLE = {
css: 'kwd'
};
const BLOCK_HEADER_STYLE = {
associateData: function (text, fragment) {
if (view.sourceResolver.disassemblyPhase.hasBlockStartInfo()) return false;
const matches = /\d+/.exec(text);
if (!matches) return true;
const blockId = matches[0];
fragment.dataset.blockId = blockId;
fragment.innerHTML = text;
fragment.className = "com block";
return true;
}
};
const SOURCE_POSITION_HEADER_STYLE = {
css: 'com'
};
view.SOURCE_POSITION_HEADER_REGEX = /^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/;
const patterns = [
[
[/^0?x?[0-9a-fA-F]{8,16}\s+[0-9a-f]+\s+/, ADDRESS_STYLE, 1],
[view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1],
[/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1],
[/^.*/, UNCLASSIFIED_STYLE, -1]
],
[
[/^\s*[0-9a-f]+\s+/, NUMBER_STYLE, 2],
[/^\s*[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2],
[/^.*/, null, -1]
],
[
[/^REX.W \S+\s+/, OPCODE_STYLE, 3],
[/^\S+\s+/, OPCODE_STYLE, 3],
[/^\S+$/, OPCODE_STYLE, -1],
[/^.*/, null, -1]
],
[
[/^\s+/, null],
[/^[^;]+$/, OPCODE_ARGS, -1],
[/^[^;]+/, OPCODE_ARGS, 4],
[/^;/, COMMENT_STYLE, 5]
],
[
[/^.+$/, COMMENT_STYLE, -1]
]
];
view.setPatterns(patterns);
const linkHandler = (e: MouseEvent) => {
if (!(e.target instanceof HTMLElement)) return;
const offsetAsString = typeof e.target.dataset.pcOffset != "undefined" ? e.target.dataset.pcOffset : e.target.parentElement.dataset.pcOffset;
const offset = Number.parseInt(offsetAsString, 10);
if ((typeof offsetAsString) != "undefined" && !Number.isNaN(offset)) {
view.offsetSelection.select([offset], true);
const nodes = view.sourceResolver.instructionsPhase.nodesForPCOffset(offset);
if (nodes.length > 0) {
e.stopPropagation();
if (!e.shiftKey) {
view.selectionHandler.clear();
}
view.selectionHandler.select(nodes, true);
} else {
view.updateSelection();
}
}
return undefined;
};
view.divNode.addEventListener('click', linkHandler);
const linkHandlerBlock = e => {
const blockId = e.target.dataset.blockId;
if (typeof blockId != "undefined") {
const blockIds = blockId.split(",");
if (!e.shiftKey) {
view.selectionHandler.clear();
}
view.blockSelectionHandler.select(blockIds, true);
}
};
view.divNode.addEventListener('click', linkHandlerBlock);
this.divNode.addEventListener("click", e => this.linkClickHandler(e));
this.divNode.addEventListener("click", e => this.linkBlockClickHandler(e));
this.offsetSelection = new SelectionMap(offset => String(offset));
const instructionSelectionHandler = {
clear: function () {
view.offsetSelection.clear();
view.updateSelection();
broker.broadcastClear(instructionSelectionHandler);
},
select: function (instructionIds, selected) {
view.offsetSelection.select(instructionIds, selected);
view.updateSelection();
broker.broadcastBlockSelect(instructionSelectionHandler, instructionIds, selected);
},
brokeredInstructionSelect: function (instructionIds, selected) {
const firstSelect = view.offsetSelection.isEmpty();
const keyPcOffsets = view.sourceResolver.instructionsPhase
.instructionsToKeyPcOffsets(instructionIds);
view.offsetSelection.select(keyPcOffsets, selected);
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.offsetSelection.clear();
view.updateSelection();
}
};
this.instructionSelectionHandler = instructionSelectionHandler;
broker.addInstructionHandler(instructionSelectionHandler);
const toolbox = document.createElement("div");
toolbox.id = "toolbox-anchor";
toolbox.innerHTML = toolboxHTML;
view.divNode.insertBefore(toolbox, view.divNode.firstChild);
const instructionAddressInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-address");
const lastShowInstructionAddress = window.sessionStorage.getItem("show-instruction-address");
instructionAddressInput.checked = lastShowInstructionAddress == 'true';
const showInstructionAddressHandler = () => {
window.sessionStorage.setItem("show-instruction-address", `${instructionAddressInput.checked}`);
for (const el of view.divNode.querySelectorAll(".instruction-address")) {
el.classList.toggle("invisible", !instructionAddressInput.checked);
}
};
instructionAddressInput.addEventListener("change", showInstructionAddressHandler);
this.showInstructionAddressHandler = showInstructionAddressHandler;
const instructionBinaryInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-binary");
const lastShowInstructionBinary = window.sessionStorage.getItem("show-instruction-binary");
instructionBinaryInput.checked = lastShowInstructionBinary == 'true';
const showInstructionBinaryHandler = () => {
window.sessionStorage.setItem("show-instruction-binary", `${instructionBinaryInput.checked}`);
for (const el of view.divNode.querySelectorAll(".instruction-binary")) {
el.classList.toggle("invisible", !instructionBinaryInput.checked);
}
};
instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler);
this.showInstructionBinaryHandler = showInstructionBinaryHandler;
const highlightGapInstructionsInput: HTMLInputElement = view.divNode.querySelector("#highlight-gap-instructions");
const lastHighlightGapInstructions = window.sessionStorage.getItem("highlight-gap-instructions");
highlightGapInstructionsInput.checked = lastHighlightGapInstructions == 'true';
const highlightGapInstructionsHandler = () => {
window.sessionStorage.setItem("highlight-gap-instructions", `${highlightGapInstructionsInput.checked}`);
view.divNode.classList.toggle("highlight-gap-instructions", highlightGapInstructionsInput.checked);
};
highlightGapInstructionsInput.addEventListener("change", highlightGapInstructionsHandler);
this.highlightGapInstructionsHandler = highlightGapInstructionsHandler;
this.instructionSelectionHandler = this.initializeInstructionSelectionHandler();
this.broker.addInstructionHandler(this.instructionSelectionHandler);
this.addDisassemblyToolbox();
}
public createViewElement(): HTMLDivElement {
@ -283,93 +74,22 @@ export class DisassemblyView extends TextView {
}
}
public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
throw new Error("Method not implemented.");
}
public detachSelection(): SelectionStorage {
return null;
}
public adaptSelection(selection: SelectionStorage): SelectionStorage {
return selection;
}
initializeCode(sourceText, sourcePosition: number = 0) {
const view = this;
view.addrEventCounts = null;
view.totalEventCounts = null;
view.maxEventCounts = null;
view.posLines = new Array();
// Comment lines for line 0 include sourcePosition already, only need to
// add sourcePosition for lines > 0.
view.posLines[0] = sourcePosition;
if (sourceText && sourceText != "") {
const base = sourcePosition;
let current = 0;
const sourceLines = sourceText.split("\n");
for (let i = 1; i < sourceLines.length; i++) {
// Add 1 for newline character that is split off.
current += sourceLines[i - 1].length + 1;
view.posLines[i] = base + current;
}
}
}
initializePerfProfile(eventCounts) {
const view = this;
if (eventCounts !== undefined) {
view.addrEventCounts = eventCounts;
view.totalEventCounts = {};
view.maxEventCounts = {};
for (const evName in view.addrEventCounts) {
if (view.addrEventCounts.hasOwnProperty(evName)) {
const keys = Object.keys(view.addrEventCounts[evName]);
const values = keys.map(key => view.addrEventCounts[evName][key]);
view.totalEventCounts[evName] = values.reduce((a, b) => a + b);
view.maxEventCounts[evName] = values.reduce((a, b) => Math.max(a, b));
}
}
} else {
view.addrEventCounts = null;
view.totalEventCounts = null;
view.maxEventCounts = null;
}
}
public showContent(data): void {
console.time("disassembly-view");
super.initializeContent(data, null);
this.showInstructionAddressHandler();
this.showInstructionBinaryHandler();
this.highlightGapInstructionsHandler();
console.timeEnd("disassembly-view");
}
// Shorten decimals and remove trailing zeroes for readability.
humanize(num) {
return num.toFixed(3).replace(/\.?0+$/, "") + "%";
}
processLine(line) {
const view = this;
public processLine(line: string): Array<HTMLSpanElement> {
let fragments = super.processLine(line);
const cssCls = "prof";
// Add profiling data per instruction if available.
if (view.totalEventCounts) {
if (this.totalEventCounts) {
const matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line);
if (matches) {
const newFragments = [];
for (const event in view.addrEventCounts) {
if (!view.addrEventCounts.hasOwnProperty(event)) continue;
const count = view.addrEventCounts[event][matches[1]];
let str = " ";
const cssCls = "prof";
const newFragments = new Array<HTMLSpanElement>();
for (const event in this.addrEventCounts) {
if (!this.addrEventCounts.hasOwnProperty(event)) continue;
const count = this.addrEventCounts[event][matches[1]];
if (count !== undefined) {
const perc = count / view.totalEventCounts[event] * 100;
const perc = count / this.totalEventCounts[event] * 100;
let col = { r: 255, g: 255, b: 255 };
for (let i = 0; i < C.PROF_COLS.length; i++) {
if (perc === C.PROF_COLS[i].perc) {
col = C.PROF_COLS[i].col;
@ -377,7 +97,6 @@ export class DisassemblyView extends TextView {
} else if (perc > C.PROF_COLS[i].perc && perc < C.PROF_COLS[i + 1].perc) {
const col1 = C.PROF_COLS[i].col;
const col2 = C.PROF_COLS[i + 1].col;
const val = perc - C.PROF_COLS[i].perc;
const max = C.PROF_COLS[i + 1].perc - C.PROF_COLS[i].perc;
@ -388,15 +107,12 @@ export class DisassemblyView extends TextView {
}
}
str = C.UNICODE_BLOCK;
const fragment = view.createFragment(str, cssCls);
fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")";
fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")";
const fragment = this.createFragment(C.UNICODE_BLOCK, cssCls);
fragment.title = `${event}: ${this.makeReadable(perc)} (${count})`;
fragment.style.color = `rgb(${col.r}, ${col.g}, ${col.b})`;
newFragments.push(fragment);
} else {
newFragments.push(view.createFragment(str, cssCls));
newFragments.push(this.createFragment(" ", cssCls));
}
}
fragments = newFragments.concat(fragments);
@ -404,4 +120,321 @@ export class DisassemblyView extends TextView {
}
return fragments;
}
public detachSelection(): SelectionStorage {
return null;
}
public adaptSelection(selection: SelectionStorage): SelectionStorage {
return selection;
}
public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
throw new Error("Method not implemented.");
}
public showContent(disassemblyPhase: DisassemblyPhase): void {
console.time("disassembly-view");
super.initializeContent(disassemblyPhase, null);
this.showInstructionAddressHandler();
this.showInstructionBinaryHandler();
this.highlightGapInstructionsHandler();
console.timeEnd("disassembly-view");
}
public initializeCode(sourceText: string, sourcePosition: number = 0): void {
this.addrEventCounts = null;
this.totalEventCounts = null;
this.maxEventCounts = null;
this.posLines = new Array<number>();
// Comment lines for line 0 include sourcePosition already, only need to
// add sourcePosition for lines > 0.
this.posLines[0] = sourcePosition;
if (sourceText && sourceText !== "") {
const base = sourcePosition;
let current = 0;
const sourceLines = sourceText.split("\n");
for (let i = 1; i < sourceLines.length; i++) {
// Add 1 for newline character that is split off.
current += sourceLines[i - 1].length + 1;
this.posLines[i] = base + current;
}
}
}
public initializePerfProfile(eventCounts): void {
if (eventCounts !== undefined) {
this.addrEventCounts = eventCounts;
this.totalEventCounts = {};
this.maxEventCounts = {};
for (const evName in this.addrEventCounts) {
if (this.addrEventCounts.hasOwnProperty(evName)) {
const keys = Object.keys(this.addrEventCounts[evName]);
const values = keys.map(key => this.addrEventCounts[evName][key]);
this.totalEventCounts[evName] = values.reduce((a, b) => a + b);
this.maxEventCounts[evName] = values.reduce((a, b) => Math.max(a, b));
}
}
} else {
this.addrEventCounts = null;
this.totalEventCounts = null;
this.maxEventCounts = null;
}
}
private initializeInstructionSelectionHandler(): InstructionSelectionHandler & ClearableHandler {
const view = this;
const broker = this.broker;
return {
select: function (instructionIds: Array<number>, selected: boolean) {
view.offsetSelection.select(instructionIds, selected);
view.updateSelection();
broker.broadcastBlockSelect(this, instructionIds, selected);
},
clear: function () {
view.offsetSelection.clear();
view.updateSelection();
broker.broadcastClear(this);
},
brokeredInstructionSelect: function (instructionIds: Array<number>, selected: boolean) {
const firstSelect = view.offsetSelection.isEmpty();
const keyPcOffsets = view.sourceResolver.instructionsPhase
.instructionsToKeyPcOffsets(instructionIds);
view.offsetSelection.select(keyPcOffsets, selected);
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.offsetSelection.clear();
view.updateSelection();
}
};
}
private linkClickHandler(e: MouseEvent): void {
if (!(e.target instanceof HTMLElement)) return;
const offsetAsString = e.target.dataset.pcOffset !== undefined
? e.target.dataset.pcOffset
: e.target.parentElement.dataset.pcOffset;
const offset = Number.parseInt(offsetAsString, 10);
if (offsetAsString !== undefined && !Number.isNaN(offset)) {
this.offsetSelection.select([offset], true);
const nodes = this.sourceResolver.instructionsPhase.nodesForPCOffset(offset);
if (nodes.length > 0) {
e.stopPropagation();
if (!e.shiftKey) this.selectionHandler.clear();
this.selectionHandler.select(nodes, true);
} else {
this.updateSelection();
}
}
return undefined;
}
private linkBlockClickHandler(e: MouseEvent): void {
const spanBlockElement = e.target as HTMLSpanElement;
const blockId = spanBlockElement.dataset.blockId;
if (blockId !== undefined) {
const blockIds = blockId.split(",");
if (!e.shiftKey) this.selectionHandler.clear();
this.blockSelectionHandler.select(blockIds, true);
}
}
private addDisassemblyToolbox(): void {
const view = this;
const toolbox = document.createElement("div");
toolbox.id = "toolbox-anchor";
toolbox.innerHTML = toolboxHTML;
view.divNode.insertBefore(toolbox, view.divNode.firstChild);
const instructionAddressInput: HTMLInputElement =
view.divNode.querySelector("#show-instruction-address");
instructionAddressInput.checked = storageGetItem("show-instruction-address");
const showInstructionAddressHandler = () => {
storageSetItem("show-instruction-address", instructionAddressInput.checked);
for (const el of view.divNode.querySelectorAll(".instruction-address")) {
el.classList.toggle("invisible", !instructionAddressInput.checked);
}
};
instructionAddressInput.addEventListener("change", showInstructionAddressHandler);
this.showInstructionAddressHandler = showInstructionAddressHandler;
const instructionBinaryInput: HTMLInputElement =
view.divNode.querySelector("#show-instruction-binary");
instructionBinaryInput.checked = storageGetItem("show-instruction-binary");
const showInstructionBinaryHandler = () => {
storageSetItem("show-instruction-binary", instructionBinaryInput.checked);
for (const el of view.divNode.querySelectorAll(".instruction-binary")) {
el.classList.toggle("invisible", !instructionBinaryInput.checked);
}
};
instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler);
this.showInstructionBinaryHandler = showInstructionBinaryHandler;
const highlightGapInstructionsInput: HTMLInputElement =
view.divNode.querySelector("#highlight-gap-instructions");
highlightGapInstructionsInput.checked = storageGetItem("highlight-gap-instructions");
const highlightGapInstructionsHandler = () => {
storageSetItem("highlight-gap-instructions", highlightGapInstructionsInput.checked);
view.divNode.classList.toggle("highlight-gap-instructions",
highlightGapInstructionsInput.checked);
};
highlightGapInstructionsInput.addEventListener("change", highlightGapInstructionsHandler);
this.highlightGapInstructionsHandler = highlightGapInstructionsHandler;
}
private makeReadable(num: number): string {
// Shorten decimals and remove trailing zeroes for readability.
return `${num.toFixed(3).replace(/\.?0+$/, "")}%`;
}
private initializePatterns(): Array<Array<any>> {
return [
[
[/^0?x?[0-9a-fA-F]{8,16}\s+[0-9a-f]+\s+/, this.addressStyle, 1],
[/^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/, this.sourcePositionHeaderStyle, -1],
[/^\s+-- B\d+ start.*/, this.blockHeaderStyle, -1],
[/^.*/, this.unclassifiedStyle, -1]
],
[
[/^\s*[0-9a-f]+\s+/, this.numberStyle, 2],
[/^\s*[0-9a-f]+\s+[0-9a-f]+\s+/, this.numberStyle, 2],
[/^.*/, null, -1]
],
[
[/^REX.W \S+\s+/, this.opcodeStyle, 3],
[/^\S+\s+/, this.opcodeStyle, 3],
[/^\S+$/, this.opcodeStyle, -1],
[/^.*/, null, -1]
],
[
[/^\s+/, null],
[/^[^;]+$/, this.opcodeArgs, -1],
[/^[^;]+/, this.opcodeArgs, 4],
[/^;/, this.commentStyle, 5]
],
[
[/^.+$/, this.commentStyle, -1]
]
];
}
private get numberStyle(): { css: Array<string> } {
return {
css: ['instruction-binary', 'lit']
};
}
private get opcodeStyle(): { css: string } {
return {
css: 'kwd'
};
}
private get unclassifiedStyle(): { css: string } {
return {
css: 'com'
};
}
private get commentStyle(): { css: string } {
return {
css: 'com'
};
}
private get sourcePositionHeaderStyle(): { css: string } {
return {
css: 'com'
};
}
private get blockHeaderStyle(): { associateData(text: string, fragment: HTMLSpanElement) } {
const view = this;
return {
associateData: (text: string, fragment: HTMLSpanElement) => {
if (view.sourceResolver.disassemblyPhase.hasBlockStartInfo()) return false;
const matches = /\d+/.exec(text);
if (!matches) return true;
const blockId = matches[0];
fragment.dataset.blockId = blockId;
fragment.innerHTML = text;
fragment.className = "com block";
return true;
}
};
}
private get opcodeArgs(): { associateData(text: string, fragment: HTMLSpanElement) } {
const view = this;
return {
associateData: (text: string, fragment: HTMLSpanElement) => {
fragment.innerHTML = text;
const replacer = (match, hexOffset) => {
const offset = Number.parseInt(hexOffset, 16);
let keyOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
if (keyOffset == -1) keyOffset = Number(offset);
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
let block = "";
let blockIdData = "";
if (blockIds && blockIds.length > 0) {
block = `B${blockIds.join(",")} `;
blockIdData = `data-block-id="${blockIds.join(",")}"`;
}
return `<span class="tag linkable-text" data-pc-offset="${keyOffset}" ${blockIdData}>${block}${match}</span>`;
};
fragment.innerHTML = text.replace(/<.0?x?([0-9a-fA-F]+)>/g, replacer);
return true;
}
};
}
private get addressStyle(): { associateData(text: string, fragment: HTMLSpanElement) } {
const view = this;
return {
associateData: (text: string, fragment: HTMLSpanElement) => {
const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/);
const offset = Number.parseInt(matches.groups["offset"], 16);
const instructionKind = view.sourceResolver.instructionsPhase
.getInstructionKindForPCOffset(offset);
fragment.dataset.instructionKind = instructionKind;
fragment.title = view.sourceResolver.instructionsPhase
.instructionKindToReadableName(instructionKind);
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
const blockIdElement = document.createElement("span");
blockIdElement.className = "block-id com linkable-text";
blockIdElement.innerText = "";
if (blockIds && blockIds.length > 0) {
blockIds.forEach(blockId => view.addHtmlElementForBlockId(blockId, fragment));
blockIdElement.innerText = `B${blockIds.join(",")}:`;
blockIdElement.dataset.blockId = `${blockIds.join(",")}`;
}
fragment.appendChild(blockIdElement);
const addressElement = document.createElement("span");
addressElement.className = "instruction-address";
addressElement.innerText = matches.groups["address"];
const offsetElement = document.createElement("span");
offsetElement.innerText = matches.groups["offset"];
fragment.appendChild(addressElement);
fragment.appendChild(document.createTextNode(matches.groups["addressSpace"]));
fragment.appendChild(offsetElement);
fragment.appendChild(document.createTextNode(matches.groups["offsetSpace"]));
fragment.classList.add("tag");
if (!Number.isNaN(offset)) {
let pcOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
if (pcOffset == -1) pcOffset = Number(offset);
fragment.dataset.pcOffset = String(pcOffset);
addressElement.classList.add("linkable-text");
offsetElement.classList.add("linkable-text");
}
return true;
}
};
}
}

View File

@ -85,7 +85,7 @@ export class SequenceView extends TextView {
if (this.showRangeView) this.rangeView.onresize();
}
initializeContent(sequence, rememberedSelection: SelectionStorage) {
initializeContent(sequence: SequencePhase, rememberedSelection: SelectionStorage) {
this.divNode.innerHTML = '';
this.sequence = sequence;
this.searchInfo = [];

View File

@ -2,12 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { PhaseView } from "./view";
import { isIterable } from "../common/util";
import { PhaseView } from "./view";
import { SelectionMap } from "../selection/selection-map";
import { SourceResolver } from "../source-resolver";
import { SelectionBroker } from "../selection/selection-broker";
import { ViewElements } from "../common/view-elements";
import { DisassemblyPhase } from "../phases/disassembly-phase";
import { SchedulePhase } from "../phases/schedule-phase";
import { SequencePhase } from "../phases/sequence-phase";
import {
NodeSelectionHandler,
BlockSelectionHandler,
@ -15,6 +18,7 @@ import {
ClearableHandler
} from "../selection/selection-handler";
type GenericTextPhase = DisassemblyPhase | SchedulePhase | SequencePhase;
export abstract class TextView extends PhaseView {
selectionHandler: NodeSelectionHandler & ClearableHandler;
blockSelectionHandler: BlockSelectionHandler & ClearableHandler;
@ -32,43 +36,42 @@ export abstract class TextView extends PhaseView {
sourceResolver: SourceResolver;
broker: SelectionBroker;
constructor(id, broker) {
super(id);
const view = this;
view.broker = broker;
view.sourceResolver = broker.sourceResolver;
view.textListNode = view.divNode.getElementsByTagName("ul")[0];
view.patterns = null;
view.instructionIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
view.nodeIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
view.blockIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
view.blockIdToNodeIds = new Map<string, Array<string>>();
view.nodeIdToBlockId = new Array<string>();
constructor(parent: HTMLDivElement, broker: SelectionBroker) {
super(parent);
this.broker = broker;
this.sourceResolver = broker.sourceResolver;
this.textListNode = this.divNode.getElementsByTagName("ul")[0];
this.instructionIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
this.nodeIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
this.blockIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
this.blockIdToNodeIds = new Map<string, Array<string>>();
this.nodeIdToBlockId = new Array<string>();
view.selection = new SelectionMap(node => String(node));
view.blockSelection = new SelectionMap(block => String(block));
view.registerAllocationSelection = new SelectionMap(register => String(register));
this.selection = new SelectionMap(node => String(node));
this.blockSelection = new SelectionMap(block => String(block));
this.registerAllocationSelection = new SelectionMap(register => String(register));
view.selectionHandler = this.initializeNodeSelectionHandler();
view.blockSelectionHandler = this.initializeBlockSelectionHandler();
view.registerAllocationSelectionHandler = this.initializeRegisterAllocationSelectionHandler();
this.selectionHandler = this.initializeNodeSelectionHandler();
this.blockSelectionHandler = this.initializeBlockSelectionHandler();
this.registerAllocationSelectionHandler = this.initializeRegisterAllocationSelectionHandler();
broker.addNodeHandler(view.selectionHandler);
broker.addBlockHandler(view.blockSelectionHandler);
broker.addRegisterAllocatorHandler(view.registerAllocationSelectionHandler);
broker.addNodeHandler(this.selectionHandler);
broker.addBlockHandler(this.blockSelectionHandler);
broker.addRegisterAllocatorHandler(this.registerAllocationSelectionHandler);
view.divNode.addEventListener("click", e => {
this.divNode.addEventListener("click", e => {
if (!e.shiftKey) {
view.selectionHandler.clear();
this.selectionHandler.clear();
}
e.stopPropagation();
});
}
// TODO (danylo boiko) Change data type
public initializeContent(data: any, _): void {
public initializeContent(genericPhase: GenericTextPhase, _): void {
this.clearText();
this.processText(data);
if (!(genericPhase instanceof SequencePhase)) {
this.processText(genericPhase.data);
}
this.show();
}
@ -120,7 +123,7 @@ export abstract class TextView extends PhaseView {
}
public processLine(line: string): Array<HTMLSpanElement> {
const result = new Array<HTMLSpanElement>();
const fragments = new Array<HTMLSpanElement>();
let patternSet = 0;
while (true) {
const beforeLine = line;
@ -132,7 +135,7 @@ export abstract class TextView extends PhaseView {
const text = matches[0];
if (text.length > 0) {
const fragment = this.createFragment(matches[0], style);
if (fragment !== null) result.push(fragment);
if (fragment !== null) fragments.push(fragment);
}
line = line.substr(matches[0].length);
}
@ -144,7 +147,7 @@ export abstract class TextView extends PhaseView {
if (nextPatternSet != -1) {
throw (`illegal parsing state in text-view in patternSet: ${patternSet}`);
}
return result;
return fragments;
}
patternSet = nextPatternSet;
break;
@ -201,10 +204,6 @@ export abstract class TextView extends PhaseView {
return fragment;
}
protected setPatterns(patterns: Array<Array<any>>): void {
this.patterns = patterns;
}
private initializeNodeSelectionHandler(): NodeSelectionHandler & ClearableHandler {
const view = this;
return {