// Copyright 2018 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 {sortUnique, anyToString} from "./util.js" function sourcePositionLe(a, b) { if (a.inliningId == b.inliningId) { return a.scriptOffset - b.scriptOffset; } return a.inliningId - b.inliningId; } function sourcePositionEq(a, b) { return a.inliningId == b.inliningId && a.scriptOffset == b.scriptOffset; } export function sourcePositionToStringKey(sourcePosition): string { if (!sourcePosition) return "undefined"; if (sourcePosition.inliningId && sourcePosition.scriptOffset) return "SP:" + sourcePosition.inliningId + ":" + sourcePosition.scriptOffset; if (sourcePosition.bytecodePosition) return "BCP:" + sourcePosition.bytecodePosition; return "undefined"; } export function sourcePositionValid(l) { return (typeof l.scriptOffset !== undefined && typeof l.inliningId !== undefined) || typeof l.bytecodePosition != undefined; } export interface SourcePosition { scriptOffset: number; inliningId: number; } interface TurboFanOrigin { phase: string; reducer: string; } export interface NodeOrigin { nodeId: number; } interface BytecodePosition { bytecodePosition: number; } type Origin = NodeOrigin | BytecodePosition; type TurboFanNodeOrigin = NodeOrigin & TurboFanOrigin; type TurboFanBytecodeOrigin = BytecodePosition & TurboFanOrigin; type AnyPosition = SourcePosition | BytecodePosition; export interface Source { sourcePositions: Array<SourcePosition>; sourceName: string; functionName: string; sourceText: string; sourceId: number; startPosition?: number; } interface Inlining { inliningPosition: SourcePosition; sourceId: number; } interface Phase { type: string; name: string; data: any; } export interface Schedule { nodes: Array<any>; } export class SourceResolver { nodePositionMap: Array<AnyPosition>; sources: Array<Source>; inlinings: Array<Inlining>; inliningsMap: Map<string, Inlining>; positionToNodes: Map<string, Array<string>>; phases: Array<Phase>; phaseNames: Map<string, number>; disassemblyPhase: Phase; lineToSourcePositions: Map<string, Array<AnyPosition>>; nodeIdToInstructionRange: Array<[number, number]>; blockIdToInstructionRange: Array<[number, number]>; instructionToPCOffset: Array<number>; pcOffsetToInstructions: Map<number, Array<number>>; constructor() { // Maps node ids to source positions. this.nodePositionMap = []; // Maps source ids to source objects. this.sources = []; // Maps inlining ids to inlining objects. this.inlinings = []; // Maps source position keys to inlinings. this.inliningsMap = new Map(); // Maps source position keys to node ids. this.positionToNodes = new Map(); // Maps phase ids to phases. this.phases = []; // Maps phase names to phaseIds. this.phaseNames = new Map(); // The disassembly phase is stored separately. this.disassemblyPhase = undefined; // Maps line numbers to source positions this.lineToSourcePositions = new Map(); // Maps node ids to instruction ranges. this.nodeIdToInstructionRange = []; // Maps block ids to instruction ranges. this.blockIdToInstructionRange = []; // Maps instruction numbers to PC offsets. this.instructionToPCOffset = []; // Maps PC offsets to instructions. this.pcOffsetToInstructions = new Map(); } setSources(sources, mainBackup) { if (sources) { for (let [sourceId, source] of Object.entries(sources)) { this.sources[sourceId] = source; this.sources[sourceId].sourcePositions = []; } } // This is a fallback if the JSON is incomplete (e.g. due to compiler crash). if (!this.sources[-1]) { this.sources[-1] = mainBackup; this.sources[-1].sourcePositions = []; } } setInlinings(inlinings) { if (inlinings) { for (const [inliningId, inlining] of Object.entries<Inlining>(inlinings)) { this.inlinings[inliningId] = inlining; this.inliningsMap.set(sourcePositionToStringKey(inlining.inliningPosition), inlining); } } // This is a default entry for the script itself that helps // keep other code more uniform. this.inlinings[-1] = { sourceId: -1, inliningPosition: null }; } setNodePositionMap(map) { if (!map) return; if (typeof map[0] != 'object') { const alternativeMap = {}; for (const [nodeId, scriptOffset] of Object.entries<number>(map)) { alternativeMap[nodeId] = { scriptOffset: scriptOffset, inliningId: -1 }; } map = alternativeMap; }; for (const [nodeId, sourcePosition] of Object.entries<SourcePosition>(map)) { if (sourcePosition == undefined) { console.log("Warning: undefined source position ", sourcePosition, " for nodeId ", nodeId); } const inliningId = sourcePosition.inliningId; const inlining = this.inlinings[inliningId]; if (inlining) { const sourceId = inlining.sourceId; this.sources[sourceId].sourcePositions.push(sourcePosition); } this.nodePositionMap[nodeId] = sourcePosition; let key = sourcePositionToStringKey(sourcePosition); if (!this.positionToNodes.has(key)) { this.positionToNodes.set(key, []); } this.positionToNodes.get(key).push(nodeId); } for (const [sourceId, source] of Object.entries(this.sources)) { source.sourcePositions = sortUnique(source.sourcePositions, sourcePositionLe, sourcePositionEq); } } sourcePositionsToNodeIds(sourcePositions) { const nodeIds = new Set(); for (const sp of sourcePositions) { let key = sourcePositionToStringKey(sp); let nodeIdsForPosition = this.positionToNodes.get(key); if (!nodeIdsForPosition) continue; for (const nodeId of nodeIdsForPosition) { nodeIds.add(nodeId); } } return nodeIds; } nodeIdsToSourcePositions(nodeIds): Array<AnyPosition> { const sourcePositions = new Map(); for (const nodeId of nodeIds) { let sp = this.nodePositionMap[nodeId]; let key = sourcePositionToStringKey(sp); sourcePositions.set(key, sp); } const sourcePositionArray = []; for (const sp of sourcePositions.values()) { sourcePositionArray.push(sp); } return sourcePositionArray; } forEachSource(f) { this.sources.forEach(f); } translateToSourceId(sourceId, location) { for (const position of this.getInlineStack(location)) { let inlining = this.inlinings[position.inliningId]; if (!inlining) continue; if (inlining.sourceId == sourceId) { return position; } } return location; } addInliningPositions(sourcePosition, locations) { let inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition)); if (!inlining) return; let sourceId = inlining.sourceId const source = this.sources[sourceId]; for (const sp of source.sourcePositions) { locations.push(sp); this.addInliningPositions(sp, locations); } } getInliningForPosition(sourcePosition) { return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition)); } getSource(sourceId) { return this.sources[sourceId]; } getSourceName(sourceId) { const source = this.sources[sourceId]; return `${source.sourceName}:${source.functionName}`; } sourcePositionFor(sourceId, scriptOffset) { if (!this.sources[sourceId]) { return null; } const list = this.sources[sourceId].sourcePositions; for (let i = 0; i < list.length; i++) { const sourcePosition = list[i] const position = sourcePosition.scriptOffset; const nextPosition = list[Math.min(i + 1, list.length - 1)].scriptOffset; if ((position <= scriptOffset && scriptOffset < nextPosition)) { return sourcePosition; } } return null; } sourcePositionsInRange(sourceId, start, end) { if (!this.sources[sourceId]) return []; const res = []; const list = this.sources[sourceId].sourcePositions; for (let i = 0; i < list.length; i++) { const sourcePosition = list[i] if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) { res.push(sourcePosition); } } return res; } getInlineStack(sourcePosition) { if (!sourcePosition) { return []; } let inliningStack = []; let cur = sourcePosition; while (cur && cur.inliningId != -1) { inliningStack.push(cur); let inlining = this.inlinings[cur.inliningId]; if (!inlining) { break; } cur = inlining.inliningPosition; } if (cur && cur.inliningId == -1) { inliningStack.push(cur); } return inliningStack; } recordOrigins(phase) { if (phase.type != "graph") return; for (const node of phase.data.nodes) { if (node.origin != undefined && node.origin.bytecodePosition != undefined) { const position = { bytecodePosition: node.origin.bytecodePosition }; this.nodePositionMap[node.id] = position; let key = sourcePositionToStringKey(position); if (!this.positionToNodes.has(key)) { this.positionToNodes.set(key, []); } const A = this.positionToNodes.get(key); if (!A.includes(node.id)) A.push("" + node.id); } } } readNodeIdToInstructionRange(nodeIdToInstructionRange) { for (const [nodeId, range] of Object.entries<[number, number]>(nodeIdToInstructionRange)) { this.nodeIdToInstructionRange[nodeId] = range; } } readBlockIdToInstructionRange(blockIdToInstructionRange) { for (const [blockId, range] of Object.entries<[number, number]>(blockIdToInstructionRange)) { this.blockIdToInstructionRange[blockId] = range; } } getInstruction(nodeId):[number, number] { const X = this.nodeIdToInstructionRange[nodeId]; if (X === undefined) return [-1, -1]; return X; } getInstructionRangeForBlock(blockId):[number, number] { const X = this.blockIdToInstructionRange[blockId]; if (X === undefined) return [-1, -1]; return X; } readInstructionOffsetToPCOffset(instructionToPCOffset) { for (const [instruction, offset] of Object.entries<number>(instructionToPCOffset)) { this.instructionToPCOffset[instruction] = offset; if (!this.pcOffsetToInstructions.has(offset)) { this.pcOffsetToInstructions.set(offset, []); } this.pcOffsetToInstructions.get(offset).push(instruction); } console.log(this.pcOffsetToInstructions); } hasPCOffsets() { return this.pcOffsetToInstructions.size > 0; } nodesForPCOffset(offset): [Array<String>, Array<String>] { const keys = Array.from(this.pcOffsetToInstructions.keys()).sort((a, b) => b - a); if (keys.length === 0) return [[],[]]; for (const key of keys) { if (key <= offset) { const instrs = this.pcOffsetToInstructions.get(key); const nodes = []; const blocks = []; for (const instr of instrs) { for (const [nodeId, range] of this.nodeIdToInstructionRange.entries()) { if (!range) continue; const [start, end] = range; if (start == end && instr == start) { nodes.push("" + nodeId); } if (start <= instr && instr < end) { nodes.push("" + nodeId); } } } return [nodes, blocks]; } } return [[],[]]; } parsePhases(phases) { for (const [phaseId, phase] of Object.entries<Phase>(phases)) { if (phase.type == 'disassembly') { this.disassemblyPhase = phase; } else if (phase.type == 'schedule') { this.phases.push(this.parseSchedule(phase)) this.phaseNames.set(phase.name, this.phases.length); } else if (phase.type == 'instructions') { if (phase.nodeIdToInstructionRange) { this.readNodeIdToInstructionRange(phase.nodeIdToInstructionRange); } if (phase.blockIdtoInstructionRange) { this.readBlockIdToInstructionRange(phase.blockIdtoInstructionRange); } if (phase.instructionOffsetToPCOffset) { this.readInstructionOffsetToPCOffset(phase.instructionOffsetToPCOffset); } } else { this.phases.push(phase); this.recordOrigins(phase); this.phaseNames.set(phase.name, this.phases.length); } } } repairPhaseId(anyPhaseId) { return Math.max(0, Math.min(anyPhaseId, this.phases.length - 1)) } getPhase(phaseId) { return this.phases[phaseId]; } getPhaseIdByName(phaseName) { return this.phaseNames.get(phaseName); } forEachPhase(f) { this.phases.forEach(f); } addAnyPositionToLine(lineNumber: number | String, sourcePosition: AnyPosition) { const lineNumberString = anyToString(lineNumber); if (!this.lineToSourcePositions.has(lineNumberString)) { this.lineToSourcePositions.set(lineNumberString, []); } const A = this.lineToSourcePositions.get(lineNumberString); if (!A.includes(sourcePosition)) A.push(sourcePosition); } setSourceLineToBytecodePosition(sourceLineToBytecodePosition: Array<number> | undefined) { if (!sourceLineToBytecodePosition) return; sourceLineToBytecodePosition.forEach((pos, i) => { this.addAnyPositionToLine(i, { bytecodePosition: pos }); }); } linetoSourcePositions(lineNumber: number | String) { const positions = this.lineToSourcePositions.get(anyToString(lineNumber)); if (positions === undefined) return []; return positions; } parseSchedule(phase) { function createNode(state, match) { let inputs = []; if (match.groups.args) { const nodeIdsString = match.groups.args.replace(/\s/g, ''); const nodeIdStrings = nodeIdsString.split(','); inputs = nodeIdStrings.map((n) => Number.parseInt(n, 10)); } const node = { id: Number.parseInt(match.groups.id, 10), label: match.groups.label, inputs: inputs }; if (match.groups.blocks) { const nodeIdsString = match.groups.blocks.replace(/\s/g, '').replace(/B/g, ''); const nodeIdStrings = nodeIdsString.split(','); const successors = nodeIdStrings.map((n) => Number.parseInt(n, 10)); state.currentBlock.succ = successors; } state.nodes[node.id] = node; state.currentBlock.nodes.push(node); } function createBlock(state, match) { let predecessors = []; if (match.groups.in) { const blockIdsString = match.groups.in.replace(/\s/g, '').replace(/B/g, ''); const blockIdStrings = blockIdsString.split(','); predecessors = blockIdStrings.map((n) => Number.parseInt(n, 10)); } const block = { id: Number.parseInt(match.groups.id, 10), isDeferred: match.groups.deferred != undefined, pred: predecessors.sort(), succ: [], nodes: [] }; state.blocks[block.id] = block; state.currentBlock = block; } function setGotoSuccessor(state, match) { state.currentBlock.succ = [Number.parseInt(match.groups.successor.replace(/\s/g, ''), 10)]; } const rules = [ { lineRegexps: [/^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)$/, /^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)\ ->\ (?<blocks>.*)$/, /^\s*(?<id>\d+):\ (?<label>.*)$/ ], process: createNode }, { lineRegexps: [/^\s*---\s*BLOCK\ B(?<id>\d+)\s*(?<deferred>\(deferred\))?(\ <-\ )?(?<in>[^-]*)?\ ---$/ ], process: createBlock }, { lineRegexps: [/^\s*Goto\s*->\s*B(?<successor>\d+)\s*$/ ], process: setGotoSuccessor } ]; const lines = phase.data.split(/[\n]/); const state = { currentBlock: undefined, blocks: [], nodes: [] }; nextLine: for (const line of lines) { for (const rule of rules) { for (const lineRegexp of rule.lineRegexps) { const match = line.match(lineRegexp); if (match) { rule.process(state, match); continue nextLine; } } } console.log("Warning: unmatched schedule line \"" + line + "\""); } phase.schedule = state; return phase; } }