[modular_aot] Support large offsets, revise usage of temp registers and fix BoxInt codegen

* Support large and unaligned offsets in Assembler.address, pairAddress
  and loadFromPool.

* Revise how temporary registers are used: reserve tempReg for code gen
  and temp2Reg for assembler.

* Clarify that inputs and outputs of the instruction can be allocated to
  the same register. Fix BoxInt code generation to account for the same
  input and output.

Issue: https://github.com/dart-lang/sdk/issues/61635
Change-Id: Iaebc824ac2b5e8e0bdfed168c167528a1dbd82d4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/486821
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2026-03-11 13:21:54 -07:00
committed by Commit Queue
parent 46cf07fe35
commit 085ed51099
4 changed files with 135 additions and 71 deletions
@@ -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<Register> 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);
}
}
@@ -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);
@@ -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;
@@ -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', () {