| // 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 "../src/util"; |
| import { NodeLabel } from "./node-label"; |
| |
| 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: AnyPosition): string { |
| if (!sourcePosition) return "undefined"; |
| if ('inliningId' in sourcePosition && 'scriptOffset' in sourcePosition) { |
| 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; |
| } |
| |
| export type Origin = NodeOrigin | BytecodePosition; |
| export type TurboFanNodeOrigin = NodeOrigin & TurboFanOrigin; |
| export type TurboFanBytecodeOrigin = BytecodePosition & TurboFanOrigin; |
| |
| type AnyPosition = SourcePosition | BytecodePosition; |
| |
| export interface Source { |
| sourcePositions: Array<SourcePosition>; |
| sourceName: string; |
| functionName: string; |
| sourceText: string; |
| sourceId: number; |
| startPosition?: number; |
| backwardsCompatibility: boolean; |
| } |
| interface Inlining { |
| inliningPosition: SourcePosition; |
| sourceId: number; |
| } |
| interface OtherPhase { |
| type: "disassembly" | "sequence" | "schedule"; |
| name: string; |
| data: any; |
| } |
| |
| interface InstructionsPhase { |
| type: "instructions"; |
| name: string; |
| data: any; |
| instructionOffsetToPCOffset?: any; |
| blockIdtoInstructionRange?: any; |
| nodeIdToInstructionRange?: any; |
| codeOffsetsInfo?: CodeOffsetsInfo; |
| } |
| |
| interface GraphPhase { |
| type: "graph"; |
| name: string; |
| data: any; |
| highestNodeId: number; |
| nodeLabelMap: Array<NodeLabel>; |
| } |
| |
| type Phase = GraphPhase | InstructionsPhase | OtherPhase; |
| |
| export interface Schedule { |
| nodes: Array<any>; |
| } |
| |
| export class Interval { |
| start: number; |
| end: number; |
| |
| constructor(numbers: [number, number]) { |
| this.start = numbers[0]; |
| this.end = numbers[1]; |
| } |
| } |
| |
| export interface ChildRange { |
| id: string; |
| type: string; |
| op: any; |
| intervals: Array<[number, number]>; |
| uses: Array<number>; |
| } |
| |
| export interface Range { |
| child_ranges: Array<ChildRange>; |
| is_deferred: boolean; |
| } |
| |
| export class RegisterAllocation { |
| fixedDoubleLiveRanges: Map<string, Range>; |
| fixedLiveRanges: Map<string, Range>; |
| liveRanges: Map<string, Range>; |
| |
| constructor(registerAllocation) { |
| this.fixedDoubleLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_double_live_ranges)); |
| this.fixedLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_live_ranges)); |
| this.liveRanges = new Map<string, Range>(Object.entries(registerAllocation.live_ranges)); |
| } |
| } |
| |
| export interface Sequence { |
| blocks: Array<any>; |
| register_allocation: RegisterAllocation; |
| } |
| |
| class CodeOffsetsInfo { |
| codeStartRegisterCheck: number; |
| deoptCheck: number; |
| initPoison: number; |
| blocksStart: number; |
| outOfLineCode: number; |
| deoptimizationExits: number; |
| pools: number; |
| jumpTables: number; |
| } |
| export class TurbolizerInstructionStartInfo { |
| gap: number; |
| arch: number; |
| condition: number; |
| } |
| |
| 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<TurbolizerInstructionStartInfo>; |
| pcOffsetToInstructions: Map<number, Array<number>>; |
| pcOffsets: Array<number>; |
| blockIdToPCOffset: Array<number>; |
| blockStartPCtoBlockIds: Map<number, Array<number>>; |
| codeOffsetsInfo: CodeOffsetsInfo; |
| |
| 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(); |
| this.pcOffsets = []; |
| this.blockIdToPCOffset = []; |
| this.blockStartPCtoBlockIds = new Map(); |
| this.codeOffsetsInfo = null; |
| } |
| |
| getBlockIdsForOffset(offset): Array<number> { |
| return this.blockStartPCtoBlockIds.get(offset); |
| } |
| |
| hasBlockStartInfo() { |
| return this.blockIdToPCOffset.length > 0; |
| } |
| |
| setSources(sources, mainBackup) { |
| if (sources) { |
| for (const [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; |
| const key = sourcePositionToStringKey(sourcePosition); |
| if (!this.positionToNodes.has(key)) { |
| this.positionToNodes.set(key, []); |
| } |
| this.positionToNodes.get(key).push(nodeId); |
| } |
| for (const [, source] of Object.entries(this.sources)) { |
| source.sourcePositions = sortUnique(source.sourcePositions, |
| sourcePositionLe, sourcePositionEq); |
| } |
| } |
| |
| sourcePositionsToNodeIds(sourcePositions) { |
| const nodeIds = new Set(); |
| for (const sp of sourcePositions) { |
| const key = sourcePositionToStringKey(sp); |
| const 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) { |
| const sp = this.nodePositionMap[nodeId]; |
| const key = sourcePositionToStringKey(sp); |
| sourcePositions.set(key, sp); |
| } |
| const sourcePositionArray = []; |
| for (const sp of sourcePositions.values()) { |
| sourcePositionArray.push(sp); |
| } |
| return sourcePositionArray; |
| } |
| |
| forEachSource(f: (value: Source, index: number, array: Array<Source>) => void) { |
| this.sources.forEach(f); |
| } |
| |
| translateToSourceId(sourceId: number, location?: SourcePosition) { |
| for (const position of this.getInlineStack(location)) { |
| const inlining = this.inlinings[position.inliningId]; |
| if (!inlining) continue; |
| if (inlining.sourceId == sourceId) { |
| return position; |
| } |
| } |
| return location; |
| } |
| |
| addInliningPositions(sourcePosition: AnyPosition, locations: Array<SourcePosition>) { |
| const inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition)); |
| if (!inlining) return; |
| const sourceId = inlining.sourceId; |
| const source = this.sources[sourceId]; |
| for (const sp of source.sourcePositions) { |
| locations.push(sp); |
| this.addInliningPositions(sp, locations); |
| } |
| } |
| |
| getInliningForPosition(sourcePosition: AnyPosition) { |
| return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition)); |
| } |
| |
| getSource(sourceId: number) { |
| return this.sources[sourceId]; |
| } |
| |
| getSourceName(sourceId: number) { |
| const source = this.sources[sourceId]; |
| return `${source.sourceName}:${source.functionName}`; |
| } |
| |
| sourcePositionFor(sourceId: number, scriptOffset: number) { |
| 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: number, start: number, end: number) { |
| if (!this.sources[sourceId]) return []; |
| const res = []; |
| const list = this.sources[sourceId].sourcePositions; |
| for (const sourcePosition of list) { |
| if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) { |
| res.push(sourcePosition); |
| } |
| } |
| return res; |
| } |
| |
| getInlineStack(sourcePosition?: SourcePosition) { |
| if (!sourcePosition) return []; |
| |
| const inliningStack = []; |
| let cur = sourcePosition; |
| while (cur && cur.inliningId != -1) { |
| inliningStack.push(cur); |
| const inlining = this.inlinings[cur.inliningId]; |
| if (!inlining) { |
| break; |
| } |
| cur = inlining.inliningPosition; |
| } |
| if (cur && cur.inliningId == -1) { |
| inliningStack.push(cur); |
| } |
| return inliningStack; |
| } |
| |
| recordOrigins(phase: GraphPhase) { |
| if (phase.type != "graph") return; |
| for (const node of phase.data.nodes) { |
| phase.highestNodeId = Math.max(phase.highestNodeId, node.id); |
| if (node.origin != undefined && |
| node.origin.bytecodePosition != undefined) { |
| const position = { bytecodePosition: node.origin.bytecodePosition }; |
| this.nodePositionMap[node.id] = position; |
| const 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}`); |
| } |
| |
| // Backwards compatibility. |
| if (typeof node.pos === "number") { |
| node.sourcePosition = { scriptOffset: node.pos, inliningId: -1 }; |
| } |
| } |
| } |
| |
| 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, number] { |
| const X = this.nodeIdToInstructionRange[nodeId]; |
| if (X === undefined) return [-1, -1]; |
| return X; |
| } |
| |
| getInstructionRangeForBlock(blockId: number): [number, number] { |
| const X = this.blockIdToInstructionRange[blockId]; |
| if (X === undefined) return [-1, -1]; |
| return X; |
| } |
| |
| readInstructionOffsetToPCOffset(instructionToPCOffset) { |
| for (const [instruction, numberOrInfo] of Object.entries<number | TurbolizerInstructionStartInfo>(instructionToPCOffset)) { |
| let info: TurbolizerInstructionStartInfo; |
| if (typeof numberOrInfo == "number") { |
| info = { gap: numberOrInfo, arch: numberOrInfo, condition: numberOrInfo }; |
| } else { |
| info = numberOrInfo; |
| } |
| this.instructionToPCOffset[instruction] = info; |
| if (!this.pcOffsetToInstructions.has(info.gap)) { |
| this.pcOffsetToInstructions.set(info.gap, []); |
| } |
| this.pcOffsetToInstructions.get(info.gap).push(Number(instruction)); |
| } |
| this.pcOffsets = Array.from(this.pcOffsetToInstructions.keys()).sort((a, b) => b - a); |
| } |
| |
| hasPCOffsets() { |
| return this.pcOffsetToInstructions.size > 0; |
| } |
| |
| getKeyPcOffset(offset: number): number { |
| if (this.pcOffsets.length === 0) return -1; |
| for (const key of this.pcOffsets) { |
| if (key <= offset) { |
| return key; |
| } |
| } |
| return -1; |
| } |
| |
| getInstructionKindForPCOffset(offset: number) { |
| if (this.codeOffsetsInfo) { |
| if (offset >= this.codeOffsetsInfo.deoptimizationExits) { |
| if (offset >= this.codeOffsetsInfo.pools) { |
| return "pools"; |
| } else if (offset >= this.codeOffsetsInfo.jumpTables) { |
| return "jump-tables"; |
| } else { |
| return "deoptimization-exits"; |
| } |
| } |
| if (offset < this.codeOffsetsInfo.deoptCheck) { |
| return "code-start-register"; |
| } else if (offset < this.codeOffsetsInfo.initPoison) { |
| return "deopt-check"; |
| } else if (offset < this.codeOffsetsInfo.blocksStart) { |
| return "init-poison"; |
| } |
| } |
| const keyOffset = this.getKeyPcOffset(offset); |
| if (keyOffset != -1) { |
| const infos = this.pcOffsetToInstructions.get(keyOffset).map(instrId => this.instructionToPCOffset[instrId]).filter(info => info.gap != info.condition); |
| if (infos.length > 0) { |
| const info = infos[0]; |
| if (!info || info.gap == info.condition) return "unknown"; |
| if (offset < info.arch) return "gap"; |
| if (offset < info.condition) return "arch"; |
| return "condition"; |
| } |
| } |
| return "unknown"; |
| } |
| |
| instructionKindToReadableName(instructionKind) { |
| switch (instructionKind) { |
| case "code-start-register": return "Check code register for right value"; |
| case "deopt-check": return "Check if function was marked for deoptimization"; |
| case "init-poison": return "Initialization of poison register"; |
| case "gap": return "Instruction implementing a gap move"; |
| case "arch": return "Instruction implementing the actual machine operation"; |
| case "condition": return "Code implementing conditional after instruction"; |
| case "pools": return "Data in a pool (e.g. constant pool)"; |
| case "jump-tables": return "Part of a jump table"; |
| case "deoptimization-exits": return "Jump to deoptimization exit"; |
| } |
| return null; |
| } |
| |
| instructionRangeToKeyPcOffsets([start, end]: [number, number]): Array<TurbolizerInstructionStartInfo> { |
| if (start == end) return [this.instructionToPCOffset[start]]; |
| return this.instructionToPCOffset.slice(start, end); |
| } |
| |
| instructionToPcOffsets(instr: number): TurbolizerInstructionStartInfo { |
| return this.instructionToPCOffset[instr]; |
| } |
| |
| instructionsToKeyPcOffsets(instructionIds: Iterable<number>): Array<number> { |
| const keyPcOffsets = []; |
| for (const instructionId of instructionIds) { |
| keyPcOffsets.push(this.instructionToPCOffset[instructionId].gap); |
| } |
| return keyPcOffsets; |
| } |
| |
| nodesToKeyPcOffsets(nodes) { |
| let offsets = []; |
| for (const node of nodes) { |
| const range = this.nodeIdToInstructionRange[node]; |
| if (!range) continue; |
| offsets = offsets.concat(this.instructionRangeToKeyPcOffsets(range)); |
| } |
| return offsets; |
| } |
| |
| nodesForPCOffset(offset: number): [Array<string>, Array<string>] { |
| if (this.pcOffsets.length === 0) return [[], []]; |
| for (const key of this.pcOffsets) { |
| 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) { |
| const nodeLabelMap = []; |
| for (const [, phase] of Object.entries<Phase>(phases)) { |
| switch (phase.type) { |
| case 'disassembly': |
| this.disassemblyPhase = phase; |
| if (phase['blockIdToOffset']) { |
| for (const [blockId, pc] of Object.entries<number>(phase['blockIdToOffset'])) { |
| this.blockIdToPCOffset[blockId] = pc; |
| if (!this.blockStartPCtoBlockIds.has(pc)) { |
| this.blockStartPCtoBlockIds.set(pc, []); |
| } |
| this.blockStartPCtoBlockIds.get(pc).push(Number(blockId)); |
| } |
| } |
| break; |
| case 'schedule': |
| this.phaseNames.set(phase.name, this.phases.length); |
| this.phases.push(this.parseSchedule(phase)); |
| break; |
| case 'sequence': |
| this.phaseNames.set(phase.name, this.phases.length); |
| this.phases.push(this.parseSequence(phase)); |
| break; |
| case 'instructions': |
| if (phase.nodeIdToInstructionRange) { |
| this.readNodeIdToInstructionRange(phase.nodeIdToInstructionRange); |
| } |
| if (phase.blockIdtoInstructionRange) { |
| this.readBlockIdToInstructionRange(phase.blockIdtoInstructionRange); |
| } |
| if (phase.instructionOffsetToPCOffset) { |
| this.readInstructionOffsetToPCOffset(phase.instructionOffsetToPCOffset); |
| } |
| if (phase.codeOffsetsInfo) { |
| this.codeOffsetsInfo = phase.codeOffsetsInfo; |
| } |
| break; |
| case 'graph': |
| const graphPhase: GraphPhase = Object.assign(phase, { highestNodeId: 0 }); |
| this.phaseNames.set(graphPhase.name, this.phases.length); |
| this.phases.push(graphPhase); |
| this.recordOrigins(graphPhase); |
| this.internNodeLabels(graphPhase, nodeLabelMap); |
| graphPhase.nodeLabelMap = nodeLabelMap.slice(); |
| break; |
| default: |
| throw "Unsupported phase type"; |
| } |
| } |
| } |
| |
| internNodeLabels(phase: GraphPhase, nodeLabelMap: Array<NodeLabel>) { |
| for (const n of phase.data.nodes) { |
| const label = new NodeLabel(n.id, n.label, n.title, n.live, |
| n.properties, n.sourcePosition, n.origin, n.opcode, n.control, |
| n.opinfo, n.type); |
| const previous = nodeLabelMap[label.id]; |
| if (!label.equals(previous)) { |
| if (previous != undefined) { |
| label.setInplaceUpdatePhase(phase.name); |
| } |
| nodeLabelMap[label.id] = label; |
| } |
| n.nodeLabel = nodeLabelMap[label.id]; |
| } |
| } |
| |
| repairPhaseId(anyPhaseId) { |
| return Math.max(0, Math.min(anyPhaseId | 0, this.phases.length - 1)); |
| } |
| |
| getPhase(phaseId: number) { |
| return this.phases[phaseId]; |
| } |
| |
| getPhaseIdByName(phaseName: string) { |
| return this.phaseNames.get(phaseName); |
| } |
| |
| forEachPhase(f: (value: Phase, index: number, array: Array<Phase>) => void) { |
| 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: any, 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; |
| } |
| |
| parseSequence(phase) { |
| phase.sequence = { blocks: phase.blocks, |
| register_allocation: phase.register_allocation ? new RegisterAllocation(phase.register_allocation) |
| : undefined }; |
| return phase; |
| } |
| } |