|  | // Copyright 2018 the V8 project authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | import { Sequence } from "../src/source-resolver"; | 
|  | import { createElement } from "../src/util"; | 
|  | import { TextView } from "../src/text-view"; | 
|  | import { RangeView } from "../src/range-view"; | 
|  |  | 
|  | export class SequenceView extends TextView { | 
|  | sequence: Sequence; | 
|  | searchInfo: Array<any>; | 
|  | phaseSelect: HTMLSelectElement; | 
|  | numInstructions: number; | 
|  | currentPhaseIndex: number; | 
|  | phaseIndexes: Set<number>; | 
|  | isShown: boolean; | 
|  | rangeView: RangeView; | 
|  | showRangeView: boolean; | 
|  | toggleRangeViewEl: HTMLElement; | 
|  |  | 
|  | createViewElement() { | 
|  | const pane = document.createElement('div'); | 
|  | pane.setAttribute('id', "sequence"); | 
|  | pane.classList.add("scrollable"); | 
|  | pane.setAttribute("tabindex", "0"); | 
|  | return pane; | 
|  | } | 
|  |  | 
|  | constructor(parentId, broker) { | 
|  | super(parentId, broker); | 
|  | this.numInstructions = 0; | 
|  | this.phaseIndexes = new Set<number>(); | 
|  | this.isShown = false; | 
|  | this.showRangeView = false; | 
|  | this.rangeView = null; | 
|  | this.toggleRangeViewEl = this.elementForToggleRangeView(); | 
|  | } | 
|  |  | 
|  | attachSelection(s) { | 
|  | const view = this; | 
|  | if (!(s instanceof Set)) return; | 
|  | view.selectionHandler.clear(); | 
|  | view.blockSelectionHandler.clear(); | 
|  | const selected = new Array(); | 
|  | for (const key of s) selected.push(key); | 
|  | view.selectionHandler.select(selected, true); | 
|  | } | 
|  |  | 
|  | detachSelection() { | 
|  | this.blockSelection.clear(); | 
|  | return this.selection.detachSelection(); | 
|  | } | 
|  |  | 
|  | show() { | 
|  | this.currentPhaseIndex = this.phaseSelect.selectedIndex; | 
|  | if (!this.isShown) { | 
|  | this.isShown = true; | 
|  | this.phaseIndexes.add(this.currentPhaseIndex); | 
|  | this.container.appendChild(this.divNode); | 
|  | this.container.getElementsByClassName("graph-toolbox")[0].appendChild(this.toggleRangeViewEl); | 
|  | } | 
|  | if (this.showRangeView) this.rangeView.show(); | 
|  | } | 
|  |  | 
|  | hide() { | 
|  | // A single SequenceView object is used for two phases (i.e before and after | 
|  | // register allocation), tracking the indexes lets the redundant hides and | 
|  | // shows be avoided when switching between the two. | 
|  | this.currentPhaseIndex = this.phaseSelect.selectedIndex; | 
|  | if (!this.phaseIndexes.has(this.currentPhaseIndex)) { | 
|  | this.isShown = false; | 
|  | this.container.removeChild(this.divNode); | 
|  | this.container.getElementsByClassName("graph-toolbox")[0].removeChild(this.toggleRangeViewEl); | 
|  | if (this.showRangeView) this.rangeView.hide(); | 
|  | } | 
|  | } | 
|  |  | 
|  | onresize() { | 
|  | if (this.showRangeView) this.rangeView.onresize(); | 
|  | } | 
|  |  | 
|  | initializeContent(data, rememberedSelection) { | 
|  | this.divNode.innerHTML = ''; | 
|  | this.sequence = data.sequence; | 
|  | this.searchInfo = []; | 
|  | this.divNode.onclick = (e: MouseEvent) => { | 
|  | if (!(e.target instanceof HTMLElement)) return; | 
|  | const instructionId = Number.parseInt(e.target.dataset.instructionId, 10); | 
|  | if (!instructionId) return; | 
|  | if (!e.shiftKey) this.broker.broadcastClear(null); | 
|  | this.broker.broadcastInstructionSelect(null, [instructionId], true); | 
|  | }; | 
|  | this.phaseSelect = (document.getElementById('phase-select') as HTMLSelectElement); | 
|  | this.currentPhaseIndex = this.phaseSelect.selectedIndex; | 
|  |  | 
|  | this.addBlocks(this.sequence.blocks); | 
|  | const lastBlock = this.sequence.blocks[this.sequence.blocks.length - 1]; | 
|  | this.numInstructions = lastBlock.instructions[lastBlock.instructions.length - 1].id + 1; | 
|  | this.addRangeView(); | 
|  | this.attachSelection(rememberedSelection); | 
|  | this.show(); | 
|  | } | 
|  |  | 
|  | elementForBlock(block) { | 
|  | const view = this; | 
|  |  | 
|  | function mkLinkHandler(id, handler) { | 
|  | return function (e) { | 
|  | e.stopPropagation(); | 
|  | if (!e.shiftKey) { | 
|  | handler.clear(); | 
|  | } | 
|  | handler.select(["" + id], true); | 
|  | }; | 
|  | } | 
|  |  | 
|  | function mkBlockLinkHandler(blockId) { | 
|  | return mkLinkHandler(blockId, view.blockSelectionHandler); | 
|  | } | 
|  |  | 
|  | function mkOperandLinkHandler(text) { | 
|  | return mkLinkHandler(text, view.selectionHandler); | 
|  | } | 
|  |  | 
|  | function elementForOperandWithSpan(span, text, searchInfo, isVirtual) { | 
|  | const selectionText = isVirtual ? "virt_" + text : text; | 
|  | span.onclick = mkOperandLinkHandler(selectionText); | 
|  | searchInfo.push(text); | 
|  | view.addHtmlElementForNodeId(selectionText, span); | 
|  | const container = createElement("div", ""); | 
|  | container.appendChild(span); | 
|  | return container; | 
|  | } | 
|  |  | 
|  | function elementForOperand(operand, searchInfo) { | 
|  | let isVirtual = false; | 
|  | let className = "parameter tag clickable " + operand.type; | 
|  | if (operand.text[0] == 'v' && !(operand.tooltip && operand.tooltip.includes("Float"))) { | 
|  | isVirtual = true; | 
|  | className += " virtual-reg"; | 
|  | } | 
|  | const span = createElement("span", className, operand.text); | 
|  | if (operand.tooltip) { | 
|  | span.setAttribute("title", operand.tooltip); | 
|  | } | 
|  | return elementForOperandWithSpan(span, operand.text, searchInfo, isVirtual); | 
|  | } | 
|  |  | 
|  | function elementForPhiOperand(text, searchInfo) { | 
|  | const span = createElement("span", "parameter tag clickable virtual-reg", text); | 
|  | return elementForOperandWithSpan(span, text, searchInfo, true); | 
|  | } | 
|  |  | 
|  | function elementForInstruction(instruction, searchInfo) { | 
|  | const instNodeEl = createElement("div", "instruction-node"); | 
|  |  | 
|  | const instId = createElement("div", "instruction-id", instruction.id); | 
|  | const offsets = view.sourceResolver.instructionToPcOffsets(instruction.id); | 
|  | instId.classList.add("clickable"); | 
|  | instId.dataset.instructionId = instruction.id; | 
|  | if (offsets) { | 
|  | instId.setAttribute("title", `This instruction generated gap code at pc-offset 0x${offsets.gap.toString(16)}, code at pc-offset 0x${offsets.arch.toString(16)}, condition handling at pc-offset 0x${offsets.condition.toString(16)}.`); | 
|  | } | 
|  | instNodeEl.appendChild(instId); | 
|  |  | 
|  | const instContentsEl = createElement("div", "instruction-contents"); | 
|  | instNodeEl.appendChild(instContentsEl); | 
|  |  | 
|  | // Print gap moves. | 
|  | const gapEl = createElement("div", "gap", "gap"); | 
|  | let hasGaps = false; | 
|  | for (const gap of instruction.gaps) { | 
|  | const moves = createElement("div", "comma-sep-list gap-move"); | 
|  | for (const move of gap) { | 
|  | hasGaps = true; | 
|  | const moveEl = createElement("div", "move"); | 
|  | const destinationEl = elementForOperand(move[0], searchInfo); | 
|  | moveEl.appendChild(destinationEl); | 
|  | const assignEl = createElement("div", "assign", "="); | 
|  | moveEl.appendChild(assignEl); | 
|  | const sourceEl = elementForOperand(move[1], searchInfo); | 
|  | moveEl.appendChild(sourceEl); | 
|  | moves.appendChild(moveEl); | 
|  | } | 
|  | gapEl.appendChild(moves); | 
|  | } | 
|  | if (hasGaps) { | 
|  | instContentsEl.appendChild(gapEl); | 
|  | } | 
|  |  | 
|  | const instEl = createElement("div", "instruction"); | 
|  | instContentsEl.appendChild(instEl); | 
|  |  | 
|  | if (instruction.outputs.length > 0) { | 
|  | const outputs = createElement("div", "comma-sep-list input-output-list"); | 
|  | for (const output of instruction.outputs) { | 
|  | const outputEl = elementForOperand(output, searchInfo); | 
|  | outputs.appendChild(outputEl); | 
|  | } | 
|  | instEl.appendChild(outputs); | 
|  | const assignEl = createElement("div", "assign", "="); | 
|  | instEl.appendChild(assignEl); | 
|  | } | 
|  |  | 
|  | const text = instruction.opcode + instruction.flags; | 
|  | const instLabel = createElement("div", "node-label", text); | 
|  | if (instruction.opcode == "ArchNop" && instruction.outputs.length == 1 && instruction.outputs[0].tooltip) { | 
|  | instLabel.innerText = instruction.outputs[0].tooltip; | 
|  | } | 
|  |  | 
|  | searchInfo.push(text); | 
|  | view.addHtmlElementForNodeId(text, instLabel); | 
|  | instEl.appendChild(instLabel); | 
|  |  | 
|  | if (instruction.inputs.length > 0) { | 
|  | const inputs = createElement("div", "comma-sep-list input-output-list"); | 
|  | for (const input of instruction.inputs) { | 
|  | const inputEl = elementForOperand(input, searchInfo); | 
|  | inputs.appendChild(inputEl); | 
|  | } | 
|  | instEl.appendChild(inputs); | 
|  | } | 
|  |  | 
|  | if (instruction.temps.length > 0) { | 
|  | const temps = createElement("div", "comma-sep-list input-output-list temps"); | 
|  | for (const temp of instruction.temps) { | 
|  | const tempEl = elementForOperand(temp, searchInfo); | 
|  | temps.appendChild(tempEl); | 
|  | } | 
|  | instEl.appendChild(temps); | 
|  | } | 
|  |  | 
|  | return instNodeEl; | 
|  | } | 
|  |  | 
|  | const sequenceBlock = createElement("div", "schedule-block"); | 
|  | sequenceBlock.classList.toggle("deferred", block.deferred); | 
|  |  | 
|  | const blockId = createElement("div", "block-id com clickable", block.id); | 
|  | blockId.onclick = mkBlockLinkHandler(block.id); | 
|  | sequenceBlock.appendChild(blockId); | 
|  | const blockPred = createElement("div", "predecessor-list block-list comma-sep-list"); | 
|  | for (const pred of block.predecessors) { | 
|  | const predEl = createElement("div", "block-id com clickable", pred); | 
|  | predEl.onclick = mkBlockLinkHandler(pred); | 
|  | blockPred.appendChild(predEl); | 
|  | } | 
|  | if (block.predecessors.length > 0) sequenceBlock.appendChild(blockPred); | 
|  | const phis = createElement("div", "phis"); | 
|  | sequenceBlock.appendChild(phis); | 
|  |  | 
|  | const phiLabel = createElement("div", "phi-label", "phi:"); | 
|  | phis.appendChild(phiLabel); | 
|  |  | 
|  | const phiContents = createElement("div", "phi-contents"); | 
|  | phis.appendChild(phiContents); | 
|  |  | 
|  | for (const phi of block.phis) { | 
|  | const phiEl = createElement("div", "phi"); | 
|  | phiContents.appendChild(phiEl); | 
|  |  | 
|  | const outputEl = elementForOperand(phi.output, this.searchInfo); | 
|  | phiEl.appendChild(outputEl); | 
|  |  | 
|  | const assignEl = createElement("div", "assign", "="); | 
|  | phiEl.appendChild(assignEl); | 
|  |  | 
|  | for (const input of phi.operands) { | 
|  | const inputEl = elementForPhiOperand(input, this.searchInfo); | 
|  | phiEl.appendChild(inputEl); | 
|  | } | 
|  | } | 
|  |  | 
|  | const instructions = createElement("div", "instructions"); | 
|  | for (const instruction of block.instructions) { | 
|  | instructions.appendChild(elementForInstruction(instruction, this.searchInfo)); | 
|  | } | 
|  | sequenceBlock.appendChild(instructions); | 
|  | const blockSucc = createElement("div", "successor-list block-list comma-sep-list"); | 
|  | for (const succ of block.successors) { | 
|  | const succEl = createElement("div", "block-id com clickable", succ); | 
|  | succEl.onclick = mkBlockLinkHandler(succ); | 
|  | blockSucc.appendChild(succEl); | 
|  | } | 
|  | if (block.successors.length > 0) sequenceBlock.appendChild(blockSucc); | 
|  | this.addHtmlElementForBlockId(block.id, sequenceBlock); | 
|  | return sequenceBlock; | 
|  | } | 
|  |  | 
|  | addBlocks(blocks) { | 
|  | for (const block of blocks) { | 
|  | const blockEl = this.elementForBlock(block); | 
|  | this.divNode.appendChild(blockEl); | 
|  | } | 
|  | } | 
|  |  | 
|  | addRangeView() { | 
|  | const preventRangeView = reason => { | 
|  | const toggleRangesInput = this.toggleRangeViewEl.firstChild as HTMLInputElement; | 
|  | if (this.rangeView) { | 
|  | toggleRangesInput.checked = false; | 
|  | this.toggleRangeView(toggleRangesInput); | 
|  | } | 
|  | toggleRangesInput.disabled = true; | 
|  | this.toggleRangeViewEl.style.textDecoration = "line-through"; | 
|  | this.toggleRangeViewEl.setAttribute("title", reason); | 
|  | }; | 
|  |  | 
|  | if (this.sequence.register_allocation) { | 
|  | if (!this.rangeView) { | 
|  | this.rangeView = new RangeView(this); | 
|  | } | 
|  | const source = this.sequence.register_allocation; | 
|  | if (source.fixedLiveRanges.size == 0 && source.liveRanges.size == 0) { | 
|  | 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. | 
|  | preventRangeView( | 
|  | "Live range display is only supported for sequences with less than 249 instructions"); | 
|  | } | 
|  | if (this.showRangeView) { | 
|  | this.rangeView.initializeContent(this.sequence.blocks); | 
|  | } | 
|  | } else { | 
|  | preventRangeView("No live range data provided"); | 
|  | } | 
|  | } | 
|  |  | 
|  | elementForToggleRangeView() { | 
|  | const toggleRangeViewEl = createElement("label", "", "show live ranges"); | 
|  | const toggleRangesInput = createElement("input", "range-toggle-show") as HTMLInputElement; | 
|  | toggleRangesInput.setAttribute("type", "checkbox"); | 
|  | toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput); | 
|  | toggleRangeViewEl.insertBefore(toggleRangesInput, toggleRangeViewEl.firstChild); | 
|  | return toggleRangeViewEl; | 
|  | } | 
|  |  | 
|  | toggleRangeView(toggleRangesInput: HTMLInputElement) { | 
|  | toggleRangesInput.disabled = true; | 
|  | this.showRangeView = toggleRangesInput.checked; | 
|  | if (this.showRangeView) { | 
|  | this.rangeView.initializeContent(this.sequence.blocks); | 
|  | this.rangeView.show(); | 
|  | } else { | 
|  | this.rangeView.hide(); | 
|  | } | 
|  | window.dispatchEvent(new Event('resize')); | 
|  | toggleRangesInput.disabled = false; | 
|  | } | 
|  |  | 
|  | searchInputAction(searchBar, e) { | 
|  | e.stopPropagation(); | 
|  | this.selectionHandler.clear(); | 
|  | const query = searchBar.value; | 
|  | if (query.length == 0) return; | 
|  | const select = []; | 
|  | window.sessionStorage.setItem("lastSearch", query); | 
|  | const reg = new RegExp(query); | 
|  | for (const item of this.searchInfo) { | 
|  | if (reg.exec(item) != null) { | 
|  | select.push(item); | 
|  | } | 
|  | } | 
|  | this.selectionHandler.select(select, true); | 
|  | } | 
|  | } |