• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 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 {LogEntry} from './log.mjs';
6
7// ===========================================================================
8// Map Log Events
9
10export const kChunkHeight = 200;
11export const kChunkWidth = 10;
12export const kChunkVisualWidth = 6;
13
14function define(prototype, name, fn) {
15  Object.defineProperty(prototype, name, {value: fn, enumerable: false});
16}
17
18define(Array.prototype, 'max', function(fn) {
19  if (this.length === 0) return undefined;
20  if (fn === undefined) fn = (each) => each;
21  let max = fn(this[0]);
22  for (let i = 1; i < this.length; i++) {
23    max = Math.max(max, fn(this[i]));
24  }
25  return max;
26})
27define(Array.prototype, 'first', function() {
28  return this[0]
29});
30define(Array.prototype, 'last', function() {
31  return this[this.length - 1]
32});
33
34// ===========================================================================
35// Map Log Events
36
37export class MapLogEntry extends LogEntry {
38  constructor(id, time) {
39    if (!time) throw new Error('Invalid time');
40    // Use MapLogEntry.type getter instead of property, since we only know the
41    // type lazily from the incoming transition.
42    super(undefined, time);
43    this.id = id;
44    MapLogEntry.set(id, this);
45    this.edge = undefined;
46    this.children = [];
47    this.depth = 0;
48    this._isDeprecated = false;
49    this.deprecatedTargets = null;
50    this.leftId = 0;
51    this.rightId = 0;
52    this.entry = undefined;
53    this.description = '';
54  }
55
56  get functionName() {
57    return this.entry?.functionName;
58  }
59
60  get code() {
61    return this.entry?.logEntry;
62  }
63
64  toString() {
65    return `Map(${this.id})`;
66  }
67
68  finalizeRootMap(id) {
69    let stack = [this];
70    while (stack.length > 0) {
71      let current = stack.pop();
72      if (current.leftId !== 0) {
73        console.warn('Skipping potential parent loop between maps:', current)
74        continue;
75      }
76      current.finalize(id);
77      id += 1;
78      current.children.forEach(edge => stack.push(edge.to));
79      // TODO implement rightId
80    }
81    return id;
82  }
83
84  finalize(id) {
85    // Initialize preorder tree traversal Ids for fast subtree inclusion checks
86    if (id <= 0) throw 'invalid id';
87    let currentId = id;
88    this.leftId = currentId
89  }
90
91  get parent() {
92    return this.edge?.from;
93  }
94
95  isDeprecated() {
96    return this._isDeprecated;
97  }
98
99  deprecate() {
100    this._isDeprecated = true;
101  }
102
103  isRoot() {
104    return this.edge === undefined || this.edge.from === undefined;
105  }
106
107  contains(map) {
108    return this.leftId < map.leftId && map.rightId < this.rightId;
109  }
110
111  addEdge(edge) {
112    this.children.push(edge);
113  }
114
115  chunkIndex(chunks) {
116    // Did anybody say O(n)?
117    for (let i = 0; i < chunks.length; i++) {
118      let chunk = chunks[i];
119      if (chunk.isEmpty()) continue;
120      if (chunk.last().time < this.time) continue;
121      return i;
122    }
123    return -1;
124  }
125
126  position(chunks) {
127    const index = this.chunkIndex(chunks);
128    if (index === -1) return [0, 0];
129    const xFrom = (index * kChunkWidth + kChunkVisualWidth / 2) | 0;
130    const yFrom = kChunkHeight - chunks[index].yOffset(this) | 0;
131    return [xFrom, yFrom];
132  }
133
134  transitions() {
135    let transitions = Object.create(null);
136    let current = this;
137    while (current) {
138      let edge = current.edge;
139      if (edge && edge.isTransition()) {
140        transitions[edge.name] = edge;
141      }
142      current = current.parent;
143    }
144    return transitions;
145  }
146
147  get type() {
148    return this.edge?.type ?? 'new';
149  }
150
151  get reason() {
152    return this.edge?.reason;
153  }
154
155  get property() {
156    return this.edge?.name;
157  }
158
159  isBootstrapped() {
160    return this.edge === undefined;
161  }
162
163  getParents() {
164    let parents = [];
165    let current = this.parent;
166    while (current) {
167      parents.push(current);
168      current = current.parent;
169    }
170    return parents;
171  }
172
173  static get(id, time = undefined) {
174    let maps = this.cache.get(id);
175    if (maps === undefined) return undefined;
176    if (time !== undefined) {
177      for (let i = 1; i < maps.length; i++) {
178        if (maps[i].time > time) {
179          return maps[i - 1];
180        }
181      }
182    }
183    // default return the latest
184    return maps[maps.length - 1];
185  }
186
187  static set(id, map) {
188    if (this.cache.has(id)) {
189      this.cache.get(id).push(map);
190    } else {
191      this.cache.set(id, [map]);
192    }
193  }
194
195  static get propertyNames() {
196    return [
197      'type', 'reason', 'property', 'parent', 'functionName', 'sourcePosition',
198      'script', 'code', 'id', 'description'
199    ];
200  }
201}
202
203MapLogEntry.cache = new Map();
204
205// ===========================================================================
206export class Edge {
207  constructor(type, name, reason, time, from, to) {
208    this.type = type;
209    this.name = name;
210    this.reason = reason;
211    this.time = time;
212    this.from = from;
213    this.to = to;
214  }
215
216  updateFrom(edge) {
217    if (this.to !== edge.to || this.from !== edge.from) {
218      throw new Error('Invalid Edge updated', this, to);
219    }
220    this.type = edge.type;
221    this.name = edge.name;
222    this.reason = edge.reason;
223    this.time = edge.time;
224  }
225
226  finishSetup() {
227    const from = this.from;
228    const to = this.to;
229    if (to?.time < from?.time) {
230      // This happens for map deprecation where the transition tree is converted
231      // in reverse order.
232      console.warn('Invalid time order');
233    }
234    if (from) from.addEdge(this);
235    if (to === undefined) return;
236    to.edge = this;
237    if (from === undefined) return;
238    if (to === from) throw 'From and to must be distinct.';
239    let newDepth = from.depth + 1;
240    if (to.depth > 0 && to.depth != newDepth) {
241      console.warn('Depth has already been initialized');
242    }
243    to.depth = newDepth;
244  }
245
246  chunkIndex(chunks) {
247    // Did anybody say O(n)?
248    for (let i = 0; i < chunks.length; i++) {
249      let chunk = chunks[i];
250      if (chunk.isEmpty()) continue;
251      if (chunk.last().time < this.time) continue;
252      return i;
253    }
254    return -1;
255  }
256
257  parentEdge() {
258    if (!this.from) return undefined;
259    return this.from.edge;
260  }
261
262  chainLength() {
263    let length = 0;
264    let prev = this;
265    while (prev) {
266      prev = this.parent;
267      length++;
268    }
269    return length;
270  }
271
272  isTransition() {
273    return this.type === 'Transition'
274  }
275
276  isFastToSlow() {
277    return this.type === 'Normalize'
278  }
279
280  isSlowToFast() {
281    return this.type === 'SlowToFast'
282  }
283
284  isInitial() {
285    return this.type === 'InitialMap'
286  }
287
288  isBootstrapped() {
289    return this.type === 'new'
290  }
291
292  isReplaceDescriptors() {
293    return this.type === 'ReplaceDescriptors'
294  }
295
296  isCopyAsPrototype() {
297    return this.reason === 'CopyAsPrototype'
298  }
299
300  isOptimizeAsPrototype() {
301    return this.reason === 'OptimizeAsPrototype'
302  }
303
304  symbol() {
305    if (this.isTransition()) return '+';
306    if (this.isFastToSlow()) return '⊡';
307    if (this.isSlowToFast()) return '⊛';
308    if (this.isReplaceDescriptors()) {
309      if (this.name) return '+';
310      return '∥';
311    }
312    return '';
313  }
314
315  toString() {
316    let s = this.symbol();
317    if (this.isTransition()) return s + this.name;
318    if (this.isFastToSlow()) return s + this.reason;
319    if (this.isCopyAsPrototype()) return s + 'Copy as Prototype';
320    if (this.isOptimizeAsPrototype()) {
321      return s + 'Optimize as Prototype';
322    }
323    if (this.isReplaceDescriptors() && this.name) {
324      return this.type + ' ' + this.symbol() + this.name;
325    }
326    return this.type + ' ' + (this.reason ? this.reason : '') + ' ' +
327        (this.name ? this.name : '')
328  }
329}