222 lines
7.5 KiB
TypeScript
222 lines
7.5 KiB
TypeScript
|
// Copyright 2022 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 * as C from "./common/constants";
|
||
|
import { Graph } from "./graph";
|
||
|
import { GraphNode } from "./phases/graph-phase/graph-node";
|
||
|
import { TurboshaftGraph } from "./turboshaft-graph";
|
||
|
import { TurboshaftGraphBlock } from "./phases/turboshaft-graph-phase/turboshaft-graph-block";
|
||
|
|
||
|
export class LayoutOccupation {
|
||
|
graph: Graph | TurboshaftGraph;
|
||
|
filledSlots: Array<boolean>;
|
||
|
occupations: Array<[number, number]>;
|
||
|
minSlot: number;
|
||
|
maxSlot: number;
|
||
|
|
||
|
constructor(graph: Graph | TurboshaftGraph) {
|
||
|
this.graph = graph;
|
||
|
this.filledSlots = new Array<boolean>();
|
||
|
this.occupations = new Array<[number, number]>();
|
||
|
this.minSlot = 0;
|
||
|
this.maxSlot = 0;
|
||
|
}
|
||
|
|
||
|
public clearOutputs(source: GraphNode | TurboshaftGraphBlock, extendHeight: boolean): void {
|
||
|
for (const edge of source.outputs) {
|
||
|
if (!edge.isVisible()) continue;
|
||
|
for (const inputEdge of edge.target.inputs) {
|
||
|
if (inputEdge.source === source) {
|
||
|
const horizontalPos = edge.getInputHorizontalPosition(this.graph, extendHeight);
|
||
|
this.clearPositionRangeWithMargin(horizontalPos, horizontalPos, C.NODE_INPUT_WIDTH / 2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public clearOccupied(): void {
|
||
|
for (const [firstSlot, endSlotExclusive] of this.occupations) {
|
||
|
this.clearSlotRange(firstSlot, endSlotExclusive);
|
||
|
}
|
||
|
this.occupations = new Array<[number, number]>();
|
||
|
}
|
||
|
|
||
|
public occupy(item: GraphNode | TurboshaftGraphBlock): number {
|
||
|
const width = item.getWidth();
|
||
|
const margin = C.MINIMUM_EDGE_SEPARATION;
|
||
|
const paddedWidth = width + 2 * margin;
|
||
|
const [direction, position] = this.getPlacementHint(item);
|
||
|
const x = position - paddedWidth + margin;
|
||
|
this.trace(`${item.id} placement hint [${x}, ${(x + paddedWidth)})`);
|
||
|
const placement = this.findSpace(x, paddedWidth, direction);
|
||
|
const [firstSlot, slotWidth] = placement;
|
||
|
const endSlotExclusive = firstSlot + slotWidth - 1;
|
||
|
this.occupySlotRange(firstSlot, endSlotExclusive);
|
||
|
this.occupations.push([firstSlot, endSlotExclusive]);
|
||
|
if (direction < 0) {
|
||
|
return this.slotToLeftPosition(firstSlot + slotWidth) - width - margin;
|
||
|
} else if (direction > 0) {
|
||
|
return this.slotToLeftPosition(firstSlot) + margin;
|
||
|
} else {
|
||
|
return this.slotToLeftPosition(firstSlot + slotWidth / 2) - (width / 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public occupyInputs(item: GraphNode | TurboshaftGraphBlock, extendHeight: boolean): void {
|
||
|
for (let i = 0; i < item.inputs.length; ++i) {
|
||
|
if (item.inputs[i].isVisible()) {
|
||
|
const edge = item.inputs[i];
|
||
|
if (!edge.isBackEdge()) {
|
||
|
const horizontalPos = edge.getInputHorizontalPosition(this.graph, extendHeight);
|
||
|
this.trace(`Occupying input ${i} of ${item.id} at ${horizontalPos}`);
|
||
|
this.occupyPositionRangeWithMargin(horizontalPos, horizontalPos, C.NODE_INPUT_WIDTH / 2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public print(): void {
|
||
|
let output = "";
|
||
|
for (let currentSlot = -40; currentSlot < 40; ++currentSlot) {
|
||
|
if (currentSlot != 0) {
|
||
|
output += " ";
|
||
|
} else {
|
||
|
output += "|";
|
||
|
}
|
||
|
}
|
||
|
console.log(output);
|
||
|
output = "";
|
||
|
for (let currentSlot2 = -40; currentSlot2 < 40; ++currentSlot2) {
|
||
|
if (this.filledSlots[this.slotToIndex(currentSlot2)]) {
|
||
|
output += "*";
|
||
|
} else {
|
||
|
output += " ";
|
||
|
}
|
||
|
}
|
||
|
console.log(output);
|
||
|
}
|
||
|
|
||
|
private getPlacementHint(item: GraphNode | TurboshaftGraphBlock): [number, number] {
|
||
|
let position = 0;
|
||
|
let direction = -1;
|
||
|
let outputEdges = 0;
|
||
|
let inputEdges = 0;
|
||
|
for (const outputEdge of item.outputs) {
|
||
|
if (!outputEdge.isVisible()) continue;
|
||
|
const output = outputEdge.target;
|
||
|
for (let l = 0; l < output.inputs.length; ++l) {
|
||
|
if (output.rank > item.rank) {
|
||
|
const inputEdge = output.inputs[l];
|
||
|
if (inputEdge.isVisible()) ++inputEdges;
|
||
|
if (output.inputs[l].source == item) {
|
||
|
position += output.x + output.getInputX(l) + C.NODE_INPUT_WIDTH / 2;
|
||
|
outputEdges++;
|
||
|
if (l >= (output.inputs.length / 2)) {
|
||
|
direction = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (outputEdges != 0) {
|
||
|
position /= outputEdges;
|
||
|
}
|
||
|
if (outputEdges > 1 || inputEdges == 1) {
|
||
|
direction = 0;
|
||
|
}
|
||
|
return [direction, position];
|
||
|
}
|
||
|
|
||
|
private occupyPositionRange(from: number, to: number): void {
|
||
|
this.occupySlotRange(this.positionToSlot(from), this.positionToSlot(to - 1));
|
||
|
}
|
||
|
|
||
|
private clearPositionRange(from: number, to: number): void {
|
||
|
this.clearSlotRange(this.positionToSlot(from), this.positionToSlot(to - 1));
|
||
|
}
|
||
|
|
||
|
private occupySlotRange(from: number, to: number): void {
|
||
|
this.trace(`Occupied [${this.slotToLeftPosition(from)} ${this.slotToLeftPosition(to + 1)})`);
|
||
|
this.setIndexRange(from, to, true);
|
||
|
}
|
||
|
|
||
|
private clearSlotRange(from: number, to: number): void {
|
||
|
this.trace(`Cleared [${this.slotToLeftPosition(from)} ${this.slotToLeftPosition(to + 1)})`);
|
||
|
this.setIndexRange(from, to, false);
|
||
|
}
|
||
|
|
||
|
private clearPositionRangeWithMargin(from: number, to: number, margin: number): void {
|
||
|
const fromMargin = from - Math.floor(margin);
|
||
|
const toMargin = to + Math.floor(margin);
|
||
|
this.clearPositionRange(fromMargin, toMargin);
|
||
|
}
|
||
|
|
||
|
private occupyPositionRangeWithMargin(from: number, to: number, margin: number): void {
|
||
|
const fromMargin = from - Math.floor(margin);
|
||
|
const toMargin = to + Math.floor(margin);
|
||
|
this.occupyPositionRange(fromMargin, toMargin);
|
||
|
}
|
||
|
|
||
|
private findSpace(pos: number, width: number, direction: number): [number, number] {
|
||
|
const widthSlots = Math.floor((width + C.NODE_INPUT_WIDTH - 1) /
|
||
|
C.NODE_INPUT_WIDTH);
|
||
|
|
||
|
const currentSlot = this.positionToSlot(pos + width / 2);
|
||
|
let widthSlotsRemainingLeft = widthSlots;
|
||
|
let widthSlotsRemainingRight = widthSlots;
|
||
|
let slotsChecked = 0;
|
||
|
|
||
|
while (true) {
|
||
|
const mod = slotsChecked++ % 2;
|
||
|
const currentScanSlot = currentSlot + (mod ? -1 : 1) * (slotsChecked >> 1);
|
||
|
if (!this.filledSlots[this.slotToIndex(currentScanSlot)]) {
|
||
|
if (mod) {
|
||
|
if (direction <= 0) --widthSlotsRemainingLeft;
|
||
|
} else if (direction >= 0) {
|
||
|
--widthSlotsRemainingRight;
|
||
|
}
|
||
|
if (widthSlotsRemainingLeft == 0 || widthSlotsRemainingRight == 0 ||
|
||
|
(widthSlotsRemainingLeft + widthSlotsRemainingRight) == widthSlots &&
|
||
|
(widthSlots == slotsChecked)) {
|
||
|
return mod ? [currentScanSlot, widthSlots]
|
||
|
: [currentScanSlot - widthSlots + 1, widthSlots];
|
||
|
}
|
||
|
} else {
|
||
|
if (mod) {
|
||
|
widthSlotsRemainingLeft = widthSlots;
|
||
|
} else {
|
||
|
widthSlotsRemainingRight = widthSlots;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private setIndexRange(from: number, to: number, value: boolean): void {
|
||
|
if (to < from) throw ("Illegal slot range");
|
||
|
while (from <= to) {
|
||
|
this.maxSlot = Math.max(from, this.maxSlot);
|
||
|
this.minSlot = Math.min(from, this.minSlot);
|
||
|
this.filledSlots[this.slotToIndex(from++)] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private positionToSlot(position: number): number {
|
||
|
return Math.floor(position / C.NODE_INPUT_WIDTH);
|
||
|
}
|
||
|
|
||
|
private slotToIndex(slot: number): number {
|
||
|
return slot >= 0 ? slot * 2 : slot * 2 + 1;
|
||
|
}
|
||
|
|
||
|
private slotToLeftPosition(slot: number): number {
|
||
|
return slot * C.NODE_INPUT_WIDTH;
|
||
|
}
|
||
|
|
||
|
private trace(message): void {
|
||
|
if (C.TRACE_LAYOUT) {
|
||
|
console.log(message);
|
||
|
}
|
||
|
}
|
||
|
}
|