Home | History | Annotate | Download | only in src
      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 
      5 import {sortUnique, anyToString} from "./util.js"
      6 
      7 function sourcePositionLe(a, b) {
      8   if (a.inliningId == b.inliningId) {
      9     return a.scriptOffset - b.scriptOffset;
     10   }
     11   return a.inliningId - b.inliningId;
     12 }
     13 
     14 function sourcePositionEq(a, b) {
     15   return a.inliningId == b.inliningId &&
     16     a.scriptOffset == b.scriptOffset;
     17 }
     18 
     19 export 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 
     28 export function sourcePositionValid(l) {
     29   return (typeof l.scriptOffset !== undefined
     30     && typeof l.inliningId !== undefined) || typeof l.bytecodePosition != undefined;
     31 }
     32 
     33 export interface SourcePosition {
     34   scriptOffset: number;
     35   inliningId: number;
     36 }
     37 
     38 interface TurboFanOrigin {
     39   phase: string;
     40   reducer: string;
     41 }
     42 
     43 export interface NodeOrigin {
     44   nodeId: number;
     45 }
     46 
     47 interface BytecodePosition {
     48   bytecodePosition: number;
     49 }
     50 
     51 type Origin = NodeOrigin | BytecodePosition;
     52 type TurboFanNodeOrigin = NodeOrigin & TurboFanOrigin;
     53 type TurboFanBytecodeOrigin = BytecodePosition & TurboFanOrigin;
     54 
     55 type AnyPosition = SourcePosition | BytecodePosition;
     56 
     57 export interface Source {
     58   sourcePositions: Array<SourcePosition>;
     59   sourceName: string;
     60   functionName: string;
     61   sourceText: string;
     62   sourceId: number;
     63   startPosition?: number;
     64 }
     65 interface Inlining {
     66   inliningPosition: SourcePosition;
     67   sourceId: number;
     68 }
     69 interface Phase {
     70   type: string;
     71   name: string;
     72   data: any;
     73 }
     74 
     75 export interface Schedule {
     76   nodes: Array<any>;
     77 }
     78 
     79 export 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