blob: a0dfeba47a907b6799e41bd6ec056cf4cdfb9978 [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 'package:cfg/ir/constant_value.dart';
import 'package:cfg/ir/instructions.dart';
import 'package:cfg/utils/misc.dart';
import 'package:native_compiler/back_end/arm64/assembler.dart';
import 'package:native_compiler/back_end/assembler.dart';
import 'package:native_compiler/back_end/code_generator.dart';
import 'package:native_compiler/back_end/locations.dart';
import 'package:native_compiler/runtime/vm_defs.dart';
final class Arm64CodeGenerator extends CodeGenerator {
late final Arm64Assembler _asm;
Arm64CodeGenerator(super.backEndState);
@override
Assembler createAssembler() => _asm = Arm64Assembler(backEndState.vmOffsets);
@override
void enterFrame() {
_asm.pushPair(FP, LR);
_asm.mov(FP, stackPointerReg);
// Tag and save caller pool pointer.
_asm.add(poolPointerReg, poolPointerReg, Immediate(heapObjectTag));
_asm.pushPair(poolPointerReg, codeReg);
// Load and untag current pool pointer.
_asm.ldr(
poolPointerReg,
_asm.fieldAddress(codeReg, _asm.vmOffsets.Code_object_pool_offset),
);
_asm.sub(poolPointerReg, poolPointerReg, Immediate(heapObjectTag));
// TODO: calculate stack frame size.
_asm.sub(stackPointerReg, stackPointerReg, Immediate(64));
}
void _generateBranch(
Block trueSuccessor,
Block falseSuccessor,
void Function(bool, Label) branch,
) {
if (canFallThroughTo(trueSuccessor)) {
branch(false, blockLabel(falseSuccessor));
} else {
branch(true, blockLabel(trueSuccessor));
if (!canFallThroughTo(falseSuccessor)) {
_asm.b(blockLabel(falseSuccessor));
}
}
}
@override
void visitBranch(Branch instr) {
final cond = inputReg(instr, 0);
final boolValueBit = boolValueBitPosition(log2wordSize);
_generateBranch(instr.trueSuccessor, instr.falseSuccessor, (
bool value,
Label label,
) {
// Test bool value bit: 0 = true, 1 = false.
if (value) {
_asm.tbz(cond, boolValueBit, label);
} else {
_asm.tbnz(cond, boolValueBit, label);
}
});
}
@override
void visitCompareAndBranch(CompareAndBranch instr) {
final trueSuccessor = instr.trueSuccessor;
final falseSuccessor = instr.falseSuccessor;
final left = inputReg(instr, 0);
final right = instr.right;
switch (instr.op) {
case ComparisonOpcode.equal:
case ComparisonOpcode.intEqual:
case ComparisonOpcode.notEqual:
case ComparisonOpcode.intNotEqual:
if (right is Constant && right.value.isZero) {
_generateBranch(trueSuccessor, falseSuccessor, (
bool value,
Label label,
) {
if (value ==
(instr.op == ComparisonOpcode.equal ||
instr.op == ComparisonOpcode.intEqual)) {
_asm.cbz(left, label);
} else {
_asm.cbnz(left, label);
}
});
return;
}
case ComparisonOpcode.intTestIsZero:
case ComparisonOpcode.intTestIsNotZero:
if (right is Constant &&
right.value.isInt &&
isPowerOf2(right.value.intValue)) {
final bitNumber = log2OfPowerOf2(right.value.intValue);
_generateBranch(trueSuccessor, falseSuccessor, (
bool value,
Label label,
) {
if (value == (instr.op == ComparisonOpcode.intTestIsZero)) {
_asm.tbz(left, bitNumber, label);
} else {
_asm.tbnz(left, bitNumber, label);
}
});
}
default:
break;
}
switch (instr.op) {
case ComparisonOpcode.equal:
case ComparisonOpcode.notEqual:
case ComparisonOpcode.intEqual:
case ComparisonOpcode.intNotEqual:
case ComparisonOpcode.intLess:
case ComparisonOpcode.intLessOrEqual:
case ComparisonOpcode.intGreater:
case ComparisonOpcode.intGreaterOrEqual:
final (operand, negated) = _generateAddSubRightOperand(instr, right);
if (negated) {
_asm.cmn(left, operand);
} else {
_asm.cmp(left, operand);
}
break;
case ComparisonOpcode.intTestIsZero:
case ComparisonOpcode.intTestIsNotZero:
final operand = _generateLogicalRightOperand(instr, right);
_asm.tst(left, operand);
break;
default:
throw 'Unexpected ${instr.op}';
}
_generateBranch(trueSuccessor, falseSuccessor, (bool value, Label label) {
final op = value ? instr.op : instr.op.negate();
_asm.b(label, op.conditionCode);
});
}
(Operand, bool negated) _generateAddSubRightOperand(
Instruction instr,
Definition right,
) {
if (right is Constant) {
if (right.value.isInt) {
final value = right.value.intValue;
if (value == 0) {
return (ZR, false);
} else if (_asm.canEncodeImm12(value)) {
return (Immediate(value), false);
} else if (_asm.canEncodeImm12(-value)) {
return (Immediate(-value), true);
}
}
_asm.loadConstant(tempReg, right.value);
return (tempReg, false);
}
return (inputReg(instr, 1), false);
}
Operand _generateLogicalRightOperand(Instruction instr, Definition right) {
if (right is Constant) {
if (right.value.isInt) {
final value = right.value.intValue;
if (_asm.canEncodeBitMasks(value)) {
return Immediate(value);
}
}
_asm.loadConstant(tempReg, right.value);
return tempReg;
}
return inputReg(instr, 1);
}
@override
void visitReturn(Return instr) {
assert(inputReg(instr, 0) == returnReg);
// Restore and untag pool pointer.
_asm.ldr(poolPointerReg, RegOffsetAddress(FP, -2 * wordSize));
_asm.sub(poolPointerReg, poolPointerReg, Immediate(heapObjectTag));
_asm.mov(stackPointerReg, FP);
_asm.popPair(FP, LR);
_asm.ret();
}
@override
void visitComparison(Comparison instr) {
_asm.unimplemented('Unimplemented: code generation for Comparison');
}
@override
void visitConstant(Constant instr) {
// No-op.
}
void _passArguments(CallInstruction instr) {
Register pendingReg = invalidReg;
var offset = 0;
Register getTempReg() => (pendingReg == tempReg) ? LR : tempReg;
for (var i = 0; i < instr.inputCount; ++i) {
final arg = instr.inputDefAt(i);
Register reg;
if (arg is Constant) {
if (arg.value.isZero) {
reg = ZR;
} else if (arg.value.isNull) {
reg = nullReg;
} else {
reg = getTempReg();
_asm.loadConstant(reg, arg.value);
}
} else {
final loc = inputLoc(instr, i);
switch (loc) {
case Register():
reg = loc;
break;
// TODO: support other locations.
default:
throw 'Unimplemented passing arg from ${loc.runtimeType} $loc';
}
}
if (pendingReg == invalidReg) {
pendingReg = reg;
} else {
_asm.stp(pendingReg, reg, RegOffsetAddress(stackPointerReg, offset));
pendingReg = invalidReg;
offset += 2 * wordSize;
}
}
if (pendingReg != invalidReg) {
_asm.str(pendingReg, RegOffsetAddress(stackPointerReg, offset));
}
}
@override
void visitDirectCall(DirectCall instr) {
// TODO: pass arg_desc when needed.
_asm.loadImmediate(argumentsDescriptorReg, 0);
_passArguments(instr);
_asm.loadFromPool(functionReg, instr.target);
// TODO: call directly through Code.
_asm.ldr(
codeReg,
_asm.fieldAddress(functionReg, _asm.vmOffsets.Function_code_offset),
);
_asm.ldr(
tempReg,
_asm.fieldAddress(
functionReg,
_asm.vmOffsets.Function_entry_point_offset.first,
),
);
_asm.blr(tempReg);
}
@override
void visitInterfaceCall(InterfaceCall instr) {
_asm.unimplemented('Unimplemented: code generation for InterfaceCall');
}
@override
void visitClosureCall(ClosureCall instr) {
_asm.unimplemented('Unimplemented: code generation for ClosureCall');
}
@override
void visitDynamicCall(DynamicCall instr) {
_asm.unimplemented('Unimplemented: code generation for DynamicCall');
}
@override
void visitParameter(Parameter instr) {
// No-op.
}
@override
void visitLoadLocal(LoadLocal instr) => throw 'Unexpected LoadLocal';
@override
void visitStoreLocal(StoreLocal instr) => throw 'Unexpected StoreLocal';
@override
void visitLoadInstanceField(LoadInstanceField instr) {
_asm.unimplemented('Unimplemented: code generation for LoadInstanceField');
}
@override
void visitStoreInstanceField(StoreInstanceField instr) {
_asm.unimplemented('Unimplemented: code generation for StoreInstanceField');
}
@override
void visitLoadStaticField(LoadStaticField instr) {
_asm.unimplemented('Unimplemented: code generation for LoadStaticField');
}
@override
void visitStoreStaticField(StoreStaticField instr) {
_asm.unimplemented('Unimplemented: code generation for StoreStaticField');
}
@override
void visitThrow(Throw instr) {
_asm.unimplemented('Unimplemented: code generation for Throw');
}
@override
void visitNullCheck(NullCheck instr) {
_asm.unimplemented('Unimplemented: code generation for NullCheck');
}
@override
void visitTypeParameters(TypeParameters instr) {
_asm.unimplemented('Unimplemented: code generation for TypeParameters');
}
@override
void visitTypeCast(TypeCast instr) {
_asm.unimplemented('Unimplemented: code generation for TypeCast');
}
@override
void visitTypeTest(TypeTest instr) {
_asm.unimplemented('Unimplemented: code generation for TypeTest');
}
@override
void visitTypeArguments(TypeArguments instr) {
_asm.unimplemented('Unimplemented: code generation for TypeArguments');
}
@override
void visitTypeLiteral(TypeLiteral instr) {
_asm.unimplemented('Unimplemented: code generation for TypeLiteral');
}
@override
void visitAllocateObject(AllocateObject instr) {
_asm.unimplemented('Unimplemented: code generation for AllocateObject');
}
@override
void visitAllocateClosure(AllocateClosure instr) {
_asm.unimplemented('Unimplemented: code generation for AllocateClosure');
}
@override
void visitAllocateList(AllocateList instr) {
_asm.unimplemented('Unimplemented: code generation for AllocateList');
}
@override
void visitSetListElement(SetListElement instr) {
_asm.unimplemented('Unimplemented: code generation for SetListElement');
}
@override
void visitBinaryIntOp(BinaryIntOp instr) {
_asm.unimplemented('Unimplemented: code generation for BinaryIntOp');
}
@override
void visitUnaryIntOp(UnaryIntOp instr) {
_asm.unimplemented('Unimplemented: code generation for UnaryIntOp');
}
@override
void visitBinaryDoubleOp(BinaryDoubleOp instr) {
_asm.unimplemented('Unimplemented: code generation for BinaryDoubleOp');
}
@override
void visitUnaryDoubleOp(UnaryDoubleOp instr) {
_asm.unimplemented('Unimplemented: code generation for UnaryDoubleOp');
}
@override
void visitUnaryBoolOp(UnaryBoolOp instr) {
_asm.unimplemented('Unimplemented: code generation for UnaryBoolOp');
}
@override
void generateMove(Location from, Location to) {
if (from is Register && to is Register) {
_asm.mov(to, from);
return;
}
_asm.unimplemented('Unimplemented: code generation for generateMove');
}
@override
void generateLoadConstant(ConstantValue value, Location to) {
if (to is Register) {
_asm.loadConstant(to, value);
return;
}
_asm.unimplemented(
'Unimplemented: code generation for generateLoadConstant',
);
}
@override
void generatePush(Location loc) {
_asm.unimplemented('Unimplemented: code generation for generatePush');
}
void generatePop(Location loc) {
_asm.unimplemented('Unimplemented: code generation for generatePop');
}
}
extension on ComparisonOpcode {
Condition get conditionCode => switch (this) {
ComparisonOpcode.equal => Condition.equal,
ComparisonOpcode.notEqual => Condition.notEqual,
ComparisonOpcode.identical => Condition.equal,
ComparisonOpcode.notIdentical => Condition.notEqual,
ComparisonOpcode.intEqual => Condition.equal,
ComparisonOpcode.intNotEqual => Condition.notEqual,
ComparisonOpcode.intLess => Condition.less,
ComparisonOpcode.intLessOrEqual => Condition.lessOrEqual,
ComparisonOpcode.intGreater => Condition.greater,
ComparisonOpcode.intGreaterOrEqual => Condition.greaterOrEqual,
ComparisonOpcode.intTestIsZero => Condition.equal,
ComparisonOpcode.intTestIsNotZero => Condition.notEqual,
ComparisonOpcode.doubleEqual => Condition.equal,
ComparisonOpcode.doubleNotEqual => Condition.notEqual,
ComparisonOpcode.doubleLess => Condition.less,
ComparisonOpcode.doubleLessOrEqual => Condition.lessOrEqual,
ComparisonOpcode.doubleGreater => Condition.greater,
ComparisonOpcode.doubleGreaterOrEqual => Condition.greaterOrEqual,
};
}