diff --git a/pkg/native_compiler/lib/back_end/arm64/assembler.dart b/pkg/native_compiler/lib/back_end/arm64/assembler.dart index e651516346b..b50fa713f5a 100644 --- a/pkg/native_compiler/lib/back_end/arm64/assembler.dart +++ b/pkg/native_compiler/lib/back_end/arm64/assembler.dart @@ -55,18 +55,44 @@ const int numberOfRegisters = 32; // Register aliases. const Register FP = R29; const Register LR = R30; + +/// Return value of a call. const Register returnReg = R0; + +/// Temporary register for code generator. const Register tempReg = R16; + +/// Temporary register for macro-instructions in assembler. const Register temp2Reg = R17; + +/// Untagged object pool. const Register poolPointerReg = R27; + +/// Dispatch table. const Register dispatchTableReg = R21; + +/// Code object during a call. const Register codeReg = R24; + +/// Function object during a call. const Register functionReg = R0; + +/// Dart stack pointer. const Register stackPointerReg = R15; + +/// ICData object during a call. const Register inlineCacheDataReg = R5; + +/// Arguments descriptor during a call. const Register argumentsDescriptorReg = R4; + +/// Current thread. const Register threadReg = R26; + +/// Write barrier mask << 32 | heap base >> 32. const Register heapBitsReg = R28; + +/// Dart null object. const Register nullReg = R22; const Set allRegisters = { @@ -320,7 +346,7 @@ const int B31 = (1 << 31); /// invalid cases. Certain macro-instructions can be used to lift these /// restrictions by generating extra code. /// -/// TODO: support long branches, large offsets and floating-point instructions. +/// TODO: support long branches and floating-point instructions. /// TODO: measure performance overhead of always checking encoding constraints. final class Arm64Assembler extends Assembler with Uint32OutputBuffer { final ObjectLayout objectLayout; @@ -335,6 +361,7 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { Register base, int offset, [ OperandSize sz = OperandSize.s64, + Register scratch = temp2Reg, ]) { final scale = sz.log2sizeInBytes; if (_isInt(9, offset) || @@ -342,7 +369,15 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { ((offset & (sz.sizeInBytes - 1)) == 0))) { return RegOffsetAddress(base, offset); } else { - throw 'Large address offsets are not implemented yet: $offset'; + assert(base != scratch); + final offsetBits = ((offset & (sz.sizeInBytes - 1)) == 0) + ? 12 + scale + : 8; + final mask = (1 << offsetBits) - 1; + final low = offset & mask; + final high = offset & (~mask); + addImmediate(scratch, base, high, .s64, scratch); + return RegOffsetAddress(scratch, low); } } @@ -353,12 +388,19 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { Register base, int offset, [ OperandSize sz = OperandSize.s64, + Register scratch = temp2Reg, ]) { final scale = sz.log2sizeInBytes; if (_isInt(7 + scale, offset) && ((offset & (sz.sizeInBytes - 1)) == 0)) { return RegOffsetAddress(base, offset); } else { - throw 'Large address offsets are not implemented yet: $offset'; + assert(base != scratch); + final offsetBits = ((offset & (sz.sizeInBytes - 1)) == 0) ? 6 + scale : 0; + final mask = (1 << offsetBits) - 1; + final low = offset & mask; + final high = offset & (~mask); + addImmediate(scratch, base, high, .s64, scratch); + return RegOffsetAddress(scratch, low); } } @@ -591,6 +633,7 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { Register src, int value, [ OperandSize sz = OperandSize.s64, + Register scratch = temp2Reg, ]) { assert(sz.is32or64); assert(_isInt(sz.bitWidth, value) || _isUint(sz.bitWidth, value)); @@ -603,12 +646,12 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { } else if (canEncodeImm12(-value)) { sub(dst, src, Immediate(-value), sz); } else { - assert(src != tempReg); - loadImmediate(tempReg, value); + assert(src != scratch); + loadImmediate(scratch, value); if (dst == SP || src == SP) { - add(dst, src, ExtRegOperand(tempReg, .UXTX, 0), sz); + add(dst, src, ExtRegOperand(scratch, .UXTX, 0), sz); } else { - add(dst, src, tempReg, sz); + add(dst, src, scratch, sz); } } } @@ -619,6 +662,7 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { Register src, int value, [ OperandSize sz = OperandSize.s64, + Register scratch = temp2Reg, ]) { assert(sz.is32or64); assert(_isInt(sz.bitWidth, value) || _isUint(sz.bitWidth, value)); @@ -631,12 +675,12 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { } else if (canEncodeImm12(-value)) { add(dst, src, Immediate(-value), sz); } else { - assert(src != tempReg); - loadImmediate(tempReg, value); + assert(src != scratch); + loadImmediate(scratch, value); if (dst == SP || src == SP) { - sub(dst, src, ExtRegOperand(tempReg, .UXTX, 0), sz); + sub(dst, src, ExtRegOperand(scratch, .UXTX, 0), sz); } else { - sub(dst, src, tempReg, sz); + sub(dst, src, scratch, sz); } } } @@ -647,6 +691,7 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { Register src, int value, [ OperandSize sz = OperandSize.s64, + Register scratch = temp2Reg, ]) { assert(sz.is32or64); assert(_isInt(sz.bitWidth, value) || _isUint(sz.bitWidth, value)); @@ -657,9 +702,9 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { } else if (canEncodeBitMasks(value, sz)) { and(dst, src, Immediate(value), sz); } else { - assert(src != tempReg); - loadImmediate(tempReg, value); - and(dst, src, tempReg, sz); + assert(src != scratch); + loadImmediate(scratch, value); + and(dst, src, scratch, sz); } } diff --git a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart index 36beb79890e..930b674e1ce 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -663,12 +663,12 @@ final class Arm64CodeGenerator extends CodeGenerator { _asm.ldr( scratch1Reg, - _asm.address(objectReg, vmOffsets.Object_tags_offset), + _asm.address(objectReg, vmOffsets.Object_tags_offset, .u8), .u8, ); _asm.ldr( scratch2Reg, - _asm.address(valueReg, vmOffsets.Object_tags_offset), + _asm.address(valueReg, vmOffsets.Object_tags_offset, .u8), .u8, ); _asm.and( @@ -942,7 +942,7 @@ final class Arm64CodeGenerator extends CodeGenerator { @override void visitBoxInt(BoxInt instr) { - final operandReg = inputReg(instr, 0); + var operandReg = inputReg(instr, 0); final tagsReg = temporaryReg(instr, 0); final scratch1Reg = temporaryReg(instr, 1); final scratch2Reg = temporaryReg(instr, 2); @@ -959,6 +959,11 @@ final class Arm64CodeGenerator extends CodeGenerator { _asm.b(done); }); + if (operandReg == resultReg) { + _asm.mov(tempReg, operandReg); + operandReg = tempReg; + } + _asm.adds(resultReg, operandReg, operandReg); _asm.b(done, .noOverflow); diff --git a/pkg/native_compiler/lib/back_end/constraints.dart b/pkg/native_compiler/lib/back_end/constraints.dart index 17a43029d91..6e9f86c500b 100644 --- a/pkg/native_compiler/lib/back_end/constraints.dart +++ b/pkg/native_compiler/lib/back_end/constraints.dart @@ -40,6 +40,16 @@ final class AnyLocation implements Constraint { /// Register allocation constraints for locations of instruction inputs, /// result and temporaries needed to generate code for the instruction. /// +/// Code generated for each instruction should read inputs and +/// then write output. It may also clobber temporaries. +/// +/// Output may be allocated to the same register as one of the inputs +/// (if it was the last use of the input). Unless output is allocated +/// to the same location as input, instruction should not modify inputs. +/// +/// Temporaries are allocated to the registers which are different from +/// both inputs and outputs. +/// /// TODO: encode constraints as int/Uint32List. class InstructionConstraints { final Constraint? result; diff --git a/pkg/native_compiler/test/back_end/arm64/assembler_test.dart b/pkg/native_compiler/test/back_end/arm64/assembler_test.dart index 230bf6402b6..282f34667e8 100644 --- a/pkg/native_compiler/test/back_end/arm64/assembler_test.dart +++ b/pkg/native_compiler/test/back_end/arm64/assembler_test.dart @@ -62,40 +62,38 @@ void main() { test('address', () { asm.ldr(R0, asm.address(R0, -256)); asm.str(R1, asm.address(R0, 0x7ff8)); - // TODO: support large offsets - expectThrows(() { - asm.address(R0, -257); - }); - expectThrows(() { - asm.address(R0, 257); - }); - expectThrows(() { - asm.address(R0, 0x8000); - }); + asm.ldr(R2, asm.address(R0, -257)); + asm.str(R3, asm.address(R0, 257)); + asm.ldr(R4, asm.address(R0, 0x8002, .u16), .u16); expectDisassembly( 'ldr r0, [r0, #-256]\n' - 'str r1, [r0, #32760]\n', + 'str r1, [r0, #32760]\n' + 'sub r17, r0, #0x200\n' + 'ldr r2, [r17, #255]\n' + 'add r17, r0, #0x100\n' + 'str r3, [r17, #1]\n' + 'add r17, r0, #0x8000\n' + 'ldrh r4, [r17, #2]\n', ); }); test('pairAddress', () { asm.ldp(R1, R2, asm.pairAddress(R0, -0x200)); asm.stp(R1, R2, asm.pairAddress(R0, 0x1f8)); - // TODO: support large and unaligned offsets - expectThrows(() { - asm.pairAddress(R0, 3); - }); - expectThrows(() { - asm.pairAddress(R0, -1); - }); - expectThrows(() { - asm.pairAddress(R0, -0x208); - }); - expectThrows(() { - asm.pairAddress(R0, 0x200); - }); + asm.ldp(R1, R2, asm.pairAddress(R0, 3)); + asm.stp(R1, R2, asm.pairAddress(R0, -1)); + asm.ldp(R1, R2, asm.pairAddress(R0, -0x208)); + asm.stp(R1, R2, asm.pairAddress(R0, 0x204, .s32), .s32); expectDisassembly( 'ldp r1, r2, [r0, #-512]\n' - 'stp r1, r2, [r0, #504]\n', + 'stp r1, r2, [r0, #504]\n' + 'add r17, r0, #0x3\n' + 'ldp r1, r2, [r17, #0]\n' + 'sub r17, r0, #0x1\n' + 'stp r1, r2, [r17, #0]\n' + 'sub r17, r0, #0x400\n' + 'ldp r1, r2, [r17, #504]\n' + 'add r17, r0, #0x200\n' + 'stpw r1, r2, [r17, #4]\n', ); }); test('enterDartFrame', () { @@ -190,10 +188,16 @@ void main() { asm.loadFromPool(R0, ConstantValue.fromString('$offs') as Object); expected.write('ldr r0, [pp, #$offs]\n'); } - // TODO: support large offsets - expectThrows(() { - asm.loadFromPool(R0, ConstantValue.fromString('oops') as Object); - }); + asm.loadFromPool(R0, ConstantValue.fromString('oops1') as Object); + expected.write( + 'add r17, pp, #0x8000\n' + 'ldr r0, [r17]\n', + ); + asm.loadFromPool(R0, ConstantValue.fromString('oops2') as Object); + expected.write( + 'add r17, pp, #0x8000\n' + 'ldr r0, [r17, #8]\n', + ); expectDisassembly(expected.toString()); }); test('loadConstant', () { @@ -266,16 +270,16 @@ void main() { 'sub r1, r2, #0xabc\n' 'add r1, r2, #0xabc000\n' 'sub r1, r2, #0xabc000\n' - 'movz tmp, #0x7788\n' - 'movk tmp, #0x5566 lsl 16\n' - 'movk tmp, #0x3344 lsl 32\n' - 'movk tmp, #0x1122 lsl 48\n' - 'add r1, r2, tmp\n' - 'movz tmp, #0x7788\n' - 'movk tmp, #0x5566 lsl 16\n' - 'movk tmp, #0x3344 lsl 32\n' - 'movk tmp, #0x1122 lsl 48\n' - 'add csp, fp, tmp uxtx 0\n', + 'movz r17, #0x7788\n' + 'movk r17, #0x5566 lsl 16\n' + 'movk r17, #0x3344 lsl 32\n' + 'movk r17, #0x1122 lsl 48\n' + 'add r1, r2, r17\n' + 'movz r17, #0x7788\n' + 'movk r17, #0x5566 lsl 16\n' + 'movk r17, #0x3344 lsl 32\n' + 'movk r17, #0x1122 lsl 48\n' + 'add csp, fp, r17 uxtx 0\n', ); }); test('subImmediate', () { @@ -295,16 +299,16 @@ void main() { 'add r1, r2, #0xabc\n' 'sub r1, r2, #0xabc000\n' 'add r1, r2, #0xabc000\n' - 'movz tmp, #0x7788\n' - 'movk tmp, #0x5566 lsl 16\n' - 'movk tmp, #0x3344 lsl 32\n' - 'movk tmp, #0x1122 lsl 48\n' - 'sub r1, r2, tmp\n' - 'movz tmp, #0x7788\n' - 'movk tmp, #0x5566 lsl 16\n' - 'movk tmp, #0x3344 lsl 32\n' - 'movk tmp, #0x1122 lsl 48\n' - 'sub csp, fp, tmp uxtx 0\n', + 'movz r17, #0x7788\n' + 'movk r17, #0x5566 lsl 16\n' + 'movk r17, #0x3344 lsl 32\n' + 'movk r17, #0x1122 lsl 48\n' + 'sub r1, r2, r17\n' + 'movz r17, #0x7788\n' + 'movk r17, #0x5566 lsl 16\n' + 'movk r17, #0x3344 lsl 32\n' + 'movk r17, #0x1122 lsl 48\n' + 'sub csp, fp, r17 uxtx 0\n', ); }); test('andImmediate', () { @@ -320,11 +324,11 @@ void main() { 'mov r1, r2\n' 'movw r1, r2\n' 'and r1, r2, 0xff\n' - 'movz tmp, #0x7788\n' - 'movk tmp, #0x5566 lsl 16\n' - 'movk tmp, #0x3344 lsl 32\n' - 'movk tmp, #0x1122 lsl 48\n' - 'and r1, r2, tmp\n', + 'movz r17, #0x7788\n' + 'movk r17, #0x5566 lsl 16\n' + 'movk r17, #0x3344 lsl 32\n' + 'movk r17, #0x1122 lsl 48\n' + 'and r1, r2, r17\n', ); }); test('callRuntime', () {