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