• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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