v8/tools/system-analyzer/view/code-panel.mjs
Camillo Bruni 1ca9a77095 [tools][system-analyzer] Add FeedbackVector support
Log FeedbackVectors for optimised code and show them in the code-panel.

Drive-by-fixes:
- Fix off-by-one in SourcePositionIteration, making sure we always show
  the last element
- Ensure we process all SourcePositions in SourcePositionIteration
- Fix first load error in script-panel
- Allow expanding all text with SHIFT-click

Bug: v8:10644
Change-Id: Ic40a36ea82f0dfa2386c3196f27ca6978cf23643
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3245931
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#77567}
2021-10-27 09:40:41 +00:00

170 lines
5.0 KiB
JavaScript

// Copyright 2020 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 {SelectRelatedEvent} from './events.mjs';
import {CollapsableElement, DOM, formatBytes, formatMicroSeconds} from './helper.mjs';
const kRegisters = ['rsp', 'rbp', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi'];
// Add Interpreter and x64 registers
for (let i = 0; i < 14; i++) {
kRegisters.push(`r${i}`);
}
DOM.defineCustomElement('view/code-panel',
(templateText) =>
class CodePanel extends CollapsableElement {
_timeline;
_selectedEntries;
_entry;
constructor() {
super(templateText);
this._propertiesNode = this.$('#properties');
this._codeSelectNode = this.$('#codeSelect');
this._disassemblyNode = this.$('#disassembly');
this._feedbackVectorNode = this.$('#feedbackVector');
this._sourceNode = this.$('#sourceCode');
this._registerSelector = new RegisterSelector(this._disassemblyNode);
this._codeSelectNode.onchange = this._handleSelectCode.bind(this);
this.$('#selectedRelatedButton').onclick =
this._handleSelectRelated.bind(this)
}
set timeline(timeline) {
this._timeline = timeline;
this.$('.panel').style.display = timeline.isEmpty() ? 'none' : 'inherit';
this.requestUpdate();
}
set selectedEntries(entries) {
this._selectedEntries = entries;
this.entry = entries.first();
}
set entry(entry) {
this._entry = entry;
if (!entry) {
this._propertiesNode.propertyDict = {};
} else {
this._propertiesNode.propertyDict = {
'__this__': entry,
functionName: entry.functionName,
size: formatBytes(entry.size),
creationTime: formatMicroSeconds(entry.time / 1000),
sourcePosition: entry.sourcePosition,
script: entry.script,
type: entry.type,
kind: entry.kindName,
variants: entry.variants.length > 1 ? entry.variants : undefined,
};
}
this.requestUpdate();
}
_update() {
this._updateSelect();
this._updateDisassembly();
this._updateFeedbackVector();
this._sourceNode.innerText = this._entry?.source ?? '';
}
_updateFeedbackVector() {
if (!this._entry?.feedbackVector) {
this._feedbackVectorNode.propertyDict = {};
} else {
const dict = this._entry.feedbackVector.toolTipDict;
delete dict.title;
delete dict.code;
this._feedbackVectorNode.propertyDict = dict;
}
}
_updateDisassembly() {
if (!this._entry?.code) {
this._disassemblyNode.innerText = '';
return;
}
const rawCode = this._entry?.code;
try {
this._disassemblyNode.innerText = rawCode;
let formattedCode = this._disassemblyNode.innerHTML;
for (let register of kRegisters) {
const button = `<span class="register ${register}">${register}</span>`
formattedCode = formattedCode.replaceAll(register, button);
}
// Let's replace the base-address since it doesn't add any value.
// TODO
this._disassemblyNode.innerHTML = formattedCode;
} catch (e) {
console.error(e);
this._disassemblyNode.innerText = rawCode;
}
}
_updateSelect() {
const select = this._codeSelectNode;
if (select.data === this._selectedEntries) return;
select.data = this._selectedEntries;
select.options.length = 0;
const sorted =
this._selectedEntries.slice().sort((a, b) => a.time - b.time);
for (const code of this._selectedEntries) {
const option = DOM.element('option');
option.text = this._entrySummary(code);
option.data = code;
select.add(option);
}
}
_entrySummary(code) {
if (code.isBuiltinKind) {
return `${code.functionName}(...) t=${
formatMicroSeconds(code.time)} size=${formatBytes(code.size)}`;
}
return `${code.functionName}(...) t=${formatMicroSeconds(code.time)} size=${
formatBytes(code.size)} script=${code.script?.toString()}`;
}
_handleSelectCode() {
this.entry = this._codeSelectNode.selectedOptions[0].data;
}
_handleSelectRelated(e) {
if (!this._entry) return;
this.dispatchEvent(new SelectRelatedEvent(this._entry));
}
});
class RegisterSelector {
_currentRegister;
constructor(node) {
this._node = node;
this._node.onmousemove = this._handleDisassemblyMouseMove.bind(this);
}
_handleDisassemblyMouseMove(event) {
const target = event.target;
if (!target.classList.contains('register')) {
this._clear();
return;
};
this._select(target.innerText);
}
_clear() {
if (this._currentRegister == undefined) return;
for (let node of this._node.querySelectorAll('.register')) {
node.classList.remove('selected');
}
}
_select(register) {
if (register == this._currentRegister) return;
this._clear();
this._currentRegister = register;
for (let node of this._node.querySelectorAll(`.register.${register}`)) {
node.classList.add('selected');
}
}
}