blob: 4df6fb847c2a933873871a88d7734df7eb239d33 [file] [log] [blame]
// 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 {LogEntry} from './log.mjs';
// ===========================================================================
// Map Log Events
const kChunkHeight = 200;
const kChunkWidth = 10;
function define(prototype, name, fn) {
Object.defineProperty(prototype, name, {value: fn, enumerable: false});
}
define(Array.prototype, 'max', function(fn) {
if (this.length === 0) return undefined;
if (fn === undefined) fn = (each) => each;
let max = fn(this[0]);
for (let i = 1; i < this.length; i++) {
max = Math.max(max, fn(this[i]));
}
return max;
})
define(Array.prototype, 'first', function() {
return this[0]
});
define(Array.prototype, 'last', function() {
return this[this.length - 1]
});
// ===========================================================================
// Map Log Events
class MapLogEntry extends LogEntry {
edge = void 0;
children = [];
depth = 0;
_isDeprecated = false;
deprecatedTargets = null;
leftId = 0;
rightId = 0;
filePosition = '';
script = '';
id = -1;
constructor(id, time) {
if (!time) throw new Error('Invalid time');
super(id, time);
MapLogEntry.set(id, this);
this.id = id;
}
finalizeRootMap(id) {
let stack = [this];
while (stack.length > 0) {
let current = stack.pop();
if (current.leftId !== 0) {
console.warn('Skipping potential parent loop between maps:', current)
continue;
}
current.finalize(id)
id += 1;
current.children.forEach(edge => stack.push(edge.to))
// TODO implement rightId
}
return id;
}
finalize(id) {
// Initialize preorder tree traversal Ids for fast subtree inclusion checks
if (id <= 0) throw 'invalid id';
let currentId = id;
this.leftId = currentId
}
parent() {
if (this.edge === void 0) return void 0;
return this.edge.from;
}
isDeprecated() {
return this._isDeprecated;
}
deprecate() {
this._isDeprecated = true;
}
isRoot() {
return this.edge === void 0 || this.edge.from === void 0;
}
contains(map) {
return this.leftId < map.leftId && map.rightId < this.rightId;
}
addEdge(edge) {
this.children.push(edge);
}
chunkIndex(chunks) {
// Did anybody say O(n)?
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
if (chunk.isEmpty()) continue;
if (chunk.last().time < this.time) continue;
return i;
}
return -1;
}
position(chunks) {
let index = this.chunkIndex(chunks);
let xFrom = (index + 1.5) * kChunkWidth;
let yFrom = kChunkHeight - chunks[index].yOffset(this);
return [xFrom, yFrom];
}
transitions() {
let transitions = Object.create(null);
let current = this;
while (current) {
let edge = current.edge;
if (edge && edge.isTransition()) {
transitions[edge.name] = edge;
}
current = current.parent()
}
return transitions;
}
get type() {
return this.edge === void 0 ? 'new' : this.edge.type;
}
isBootstrapped() {
return this.edge === void 0;
}
getParents() {
let parents = [];
let current = this.parent();
while (current) {
parents.push(current);
current = current.parent();
}
return parents;
}
static get(id, time = undefined) {
let maps = this.cache.get(id);
if (maps) {
for (let i = 1; i < maps.length; i++) {
if (maps[i].time > time) {
return maps[i - 1];
}
}
// default return the latest
return (maps.length > 0) ? maps[maps.length - 1] : undefined;
}
}
static set(id, map) {
if (this.cache.has(id)) {
this.cache.get(id).push(map);
} else {
this.cache.set(id, [map]);
}
}
}
MapLogEntry.cache = new Map();
// ===========================================================================
class Edge {
constructor(type, name, reason, time, from, to) {
this.type = type;
this.name = name;
this.reason = reason;
this.time = time;
this.from = from;
this.to = to;
}
finishSetup() {
const from = this.from;
if (from) from.addEdge(this);
const to = this.to;
if (to === undefined) return;
to.edge = this;
if (from === undefined) return;
if (to === from) throw 'From and to must be distinct.';
if (to.time < from.time) {
console.warn('invalid time order');
}
let newDepth = from.depth + 1;
if (to.depth > 0 && to.depth != newDepth) {
console.warn('Depth has already been initialized');
}
to.depth = newDepth;
}
chunkIndex(chunks) {
// Did anybody say O(n)?
for (let i = 0; i < chunks.length; i++) {
let chunk = chunks[i];
if (chunk.isEmpty()) continue;
if (chunk.last().time < this.time) continue;
return i;
}
return -1;
}
parentEdge() {
if (!this.from) return undefined;
return this.from.edge;
}
chainLength() {
let length = 0;
let prev = this;
while (prev) {
prev = this.parent;
length++;
}
return length;
}
isTransition() {
return this.type === 'Transition'
}
isFastToSlow() {
return this.type === 'Normalize'
}
isSlowToFast() {
return this.type === 'SlowToFast'
}
isInitial() {
return this.type === 'InitialMap'
}
isBootstrapped() {
return this.type === 'new'
}
isReplaceDescriptors() {
return this.type === 'ReplaceDescriptors'
}
isCopyAsPrototype() {
return this.reason === 'CopyAsPrototype'
}
isOptimizeAsPrototype() {
return this.reason === 'OptimizeAsPrototype'
}
symbol() {
if (this.isTransition()) return '+';
if (this.isFastToSlow()) return '⊡';
if (this.isSlowToFast()) return '⊛';
if (this.isReplaceDescriptors()) {
if (this.name) return '+';
return '∥';
}
return '';
}
toString() {
let s = this.symbol();
if (this.isTransition()) return s + this.name;
if (this.isFastToSlow()) return s + this.reason;
if (this.isCopyAsPrototype()) return s + 'Copy as Prototype';
if (this.isOptimizeAsPrototype()) {
return s + 'Optimize as Prototype';
}
if (this.isReplaceDescriptors() && this.name) {
return this.type + ' ' + this.symbol() + this.name;
}
return this.type + ' ' + (this.reason ? this.reason : '') + ' ' +
(this.name ? this.name : '')
}
}
export {MapLogEntry, Edge, kChunkWidth, kChunkHeight};