[tools][system-analyzer] Improve Deopt and assembly support
- Add references from CodeLogEntry to DeoptLogEntry - Add simple basic blocks in the disassembly code view Bug: v8:10644 Change-Id: I15f3b56751d515b902185b08f9454be3951ffa48 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3540142 Reviewed-by: Patrick Thier <pthier@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/main@{#79754}
This commit is contained in:
parent
4d7877ae13
commit
f21e2a7f03
@ -64,14 +64,33 @@ export class DeoptLogEntry extends LogEntry {
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeLogEntry extends LogEntry {
|
||||
constructor(type, time, kindName, kind, entry) {
|
||||
class CodeLikeLogEntry extends LogEntry {
|
||||
constructor(type, time, profilerEntry) {
|
||||
super(type, time);
|
||||
this._entry = profilerEntry;
|
||||
profilerEntry.logEntry = this;
|
||||
this._relatedEntries = [];
|
||||
}
|
||||
|
||||
get entry() {
|
||||
return this._entry;
|
||||
}
|
||||
|
||||
add(entry) {
|
||||
this._relatedEntries.push(entry);
|
||||
}
|
||||
|
||||
relatedEntries() {
|
||||
return this._relatedEntries;
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeLogEntry extends CodeLikeLogEntry {
|
||||
constructor(type, time, kindName, kind, profilerEntry) {
|
||||
super(type, time, profilerEntry);
|
||||
this._kind = kind;
|
||||
this._kindName = kindName;
|
||||
this._entry = entry;
|
||||
this._feedbackVector = undefined;
|
||||
entry.logEntry = this;
|
||||
}
|
||||
|
||||
get kind() {
|
||||
@ -90,10 +109,6 @@ export class CodeLogEntry extends LogEntry {
|
||||
return this._kindName;
|
||||
}
|
||||
|
||||
get entry() {
|
||||
return this._entry;
|
||||
}
|
||||
|
||||
get functionName() {
|
||||
return this._entry.functionName ?? this._entry.getRawName();
|
||||
}
|
||||
@ -200,20 +215,15 @@ export class FeedbackVectorEntry extends LogEntry {
|
||||
}
|
||||
}
|
||||
|
||||
export class SharedLibLogEntry extends LogEntry {
|
||||
constructor(entry) {
|
||||
super('SHARED_LIB', 0);
|
||||
this._entry = entry;
|
||||
export class SharedLibLogEntry extends CodeLikeLogEntry {
|
||||
constructor(profilerEntry) {
|
||||
super('SHARED_LIB', 0, profilerEntry);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._entry.name;
|
||||
}
|
||||
|
||||
get entry() {
|
||||
return this._entry;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `SharedLib`;
|
||||
}
|
||||
|
@ -314,12 +314,13 @@ export class Processor extends LogReader {
|
||||
timestamp, codeSize, instructionStart, inliningId, scriptOffset,
|
||||
deoptKind, deoptLocation, deoptReason) {
|
||||
this._lastTimestamp = timestamp;
|
||||
const codeEntry = this._profile.findEntry(instructionStart);
|
||||
const profCodeEntry = this._profile.findEntry(instructionStart);
|
||||
const logEntry = new DeoptLogEntry(
|
||||
deoptKind, timestamp, codeEntry, deoptReason, deoptLocation,
|
||||
deoptKind, timestamp, profCodeEntry, deoptReason, deoptLocation,
|
||||
scriptOffset, instructionStart, codeSize, inliningId);
|
||||
profCodeEntry.logEntry.add(logEntry);
|
||||
this._deoptTimeline.push(logEntry);
|
||||
this.addSourcePosition(codeEntry, logEntry);
|
||||
this.addSourcePosition(profCodeEntry, logEntry);
|
||||
logEntry.functionSourcePosition = logEntry.sourcePosition;
|
||||
// custom parse deopt location
|
||||
if (deoptLocation === '<unknown>') return;
|
||||
@ -328,7 +329,7 @@ export class Processor extends LogReader {
|
||||
if (inlinedPos > 0) {
|
||||
deoptLocation = deoptLocation.substring(0, inlinedPos)
|
||||
}
|
||||
const script = this.getProfileEntryScript(codeEntry);
|
||||
const script = this.getProfileEntryScript(profCodeEntry);
|
||||
if (!script) return;
|
||||
const colSeparator = deoptLocation.lastIndexOf(':');
|
||||
const rowSeparator = deoptLocation.lastIndexOf(':', colSeparator - 1);
|
||||
@ -342,16 +343,16 @@ export class Processor extends LogReader {
|
||||
processFeedbackVector(
|
||||
timestamp, fbv_address, fbv_length, instructionStart, optimization_marker,
|
||||
optimization_tier, invocation_count, profiler_ticks, fbv_string) {
|
||||
const codeEntry = this._profile.findEntry(instructionStart);
|
||||
if (!codeEntry) {
|
||||
const profCodeEntry = this._profile.findEntry(instructionStart);
|
||||
if (!profCodeEntry) {
|
||||
console.warn('Didn\'t find code for FBV', {fbv, instructionStart});
|
||||
return;
|
||||
}
|
||||
const fbv = new FeedbackVectorEntry(
|
||||
timestamp, codeEntry.logEntry, fbv_address, fbv_length,
|
||||
timestamp, profCodeEntry.logEntry, fbv_address, fbv_length,
|
||||
optimization_marker, optimization_tier, invocation_count,
|
||||
profiler_ticks, fbv_string);
|
||||
codeEntry.logEntry.setFeedbackVector(fbv);
|
||||
profCodeEntry.logEntry.setFeedbackVector(fbv);
|
||||
}
|
||||
|
||||
processScriptSource(scriptId, url, source) {
|
||||
@ -488,14 +489,18 @@ export class Processor extends LogReader {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO: use SourcePosition directly.
|
||||
let edge = new Edge(type, name, reason, time, from_, to_);
|
||||
const codeEntry = this._profile.findEntry(pc)
|
||||
to_.entry = codeEntry;
|
||||
let script = this.getProfileEntryScript(codeEntry);
|
||||
if (script) {
|
||||
to_.sourcePosition = script.addSourcePosition(line, column, to_)
|
||||
if (pc) {
|
||||
const profCodeEntry = this._profile.findEntry(pc);
|
||||
if (profCodeEntry) {
|
||||
to_.entry = profCodeEntry;
|
||||
profCodeEntry.logEntry.add(to_);
|
||||
let script = this.getProfileEntryScript(profCodeEntry);
|
||||
if (script) {
|
||||
to_.sourcePosition = script.addSourcePosition(line, column, to_);
|
||||
}
|
||||
}
|
||||
}
|
||||
let edge = new Edge(type, name, reason, time, from_, to_);
|
||||
if (to_.parent !== undefined && to_.parent === from_) {
|
||||
// Fix bug where we double log transitions.
|
||||
console.warn('Fixing up double transition');
|
||||
|
@ -23,6 +23,10 @@ found in the LICENSE file. -->
|
||||
.addr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.basicBlock:hover {
|
||||
background-color: var(--border-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="panel">
|
||||
|
@ -1,23 +1,10 @@
|
||||
// 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 {LinuxCppEntriesProvider} from '../../tickprocessor.mjs';
|
||||
|
||||
import {SelectRelatedEvent} from './events.mjs';
|
||||
import {CollapsableElement, DOM, formatBytes, formatMicroSeconds} from './helper.mjs';
|
||||
|
||||
const kRegisters = ['rsp', 'rbp', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi'];
|
||||
// Make sure we dont match register on bytecode: Star1 or Star2
|
||||
const kAvoidBytecodeOps = '(.*?[^a-zA-Z])'
|
||||
// Look for registers in strings like: movl rbx,[rcx-0x30]
|
||||
const kRegisterRegexp = `(${kRegisters.join('|')}|r[0-9]+)`
|
||||
const kRegisterRegexpSplit =
|
||||
new RegExp(`${kAvoidBytecodeOps}${kRegisterRegexp}`)
|
||||
const kIsRegisterRegexp = new RegExp(`^${kRegisterRegexp}$`);
|
||||
|
||||
const kFullAddressRegexp = /(0x[0-9a-f]{8,})/;
|
||||
const kRelativeAddressRegexp = /([+-]0x[0-9a-f]+)/;
|
||||
const kAnyAddressRegexp = /([+-]?0x[0-9a-f]+)/;
|
||||
|
||||
DOM.defineCustomElement('view/code-panel',
|
||||
(templateText) =>
|
||||
class CodePanel extends CollapsableElement {
|
||||
@ -132,36 +119,145 @@ DOM.defineCustomElement('view/code-panel',
|
||||
}
|
||||
});
|
||||
|
||||
const kRegisters = ['rsp', 'rbp', 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi'];
|
||||
// Make sure we dont match register on bytecode: Star1 or Star2
|
||||
const kAvoidBytecodeOpsRegexpSource = '(.*?[^a-zA-Z])'
|
||||
// Look for registers in strings like: movl rbx,[rcx-0x30]
|
||||
const kRegisterRegexpSource = `(?<register>${kRegisters.join('|')}|r[0-9]+)`
|
||||
const kRegisterSplitRegexp =
|
||||
new RegExp(`${kAvoidBytecodeOpsRegexpSource}${kRegisterRegexpSource}`)
|
||||
const kIsRegisterRegexp = new RegExp(`^${kRegisterRegexpSource}$`);
|
||||
|
||||
const kFullAddressRegexp = /(0x[0-9a-f]{8,})/;
|
||||
const kRelativeAddressRegexp = /([+-]0x[0-9a-f]+)/;
|
||||
const kAnyAddressRegexp = /(?<address>[+-]?0x[0-9a-f]+)/;
|
||||
|
||||
const kJmpRegexp = new RegExp(`jmp ${kRegisterRegexpSource}`);
|
||||
const kMovRegexp =
|
||||
new RegExp(`mov. ${kRegisterRegexpSource},${kAnyAddressRegexp.source}`);
|
||||
|
||||
class AssemblyFormatter {
|
||||
constructor(codeLogEntry) {
|
||||
this._fragment = new DocumentFragment();
|
||||
this._entry = codeLogEntry;
|
||||
codeLogEntry.code.split('\n').forEach(line => this._addLine(line));
|
||||
this._lines = new Map();
|
||||
this._previousLine = undefined;
|
||||
this._parseLines();
|
||||
this._format();
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
return this._fragment;
|
||||
}
|
||||
|
||||
_addLine(line) {
|
||||
_format() {
|
||||
let block = DOM.div(['basicBlock', 'header']);
|
||||
this._lines.forEach(line => {
|
||||
if (!block || line.isBlockStart) {
|
||||
this._fragment.appendChild(block);
|
||||
block = DOM.div('basicBlock');
|
||||
}
|
||||
block.appendChild(line.format())
|
||||
});
|
||||
this._fragment.appendChild(block);
|
||||
}
|
||||
|
||||
_parseLines() {
|
||||
this._entry.code.split('\n').forEach(each => this._parseLine(each));
|
||||
this._findBasicBlocks();
|
||||
}
|
||||
|
||||
_parseLine(line) {
|
||||
const parts = line.split(' ');
|
||||
let lineAddress = 0;
|
||||
if (kFullAddressRegexp.test(parts[0])) {
|
||||
lineAddress = parseInt(parts[0]);
|
||||
// Use unique placeholder for address:
|
||||
let lineAddress = -this._lines.size;
|
||||
for (let part of parts) {
|
||||
if (kFullAddressRegexp.test(part)) {
|
||||
lineAddress = parseInt(part);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const content = DOM.span({textContent: parts.join(' ') + '\n'});
|
||||
let formattedCode = content.innerHTML.split(kRegisterRegexpSplit)
|
||||
const newLine = new AssemblyLine(lineAddress, parts);
|
||||
// special hack for: mov reg 0x...; jmp reg;
|
||||
if (lineAddress <= 0 && this._previousLine) {
|
||||
const jmpMatch = line.match(kJmpRegexp);
|
||||
if (jmpMatch) {
|
||||
const register = jmpMatch.groups.register;
|
||||
const movMatch = this._previousLine.line.match(kMovRegexp);
|
||||
if (movMatch.groups.register === register) {
|
||||
newLine.outgoing.push(movMatch.groups.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._lines.set(lineAddress, newLine);
|
||||
this._previousLine = newLine;
|
||||
}
|
||||
|
||||
_findBasicBlocks() {
|
||||
const lines = Array.from(this._lines.values());
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
let forceBasicBlock = i == 0;
|
||||
if (i > 0 && i < lines.length - 1) {
|
||||
const prevHasAddress = lines[i - 1].address > 0;
|
||||
const currentHasAddress = lines[i].address > 0;
|
||||
const nextHasAddress = lines[i + 1].address > 0;
|
||||
if (prevHasAddress !== currentHasAddress &&
|
||||
currentHasAddress == nextHasAddress) {
|
||||
forceBasicBlock = true;
|
||||
}
|
||||
}
|
||||
if (forceBasicBlock) {
|
||||
// Add fake-incoming address to mark a block start.
|
||||
line.addIncoming(0);
|
||||
}
|
||||
line.outgoing.forEach(address => {
|
||||
const outgoing = this._lines.get(address);
|
||||
if (outgoing) outgoing.addIncoming(line.address);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AssemblyLine {
|
||||
constructor(address, parts) {
|
||||
this.address = address;
|
||||
this.outgoing = [];
|
||||
this.incoming = [];
|
||||
parts.forEach(part => {
|
||||
const fullMatch = part.match(kFullAddressRegexp);
|
||||
if (fullMatch) {
|
||||
let inlineAddress = parseInt(fullMatch[0]);
|
||||
if (inlineAddress != this.address) this.outgoing.push(inlineAddress);
|
||||
if (Number.isNaN(inlineAddress)) throw 'invalid address';
|
||||
} else if (kRelativeAddressRegexp.test(part)) {
|
||||
this.outgoing.push(this._toAbsoluteAddress(part));
|
||||
}
|
||||
});
|
||||
this.line = parts.join(' ');
|
||||
}
|
||||
|
||||
get isBlockStart() {
|
||||
return this.incoming.length > 0;
|
||||
}
|
||||
|
||||
addIncoming(address) {
|
||||
this.incoming.push(address);
|
||||
}
|
||||
|
||||
format() {
|
||||
const content = DOM.span({textContent: this.line + '\n'});
|
||||
let formattedCode = content.innerHTML.split(kRegisterSplitRegexp)
|
||||
.map(part => this._formatRegisterPart(part))
|
||||
.join('');
|
||||
formattedCode = formattedCode.split(kAnyAddressRegexp)
|
||||
.map(
|
||||
(part, index) => this._formatAddressPart(
|
||||
part, index, lineAddress))
|
||||
.join('');
|
||||
formattedCode =
|
||||
formattedCode.split(kAnyAddressRegexp)
|
||||
.map((part, index) => this._formatAddressPart(part, index))
|
||||
.join('');
|
||||
// Let's replace the base-address since it doesn't add any value.
|
||||
// TODO
|
||||
content.innerHTML = formattedCode;
|
||||
this._fragment.appendChild(content);
|
||||
return content;
|
||||
}
|
||||
|
||||
_formatRegisterPart(part) {
|
||||
@ -169,7 +265,7 @@ class AssemblyFormatter {
|
||||
return `<span class="reg ${part}">${part}</span>`
|
||||
}
|
||||
|
||||
_formatAddressPart(part, index, lineAddress) {
|
||||
_formatAddressPart(part, index) {
|
||||
if (kFullAddressRegexp.test(part)) {
|
||||
// The first or second address must be the line address
|
||||
if (index <= 1) {
|
||||
@ -177,12 +273,16 @@ class AssemblyFormatter {
|
||||
}
|
||||
return `<span class=addr data-addr="${part}">${part}</span>`;
|
||||
} else if (kRelativeAddressRegexp.test(part)) {
|
||||
const targetAddress = (lineAddress + parseInt(part)).toString(16);
|
||||
return `<span class=addr data-addr="0x${targetAddress}">${part}</span>`;
|
||||
return `<span class=addr data-addr="0x${
|
||||
this._toAbsoluteAddress(part).toString(16)}">${part}</span>`;
|
||||
} else {
|
||||
return part;
|
||||
}
|
||||
}
|
||||
|
||||
_toAbsoluteAddress(part) {
|
||||
return this.address + parseInt(part);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionHandler {
|
||||
|
Loading…
Reference in New Issue
Block a user