blob: 16c5768dc57441a468b1bb6441456a4b6d8095dc [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:typed_data';
import 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/ir/visitor.dart';
import 'package:cfg/passes/pass.dart';
import 'package:native_compiler/back_end/assembler.dart';
import 'package:native_compiler/back_end/back_end_state.dart';
import 'package:native_compiler/back_end/code.dart';
import 'package:native_compiler/back_end/locations.dart';
import 'package:native_compiler/back_end/stack_frame.dart';
import 'package:native_compiler/runtime/object_layout.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
/// Base class for architecture-specific code generator.
///
/// Contains common algorithms and code generation snippets which can be
/// used on multiple architectures.
abstract base class CodeGenerator extends Pass
implements InstructionVisitor<void> {
final BackEndState backEndState;
late final Assembler _asm;
/// Index of the current block in [codeGenBlockOrder].
int _currentBlockIndex = -1;
/// Maps block preorder number to a preorder number of
/// the first non-empty block.
late final Int32List _firstNonEmptyBlock = _computeFirstNonEmptyBlock();
/// Maps preorder number of a block to
/// a [Label] corresponding to the block entry.
late final List<Label> _blockLabels = List<Label>.generate(
graph.preorder.length,
(i) => Label(),
);
/// Slow paths generated after all blocks.
final List<SlowPath> _slowPaths = [];
CodeGenerator(this.backEndState) : super('CodeGen');
VMOffsets get vmOffsets => backEndState.vmOffsets;
ObjectLayout get objectLayout => backEndState.objectLayout;
List<Block> get codeGenBlockOrder => backEndState.codeGenBlockOrder;
StackFrame get stackFrame => backEndState.stackFrame;
Location loc(OperandId operandId) =>
backEndState.operandLocations[operandId]!.physicalLocation;
Location inputLoc(Instruction instr, int inputIndex) =>
loc(OperandId.input(instr.id, inputIndex));
Register inputReg(Instruction instr, int inputIndex) =>
inputLoc(instr, inputIndex) as Register;
Register temporaryReg(Instruction instr, int tempIndex) =>
loc(OperandId.temp(instr.id, tempIndex)) as Register;
Register outputReg(Instruction instr) =>
loc(OperandId.result(instr.id)) as Register;
FPRegister inputFPReg(Instruction instr, int inputIndex) =>
inputLoc(instr, inputIndex) as FPRegister;
FPRegister tempFPReg(Instruction instr, int tempIndex) =>
loc(OperandId.temp(instr.id, tempIndex)) as FPRegister;
FPRegister outputFPReg(Instruction instr) =>
loc(OperandId.result(instr.id)) as FPRegister;
Block firstNonEmptyBlocks(Block block) =>
graph.preorder[_firstNonEmptyBlock[block.preorderNumber]];
Label blockLabel(Block block) => _blockLabels[block.preorderNumber];
bool canFallThroughTo(Block block) {
assert(_currentBlockIndex >= 0);
if (_currentBlockIndex == codeGenBlockOrder.length - 1) {
// The last block cannot fall through.
return false;
}
final nextBlock = codeGenBlockOrder[_currentBlockIndex + 1];
return _firstNonEmptyBlock[block.preorderNumber] ==
_firstNonEmptyBlock[nextBlock.preorderNumber];
}
@override
void run() {
final blocks = codeGenBlockOrder;
assert(blocks.first is EntryBlock);
assert(blocks.length == graph.preorder.length);
_asm = createAssembler();
enterFrame();
for (int i = 0, n = blocks.length; i < n; ++i) {
_currentBlockIndex = i;
final block = currentBlock = blocks[i];
generateBlock(block);
}
_currentBlockIndex = -1;
for (final slowPath in _slowPaths) {
_asm.bind(slowPath.entry);
slowPath.generator();
}
backEndState.consumeGeneratedCode(
Code(
graph.function.toString(),
graph.function,
_asm.bytes,
_asm.objectPool,
),
);
}
Assembler createAssembler();
void enterFrame();
void generateBlock(Block block) {
_asm.bind(blockLabel(block));
block.accept(this);
for (final instr in block) {
currentInstruction = instr;
instr.accept(this);
}
}
/// Returns true if no code should be generated for this block.
bool isEmptyBlock(Block block) => block is! CatchBlock && block.next is Goto;
/// For each block, calculate the first non-empty block
/// which cannot be skipped while generating code. Handles cycles.
Int32List _computeFirstNonEmptyBlock() {
final numBlocks = graph.preorder.length;
final firstNonEmpty = Int32List(numBlocks);
final emptyBlocks = <Block>[];
for (var i = 1; i < numBlocks; ++i) {
if (firstNonEmpty[i] != 0) {
continue;
}
firstNonEmpty[i] = i;
Block block = graph.preorder[i];
if (!isEmptyBlock(block)) continue;
// Collect empty blocks until reaching an non-empty block or
// an empty block which has been seen already.
for (;;) {
emptyBlocks.add(block);
block = block.successors.single;
if (firstNonEmpty[block.preorderNumber] != 0) {
break;
}
firstNonEmpty[block.preorderNumber] = block.preorderNumber;
if (!isEmptyBlock(block)) {
break;
}
}
final nonEmpty = firstNonEmpty[block.preorderNumber];
for (final block in emptyBlocks) {
firstNonEmpty[block.preorderNumber] = nonEmpty;
}
emptyBlocks.clear();
}
return firstNonEmpty;
}
Label addSlowPath(void Function() generator) {
final entry = Label();
_slowPaths.add(SlowPath(entry, generator));
return entry;
}
@override
void visitEntryBlock(EntryBlock instr) {}
@override
void visitJoinBlock(JoinBlock instr) {}
@override
void visitTargetBlock(TargetBlock instr) {}
@override
void visitCatchBlock(CatchBlock instr) {}
@override
void visitGoto(Goto instr) {
final target = instr.target;
if (!canFallThroughTo(target)) {
_asm.jump(blockLabel(target));
}
}
@override
void visitTryEntry(TryEntry instr) {
final target = instr.tryBody;
if (!canFallThroughTo(target)) {
_asm.jump(blockLabel(target));
}
}
@override
void visitPhi(Phi instr) {
// Generated via ParallelMove instructions inserted by register allocator.
}
@override
void visitParallelMove(ParallelMove instr) {
// TODO: merge subsequent ParallelMove instructions.
final map = <Location, Location>{};
for (final move in instr.moves) {
if (move is Move) {
final from = move.from.physicalLocation;
final to = move.to.physicalLocation;
if (from != to) {
assert(!map.containsKey(from));
map[from] = to;
}
}
}
for (final move in instr.moves) {
if (move is Move) {
final from = move.from.physicalLocation;
final to = move.to.physicalLocation;
if (map.containsKey(from)) {
if (map.containsKey(to)) {
_generateDependentMoves(from, to, map);
} else {
generateMove(from, to);
map.remove(from);
}
}
}
}
for (final move in instr.moves) {
if (move is LoadConstant) {
generateLoadConstant(move.value, move.to.physicalLocation);
}
}
}
void _generateDependentMoves(
Location from,
Location to,
Map<Location, Location> moves,
) {
assert(from != to);
final pendingList = <Location>[from];
final pendingSet = <Location>{from};
// Visit the chain of dependent moves until it ends or cycle is found.
while (moves.containsKey(to)) {
if (pendingSet.contains(to)) {
// Moves form a cycle. Save value on the stack to generate moves.
// TODO: regalloc should provide scratch register(s) for
// ParallelMove instructions if there are available registers.
// TODO: we can also allocate a scratch register from ParallelMove
// itself, resusing source registers which are already moved out or
// destination registers which are not moved in yet.
// TODO: as a last resort, allocate a scratch space on the stack and
// avoid any push/pop.
generatePush(to);
for (final from in pendingList.reversed) {
if (from == to) {
generatePop(moves.remove(from)!);
break;
}
generateMove(from, moves.remove(from)!);
}
break;
}
from = to;
to = moves[from]!;
pendingList.add(from);
pendingSet.add(from);
}
for (final from in pendingList.reversed) {
generateMove(from, moves.remove(from)!);
}
}
void generateMove(Location from, Location to);
void generateLoadConstant(ConstantValue value, Location to);
void generatePush(Location loc);
void generatePop(Location loc);
@override
void visitAllocateListLiteral(AllocateListLiteral instr) =>
throw 'Unexpected AllocateListLiteral (should be lowered)';
@override
void visitAllocateMapLiteral(AllocateMapLiteral instr) =>
throw 'Unexpected AllocateMapLiteral (should be lowered)';
@override
void visitStringInterpolation(StringInterpolation instr) =>
throw 'Unexpected StringInterpolation (should be lowered)';
}
class SlowPath {
final Label entry;
final void Function() generator;
SlowPath(this.entry, this.generator);
}