Files
sdk/runtime/vm/compiler/assembler/assembler_arm.h
T
Vyacheslav Egorov 8085a97a63 [vm] Fix JumpToFrame execution state transition
Instead of handling FFI related execution state and safepoint
in assembly handle it in runtime code.

The transition needs to be done before JumpToFrame unwinds
stack because unwinding destroys exit frame and this can't
be done at safepoint as GC might be traversing the stack.

An incorrect order of operation was manifesting as crashes in
GC when one isolate in a group was encountering a lot of
exceptions thrown from an FFI call and another isolate is
triggering GCs.

To catch this in the future added a bit of validation to
ExitSafepoint runtime call which triggers when --use-slow-path
is enabled. Though after refactoring this code does not
trigger this code path anymore because it was completely
removed - but it is better than nothing.

This CL also removes a lot of unnecessary complexity which
was associated with handling this transition in the stub
itself.

TEST=ffi/vmspecific_handle_test

Bug: b/408377905
Cq-Include-Trybots: dart/try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-x64-try,vm-aot-win-debug-x64c-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-arm64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-mac-debug-simarm64_arm64-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-fuchsia-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-arm64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-tsan-linux-release-arm64-try,vm-tsan-linux-release-x64-try,vm-ubsan-linux-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try
Change-Id: Ia073cb6bb9e1b5a0ea8514c7e048cee6019b84d6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420324
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
2025-04-07 05:22:56 -07:00

