[modular_aot] Support more cases of parallel moves and double immediates

Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: I8b167ea819d4247ca748f5200b5509e99a68d56e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/503560
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Markov
2026-05-18 10:10:21 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 2edcad4892
commit ca9c12f1b6
9 changed files with 237 additions and 31 deletions
+16
View File
@@ -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<Object?> a, List<Object?> 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);
}
+31
View File
@@ -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));
}
});
}
@@ -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 {
@@ -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',
);
}
}
@@ -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,
@@ -210,13 +210,33 @@ abstract base class CodeGenerator extends Pass
void visitParallelMove(ParallelMove instr) {
// TODO: merge subsequent ParallelMove instructions.
final map = <Location, Location>{};
Set<Location>? 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;
}
}
}
}
@@ -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) {
@@ -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)));
});
});
});
}
@@ -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')) {