• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 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 {sortUnique, anyToString} from "./util.js"
6
7function sourcePositionLe(a, b) {
8  if (a.inliningId == b.inliningId) {
9    return a.scriptOffset - b.scriptOffset;
10  }
11  return a.inliningId - b.inliningId;
12}
13
14function sourcePositionEq(a, b) {
15  return a.inliningId == b.inliningId &&
16    a.scriptOffset == b.scriptOffset;
17}
18
19export function sourcePositionToStringKey(sourcePosition): string {
20  if (!sourcePosition) return "undefined";
21  if (sourcePosition.inliningId && sourcePosition.scriptOffset)
22    return "SP:" + sourcePosition.inliningId + ":" + sourcePosition.scriptOffset;
23  if (sourcePosition.bytecodePosition)
24    return "BCP:" + sourcePosition.bytecodePosition;
25  return "undefined";
26}
27
28export function sourcePositionValid(l) {
29  return (typeof l.scriptOffset !== undefined
30    && typeof l.inliningId !== undefined) || typeof l.bytecodePosition != undefined;
31}
32
33export interface SourcePosition {
34  scriptOffset: number;
35  inliningId: number;
36}
37
38interface TurboFanOrigin {
39  phase: string;
40  reducer: string;
41}
42
43export interface NodeOrigin {
44  nodeId: number;
45}
46
47interface BytecodePosition {
48  bytecodePosition: number;
49}
50
51type Origin = NodeOrigin | BytecodePosition;
52type TurboFanNodeOrigin = NodeOrigin & TurboFanOrigin;
53type TurboFanBytecodeOrigin = BytecodePosition & TurboFanOrigin;
54
55type AnyPosition = SourcePosition | BytecodePosition;
56
57export interface Source {
58  sourcePositions: Array<SourcePosition>;
59  sourceName: string;
60  functionName: string;
61  sourceText: string;
62  sourceId: number;
63  startPosition?: number;
64}
65interface Inlining {
66  inliningPosition: SourcePosition;
67  sourceId: number;
68}
69interface Phase {
70  type: string;
71  name: string;
72  data: any;
73}
74
75export interface Schedule {
76  nodes: Array<any>;
77}
78
79export class SourceResolver {
80  nodePositionMap: Array<AnyPosition>;
81  sources: Array<Source>;
82  inlinings: Array<Inlining>;
83  inliningsMap: Map<string, Inlining>;
84  positionToNodes: Map<string, Array<string>>;
85  phases: Array<Phase>;
86  phaseNames: Map<string, number>;
87  disassemblyPhase: Phase;
88  lineToSourcePositions: Map<string, Array<AnyPosition>>;
89  nodeIdToInstructionRange: Array<[number, number]>;
90  blockIdToInstructionRange: Array<[number, number]>;
91  instructionToPCOffset: Array<number>;
92  pcOffsetToInstructions: Map<number, Array<number>>;
93
94
95  constructor() {
96    // Maps node ids to source positions.
97    this.nodePositionMap = [];
98    // Maps source ids to source objects.
99    this.sources = [];
100    // Maps inlining ids to inlining objects.
101    this.inlinings = [];
102    // Maps source position keys to inlinings.
103    this.inliningsMap = new Map();
104    // Maps source position keys to node ids.
105    this.positionToNodes = new Map();
106    // Maps phase ids to phases.
107    this.phases = [];
108    // Maps phase names to phaseIds.
109    this.phaseNames = new Map();
110    // The disassembly phase is stored separately.
111    this.disassemblyPhase = undefined;
112    // Maps line numbers to source positions
113    this.lineToSourcePositions = new Map();
114    // Maps node ids to instruction ranges.
115    this.nodeIdToInstructionRange = [];
116    // Maps block ids to instruction ranges.
117    this.blockIdToInstructionRange = [];
118    // Maps instruction numbers to PC offsets.
119    this.instructionToPCOffset = [];
120    // Maps PC offsets to instructions.
121    this.pcOffsetToInstructions = new Map();
122  }
123
124  setSources(sources, mainBackup) {
125    if (sources) {
126      for (let [sourceId, source] of Object.entries(sources)) {
127        this.sources[sourceId] = source;
128        this.sources[sourceId].sourcePositions = [];
129      }
130    }
131    // This is a fallback if the JSON is incomplete (e.g. due to compiler crash).
132    if (!this.sources[-1]) {
133      this.sources[-1] = mainBackup;
134      this.sources[-1].sourcePositions = [];
135    }
136  }
137
138  setInlinings(inlinings) {
139    if (inlinings) {
140      for (const [inliningId, inlining] of Object.entries<Inlining>(inlinings)) {
141        this.inlinings[inliningId] = inlining;
142        this.inliningsMap.set(sourcePositionToStringKey(inlining.inliningPosition), inlining);
143      }
144    }
145    // This is a default entry for the script itself that helps
146    // keep other code more uniform.
147    this.inlinings[-1] = { sourceId: -1, inliningPosition: null };
148  }
149
150  setNodePositionMap(map) {
151    if (!map) return;
152    if (typeof map[0] != 'object') {
153      const alternativeMap = {};
154      for (const [nodeId, scriptOffset] of Object.entries<number>(map)) {
155        alternativeMap[nodeId] = { scriptOffset: scriptOffset, inliningId: -1 };
156      }
157      map = alternativeMap;
158    };
159
160    for (const [nodeId, sourcePosition] of Object.entries<SourcePosition>(map)) {
161      if (sourcePosition == undefined) {
162        console.log("Warning: undefined source position ", sourcePosition, " for nodeId ", nodeId);
163      }
164      const inliningId = sourcePosition.inliningId;
165      const inlining = this.inlinings[inliningId];
166      if (inlining) {
167        const sourceId = inlining.sourceId;
168        this.sources[sourceId].sourcePositions.push(sourcePosition);
169      }
170      this.nodePositionMap[nodeId] = sourcePosition;
171      let key = sourcePositionToStringKey(sourcePosition);
172      if (!this.positionToNodes.has(key)) {
173        this.positionToNodes.set(key, []);
174      }
175      this.positionToNodes.get(key).push(nodeId);
176    }
177    for (const [sourceId, source] of Object.entries(this.sources)) {
178      source.sourcePositions = sortUnique(source.sourcePositions,
179        sourcePositionLe, sourcePositionEq);
180    }
181  }
182
183  sourcePositionsToNodeIds(sourcePositions) {
184    const nodeIds = new Set();
185    for (const sp of sourcePositions) {
186      let key = sourcePositionToStringKey(sp);
187      let nodeIdsForPosition = this.positionToNodes.get(key);
188      if (!nodeIdsForPosition) continue;
189      for (const nodeId of nodeIdsForPosition) {
190        nodeIds.add(nodeId);
191      }
192    }
193    return nodeIds;
194  }
195
196  nodeIdsToSourcePositions(nodeIds): Array<AnyPosition> {
197    const sourcePositions = new Map();
198    for (const nodeId of nodeIds) {
199      let sp = this.nodePositionMap[nodeId];
200      let key = sourcePositionToStringKey(sp);
201      sourcePositions.set(key, sp);
202    }
203    const sourcePositionArray = [];
204    for (const sp of sourcePositions.values()) {
205      sourcePositionArray.push(sp);
206    }
207    return sourcePositionArray;
208  }
209
210  forEachSource(f) {
211    this.sources.forEach(f);
212  }
213
214  translateToSourceId(sourceId, location) {
215    for (const position of this.getInlineStack(location)) {
216      let inlining = this.inlinings[position.inliningId];
217      if (!inlining) continue;
218      if (inlining.sourceId == sourceId) {
219        return position;
220      }
221    }
222    return location;
223  }
224
225  addInliningPositions(sourcePosition, locations) {
226    let inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
227    if (!inlining) return;
228    let sourceId = inlining.sourceId
229    const source = this.sources[sourceId];
230    for (const sp of source.sourcePositions) {
231      locations.push(sp);
232      this.addInliningPositions(sp, locations);
233    }
234  }
235
236  getInliningForPosition(sourcePosition) {
237    return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
238  }
239
240  getSource(sourceId) {
241    return this.sources[sourceId];
242  }
243
244  getSourceName(sourceId) {
245    const source = this.sources[sourceId];
246    return `${source.sourceName}:${source.functionName}`;
247  }
248
249  sourcePositionFor(sourceId, scriptOffset) {
250    if (!this.sources[sourceId]) {
251      return null;
252    }
253    const list = this.sources[sourceId].sourcePositions;
254    for (let i = 0; i < list.length; i++) {
255      const sourcePosition = list[i]
256      const position = sourcePosition.scriptOffset;
257      const nextPosition = list[Math.min(i + 1, list.length - 1)].scriptOffset;
258      if ((position <= scriptOffset && scriptOffset < nextPosition)) {
259        return sourcePosition;
260      }
261    }
262    return null;
263  }
264
265  sourcePositionsInRange(sourceId, start, end) {
266    if (!this.sources[sourceId]) return [];
267    const res = [];
268    const list = this.sources[sourceId].sourcePositions;
269    for (let i = 0; i < list.length; i++) {
270      const sourcePosition = list[i]
271      if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) {
272        res.push(sourcePosition);
273      }
274    }
275    return res;
276  }
277
278  getInlineStack(sourcePosition) {
279    if (!sourcePosition) {
280      return [];
281    }
282    let inliningStack = [];
283    let cur = sourcePosition;
284    while (cur && cur.inliningId != -1) {
285      inliningStack.push(cur);
286      let inlining = this.inlinings[cur.inliningId];
287      if (!inlining) {
288        break;
289      }
290      cur = inlining.inliningPosition;
291    }
292    if (cur && cur.inliningId == -1) {
293      inliningStack.push(cur);
294    }
295    return inliningStack;
296  }
297
298  recordOrigins(phase) {
299    if (phase.type != "graph") return;
300    for (const node of phase.data.nodes) {
301      if (node.origin != undefined &&
302        node.origin.bytecodePosition != undefined) {
303        const position = { bytecodePosition: node.origin.bytecodePosition };
304        this.nodePositionMap[node.id] = position;
305        let key = sourcePositionToStringKey(position);
306        if (!this.positionToNodes.has(key)) {
307          this.positionToNodes.set(key, []);
308        }
309        const A = this.positionToNodes.get(key);
310        if (!A.includes(node.id)) A.push("" + node.id);
311      }
312    }
313  }
314
315  readNodeIdToInstructionRange(nodeIdToInstructionRange) {
316    for (const [nodeId, range] of Object.entries<[number, number]>(nodeIdToInstructionRange)) {
317      this.nodeIdToInstructionRange[nodeId] = range;
318    }
319  }
320
321  readBlockIdToInstructionRange(blockIdToInstructionRange) {
322    for (const [blockId, range] of Object.entries<[number, number]>(blockIdToInstructionRange)) {
323      this.blockIdToInstructionRange[blockId] = range;
324    }
325  }
326
327  getInstruction(nodeId):[number, number] {
328    const X = this.nodeIdToInstructionRange[nodeId];
329    if (X === undefined) return [-1, -1];
330    return X;
331  }
332
333  getInstructionRangeForBlock(blockId):[number, number] {
334    const X = this.blockIdToInstructionRange[blockId];
335    if (X === undefined) return [-1, -1];
336    return X;
337  }
338
339  readInstructionOffsetToPCOffset(instructionToPCOffset) {
340    for (const [instruction, offset] of Object.entries<number>(instructionToPCOffset)) {
341      this.instructionToPCOffset[instruction] = offset;
342      if (!this.pcOffsetToInstructions.has(offset)) {
343        this.pcOffsetToInstructions.set(offset, []);
344      }
345      this.pcOffsetToInstructions.get(offset).push(instruction);
346    }
347    console.log(this.pcOffsetToInstructions);
348  }
349
350  hasPCOffsets() {
351    return this.pcOffsetToInstructions.size > 0;
352  }
353
354
355  nodesForPCOffset(offset): [Array<String>, Array<String>] {
356    const keys = Array.from(this.pcOffsetToInstructions.keys()).sort((a, b) => b - a);
357    if (keys.length === 0) return [[],[]];
358    for (const key of keys) {
359      if (key <= offset) {
360        const instrs = this.pcOffsetToInstructions.get(key);
361        const nodes = [];
362        const blocks = [];
363        for (const instr of instrs) {
364          for (const [nodeId, range] of this.nodeIdToInstructionRange.entries()) {
365            if (!range) continue;
366            const [start, end] = range;
367            if (start == end && instr == start) {
368              nodes.push("" + nodeId);
369            }
370            if (start <= instr && instr < end) {
371              nodes.push("" + nodeId);
372            }
373          }
374        }
375        return [nodes, blocks];
376      }
377    }
378    return [[],[]];
379  }
380
381  parsePhases(phases) {
382    for (const [phaseId, phase] of Object.entries<Phase>(phases)) {
383      if (phase.type == 'disassembly') {
384        this.disassemblyPhase = phase;
385      } else if (phase.type == 'schedule') {
386        this.phases.push(this.parseSchedule(phase))
387        this.phaseNames.set(phase.name, this.phases.length);
388      } else if (phase.type == 'instructions') {
389        if (phase.nodeIdToInstructionRange) {
390          this.readNodeIdToInstructionRange(phase.nodeIdToInstructionRange);
391        }
392        if (phase.blockIdtoInstructionRange) {
393          this.readBlockIdToInstructionRange(phase.blockIdtoInstructionRange);
394        }
395        if (phase.instructionOffsetToPCOffset) {
396          this.readInstructionOffsetToPCOffset(phase.instructionOffsetToPCOffset);
397        }
398      } else {
399        this.phases.push(phase);
400        this.recordOrigins(phase);
401        this.phaseNames.set(phase.name, this.phases.length);
402      }
403    }
404  }
405
406  repairPhaseId(anyPhaseId) {
407    return Math.max(0, Math.min(anyPhaseId, this.phases.length - 1))
408  }
409
410  getPhase(phaseId) {
411    return this.phases[phaseId];
412  }
413
414  getPhaseIdByName(phaseName) {
415    return this.phaseNames.get(phaseName);
416  }
417
418  forEachPhase(f) {
419    this.phases.forEach(f);
420  }
421
422  addAnyPositionToLine(lineNumber: number | String, sourcePosition: AnyPosition) {
423    const lineNumberString = anyToString(lineNumber);
424    if (!this.lineToSourcePositions.has(lineNumberString)) {
425      this.lineToSourcePositions.set(lineNumberString, []);
426    }
427    const A = this.lineToSourcePositions.get(lineNumberString);
428    if (!A.includes(sourcePosition)) A.push(sourcePosition);
429  }
430
431  setSourceLineToBytecodePosition(sourceLineToBytecodePosition: Array<number> | undefined) {
432    if (!sourceLineToBytecodePosition) return;
433    sourceLineToBytecodePosition.forEach((pos, i) => {
434      this.addAnyPositionToLine(i, { bytecodePosition: pos });
435    });
436  }
437
438  linetoSourcePositions(lineNumber: number | String) {
439    const positions = this.lineToSourcePositions.get(anyToString(lineNumber));
440    if (positions === undefined) return [];
441    return positions;
442  }
443
444  parseSchedule(phase) {
445    function createNode(state, match) {
446      let inputs = [];
447      if (match.groups.args) {
448        const nodeIdsString = match.groups.args.replace(/\s/g, '');
449        const nodeIdStrings = nodeIdsString.split(',');
450        inputs = nodeIdStrings.map((n) => Number.parseInt(n, 10));
451      }
452      const node = {
453        id: Number.parseInt(match.groups.id, 10),
454        label: match.groups.label,
455        inputs: inputs
456      };
457      if (match.groups.blocks) {
458        const nodeIdsString = match.groups.blocks.replace(/\s/g, '').replace(/B/g, '');
459        const nodeIdStrings = nodeIdsString.split(',');
460        const successors = nodeIdStrings.map((n) => Number.parseInt(n, 10));
461        state.currentBlock.succ = successors;
462      }
463      state.nodes[node.id] = node;
464      state.currentBlock.nodes.push(node);
465    }
466    function createBlock(state, match) {
467      let predecessors = [];
468      if (match.groups.in) {
469        const blockIdsString = match.groups.in.replace(/\s/g, '').replace(/B/g, '');
470        const blockIdStrings = blockIdsString.split(',');
471        predecessors = blockIdStrings.map((n) => Number.parseInt(n, 10));
472      }
473      const block = {
474        id: Number.parseInt(match.groups.id, 10),
475        isDeferred: match.groups.deferred != undefined,
476        pred: predecessors.sort(),
477        succ: [],
478        nodes: []
479      };
480      state.blocks[block.id] = block;
481      state.currentBlock = block;
482    }
483    function setGotoSuccessor(state, match) {
484      state.currentBlock.succ = [Number.parseInt(match.groups.successor.replace(/\s/g, ''), 10)];
485    }
486    const rules = [
487      {
488        lineRegexps:
489          [/^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)$/,
490            /^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)\ ->\ (?<blocks>.*)$/,
491            /^\s*(?<id>\d+):\ (?<label>.*)$/
492          ],
493        process: createNode
494      },
495      {
496        lineRegexps:
497          [/^\s*---\s*BLOCK\ B(?<id>\d+)\s*(?<deferred>\(deferred\))?(\ <-\ )?(?<in>[^-]*)?\ ---$/
498          ],
499        process: createBlock
500      },
501      {
502        lineRegexps:
503          [/^\s*Goto\s*->\s*B(?<successor>\d+)\s*$/
504          ],
505        process: setGotoSuccessor
506      }
507    ];
508
509    const lines = phase.data.split(/[\n]/);
510    const state = { currentBlock: undefined, blocks: [], nodes: [] };
511
512    nextLine:
513    for (const line of lines) {
514      for (const rule of rules) {
515        for (const lineRegexp of rule.lineRegexps) {
516          const match = line.match(lineRegexp);
517          if (match) {
518            rule.process(state, match);
519            continue nextLine;
520          }
521        }
522      }
523      console.log("Warning: unmatched schedule line \"" + line + "\"");
524    }
525    phase.schedule = state;
526    return phase;
527  }
528}
529