1832 lines
65 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_COMPILER_ASSEMBLER_ASSEMBLER_ARM_H_
#define RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_ARM_H_
#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif // defined(DART_PRECOMPILED_RUNTIME)
#ifndef RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_H_
#error Do not include assembler_arm.h directly; use assembler.h instead.
#endif
#include <functional>
#include "platform/assert.h"
#include "platform/utils.h"
#include "vm/code_entry_kind.h"
#include "vm/compiler/assembler/assembler_base.h"
#include "vm/compiler/assembler/object_pool_builder.h"
#include "vm/compiler/runtime_api.h"
#include "vm/constants.h"
#include "vm/cpu.h"
#include "vm/hash_map.h"
#include "vm/simulator.h"
namespace dart {
// Forward declarations.
class FlowGraphCompiler;
class RegisterSet;
class RuntimeEntry;
// Load/store multiple addressing mode.
enum BlockAddressMode {
// bit encoding P U W
DA = (0 | 0 | 0) << 21, // decrement after
IA = (0 | 4 | 0) << 21, // increment after
DB = (8 | 0 | 0) << 21, // decrement before
IB = (8 | 4 | 0) << 21, // increment before
DA_W = (0 | 0 | 1) << 21, // decrement after with writeback to base
IA_W = (0 | 4 | 1) << 21, // increment after with writeback to base
DB_W = (8 | 0 | 1) << 21, // decrement before with writeback to base
IB_W = (8 | 4 | 1) << 21 // increment before with writeback to base
};
namespace compiler {
class Immediate : public ValueObject {
public:
explicit Immediate(int32_t value) : value_(value) {}
int32_t value() const { return value_; }
private:
const int32_t value_;
};
// Instruction encoding bits.
constexpr int32_t H = 1 << 5; // halfword (or byte)
constexpr int32_t L = 1 << 20; // load (or store)
constexpr int32_t S = 1 << 20; // set condition code (or leave unchanged)
constexpr int32_t W = 1 << 21; // writeback base register (or leave unchanged)
constexpr int32_t A = 1 << 21; // accumulate in multiply instruction (or not)
constexpr int32_t B = 1 << 22; // unsigned byte (or word)
constexpr int32_t D = 1 << 22; // high/lo bit of start of s/d register range
constexpr int32_t N = 1 << 22; // long (or short)
constexpr int32_t U = 1 << 23; // positive (or negative) offset/index
constexpr int32_t P = 1 << 24; // offset/pre-indexed addressing
// (or post-indexed addressing)
constexpr int32_t I = 1 << 25; // immediate shifter operand (or not)
constexpr int32_t B0 = 1;
constexpr int32_t B1 = 1 << 1;
constexpr int32_t B2 = 1 << 2;
constexpr int32_t B3 = 1 << 3;
constexpr int32_t B4 = 1 << 4;
constexpr int32_t B5 = 1 << 5;
constexpr int32_t B6 = 1 << 6;
constexpr int32_t B7 = 1 << 7;
constexpr int32_t B8 = 1 << 8;
constexpr int32_t B9 = 1 << 9;
constexpr int32_t B10 = 1 << 10;
constexpr int32_t B11 = 1 << 11;
constexpr int32_t B12 = 1 << 12;
constexpr int32_t B13 = 1 << 13;
constexpr int32_t B14 = 1 << 14;
constexpr int32_t B15 = 1 << 15;
constexpr int32_t B16 = 1 << 16;
constexpr int32_t B17 = 1 << 17;
constexpr int32_t B18 = 1 << 18;
constexpr int32_t B19 = 1 << 19;
constexpr int32_t B20 = 1 << 20;
constexpr int32_t B21 = 1 << 21;
constexpr int32_t B22 = 1 << 22;
constexpr int32_t B23 = 1 << 23;
constexpr int32_t B24 = 1 << 24;
constexpr int32_t B25 = 1 << 25;
constexpr int32_t B26 = 1 << 26;
constexpr int32_t B27 = 1 << 27;
class ArmEncode : public AllStatic {
public:
static inline uint32_t Rd(Register rd) {
ASSERT(rd < 16);
return static_cast<uint32_t>(rd) << kRdShift;
}
static inline uint32_t Rm(Register rm) {
ASSERT(rm < 16);
return static_cast<uint32_t>(rm) << kRmShift;
}
static inline uint32_t Rn(Register rn) {
ASSERT(rn < 16);
return static_cast<uint32_t>(rn) << kRnShift;
}
static inline uint32_t Rs(Register rs) {
ASSERT(rs < 16);
return static_cast<uint32_t>(rs) << kRsShift;
}
};
// Encodes Addressing Mode 1 - Data-processing operands.
class Operand : public ValueObject {
public:
// Data-processing operands - Uninitialized.
Operand() : type_(-1), encoding_(-1) {}
// Data-processing operands - Copy constructor.
Operand(const Operand& other)
: ValueObject(), type_(other.type_), encoding_(other.encoding_) {}
// Data-processing operands - Assignment operator.
Operand& operator=(const Operand& other) {
type_ = other.type_;
encoding_ = other.encoding_;
return *this;
}
// Data-processing operands - Immediate.
explicit Operand(uint32_t immediate) {
ASSERT(immediate < (1 << kImmed8Bits));
type_ = 1;
encoding_ = immediate;
}
// Data-processing operands - Rotated immediate.
Operand(uint32_t rotate, uint32_t immed8) {
ASSERT((rotate < (1 << kRotateBits)) && (immed8 < (1 << kImmed8Bits)));
type_ = 1;
encoding_ = (rotate << kRotateShift) | (immed8 << kImmed8Shift);
}
// Data-processing operands - Register.
explicit Operand(Register rm) {
type_ = 0;
encoding_ = static_cast<uint32_t>(rm);
}
// Data-processing operands - Logical shift/rotate by immediate.
Operand(Register rm, Shift shift, uint32_t shift_imm) {
ASSERT(shift_imm < (1 << kShiftImmBits));
type_ = 0;
encoding_ = shift_imm << kShiftImmShift |
static_cast<uint32_t>(shift) << kShiftShift |
static_cast<uint32_t>(rm);
}
// Data-processing operands - Logical shift/rotate by register.
Operand(Register rm, Shift shift, Register rs) {
type_ = 0;
encoding_ = static_cast<uint32_t>(rs) << kShiftRegisterShift |
static_cast<uint32_t>(shift) << kShiftShift | (1 << 4) |
static_cast<uint32_t>(rm);
}
static bool CanHold(uint32_t immediate, Operand* o) {
// Avoid the more expensive test for frequent small immediate values.
if (immediate < (1 << kImmed8Bits)) {
o->type_ = 1;
o->encoding_ = (0 << kRotateShift) | (immediate << kImmed8Shift);
return true;
}
// Note that immediate must be unsigned for the test to work correctly.
for (int rot = 0; rot < 16; rot++) {
uint32_t imm8 = Utils::RotateLeft(immediate, 2 * rot);
if (imm8 < (1 << kImmed8Bits)) {
o->type_ = 1;
o->encoding_ = (rot << kRotateShift) | (imm8 << kImmed8Shift);
return true;
}
}
return false;
}
private:
bool is_valid() const { return (type_ == 0) || (type_ == 1); }
uint32_t type() const {
ASSERT(is_valid());
return type_;
}
uint32_t encoding() const {
ASSERT(is_valid());
return encoding_;
}
uint32_t type_; // Encodes the type field (bits 27-25) in the instruction.
uint32_t encoding_;
friend class Assembler;
friend class Address;
};
class Address : public ValueObject {
public:
enum OffsetKind {
Immediate,
IndexRegister,
ScaledIndexRegister,
};
// Memory operand addressing mode
enum Mode {
kModeMask = (8 | 4 | 1) << 21,
// bit encoding P U W
Offset = (8 | 4 | 0) << 21, // offset (w/o writeback to base)
PreIndex = (8 | 4 | 1) << 21, // pre-indexed addressing with writeback
PostIndex = (0 | 4 | 0) << 21, // post-indexed addressing with writeback
NegOffset = (8 | 0 | 0) << 21, // negative offset (w/o writeback to base)
NegPreIndex = (8 | 0 | 1) << 21, // negative pre-indexed with writeback
NegPostIndex = (0 | 0 | 0) << 21 // negative post-indexed with writeback
};
Address(const Address& other)
: ValueObject(),
encoding_(other.encoding_),
kind_(other.kind_),
base_(other.base_),
offset_(other.offset_) {}
Address& operator=(const Address& other) {
encoding_ = other.encoding_;
kind_ = other.kind_;
base_ = other.base_;
offset_ = other.offset_;
return *this;
}
bool Equals(const Address& other) const {
return (encoding_ == other.encoding_) && (kind_ == other.kind_);
}
explicit Address(Register rn, int32_t offset = 0, Mode am = Offset) {
kind_ = Immediate;
base_ = rn;
offset_ = offset;
// If the offset can't be encoded in fewer bits, then it'll conflict with
// the encoding of the mode and we won't be able to retrieve it later.
ASSERT(Utils::MagnitudeIsUint(kOpcodeShift, offset));
if (offset < 0) {
encoding_ = (am ^ (1 << kUShift)) | -offset; // Flip U to adjust sign.
} else {
encoding_ = am | offset;
}
encoding_ |= ArmEncode::Rn(rn);
}
// There is no register offset mode unless Mode is Offset, in which case the
// shifted register case below should be used.
Address(Register rn, Register r, Mode am);
Address(Register rn,
Register rm,
Shift shift = LSL,
uint32_t shift_imm = 0,
Mode am = Offset) {
Operand o(rm, shift, shift_imm);
if ((shift == LSL) && (shift_imm == 0)) {
kind_ = IndexRegister;
} else {
kind_ = ScaledIndexRegister;
}
encoding_ = o.encoding() | am | ArmEncode::Rn(rn);
}
// There is no shifted register mode with a register shift.
Address(Register rn, Register rm, Shift shift, Register r, Mode am = Offset);
static OperandSize OperandSizeFor(intptr_t cid);
static bool CanHoldLoadOffset(OperandSize size,
int32_t offset,
int32_t* offset_mask);
static bool CanHoldStoreOffset(OperandSize size,
int32_t offset,
int32_t* offset_mask);
static bool CanHoldImmediateOffset(bool is_load,
intptr_t cid,
int64_t offset);
private:
Register rn() const {
return Instr::At(reinterpret_cast<uword>(&encoding_))->RnField();
}
Register rm() const {
return ((kind() == IndexRegister) || (kind() == ScaledIndexRegister))
? Instr::At(reinterpret_cast<uword>(&encoding_))->RmField()
: kNoRegister;
}
Mode mode() const { return static_cast<Mode>(encoding_ & kModeMask); }
bool has_writeback() const {
return (mode() == PreIndex) || (mode() == PostIndex) ||
(mode() == NegPreIndex) || (mode() == NegPostIndex);
}
static bool has_writeback(BlockAddressMode am) {
switch (am) {
case DA:
case IA:
case DB:
case IB:
return false;
case DA_W:
case IA_W:
case DB_W:
case IB_W:
return true;
default:
UNREACHABLE();
return false;
}
}
uint32_t encoding() const {
ASSERT_IMPLIES(kind_ == Immediate, Utils::MagnitudeIsUint(12, offset_));
return encoding_;
}
// Encoding for addressing mode 3.
uint32_t encoding3() const;
// Encoding for vfp load/store addressing.
uint32_t vencoding() const;
OffsetKind kind() const { return kind_; }
Register base() const { return base_; }
int32_t offset() const { return offset_; }
uint32_t encoding_;
OffsetKind kind_;
Register base_ = kNoRegister;
int32_t offset_ = 0;
friend class Assembler;
};
class FieldAddress : public Address {
public:
FieldAddress(Register base, int32_t disp)
: Address(base, disp - kHeapObjectTag) {}
// This addressing mode does not exist.
FieldAddress(Register base, Register r);
FieldAddress(const FieldAddress& other) : Address(other) {}
FieldAddress& operator=(const FieldAddress& other) {
Address::operator=(other);
return *this;
}
};
class Assembler : public AssemblerBase {
public:
explicit Assembler(ObjectPoolBuilder* object_pool_builder,
intptr_t far_branch_level = 0);
~Assembler() {}
void PushRegister(Register r) { Push(r); }
void PopRegister(Register r) { Pop(r); }
// Push two registers to the stack; r0 to lower address location.
void PushRegisterPair(Register r0, Register r1) {
if ((r0 < r1) && (r0 != SP) && (r1 != SP)) {
RegList reg_list = (1 << r0) | (1 << r1);
PushList(reg_list);
} else {
PushRegister(r1);
PushRegister(r0);
}
}
// Pop two registers from the stack; r0 from lower address location.
void PopRegisterPair(Register r0, Register r1) {
if ((r0 < r1) && (r0 != SP) && (r1 != SP)) {
RegList reg_list = (1 << r0) | (1 << r1);
PopList(reg_list);
} else {
PopRegister(r0);
PopRegister(r1);
}
}
void PushValueAtOffset(Register base, int32_t offset) { UNIMPLEMENTED(); }
void Bind(Label* label) override;
// Unconditional jump to a given label. [distance] is ignored on ARM.
void Jump(Label* label, JumpDistance distance = kFarJump) { b(label); }
// Unconditional jump to a given address in register.
void Jump(Register target) { bx(target); }
// Unconditional jump to a given address in memory.
void Jump(const Address& address) { Branch(address); }
void LoadMemoryValue(Register dst, Register base, int32_t offset) {
LoadFromOffset(dst, base, offset);
}
void StoreMemoryValue(Register src, Register base, int32_t offset) {
StoreToOffset(src, base, offset);
}
void LoadAcquire(Register dst,
const Address& address,
OperandSize size = kFourBytes) override {
Load(dst, address, size);
dmb();
}
void StoreRelease(Register src,
const Address& address,
OperandSize size = kFourBytes) override {
dmb();
Store(src, address, size);
}
void CompareWithMemoryValue(Register value,
Address address,
OperandSize size = kFourBytes) override {
ASSERT_EQUAL(size, kFourBytes);
Load(TMP, address, size);
cmp(value, Operand(TMP));
}
// Misc. functionality
bool use_far_branches() const {
return FLAG_use_far_branches || use_far_branches_;
}
#if defined(TESTING) || defined(DEBUG)
// Used in unit tests and to ensure predictable verification code size in
// FlowGraphCompiler::EmitEdgeCounter.
void set_use_far_branches(bool b) { use_far_branches_ = b; }
#endif // TESTING || DEBUG
// Debugging and bringup support.
void Breakpoint() override { bkpt(0); }
// Data-processing instructions.
void and_(Register rd, Register rn, Operand o, Condition cond = AL);
void ands(Register rd, Register rn, Operand o, Condition cond = AL);
void eor(Register rd, Register rn, Operand o, Condition cond = AL);
void sub(Register rd, Register rn, Operand o, Condition cond = AL);
void subs(Register rd, Register rn, Operand o, Condition cond = AL);
void rsb(Register rd, Register rn, Operand o, Condition cond = AL);
void rsbs(Register rd, Register rn, Operand o, Condition cond = AL);
void add(Register rd, Register rn, Operand o, Condition cond = AL);
void adds(Register rd, Register rn, Operand o, Condition cond = AL);
void adc(Register rd, Register rn, Operand o, Condition cond = AL);
void adcs(Register rd, Register rn, Operand o, Condition cond = AL);
void sbc(Register rd, Register rn, Operand o, Condition cond = AL);
void sbcs(Register rd, Register rn, Operand o, Condition cond = AL);
void rsc(Register rd, Register rn, Operand o, Condition cond = AL);
void tst(Register rn, Operand o, Condition cond = AL);
void teq(Register rn, Operand o, Condition cond = AL);
void cmp(Register rn, Operand o, Condition cond = AL);
void cmn(Register rn, Operand o, Condition cond = AL);
void orr(Register rd, Register rn, Operand o, Condition cond = AL);
void orrs(Register rd, Register rn, Operand o, Condition cond = AL);
void mov(Register rd, Operand o, Condition cond = AL);
void movs(Register rd, Operand o, Condition cond = AL);
void bic(Register rd, Register rn, Operand o, Condition cond = AL);
void bics(Register rd, Register rn, Operand o, Condition cond = AL);
void mvn_(Register rd, Operand o, Condition cond = AL);
void mvns(Register rd, Operand o, Condition cond = AL);
// Miscellaneous data-processing instructions.
void clz(Register rd, Register rm, Condition cond = AL);
void rbit(Register rd, Register rm, Condition cond = AL);
// Multiply instructions.
void mul(Register rd, Register rn, Register rm, Condition cond = AL);
void muls(Register rd, Register rn, Register rm, Condition cond = AL);
void mla(Register rd,
Register rn,
Register rm,
Register ra,
Condition cond = AL);
void mls(Register rd,
Register rn,
Register rm,
Register ra,
Condition cond = AL);
void smull(Register rd_lo,
Register rd_hi,
Register rn,
Register rm,
Condition cond = AL);
void umull(Register rd_lo,
Register rd_hi,
Register rn,
Register rm,
Condition cond = AL);
void smlal(Register rd_lo,
Register rd_hi,
Register rn,
Register rm,
Condition cond = AL);
void umlal(Register rd_lo,
Register rd_hi,
Register rn,
Register rm,
Condition cond = AL);
// Emulation of this instruction uses IP and the condition codes. Therefore,
// none of the registers can be IP, and the instruction can only be used
// unconditionally.
void umaal(Register rd_lo, Register rd_hi, Register rn, Register rm);
// Division instructions.
void sdiv(Register rd, Register rn, Register rm, Condition cond = AL);
void udiv(Register rd, Register rn, Register rm, Condition cond = AL);
// Load/store instructions.
void ldr(Register rd, Address ad, Condition cond = AL);
void str(Register rd, Address ad, Condition cond = AL);
void ldrb(Register rd, Address ad, Condition cond = AL);
void strb(Register rd, Address ad, Condition cond = AL);
void ldrh(Register rd, Address ad, Condition cond = AL);
void strh(Register rd, Address ad, Condition cond = AL);
void ldrsb(Register rd, Address ad, Condition cond = AL);
void ldrsh(Register rd, Address ad, Condition cond = AL);
// ldrd and strd actually support the full range of addressing modes, but
// we don't use them, so we only support the base + offset mode.
// rd must be an even register and rd2 must be rd + 1.
void ldrd(Register rd,
Register rd2,
Register rn,
int32_t offset,
Condition cond = AL);
void strd(Register rd,
Register rd2,
Register rn,
int32_t offset,
Condition cond = AL);
void ldm(BlockAddressMode am,
Register base,
RegList regs,
Condition cond = AL);
void stm(BlockAddressMode am,
Register base,
RegList regs,
Condition cond = AL);
void ldrex(Register rd, Register rn, Condition cond = AL);
void strex(Register rd, Register rt, Register rn, Condition cond = AL);
void dmb();
// Media instructions.
void sbfx(Register rd,
Register rn,
int32_t lsb,
int32_t width,
Condition cond = AL);
void ubfx(Register rd,
Register rn,
int32_t lsb,
int32_t width,
Condition cond = AL);
// Emit code to transition between generated and native modes.
//
// These require that CSP and SP are equal and aligned and require two scratch
// registers (in addition to TMP).
void TransitionGeneratedToNative(Register destination_address,
Register exit_frame_fp,
Register exit_through_ffi,
Register scratch0,
bool enter_safepoint);
void TransitionNativeToGenerated(Register scratch0,
Register scratch1,
bool exit_safepoint,
bool set_tag = true);
void VerifyInGenerated(Register scratch);
void VerifyNotInGenerated(Register scratch);
void EnterFullSafepoint(Register scratch0, Register scratch1);
void ExitFullSafepoint(Register scratch0, Register scratch1);
// Miscellaneous instructions.
void clrex();
void nop(Condition cond = AL);
// Note that gdb sets breakpoints using the undefined instruction 0xe7f001f0.
void bkpt(uint16_t imm16);
static int32_t BkptEncoding(uint16_t imm16) {
// bkpt requires that the cond field is AL.
return (AL << kConditionShift) | B24 | B21 | ((imm16 >> 4) << 8) | B6 | B5 |
B4 | (imm16 & 0xf);
}
// Floating point instructions (VFPv3-D16 and VFPv3-D32 profiles).
void vmovsr(SRegister sn, Register rt, Condition cond = AL);
void vmovrs(Register rt, SRegister sn, Condition cond = AL);
void vmovsrr(SRegister sm, Register rt, Register rt2, Condition cond = AL);
void vmovrrs(Register rt, Register rt2, SRegister sm, Condition cond = AL);
void vmovdrr(DRegister dm, Register rt, Register rt2, Condition cond = AL);
void vmovrrd(Register rt, Register rt2, DRegister dm, Condition cond = AL);
void vmovdr(DRegister dd, int i, Register rt, Condition cond = AL);
void vmovs(SRegister sd, SRegister sm, Condition cond = AL);
void vmovd(DRegister dd, DRegister dm, Condition cond = AL);
void vmovq(QRegister qd, QRegister qm);
// Returns false if the immediate cannot be encoded.
bool vmovs(SRegister sd, float s_imm, Condition cond = AL);
bool vmovd(DRegister dd, double d_imm, Condition cond = AL);
void vldrs(SRegister sd, Address ad, Condition cond = AL);
void vstrs(SRegister sd, Address ad, Condition cond = AL);
void vldrd(DRegister dd, Address ad, Condition cond = AL);
void vstrd(DRegister dd, Address ad, Condition cond = AL);
void vldms(BlockAddressMode am,
Register base,
SRegister first,
SRegister last,
Condition cond = AL);
void vstms(BlockAddressMode am,
Register base,
SRegister first,
SRegister last,
Condition cond = AL);
void vldmd(BlockAddressMode am,
Register base,
DRegister first,
intptr_t count,
Condition cond = AL);
void vstmd(BlockAddressMode am,
Register base,
DRegister first,
intptr_t count,
Condition cond = AL);
void vadds(SRegister sd, SRegister sn, SRegister sm, Condition cond = AL);
void vaddd(DRegister dd, DRegister dn, DRegister dm, Condition cond = AL);
void vaddqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vaddqs(QRegister qd, QRegister qn, QRegister qm);
void vsubs(SRegister sd, SRegister sn, SRegister sm, Condition cond = AL);
void vsubd(DRegister dd, DRegister dn, DRegister dm, Condition cond = AL);
void vsubqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vsubqs(QRegister qd, QRegister qn, QRegister qm);
void vmuls(SRegister sd, SRegister sn, SRegister sm, Condition cond = AL);
void vmuld(DRegister dd, DRegister dn, DRegister dm, Condition cond = AL);
void vmulqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vmulqs(QRegister qd, QRegister qn, QRegister qm);
void vshlqi(OperandSize sz, QRegister qd, QRegister qm, QRegister qn);
void vshlqu(OperandSize sz, QRegister qd, QRegister qm, QRegister qn);
void vmlas(SRegister sd, SRegister sn, SRegister sm, Condition cond = AL);
void vmlad(DRegister dd, DRegister dn, DRegister dm, Condition cond = AL);
void vmlss(SRegister sd, SRegister sn, SRegister sm, Condition cond = AL);
void vmlsd(DRegister dd, DRegister dn, DRegister dm, Condition cond = AL);
void vdivs(SRegister sd, SRegister sn, SRegister sm, Condition cond = AL);
void vdivd(DRegister dd, DRegister dn, DRegister dm, Condition cond = AL);
void vminqs(QRegister qd, QRegister qn, QRegister qm);
void vmaxqs(QRegister qd, QRegister qn, QRegister qm);
void vrecpeqs(QRegister qd, QRegister qm);
void vrecpsqs(QRegister qd, QRegister qn, QRegister qm);
void vrsqrteqs(QRegister qd, QRegister qm);
void vrsqrtsqs(QRegister qd, QRegister qn, QRegister qm);
void veorq(QRegister qd, QRegister qn, QRegister qm);
void vorrq(QRegister qd, QRegister qn, QRegister qm);
void vornq(QRegister qd, QRegister qn, QRegister qm);
void vandq(QRegister qd, QRegister qn, QRegister qm);
void vmvnq(QRegister qd, QRegister qm);
void vceqqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vceqqs(QRegister qd, QRegister qn, QRegister qm);
void vcgeqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vcugeqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vcgeqs(QRegister qd, QRegister qn, QRegister qm);
void vcgtqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vcugtqi(OperandSize sz, QRegister qd, QRegister qn, QRegister qm);
void vcgtqs(QRegister qd, QRegister qn, QRegister qm);
void vabss(SRegister sd, SRegister sm, Condition cond = AL);
void vabsd(DRegister dd, DRegister dm, Condition cond = AL);
void vabsqs(QRegister qd, QRegister qm);
void vnegs(SRegister sd, SRegister sm, Condition cond = AL);
void vnegd(DRegister dd, DRegister dm, Condition cond = AL);
void vnegqs(QRegister qd, QRegister qm);
void vsqrts(SRegister sd, SRegister sm, Condition cond = AL);
void vsqrtd(DRegister dd, DRegister dm, Condition cond = AL);
void vcvtsd(SRegister sd, DRegister dm, Condition cond = AL);
void vcvtds(DRegister dd, SRegister sm, Condition cond = AL);
void vcvtis(SRegister sd, SRegister sm, Condition cond = AL);
void vcvtid(SRegister sd, DRegister dm, Condition cond = AL);
void vcvtsi(SRegister sd, SRegister sm, Condition cond = AL);
void vcvtdi(DRegister dd, SRegister sm, Condition cond = AL);
void vcvtus(SRegister sd, SRegister sm, Condition cond = AL);
void vcvtud(SRegister sd, DRegister dm, Condition cond = AL);
void vcvtsu(SRegister sd, SRegister sm, Condition cond = AL);
void vcvtdu(DRegister dd, SRegister sm, Condition cond = AL);
void vcmps(SRegister sd, SRegister sm, Condition cond = AL);
void vcmpd(DRegister dd, DRegister dm, Condition cond = AL);
void vcmpsz(SRegister sd, Condition cond = AL);
void vcmpdz(DRegister dd, Condition cond = AL);
void vmrs(Register rd, Condition cond = AL);
void vmstat(Condition cond = AL);
// Duplicates the operand of size sz at index idx from dm to all elements of
// qd. This is a special case of vtbl.
void vdup(OperandSize sz, QRegister qd, DRegister dm, int idx);
// Each byte of dm is an index into the table of bytes formed by concatenating
// a list of 'length' registers starting with dn. The result is placed in dd.
void vtbl(DRegister dd, DRegister dn, int length, DRegister dm);
// The words of qd and qm are interleaved with the low words of the result
// in qd and the high words in qm.
void vzipqw(QRegister qd, QRegister qm);
// Branch instructions.
void b(Label* label, Condition cond = AL);
void bl(Label* label, Condition cond = AL);
void bx(Register rm, Condition cond = AL);
void blx(Register rm, Condition cond = AL);
void Branch(const Address& address, Condition cond = AL);
void BranchLink(const Code& code,
ObjectPoolBuilderEntry::Patchability patchable =
ObjectPoolBuilderEntry::kNotPatchable,
CodeEntryKind entry_kind = CodeEntryKind::kNormal,
ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
ObjectPoolBuilderEntry::kSnapshotable);
// Branch and link to an entry address. Call sequence can be patched.
void BranchLinkPatchable(
const Code& code,
CodeEntryKind entry_kind = CodeEntryKind::kNormal,
ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
ObjectPoolBuilderEntry::kSnapshotable);
// Emit a call that shares its object pool entries with other calls
// that have the same equivalence marker.
void BranchLinkWithEquivalence(
const Code& code,
const Object& equivalence,
CodeEntryKind entry_kind = CodeEntryKind::kNormal);
// Branch and link to [base + offset]. Call sequence is never patched.
void BranchLinkOffset(Register base, int32_t offset);
void Call(Address target, Condition cond = AL) {
// CLOBBERS_LR uses __ to access the assembler.
#define __ this->
CLOBBERS_LR({
ldr(LR, target, cond);
blx(LR, cond);
});
#undef __
}
void Call(const Code& code) { BranchLink(code); }
void CallCFunction(Address target) { Call(target); }
void CallCFunction(Register target, Condition cond = AL) {
blx(target, cond);
}
// Add signed immediate value to rd. May clobber IP.
void AddImmediate(Register rd, int32_t value, Condition cond = AL) {
AddImmediate(rd, rd, value, cond);
}
// Add signed immediate value. May clobber IP.
void AddImmediate(Register rd,
Register rn,
int32_t value,
Condition cond = AL);
void AddImmediateSetFlags(Register rd,
Register rn,
int32_t value,
Condition cond = AL);
void AddRegisters(Register dest, Register src) {
add(dest, dest, Operand(src));
}
void AddScaled(Register dest,
Register base,
Register index,
ScaleFactor scale,
int32_t disp) override {
if (base == kNoRegister) {
if (scale == TIMES_1) {
AddImmediate(dest, index, disp);
} else {
Lsl(dest, index, Operand(scale));
AddImmediate(dest, disp);
}
} else {
add(dest, base, compiler::Operand(index, LSL, scale));
AddImmediate(dest, disp);
}
}
void SubImmediate(Register rd,
Register rn,
int32_t value,
Condition cond = AL);
void SubImmediateSetFlags(Register rd,
Register rn,
int32_t value,
Condition cond = AL);
void SubRegisters(Register dest, Register src) {
sub(dest, dest, Operand(src));
}
void MulImmediate(Register reg,
int32_t imm,
OperandSize width = kFourBytes) override {
MulImmediate(reg, reg, imm, width);
}
void MulImmediate(Register rd,
Register rn,
int32_t imm,
OperandSize width = kFourBytes) {
ASSERT(width == kFourBytes);
if (Utils::IsPowerOfTwo(imm)) {
LslImmediate(rd, rn, Utils::ShiftForPowerOfTwo(imm));
} else {
LoadImmediate(TMP, imm);
mul(rd, rn, TMP);
}
}
void AndImmediate(Register rd,
Register rs,
int32_t imm,
OperandSize sz,
Condition cond);
void AndImmediate(Register rd, Register rs, int32_t imm, Condition cond) {
AndImmediate(rd, rs, imm, kFourBytes, cond);
}
void AndImmediate(Register rd,
Register rs,
int32_t imm,
OperandSize sz = kFourBytes) override {
AndImmediate(rd, rs, imm, sz, AL);
}
void AndImmediate(Register rd, int32_t imm, OperandSize sz, Condition cond) {
AndImmediate(rd, rd, imm, sz, cond);
}
void AndImmediate(Register rd, int32_t imm, Condition cond) {
AndImmediate(rd, rd, imm, kFourBytes, cond);
}
void AndImmediate(Register rd,
int32_t imm,
OperandSize sz = kFourBytes) override {
AndImmediate(rd, imm, sz, AL);
}
void AndImmediateSetFlags(Register rd,
Register rn,
int32_t value,
Condition cond = AL);
void AndRegisters(Register dst,
Register src1,
Register src2 = kNoRegister) override {
ASSERT(src1 != src2); // Likely a mistake.
if (src2 == kNoRegister) {
src2 = dst;
}
and_(dst, src2, Operand(src1));
}
void OrImmediate(Register rd, Register rs, int32_t imm, Condition cond = AL);
void OrImmediate(Register rd, int32_t imm, Condition cond = AL) {
OrImmediate(rd, rd, imm, cond);
}
void XorImmediate(Register rd, Register rn, int32_t imm, Condition cond = AL);
void LslImmediate(Register rd,
Register rn,
int32_t shift,
OperandSize sz = kFourBytes) override {
ASSERT(sz == kFourBytes || sz == kUnsignedFourBytes);
ASSERT((shift >= 0) && (shift < OperandSizeInBits(sz)));
if (shift != 0) {
Lsl(rd, rn, Operand(shift));
} else {
MoveRegister(rd, rn);
}
}
void LslImmediate(Register rd,
int32_t shift,
OperandSize sz = kFourBytes) override {
LslImmediate(rd, rd, shift, sz);
}
void LslRegister(Register dst, Register shift) override {
Lsl(dst, dst, shift);
}
void LsrImmediate(Register rd, Register rn, int32_t shift) {
ASSERT((shift >= 0) && (shift < kBitsPerInt32));
if (shift != 0) {
Lsr(rd, rn, Operand(shift));
} else {
MoveRegister(rd, rn);
}
}
void LsrImmediate(Register rd, int32_t shift) override {
LsrImmediate(rd, rd, shift);
}
// Test rn and immediate. May clobber IP.
void TestImmediate(Register rn, int32_t imm, Condition cond = AL);
// Compare rn with signed immediate value. May clobber IP.
void CompareImmediate(Register rn, int32_t value, Condition cond);
void CompareImmediate(Register rn,
int32_t value,
OperandSize width = kFourBytes) override {
ASSERT_EQUAL(width, kFourBytes);
CompareImmediate(rn, value, AL);
}
// Signed integer division of left by right. Checks to see if integer
// division is supported. If not, uses the FPU for division with
// temporary registers tmpl and tmpr. tmpl and tmpr must be different
// registers.
void IntegerDivide(Register result,
Register left,
Register right,
DRegister tmpl,
DRegister tmpr);
// Load and Store.
// These three do not clobber IP.
void LoadPatchableImmediate(Register rd, int32_t value, Condition cond = AL);
void LoadDecodableImmediate(Register rd, int32_t value, Condition cond = AL);
void LoadImmediate(Register rd, Immediate value, Condition cond = AL);
void LoadImmediate(Register rd, int32_t value, Condition cond);
void LoadImmediate(Register rd, int32_t value) override {
LoadImmediate(rd, value, AL);
}
// These two may clobber IP.
void LoadSImmediate(SRegister sd, float value, Condition cond = AL);
void LoadDImmediate(DRegister dd,
double value,
Register scratch,
Condition cond = AL);
void LoadQImmediate(QRegister dd, simd128_value_t value);
void MarkExceptionHandler(Label* label);
void Drop(intptr_t stack_elements);
void RestoreCodePointer();
void LoadPoolPointer(Register reg = PP);
void SetupGlobalPoolAndDispatchTable();
void LoadIsolate(Register rd);
void LoadIsolateGroup(Register dst);
// Load word from pool from the given index using encoding that
// InstructionPattern::DecodeLoadWordFromPool can decode.
void LoadWordFromPoolIndex(Register rd,
intptr_t index,
Register pp = PP,
Condition cond = AL);
// Store word to pool at the given offset.
//
// Note: clobbers TMP.
void StoreWordToPoolIndex(Register value,
intptr_t index,
Register pp = PP,
Condition cond = AL);
void LoadObject(Register rd, const Object& object, Condition cond = AL);
void LoadUniqueObject(
Register rd,
const Object& object,
Condition cond = AL,
ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
ObjectPoolBuilderEntry::kSnapshotable);
void LoadNativeEntry(Register dst,
const ExternalLabel* label,
ObjectPoolBuilderEntry::Patchability patchable,
Condition cond = AL);
void PushObject(const Object& object);
void PushImmediate(int32_t immediate) {
LoadImmediate(TMP, immediate);
Push(TMP);
}
void CompareObject(Register rn, const Object& object);
void InitializeHeader(Register tags, Register object) {
str(tags, FieldAddress(object, target::Object::tags_offset()));
#if defined(TARGET_HAS_FAST_WRITE_WRITE_FENCE)
dmb();
#endif
}
void InitializeHeaderUntagged(Register tags, Register object) {
str(tags, Address(object, target::Object::tags_offset()));
#if defined(TARGET_HAS_FAST_WRITE_WRITE_FENCE)
dmb();
#endif
}
void StoreObjectIntoObjectNoBarrier(
Register object,
const Address& dest,
const Object& value,
MemoryOrder memory_order = kRelaxedNonAtomic,
OperandSize size = kWordBytes) override;
void StoreBarrier(Register object,
Register value,
CanBeSmi can_be_smi,
Register scratch) override;
void ArrayStoreBarrier(Register object,
Register slot,
Register value,
CanBeSmi can_be_smi,
Register scratch) override;
void VerifyStoreNeedsNoWriteBarrier(Register object, Register value) override;
// Stores a non-tagged value into a heap object.
void StoreInternalPointer(Register object,
const Address& dest,
Register value);
// Store value_even, value_odd, value_even, ... into the words in the address
// range [begin, end), assumed to be uninitialized fields in object (tagged).
// The stores must not need a generational store barrier (e.g., smi/null),
// and (value_even, value_odd) must be a valid register pair.
// Destroys register 'begin'.
void InitializeFieldsNoBarrier(Register object,
Register begin,
Register end,
Register value_even,
Register value_odd);
// Like above, for the range [base+begin_offset, base+end_offset), unrolled.
void InitializeFieldsNoBarrierUnrolled(Register object,
Register base,
intptr_t begin_offset,
intptr_t end_offset,
Register value_even,
Register value_odd);
// Stores a Smi value into a heap object field that always contains a Smi.
void StoreIntoSmiField(const Address& dest, Register value);
void ExtractClassIdFromTags(Register result,
Register tags,
Condition cond = AL);
void ExtractInstanceSizeFromTags(Register result, Register tags);
void RangeCheck(Register value,
Register temp,
intptr_t low,
intptr_t high,
RangeCheckCondition condition,
Label* target) override;
void LoadClassId(Register result, Register object, Condition cond = AL);
void LoadClassById(Register result, Register class_id);
void CompareClassId(Register object, intptr_t class_id, Register scratch);
void LoadClassIdMayBeSmi(Register result, Register object);
void LoadTaggedClassIdMayBeSmi(Register result, Register object);
void EnsureHasClassIdInDEBUG(intptr_t cid,
Register src,
Register scratch,
bool can_be_null = false) override;
bool CanLoadFromObjectPool(const Object& object) const;
Address PrepareLargeLoadOffset(const Address& addr,
OperandSize sz,
Condition cond);
Address PrepareLargeStoreOffset(const Address& addr,
OperandSize sz,
Condition cond);
void Load(Register reg,
const Address& address,
OperandSize type,
Condition cond);
void Load(Register reg,
const Address& address,
OperandSize type = kFourBytes) override {
Load(reg, address, type, AL);
}
void LoadFromOffset(Register reg,
Register base,
int32_t offset,
OperandSize type = kFourBytes) override {
LoadFromOffset(reg, base, offset, type, AL);
}
void LoadFromOffset(Register reg,
Register base,
int32_t offset,
OperandSize type,
Condition cond) {
Load(reg, Address(base, offset), type, cond);
}
void LoadFieldFromOffset(Register reg,
Register base,
int32_t offset,
OperandSize type = kFourBytes) override {
LoadFieldFromOffset(reg, base, offset, type, AL);
}
void LoadFieldFromOffset(Register reg,
Register base,
int32_t offset,
OperandSize type,
Condition cond) {
Load(reg, FieldAddress(base, offset), type, cond);
}
// For loading indexed payloads out of tagged objects like Arrays. If the
// payload objects are word-sized, use TIMES_HALF_WORD_SIZE if the contents of
// [index] is a Smi, otherwise TIMES_WORD_SIZE if unboxed.
void LoadIndexedPayload(Register dst,
Register base,
int32_t payload_start,
Register index,
ScaleFactor scale,
OperandSize type = kFourBytes) override {
add(dst, base, Operand(index, LSL, scale));
LoadFromOffset(dst, dst, payload_start - kHeapObjectTag, type);
}
void LoadFromStack(Register dst, intptr_t depth);
void StoreToStack(Register src, intptr_t depth);
void CompareToStack(Register src, intptr_t depth);
void Store(Register reg,
const Address& address,
OperandSize type,
Condition cond);
void Store(Register reg,
const Address& address,
OperandSize type = kFourBytes) override {
Store(reg, address, type, AL);
}
void StoreToOffset(Register reg,
Register base,
int32_t offset,
OperandSize type = kFourBytes) override {
StoreToOffset(reg, base, offset, type, AL);
}
void StoreToOffset(Register reg,
Register base,
int32_t offset,
OperandSize type,
Condition cond) {
Store(reg, Address(base, offset), type, cond);
}
void StoreFieldToOffset(Register reg,
Register base,
int32_t offset,
OperandSize type = kFourBytes) override {
StoreFieldToOffset(reg, base, offset, type, AL);
}
void StoreFieldToOffset(Register reg,
Register base,
int32_t offset,
OperandSize type,
Condition cond) {
Store(reg, FieldAddress(base, offset), type, cond);
}
void StoreZero(const Address& address, Register temp) {
mov(temp, Operand(0));
Store(temp, address);
}
void LoadSFromOffset(SRegister reg,
Register base,
int32_t offset,
Condition cond = AL);
void StoreSToOffset(SRegister reg,
Register base,
int32_t offset,
Condition cond = AL);
void LoadDFromOffset(DRegister reg,
Register base,
int32_t offset,
Condition cond = AL);
void StoreDToOffset(DRegister reg,
Register base,
int32_t offset,
Condition cond = AL);
void LoadUnboxedDouble(FpuRegister dst, Register base, int32_t offset) {
LoadDFromOffset(EvenDRegisterOf(dst), base, offset);
}
void StoreUnboxedDouble(FpuRegister src, Register base, int32_t offset) {
StoreDToOffset(EvenDRegisterOf(src), base, offset);
}
void MoveUnboxedDouble(FpuRegister dst, FpuRegister src) {
if (src != dst) {
vmovd(EvenDRegisterOf(dst), EvenDRegisterOf(src));
}
}
void LoadMultipleDFromOffset(DRegister first,
intptr_t count,
Register base,
int32_t offset);
void StoreMultipleDToOffset(DRegister first,
intptr_t count,
Register base,
int32_t offset);
void LoadUnboxedSimd128(FpuRegister dst, Register base, int32_t offset) {
LoadMultipleDFromOffset(EvenDRegisterOf(dst), 2, base, offset);
}
void StoreUnboxedSimd128(FpuRegister src, Register base, int32_t offset) {
StoreMultipleDToOffset(EvenDRegisterOf(src), 2, base, offset);
}
void MoveUnboxedSimd128(FpuRegister dst, FpuRegister src) {
if (src != dst) {
vmovq(dst, src);
}
}
void Push(Register rd, Condition cond = AL);
void Pop(Register rd, Condition cond = AL);
void PushList(RegList regs, Condition cond = AL);
void PopList(RegList regs, Condition cond = AL);
void PushQuad(FpuRegister rd, Condition cond = AL);
void PopQuad(FpuRegister rd, Condition cond = AL);
void PushRegisters(const RegisterSet& regs);
void PopRegisters(const RegisterSet& regs);
void PushRegistersInOrder(std::initializer_list<Register> regs);
// Push all registers which are callee-saved according to the ARM ABI.
void PushNativeCalleeSavedRegisters();
// Pop all registers which are callee-saved according to the ARM ABI.
void PopNativeCalleeSavedRegisters();
void CompareRegisters(Register rn, Register rm) { cmp(rn, Operand(rm)); }
void CompareObjectRegisters(Register rn, Register rm) {
CompareRegisters(rn, rm);
}
// Branches to the given label if the condition holds.
// [distance] is ignored on ARM.
void BranchIf(Condition condition,
Label* label,
JumpDistance distance = kFarJump) {
b(label, condition);
}
void BranchIfZero(Register rn,
Label* label,
JumpDistance distance = kFarJump) {
cmp(rn, Operand(0));
b(label, ZERO);
}
void BranchIfBit(Register rn,
intptr_t bit_number,
Condition condition,
Label* label,
JumpDistance distance = kFarJump) {
tst(rn, Operand(1 << bit_number));
b(label, condition);
}
void MoveRegister(Register rd, Register rm, Condition cond) {
ExtendValue(rd, rm, kFourBytes, cond);
}
void MoveRegister(Register rd, Register rm) override {
MoveRegister(rd, rm, AL);
}
void MoveAndSmiTagRegister(Register rd, Register rm, Condition cond) {
ExtendAndSmiTagValue(rd, rm, kFourBytes, cond);
}
void MoveAndSmiTagRegister(Register rd, Register rm) override {
MoveAndSmiTagRegister(rd, rm, AL);
}
void ExtendValue(Register rd, Register rm, OperandSize sz, Condition cond);
void ExtendValue(Register rd, Register rm, OperandSize sz) override {
ExtendValue(rd, rm, sz, AL);
}
void ExtendAndSmiTagValue(Register rd,
Register rm,
OperandSize sz,
Condition cond) {
ExtendValue(rd, rm, sz, cond);
SmiTag(rd, cond);
}
void ExtendAndSmiTagValue(Register rd,
Register rm,
OperandSize sz = kFourBytes) override {
ExtendAndSmiTagValue(rd, rm, sz, AL);
}
// Convenience shift instructions. Use mov instruction with shifter operand
// for variants setting the status flags.
void Lsl(Register rd,
Register rm,
const Operand& shift_imm,
Condition cond = AL);
void Lsl(Register rd, Register rm, Register rs, Condition cond = AL);
void Lsr(Register rd,
Register rm,
const Operand& shift_imm,
Condition cond = AL);
void Lsr(Register rd, Register rm, Register rs, Condition cond = AL);
void Asr(Register rd,
Register rm,
const Operand& shift_imm,
Condition cond = AL);
void Asr(Register rd, Register rm, Register rs, Condition cond = AL);
void Asrs(Register rd,
Register rm,
const Operand& shift_imm,
Condition cond = AL);
void Ror(Register rd,
Register rm,
const Operand& shift_imm,
Condition cond = AL);
void Ror(Register rd, Register rm, Register rs, Condition cond = AL);
void Rrx(Register rd, Register rm, Condition cond = AL);
// Fill rd with the sign of rm.
void SignFill(Register rd, Register rm, Condition cond = AL);
void Vreciprocalqs(QRegister qd, QRegister qm);
void VreciprocalSqrtqs(QRegister qd, QRegister qm);
// If qm must be preserved, then provide a (non-QTMP) temporary.
void Vsqrtqs(QRegister qd, QRegister qm, QRegister temp);
void Vdivqs(QRegister qd, QRegister qn, QRegister qm);
void SmiTag(Register reg, Condition cond) { SmiTag(reg, reg, cond); }
void SmiTag(Register reg) override { SmiTag(reg, AL); }
void SmiTag(Register dst, Register src, Condition cond = AL) {
Lsl(dst, src, Operand(kSmiTagSize), cond);
}
void SmiUntag(Register reg, Condition cond = AL) { SmiUntag(reg, reg, cond); }
void SmiUntag(Register dst, Register src, Condition cond = AL) {
Asr(dst, src, Operand(kSmiTagSize), cond);
}
// Untag the value in the register assuming it is a smi.
// Untagging shifts tag bit into the carry flag - if carry is clear
// assumption was correct. In this case jump to the is_smi label.
// Otherwise fall-through.
void SmiUntag(Register dst, Register src, Label* is_smi) {
ASSERT(kSmiTagSize == 1);
Asrs(dst, src, Operand(kSmiTagSize));
b(is_smi, CC);
}
// For ARM, the near argument is ignored.
void BranchIfNotSmi(Register reg,
Label* label,
JumpDistance distance = kFarJump) {
tst(reg, Operand(kSmiTagMask));
b(label, NE);
}
// Truncates upper bits.
void LoadInt32FromBoxOrSmi(Register result, Register value) override {
if (result == value) {
ASSERT(TMP != value);
MoveRegister(TMP, value);
value = TMP;
}
ASSERT(value != result);
compiler::Label done;
SmiUntag(result, value, &done);
LoadFieldFromOffset(result, value, compiler::target::Mint::value_offset());
Bind(&done);
}
// For ARM, the near argument is ignored.
void BranchIfSmi(Register reg,
Label* label,
JumpDistance distance = kFarJump) override {
tst(reg, Operand(kSmiTagMask));
b(label, EQ);
}
void CheckCodePointer();
void ArithmeticShiftRightImmediate(Register dst,
Register src,
int32_t shift,
OperandSize sz = kFourBytes) override;
void ArithmeticShiftRightImmediate(Register reg,
int32_t shift,
OperandSize sz = kFourBytes) override {
ArithmeticShiftRightImmediate(reg, reg, shift, sz);
}
void CompareWords(Register reg1,
Register reg2,
intptr_t offset,
Register count,
Register temp,
Label* equals) override;
// Function frame setup and tear down.
void EnterFrame(RegList regs, intptr_t frame_space);
void LeaveFrame(RegList regs, bool allow_pop_pc = false);
void Ret(Condition cond = AL);
// Sets the return address to [value] as if there was a call.
// On ARM sets LR.
void SetReturnAddress(Register value);
void ReserveAlignedFrameSpace(intptr_t frame_space);
// In debug mode, this generates code to check that:
// FP + kExitLinkSlotFromEntryFp == SP
// or triggers breakpoint otherwise.
//
// Requires a scratch register in addition to the assembler temporary.
void EmitEntryFrameVerification(Register scratch);
// For non-leaf runtime calls. For leaf runtime calls, use LeafRuntimeScope,
void CallRuntime(const RuntimeEntry& entry, intptr_t argument_count);
// Set up a Dart frame on entry with a frame pointer and PC information to
// enable easy access to the RawInstruction object of code corresponding
// to this frame.
void EnterDartFrame(intptr_t frame_size, bool load_pool_pointer = true);
void LeaveDartFrame();
// Leaves the frame and returns.
//
// The difference to "LeaveDartFrame(); Ret();" is that we return using
//
// ldmia sp!, {fp, pc}
//
// instead of
//
// ldmia sp!, {fp, lr}
// blx lr
//
// This means that our return must go to ARM mode (and not thumb).
void LeaveDartFrameAndReturn();
// Set up a Dart frame for a function compiled for on-stack replacement.
// The frame layout is a normal Dart frame, but the frame is partially set
// up on entry (it is the frame of the unoptimized code).
void EnterOsrFrame(intptr_t extra_size);
// Set up a stub frame so that the stack traversal code can easily identify
// a stub frame.
void EnterStubFrame();
void LeaveStubFrame();
// Set up a frame for calling a C function.
// Automatically save the pinned registers in Dart which are not callee-
// saved in the native calling convention.
// Use together with CallCFunction.
void EnterCFrame(intptr_t frame_space);
void LeaveCFrame();
void MonomorphicCheckedEntryJIT();
void MonomorphicCheckedEntryAOT();
void BranchOnMonomorphicCheckedEntryJIT(Label* label);
void CombineHashes(Register dst, Register other) override;
void FinalizeHashForSize(intptr_t bit_size,
Register dst,
Register scratch = TMP) override;
// The register into which the allocation tracing state table is loaded with
// LoadAllocationTracingStateAddress should be passed to MaybeTraceAllocation.
//
// These are separate assembler macros so we can avoid a dependent load too
// nearby the load of the table address.
void LoadAllocationTracingStateAddress(Register dest, intptr_t cid);
void LoadAllocationTracingStateAddress(Register dest, Register cid);
// If true is returned, then the out parameter [need_base] signifies whether
// a register is needed for storing the array base (which should be passed
// as the [temp] parameter to ElementAddressForIntIndex).
static bool AddressCanHoldConstantIndex(const Object& constant,
bool is_load,
bool is_external,
intptr_t cid,
intptr_t index_scale,
bool* needs_base = nullptr);
Address ElementAddressForIntIndex(bool is_load,
bool is_external,
intptr_t cid,
intptr_t index_scale,
Register array,
intptr_t index,
Register temp);
void LoadElementAddressForIntIndex(Register address,
bool is_load,
bool is_external,
intptr_t cid,
intptr_t index_scale,
Register array,
intptr_t index);
Address ElementAddressForRegIndex(bool is_load,
bool is_external,
intptr_t cid,
intptr_t index_scale,
bool index_unboxed,
Register array,
Register index);
void LoadElementAddressForRegIndex(Register address,
bool is_load,
bool is_external,
intptr_t cid,
intptr_t index_scale,
bool index_unboxed,
Register array,
Register index);
void LoadStaticFieldAddress(Register address,
Register field,
Register scratch,
bool is_shared);
void LoadFieldAddressForRegOffset(Register address,
Register instance,
Register offset_in_words_as_smi) override;
void LoadFieldAddressForOffset(Register address,
Register instance,
int32_t offset) override {
AddImmediate(address, instance, offset - kHeapObjectTag);
}
void LoadHalfWordUnaligned(Register dst, Register addr, Register tmp);
void LoadHalfWordUnsignedUnaligned(Register dst, Register addr, Register tmp);
void StoreHalfWordUnaligned(Register src, Register addr, Register tmp);
void LoadWordUnaligned(Register dst, Register addr, Register tmp);
void StoreWordUnaligned(Register src, Register addr, Register tmp);
// If allocation tracing is enabled, will jump to |trace| label,
// which will allocate in the runtime where tracing occurs.
void MaybeTraceAllocation(Register stats_addr_reg, Label* trace);
// If allocation tracing for |cid| is enabled, will jump to |trace| label,
// which will allocate in the runtime where tracing occurs.
void MaybeTraceAllocation(intptr_t cid,
Label* trace,
Register temp_reg,
JumpDistance distance = JumpDistance::kFarJump);
void MaybeTraceAllocation(Register cid,
Label* trace,
Register temp_reg,
JumpDistance distance = JumpDistance::kFarJump);
void TryAllocateObject(intptr_t cid,
intptr_t instance_size,
Label* failure,
JumpDistance distance,
Register instance_reg,
Register temp_reg) override;
void TryAllocateArray(intptr_t cid,
intptr_t instance_size,
Label* failure,
Register instance,
Register end_address,
Register temp1,
Register temp2);
void CheckAllocationCanary(Register top, Register tmp = TMP) {
#if defined(DEBUG)
Label okay;
ldr(tmp, Address(top, 0));
cmp(tmp, Operand(kAllocationCanary));
b(&okay, EQUAL);
Stop("Allocation canary");
Bind(&okay);
#endif
}
void WriteAllocationCanary(Register top) {
#if defined(DEBUG)
ASSERT(top != TMP);
LoadImmediate(TMP, kAllocationCanary);
str(TMP, Address(top, 0));
#endif
}
// Copy [size] bytes from [src] address to [dst] address.
// [size] should be a multiple of word size.
// Clobbers [src], [dst], [size] and [temp] registers.
void CopyMemoryWords(Register src,
Register dst,
Register size,
Register temp);
// This emits an PC-relative call of the form "blr.<cond> <offset>". The
// offset is not yet known and needs therefore relocation to the right place
// before the code can be used.
//
// The necessary information for the "linker" (i.e. the relocation
// information) is stored in [UntaggedCode::static_calls_target_table_]: an
// entry of the form
//
// (Code::kPcRelativeCall & pc_offset, <target-code>, <target-function>)
//
// will be used during relocation to fix the offset.
//
// The provided [offset_into_target] will be added to calculate the final
// destination. It can be used e.g. for calling into the middle of a
// function.
void GenerateUnRelocatedPcRelativeCall(Condition cond = AL,
intptr_t offset_into_target = 0);
// This emits an PC-relative tail call of the form "b.<cond> <offset>".
//
// See also above for the pc-relative call.
void GenerateUnRelocatedPcRelativeTailCall(Condition cond = AL,
intptr_t offset_into_target = 0);
// Emit data (e.g encoded instruction or immediate) in instruction stream.
void Emit(int32_t value);
// On some other platforms, we draw a distinction between safe and unsafe
// smis.
static bool IsSafe(const Object& object) { return true; }
static bool IsSafeSmi(const Object& object) { return target::IsSmi(object); }
bool constant_pool_allowed() const { return constant_pool_allowed_; }
void set_constant_pool_allowed(bool b) { constant_pool_allowed_ = b; }
compiler::LRState lr_state() const { return lr_state_; }
void set_lr_state(compiler::LRState b) { lr_state_ = b; }
// Whether we can branch to a target which is [distance] bytes away from the
// beginning of the branch instruction.
//
// Use this function for testing whether [distance] can be encoded using the
// 24-bit offsets in the branch instructions, which are multiples of 4.
static bool CanEncodeBranchDistance(int32_t distance) {
ASSERT(Utils::IsAligned(distance, 4));
// The distance is off by 8 due to the way the ARM CPUs read PC.
distance -= Instr::kPCReadOffset;
distance >>= 2;
return Utils::IsInt(24, distance);
}
static int32_t EncodeBranchOffset(int32_t offset, int32_t inst);
static int32_t DecodeBranchOffset(int32_t inst);
private:
bool use_far_branches_;
bool constant_pool_allowed_;
compiler::LRState lr_state_ = compiler::LRState::OnEntry();
// If you are thinking of using one or both of these instructions directly,
// instead LoadImmediate should probably be used.
void movw(Register rd, uint16_t imm16, Condition cond = AL);
void movt(Register rd, uint16_t imm16, Condition cond = AL);
void BindARMv7(Label* label);
void BranchLink(const ExternalLabel* label);
void BranchLink(intptr_t target_code_pool_index, CodeEntryKind entry_kind);
void LoadObjectHelper(
Register rd,
const Object& object,
Condition cond,
bool is_unique,
Register pp,
ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
ObjectPoolBuilderEntry::kSnapshotable);
void EmitType01(Condition cond,
int type,
Opcode opcode,
int set_cc,
Register rn,
Register rd,
Operand o);
void EmitType5(Condition cond, int32_t offset, bool link);
void EmitMemOp(Condition cond, bool load, bool byte, Register rd, Address ad);
void EmitMemOpAddressMode3(Condition cond,
int32_t mode,
Register rd,
Address ad);
void EmitMultiMemOp(Condition cond,
BlockAddressMode am,
bool load,
Register base,
RegList regs);
void EmitShiftImmediate(Condition cond,
Shift opcode,
Register rd,
Register rm,
Operand o);
void EmitShiftRegister(Condition cond,
Shift opcode,
Register rd,
Register rm,
Operand o);
void EmitMulOp(Condition cond,
int32_t opcode,
Register rd,
Register rn,
Register rm,
Register rs);
void EmitDivOp(Condition cond,
int32_t opcode,
Register rd,
Register rn,
Register rm);
void EmitMultiVSMemOp(Condition cond,
BlockAddressMode am,
bool load,
Register base,
SRegister start,
uint32_t count);
void EmitMultiVDMemOp(Condition cond,
BlockAddressMode am,
bool load,
Register base,
DRegister start,
int32_t count);
void EmitVFPsss(Condition cond,
int32_t opcode,
SRegister sd,
SRegister sn,
SRegister sm);
void EmitVFPddd(Condition cond,
int32_t opcode,
DRegister dd,
DRegister dn,
DRegister dm);
void EmitVFPsd(Condition cond, int32_t opcode, SRegister sd, DRegister dm);
void EmitVFPds(Condition cond, int32_t opcode, DRegister dd, SRegister sm);
void EmitSIMDqqq(int32_t opcode,
OperandSize sz,
QRegister qd,
QRegister qn,
QRegister qm);
void EmitSIMDddd(int32_t opcode,
OperandSize sz,
DRegister dd,
DRegister dn,
DRegister dm);
void EmitFarBranch(Condition cond, int32_t offset, bool link);
void EmitBranch(Condition cond, Label* label, bool link);
void BailoutIfInvalidBranchOffset(int32_t offset);
int32_t EncodeTstOffset(int32_t offset, int32_t inst);
int32_t DecodeTstOffset(int32_t inst);
friend class dart::FlowGraphCompiler;
std::function<void(Condition, Register)>
generate_invoke_write_barrier_wrapper_;
std::function<void(Condition)> generate_invoke_array_write_barrier_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(Assembler);
};
} // namespace compiler
} // namespace dart
#endif // RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_ARM_H_