blob: afba59e951636a4f48e4d8a48adeb26c433ce13d [file] [log] [blame]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math' as math show min, max;
import 'dart:typed_data';
import 'package:native_compiler/back_end/back_end_state.dart';
import 'package:native_compiler/back_end/constraints.dart';
import 'package:native_compiler/back_end/locations.dart';
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/ir_to_text.dart';
import 'package:cfg/ir/liveness_analysis.dart';
import 'package:cfg/ir/types.dart';
import 'package:cfg/passes/pass.dart';
import 'package:native_compiler/utils/interval_list.dart';
abstract base class RegisterAllocator extends Pass {
final BackEndState backEndState;
RegisterAllocator(this.backEndState) : super('RegisterAllocation');
}
/// Linear scan register allocator.
///
/// Allocates registers on the linearized control flow.
/// Live ranges are represented as a sequence of intervals.
///
/// The original idea of linear scan register allocation is described in
/// Massimiliano Poletto and Vivek Sarkar "Linear scan register allocation" (1999).
///
/// This variant of linear scan is largerly based on
/// "Linear Scan Register Allocation for the Java HotSpot Client Compiler",
/// by Christian Wimmer (2004).
/// http://www.christianwimmer.at/Publications/Wimmer04a/Wimmer04a.pdf
///
final class LinearScanRegisterAllocator extends RegisterAllocator {
// Step of instruction positions in the linearized control flow.
// Increment positions by 2 to leave space for ParallelMove instructions
// which can be inserted later between instructions.
static const int step = 2;
static const bool trace = const bool.fromEnvironment('trace.regalloc');
static const int maxPosition = 0x7fffffff;
final Constraints constraints;
/// Instruction id -> instruction position in the
/// linearized control flow.
late final Int32List _instructionPos;
/// Instruction position ~/ step -> instruction id.
late final Int32List _instructionByPos;
/// Instruction id -> LiveRange corresponding to the instruction output.
late final List<LiveRange?> _liveRanges;
/// Locations of instruction inputs/outputs/temps.
final Map<OperandId, Location> _operandLocations = {};
/// Live ranges corresponding to the fixed register locations.
late final List<LiveRange?> _cpuRegLiveRanges;
late final List<LiveRange?> _fpuRegLiveRanges;
/// Register index -> [Location].
late List<Location?> _registerLocations;
/// List of live ranges which were not handled yet by [allocate].
late List<LiveRange> _unhandled;
int _unhandledIndex = -1;
/// For each register, list of live ranges allocated to this register,
/// or null if register is not allocatable.
late List<List<LiveRange>?> _allocated;
LinearScanRegisterAllocator(super.backEndState, this.constraints);
RegisterClass registerClass(Definition instr) =>
instr.type is DoubleType ? RegisterClass.fpu : RegisterClass.cpu;
int instructionPos(Instruction instr) => _instructionPos[instr.id];
int blockStartPos(Block block) => instructionPos(block);
int blockEndPos(Block block) => instructionPos(block.lastInstruction) + step;
Instruction instructionByPos(int pos) =>
graph.instructions[_instructionByPos[pos ~/ step]];
LiveRange liveRangeFor(Definition instr) {
assert(instr is! Constant);
return _liveRanges[instr.id] ??= LiveRange(registerClass(instr));
}
LiveRange regLiveRange(PhysicalRegister r) => (r is Register)
? (_cpuRegLiveRanges[r.index] ??= LiveRange(
RegisterClass.cpu,
isPhysical: true,
))
: (_fpuRegLiveRanges[r.index] ??= LiveRange(
RegisterClass.fpu,
isPhysical: true,
));
@override
void run() {
numberInstructions();
final liveness = SSALivenessAnalysis(graph);
liveness.analyze();
buildLiveRanges(liveness);
coalesceLiveRanges();
allocate(
RegisterClass.cpu,
constraints.getNumberOfRegisters(),
constraints.getAllocatableRegisters(),
_cpuRegLiveRanges,
);
allocate(
RegisterClass.fpu,
constraints.getNumberOfFPRegisters(),
constraints.getAllocatableFPRegisters(),
_fpuRegLiveRanges,
);
resolveDataFlow(liveness);
backEndState.operandLocations = _operandLocations;
backEndState.stackFrame.finalize();
}
/// Number instructions in the linearized control flow.
void numberInstructions() {
_instructionPos = Int32List(graph.instructions.length);
_instructionByPos = Int32List(graph.instructions.length + 1);
// Reserve 0 for uninitialized value, use even positions
// for the instructions.
int pos = step;
for (final block in backEndState.codeGenBlockOrder) {
_instructionPos[block.id] = pos;
_instructionByPos[pos ~/ step] = block.id;
pos += step;
for (final instr in block) {
if (instr is Phi) {
// All Phis have the same position as their Block.
_instructionPos[instr.id] = blockStartPos(block);
} else {
_instructionPos[instr.id] = pos;
_instructionByPos[pos ~/ step] = instr.id;
pos += step;
}
}
assert(pos == blockEndPos(block));
}
errorContext.annotator = (Instruction instr) =>
'[${instructionPos(instr)}]';
}
void buildLiveRanges(SSALivenessAnalysis liveness) {
_liveRanges = List.filled(graph.instructions.length, null, growable: true);
_cpuRegLiveRanges = List.filled(constraints.getNumberOfRegisters(), null);
_fpuRegLiveRanges = List.filled(constraints.getNumberOfFPRegisters(), null);
for (final block in backEndState.codeGenBlockOrder.reversed) {
final blockStart = blockStartPos(block);
final blockEnd = blockEndPos(block);
// Add intervals for values which are live-out.
for (final instrId in liveness.liveOut(block).elements) {
final instr = graph.instructions[instrId] as Definition;
if (instr is! Constant) {
final liveRange = liveRangeFor(instr);
liveRange.addInterval(blockStart, blockEnd);
}
}
// Add uses in the Phis in the successor block.
if (block.successors.length == 1) {
final succ = block.successors.single;
if (succ is JoinBlock) {
final int predIndex = succ.predecessors.indexOf(block);
assert(predIndex >= 0);
for (final phi in succ.phis) {
final input = phi.inputDefAt(predIndex);
if (input is! Constant) {
final inputConstr = constraints
.getConstraints(phi)!
.inputs[predIndex]!;
final operandId = OperandId.input(phi.id, predIndex);
final liveRange = liveRangeFor(input);
_processInput(phi, liveRange, blockEnd, inputConstr, operandId);
}
}
}
}
for (final instr in block.reversed) {
if (instr is CallInstruction) {
backEndState.stackFrame.allocateArgumentsSlots(instr);
}
final pos = instructionPos(instr);
final constr = constraints.getConstraints(instr);
if (constr == null) {
continue;
}
// Ignore Phis as they are processed in the predecessor blocks.
if (instr is! Phi) {
// Process inputs.
for (int i = 0, n = instr.inputCount; i < n; ++i) {
final input = instr.inputDefAt(i);
final inputConstr = constr.inputs[i];
final operandId = OperandId.input(instr.id, i);
if (input is Constant) {
if (inputConstr != null) {
_processConstantInput(
instr,
input.value,
pos,
inputConstr,
operandId,
);
}
} else {
final liveRange = liveRangeFor(input);
// Extend range up to block start position.
// It will be truncated by the definition if needed.
liveRange.addInterval(blockStart, pos);
if (inputConstr != null) {
_processInput(instr, liveRange, pos, inputConstr, operandId);
}
}
}
}
// Process temps.
for (int i = 0, n = constr.temps.length; i < n; ++i) {
final operandId = OperandId.temp(instr.id, i);
_processTemp(pos, constr.temps[i], operandId);
}
// Process output.
if (instr is Definition && instr is! Constant) {
final operandId = OperandId.result(instr.id);
final liveRange = liveRangeFor(instr);
final resultConstr = constr.result;
liveRange.defineAt(
instructionPos(instr) +
((resultConstr is PhysicalRegister ||
resultConstr is ParameterStackLocation)
? 1
: 0),
);
if (resultConstr != null) {
_processOutput(instr, liveRange, pos, resultConstr, operandId);
}
}
}
}
errorContext.annotator = (Instruction instr) {
if (instr is ParallelMove) return null;
return '[${instructionPos(instr)}]' +
((instr is Definition && instr is! Constant)
? ' ${liveRangeFor(instr)}'
: '');
};
if (trace) {
print(
IrToText(
graph,
printDominators: true,
printLoops: true,
annotator: errorContext.annotator,
).toString(),
);
}
}
void _processInput(
Instruction instr,
LiveRange liveRange,
int pos,
Constraint constr,
OperandId operandId,
) {
if (constr is PhysicalRegister) {
regLiveRange(constr).addInterval(pos, pos + 1);
final loc = liveRange.addUse(pos - 1, constr);
_insertMoveBefore(instr, ParallelMoveStage.input, loc, constr);
_operandLocations[operandId] = constr;
} else {
final loc = liveRange.addUse(pos - 1, constr);
_operandLocations[operandId] = loc;
}
}
// TODO: allocate constants to registers.
void _processConstantInput(
Instruction instr,
ConstantValue value,
int pos,
Constraint constr,
OperandId operandId,
) {
if (constr is PhysicalRegister) {
_insertMoveBefore(instr, ParallelMoveStage.input, null, constr, value);
regLiveRange(constr).addInterval(pos, pos + 1);
_operandLocations[operandId] = constr;
} else {
final liveRange = LiveRange(constr.registerClass);
_liveRanges.add(liveRange);
liveRange.addInterval(pos - 1, pos);
final loc = liveRange.addUse(pos - 1, constr);
_insertMoveBefore(instr, ParallelMoveStage.input, null, loc, value);
_operandLocations[operandId] = loc;
}
}
void _processTemp(int pos, Constraint constr, OperandId operandId) {
if (constr is PhysicalRegister) {
regLiveRange(constr).addInterval(pos, pos + 1);
_operandLocations[operandId] = constr;
} else {
final liveRange = LiveRange(constr.registerClass);
_liveRanges.add(liveRange);
liveRange.addInterval(pos, pos + 1);
final loc = liveRange.addUse(pos, constr);
_operandLocations[operandId] = loc;
}
}
void _processOutput(
Definition instr,
LiveRange liveRange,
int pos,
Constraint constr,
OperandId operandId,
) {
assert(instr is! ControlFlowInstruction);
if (constr is PhysicalRegister) {
regLiveRange(constr).addInterval(pos, pos + 1);
_operandLocations[operandId] = constr;
if (instr.hasUses) {
final loc = liveRange.addUse(pos + 1, constr);
_insertMoveBefore(
_nextInstruction(instr),
ParallelMoveStage.output,
constr,
loc,
);
}
} else if (constr is ParameterStackLocation) {
assert(liveRange.splitFrom == null);
liveRange.spillSlot = constr;
_operandLocations[operandId] = constr;
if (instr.hasUses) {
final loc = liveRange.addUse(pos + 1, constr);
_insertMoveBefore(
_nextInstruction(instr),
ParallelMoveStage.output,
constr,
loc,
);
}
} else {
final loc = liveRange.addUse(pos, constr);
_operandLocations[operandId] = loc;
}
}
ParallelMove _parallelMoveBefore(Instruction instr, ParallelMoveStage stage) {
assert(instr is! Phi);
assert(instr is! Constant);
assert(instr is! ParallelMove);
for (;;) {
Instruction? prev = instr.previous;
if (prev is! ParallelMove || prev.stage.index < stage.index) {
break;
}
if (prev.stage == stage) {
return prev;
}
instr = prev;
}
final move = ParallelMove(instr.graph, stage);
move.insertBefore(instr);
return move;
}
void _insertMoveBefore(
Instruction instr,
ParallelMoveStage stage,
Location? src,
Location dst, [
ConstantValue? value,
]) {
ParallelMove move = _parallelMoveBefore(instr, stage);
MoveOp moveOp;
if (src == null) {
moveOp = LoadConstant(value!, dst);
} else {
assert(value == null);
moveOp = Move(src, dst);
}
move.moves.add(moveOp);
}
/// Returns the instruction after [instr], skipping all [ParallelMove]
/// instructions in between.
Instruction _nextInstruction(Instruction instr) {
instr = instr.next!;
while (instr is ParallelMove) {
instr = instr.next!;
}
return instr;
}
/// Tries to merge live ranges which would benefit from
/// allocation to the same location.
///
/// TODO: Currently only inputs/outputs of phis are merged, but we
/// can also merge inputs/outputs of instructions reusing
/// the same register if/when we add "SameAsFirstInput" constraint.
void coalesceLiveRanges() {
for (final block in backEndState.codeGenBlockOrder.reversed) {
if (block is JoinBlock) {
for (final phi in block.phis) {
final liveRange = liveRangeFor(phi).bundle;
for (int i = 0, n = phi.inputCount; i < n; ++i) {
final input = phi.inputDefAt(i);
if (input is! Constant) {
liveRange.tryMerge(liveRangeFor(input).bundle);
}
}
}
}
}
}
void allocate(
RegisterClass registerClass,
int numberOfRegisters,
List<PhysicalRegister> allocatableRegisters,
List<LiveRange?> blockedRegisters,
) {
_unhandled = <LiveRange>[];
for (final liveRange in _liveRanges) {
if (liveRange != null &&
liveRange.registerClass == registerClass &&
liveRange.mergedTo == null) {
_unhandled.add(liveRange);
}
}
_unhandled.sort((a, b) => a.start.compareTo(b.start));
_unhandledIndex = 0;
if (trace) {
print('UNHANDLED:');
print('----------');
for (final range in _unhandled) {
print(range);
}
print('----------');
}
_registerLocations = List<Location?>.filled(numberOfRegisters, null);
_allocated = List<List<LiveRange>?>.filled(numberOfRegisters, null);
for (final reg in allocatableRegisters) {
_registerLocations[reg.index] = reg;
final blocked = blockedRegisters[reg.index];
assert(blocked == null || blocked.isPhysical);
_allocated[reg.index] = <LiveRange>[if (blocked != null) blocked];
}
if (trace) {
print('BLOCKED:');
print('----------');
for (var reg = 0; reg < _allocated.length; ++reg) {
final ranges = _allocated[reg];
if (ranges != null && ranges.isNotEmpty) {
print('${_registerLocations[reg]}: ${ranges.single}');
}
}
print('----------');
}
for (; _unhandledIndex < _unhandled.length; ++_unhandledIndex) {
final range = _unhandled[_unhandledIndex];
if (trace) {
print('Allocating $range');
}
advance(range.start);
if (!allocateFreeRegister(range)) {
allocateBlockedRegister(range);
}
}
advance(maxPosition);
}
void advance(int pos) {
if (trace) {
print('Advance to $pos');
}
for (var reg = 0; reg < _allocated.length; ++reg) {
final ranges = _allocated[reg];
if (ranges == null) {
continue;
}
var i = 0;
for (var j = 0; j < ranges.length; ++j) {
final range = ranges[j];
if (range.advance(pos)) {
// Live range ended, set its location and move it to [handled].
if (range.isPhysical) {
assert(range.uses.isEmpty);
if (trace) {
print('Finished blocked $range');
}
} else {
if (trace) {
print('Finished $range, allocated to ${_registerLocations[reg]}');
}
range.setLocation(_registerLocations[reg]!);
}
} else {
ranges[i++] = range;
}
}
ranges.length = i;
}
}
/// Tries to allocate a free register for [range].
bool allocateFreeRegister(LiveRange range) {
// Handle fixed register location first.
final preferredRegisterUse = range.firstPreferredRegisterUse;
if (preferredRegisterUse != null) {
int fixedRegister =
(preferredRegisterUse.constraint as PhysicalRegister).index;
int freeUntil = firstIntersectionWithAllocated(range, fixedRegister);
if (freeUntil > preferredRegisterUse.pos) {
if (freeUntil < range.end) {
if (trace) {
print('Split $range at $freeUntil');
}
addToUnhandled(range.splitAt(freeUntil));
}
if (trace) {
print(
'Allocating $range to preferred ${_registerLocations[fixedRegister]}',
);
}
_allocated[fixedRegister]!.add(range);
return true;
}
}
// Pick a register which is free the longest span.
var candidate = -1;
int freeUntil = range.start;
for (var reg = 0; reg < _allocated.length; ++reg) {
if (_allocated[reg] == null) continue;
int intersection = firstIntersectionWithAllocated(range, reg);
if (intersection > freeUntil) {
candidate = reg;
freeUntil = intersection;
}
if (intersection == maxPosition) {
break;
}
}
if (candidate < 0) {
if (trace) {
print('Free register is not found');
}
return false;
}
if (freeUntil < range.end) {
if (trace) {
print('Split $range at $freeUntil');
}
addToUnhandled(range.splitAt(freeUntil));
}
if (trace) {
print('Allocating $range to free ${_registerLocations[candidate]}');
}
_allocated[candidate]!.add(range);
return true;
}
/// Returns position of the first intersection of [range] with live ranges
/// allocated to [reg], or [maxPosition] if they do not intersect.
int firstIntersectionWithAllocated(LiveRange range, int reg) {
int pos = maxPosition;
for (final allocated in _allocated[reg]!) {
final intersection = range.intervals.firstIntersection(
0,
allocated.intervals,
allocated.currentInterval,
);
if (intersection >= 0 && intersection < pos) {
pos = intersection;
}
}
return pos;
}
/// Add [tail] to the [_unhandled] list after splitting.
void addToUnhandled(LiveRange tail) {
if (trace) {
print('Add $tail to unhandled');
}
final start = tail.start;
int i = _unhandledIndex + 1;
for (; i < _unhandled.length; ++i) {
final range = _unhandled[i];
if (range.start > start) {
break;
}
_unhandled[i - 1] = range;
}
_unhandled[i - 1] = tail;
--_unhandledIndex;
}
void allocateBlockedRegister(LiveRange unallocated) {
var candidateReg = -1;
var bestFreeUntil = 0;
int bestBlockedAt = maxPosition;
// Select a live range which is not going to be used for the longest time.
// Spilling this live range would free a register as long as possible.
void inspectAllocatedRegister(int reg) {
final ranges = _allocated[reg];
if (ranges == null) {
return;
}
int freeUntil = maxPosition;
int blockedAt = maxPosition;
final start = unallocated.start;
for (final allocated in ranges) {
if (allocated.nextIntervalContains(start)) {
// Active interval.
if (allocated.isPhysical) {
return;
}
int nextUsePosition = allocated.nextUse?.pos ?? allocated.end;
if (nextUsePosition < freeUntil) {
freeUntil = nextUsePosition;
}
} else {
// Inactive interval.
final intersection = unallocated.intervals.firstIntersection(
0,
allocated.intervals,
allocated.currentInterval,
);
if (intersection >= 0) {
if (intersection < freeUntil) {
freeUntil = intersection;
}
if (allocated.isPhysical && intersection < blockedAt) {
blockedAt = intersection;
}
}
}
if (freeUntil <= bestFreeUntil) {
return;
}
}
assert(freeUntil > bestFreeUntil);
bestFreeUntil = freeUntil;
bestBlockedAt = blockedAt;
candidateReg = reg;
}
for (var reg = 0; reg < _allocated.length; ++reg) {
inspectAllocatedRegister(reg);
}
int firstUsePos = unallocated.uses.isEmpty
? unallocated.start
: unallocated.uses.last.pos;
if (bestFreeUntil < firstUsePos) {
if (unallocated.start < firstUsePos) {
if (trace) {
print('Split $unallocated at $firstUsePos');
}
addToUnhandled(unallocated.splitAt(firstUsePos));
}
spillLiveRange(unallocated);
return;
}
assert(candidateReg >= 0);
if (bestBlockedAt < unallocated.end) {
if (trace) {
print('Split $unallocated at $bestBlockedAt');
}
addToUnhandled(unallocated.splitAt(bestBlockedAt));
}
assignNonFreeRegister(unallocated, candidateReg);
}
void spillLiveRange(LiveRange range) {
final splitParent = range.splitParent;
assert(splitParent.registerClass == range.registerClass);
final spillSlot = (splitParent.spillSlot ??= backEndState.stackFrame
.allocateSpillSlot(splitParent.registerClass));
if (trace) {
print('Spill $range to $spillSlot');
}
range.setLocation(spillSlot);
}
void assignNonFreeRegister(LiveRange range, int reg) {
if (trace) {
print('Assigning non-free ${_registerLocations[reg]} to $range');
}
_allocated[reg]!.removeWhere(
(LiveRange allocated) =>
!allocated.isPhysical && evictIntersection(allocated, range, reg),
);
_allocated[reg]!.add(range);
}
bool evictIntersection(LiveRange allocated, LiveRange unallocated, int reg) {
final intersection = unallocated.intervals.firstIntersection(
0,
allocated.intervals,
allocated.currentInterval,
);
if (intersection < 0) {
if (trace) {
print(' ... no intersection with $allocated');
}
return false;
}
final spillPos = unallocated.start;
final nextUse = allocated.nextUse;
if (nextUse == null) {
// No more uses which require registers.
if (trace) {
print('Split $allocated at $spillPos');
}
spillLiveRange(allocated.splitAt(spillPos));
if (trace) {
print('Finish evicted $allocated at ${_registerLocations[reg]}');
}
allocated.setLocation(_registerLocations[reg]!);
} else {
final usePos = nextUse.pos;
final restorePos = (spillPos < intersection)
? math.min(intersection, usePos)
: usePos;
if (spillPos == allocated.start) {
if (trace) {
print('Split $allocated at $restorePos');
}
addToUnhandled(allocated.splitAt(restorePos));
spillLiveRange(allocated);
} else {
if (trace) {
print('Split $allocated at $spillPos');
}
final rangeToSpill = allocated.splitAt(spillPos);
if (trace) {
print('Split $rangeToSpill at $restorePos');
}
addToUnhandled(rangeToSpill.splitAt(restorePos));
spillLiveRange(rangeToSpill);
if (trace) {
print('Finish evicted $allocated at ${_registerLocations[reg]}');
}
allocated.setLocation(_registerLocations[reg]!);
}
}
return true;
}
/// Resolve data flow between blocks and split live ranges.
void resolveDataFlow(SSALivenessAnalysis liveness) {
for (var i = 0; i < _liveRanges.length; ++i) {
LiveRange? liveRange = _liveRanges[i];
if (liveRange == null) {
continue;
}
currentInstruction = (i < graph.instructions.length)
? graph.instructions[i]
: null;
// Insert moves between split live ranges at split points.
if (liveRange.mergedTo == null) {
for (;;) {
LiveRange? next = liveRange!.splitNext;
if (next == null) {
break;
}
if (liveRange.end == next.start &&
liveRange.allocatedLocation != next.allocatedLocation &&
next.allocatedLocation is! StackLocation) {
Instruction instr = instructionByPos(next.start);
if (instr is JoinBlock && instr.hasPhis) {
instr = instr.phis.last;
}
if (instr is! Goto && next.start.isOdd) {
instr = _nextInstruction(instr);
}
_insertMoveBefore(
instr,
ParallelMoveStage.split,
liveRange.allocatedLocation!,
next.allocatedLocation!,
);
}
liveRange = next;
}
}
// Fill in spill slots after definitions if needed
// (so the value is always available in the spill slot).
liveRange = _liveRanges[i]!.bundle;
assert(liveRange.splitParent == liveRange);
final spillSlot = liveRange.spillSlot;
if (spillSlot != null &&
spillSlot is! ParameterStackLocation &&
i < graph.instructions.length) {
Instruction instr = graph.instructions[i];
if (instr is Phi) {
instr = (instr.block as JoinBlock).phis.last;
}
liveRange = liveRange.findSplitChildAt(instructionPos(instr) + 1);
if (liveRange.allocatedLocation is! StackLocation) {
_insertMoveBefore(
_nextInstruction(instr),
ParallelMoveStage.spill,
liveRange.allocatedLocation!,
spillSlot,
);
}
}
}
for (final block in backEndState.codeGenBlockOrder) {
final blockStart = blockStartPos(block);
for (
int predIndex = 0, n = block.predecessors.length;
predIndex < n;
++predIndex
) {
final pred = block.predecessors[predIndex];
final insertionPoint = block is JoinBlock
? pred.lastInstruction
: _nextInstruction(block);
final predEnd = blockEndPos(pred);
// Insert moves between split live ranges at control flow edges
// which do not match linearized control flow.
if (predEnd != blockStart) {
for (final instrId in liveness.liveIn(block).elements) {
LiveRange? liveRange = _liveRanges[instrId]?.bundle;
if (liveRange != null) {
final from = liveRange.findSplitChildAt(predEnd - 1);
final to = liveRange.findSplitChildAt(blockStart);
if (from.allocatedLocation != to.allocatedLocation &&
to.allocatedLocation is! StackLocation) {
_insertMoveBefore(
insertionPoint,
ParallelMoveStage.control,
from.allocatedLocation!,
to.allocatedLocation!,
);
}
}
}
}
// Insert moves between phis and their inputs.
if (block is JoinBlock) {
for (final phi in block.phis) {
final to = liveRangeFor(phi).bundle.findSplitChildAt(blockStart);
final input = phi.inputDefAt(predIndex);
if (input is Constant) {
_insertMoveBefore(
insertionPoint,
ParallelMoveStage.control,
null,
to.allocatedLocation!,
input.value,
);
} else {
final from = liveRangeFor(
input,
).bundle.findSplitChildAt(predEnd - 1);
if (from.allocatedLocation != to.allocatedLocation) {
_insertMoveBefore(
insertionPoint,
ParallelMoveStage.control,
from.allocatedLocation!,
to.allocatedLocation!,
);
}
}
}
}
}
}
}
}
/// Single use of a [LiveRange].
class UsePosition {
final int pos;
final VirtualLocation vloc;
Constraint constraint;
UsePosition(this.pos, this.vloc, this.constraint);
@override
String toString() => 'Use[$pos, $constraint]';
}
/// A unit of register allocation.
///
/// Combines multiple related non-intersecting live ranges
/// which would benefit from being allocating to the same location.
/// Has a list of live intervals [s1, e1), [s2, e2), ...., [sN, eN).
class LiveRange {
static const int maxUseIndex = 0xffffffff;
final RegisterClass registerClass;
// Whether this live range represents a blocked physical register
// or an allocatable/splittable/spillable live range.
final bool isPhysical;
// List of intervals.
IntervalList intervals = IntervalList();
// Uses of this live range in the descending order.
List<UsePosition> uses = [];
// Live range where this live range was merged.
LiveRange? mergedTo;
// Original live range which was split.
LiveRange? splitFrom;
// Next live range which was split from this live range.
LiveRange? splitNext;
// Location assigned to this live range.
Location? allocatedLocation;
// Spill slot assigned to this live range (and all its split siblings).
StackLocation? spillSlot;
// Used for the currently tracked live ranges (both active and inactive).
// Index in [intervals].
int currentInterval = 0;
// uses.length - index in [uses].
int currentUse = 1;
LiveRange(this.registerClass, {this.isPhysical = false});
/// Add interval [start, end) to this live range.
///
/// Intervals should be added in the descending order by the end position.
/// Intersecting intervals are not allowed except nested intervals with
/// the same starting position (which are ignored).
void addInterval(int start, int end) {
intervals.addInterval(start, end);
}
/// Cut the last use interval to start at [pos].
void defineAt(int pos) {
if (intervals.isEmpty) {
// Value is defined but not used.
// Add synthetic one-point interval.
intervals.addInterval(pos, pos + 1);
} else {
assert(intervals.startAt(0) <= pos && pos < intervals.endAt(0));
intervals.setStartAt(0, pos);
}
}
VirtualLocation addUse(int pos, Constraint constr) {
if (uses.isNotEmpty) {
final last = uses.last;
if (last.pos == pos) {
// Reuse existing use with the same position.
if (constr is PhysicalRegister &&
last.constraint is! PhysicalRegister) {
last.constraint = constr;
}
return last.vloc;
}
}
final vloc = VirtualLocation();
uses.add(UsePosition(pos, vloc, constr));
return vloc;
}
int get start => intervals.start;
int get end => intervals.end;
LiveRange get bundle {
LiveRange? parent = mergedTo;
if (parent == null) return this;
while (parent!.mergedTo != null) {
parent = parent.mergedTo;
}
mergedTo = parent;
return parent;
}
bool intersects(LiveRange other) => intervals.intersects(other.intervals);
void tryMerge(LiveRange other) {
assert(mergedTo == null && other.mergedTo == null);
assert(!intervals.isEmpty && !other.intervals.isEmpty);
if (this == other ||
this.intersects(other) ||
registerClass != other.registerClass) {
return;
}
intervals.merge(other.intervals);
uses = _mergeUsePositions(uses, other.uses);
other.mergedTo = this;
}
List<UsePosition> _mergeUsePositions(
List<UsePosition> list1,
List<UsePosition> list2,
) {
final list = <UsePosition>[];
var i = 0;
var j = 0;
while (i < list1.length && j < list2.length) {
final u1 = list1[i];
final u2 = list2[j];
if (u1.pos >= u2.pos) {
list.add(u1);
++i;
} else {
list.add(u2);
++j;
}
}
while (i < list1.length) {
list.add(list1[i++]);
}
while (j < list2.length) {
list.add(list2[j++]);
}
return list;
}
LiveRange get splitParent => splitFrom ?? this;
// TODO: figure out more optimal split position using loop boundaries.
LiveRange splitAt(int pos) {
assert(pos > start);
assert(!isPhysical);
assert(mergedTo == null);
assert(splitNext == null);
final sibling = LiveRange(registerClass);
sibling.splitFrom = splitParent;
sibling.intervals = intervals.splitAt(pos);
_splitUsePositions(pos, uses, sibling.uses);
currentUse = math.max(1, currentUse - sibling.uses.length);
splitNext = sibling;
return sibling;
}
void _splitUsePositions(
int pos,
List<UsePosition> src,
List<UsePosition> dst,
) {
var i = 0;
for (; i < src.length; ++i) {
if (src[i].pos < pos) break;
}
dst.addAll(src.getRange(0, i));
src.removeRange(0, i);
}
/// Advance [currentInterval] and [currentUse], skipping
/// intervals and uses which end before [pos].
/// Returns true if the whole live range ended before [pos].
bool advance(int pos) {
if (pos >= end) {
currentInterval = intervals.length;
currentUse = uses.length + 1;
return true;
}
while (intervals.endAt(currentInterval) <= pos) {
++currentInterval;
}
while (currentUse <= uses.length &&
uses[uses.length - currentUse].pos < pos) {
++currentUse;
}
return false;
}
UsePosition? get nextUse =>
(currentUse <= uses.length) ? uses[uses.length - currentUse] : null;
(int, int)? get nextInterval => (currentInterval < intervals.length)
? (intervals.startAt(currentInterval), intervals.endAt(currentInterval))
: null;
bool nextIntervalContains(int pos) =>
intervals.startAt(currentInterval) <= pos &&
pos < intervals.endAt(currentInterval);
UsePosition? get firstPreferredRegisterUse {
for (final use in uses.reversed) {
if (use.constraint is PhysicalRegister) {
return use;
}
}
return null;
}
void setLocation(Location loc) {
assert(!isPhysical);
allocatedLocation = loc;
for (final use in uses) {
use.vloc.location = loc;
}
}
LiveRange findSplitChildAt(int pos) {
for (LiveRange? range = this; range != null; range = range.splitNext) {
if (range.start <= pos && pos < range.end) {
return range;
}
}
throw 'Unable to find cover for pos $pos in $this';
}
@override
String toString() =>
'LR $intervals ${uses.reversed} next: $nextInterval $nextUse';
}