diff --git a/pkg/cfg/lib/utils/misc.dart b/pkg/cfg/lib/utils/misc.dart index 5fe883c12bd..c14d3f95293 100644 --- a/pkg/cfg/lib/utils/misc.dart +++ b/pkg/cfg/lib/utils/misc.dart @@ -2,6 +2,8 @@ // 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'; + /// Compares elements of the given lists. bool listEquals(List a, List b) { if (a.length != b.length) return false; @@ -67,3 +69,17 @@ int roundUp(int value, int alignment) { assert(value >= 0); return roundDown(value + alignment - 1, alignment); } + +final ByteData _conversionBuffer = ByteData(8); + +/// Reinterpret double [value] as int (without changing bits). +int doubleToIntBits(double value) { + _conversionBuffer.setFloat64(0, value, Endian.little); + return _conversionBuffer.getInt64(0, Endian.little); +} + +/// Reinterpret int [value] as double (without changing bits). +double intBitsToDouble(int value) { + _conversionBuffer.setInt64(0, value, Endian.little); + return _conversionBuffer.getFloat64(0, Endian.little); +} diff --git a/pkg/cfg/test/utils/misc_test.dart b/pkg/cfg/test/utils/misc_test.dart index 7f4aa7a8e69..b88aa424420 100644 --- a/pkg/cfg/test/utils/misc_test.dart +++ b/pkg/cfg/test/utils/misc_test.dart @@ -78,4 +78,35 @@ void main() { } } }); + + test('doubleToIntBits and intBitsToDouble', () { + List<(double, int)> values = [ + (1.0, 0x3FF00000_00000000), + (-1.0, 0xBFF00000_00000000), + (2.0, 0x40000000_00000000), + (-0.25, 0xBFD00000_00000000), + (1e100, 0x54b249ad_2594c37d), + (0.0, 0x00000000_00000000), + (-0.0, 0x80000000_00000000), + (double.nan, 0xFFF80000_00000000), + (double.infinity, 0x7ff00000_00000000), + (double.negativeInfinity, 0xFFF00000_00000000), + // More NaNs. + (intBitsToDouble(0xFFF81234_56789abc), 0xFFF81234_56789abc), + (intBitsToDouble(0xFFF00000_00000001), 0xFFF00000_00000001), + (intBitsToDouble(0xFFF01234_56789abc), 0xFFF01234_56789abc), + (intBitsToDouble(0x7FF80000_00000000), 0x7FF80000_00000000), + (intBitsToDouble(0x7FF81234_56789abc), 0x7FF81234_56789abc), + (intBitsToDouble(0x7FF00000_00000001), 0x7FF00000_00000001), + (intBitsToDouble(0x7FF01234_56789abc), 0x7FF01234_56789abc), + ]; + for (var (d, i) in values) { + print('($d, ${doubleToIntBits(d).toRadixString(16)})'); + expect(doubleToIntBits(d), equals(i)); + expect(intBitsToDouble(i), same(d)); + } + for (var d = -100.0; d < 100.0; d += 0.1) { + expect(intBitsToDouble(doubleToIntBits(d)), same(d)); + } + }); } diff --git a/pkg/native_compiler/lib/back_end/arm64/assembler.dart b/pkg/native_compiler/lib/back_end/arm64/assembler.dart index d954bc54b67..0ae9e4ab66a 100644 --- a/pkg/native_compiler/lib/back_end/arm64/assembler.dart +++ b/pkg/native_compiler/lib/back_end/arm64/assembler.dart @@ -2,6 +2,7 @@ // 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/utils/misc.dart'; import 'package:native_compiler/back_end/arm64/stack_frame.dart'; import 'package:native_compiler/back_end/assembler.dart'; import 'package:native_compiler/back_end/code.dart'; @@ -642,6 +643,24 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { assert(initialized); } + @override + void loadDoubleImmediate(FPRegister reg, double v) { + final imm = Immediate(doubleToIntBits(v)); + if (imm.tryEncodingFpImm(.s64) != null) { + fmov(reg, imm); + return; + } + if (imm.value == 0) { + fmov(reg, ZR); + return; + } + int poolIndex = objectPool.getObject(UnboxedDoubleConstant(v)); + fldr( + reg, + address(poolPointerReg, vmOffsets.ObjectPool_elementOffset(poolIndex)), + ); + } + bool canEncodeImm12(int value) => _isUint(12, value) || (value & 0xfff == 0 && _isUint(12, value >> 12)); @@ -1900,6 +1919,56 @@ final class Arm64Assembler extends Assembler with Uint32OutputBuffer { (srcSize.is64 ? B31 : 0), ); } + + void fmov(FPRegister rd, Operand o, [OperandSize sz = OperandSize.s64]) { + assert(sz.is16or32or64); + switch (o) { + case FPRegister(): + emit( + B14 | + B21 | + B25 | + B26 | + B27 | + B28 | + rd.encodingRd | + o.encodingRn | + (sz.is64 ? B22 : (sz.is32 ? 0 : (B22 | B23))), + ); + break; + case Register(): + emit( + B16 | + B17 | + B18 | + B21 | + B25 | + B26 | + B27 | + B28 | + rd.encodingRd | + o.encodingRn() | + (sz.is64 ? B22 : (sz.is32 ? 0 : (B22 | B23))) | + (sz.is64 ? B31 : 0), + ); + break; + case Immediate(): + emit( + B12 | + B21 | + B25 | + B26 | + B27 | + B28 | + rd.encodingRd | + (o.encodingFpImm(sz) << 13) | + (sz.is64 ? B22 : (sz.is32 ? 0 : (B22 | B23))), + ); + break; + default: + throw 'Unexpect operand ${o.runtimeType}'; + } + } } bool _isUint(int numBits, int value) => (value >>> numBits) == 0; @@ -1930,6 +1999,7 @@ extension on Register { extension on FPRegister { int get encodingRd => index; int get encodingRt => index; + int get encodingRn => index << 5; } extension on Immediate { @@ -2075,6 +2145,30 @@ extension on Immediate { ((value >>> 32) & 0x00000000ffffffff) + (value & 0x00000000ffffffff); return value; } + + int encodingFpImm(OperandSize sz) => + tryEncodingFpImm(sz) ?? + (throw 'Immediate $value cannot be encoded as FP immediate'); + + int? tryEncodingFpImm(OperandSize sz) { + if (!sz.is64) { + throw 'Unimplemented FP immediates of size $sz'; + } + // value: aBbbbbbb bbcdefgh 00000000 00000000 00000000 00000000 00000000 00000000 + // encoded as: abcdefgh (where B = NOT(b)). + final int value = this.value; + if (value & ((1 << 48) - 1) != 0) { + return null; + } + final int bitB = (value >> 62) & 0x1; + const int maskb = 0xff << 54; + if ((value & maskb) != ((bitB - 1) & maskb)) { + return null; + } + return (((value >> 63) & 0x1) << 7) | + ((((~value) >> 62) & 0x1) << 6) | + ((value >> 48) & 0x3f); + } } extension on ExtRegOperand { 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 87921b8845f..03ea583d09a 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -1892,6 +1892,17 @@ final class Arm64CodeGenerator extends CodeGenerator { case Register(): _asm.ldr(to, _asm.address(FP, stackFrame.offsetFromFP(from))); return; + case FPRegister(): + _asm.fldr(to, _asm.address(FP, stackFrame.offsetFromFP(from))); + return; + default: + break; + } + case FPRegister(): + switch (to) { + case StackLocation(): + _asm.fstr(from, _asm.address(FP, stackFrame.offsetFromFP(to))); + return; default: break; } @@ -1905,13 +1916,19 @@ final class Arm64CodeGenerator extends CodeGenerator { @override void generateLoadConstant(ConstantValue value, Location to) { - if (to is Register) { - _asm.loadConstant(to, value); - return; + switch (to) { + case Register(): + _asm.loadConstant(to, value); + return; + case FPRegister(): + assert(value.isDouble && value.isUnboxed); + _asm.loadDoubleImmediate(to, value.doubleValue); + return; + case StackLocation(): + _asm.loadConstant(tempReg, value); + _asm.str(tempReg, _asm.address(FP, stackFrame.offsetFromFP(to))); + return; } - _asm.unimplemented( - 'Unimplemented: code generation for generateLoadConstant', - ); } } diff --git a/pkg/native_compiler/lib/back_end/assembler.dart b/pkg/native_compiler/lib/back_end/assembler.dart index f37fb61f6d5..65726a43b0d 100644 --- a/pkg/native_compiler/lib/back_end/assembler.dart +++ b/pkg/native_compiler/lib/back_end/assembler.dart @@ -163,6 +163,9 @@ abstract base class Assembler { /// Load arbitrary integer [value] into register. void loadImmediate(Register reg, int value); + /// Load arbitrary double [value] into a floating-point register. + void loadDoubleImmediate(FPRegister reg, double v); + /// [dst] = [src] + arbitrary integer [value]. void addImmediate( Register dst, diff --git a/pkg/native_compiler/lib/back_end/code_generator.dart b/pkg/native_compiler/lib/back_end/code_generator.dart index 4b31bd17a07..df5b7890af2 100644 --- a/pkg/native_compiler/lib/back_end/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/code_generator.dart @@ -210,13 +210,33 @@ abstract base class CodeGenerator extends Pass void visitParallelMove(ParallelMove instr) { // TODO: merge subsequent ParallelMove instructions. final map = {}; + Set? overwritten; for (final move in instr.moves) { if (move is Move) { final from = move.from.physicalLocation; final to = move.to.physicalLocation; + assert(!(overwritten?.contains(from) ?? false)); if (from != to) { - assert(!map.containsKey(from)); - map[from] = to; + if (to is StackLocation) { + // Moves into spill slots cannot participate in cycles. + // Generate them eagerly and do not put them into the map + // as they may have the same source as register/register moves. + assert(!map.containsKey(to)); + assert(() { + (overwritten ??= {}).add(to); + return true; + }()); + if (from is StackLocation) { + final temp = getMoveTempRegister(RegisterClass.cpu); + generateMove(from, temp); + generateMove(temp, to); + } else { + generateMove(from, to); + } + } else { + assert(!map.containsKey(from)); + map[from] = to; + } } } } diff --git a/pkg/native_compiler/lib/snapshot/snapshot.dart b/pkg/native_compiler/lib/snapshot/snapshot.dart index 2270b80c05a..afe4ff26e63 100644 --- a/pkg/native_compiler/lib/snapshot/snapshot.dart +++ b/pkg/native_compiler/lib/snapshot/snapshot.dart @@ -1775,10 +1775,7 @@ class SnapshotStreamWriter { } void writeDouble(double value) { - final buf = ByteData(8); - buf.setFloat64(0, value, Endian.little); - final intValue = buf.getInt64(0, Endian.little); - writeInt(intValue); + writeInt(doubleToIntBits(value)); } ByteData _bufferAt(int offset) { 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 2d7073083d2..b54f29df6ba 100644 --- a/pkg/native_compiler/test/back_end/arm64/assembler_test.dart +++ b/pkg/native_compiler/test/back_end/arm64/assembler_test.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:cfg/ir/constant_value.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.dart'; @@ -259,6 +260,20 @@ void main() { 'mov r5, 0xff00ff00ff00ff00\n', ); }); + test('loadDoubleImmediate', () { + asm.loadDoubleImmediate(V0, 1.0); + asm.loadDoubleImmediate(V31, -3.5); + asm.loadDoubleImmediate(V1, 0.0); + asm.loadDoubleImmediate(V2, 0.123456789); + asm.loadDoubleImmediate(V3, double.nan); + expectDisassembly( + 'fmovd v0, 1.0\n' + 'fmovd v31, -3.5\n' + 'fmovdr v1, zr\n' + 'fldrd v2, [pp, #${objectPoolBase}]\n' + 'fldrd v3, [pp, #${objectPoolBase + 8}]\n', + ); + }); test('addImmediate', () { asm.addImmediate(R0, R0, 0); asm.addImmediate(R1, R2, 0); @@ -1645,5 +1660,28 @@ void main() { asm.scvtf(V0, SP); }); }); + + test('fmov', () { + asm.fmov(V0, R0); + asm.fmov(V2, ZR); + asm.fmov(V3, R2, .s32); + asm.fmov(V0, Immediate(doubleToIntBits(1.0))); + asm.fmov(V1, Immediate(doubleToIntBits(2.0))); + asm.fmov(V31, Immediate(doubleToIntBits(-0.25))); + expectDisassembly( + 'fmovdr v0, r0\n' + 'fmovdr v2, zr\n' + 'fmovsrw v3, r2\n' + 'fmovd v0, 1.0\n' + 'fmovd v1, 2.0\n' + 'fmovd v31, -0.25\n', + ); + expectThrows(() { + asm.fmov(V0, Immediate(doubleToIntBits(0.0))); + }); + expectThrows(() { + asm.fmov(V1, Immediate(doubleToIntBits(1.23456789))); + }); + }); }); } diff --git a/pkg/native_compiler/test/back_end/arm64/disassembler.dart b/pkg/native_compiler/test/back_end/arm64/disassembler.dart index 1e19b7f7e1d..44d52e83103 100644 --- a/pkg/native_compiler/test/back_end/arm64/disassembler.dart +++ b/pkg/native_compiler/test/back_end/arm64/disassembler.dart @@ -12,6 +12,8 @@ import 'dart:typed_data'; +import 'package:cfg/utils/misc.dart'; + // --- Constants from runtime/vm/constants_arm64.h --- // enum Register @@ -691,22 +693,12 @@ extension type Instr(int value) { int imm8Field() => bits(13, 8); // kImm8Shift is not defined static int vfpExpandImm(int imm8) { - int sign = (imm8 >> 7) & 0x1; - int exp = (imm8 >> 4) & 0x7; - int frac = imm8 & 0xf; - - if ((exp & 0x4) == 0) { - exp = (0x2 | (exp & 0x1)) << 1; - if ((exp & 0x2) == 0) { - exp = ((exp & 0x1) ^ 0x1); - } else { - exp = ((exp & 0x1) | 0x2); - } - exp = (0x3ff ^ 0x7) | (exp << 2); - } else { - exp = 0x3ff; - } - return (sign << 63) | (exp << 52) | (frac << 48); + int sign = ((imm8 & 0x80) >> 7) << 63; + int expHigh = (((~imm8) & 0x40) >> 6) << 62; + int expMid = (((imm8 & 0x40) >> 6) == 0) ? 0 : (0xff << 54); + int expLow = ((imm8 & 0x30) >> 4) << 52; + int frac = (imm8 & 0x0f) << 48; + return sign | expHigh | expMid | expLow | frac; } int immLogical() { @@ -1047,9 +1039,7 @@ class ARM64Decoder { return 5; } else if (option.startsWith('immd')) { final imm = Instr.vfpExpandImm(instr.imm8Field()); - final d = ByteData(8) - ..setInt64(0, imm, Endian.host) - ..getFloat64(0, Endian.host); + final d = intBitsToDouble(imm); print(d.toString()); return 4; } else if (option.startsWith('immr')) {