• 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 "../src/util";
6import { NodeLabel } from "./node-label";
7
8function sourcePositionLe(a, b) {
9  if (a.inliningId == b.inliningId) {
10    return a.scriptOffset - b.scriptOffset;
11  }
12  return a.inliningId - b.inliningId;
13}
14
15function sourcePositionEq(a, b) {
16  return a.inliningId == b.inliningId &&
17    a.scriptOffset == b.scriptOffset;
18}
19
20export function sourcePositionToStringKey(sourcePosition: AnyPosition): string {
21  if (!sourcePosition) return "undefined";
22  if ('inliningId' in sourcePosition && 'scriptOffset' in sourcePosition) {
23    return "SP:" + sourcePosition.inliningId + ":" + sourcePosition.scriptOffset;
24  }
25  if (sourcePosition.bytecodePosition) {
26    return "BCP:" + sourcePosition.bytecodePosition;
27  }
28  return "undefined";
29}
30
31export function sourcePositionValid(l) {
32  return (typeof l.scriptOffset !== undefined
33    && typeof l.inliningId !== undefined) || typeof l.bytecodePosition != undefined;
34}
35
36export interface SourcePosition {
37  scriptOffset: number;
38  inliningId: number;
39}
40
41interface TurboFanOrigin {
42  phase: string;
43  reducer: string;
44}
45
46export interface NodeOrigin {
47  nodeId: number;
48}
49
50interface BytecodePosition {
51  bytecodePosition: number;
52}
53
54export type Origin = NodeOrigin | BytecodePosition;
55export type TurboFanNodeOrigin = NodeOrigin & TurboFanOrigin;
56export type TurboFanBytecodeOrigin = BytecodePosition & TurboFanOrigin;
57
58type AnyPosition = SourcePosition | BytecodePosition;
59
60export interface Source {
61  sourcePositions: Array<SourcePosition>;
62  sourceName: string;
63  functionName: string;
64  sourceText: string;
65  sourceId: number;
66  startPosition?: number;
67  backwardsCompatibility: boolean;
68}
69interface Inlining {
70  inliningPosition: SourcePosition;
71  sourceId: number;
72}
73interface OtherPhase {
74  type: "disassembly" | "sequence" | "schedule";
75  name: string;
76  data: any;
77}
78
79interface InstructionsPhase {
80  type: "instructions";
81  name: string;
82  data: any;
83  instructionOffsetToPCOffset?: any;
84  blockIdToInstructionRange?: any;
85  nodeIdToInstructionRange?: any;
86  codeOffsetsInfo?: CodeOffsetsInfo;
87}
88
89interface GraphPhase {
90  type: "graph";
91  name: string;
92  data: any;
93  highestNodeId: number;
94  nodeLabelMap: Array<NodeLabel>;
95}
96
97type Phase = GraphPhase | InstructionsPhase | OtherPhase;
98
99export interface Schedule {
100  nodes: Array<any>;
101}
102
103export class Interval {
104  start: number;
105  end: number;
106
107  constructor(numbers: [number, number]) {
108    this.start = numbers[0];
109    this.end = numbers[1];
110  }
111}
112
113export interface ChildRange {
114  id: string;
115  type: string;
116  op: any;
117  intervals: Array<[number, number]>;
118  uses: Array<number>;
119}
120
121export interface Range {
122  child_ranges: Array<ChildRange>;
123  is_deferred: boolean;
124}
125
126export class RegisterAllocation {
127  fixedDoubleLiveRanges: Map<string, Range>;
128  fixedLiveRanges: Map<string, Range>;
129  liveRanges: Map<string, Range>;
130
131  constructor(registerAllocation) {
132    this.fixedDoubleLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_double_live_ranges));
133    this.fixedLiveRanges = new Map<string, Range>(Object.entries(registerAllocation.fixed_live_ranges));
134    this.liveRanges = new Map<string, Range>(Object.entries(registerAllocation.live_ranges));
135  }
136}
137
138export interface Sequence {
139  blocks: Array<any>;
140  register_allocation: RegisterAllocation;
141}
142
143class CodeOffsetsInfo {
144  codeStartRegisterCheck: number;
145  deoptCheck: number;
146  initPoison: number;
147  blocksStart: number;
148  outOfLineCode: number;
149  deoptimizationExits: number;
150  pools: number;
151  jumpTables: number;
152}
153export class TurbolizerInstructionStartInfo {
154  gap: number;
155  arch: number;
156  condition: number;
157}
158
159export class SourceResolver {
160  nodePositionMap: Array<AnyPosition>;
161  sources: Array<Source>;
162  inlinings: Array<Inlining>;
163  inliningsMap: Map<string, Inlining>;
164  positionToNodes: Map<string, Array<string>>;
165  phases: Array<Phase>;
166  phaseNames: Map<string, number>;
167  disassemblyPhase: Phase;
168  linePositionMap: Map<string, Array<AnyPosition>>;
169  nodeIdToInstructionRange: Array<[number, number]>;
170  blockIdToInstructionRange: Array<[number, number]>;
171  instructionToPCOffset: Array<TurbolizerInstructionStartInfo>;
172  pcOffsetToInstructions: Map<number, Array<number>>;
173  pcOffsets: Array<number>;
174  blockIdToPCOffset: Array<number>;
175  blockStartPCtoBlockIds: Map<number, Array<number>>;
176  codeOffsetsInfo: CodeOffsetsInfo;
177
178  constructor() {
179    // Maps node ids to source positions.
180    this.nodePositionMap = [];
181    // Maps source ids to source objects.
182    this.sources = [];
183    // Maps inlining ids to inlining objects.
184    this.inlinings = [];
185    // Maps source position keys to inlinings.
186    this.inliningsMap = new Map();
187    // Maps source position keys to node ids.
188    this.positionToNodes = new Map();
189    // Maps phase ids to phases.
190    this.phases = [];
191    // Maps phase names to phaseIds.
192    this.phaseNames = new Map();
193    // The disassembly phase is stored separately.
194    this.disassemblyPhase = undefined;
195    // Maps line numbers to source positions
196    this.linePositionMap = new Map();
197    // Maps node ids to instruction ranges.
198    this.nodeIdToInstructionRange = [];
199    // Maps block ids to instruction ranges.
200    this.blockIdToInstructionRange = [];
201    // Maps instruction numbers to PC offsets.
202    this.instructionToPCOffset = [];
203    // Maps PC offsets to instructions.
204    this.pcOffsetToInstructions = new Map();
205    this.pcOffsets = [];
206    this.blockIdToPCOffset = [];
207    this.blockStartPCtoBlockIds = new Map();
208    this.codeOffsetsInfo = null;
209  }
210
211  getBlockIdsForOffset(offset): Array<number> {
212    return this.blockStartPCtoBlockIds.get(offset);
213  }
214
215  hasBlockStartInfo() {
216    return this.blockIdToPCOffset.length > 0;
217  }
218
219  setSources(sources, mainBackup) {
220    if (sources) {
221      for (const [sourceId, source] of Object.entries(sources)) {
222        this.sources[sourceId] = source;
223        this.sources[sourceId].sourcePositions = [];
224      }
225    }
226    // This is a fallback if the JSON is incomplete (e.g. due to compiler crash).
227    if (!this.sources[-1]) {
228      this.sources[-1] = mainBackup;
229      this.sources[-1].sourcePositions = [];
230    }
231  }
232
233  setInlinings(inlinings) {
234    if (inlinings) {
235      for (const [inliningId, inlining] of Object.entries<Inlining>(inlinings)) {
236        this.inlinings[inliningId] = inlining;
237        this.inliningsMap.set(sourcePositionToStringKey(inlining.inliningPosition), inlining);
238      }
239    }
240    // This is a default entry for the script itself that helps
241    // keep other code more uniform.
242    this.inlinings[-1] = { sourceId: -1, inliningPosition: null };
243  }
244
245  setNodePositionMap(map) {
246    if (!map) return;
247    if (typeof map[0] != 'object') {
248      const alternativeMap = {};
249      for (const [nodeId, scriptOffset] of Object.entries<number>(map)) {
250        alternativeMap[nodeId] = { scriptOffset: scriptOffset, inliningId: -1 };
251      }
252      map = alternativeMap;
253    }
254
255    for (const [nodeId, sourcePosition] of Object.entries<SourcePosition>(map)) {
256      if (sourcePosition == undefined) {
257        console.log("Warning: undefined source position ", sourcePosition, " for nodeId ", nodeId);
258      }
259      const inliningId = sourcePosition.inliningId;
260      const inlining = this.inlinings[inliningId];
261      if (inlining) {
262        const sourceId = inlining.sourceId;
263        this.sources[sourceId].sourcePositions.push(sourcePosition);
264      }
265      this.nodePositionMap[nodeId] = sourcePosition;
266      const key = sourcePositionToStringKey(sourcePosition);
267      if (!this.positionToNodes.has(key)) {
268        this.positionToNodes.set(key, []);
269      }
270      this.positionToNodes.get(key).push(nodeId);
271    }
272    for (const [, source] of Object.entries(this.sources)) {
273      source.sourcePositions = sortUnique(source.sourcePositions,
274        sourcePositionLe, sourcePositionEq);
275    }
276  }
277
278  sourcePositionsToNodeIds(sourcePositions) {
279    const nodeIds = new Set<string>();
280    for (const sp of sourcePositions) {
281      const key = sourcePositionToStringKey(sp);
282      const nodeIdsForPosition = this.positionToNodes.get(key);
283      if (!nodeIdsForPosition) continue;
284      for (const nodeId of nodeIdsForPosition) {
285        nodeIds.add(nodeId);
286      }
287    }
288    return nodeIds;
289  }
290
291  nodeIdsToSourcePositions(nodeIds): Array<AnyPosition> {
292    const sourcePositions = new Map();
293    for (const nodeId of nodeIds) {
294      const sp = this.nodePositionMap[nodeId];
295      const key = sourcePositionToStringKey(sp);
296      sourcePositions.set(key, sp);
297    }
298    const sourcePositionArray = [];
299    for (const sp of sourcePositions.values()) {
300      sourcePositionArray.push(sp);
301    }
302    return sourcePositionArray;
303  }
304
305  forEachSource(f: (value: Source, index: number, array: Array<Source>) => void) {
306    this.sources.forEach(f);
307  }
308
309  translateToSourceId(sourceId: number, location?: SourcePosition) {
310    for (const position of this.getInlineStack(location)) {
311      const inlining = this.inlinings[position.inliningId];
312      if (!inlining) continue;
313      if (inlining.sourceId == sourceId) {
314        return position;
315      }
316    }
317    return location;
318  }
319
320  addInliningPositions(sourcePosition: AnyPosition, locations: Array<SourcePosition>) {
321    const inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
322    if (!inlining) return;
323    const sourceId = inlining.sourceId;
324    const source = this.sources[sourceId];
325    for (const sp of source.sourcePositions) {
326      locations.push(sp);
327      this.addInliningPositions(sp, locations);
328    }
329  }
330
331  getInliningForPosition(sourcePosition: AnyPosition) {
332    return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
333  }
334
335  getSource(sourceId: number) {
336    return this.sources[sourceId];
337  }
338
339  getSourceName(sourceId: number) {
340    const source = this.sources[sourceId];
341    return `${source.sourceName}:${source.functionName}`;
342  }
343
344  sourcePositionFor(sourceId: number, scriptOffset: number) {
345    if (!this.sources[sourceId]) {
346      return null;
347    }
348    const list = this.sources[sourceId].sourcePositions;
349    for (let i = 0; i < list.length; i++) {
350      const sourcePosition = list[i];
351      const position = sourcePosition.scriptOffset;
352      const nextPosition = list[Math.min(i + 1, list.length - 1)].scriptOffset;
353      if ((position <= scriptOffset && scriptOffset < nextPosition)) {
354        return sourcePosition;
355      }
356    }
357    return null;
358  }
359
360  sourcePositionsInRange(sourceId: number, start: number, end: number) {
361    if (!this.sources[sourceId]) return [];
362    const res = [];
363    const list = this.sources[sourceId].sourcePositions;
364    for (const sourcePosition of list) {
365      if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) {
366        res.push(sourcePosition);
367      }
368    }
369    return res;
370  }
371
372  getInlineStack(sourcePosition?: SourcePosition) {
373    if (!sourcePosition) return [];
374
375    const inliningStack = [];
376    let cur = sourcePosition;
377    while (cur && cur.inliningId != -1) {
378      inliningStack.push(cur);
379      const inlining = this.inlinings[cur.inliningId];
380      if (!inlining) {
381        break;
382      }
383      cur = inlining.inliningPosition;
384    }
385    if (cur && cur.inliningId == -1) {
386      inliningStack.push(cur);
387    }
388    return inliningStack;
389  }
390
391  recordOrigins(phase: GraphPhase) {
392    if (phase.type != "graph") return;
393    for (const node of phase.data.nodes) {
394      phase.highestNodeId = Math.max(phase.highestNodeId, node.id);
395      if (node.origin != undefined &&
396        node.origin.bytecodePosition != undefined) {
397        const position = { bytecodePosition: node.origin.bytecodePosition };
398        this.nodePositionMap[node.id] = position;
399        const key = sourcePositionToStringKey(position);
400        if (!this.positionToNodes.has(key)) {
401          this.positionToNodes.set(key, []);
402        }
403        const A = this.positionToNodes.get(key);
404        if (!A.includes(node.id)) A.push(`${node.id}`);
405      }
406
407      // Backwards compatibility.
408      if (typeof node.pos === "number") {
409        node.sourcePosition = { scriptOffset: node.pos, inliningId: -1 };
410      }
411    }
412  }
413
414  readNodeIdToInstructionRange(nodeIdToInstructionRange) {
415    for (const [nodeId, range] of Object.entries<[number, number]>(nodeIdToInstructionRange)) {
416      this.nodeIdToInstructionRange[nodeId] = range;
417    }
418  }
419
420  readBlockIdToInstructionRange(blockIdToInstructionRange) {
421    for (const [blockId, range] of Object.entries<[number, number]>(blockIdToInstructionRange)) {
422      this.blockIdToInstructionRange[blockId] = range;
423    }
424  }
425
426  getInstruction(nodeId: number): [number, number] {
427    const X = this.nodeIdToInstructionRange[nodeId];
428    if (X === undefined) return [-1, -1];
429    return X;
430  }
431
432  getInstructionRangeForBlock(blockId: number): [number, number] {
433    const X = this.blockIdToInstructionRange[blockId];
434    if (X === undefined) return [-1, -1];
435    return X;
436  }
437
438  readInstructionOffsetToPCOffset(instructionToPCOffset) {
439    for (const [instruction, numberOrInfo] of Object.entries<number | TurbolizerInstructionStartInfo>(instructionToPCOffset)) {
440      let info: TurbolizerInstructionStartInfo;
441      if (typeof numberOrInfo == "number") {
442        info = { gap: numberOrInfo, arch: numberOrInfo, condition: numberOrInfo };
443      } else {
444        info = numberOrInfo;
445      }
446      this.instructionToPCOffset[instruction] = info;
447      if (!this.pcOffsetToInstructions.has(info.gap)) {
448        this.pcOffsetToInstructions.set(info.gap, []);
449      }
450      this.pcOffsetToInstructions.get(info.gap).push(Number(instruction));
451    }
452    this.pcOffsets = Array.from(this.pcOffsetToInstructions.keys()).sort((a, b) => b - a);
453  }
454
455  hasPCOffsets() {
456    return this.pcOffsetToInstructions.size > 0;
457  }
458
459  getKeyPcOffset(offset: number): number {
460    if (this.pcOffsets.length === 0) return -1;
461    for (const key of this.pcOffsets) {
462      if (key <= offset) {
463        return key;
464      }
465    }
466    return -1;
467  }
468
469  getInstructionKindForPCOffset(offset: number) {
470    if (this.codeOffsetsInfo) {
471      if (offset >= this.codeOffsetsInfo.deoptimizationExits) {
472        if (offset >= this.codeOffsetsInfo.pools) {
473          return "pools";
474        } else if (offset >= this.codeOffsetsInfo.jumpTables) {
475          return "jump-tables";
476        } else {
477          return "deoptimization-exits";
478        }
479      }
480      if (offset < this.codeOffsetsInfo.deoptCheck) {
481        return "code-start-register";
482      } else if (offset < this.codeOffsetsInfo.initPoison) {
483        return "deopt-check";
484      } else if (offset < this.codeOffsetsInfo.blocksStart) {
485        return "init-poison";
486      }
487    }
488    const keyOffset = this.getKeyPcOffset(offset);
489    if (keyOffset != -1) {
490      const infos = this.pcOffsetToInstructions.get(keyOffset).map(instrId => this.instructionToPCOffset[instrId]).filter(info => info.gap != info.condition);
491      if (infos.length > 0) {
492        const info = infos[0];
493        if (!info || info.gap == info.condition) return "unknown";
494        if (offset < info.arch) return "gap";
495        if (offset < info.condition) return "arch";
496        return "condition";
497      }
498    }
499    return "unknown";
500  }
501
502  instructionKindToReadableName(instructionKind) {
503    switch (instructionKind) {
504      case "code-start-register": return "Check code register for right value";
505      case "deopt-check": return "Check if function was marked for deoptimization";
506      case "init-poison": return "Initialization of poison register";
507      case "gap": return "Instruction implementing a gap move";
508      case "arch": return "Instruction implementing the actual machine operation";
509      case "condition": return "Code implementing conditional after instruction";
510      case "pools": return "Data in a pool (e.g. constant pool)";
511      case "jump-tables": return "Part of a jump table";
512      case "deoptimization-exits": return "Jump to deoptimization exit";
513    }
514    return null;
515  }
516
517  instructionRangeToKeyPcOffsets([start, end]: [number, number]): Array<TurbolizerInstructionStartInfo> {
518    if (start == end) return [this.instructionToPCOffset[start]];
519    return this.instructionToPCOffset.slice(start, end);
520  }
521
522  instructionToPcOffsets(instr: number): TurbolizerInstructionStartInfo {
523    return this.instructionToPCOffset[instr];
524  }
525
526  instructionsToKeyPcOffsets(instructionIds: Iterable<number>): Array<number> {
527    const keyPcOffsets = [];
528    for (const instructionId of instructionIds) {
529      const pcOffset = this.instructionToPCOffset[instructionId];
530      if (pcOffset !== undefined) keyPcOffsets.push(pcOffset.gap);
531    }
532    return keyPcOffsets;
533  }
534
535  nodesToKeyPcOffsets(nodes) {
536    let offsets = [];
537    for (const node of nodes) {
538      const range = this.nodeIdToInstructionRange[node];
539      if (!range) continue;
540      offsets = offsets.concat(this.instructionRangeToKeyPcOffsets(range));
541    }
542    return offsets;
543  }
544
545  nodesForPCOffset(offset: number): [Array<string>, Array<string>] {
546    if (this.pcOffsets.length === 0) return [[], []];
547    for (const key of this.pcOffsets) {
548      if (key <= offset) {
549        const instrs = this.pcOffsetToInstructions.get(key);
550        const nodes = [];
551        const blocks = [];
552        for (const instr of instrs) {
553          for (const [nodeId, range] of this.nodeIdToInstructionRange.entries()) {
554            if (!range) continue;
555            const [start, end] = range;
556            if (start == end && instr == start) {
557              nodes.push("" + nodeId);
558            }
559            if (start <= instr && instr < end) {
560              nodes.push("" + nodeId);
561            }
562          }
563        }
564        return [nodes, blocks];
565      }
566    }
567    return [[], []];
568  }
569
570  parsePhases(phases) {
571    const nodeLabelMap = [];
572    for (const [, phase] of Object.entries<Phase>(phases)) {
573      switch (phase.type) {
574        case 'disassembly':
575          this.disassemblyPhase = phase;
576          if (phase['blockIdToOffset']) {
577            for (const [blockId, pc] of Object.entries<number>(phase['blockIdToOffset'])) {
578              this.blockIdToPCOffset[blockId] = pc;
579              if (!this.blockStartPCtoBlockIds.has(pc)) {
580                this.blockStartPCtoBlockIds.set(pc, []);
581              }
582              this.blockStartPCtoBlockIds.get(pc).push(Number(blockId));
583            }
584          }
585          break;
586        case 'schedule':
587          this.phaseNames.set(phase.name, this.phases.length);
588          this.phases.push(this.parseSchedule(phase));
589          break;
590        case 'sequence':
591          this.phaseNames.set(phase.name, this.phases.length);
592          this.phases.push(this.parseSequence(phase));
593          break;
594        case 'instructions':
595          if (phase.nodeIdToInstructionRange) {
596            this.readNodeIdToInstructionRange(phase.nodeIdToInstructionRange);
597          }
598          if (phase.blockIdToInstructionRange) {
599            this.readBlockIdToInstructionRange(phase.blockIdToInstructionRange);
600          }
601          if (phase.instructionOffsetToPCOffset) {
602            this.readInstructionOffsetToPCOffset(phase.instructionOffsetToPCOffset);
603          }
604          if (phase.codeOffsetsInfo) {
605            this.codeOffsetsInfo = phase.codeOffsetsInfo;
606          }
607          break;
608        case 'graph':
609          const graphPhase: GraphPhase = Object.assign(phase, { highestNodeId: 0 });
610          this.phaseNames.set(graphPhase.name, this.phases.length);
611          this.phases.push(graphPhase);
612          this.recordOrigins(graphPhase);
613          this.internNodeLabels(graphPhase, nodeLabelMap);
614          graphPhase.nodeLabelMap = nodeLabelMap.slice();
615          break;
616        default:
617          throw "Unsupported phase type";
618      }
619    }
620  }
621
622  internNodeLabels(phase: GraphPhase, nodeLabelMap: Array<NodeLabel>) {
623    for (const n of phase.data.nodes) {
624      const label = new NodeLabel(n.id, n.label, n.title, n.live,
625        n.properties, n.sourcePosition, n.origin, n.opcode, n.control,
626        n.opinfo, n.type);
627      const previous = nodeLabelMap[label.id];
628      if (!label.equals(previous)) {
629        if (previous != undefined) {
630          label.setInplaceUpdatePhase(phase.name);
631        }
632        nodeLabelMap[label.id] = label;
633      }
634      n.nodeLabel = nodeLabelMap[label.id];
635    }
636  }
637
638  repairPhaseId(anyPhaseId) {
639    return Math.max(0, Math.min(anyPhaseId | 0, this.phases.length - 1));
640  }
641
642  getPhase(phaseId: number) {
643    return this.phases[phaseId];
644  }
645
646  getPhaseIdByName(phaseName: string) {
647    return this.phaseNames.get(phaseName);
648  }
649
650  forEachPhase(f: (value: Phase, index: number, array: Array<Phase>) => void) {
651    this.phases.forEach(f);
652  }
653
654  addAnyPositionToLine(lineNumber: number | string, sourcePosition: AnyPosition) {
655    const lineNumberString = anyToString(lineNumber);
656    if (!this.linePositionMap.has(lineNumberString)) {
657      this.linePositionMap.set(lineNumberString, []);
658    }
659    const A = this.linePositionMap.get(lineNumberString);
660    if (!A.includes(sourcePosition)) A.push(sourcePosition);
661  }
662
663  setSourceLineToBytecodePosition(sourceLineToBytecodePosition: Array<number> | undefined) {
664    if (!sourceLineToBytecodePosition) return;
665    sourceLineToBytecodePosition.forEach((pos, i) => {
666      this.addAnyPositionToLine(i, { bytecodePosition: pos });
667    });
668  }
669
670  lineToSourcePositions(lineNumber: number | string) {
671    const positions = this.linePositionMap.get(anyToString(lineNumber));
672    if (positions === undefined) return [];
673    return positions;
674  }
675
676  parseSchedule(phase) {
677    function createNode(state: any, match) {
678      let inputs = [];
679      if (match.groups.args) {
680        const nodeIdsString = match.groups.args.replace(/\s/g, '');
681        const nodeIdStrings = nodeIdsString.split(',');
682        inputs = nodeIdStrings.map(n => Number.parseInt(n, 10));
683      }
684      const node = {
685        id: Number.parseInt(match.groups.id, 10),
686        label: match.groups.label,
687        inputs: inputs
688      };
689      if (match.groups.blocks) {
690        const nodeIdsString = match.groups.blocks.replace(/\s/g, '').replace(/B/g, '');
691        const nodeIdStrings = nodeIdsString.split(',');
692        const successors = nodeIdStrings.map(n => Number.parseInt(n, 10));
693        state.currentBlock.succ = successors;
694      }
695      state.nodes[node.id] = node;
696      state.currentBlock.nodes.push(node);
697    }
698    function createBlock(state, match) {
699      let predecessors = [];
700      if (match.groups.in) {
701        const blockIdsString = match.groups.in.replace(/\s/g, '').replace(/B/g, '');
702        const blockIdStrings = blockIdsString.split(',');
703        predecessors = blockIdStrings.map(n => Number.parseInt(n, 10));
704      }
705      const block = {
706        id: Number.parseInt(match.groups.id, 10),
707        isDeferred: match.groups.deferred != undefined,
708        pred: predecessors.sort(),
709        succ: [],
710        nodes: []
711      };
712      state.blocks[block.id] = block;
713      state.currentBlock = block;
714    }
715    function setGotoSuccessor(state, match) {
716      state.currentBlock.succ = [Number.parseInt(match.groups.successor.replace(/\s/g, ''), 10)];
717    }
718    const rules = [
719      {
720        lineRegexps:
721          [/^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)$/,
722            /^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)\ ->\ (?<blocks>.*)$/,
723            /^\s*(?<id>\d+):\ (?<label>.*)$/
724          ],
725        process: createNode
726      },
727      {
728        lineRegexps:
729          [/^\s*---\s*BLOCK\ B(?<id>\d+)\s*(?<deferred>\(deferred\))?(\ <-\ )?(?<in>[^-]*)?\ ---$/
730          ],
731        process: createBlock
732      },
733      {
734        lineRegexps:
735          [/^\s*Goto\s*->\s*B(?<successor>\d+)\s*$/
736          ],
737        process: setGotoSuccessor
738      }
739    ];
740
741    const lines = phase.data.split(/[\n]/);
742    const state = { currentBlock: undefined, blocks: [], nodes: [] };
743
744    nextLine:
745    for (const line of lines) {
746      for (const rule of rules) {
747        for (const lineRegexp of rule.lineRegexps) {
748          const match = line.match(lineRegexp);
749          if (match) {
750            rule.process(state, match);
751            continue nextLine;
752          }
753        }
754      }
755      console.log("Warning: unmatched schedule line \"" + line + "\"");
756    }
757    phase.schedule = state;
758    return phase;
759  }
760
761  parseSequence(phase) {
762    phase.sequence = { blocks: phase.blocks,
763                       register_allocation: phase.register_allocation ? new RegisterAllocation(phase.register_allocation)
764                                                                      : undefined };
765    return phase;
766  }
767}
768