dadbde038d
Rather than lumping in parsing, bytecode compilation and optimized compilation all into the same VM "compile" state, seperate them out into individual states. Additionally, add support for these states to tickprocessor and profview. Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng Change-Id: I5be943e23cae042e32e9ccb24415c67c18658b4b Reviewed-on: https://chromium-review.googlesource.com/608973 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Reviewed-by: Marja Hölttä <marja@chromium.org> Cr-Commit-Position: refs/heads/master@{#47283}
604 lines
17 KiB
JavaScript
604 lines
17 KiB
JavaScript
// Copyright 2017 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.
|
|
|
|
"use strict"
|
|
|
|
let codeKinds = [
|
|
"UNKNOWN",
|
|
"CPPPARSE",
|
|
"CPPCOMPBC",
|
|
"CPPCOMP",
|
|
"CPPGC",
|
|
"CPPEXT",
|
|
"CPP",
|
|
"LIB",
|
|
"IC",
|
|
"BC",
|
|
"STUB",
|
|
"BUILTIN",
|
|
"REGEXP",
|
|
"JSOPT",
|
|
"JSUNOPT"
|
|
];
|
|
|
|
function resolveCodeKind(code) {
|
|
if (!code || !code.type) {
|
|
return "UNKNOWN";
|
|
} else if (code.type === "CPP") {
|
|
return "CPP";
|
|
} else if (code.type === "SHARED_LIB") {
|
|
return "LIB";
|
|
} else if (code.type === "CODE") {
|
|
if (code.kind === "LoadIC" ||
|
|
code.kind === "StoreIC" ||
|
|
code.kind === "KeyedStoreIC" ||
|
|
code.kind === "KeyedLoadIC" ||
|
|
code.kind === "LoadGlobalIC" ||
|
|
code.kind === "Handler") {
|
|
return "IC";
|
|
} else if (code.kind === "BytecodeHandler") {
|
|
return "BC";
|
|
} else if (code.kind === "Stub") {
|
|
return "STUB";
|
|
} else if (code.kind === "Builtin") {
|
|
return "BUILTIN";
|
|
} else if (code.kind === "RegExp") {
|
|
return "REGEXP";
|
|
}
|
|
console.log("Unknown CODE: '" + code.kind + "'.");
|
|
return "CODE";
|
|
} else if (code.type === "JS") {
|
|
if (code.kind === "Builtin") {
|
|
return "JSUNOPT";
|
|
} else if (code.kind === "Opt") {
|
|
return "JSOPT";
|
|
} else if (code.kind === "Unopt") {
|
|
return "JSUNOPT";
|
|
}
|
|
}
|
|
console.log("Unknown code type '" + type + "'.");
|
|
}
|
|
|
|
function resolveCodeKindAndVmState(code, vmState) {
|
|
let kind = resolveCodeKind(code);
|
|
if (kind === "CPP") {
|
|
if (vmState === 1) {
|
|
kind = "CPPGC";
|
|
} else if (vmState === 2) {
|
|
kind = "CPPPARSE";
|
|
} else if (vmState === 3) {
|
|
kind = "CPPCOMPBC";
|
|
} else if (vmState === 4) {
|
|
kind = "CPPCOMP";
|
|
} else if (vmState === 6) {
|
|
kind = "CPPEXT";
|
|
}
|
|
}
|
|
return kind;
|
|
}
|
|
|
|
function codeEquals(code1, code2, allowDifferentKinds = false) {
|
|
if (!code1 || !code2) return false;
|
|
if (code1.name != code2.name || code1.type != code2.type) return false;
|
|
|
|
if (code1.type == 'CODE') {
|
|
if (!allowDifferentKinds && code1.kind != code2.kind) return false;
|
|
} else if (code1.type == 'JS') {
|
|
if (!allowDifferentKinds && code1.kind != code2.kind) return false;
|
|
if (code1.func != code2.func) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function createNodeFromStackEntry(code, codeId, vmState) {
|
|
let name = code ? code.name : "UNKNOWN";
|
|
|
|
return { name, codeId, type : resolveCodeKindAndVmState(code, vmState),
|
|
children : [], ownTicks : 0, ticks : 0 };
|
|
}
|
|
|
|
function childIdFromCode(codeId, code) {
|
|
// For JavaScript function, pretend there is one instance of optimized
|
|
// function and one instance of unoptimized function per SFI.
|
|
// Otherwise, just compute the id from code id.
|
|
let type = resolveCodeKind(code);
|
|
if (type === "JSOPT") {
|
|
return code.func * 4 + 1;
|
|
} else if (type === "JSUNOPT") {
|
|
return code.func * 4 + 2;
|
|
} else {
|
|
return codeId * 4;
|
|
}
|
|
}
|
|
|
|
// We store list of ticks and positions within the ticks stack by
|
|
// storing flattened triplets of { tickIndex, depth, count }.
|
|
// Triplet { 123, 2, 3 } encodes positions in ticks 123, 124, 125,
|
|
// all of them at depth 2. The flattened array is used to encode
|
|
// position within the call-tree.
|
|
|
|
// The following function helps to encode such triplets.
|
|
function addFrameToFrameList(paths, pathIndex, depth) {
|
|
// Try to combine with the previous code run.
|
|
if (paths.length > 0 &&
|
|
paths[paths.length - 3] + 1 === pathIndex &&
|
|
paths[paths.length - 2] === depth) {
|
|
paths[paths.length - 1]++;
|
|
} else {
|
|
paths.push(pathIndex, depth, 1);
|
|
}
|
|
}
|
|
|
|
function findNextFrame(file, stack, stackPos, step, filter) {
|
|
let codeId = -1;
|
|
let code = null;
|
|
while (stackPos >= 0 && stackPos < stack.length) {
|
|
codeId = stack[stackPos];
|
|
code = codeId >= 0 ? file.code[codeId] : undefined;
|
|
|
|
if (filter) {
|
|
let type = code ? code.type : undefined;
|
|
let kind = code ? code.kind : undefined;
|
|
if (filter(type, kind)) return stackPos;
|
|
}
|
|
stackPos += step;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function addOrUpdateChildNode(parent, file, stackIndex, stackPos, ascending) {
|
|
let stack = file.ticks[stackIndex].s;
|
|
let vmState = file.ticks[stackIndex].vm;
|
|
let codeId = stack[stackPos];
|
|
let code = codeId >= 0 ? file.code[codeId] : undefined;
|
|
if (stackPos === -1) {
|
|
// We reached the end without finding the next step.
|
|
// If we are doing top-down call tree, update own ticks.
|
|
if (!ascending) {
|
|
parent.ownTicks++;
|
|
}
|
|
} else {
|
|
console.assert(stackPos >= 0 && stackPos < stack.length);
|
|
// We found a child node.
|
|
let childId = childIdFromCode(codeId, code);
|
|
let child = parent.children[childId];
|
|
if (!child) {
|
|
child = createNodeFromStackEntry(code, codeId, vmState);
|
|
child.delayedExpansion = { frameList : [], ascending };
|
|
parent.children[childId] = child;
|
|
}
|
|
child.ticks++;
|
|
addFrameToFrameList(child.delayedExpansion.frameList, stackIndex, stackPos);
|
|
}
|
|
}
|
|
|
|
// This expands a tree node (direct children only).
|
|
function expandTreeNode(file, node, filter) {
|
|
let { frameList, ascending } = node.delayedExpansion;
|
|
|
|
let step = ascending ? 2 : -2;
|
|
|
|
for (let i = 0; i < frameList.length; i+= 3) {
|
|
let firstStackIndex = frameList[i];
|
|
let depth = frameList[i + 1];
|
|
let count = frameList[i + 2];
|
|
for (let j = 0; j < count; j++) {
|
|
let stackIndex = firstStackIndex + j;
|
|
let stack = file.ticks[stackIndex].s;
|
|
|
|
// Get to the next frame that has not been filtered out.
|
|
let stackPos = findNextFrame(file, stack, depth + step, step, filter);
|
|
addOrUpdateChildNode(node, file, stackIndex, stackPos, ascending);
|
|
}
|
|
}
|
|
node.delayedExpansion = null;
|
|
}
|
|
|
|
function createEmptyNode(name) {
|
|
return {
|
|
name : name,
|
|
codeId: -1,
|
|
type : "CAT",
|
|
children : [],
|
|
ownTicks : 0,
|
|
ticks : 0
|
|
};
|
|
}
|
|
|
|
class RuntimeCallTreeProcessor {
|
|
constructor() {
|
|
this.tree = createEmptyNode("root");
|
|
this.tree.delayedExpansion = { frameList : [], ascending : false };
|
|
}
|
|
|
|
addStack(file, tickIndex) {
|
|
this.tree.ticks++;
|
|
|
|
let stack = file.ticks[tickIndex].s;
|
|
let i;
|
|
for (i = 0; i < stack.length; i += 2) {
|
|
let codeId = stack[i];
|
|
if (codeId < 0) return;
|
|
let code = file.code[codeId];
|
|
if (code.type !== "CPP" && code.type !== "SHARED_LIB") {
|
|
i -= 2;
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0 || i >= stack.length) return;
|
|
addOrUpdateChildNode(this.tree, file, tickIndex, i, false);
|
|
}
|
|
}
|
|
|
|
class PlainCallTreeProcessor {
|
|
constructor(filter, isBottomUp) {
|
|
this.filter = filter;
|
|
this.tree = createEmptyNode("root");
|
|
this.tree.delayedExpansion = { frameList : [], ascending : isBottomUp };
|
|
this.isBottomUp = isBottomUp;
|
|
}
|
|
|
|
addStack(file, tickIndex) {
|
|
let stack = file.ticks[tickIndex].s;
|
|
let step = this.isBottomUp ? 2 : -2;
|
|
let start = this.isBottomUp ? 0 : stack.length - 2;
|
|
|
|
let stackPos = findNextFrame(file, stack, start, step, this.filter);
|
|
addOrUpdateChildNode(this.tree, file, tickIndex, stackPos, this.isBottomUp);
|
|
|
|
this.tree.ticks++;
|
|
}
|
|
}
|
|
|
|
function buildCategoryTreeAndLookup() {
|
|
let root = createEmptyNode("root");
|
|
let categories = {};
|
|
function addCategory(name, types) {
|
|
let n = createEmptyNode(name);
|
|
for (let i = 0; i < types.length; i++) {
|
|
categories[types[i]] = n;
|
|
}
|
|
root.children.push(n);
|
|
}
|
|
addCategory("JS Optimized", [ "JSOPT" ]);
|
|
addCategory("JS Unoptimized", [ "JSUNOPT", "BC" ]);
|
|
addCategory("IC", [ "IC" ]);
|
|
addCategory("RegExp", [ "REGEXP" ]);
|
|
addCategory("Other generated", [ "STUB", "BUILTIN" ]);
|
|
addCategory("C++", [ "CPP", "LIB" ]);
|
|
addCategory("C++/GC", [ "CPPGC" ]);
|
|
addCategory("C++/Parser", [ "CPPPARSE" ]);
|
|
addCategory("C++/Bytecode compiler", [ "CPPCOMPBC" ]);
|
|
addCategory("C++/Compiler", [ "CPPCOMP" ]);
|
|
addCategory("C++/External", [ "CPPEXT" ]);
|
|
addCategory("Unknown", [ "UNKNOWN" ]);
|
|
|
|
return { categories, root };
|
|
}
|
|
|
|
class CategorizedCallTreeProcessor {
|
|
constructor(filter, isBottomUp) {
|
|
this.filter = filter;
|
|
let { categories, root } = buildCategoryTreeAndLookup();
|
|
|
|
this.tree = root;
|
|
this.categories = categories;
|
|
this.isBottomUp = isBottomUp;
|
|
}
|
|
|
|
addStack(file, tickIndex) {
|
|
let stack = file.ticks[tickIndex].s;
|
|
let vmState = file.ticks[tickIndex].vm;
|
|
if (stack.length === 0) return;
|
|
let codeId = stack[0];
|
|
let code = codeId >= 0 ? file.code[codeId] : undefined;
|
|
let kind = resolveCodeKindAndVmState(code, vmState);
|
|
let node = this.categories[kind];
|
|
|
|
this.tree.ticks++;
|
|
node.ticks++;
|
|
|
|
let step = this.isBottomUp ? 2 : -2;
|
|
let start = this.isBottomUp ? 0 : stack.length - 2;
|
|
|
|
let stackPos = findNextFrame(file, stack, start, step, this.filter);
|
|
addOrUpdateChildNode(node, file, tickIndex, stackPos, this.isBottomUp);
|
|
}
|
|
}
|
|
|
|
class FunctionListTree {
|
|
constructor(filter, withCategories) {
|
|
if (withCategories) {
|
|
let { categories, root } = buildCategoryTreeAndLookup();
|
|
this.tree = root;
|
|
this.categories = categories;
|
|
} else {
|
|
this.tree = {
|
|
name : "root",
|
|
codeId: -1,
|
|
children : [],
|
|
ownTicks : 0,
|
|
ticks : 0
|
|
};
|
|
this.categories = null;
|
|
}
|
|
|
|
this.codeVisited = [];
|
|
this.filter = filter;
|
|
}
|
|
|
|
addStack(file, tickIndex) {
|
|
let stack = file.ticks[tickIndex].s;
|
|
let vmState = file.ticks[tickIndex].vm;
|
|
|
|
this.tree.ticks++;
|
|
let child = null;
|
|
let tree = null;
|
|
for (let i = stack.length - 2; i >= 0; i -= 2) {
|
|
let codeId = stack[i];
|
|
if (codeId < 0 || this.codeVisited[codeId]) continue;
|
|
|
|
let code = codeId >= 0 ? file.code[codeId] : undefined;
|
|
if (this.filter) {
|
|
let type = code ? code.type : undefined;
|
|
let kind = code ? code.kind : undefined;
|
|
if (!this.filter(type, kind)) continue;
|
|
}
|
|
let childId = childIdFromCode(codeId, code);
|
|
if (this.categories) {
|
|
let kind = resolveCodeKindAndVmState(code, vmState);
|
|
tree = this.categories[kind];
|
|
} else {
|
|
tree = this.tree;
|
|
}
|
|
child = tree.children[childId];
|
|
if (!child) {
|
|
child = createNodeFromStackEntry(code, codeId, vmState);
|
|
child.children[0] = createEmptyNode("Top-down tree");
|
|
child.children[0].delayedExpansion =
|
|
{ frameList : [], ascending : false };
|
|
child.children[1] = createEmptyNode("Bottom-up tree");
|
|
child.children[1].delayedExpansion =
|
|
{ frameList : [], ascending : true };
|
|
tree.children[childId] = child;
|
|
}
|
|
child.ticks++;
|
|
child.children[0].ticks++;
|
|
addFrameToFrameList(
|
|
child.children[0].delayedExpansion.frameList, tickIndex, i);
|
|
child.children[1].ticks++;
|
|
addFrameToFrameList(
|
|
child.children[1].delayedExpansion.frameList, tickIndex, i);
|
|
this.codeVisited[codeId] = true;
|
|
}
|
|
if (child) {
|
|
child.ownTicks++;
|
|
console.assert(tree !== null);
|
|
tree.ticks++;
|
|
console.assert(tree.type === "CAT");
|
|
}
|
|
|
|
for (let i = 0; i < stack.length; i += 2) {
|
|
let codeId = stack[i];
|
|
if (codeId >= 0) this.codeVisited[codeId] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class CategorySampler {
|
|
constructor(file, bucketCount) {
|
|
this.bucketCount = bucketCount;
|
|
|
|
this.firstTime = file.ticks[0].tm;
|
|
let lastTime = file.ticks[file.ticks.length - 1].tm;
|
|
this.step = (lastTime - this.firstTime) / bucketCount;
|
|
|
|
this.buckets = [];
|
|
let bucket = {};
|
|
for (let i = 0; i < codeKinds.length; i++) {
|
|
bucket[codeKinds[i]] = 0;
|
|
}
|
|
for (let i = 0; i < bucketCount; i++) {
|
|
this.buckets.push(Object.assign({ total : 0 }, bucket));
|
|
}
|
|
}
|
|
|
|
addStack(file, tickIndex) {
|
|
let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex];
|
|
|
|
let i = Math.floor((timestamp - this.firstTime) / this.step);
|
|
if (i == this.buckets.length) i--;
|
|
console.assert(i >= 0 && i < this.buckets.length);
|
|
|
|
let bucket = this.buckets[i];
|
|
bucket.total++;
|
|
|
|
let codeId = (stack.length > 0) ? stack[0] : -1;
|
|
let code = codeId >= 0 ? file.code[codeId] : undefined;
|
|
let kind = resolveCodeKindAndVmState(code, vmState);
|
|
bucket[kind]++;
|
|
}
|
|
}
|
|
|
|
class FunctionTimelineProcessor {
|
|
constructor(functionCodeId, filter) {
|
|
this.functionCodeId = functionCodeId;
|
|
this.filter = filter;
|
|
this.blocks = [];
|
|
this.currentBlock = null;
|
|
}
|
|
|
|
addStack(file, tickIndex) {
|
|
if (!this.functionCodeId) return;
|
|
|
|
let { tm : timestamp, vm : vmState, s : stack } = file.ticks[tickIndex];
|
|
let functionCode = file.code[this.functionCodeId];
|
|
|
|
// Find if the function is on the stack, and its position on the stack,
|
|
// ignoring any filtered entries.
|
|
let stackCode = undefined;
|
|
let functionPosInStack = -1;
|
|
let filteredI = 0
|
|
for (let i = 0; i < stack.length - 1; i += 2) {
|
|
let codeId = stack[i];
|
|
let code = codeId >= 0 ? file.code[codeId] : undefined;
|
|
let type = code ? code.type : undefined;
|
|
let kind = code ? code.kind : undefined;
|
|
if (!this.filter(type, kind)) continue;
|
|
|
|
// Match other instances of the same function (e.g. unoptimised, various
|
|
// different optimised versions).
|
|
if (codeEquals(code, functionCode, true)) {
|
|
functionPosInStack = filteredI;
|
|
stackCode = code;
|
|
break;
|
|
}
|
|
filteredI++;
|
|
}
|
|
|
|
if (functionPosInStack >= 0) {
|
|
let stackKind = resolveCodeKindAndVmState(stackCode, vmState);
|
|
|
|
let codeIsTopOfStack = (functionPosInStack == 0);
|
|
|
|
if (this.currentBlock !== null) {
|
|
this.currentBlock.end = timestamp;
|
|
|
|
if (codeIsTopOfStack === this.currentBlock.topOfStack
|
|
&& stackKind === this.currentBlock.kind) {
|
|
// If we haven't changed the stack top or the function kind, then
|
|
// we're happy just extending the current block and not starting
|
|
// a new one.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Start a new block at the current timestamp.
|
|
this.currentBlock = {
|
|
start: timestamp,
|
|
end: timestamp,
|
|
code: stackCode,
|
|
kind: stackKind,
|
|
topOfStack: codeIsTopOfStack
|
|
};
|
|
this.blocks.push(this.currentBlock);
|
|
} else {
|
|
this.currentBlock = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generates a tree out of a ticks sequence.
|
|
// {file} is the JSON files with the ticks and code objects.
|
|
// {startTime}, {endTime} is the interval.
|
|
// {tree} is the processor of stacks.
|
|
function generateTree(
|
|
file, startTime, endTime, tree) {
|
|
let ticks = file.ticks;
|
|
let i = 0;
|
|
while (i < ticks.length && ticks[i].tm < startTime) {
|
|
i++;
|
|
}
|
|
|
|
let tickCount = 0;
|
|
while (i < ticks.length && ticks[i].tm < endTime) {
|
|
tree.addStack(file, i);
|
|
i++;
|
|
tickCount++;
|
|
}
|
|
|
|
return tickCount;
|
|
}
|
|
|
|
function computeOptimizationStats(file,
|
|
timeStart = -Infinity, timeEnd = Infinity) {
|
|
function newCollection() {
|
|
return { count : 0, functions : [], functionTable : [] };
|
|
}
|
|
function addToCollection(collection, code) {
|
|
collection.count++;
|
|
let funcData = collection.functionTable[code.func];
|
|
if (!funcData) {
|
|
funcData = { f : file.functions[code.func], instances : [] };
|
|
collection.functionTable[code.func] = funcData;
|
|
collection.functions.push(funcData);
|
|
}
|
|
funcData.instances.push(code);
|
|
}
|
|
|
|
let functionCount = 0;
|
|
let optimizedFunctionCount = 0;
|
|
let deoptimizedFunctionCount = 0;
|
|
let optimizations = newCollection();
|
|
let eagerDeoptimizations = newCollection();
|
|
let softDeoptimizations = newCollection();
|
|
let lazyDeoptimizations = newCollection();
|
|
|
|
for (let i = 0; i < file.functions.length; i++) {
|
|
let f = file.functions[i];
|
|
|
|
// Skip special SFIs that do not correspond to JS functions.
|
|
if (f.codes.length === 0) continue;
|
|
if (file.code[f.codes[0]].type !== "JS") continue;
|
|
|
|
functionCount++;
|
|
let optimized = false;
|
|
let deoptimized = false;
|
|
|
|
for (let j = 0; j < f.codes.length; j++) {
|
|
let code = file.code[f.codes[j]];
|
|
console.assert(code.type === "JS");
|
|
if (code.kind === "Opt") {
|
|
optimized = true;
|
|
if (code.tm >= timeStart && code.tm <= timeEnd) {
|
|
addToCollection(optimizations, code);
|
|
}
|
|
}
|
|
if (code.deopt) {
|
|
deoptimized = true;
|
|
if (code.deopt.tm >= timeStart && code.deopt.tm <= timeEnd) {
|
|
switch (code.deopt.bailoutType) {
|
|
case "lazy":
|
|
addToCollection(lazyDeoptimizations, code);
|
|
break;
|
|
case "eager":
|
|
addToCollection(eagerDeoptimizations, code);
|
|
break;
|
|
case "soft":
|
|
addToCollection(softDeoptimizations, code);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (optimized) {
|
|
optimizedFunctionCount++;
|
|
}
|
|
if (deoptimized) {
|
|
deoptimizedFunctionCount++;
|
|
}
|
|
}
|
|
|
|
function sortCollection(collection) {
|
|
collection.functions.sort(
|
|
(a, b) => a.instances.length - b.instances.length);
|
|
}
|
|
|
|
sortCollection(eagerDeoptimizations);
|
|
sortCollection(lazyDeoptimizations);
|
|
sortCollection(softDeoptimizations);
|
|
sortCollection(optimizations);
|
|
|
|
return {
|
|
functionCount,
|
|
optimizedFunctionCount,
|
|
deoptimizedFunctionCount,
|
|
optimizations,
|
|
eagerDeoptimizations,
|
|
lazyDeoptimizations,
|
|
softDeoptimizations,
|
|
};
|
|
}
|