[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:
committed by
Commit Queue
parent
46cf07fe35
commit
085ed51099
@@ -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', () {
|
||||
|
||||
Reference in New Issue
Block a user