1// Copyright 2018 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import { Sequence } from "../src/source-resolver"; 6import { createElement } from "../src/util"; 7import { TextView } from "../src/text-view"; 8import { RangeView } from "../src/range-view"; 9 10export class SequenceView extends TextView { 11 sequence: Sequence; 12 searchInfo: Array<any>; 13 phaseSelect: HTMLSelectElement; 14 numInstructions: number; 15 currentPhaseIndex: number; 16 phaseIndexes: Set<number>; 17 isShown: boolean; 18 rangeView: RangeView; 19 showRangeView: boolean; 20 toggleRangeViewEl: HTMLElement; 21 22 createViewElement() { 23 const pane = document.createElement('div'); 24 pane.setAttribute('id', "sequence"); 25 pane.classList.add("scrollable"); 26 pane.setAttribute("tabindex", "0"); 27 return pane; 28 } 29 30 constructor(parentId, broker) { 31 super(parentId, broker); 32 this.numInstructions = 0; 33 this.phaseIndexes = new Set<number>(); 34 this.isShown = false; 35 this.showRangeView = false; 36 this.rangeView = null; 37 this.toggleRangeViewEl = this.elementForToggleRangeView(); 38 } 39 40 attachSelection(s) { 41 const view = this; 42 if (!(s instanceof Set)) return; 43 view.selectionHandler.clear(); 44 view.blockSelectionHandler.clear(); 45 const selected = new Array(); 46 for (const key of s) selected.push(key); 47 view.selectionHandler.select(selected, true); 48 } 49 50 detachSelection() { 51 this.blockSelection.clear(); 52 return this.selection.detachSelection(); 53 } 54 55 show() { 56 this.currentPhaseIndex = this.phaseSelect.selectedIndex; 57 if (!this.isShown) { 58 this.isShown = true; 59 this.phaseIndexes.add(this.currentPhaseIndex); 60 this.container.appendChild(this.divNode); 61 this.container.getElementsByClassName("graph-toolbox")[0].appendChild(this.toggleRangeViewEl); 62 } 63 if (this.showRangeView) this.rangeView.show(); 64 } 65 66 hide() { 67 // A single SequenceView object is used for two phases (i.e before and after 68 // register allocation), tracking the indexes lets the redundant hides and 69 // shows be avoided when switching between the two. 70 this.currentPhaseIndex = this.phaseSelect.selectedIndex; 71 if (!this.phaseIndexes.has(this.currentPhaseIndex)) { 72 this.isShown = false; 73 this.container.removeChild(this.divNode); 74 this.container.getElementsByClassName("graph-toolbox")[0].removeChild(this.toggleRangeViewEl); 75 if (this.showRangeView) this.rangeView.hide(); 76 } 77 } 78 79 onresize() { 80 if (this.showRangeView) this.rangeView.onresize(); 81 } 82 83 initializeContent(data, rememberedSelection) { 84 this.divNode.innerHTML = ''; 85 this.sequence = data.sequence; 86 this.searchInfo = []; 87 this.divNode.onclick = (e: MouseEvent) => { 88 if (!(e.target instanceof HTMLElement)) return; 89 const instructionId = Number.parseInt(e.target.dataset.instructionId, 10); 90 if (!instructionId) return; 91 if (!e.shiftKey) this.broker.broadcastClear(null); 92 this.broker.broadcastInstructionSelect(null, [instructionId], true); 93 }; 94 this.phaseSelect = (document.getElementById('phase-select') as HTMLSelectElement); 95 this.currentPhaseIndex = this.phaseSelect.selectedIndex; 96 97 this.addBlocks(this.sequence.blocks); 98 const lastBlock = this.sequence.blocks[this.sequence.blocks.length - 1]; 99 this.numInstructions = lastBlock.instructions[lastBlock.instructions.length - 1].id + 1; 100 this.addRangeView(); 101 this.attachSelection(rememberedSelection); 102 this.show(); 103 } 104 105 elementForBlock(block) { 106 const view = this; 107 108 function mkLinkHandler(id, handler) { 109 return function (e) { 110 e.stopPropagation(); 111 if (!e.shiftKey) { 112 handler.clear(); 113 } 114 handler.select(["" + id], true); 115 }; 116 } 117 118 function mkBlockLinkHandler(blockId) { 119 return mkLinkHandler(blockId, view.blockSelectionHandler); 120 } 121 122 function mkInstructionLinkHandler(instrId) { 123 return mkLinkHandler(instrId, view.registerAllocationSelectionHandler); 124 } 125 126 function mkOperandLinkHandler(text) { 127 return mkLinkHandler(text, view.selectionHandler); 128 } 129 130 function elementForOperandWithSpan(span, text, searchInfo, isVirtual) { 131 const selectionText = isVirtual ? "virt_" + text : text; 132 span.onclick = mkOperandLinkHandler(selectionText); 133 searchInfo.push(text); 134 view.addHtmlElementForNodeId(selectionText, span); 135 const container = createElement("div", ""); 136 container.appendChild(span); 137 return container; 138 } 139 140 function elementForOperand(operand, searchInfo) { 141 let isVirtual = false; 142 let className = "parameter tag clickable " + operand.type; 143 if (operand.text[0] == 'v' && !(operand.tooltip && operand.tooltip.includes("Float"))) { 144 isVirtual = true; 145 className += " virtual-reg"; 146 } 147 const span = createElement("span", className, operand.text); 148 if (operand.tooltip) { 149 span.setAttribute("title", operand.tooltip); 150 } 151 return elementForOperandWithSpan(span, operand.text, searchInfo, isVirtual); 152 } 153 154 function elementForPhiOperand(text, searchInfo) { 155 const span = createElement("span", "parameter tag clickable virtual-reg", text); 156 return elementForOperandWithSpan(span, text, searchInfo, true); 157 } 158 159 function elementForInstruction(instruction, searchInfo) { 160 const instNodeEl = createElement("div", "instruction-node"); 161 162 const instId = createElement("div", "instruction-id", instruction.id); 163 const offsets = view.sourceResolver.instructionToPcOffsets(instruction.id); 164 instId.classList.add("clickable"); 165 view.addHtmlElementForInstructionId(instruction.id, instId); 166 instId.onclick = mkInstructionLinkHandler(instruction.id); 167 instId.dataset.instructionId = instruction.id; 168 if (offsets) { 169 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)}.`); 170 } 171 instNodeEl.appendChild(instId); 172 173 const instContentsEl = createElement("div", "instruction-contents"); 174 instNodeEl.appendChild(instContentsEl); 175 176 // Print gap moves. 177 const gapEl = createElement("div", "gap", "gap"); 178 let hasGaps = false; 179 for (const gap of instruction.gaps) { 180 const moves = createElement("div", "comma-sep-list gap-move"); 181 for (const move of gap) { 182 hasGaps = true; 183 const moveEl = createElement("div", "move"); 184 const destinationEl = elementForOperand(move[0], searchInfo); 185 moveEl.appendChild(destinationEl); 186 const assignEl = createElement("div", "assign", "="); 187 moveEl.appendChild(assignEl); 188 const sourceEl = elementForOperand(move[1], searchInfo); 189 moveEl.appendChild(sourceEl); 190 moves.appendChild(moveEl); 191 } 192 gapEl.appendChild(moves); 193 } 194 if (hasGaps) { 195 instContentsEl.appendChild(gapEl); 196 } 197 198 const instEl = createElement("div", "instruction"); 199 instContentsEl.appendChild(instEl); 200 201 if (instruction.outputs.length > 0) { 202 const outputs = createElement("div", "comma-sep-list input-output-list"); 203 for (const output of instruction.outputs) { 204 const outputEl = elementForOperand(output, searchInfo); 205 outputs.appendChild(outputEl); 206 } 207 instEl.appendChild(outputs); 208 const assignEl = createElement("div", "assign", "="); 209 instEl.appendChild(assignEl); 210 } 211 212 const text = instruction.opcode + instruction.flags; 213 const instLabel = createElement("div", "node-label", text); 214 if (instruction.opcode == "ArchNop" && instruction.outputs.length == 1 && instruction.outputs[0].tooltip) { 215 instLabel.innerText = instruction.outputs[0].tooltip; 216 } 217 218 searchInfo.push(text); 219 view.addHtmlElementForNodeId(text, instLabel); 220 instEl.appendChild(instLabel); 221 222 if (instruction.inputs.length > 0) { 223 const inputs = createElement("div", "comma-sep-list input-output-list"); 224 for (const input of instruction.inputs) { 225 const inputEl = elementForOperand(input, searchInfo); 226 inputs.appendChild(inputEl); 227 } 228 instEl.appendChild(inputs); 229 } 230 231 if (instruction.temps.length > 0) { 232 const temps = createElement("div", "comma-sep-list input-output-list temps"); 233 for (const temp of instruction.temps) { 234 const tempEl = elementForOperand(temp, searchInfo); 235 temps.appendChild(tempEl); 236 } 237 instEl.appendChild(temps); 238 } 239 240 return instNodeEl; 241 } 242 243 const sequenceBlock = createElement("div", "schedule-block"); 244 sequenceBlock.classList.toggle("deferred", block.deferred); 245 246 const blockId = createElement("div", "block-id com clickable", block.id); 247 blockId.onclick = mkBlockLinkHandler(block.id); 248 sequenceBlock.appendChild(blockId); 249 const blockPred = createElement("div", "predecessor-list block-list comma-sep-list"); 250 for (const pred of block.predecessors) { 251 const predEl = createElement("div", "block-id com clickable", pred); 252 predEl.onclick = mkBlockLinkHandler(pred); 253 blockPred.appendChild(predEl); 254 } 255 if (block.predecessors.length > 0) sequenceBlock.appendChild(blockPred); 256 const phis = createElement("div", "phis"); 257 sequenceBlock.appendChild(phis); 258 259 const phiLabel = createElement("div", "phi-label", "phi:"); 260 phis.appendChild(phiLabel); 261 262 const phiContents = createElement("div", "phi-contents"); 263 phis.appendChild(phiContents); 264 265 for (const phi of block.phis) { 266 const phiEl = createElement("div", "phi"); 267 phiContents.appendChild(phiEl); 268 269 const outputEl = elementForOperand(phi.output, this.searchInfo); 270 phiEl.appendChild(outputEl); 271 272 const assignEl = createElement("div", "assign", "="); 273 phiEl.appendChild(assignEl); 274 275 for (const input of phi.operands) { 276 const inputEl = elementForPhiOperand(input, this.searchInfo); 277 phiEl.appendChild(inputEl); 278 } 279 } 280 281 const instructions = createElement("div", "instructions"); 282 for (const instruction of block.instructions) { 283 instructions.appendChild(elementForInstruction(instruction, this.searchInfo)); 284 } 285 sequenceBlock.appendChild(instructions); 286 const blockSucc = createElement("div", "successor-list block-list comma-sep-list"); 287 for (const succ of block.successors) { 288 const succEl = createElement("div", "block-id com clickable", succ); 289 succEl.onclick = mkBlockLinkHandler(succ); 290 blockSucc.appendChild(succEl); 291 } 292 if (block.successors.length > 0) sequenceBlock.appendChild(blockSucc); 293 this.addHtmlElementForBlockId(block.id, sequenceBlock); 294 return sequenceBlock; 295 } 296 297 addBlocks(blocks) { 298 for (const block of blocks) { 299 const blockEl = this.elementForBlock(block); 300 this.divNode.appendChild(blockEl); 301 } 302 } 303 304 addRangeView() { 305 const preventRangeView = reason => { 306 const toggleRangesInput = this.toggleRangeViewEl.firstChild as HTMLInputElement; 307 if (this.rangeView) { 308 toggleRangesInput.checked = false; 309 this.toggleRangeView(toggleRangesInput); 310 } 311 toggleRangesInput.disabled = true; 312 this.toggleRangeViewEl.style.textDecoration = "line-through"; 313 this.toggleRangeViewEl.setAttribute("title", reason); 314 }; 315 316 if (this.sequence.register_allocation) { 317 if (!this.rangeView) { 318 this.rangeView = new RangeView(this); 319 } 320 const source = this.sequence.register_allocation; 321 if (source.fixedLiveRanges.size == 0 && source.liveRanges.size == 0) { 322 preventRangeView("No live ranges to show"); 323 } else if (this.numInstructions >= 249) { 324 // This is due to the css grid-column being limited to 1000 columns. 325 // Performance issues would otherwise impose some limit. 326 // TODO(george.wort@arm.com): Allow the user to specify an instruction range 327 // to display that spans less than 249 instructions. 328 preventRangeView( 329 "Live range display is only supported for sequences with less than 249 instructions"); 330 } 331 if (this.showRangeView) { 332 this.rangeView.initializeContent(this.sequence.blocks); 333 } 334 } else { 335 preventRangeView("No live range data provided"); 336 } 337 } 338 339 elementForToggleRangeView() { 340 const toggleRangeViewEl = createElement("label", "", "show live ranges"); 341 const toggleRangesInput = createElement("input", "range-toggle-show") as HTMLInputElement; 342 toggleRangesInput.setAttribute("type", "checkbox"); 343 toggleRangesInput.oninput = () => this.toggleRangeView(toggleRangesInput); 344 toggleRangeViewEl.insertBefore(toggleRangesInput, toggleRangeViewEl.firstChild); 345 return toggleRangeViewEl; 346 } 347 348 toggleRangeView(toggleRangesInput: HTMLInputElement) { 349 toggleRangesInput.disabled = true; 350 this.showRangeView = toggleRangesInput.checked; 351 if (this.showRangeView) { 352 this.rangeView.initializeContent(this.sequence.blocks); 353 this.rangeView.show(); 354 } else { 355 this.rangeView.hide(); 356 } 357 window.dispatchEvent(new Event('resize')); 358 toggleRangesInput.disabled = false; 359 } 360 361 searchInputAction(searchBar, e) { 362 e.stopPropagation(); 363 this.selectionHandler.clear(); 364 const query = searchBar.value; 365 if (query.length == 0) return; 366 const select = []; 367 window.sessionStorage.setItem("lastSearch", query); 368 const reg = new RegExp(query); 369 for (const item of this.searchInfo) { 370 if (reg.exec(item) != null) { 371 select.push(item); 372 } 373 } 374 this.selectionHandler.select(select, true); 375 } 376} 377