dart-sdk/runtime/vm/constants_arm.h
Daco Harkes 1854bb00c6 [vm/ffi] Support varargs in the backend
On ARM64 macos and ios, when varargs are used, the first vararg blocks
all cpu and fpu registers.

On Windows x64, when varargs are used, floating point arguments are
passed _both_ in the integer and double register.

The Windows logic requires a new kind of native location:
`BothNativeLocations`, which signals that a value needs to be copied
to both locations before an FFI call, and can be copied from any of
the two locations when getting an FFI callback.
TEST=runtime/vm/compiler/ffi/unit_tests/variadic_double/x64_win.expect

Note that integer arguments already block out the corresponding xmm
registers on Windows x64.

On System-V, an upper bound of the number of XMM registers used must
be passed in AL. (Not reflected in the unit tests here, but will be in
the dependent CL.)

On ARM (32 bit), using varargs forces the calling convention to be in
softfp mode even on hardfp supported devices.

On RISC-V, the FPU registers are blocked when using varargs.

TEST=runtime/vm/compiler/ffi/native_calling_convention_test.cc
Test outputs in: runtime/vm/compiler/ffi/unit_tests/variadic_*
Run test with `tools/test.py ffi_unit`.

Bug: https://github.com/dart-lang/sdk/issues/38578
Change-Id: Ic568f8156c1c28ac3d6a2144805edf8caaa0169c
Cq-Include-Trybots: luci.dart.try:vm-precomp-ffi-qemu-linux-release-arm-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278342
Reviewed-by: Ryan Macnak <rmacnak@google.com>
2023-01-20 10:30:41 +00:00

1329 lines
42 KiB
C++

// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// 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.
#ifndef RUNTIME_VM_CONSTANTS_ARM_H_
#define RUNTIME_VM_CONSTANTS_ARM_H_
#ifndef RUNTIME_VM_CONSTANTS_H_
#error Do not include constants_arm.h directly; use constants.h instead.
#endif
#include "platform/assert.h"
#include "platform/globals.h"
#include "platform/utils.h"
#include "vm/constants_base.h"
namespace dart {
// LR register should not be used directly in handwritten assembly patterns,
// because it might contain return address. Instead use macross CLOBBERS_LR,
// SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER,
// RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR, SPILLS_LR_TO_FRAME,
// RESTORES_LR_FROM_FRAME, READS_RETURN_ADDRESS_FROM_LR,
// WRITES_RETURN_ADDRESS_TO_LR to get access to LR constant in a checked way.
//
// To prevent accidental use of LR constant we rename it to
// LR_DO_NOT_USE_DIRECTLY (while keeping the code in this file and other files
// which are permitted to access LR constant the same by defining LR as
// LR_DO_NOT_USE_DIRECTLY). You can also use LINK_REGISTER if you need
// to compare LR register code.
#define LR LR_DO_NOT_USE_DIRECTLY
#define R(reg) (static_cast<RegList>(1) << (reg))
// We support both VFPv3-D16 and VFPv3-D32 profiles, but currently only one at
// a time.
#if defined(DART_TARGET_OS_ANDROID) || defined(DART_TARGET_OS_LINUX)
#define VFPv3_D16
#elif defined(DART_TARGET_OS_MACOS_IOS) || defined(DART_TARGET_OS_WINDOWS)
#define VFPv3_D32
#else
#error Which VFP?
#endif
// The Linux/Android ABI and the iOS ABI differ in their choice of frame
// pointer, their treatment of R9, and the interprocedural stack alignment.
// EABI (Linux, Android, Windows)
// See "Procedure Call Standard for the ARM Architecture".
// R0-R1: Argument / result / volatile
// R2-R3: Argument / volatile
// R4-R10: Preserved
// R11: Frame pointer
// R12: Volatile
// R13: Stack pointer
// R14: Link register
// R15: Program counter
// Stack alignment: 4 bytes always, 8 bytes at public interfaces
// Linux (Debian armhf), Windows and Android also differ in whether floating
// point arguments are passed in floating point registers. Linux and Windows
// use hardfp and Android uses softfp. See
// TargetCPUFeatures::hardfp_supported().
// iOS ABI
// See "iOS ABI Function Call Guide"
// R0-R1: Argument / result / volatile
// R2-R3: Argument / volatile
// R4-R6: Preserved
// R7: Frame pointer
// R8-R11: Preserved
// R12: Volatile
// R13: Stack pointer
// R14: Link register
// R15: Program counter
// Stack alignment: 4 bytes always, 4 bytes at public interfaces
// iOS passes floating point arguments in integer registers (softfp)
enum Register {
R0 = 0,
R1 = 1,
R2 = 2,
R3 = 3,
R4 = 4,
R5 = 5, // PP
R6 = 6, // CODE_REG
R7 = 7, // FP on iOS, DISPATCH_TABLE_REG on non-iOS (AOT only)
R8 = 8,
R9 = 9,
R10 = 10, // THR
R11 = 11, // FP on non-iOS, DISPATCH_TABLE_REG on iOS (AOT only)
R12 = 12, // IP aka TMP
R13 = 13, // SP
R14 = 14, // LR
R15 = 15, // PC
kNumberOfCpuRegisters = 16,
kNoRegister = -1, // Signals an illegal register.
// Aliases.
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
FP = R7,
NOTFP = R11,
#else
FP = R11,
NOTFP = R7,
#endif
IP = R12,
SP = R13,
LR = R14, // Note: direct access to this constant is not allowed. See above.
PC = R15,
};
// Values for single-precision floating point registers.
enum SRegister {
kNoSRegister = -1,
S0 = 0,
S1 = 1,
S2 = 2,
S3 = 3,
S4 = 4,
S5 = 5,
S6 = 6,
S7 = 7,
S8 = 8,
S9 = 9,
S10 = 10,
S11 = 11,
S12 = 12,
S13 = 13,
S14 = 14,
S15 = 15,
S16 = 16,
S17 = 17,
S18 = 18,
S19 = 19,
S20 = 20,
S21 = 21,
S22 = 22,
S23 = 23,
S24 = 24,
S25 = 25,
S26 = 26,
S27 = 27,
S28 = 28,
S29 = 29,
S30 = 30,
S31 = 31,
kNumberOfSRegisters = 32,
};
// Values for double-precision floating point registers.
enum DRegister {
kNoDRegister = -1,
D0 = 0,
D1 = 1,
D2 = 2,
D3 = 3,
D4 = 4,
D5 = 5,
D6 = 6,
D7 = 7,
D8 = 8,
D9 = 9,
D10 = 10,
D11 = 11,
D12 = 12,
D13 = 13,
D14 = 14,
D15 = 15,
#if defined(VFPv3_D16)
kNumberOfDRegisters = 16,
// Leaving these defined, but marking them as kNoDRegister to avoid polluting
// other parts of the code with #ifdef's. Instead, query kNumberOfDRegisters
// to see which registers are valid.
D16 = kNoDRegister,
D17 = kNoDRegister,
D18 = kNoDRegister,
D19 = kNoDRegister,
D20 = kNoDRegister,
D21 = kNoDRegister,
D22 = kNoDRegister,
D23 = kNoDRegister,
D24 = kNoDRegister,
D25 = kNoDRegister,
D26 = kNoDRegister,
D27 = kNoDRegister,
D28 = kNoDRegister,
D29 = kNoDRegister,
D30 = kNoDRegister,
D31 = kNoDRegister,
#else
D16 = 16,
D17 = 17,
D18 = 18,
D19 = 19,
D20 = 20,
D21 = 21,
D22 = 22,
D23 = 23,
D24 = 24,
D25 = 25,
D26 = 26,
D27 = 27,
D28 = 28,
D29 = 29,
D30 = 30,
D31 = 31,
kNumberOfDRegisters = 32,
#endif
// Number of D registers that overlap S registers.
// One D register overlaps two S registers, so regardless of the numbers of D
// registers, there are only 32 S registers that are overlapped.
kNumberOfOverlappingDRegisters = 16,
};
enum QRegister {
kNoQRegister = -1,
Q0 = 0,
Q1 = 1,
Q2 = 2,
Q3 = 3,
Q4 = 4,
Q5 = 5,
Q6 = 6,
Q7 = 7,
#if defined(VFPv3_D16)
kNumberOfQRegisters = 8,
Q8 = kNoQRegister,
Q9 = kNoQRegister,
Q10 = kNoQRegister,
Q11 = kNoQRegister,
Q12 = kNoQRegister,
Q13 = kNoQRegister,
Q14 = kNoQRegister,
Q15 = kNoQRegister,
#else
Q8 = 8,
Q9 = 9,
Q10 = 10,
Q11 = 11,
Q12 = 12,
Q13 = 13,
Q14 = 14,
Q15 = 15,
kNumberOfQRegisters = 16,
#endif
// Number of Q registers that overlap S registers.
// One Q register overlaps four S registers, so regardless of the numbers of Q
// registers, there are only 32 S registers that are overlapped.
kNumberOfOverlappingQRegisters = 8,
};
static inline DRegister EvenDRegisterOf(QRegister q) {
return static_cast<DRegister>(q * 2);
}
static inline DRegister OddDRegisterOf(QRegister q) {
return static_cast<DRegister>((q * 2) + 1);
}
static inline SRegister EvenSRegisterOf(DRegister d) {
#if defined(VFPv3_D32)
// When we have 32 D registers, the S registers only overlap the first 16.
// That is, there are only ever 32 S registers in any extension.
ASSERT(d < D16);
#endif
return static_cast<SRegister>(d * 2);
}
static inline SRegister OddSRegisterOf(DRegister d) {
#if defined(VFPv3_D32)
ASSERT(d < D16);
#endif
return static_cast<SRegister>((d * 2) + 1);
}
static inline QRegister QRegisterOf(DRegister d) {
return static_cast<QRegister>(d / 2);
}
static inline QRegister QRegisterOf(SRegister s) {
return static_cast<QRegister>(s / 4);
}
static inline DRegister DRegisterOf(SRegister s) {
return static_cast<DRegister>(s / 2);
}
// Register aliases for floating point scratch registers.
const QRegister QTMP = Q7; // Overlaps with DTMP, STMP.
const DRegister DTMP = EvenDRegisterOf(QTMP); // Overlaps with STMP.
const SRegister STMP DART_USED = EvenSRegisterOf(DTMP);
// Architecture independent aliases.
typedef QRegister FpuRegister;
const FpuRegister FpuTMP = QTMP;
const int kFpuRegisterSize = 16;
typedef simd128_value_t fpu_register_t;
const int kNumberOfFpuRegisters = kNumberOfQRegisters;
const FpuRegister kNoFpuRegister = kNoQRegister;
extern const char* const cpu_reg_names[kNumberOfCpuRegisters];
extern const char* const cpu_reg_abi_names[kNumberOfCpuRegisters];
extern const char* const fpu_reg_names[kNumberOfFpuRegisters];
extern const char* const fpu_s_reg_names[kNumberOfSRegisters];
extern const char* const fpu_d_reg_names[kNumberOfDRegisters];
// Register aliases.
const Register TMP = IP; // Used as scratch register by assembler.
const Register TMP2 = kNoRegister; // There is no second assembler temporary.
const Register PP = R5; // Caches object pool pointer in generated code.
const Register DISPATCH_TABLE_REG = NOTFP; // Dispatch table register.
const Register SPREG = SP; // Stack pointer register.
const Register FPREG = FP; // Frame pointer register.
const Register IC_DATA_REG = R9; // ICData/MegamorphicCache register.
const Register ARGS_DESC_REG = R4;
const Register CODE_REG = R6;
// Set when calling Dart functions in JIT mode, used by LazyCompileStub.
const Register FUNCTION_REG = R0;
const Register THR = R10; // Caches current thread in generated code.
const Register CALLEE_SAVED_TEMP = R8;
// R15 encodes APSR in the vmrs instruction.
const Register APSR = R15;
// ABI for catch-clause entry point.
const Register kExceptionObjectReg = R0;
const Register kStackTraceObjectReg = R1;
// ABI for write barrier stub.
const Register kWriteBarrierObjectReg = R1;
const Register kWriteBarrierValueReg = R0;
const Register kWriteBarrierSlotReg = R9;
// Common ABI for shared slow path stubs.
struct SharedSlowPathStubABI {
static const Register kResultReg = R0;
};
// ABI for instantiation stubs.
struct InstantiationABI {
static const Register kUninstantiatedTypeArgumentsReg = R3;
static const Register kInstantiatorTypeArgumentsReg = R2;
static const Register kFunctionTypeArgumentsReg = R1;
static const Register kResultTypeArgumentsReg = R0;
static const Register kResultTypeReg = R0;
static const Register kScratchReg = R8;
};
// Registers in addition to those listed in InstantiationABI used inside the
// implementation of the InstantiateTypeArguments stubs.
struct InstantiateTAVInternalRegs {
// The set of registers that must be pushed/popped when probing a hash-based
// cache due to overlap with the registers in InstantiationABI.
static const intptr_t kSavedRegisters =
#if defined(DART_PRECOMPILER)
(1 << DISPATCH_TABLE_REG) |
#endif
(1 << InstantiationABI::kUninstantiatedTypeArgumentsReg);
// Additional registers used to probe hash-based caches.
static const Register kEntryStartReg = R9;
static const Register kProbeMaskReg = R4;
static const Register kProbeDistanceReg = DISPATCH_TABLE_REG;
static const Register kCurrentEntryIndexReg =
InstantiationABI::kUninstantiatedTypeArgumentsReg;
};
// Registers in addition to those listed in TypeTestABI used inside the
// implementation of type testing stubs that are _not_ preserved.
struct TTSInternalRegs {
static const Register kInstanceTypeArgumentsReg = R4;
static const Register kScratchReg = R9;
static const Register kSubTypeArgumentReg = R3;
static const Register kSuperTypeArgumentReg = R8;
// Must be pushed/popped whenever generic type arguments are being checked as
// they overlap with registers in TypeTestABI.
static const intptr_t kSavedTypeArgumentRegisters =
(1 << kSubTypeArgumentReg) | (1 << kSuperTypeArgumentReg);
static const intptr_t kInternalRegisters =
((1 << kInstanceTypeArgumentsReg) | (1 << kScratchReg) |
(1 << kSubTypeArgumentReg) | (1 << kSuperTypeArgumentReg)) &
~kSavedTypeArgumentRegisters;
};
// Registers in addition to those listed in TypeTestABI used inside the
// implementation of subtype test cache stubs that are _not_ preserved.
struct STCInternalRegs {
static const Register kInstanceCidOrSignatureReg = R9;
static const intptr_t kInternalRegisters = (1 << kInstanceCidOrSignatureReg);
};
// Calling convention when calling TypeTestingStub and SubtypeTestCacheStub.
struct TypeTestABI {
static const Register kInstanceReg = R0;
static const Register kDstTypeReg = R8;
static const Register kInstantiatorTypeArgumentsReg = R2;
static const Register kFunctionTypeArgumentsReg = R1;
static const Register kSubtypeTestCacheReg = R3;
static const Register kScratchReg = R4;
// For calls to InstanceOfStub.
static const Register kInstanceOfResultReg = kInstanceReg;
// For calls to SubtypeNTestCacheStub. Must be saved by the caller if the
// original value is needed after the call.
static const Register kSubtypeTestCacheResultReg = kSubtypeTestCacheReg;
// Registers that need saving across SubtypeTestCacheStub calls.
static const intptr_t kSubtypeTestCacheStubCallerSavedRegisters =
1 << kSubtypeTestCacheReg;
static const intptr_t kPreservedAbiRegisters =
(1 << kInstanceReg) | (1 << kDstTypeReg) |
(1 << kInstantiatorTypeArgumentsReg) | (1 << kFunctionTypeArgumentsReg);
static const intptr_t kNonPreservedAbiRegisters =
TTSInternalRegs::kInternalRegisters |
STCInternalRegs::kInternalRegisters | (1 << kSubtypeTestCacheReg) |
(1 << kScratchReg) | (1 << kSubtypeTestCacheResultReg) | (1 << CODE_REG);
static const intptr_t kAbiRegisters =
kPreservedAbiRegisters | kNonPreservedAbiRegisters;
};
// Calling convention when calling AssertSubtypeStub.
struct AssertSubtypeABI {
static const Register kSubTypeReg = R0;
static const Register kSuperTypeReg = R8;
static const Register kInstantiatorTypeArgumentsReg = R2;
static const Register kFunctionTypeArgumentsReg = R1;
static const Register kDstNameReg = R3;
static const intptr_t kAbiRegisters =
(1 << kSubTypeReg) | (1 << kSuperTypeReg) |
(1 << kInstantiatorTypeArgumentsReg) | (1 << kFunctionTypeArgumentsReg) |
(1 << kDstNameReg);
// No result register, as AssertSubtype is only run for side effect
// (throws if the subtype check fails).
};
// ABI for InitStaticFieldStub.
struct InitStaticFieldABI {
static const Register kFieldReg = R2;
static const Register kResultReg = R0;
};
// Registers used inside the implementation of InitLateStaticFieldStub.
struct InitLateStaticFieldInternalRegs {
static const Register kAddressReg = R3;
static const Register kScratchReg = R4;
};
// ABI for InitInstanceFieldStub.
struct InitInstanceFieldABI {
static const Register kInstanceReg = R1;
static const Register kFieldReg = R2;
static const Register kResultReg = R0;
};
// Registers used inside the implementation of InitLateInstanceFieldStub.
struct InitLateInstanceFieldInternalRegs {
static const Register kAddressReg = R3;
static const Register kScratchReg = R4;
};
// ABI for LateInitializationError stubs.
struct LateInitializationErrorABI {
static const Register kFieldReg = R9;
};
// ABI for ThrowStub.
struct ThrowABI {
static const Register kExceptionReg = R0;
};
// ABI for ReThrowStub.
struct ReThrowABI {
static const Register kExceptionReg = R0;
static const Register kStackTraceReg = R1;
};
// ABI for AssertBooleanStub.
struct AssertBooleanABI {
static const Register kObjectReg = R0;
};
// ABI for RangeErrorStub.
struct RangeErrorABI {
static const Register kLengthReg = R0;
static const Register kIndexReg = R1;
};
// ABI for AllocateObjectStub.
struct AllocateObjectABI {
static const Register kResultReg = R0;
static const Register kTypeArgumentsReg = R3;
static const Register kTagsReg = R2;
};
// ABI for AllocateClosureStub.
struct AllocateClosureABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kFunctionReg = R1;
static const Register kContextReg = R2;
static const Register kScratchReg = R4;
};
// ABI for AllocateMintShared*Stub.
struct AllocateMintABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kTempReg = R1;
};
// ABI for Allocate{Mint,Double,Float32x4,Float64x2}Stub.
struct AllocateBoxABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kTempReg = R1;
};
// ABI for AllocateArrayStub.
struct AllocateArrayABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kLengthReg = R2;
static const Register kTypeArgumentsReg = R1;
};
// ABI for AllocateRecordStub.
struct AllocateRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kShapeReg = R1;
static const Register kTemp1Reg = R2;
static const Register kTemp2Reg = R3;
};
// ABI for AllocateSmallRecordStub (AllocateRecord2, AllocateRecord2Named,
// AllocateRecord3, AllocateRecord3Named).
struct AllocateSmallRecordABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kShapeReg = R1;
static const Register kValue0Reg = R2;
static const Register kValue1Reg = R3;
static const Register kValue2Reg = R4;
static const Register kTempReg = R9;
};
// ABI for AllocateTypedDataArrayStub.
struct AllocateTypedDataArrayABI {
static const Register kResultReg = AllocateObjectABI::kResultReg;
static const Register kLengthReg = R4;
};
// ABI for BoxDoubleStub.
struct BoxDoubleStubABI {
static const FpuRegister kValueReg = Q0;
static const Register kTempReg = R1;
static const Register kResultReg = R0;
};
// ABI for DoubleToIntegerStub.
struct DoubleToIntegerStubABI {
static const FpuRegister kInputReg = Q0;
static const Register kRecognizedKindReg = R0;
static const Register kResultReg = R0;
};
// ABI for SuspendStub (AwaitStub, AwaitWithTypeCheckStub, YieldAsyncStarStub,
// SuspendSyncStarAtStartStub, SuspendSyncStarAtYieldStub).
struct SuspendStubABI {
static const Register kArgumentReg = R0;
static const Register kTypeArgsReg = R1; // Can be the same as kTempReg
static const Register kTempReg = R1;
static const Register kFrameSizeReg = R2;
static const Register kSuspendStateReg = R3;
static const Register kFunctionDataReg = R4;
static const Register kSrcFrameReg = R8;
static const Register kDstFrameReg = R9;
};
// ABI for InitSuspendableFunctionStub (InitAsyncStub, InitAsyncStarStub,
// InitSyncStarStub).
struct InitSuspendableFunctionStubABI {
static const Register kTypeArgsReg = R0;
};
// ABI for ResumeStub
struct ResumeStubABI {
static const Register kSuspendStateReg = R2;
static const Register kTempReg = R0;
// Registers for the frame copying (the 1st part).
static const Register kFrameSizeReg = R1;
static const Register kSrcFrameReg = R3;
static const Register kDstFrameReg = R4;
// Registers for control transfer.
// (the 2nd part, can reuse registers from the 1st part)
static const Register kResumePcReg = R1;
// Can also reuse kSuspendStateReg but should not conflict with CODE_REG/PP.
static const Register kExceptionReg = R3;
static const Register kStackTraceReg = R4;
};
// ABI for ReturnStub (ReturnAsyncStub, ReturnAsyncNotFutureStub,
// ReturnAsyncStarStub).
struct ReturnStubABI {
static const Register kSuspendStateReg = R2;
};
// ABI for AsyncExceptionHandlerStub.
struct AsyncExceptionHandlerStubABI {
static const Register kSuspendStateReg = R2;
};
// ABI for CloneSuspendStateStub.
struct CloneSuspendStateStubABI {
static const Register kSourceReg = R0;
static const Register kDestinationReg = R1;
static const Register kTempReg = R2;
static const Register kFrameSizeReg = R3;
static const Register kSrcFrameReg = R4;
static const Register kDstFrameReg = R8;
};
// ABI for DispatchTableNullErrorStub and consequently for all dispatch
// table calls (though normal functions will not expect or use this
// register). This ABI is added to distinguish memory corruption errors from
// null errors.
struct DispatchTableNullErrorABI {
static const Register kClassIdReg = R0;
};
// TODO(regis): Add ABIs for type testing stubs and is-type test stubs instead
// of reusing the constants of the instantiation stubs ABI.
// List of registers used in load/store multiple.
typedef uint16_t RegList;
const RegList kAllCpuRegistersList = 0xFFFF;
const RegList kAllFpuRegistersList = (1 << kNumberOfFpuRegisters) - 1;
// C++ ABI call registers.
const RegList kAbiArgumentCpuRegs =
(1 << R0) | (1 << R1) | (1 << R2) | (1 << R3);
const RegList kAbiVolatileCpuRegs = kAbiArgumentCpuRegs | (1 << IP) | (1 << LR);
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
const RegList kAbiPreservedCpuRegs =
(1 << R4) | (1 << R5) | (1 << R6) | (1 << R8) | (1 << R10) | (1 << R11);
const int kAbiPreservedCpuRegCount = 6;
#else
const RegList kAbiPreservedCpuRegs = (1 << R4) | (1 << R5) | (1 << R6) |
(1 << R7) | (1 << R8) | (1 << R9) |
(1 << R10);
const int kAbiPreservedCpuRegCount = 7;
#endif
const QRegister kAbiFirstPreservedFpuReg = Q4;
const QRegister kAbiLastPreservedFpuReg = Q7;
const int kAbiPreservedFpuRegCount = 4;
const RegList kReservedCpuRegisters = (1 << SPREG) | (1 << FPREG) | (1 << TMP) |
(1 << PP) | (1 << THR) | (1 << LR) |
(1 << PC) | (1 << NOTFP);
constexpr intptr_t kNumberOfReservedCpuRegisters =
Utils::CountOneBits32(kReservedCpuRegisters);
// CPU registers available to Dart allocator.
constexpr RegList kDartAvailableCpuRegs =
kAllCpuRegistersList & ~kReservedCpuRegisters;
constexpr int kNumberOfDartAvailableCpuRegs =
kNumberOfCpuRegisters - kNumberOfReservedCpuRegisters;
// No reason to prefer certain registers on ARM.
constexpr int kRegisterAllocationBias = 0;
const intptr_t kStoreBufferWrapperSize = 24;
// Registers available to Dart that are not preserved by runtime calls.
const RegList kDartVolatileCpuRegs =
kDartAvailableCpuRegs & ~kAbiPreservedCpuRegs;
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
const int kDartVolatileCpuRegCount = 6;
#else
const int kDartVolatileCpuRegCount = 5;
#endif
const RegList kAbiVolatileFpuRegs = R(Q0) | R(Q1) | R(Q2) | R(Q3);
const RegList kFpuRegistersWithoutSOverlap =
kAllFpuRegistersList &
~((1 << QRegister::kNumberOfOverlappingQRegisters) - 1);
class CallingConventions {
public:
static const intptr_t kArgumentRegisters = kAbiArgumentCpuRegs;
static const Register ArgumentRegisters[];
static const intptr_t kNumArgRegs = 4;
static const Register kPointerToReturnStructRegisterCall = R0;
static const intptr_t kFpuArgumentRegisters = 0;
static const FpuRegister FpuArgumentRegisters[];
static const intptr_t kNumFpuArgRegs = 4;
static const DRegister FpuDArgumentRegisters[];
static const intptr_t kNumDFpuArgRegs = 8;
static const SRegister FpuSArgumentRegisters[];
static const intptr_t kNumSFpuArgRegs = 16;
static constexpr bool kArgumentIntRegXorFpuReg = false;
static constexpr intptr_t kCalleeSaveCpuRegisters = kAbiPreservedCpuRegs;
// Whether larger than wordsize arguments are aligned to even registers.
static constexpr AlignmentStrategy kArgumentRegisterAlignment =
kAlignedToWordSizeAndValueSize;
static constexpr AlignmentStrategy kArgumentRegisterAlignmentVarArgs =
kArgumentRegisterAlignment;
// How stack arguments are aligned.
static constexpr AlignmentStrategy kArgumentStackAlignment =
kAlignedToWordSizeAndValueSize;
// How fields in compounds are aligned.
#if defined(DART_TARGET_OS_MACOS_IOS)
static constexpr AlignmentStrategy kFieldAlignment =
kAlignedToValueSizeBut8AlignedTo4;
#else
static constexpr AlignmentStrategy kFieldAlignment = kAlignedToValueSize;
#endif
// Whether 1 or 2 byte-sized arguments or return values are passed extended
// to 4 bytes.
static constexpr ExtensionStrategy kReturnRegisterExtension = kExtendedTo4;
static constexpr ExtensionStrategy kArgumentRegisterExtension = kExtendedTo4;
static constexpr ExtensionStrategy kArgumentStackExtension = kExtendedTo4;
static constexpr Register kReturnReg = R0;
static constexpr Register kSecondReturnReg = R1;
static constexpr FpuRegister kReturnFpuReg = Q0;
static constexpr Register kPointerToReturnStructRegisterReturn = kReturnReg;
// We choose these to avoid overlap between themselves and reserved registers.
static constexpr Register kFirstNonArgumentRegister = R8;
static constexpr Register kSecondNonArgumentRegister = R9;
static constexpr Register kFfiAnyNonAbiRegister = R4;
static constexpr Register kStackPointerRegister = SPREG;
COMPILE_ASSERT(
((R(kFirstNonArgumentRegister) | R(kSecondNonArgumentRegister)) &
(kArgumentRegisters | R(kPointerToReturnStructRegisterCall))) == 0);
};
#undef R
// Values for the condition field as defined in section A3.2.
enum Condition {
kNoCondition = -1,
EQ = 0, // equal
NE = 1, // not equal
CS = 2, // carry set/unsigned higher or same
CC = 3, // carry clear/unsigned lower
MI = 4, // minus/negative
PL = 5, // plus/positive or zero
VS = 6, // overflow
VC = 7, // no overflow
HI = 8, // unsigned higher
LS = 9, // unsigned lower or same
GE = 10, // signed greater than or equal
LT = 11, // signed less than
GT = 12, // signed greater than
LE = 13, // signed less than or equal
AL = 14, // always (unconditional)
kSpecialCondition = 15, // special condition (refer to section A3.2.1)
kNumberOfConditions = 16,
// Platform-independent variants declared for all platforms
EQUAL = EQ,
ZERO = EQUAL,
NOT_EQUAL = NE,
NOT_ZERO = NOT_EQUAL,
LESS = LT,
LESS_EQUAL = LE,
GREATER_EQUAL = GE,
GREATER = GT,
UNSIGNED_LESS = CC,
UNSIGNED_LESS_EQUAL = LS,
UNSIGNED_GREATER = HI,
UNSIGNED_GREATER_EQUAL = CS,
OVERFLOW = VS,
NO_OVERFLOW = VC,
kInvalidCondition = 16
};
static inline Condition InvertCondition(Condition c) {
COMPILE_ASSERT((EQ ^ NE) == 1);
COMPILE_ASSERT((CS ^ CC) == 1);
COMPILE_ASSERT((MI ^ PL) == 1);
COMPILE_ASSERT((VS ^ VC) == 1);
COMPILE_ASSERT((HI ^ LS) == 1);
COMPILE_ASSERT((GE ^ LT) == 1);
COMPILE_ASSERT((GT ^ LE) == 1);
ASSERT(c != AL);
ASSERT(c != kSpecialCondition);
ASSERT(c != kInvalidCondition);
return static_cast<Condition>(c ^ 1);
}
// Opcodes for Data-processing instructions (instructions with a type 0 and 1)
// as defined in section A3.4
enum Opcode {
kNoOperand = -1,
AND = 0, // Logical AND
EOR = 1, // Logical Exclusive OR
SUB = 2, // Subtract
RSB = 3, // Reverse Subtract
ADD = 4, // Add
ADC = 5, // Add with Carry
SBC = 6, // Subtract with Carry
RSC = 7, // Reverse Subtract with Carry
TST = 8, // Test
TEQ = 9, // Test Equivalence
CMP = 10, // Compare
CMN = 11, // Compare Negated
ORR = 12, // Logical (inclusive) OR
MOV = 13, // Move
BIC = 14, // Bit Clear
MVN = 15, // Move Not
kMaxOperand = 16
};
// Shifter types for Data-processing operands as defined in section A5.1.2.
enum Shift {
kNoShift = -1,
LSL = 0, // Logical shift left
LSR = 1, // Logical shift right
ASR = 2, // Arithmetic shift right
ROR = 3, // Rotate right
kMaxShift = 4
};
// Constants used for the decoding or encoding of the individual fields of
// instructions. Based on the "Figure 3-1 ARM instruction set summary".
enum InstructionFields {
kConditionShift = 28,
kConditionBits = 4,
kTypeShift = 25,
kTypeBits = 3,
kLinkShift = 24,
kLinkBits = 1,
kUShift = 23,
kUBits = 1,
kOpcodeShift = 21,
kOpcodeBits = 4,
kSShift = 20,
kSBits = 1,
kRnShift = 16,
kRnBits = 4,
kRdShift = 12,
kRdBits = 4,
kRsShift = 8,
kRsBits = 4,
kRmShift = 0,
kRmBits = 4,
// Immediate instruction fields encoding.
kRotateShift = 8,
kRotateBits = 4,
kImmed8Shift = 0,
kImmed8Bits = 8,
// Shift instruction register fields encodings.
kShiftImmShift = 7,
kShiftRegisterShift = 8,
kShiftImmBits = 5,
kShiftShift = 5,
kShiftBits = 2,
// Load/store instruction offset field encoding.
kOffset12Shift = 0,
kOffset12Bits = 12,
kOffset12Mask = 0x00000fff,
// Mul instruction register field encodings.
kMulRdShift = 16,
kMulRdBits = 4,
kMulRnShift = 12,
kMulRnBits = 4,
// ldrex/strex register field encodings.
kLdrExRnShift = 16,
kLdrExRtShift = 12,
kStrExRnShift = 16,
kStrExRdShift = 12,
kStrExRtShift = 0,
// Media operation field encodings.
kMediaOp1Shift = 20,
kMediaOp1Bits = 5,
kMediaOp2Shift = 5,
kMediaOp2Bits = 3,
// udiv/sdiv instruction register field encodings.
kDivRdShift = 16,
kDivRdBits = 4,
kDivRmShift = 8,
kDivRmBits = 4,
kDivRnShift = 0,
kDivRnBits = 4,
// sbfx/ubfx instruction register and immediate field encodings.
kBitFieldExtractWidthShift = 16,
kBitFieldExtractWidthBits = 5,
kBitFieldExtractLSBShift = 7,
kBitFieldExtractLSBBits = 5,
kBitFieldExtractRnShift = 0,
kBitFieldExtractRnBits = 4,
// MRC instruction offset field encoding.
kCRmShift = 0,
kCRmBits = 4,
kOpc2Shift = 5,
kOpc2Bits = 3,
kCoprocShift = 8,
kCoprocBits = 4,
kCRnShift = 16,
kCRnBits = 4,
kOpc1Shift = 21,
kOpc1Bits = 3,
kBranchOffsetMask = 0x00ffffff
};
enum ScaleFactor {
TIMES_1 = 0,
TIMES_2 = 1,
TIMES_4 = 2,
TIMES_8 = 3,
TIMES_16 = 4,
// Don't use (dart::)kWordSizeLog2, as this needs to work for crossword as
// well. If this is included, we know the target is 32 bit.
#if defined(TARGET_ARCH_IS_32_BIT)
// Used for Smi-boxed indices.
TIMES_HALF_WORD_SIZE = kInt32SizeLog2 - 1,
// Used for unboxed indices.
TIMES_WORD_SIZE = kInt32SizeLog2,
#else
#error "Unexpected word size"
#endif
#if !defined(DART_COMPRESSED_POINTERS)
TIMES_COMPRESSED_WORD_SIZE = TIMES_WORD_SIZE,
#else
#error Cannot compress ARM32
#endif
// Used for Smi-boxed indices.
TIMES_COMPRESSED_HALF_WORD_SIZE = TIMES_COMPRESSED_WORD_SIZE - 1,
};
// The class Instr enables access to individual fields defined in the ARM
// architecture instruction set encoding as described in figure A3-1.
//
// Example: Test whether the instruction at ptr sets the condition code bits.
//
// bool InstructionSetsConditionCodes(byte* ptr) {
// Instr* instr = Instr::At(ptr);
// int type = instr->TypeField();
// return ((type == 0) || (type == 1)) && instr->HasS();
// }
//
class Instr {
public:
enum { kInstrSize = 4, kInstrSizeLog2 = 2, kPCReadOffset = 8 };
static const int32_t kNopInstruction = // nop
((AL << kConditionShift) | (0x32 << 20) | (0xf << 12));
static const int32_t kBreakPointCode = 0xdeb0; // For breakpoint.
static const int32_t kSimulatorBreakCode = 0xdeb2; // For breakpoint in sim.
static const int32_t kSimulatorRedirectCode = 0xca11; // For redirection.
// Breakpoint instruction filling assembler code buffers in debug mode.
static const int32_t kBreakPointInstruction = // bkpt(0xdeb0)
((AL << kConditionShift) | (0x12 << 20) | (0xdeb << 8) | (0x7 << 4));
// Breakpoint instruction used by the simulator.
// Should be distinct from kBreakPointInstruction and from a typical user
// breakpoint inserted in generated code for debugging, e.g. bkpt(0).
static const int32_t kSimulatorBreakpointInstruction =
// svc #kBreakpointSvcCode
((AL << kConditionShift) | (0xf << 24) | kSimulatorBreakCode);
// Runtime call redirection instruction used by the simulator.
static const int32_t kSimulatorRedirectInstruction =
((AL << kConditionShift) | (0xf << 24) | kSimulatorRedirectCode);
// Get the raw instruction bits.
inline int32_t InstructionBits() const {
return *reinterpret_cast<const int32_t*>(this);
}
// Set the raw instruction bits to value.
inline void SetInstructionBits(int32_t value) {
*reinterpret_cast<int32_t*>(this) = value;
}
// Read one particular bit out of the instruction bits.
inline int Bit(int nr) const { return (InstructionBits() >> nr) & 1; }
// Read a bit field out of the instruction bits.
inline int Bits(int shift, int count) const {
return (InstructionBits() >> shift) & ((1 << count) - 1);
}
// Accessors for the different named fields used in the ARM encoding.
// The naming of these accessor corresponds to figure A3-1.
// Generally applicable fields
inline Condition ConditionField() const {
return static_cast<Condition>(Bits(kConditionShift, kConditionBits));
}
inline int TypeField() const { return Bits(kTypeShift, kTypeBits); }
inline int SubtypeField() const { return Bit(4); }
inline Register RnField() const {
return static_cast<Register>(Bits(kRnShift, kRnBits));
}
inline Register RdField() const {
return static_cast<Register>(Bits(kRdShift, kRdBits));
}
// Fields used in Data processing instructions
inline Opcode OpcodeField() const {
return static_cast<Opcode>(Bits(kOpcodeShift, kOpcodeBits));
}
inline int SField() const { return Bits(kSShift, kSBits); }
// with register
inline Register RmField() const {
return static_cast<Register>(Bits(kRmShift, kRmBits));
}
inline Shift ShiftField() const {
return static_cast<Shift>(Bits(kShiftShift, kShiftBits));
}
inline int RegShiftField() const { return Bit(4); }
inline Register RsField() const {
return static_cast<Register>(Bits(kRsShift, kRsBits));
}
inline int ShiftAmountField() const {
return Bits(kShiftImmShift, kShiftImmBits);
}
// with immediate
inline int RotateField() const { return Bits(kRotateShift, kRotateBits); }
inline int Immed8Field() const { return Bits(kImmed8Shift, kImmed8Bits); }
// Fields used in Load/Store instructions
inline int PUField() const { return Bits(23, 2); }
inline int BField() const { return Bit(22); }
inline int WField() const { return Bit(21); }
inline int LField() const { return Bit(20); }
// with register uses same fields as Data processing instructions above
// with immediate
inline int Offset12Field() const {
return Bits(kOffset12Shift, kOffset12Bits);
}
// multiple
inline int RlistField() const { return Bits(0, 16); }
// extra loads and stores
inline int SignField() const { return Bit(6); }
inline int HField() const { return Bit(5); }
inline int ImmedHField() const { return Bits(8, 4); }
inline int ImmedLField() const { return Bits(0, 4); }
// Fields used in Branch instructions
inline int LinkField() const { return Bits(kLinkShift, kLinkBits); }
inline int32_t SImmed24Field() const {
uint32_t bits = InstructionBits();
return static_cast<int32_t>(bits << 8) >> 8;
}
// Fields used in Supervisor Call instructions
inline uint32_t SvcField() const { return Bits(0, 24); }
// Field used in Breakpoint instruction
inline uint16_t BkptField() const {
return ((Bits(8, 12) << 4) | Bits(0, 4));
}
// Field used in 16-bit immediate move instructions
inline uint16_t MovwField() const {
return ((Bits(16, 4) << 12) | Bits(0, 12));
}
// Field used in VFP float immediate move instruction
inline float ImmFloatField() const {
uint32_t imm32 = (Bit(19) << 31) | (((1 << 5) - Bit(18)) << 25) |
(Bits(16, 2) << 23) | (Bits(0, 4) << 19);
return bit_cast<float, uint32_t>(imm32);
}
// Field used in VFP double immediate move instruction
inline double ImmDoubleField() const {
uint64_t imm64 = (Bit(19) * (1LL << 63)) | (((1LL << 8) - Bit(18)) << 54) |
(Bits(16, 2) * (1LL << 52)) | (Bits(0, 4) * (1LL << 48));
return bit_cast<double, uint64_t>(imm64);
}
// Shared fields used in media instructions.
inline int MediaOp1Field() const {
return static_cast<Register>(Bits(kMediaOp1Shift, kMediaOp1Bits));
}
inline int MediaOp2Field() const {
return static_cast<Register>(Bits(kMediaOp2Shift, kMediaOp2Bits));
}
// Fields used in division instructions.
inline bool IsDivUnsigned() const { return Bit(21) == 0b1; }
inline Register DivRdField() const {
return static_cast<Register>(Bits(kDivRdShift, kDivRdBits));
}
inline Register DivRmField() const {
return static_cast<Register>(Bits(kDivRmShift, kDivRmBits));
}
inline Register DivRnField() const {
return static_cast<Register>(Bits(kDivRnShift, kDivRnBits));
}
// Fields used in bit field extract instructions.
inline bool IsBitFieldExtractSignExtended() const { return Bit(22) == 0; }
inline uint8_t BitFieldExtractWidthField() const {
return Bits(kBitFieldExtractWidthShift, kBitFieldExtractWidthBits);
}
inline uint8_t BitFieldExtractLSBField() const {
return Bits(kBitFieldExtractLSBShift, kBitFieldExtractLSBBits);
}
inline Register BitFieldExtractRnField() const {
return static_cast<Register>(
Bits(kBitFieldExtractRnShift, kBitFieldExtractRnBits));
}
// Test for data processing instructions of type 0 or 1.
// See "ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition",
// section A5.1 "ARM instruction set encoding".
inline bool IsDataProcessing() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(Bits(26, 2) == 0); // Type 0 or 1.
return ((Bits(20, 5) & 0x19) != 0x10) &&
((Bit(25) == 1) || // Data processing immediate.
(Bit(4) == 0) || // Data processing register.
(Bit(7) == 0)); // Data processing register-shifted register.
}
// Tests for special encodings of type 0 instructions (extra loads and stores,
// as well as multiplications, synchronization primitives, and miscellaneous).
// Can only be called for a type 0 or 1 instruction.
inline bool IsMiscellaneous() const {
ASSERT(Bits(26, 2) == 0); // Type 0 or 1.
return ((Bit(25) == 0) && ((Bits(20, 5) & 0x19) == 0x10) && (Bit(7) == 0));
}
inline bool IsMultiplyOrSyncPrimitive() const {
ASSERT(Bits(26, 2) == 0); // Type 0 or 1.
return ((Bit(25) == 0) && (Bits(4, 4) == 9));
}
// Test for Supervisor Call instruction.
inline bool IsSvc() const {
return ((InstructionBits() & 0x0f000000) == 0x0f000000);
}
// Test for Breakpoint instruction.
inline bool IsBkpt() const {
return ((InstructionBits() & 0x0ff000f0) == 0x01200070);
}
// VFP register fields.
inline SRegister SnField() const {
return static_cast<SRegister>((Bits(kRnShift, kRnBits) << 1) + Bit(7));
}
inline SRegister SdField() const {
return static_cast<SRegister>((Bits(kRdShift, kRdBits) << 1) + Bit(22));
}
inline SRegister SmField() const {
return static_cast<SRegister>((Bits(kRmShift, kRmBits) << 1) + Bit(5));
}
inline DRegister DnField() const {
return static_cast<DRegister>(Bits(kRnShift, kRnBits) + (Bit(7) << 4));
}
inline DRegister DdField() const {
return static_cast<DRegister>(Bits(kRdShift, kRdBits) + (Bit(22) << 4));
}
inline DRegister DmField() const {
return static_cast<DRegister>(Bits(kRmShift, kRmBits) + (Bit(5) << 4));
}
inline QRegister QnField() const {
const intptr_t bits = Bits(kRnShift, kRnBits) + (Bit(7) << 4);
return static_cast<QRegister>(bits >> 1);
}
inline QRegister QdField() const {
const intptr_t bits = Bits(kRdShift, kRdBits) + (Bit(22) << 4);
return static_cast<QRegister>(bits >> 1);
}
inline QRegister QmField() const {
const intptr_t bits = Bits(kRmShift, kRmBits) + (Bit(5) << 4);
return static_cast<QRegister>(bits >> 1);
}
// Test for VFP data processing or single transfer instructions of type 7.
inline bool IsVFPDataProcessingOrSingleTransfer() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(TypeField() == 7);
return ((Bit(24) == 0) && (Bits(9, 3) == 5));
// Bit(4) == 0: Data Processing
// Bit(4) == 1: 8, 16, or 32-bit Transfer between ARM Core and VFP
}
// Test for VFP 64-bit transfer instructions of type 6.
inline bool IsVFPDoubleTransfer() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(TypeField() == 6);
return ((Bits(21, 4) == 2) && (Bits(9, 3) == 5) &&
((Bits(4, 4) & 0xd) == 1));
}
// Test for VFP load and store instructions of type 6.
inline bool IsVFPLoadStore() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(TypeField() == 6);
return ((Bits(20, 5) & 0x12) == 0x10) && (Bits(9, 3) == 5);
}
// Test for VFP multiple load and store instructions of type 6.
inline bool IsVFPMultipleLoadStore() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(TypeField() == 6);
int32_t puw = (PUField() << 1) | Bit(21); // don't care about D bit
return (Bits(9, 3) == 5) && ((puw == 2) || (puw == 3) || (puw == 5));
}
inline bool IsSIMDDataProcessing() const {
ASSERT(ConditionField() == kSpecialCondition);
return (Bits(25, 3) == 1);
}
inline bool IsSIMDLoadStore() const {
ASSERT(ConditionField() == kSpecialCondition);
return (Bits(24, 4) == 4) && (Bit(20) == 0);
}
// Tests for media instructions of type 3.
inline bool IsMedia() const {
ASSERT_EQUAL(TypeField(), 3);
return SubtypeField() == 1;
}
inline bool IsDivision() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(IsMedia());
// B21 determines whether the division is signed or unsigned.
return (((MediaOp1Field() & 0b11101) == 0b10001) &&
(MediaOp2Field() == 0b000));
}
inline bool IsRbit() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(IsMedia());
// B19-B16 and B11-B8 are always set for rbit.
return ((MediaOp1Field() == 0b01111) && (MediaOp2Field() == 0b001) &&
(Bits(8, 4) == 0b1111) && (Bits(16, 4) == 0b1111));
}
inline bool IsBitFieldExtract() const {
ASSERT(ConditionField() != kSpecialCondition);
ASSERT(IsMedia());
// B22 determines whether extracted value is sign extended or not, and
// op bits B20 and B7 are part of the width and LSB fields, respectively.
return ((MediaOp1Field() & 0b11010) == 0b11010) &&
((MediaOp2Field() & 0b011) == 0b10);
}
// Special accessors that test for existence of a value.
inline bool HasS() const { return SField() == 1; }
inline bool HasB() const { return BField() == 1; }
inline bool HasW() const { return WField() == 1; }
inline bool HasL() const { return LField() == 1; }
inline bool HasSign() const { return SignField() == 1; }
inline bool HasH() const { return HField() == 1; }
inline bool HasLink() const { return LinkField() == 1; }
// Instructions are read out of a code stream. The only way to get a
// reference to an instruction is to convert a pointer. There is no way
// to allocate or create instances of class Instr.
// Use the At(pc) function to create references to Instr.
static Instr* At(uword pc) { return reinterpret_cast<Instr*>(pc); }
private:
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(Instr);
};
// Floating-point reciprocal estimate and step (see pages A2-85 and A2-86 of
// ARM Architecture Reference Manual ARMv7-A edition).
float ReciprocalEstimate(float op);
float ReciprocalStep(float op1, float op2);
// Floating-point reciprocal square root estimate and step (see pages A2-87 to
// A2-90 of ARM Architecture Reference Manual ARMv7-A edition).
float ReciprocalSqrtEstimate(float op);
float ReciprocalSqrtStep(float op1, float op2);
constexpr uword kBreakInstructionFiller = 0xE1200070; // bkpt #0
constexpr uword kDataMemoryBarrier = 0xf57ff050 | 0xb; // dmb ish
struct LinkRegister {
const int32_t code = LR;
};
constexpr bool operator==(Register r, LinkRegister) {
return r == LR;
}
constexpr bool operator!=(Register r, LinkRegister lr) {
return !(r == lr);
}
inline Register ConcreteRegister(LinkRegister) {
return LR;
}
#undef LR
#define LINK_REGISTER (LinkRegister())
} // namespace dart
#endif // RUNTIME_VM_CONSTANTS_ARM_H_