// Copyright (c) 2017, 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_RISCV_H_
#define RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_RISCV_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_riscv.h directly; use assembler.h instead.
#endif

#include <functional>

#include "platform/assert.h"
#include "platform/utils.h"
#include "vm/class_id.h"
#include "vm/compiler/assembler/assembler_base.h"
#include "vm/compiler/assembler/object_pool_builder.h"
#include "vm/constants.h"
#include "vm/hash_map.h"
#include "vm/simulator.h"

namespace dart {

// Forward declarations.
class FlowGraphCompiler;
class RuntimeEntry;
class RegisterSet;

namespace compiler {

class Address {
 public:
  Address(Register base, intptr_t offset) : base_(base), offset_(offset) {}
  explicit Address(Register base) : base_(base), offset_(0) {}

  // Prevent implicit conversion of Register to intptr_t.
  Address(Register base, Register index) = delete;

  Register base() const { return base_; }
  intptr_t offset() const { return offset_; }

 private:
  Register base_;
  intptr_t offset_;
};

class FieldAddress : public Address {
 public:
  FieldAddress(Register base, intptr_t offset)
      : Address(base, offset - kHeapObjectTag) {}

  // Prevent implicit conversion of Register to intptr_t.
  FieldAddress(Register base, Register index) = delete;
};

#if XLEN == 32
#define XSUFFIX(inst) inst##w
#elif XLEN == 64
#define XSUFFIX(inst) inst##d
#elif XLEN == 128
#define XSUFFIX(inst) inst##q
#endif

// All functions produce exactly one instruction.
class MicroAssembler : public AssemblerBase {
 public:
  MicroAssembler(ObjectPoolBuilder* object_pool_builder,
                 intptr_t far_branch_level,
                 ExtensionSet extensions);
  ~MicroAssembler();

#if defined(TESTING)
  void SetExtensions(ExtensionSet extensions) { extensions_ = extensions; }
#endif
  ExtensionSet extensions() const { return extensions_; }
  bool Supports(Extension extension) const {
    return extensions_.Includes(extension);
  }
  bool Supports(ExtensionSet extensions) const {
    return extensions_.IncludesAll(extensions);
  }

  intptr_t far_branch_level() const { return far_branch_level_; }
  void set_far_branch_level(intptr_t level) { far_branch_level_ = level; }
  void Bind(Label* label);

  // ==== RV32I ====
  void lui(Register rd, intptr_t imm);
  void lui_fixed(Register rd, intptr_t imm);
  void auipc(Register rd, intptr_t imm);

  void jal(Register rd, Label* label, JumpDistance d = kFarJump);
  void jal(Label* label, JumpDistance d = kFarJump) { jal(RA, label, d); }
  void j(Label* label, JumpDistance d = kFarJump) { jal(ZR, label, d); }

  void jalr(Register rd, Register rs1, intptr_t offset = 0);
  void jalr_fixed(Register rd, Register rs1, intptr_t offset);
  void jalr(Register rs1, intptr_t offset = 0) { jalr(RA, rs1, offset); }
  void jr(Register rs1, intptr_t offset = 0) { jalr(ZR, rs1, offset); }
  void ret() { jalr(ZR, RA, 0); }

  void beq(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump);
  void bne(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump);
  void blt(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump);
  void bge(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump);
  void bgt(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump) {
    blt(rs2, rs1, l, d);
  }
  void ble(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump) {
    bge(rs2, rs1, l, d);
  }
  void bltu(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump);
  void bgeu(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump);
  void bgtu(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump) {
    bltu(rs2, rs1, l, d);
  }
  void bleu(Register rs1, Register rs2, Label* l, JumpDistance d = kFarJump) {
    bgeu(rs2, rs1, l, d);
  }

  void lb(Register rd, Address addr);
  void lh(Register rd, Address addr);
  void lw(Register rd, Address addr);
  void lbu(Register rd, Address addr);
  void lhu(Register rd, Address addr);

  void sb(Register rs2, Address addr);
  void sh(Register rs2, Address addr);
  void sw(Register rs2, Address addr);

  void addi(Register rd, Register rs1, intptr_t imm);
  void subi(Register rd, Register rs1, intptr_t imm) { addi(rd, rs1, -imm); }
  void slti(Register rd, Register rs1, intptr_t imm);
  void sltiu(Register rd, Register rs1, intptr_t imm);
  void xori(Register rd, Register rs1, intptr_t imm);
  void ori(Register rd, Register rs1, intptr_t imm);
  void andi(Register rd, Register rs1, intptr_t imm);
  void slli(Register rd, Register rs1, intptr_t shamt);
  void srli(Register rd, Register rs1, intptr_t shamt);
  void srai(Register rd, Register rs1, intptr_t shamt);

  void add(Register rd, Register rs1, Register rs2);
  void sub(Register rd, Register rs1, Register rs2);
  void sll(Register rd, Register rs1, Register rs2);
  void slt(Register rd, Register rs1, Register rs2);
  void sltu(Register rd, Register rs1, Register rs2);
  void xor_(Register rd, Register rs1, Register rs2);
  void srl(Register rd, Register rs1, Register rs2);
  void sra(Register rd, Register rs1, Register rs2);
  void or_(Register rd, Register rs1, Register rs2);
  void and_(Register rd, Register rs1, Register rs2);

  void fence(HartEffects predecessor, HartEffects successor);
  void fence() { fence(kAll, kAll); }
  void fencei();
  void ecall();
  void ebreak();  // Causes SIGTRAP(5).

  void csrrw(Register rd, uint32_t csr, Register rs1);
  void csrrs(Register rd, uint32_t csr, Register rs1);
  void csrrc(Register rd, uint32_t csr, Register rs1);
  void csrr(Register rd, uint32_t csr) { csrrs(rd, csr, ZR); }
  void csrw(uint32_t csr, Register rs) { csrrw(ZR, csr, rs); }
  void csrs(uint32_t csr, Register rs) { csrrs(ZR, csr, rs); }
  void csrc(uint32_t csr, Register rs) { csrrc(ZR, csr, rs); }
  void csrrwi(Register rd, uint32_t csr, uint32_t imm);
  void csrrsi(Register rd, uint32_t csr, uint32_t imm);
  void csrrci(Register rd, uint32_t csr, uint32_t imm);
  void csrwi(uint32_t csr, uint32_t imm) { csrrwi(ZR, csr, imm); }
  void csrsi(uint32_t csr, uint32_t imm) { csrrsi(ZR, csr, imm); }
  void csrci(uint32_t csr, uint32_t imm) { csrrci(ZR, csr, imm); }

  void trap();  // Permanently reserved illegal instruction; causes SIGILL(4).

  void nop() { addi(ZR, ZR, 0); }
  void li(Register rd, intptr_t imm) { addi(rd, ZR, imm); }
  void mv(Register rd, Register rs) { addi(rd, rs, 0); }
  void not_(Register rd, Register rs) { xori(rd, rs, -1); }
  void neg(Register rd, Register rs) { sub(rd, ZR, rs); }
  void zextb(Register rd, Register rs) { andi(rd, rs, 0xFF); }

  void snez(Register rd, Register rs) { sltu(rd, ZR, rs); }
  void seqz(Register rd, Register rs) { sltiu(rd, rs, 1); }
  void sltz(Register rd, Register rs) { slt(rd, rs, ZR); }
  void sgtz(Register rd, Register rs) { slt(rd, ZR, rs); }

  void beqz(Register rs, Label* label, JumpDistance distance = kFarJump) {
    beq(rs, ZR, label, distance);
  }
  void bnez(Register rs, Label* label, JumpDistance distance = kFarJump) {
    bne(rs, ZR, label, distance);
  }
  void blez(Register rs, Label* label, JumpDistance distance = kFarJump) {
    bge(ZR, rs, label, distance);
  }
  void bgez(Register rs, Label* label, JumpDistance distance = kFarJump) {
    bge(rs, ZR, label, distance);
  }
  void bltz(Register rs, Label* label, JumpDistance distance = kFarJump) {
    blt(rs, ZR, label, distance);
  }
  void bgtz(Register rs, Label* label, JumpDistance distance = kFarJump) {
    blt(ZR, rs, label, distance);
  }

  // ==== RV64I ====
#if XLEN >= 64
  void lwu(Register rd, Address addr);
  void ld(Register rd, Address addr);

  void sd(Register rs2, Address addr);

  void addiw(Register rd, Register rs1, intptr_t imm);
  void subiw(Register rd, Register rs1, intptr_t imm) { addiw(rd, rs1, -imm); }
  void slliw(Register rd, Register rs1, intptr_t shamt);
  void srliw(Register rd, Register rs1, intptr_t shamt);
  void sraiw(Register rd, Register rs1, intptr_t shamt);

  void addw(Register rd, Register rs1, Register rs2);
  void subw(Register rd, Register rs1, Register rs2);
  void sllw(Register rd, Register rs1, Register rs2);
  void srlw(Register rd, Register rs1, Register rs2);
  void sraw(Register rd, Register rs1, Register rs2);

  void negw(Register rd, Register rs) { subw(rd, ZR, rs); }
  void sextw(Register rd, Register rs) { addiw(rd, rs, 0); }
#endif  // XLEN >= 64

  void lx(Register rd, Address addr) { XSUFFIX(l)(rd, addr); }
  void sx(Register rs2, Address addr) { XSUFFIX(s)(rs2, addr); }

  // ==== RV32M ====
  void mul(Register rd, Register rs1, Register rs2);
  void mulh(Register rd, Register rs1, Register rs2);
  void mulhsu(Register rd, Register rs1, Register rs2);
  void mulhu(Register rd, Register rs1, Register rs2);
  void div(Register rd, Register rs1, Register rs2);
  void divu(Register rd, Register rs1, Register rs2);
  void rem(Register rd, Register rs1, Register rs2);
  void remu(Register rd, Register rs1, Register rs2);

  // ==== RV64M ====
#if XLEN >= 64
  void mulw(Register rd, Register rs1, Register rs2);
  void divw(Register rd, Register rs1, Register rs2);
  void divuw(Register rd, Register rs1, Register rs2);
  void remw(Register rd, Register rs1, Register rs2);
  void remuw(Register rd, Register rs1, Register rs2);
#endif  // XLEN >= 64

  // ==== RV32A ====
  void lrw(Register rd,
           Address addr,
           std::memory_order order = std::memory_order_relaxed);
  void scw(Register rd,
           Register rs2,
           Address addr,
           std::memory_order order = std::memory_order_relaxed);
  void amoswapw(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amoaddw(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoxorw(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoandw(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoorw(Register rd,
              Register rs2,
              Address addr,
              std::memory_order order = std::memory_order_relaxed);
  void amominw(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amomaxw(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amominuw(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amomaxuw(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);

  // ==== RV64A ====
#if XLEN >= 64
  void lrd(Register rd,
           Address addr,
           std::memory_order order = std::memory_order_relaxed);
  void scd(Register rd,
           Register rs2,
           Address addr,
           std::memory_order order = std::memory_order_relaxed);
  void amoswapd(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amoaddd(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoxord(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoandd(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoord(Register rd,
              Register rs2,
              Address addr,
              std::memory_order order = std::memory_order_relaxed);
  void amomind(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amomaxd(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amominud(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amomaxud(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
#endif  // XLEN >= 64

  void lrx(Register rd,
           Address addr,
           std::memory_order order = std::memory_order_relaxed) {
    XSUFFIX(lr)(rd, addr, order);
  }
#define AMOX(amo)                                                              \
  void amo##x(Register rd, Register rs2, Address addr,                         \
              std::memory_order order = std::memory_order_relaxed) {           \
    XSUFFIX(amo)(rd, rs2, addr, order);                                        \
  }
  AMOX(sc)
  AMOX(amoswap)
  AMOX(amoadd)
  AMOX(amoxor)
  AMOX(amoand)
  AMOX(amoor)
  AMOX(amomin)
  AMOX(amomax)
  AMOX(amominu)
  AMOX(amomaxu)
  AMOX(amocas)
#undef AMOX

  // ==== RV32F ====
  void flw(FRegister rd, Address addr);
  void fsw(FRegister rs2, Address addr);
  // rd := (rs1 * rs2) + rs3
  void fmadds(FRegister rd,
              FRegister rs1,
              FRegister rs2,
              FRegister rs3,
              RoundingMode rounding = RNE);
  // rd := (rs1 * rs2) - rs3
  void fmsubs(FRegister rd,
              FRegister rs1,
              FRegister rs2,
              FRegister rs3,
              RoundingMode rounding = RNE);
  // rd := -(rs1 * rs2) + rs3
  void fnmsubs(FRegister rd,
               FRegister rs1,
               FRegister rs2,
               FRegister rs3,
               RoundingMode rounding = RNE);
  // rd := -(rs1 * rs2) - rs3
  void fnmadds(FRegister rd,
               FRegister rs1,
               FRegister rs2,
               FRegister rs3,
               RoundingMode rounding = RNE);
  void fadds(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fsubs(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fmuls(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fdivs(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fsqrts(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void fsgnjs(FRegister rd, FRegister rs1, FRegister rs2);
  void fsgnjns(FRegister rd, FRegister rs1, FRegister rs2);
  void fsgnjxs(FRegister rd, FRegister rs1, FRegister rs2);
  void fmins(FRegister rd, FRegister rs1, FRegister rs2);
  void fmaxs(FRegister rd, FRegister rs1, FRegister rs2);
  void feqs(Register rd, FRegister rs1, FRegister rs2);
  void flts(Register rd, FRegister rs1, FRegister rs2);
  void fles(Register rd, FRegister rs1, FRegister rs2);
  void fgts(Register rd, FRegister rs1, FRegister rs2) { flts(rd, rs2, rs1); }
  void fges(Register rd, FRegister rs1, FRegister rs2) { fles(rd, rs2, rs1); }
  void fclasss(Register rd, FRegister rs1);
  // int32_t <- float
  void fcvtws(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // uint32_t <- float
  void fcvtwus(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // float <- int32_t
  void fcvtsw(FRegister rd, Register rs1, RoundingMode rounding = RNE);
  // float <- uint32_t
  void fcvtswu(FRegister rd, Register rs1, RoundingMode rounding = RNE);

  void fmvs(FRegister rd, FRegister rs) { fsgnjs(rd, rs, rs); }
  void fabss(FRegister rd, FRegister rs) { fsgnjxs(rd, rs, rs); }
  void fnegs(FRegister rd, FRegister rs) { fsgnjns(rd, rs, rs); }

  // xlen <--bit_cast-- float
  void fmvxw(Register rd, FRegister rs1);
  // float <--bit_cast-- xlen
  void fmvwx(FRegister rd, Register rs1);

  // ==== RV64F ====
#if XLEN >= 64
  // int64_t <- double
  void fcvtls(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // uint64_t <- double
  void fcvtlus(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // double <- int64_t
  void fcvtsl(FRegister rd, Register rs1, RoundingMode rounding = RNE);
  // double <- uint64_t
  void fcvtslu(FRegister rd, Register rs1, RoundingMode rounding = RNE);
#endif  // XLEN >= 64

  // ==== RV32D ====
  void fld(FRegister rd, Address addr);
  void fsd(FRegister rs2, Address addr);
  // rd := (rs1 * rs2) + rs3
  void fmaddd(FRegister rd,
              FRegister rs1,
              FRegister rs2,
              FRegister rs3,
              RoundingMode rounding = RNE);
  // rd := (rs1 * rs2) - rs3
  void fmsubd(FRegister rd,
              FRegister rs1,
              FRegister rs2,
              FRegister rs3,
              RoundingMode rounding = RNE);
  // rd := -(rs1 * rs2) - rs3
  void fnmsubd(FRegister rd,
               FRegister rs1,
               FRegister rs2,
               FRegister rs3,
               RoundingMode rounding = RNE);
  // rd := -(rs1 * rs2) + rs3
  void fnmaddd(FRegister rd,
               FRegister rs1,
               FRegister rs2,
               FRegister rs3,
               RoundingMode rounding = RNE);
  void faddd(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fsubd(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fmuld(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fdivd(FRegister rd,
             FRegister rs1,
             FRegister rs2,
             RoundingMode rounding = RNE);
  void fsqrtd(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void fsgnjd(FRegister rd, FRegister rs1, FRegister rs2);
  void fsgnjnd(FRegister rd, FRegister rs1, FRegister rs2);
  void fsgnjxd(FRegister rd, FRegister rs1, FRegister rs2);
  void fmind(FRegister rd, FRegister rs1, FRegister rs2);
  void fmaxd(FRegister rd, FRegister rs1, FRegister rs2);
  void fcvtsd(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void fcvtds(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void feqd(Register rd, FRegister rs1, FRegister rs2);
  void fltd(Register rd, FRegister rs1, FRegister rs2);
  void fled(Register rd, FRegister rs1, FRegister rs2);
  void fgtd(Register rd, FRegister rs1, FRegister rs2) { fltd(rd, rs2, rs1); }
  void fged(Register rd, FRegister rs1, FRegister rs2) { fled(rd, rs2, rs1); }
  void fclassd(Register rd, FRegister rs1);
  // int32_t <- double
  void fcvtwd(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // uint32_t <- double
  void fcvtwud(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // double <- int32_t
  void fcvtdw(FRegister rd, Register rs1, RoundingMode rounding = RNE);
  // double <- uint32_t
  void fcvtdwu(FRegister rd, Register rs1, RoundingMode rounding = RNE);

  void fmvd(FRegister rd, FRegister rs) { fsgnjd(rd, rs, rs); }
  void fabsd(FRegister rd, FRegister rs) { fsgnjxd(rd, rs, rs); }
  void fnegd(FRegister rd, FRegister rs) { fsgnjnd(rd, rs, rs); }

  // ==== RV64D ====
#if XLEN >= 64
  // int64_t <- double
  void fcvtld(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // uint64_t <- double
  void fcvtlud(Register rd, FRegister rs1, RoundingMode rounding = RNE);
  // xlen <--bit_cast-- double
  void fmvxd(Register rd, FRegister rs1);
  // double <- int64_t
  void fcvtdl(FRegister rd, Register rs1, RoundingMode rounding = RNE);
  // double <- uint64_t
  void fcvtdlu(FRegister rd, Register rs1, RoundingMode rounding = RNE);
  // double <--bit_cast-- xlen
  void fmvdx(FRegister rd, Register rs1);
#endif  // XLEN >= 64

  // ==== Zba: Address generation ====
  void adduw(Register rd, Register rs1, Register rs2);
  void sh1add(Register rd, Register rs1, Register rs2);
  void sh1adduw(Register rd, Register rs1, Register rs2);
  void sh2add(Register rd, Register rs1, Register rs2);
  void sh2adduw(Register rd, Register rs1, Register rs2);
  void sh3add(Register rd, Register rs1, Register rs2);
  void sh3adduw(Register rd, Register rs1, Register rs2);
  void slliuw(Register rd, Register rs1, intx_t imm);

  void zextw(Register rd, Register rs) { adduw(rd, rs, ZR); }

  // ==== Zbb: Basic bit-manipulation ====
  void andn(Register rd, Register rs1, Register rs2);
  void orn(Register rd, Register rs1, Register rs2);
  void xnor(Register rd, Register rs1, Register rs2);
  void clz(Register rd, Register rs);
  void clzw(Register rd, Register rs);
  void ctz(Register rd, Register rs);
  void ctzw(Register rd, Register rs);
  void cpop(Register rd, Register rs);
  void cpopw(Register rd, Register rs);
  void max(Register rd, Register rs1, Register rs2);  // NOLINT
  void maxu(Register rd, Register rs1, Register rs2);
  void min(Register rd, Register rs1, Register rs2);  // NOLINT
  void minu(Register rd, Register rs1, Register rs2);
  void sextb(Register rd, Register rs);
  void sexth(Register rd, Register rs);
  void zexth(Register rd, Register rs);
  void rol(Register rd, Register rs1, Register rs2);
  void rolw(Register rd, Register rs1, Register rs2);
  void ror(Register rd, Register rs1, Register rs2);
  void rori(Register rd, Register rs1, intx_t imm);
  void roriw(Register rd, Register rs1, intx_t imm);
  void rorw(Register rd, Register rs1, Register rs2);
  void orcb(Register rd, Register rs);
  void rev8(Register rd, Register rs);

  // ==== Zbc: Carry-less multiplication ====
  void clmul(Register rd, Register rs1, Register rs2);
  void clmulh(Register rd, Register rs1, Register rs2);
  void clmulr(Register rd, Register rs1, Register rs2);

  // ==== Zbs: Single-bit instructions ====
  void bclr(Register rd, Register rs1, Register rs2);
  void bclri(Register rd, Register rs1, intx_t shamt);
  void bext(Register rd, Register rs1, Register rs2);
  void bexti(Register rd, Register rs1, intx_t shamt);
  void binv(Register rd, Register rs1, Register rs2);
  void binvi(Register rd, Register rs1, intx_t shamt);
  void bset(Register rd, Register rs1, Register rs2);
  void bseti(Register rd, Register rs1, intx_t shamt);

  // ==== Zicond: Integer conditional operations ====
  // rd := rs2 == 0 ? 0 : rs1
  void czeroeqz(Register rd, Register rs1, Register rs2);
  // rd := rs2 != 0 ? 0 : rs1
  void czeronez(Register rd, Register rs1, Register rs2);

  // ==== Zabha: Byte and halfword AMOs ====
  void amoswapb(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amoaddb(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoxorb(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoandb(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoorb(Register rd,
              Register rs2,
              Address addr,
              std::memory_order order = std::memory_order_relaxed);
  void amominb(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amomaxb(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amominub(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amomaxub(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amoswaph(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amoaddh(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoxorh(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoandh(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amoorh(Register rd,
              Register rs2,
              Address addr,
              std::memory_order order = std::memory_order_relaxed);
  void amominh(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amomaxh(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amominuh(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);
  void amomaxuh(Register rd,
                Register rs2,
                Address addr,
                std::memory_order order = std::memory_order_relaxed);

  // ==== Zfa: Additional floating-point instructions ====
  void flis(FRegister rd, intptr_t index);
  void flid(FRegister rd, intptr_t index);
  void fminms(FRegister rd, FRegister rs1, FRegister rs2);
  void fmaxms(FRegister rd, FRegister rs1, FRegister rs2);
  void fminmd(FRegister rd, FRegister rs1, FRegister rs2);
  void fmaxmd(FRegister rd, FRegister rs1, FRegister rs2);
  void frounds(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void froundd(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void froundnxs(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void froundnxd(FRegister rd, FRegister rs1, RoundingMode rounding = RNE);
  void fcvtmodwd(Register rd, FRegister rs1);
#if XLEN == 32
  void fmvhxd(Register rd, FRegister rs1);
  void fmvpdx(FRegister rd, Register rs1, Register rs2);
#endif
  void fltqs(Register rd, FRegister rs1, FRegister rs2);
  void fleqs(Register rd, FRegister rs1, FRegister rs2);
  void fltqd(Register rd, FRegister rs1, FRegister rs2);
  void fleqd(Register rd, FRegister rs1, FRegister rs2);

  // ==== Zimop: May-be-operations ====
  void mopr(intptr_t n, Register rd, Register rs1);
  void moprr(intptr_t n, Register rd, Register rs1, Register rs2);
  // ==== Zcmop: Compressed may-be-operations ====
  void cmop(intptr_t n);

  // ==== Zicfiss: Shadow stack ====
  void sspush(Register rs2);
  void sspopchk(Register rs1);
  void ssrdp(Register rd);
  void ssamoswapw(Register rd,
                  Register rs2,
                  Address addr,
                  std::memory_order order = std::memory_order_relaxed);
#if XLEN >= 64
  void ssamoswapd(Register rd,
                  Register rs2,
                  Address addr,
                  std::memory_order order = std::memory_order_relaxed);
#endif  // XLEN >= 64

  // ==== RV32V: Vectors ====
  void vsetvli(Register rd,
               Register rs1,
               ElementWidth sew,
               LengthMultiplier lmul,
               TailMode vta,
               MaskMode vma);
  void vle8v(VRegister vd, Address rs1, VectorMask vm = unmasked);
  void vle16v(VRegister vd, Address rs1, VectorMask vm = unmasked);
  void vle32v(VRegister vd, Address rs1, VectorMask vm = unmasked);
  void vle64v(VRegister vd, Address rs1, VectorMask vm = unmasked);
  void vse8v(VRegister vs3, Address rs1, VectorMask vm = unmasked);
  void vse16v(VRegister vs3, Address rs1, VectorMask vm = unmasked);
  void vse32v(VRegister vs3, Address rs1, VectorMask vm = unmasked);
  void vse64v(VRegister vs3, Address rs1, VectorMask vm = unmasked);
  void vmvvx(VRegister vd, Register rs1, VectorMask vm = unmasked);

  // ==== Zalasr: Load-acquire, store-release ====
  void lb(Register rd, Address addr, std::memory_order order);
  void lh(Register rd, Address addr, std::memory_order order);
  void lw(Register rd, Address addr, std::memory_order order);
  void sb(Register rs2, Address addr, std::memory_order order);
  void sh(Register rs2, Address addr, std::memory_order order);
  void sw(Register rs2, Address addr, std::memory_order order);

#if XLEN >= 64
  void ld(Register rd, Address addr, std::memory_order order);
  void sd(Register rs2, Address addr, std::memory_order order);
#endif

  void lx(Register rd, Address addr, std::memory_order o) {
    XSUFFIX(l)(rd, addr, o);
  }
  void sx(Register rs2, Address addr, std::memory_order o) {
    XSUFFIX(s)(rs2, addr, o);
  }

  // ==== Zacas: Compare-and-swap ====
  void amocasb(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amocash(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amocasw(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
  void amocasd(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
#if XLEN >= 64
  void amocasq(Register rd,
               Register rs2,
               Address addr,
               std::memory_order order = std::memory_order_relaxed);
#endif

  // ==== Zawrs: Wait on reservation set ====
  void wrsnto();
  void wrssto();

  // ==== Dart Simulator Debugging ====
  void SimulatorPrintObject(Register rs1);

 private:
  // ==== RV32/64C ====
  void c_lwsp(Register rd, Address addr);
#if XLEN == 32
  void c_flwsp(FRegister rd, Address addr);
#else
  void c_ldsp(Register rd, Address addr);
#endif
  void c_fldsp(FRegister rd, Address addr);

  void c_swsp(Register rs2, Address addr);
#if XLEN == 32
  void c_fswsp(FRegister rs2, Address addr);
#else
  void c_sdsp(Register rs2, Address addr);
#endif
  void c_fsdsp(FRegister rs2, Address addr);

  void c_lw(Register rd, Address addr);
  void c_ld(Register rd, Address addr);
  void c_flw(FRegister rd, Address addr);
  void c_fld(FRegister rd, Address addr);

  void c_sw(Register rs2, Address addr);
  void c_sd(Register rs2, Address addr);
  void c_fsw(FRegister rs2, Address addr);
  void c_fsd(FRegister rs2, Address addr);

  void c_j(Label* label);
#if XLEN == 32
  void c_jal(Label* label);
#endif
  void c_jr(Register rs1);
  void c_jalr(Register rs1);

  void c_beqz(Register rs1p, Label* label);
  void c_bnez(Register rs1p, Label* label);

  void c_li(Register rd, intptr_t imm);
  void c_lui(Register rd, uintptr_t imm);

  void c_addi(Register rd, Register rs1, intptr_t imm);
#if XLEN >= 64
  void c_addiw(Register rd, Register rs1, intptr_t imm);
#endif
  void c_addi16sp(Register rd, Register rs1, intptr_t imm);
  void c_addi4spn(Register rdp, Register rs1, intptr_t imm);

  void c_slli(Register rd, Register rs1, intptr_t imm);
  void c_srli(Register rd, Register rs1, intptr_t imm);
  void c_srai(Register rd, Register rs1, intptr_t imm);
  void c_andi(Register rd, Register rs1, intptr_t imm);

  void c_mv(Register rd, Register rs2);

  void c_add(Register rd, Register rs1, Register rs2);
  void c_and(Register rd, Register rs1, Register rs2);
  void c_or(Register rd, Register rs1, Register rs2);
  void c_xor(Register rd, Register rs1, Register rs2);
  void c_sub(Register rd, Register rs1, Register rs2);
#if XLEN >= 64
  void c_addw(Register rd, Register rs1, Register rs2);
  void c_subw(Register rd, Register rs1, Register rs2);
#endif

  void c_nop();
  void c_ebreak();

  // ==== Zcb: Additional code-size saving instructions ====
  void c_lbu(Register rd, Address addr);
  void c_lh(Register rd, Address addr);
  void c_lhu(Register rd, Address addr);
  void c_sb(Register rs2, Address addr);
  void c_sh(Register rs2, Address addr);
  void c_zextb(Register rd, Register rs1);
  void c_sextb(Register rd, Register rs1);
  void c_zexth(Register rd, Register rs1);
  void c_sexth(Register rd, Register rs1);
#if XLEN >= 64
  void c_zextw(Register rd, Register rs1);
#endif
  void c_mul(Register rd, Register rs1, Register rs2);
  void c_not(Register rd, Register rs1);

 protected:
  intptr_t UpdateCBOffset(intptr_t branch_position, intptr_t new_offset);
  intptr_t UpdateCJOffset(intptr_t branch_position, intptr_t new_offset);
  intptr_t UpdateBOffset(intptr_t branch_position, intptr_t new_offset);
  intptr_t UpdateJOffset(intptr_t branch_position, intptr_t new_offset);
  intptr_t UpdateFarOffset(intptr_t branch_position, intptr_t new_offset);

  intptr_t Position() { return buffer_.Size(); }
  void EmitBranch(Register rs1,
                  Register rs2,
                  Label* label,
                  Funct3 func,
                  JumpDistance distance);
  void EmitJump(Register rd, Label* label, Opcode op, JumpDistance distance);
  void EmitCBranch(Register rs1p, Label* label, COpcode op);
  void EmitCJump(Label* label, COpcode op);

  void EmitRType(Funct5 funct5,
                 std::memory_order order,
                 Register rs2,
                 Register rs1,
                 Funct3 funct3,
                 Register rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 Register rs2,
                 Register rs1,
                 Funct3 funct3,
                 Register rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 FRegister rs2,
                 FRegister rs1,
                 Funct3 funct3,
                 FRegister rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 FRegister rs2,
                 FRegister rs1,
                 RoundingMode round,
                 FRegister rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 Register rs2,
                 Register rs1,
                 RoundingMode round,
                 FRegister rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 Register rs2,
                 Register rs1,
                 Funct3 funct3,
                 FRegister rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 FRegister rs2,
                 FRegister rs1,
                 Funct3 funct3,
                 Register rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 FRegister rs2,
                 FRegister rs1,
                 RoundingMode round,
                 Register rd,
                 Opcode opcode);
  void EmitRType(Funct7 funct7,
                 intptr_t shamt,
                 Register rs1,
                 Funct3 funct3,
                 Register rd,
                 Opcode opcode);

  void EmitR4Type(FRegister rs3,
                  Funct2 funct2,
                  FRegister rs2,
                  FRegister rs1,
                  RoundingMode round,
                  FRegister rd,
                  Opcode opcode);

  void EmitIType(Funct12 funct12,
                 Register rs1,
                 Funct3 funct3,
                 Register rd,
                 Opcode opcode);
  void EmitIType(intptr_t imm,
                 Register rs1,
                 Funct3 funct3,
                 Register rd,
                 Opcode opcode);
  void EmitIType(intptr_t imm,
                 Register rs1,
                 Funct3 funct3,
                 FRegister rd,
                 Opcode opcode);

  void EmitSType(intptr_t imm,
                 Register rs2,
                 Register rs1,
                 Funct3 funct3,
                 Opcode opcode);
  void EmitSType(intptr_t imm,
                 FRegister rs2,
                 Register rs1,
                 Funct3 funct3,
                 Opcode opcode);

  void EmitBType(intptr_t imm,
                 Register rs2,
                 Register rs1,
                 Funct3 funct3,
                 Opcode opcode);

  void EmitUType(intptr_t imm, Register rd, Opcode opcode);

  void EmitJType(intptr_t imm, Register rd, Opcode opcode);

  uint16_t Read16(intptr_t position) {
    return buffer_.Load<uint16_t>(position);
  }
  void Write16(intptr_t position, uint16_t instruction) {
    return buffer_.Store<uint16_t>(position, instruction);
  }
  void Emit16(uint16_t instruction) {
    AssemblerBuffer::EnsureCapacity ensured(&buffer_);
    buffer_.Emit<uint16_t>(instruction);
  }
  uint32_t Read32(intptr_t position) {
    return buffer_.Load<uint32_t>(position);
  }
  void Write32(intptr_t position, uint32_t instruction) {
    return buffer_.Store<uint32_t>(position, instruction);
  }

 public:
  void Emit32(uint32_t instruction) {
    AssemblerBuffer::EnsureCapacity ensured(&buffer_);
    buffer_.Emit<uint32_t>(instruction);
  }
  void Emit64(uint64_t instruction) {
    AssemblerBuffer::EnsureCapacity ensured(&buffer_);
    buffer_.Emit<uint64_t>(instruction);
  }

 protected:
  ExtensionSet extensions_;
  intptr_t far_branch_level_;
};

class Assembler : public MicroAssembler {
 public:
  explicit Assembler(ObjectPoolBuilder* object_pool_builder,
                     intptr_t far_branch_level = 0);
  ~Assembler() {}

  void PushRegister(Register r);
  void PopRegister(Register r);

  void PushRegisterPair(Register r0, Register r1);
  void PopRegisterPair(Register r0, Register r1);

  void PushRegisters(const RegisterSet& registers);
  void PopRegisters(const RegisterSet& registers);
  void PushRegistersAligned(const RegisterSet& registers, intptr_t space);
  void PopRegistersAligned(const RegisterSet& registers, intptr_t space);

  void PushRegistersInOrder(std::initializer_list<Register> regs);

  void PushValueAtOffset(Register base, int32_t offset) { UNIMPLEMENTED(); }

  // Push all registers which are callee-saved according to the ARM64 ABI.
  void PushNativeCalleeSavedRegisters();

  // Pop all registers which are callee-saved according to the ARM64 ABI.
  void PopNativeCalleeSavedRegisters();

  void ExtendValue(Register rd, Register rn, OperandSize sz) override;
  void ExtendAndSmiTagValue(Register rd,
                            Register rn,
                            OperandSize sz = kWordBytes) override;

  void Drop(intptr_t stack_elements) {
    ASSERT(stack_elements >= 0);
    if (stack_elements > 0) {
      AddImmediate(SP, SP, stack_elements * target::kWordSize);
    }
  }

  void Bind(Label* label) override { MicroAssembler::Bind(label); }
  // Unconditional jump to a given label.
  void Jump(Label* label, JumpDistance distance = kFarJump) {
    j(label, distance);
  }
  // Unconditional jump to a given address in register.
  void Jump(Register target) { jr(target); }
  // Unconditional jump to a given address in memory. Clobbers TMP.
  void Jump(const Address& address);

  void LoadMemoryValue(Register dst, Register base, int32_t offset) {
    LoadFromOffset(dst, base, offset, kWordBytes);
  }
  void StoreMemoryValue(Register src, Register base, int32_t offset) {
    StoreToOffset(src, base, offset, kWordBytes);
  }

  void TsanLoadAcquire(Register dst, const Address& address, OperandSize size);
  void TsanStoreRelease(Register src, const Address& address, OperandSize size);
  void TsanFuncEntry(bool preserve_registers = true);
  void TsanFuncExit(bool preserve_registers = true);

  void LoadAcquire(Register dst,
                   const Address& address,
                   OperandSize size = kWordBytes) override;

  void StoreRelease(Register src,
                    const Address& address,
                    OperandSize size = kWordBytes) override;

  void CompareWithMemoryValue(Register value,
                              Address address,
                              OperandSize size = kWordBytes) override;

  // Debugging and bringup support.
  void Breakpoint() override { trap(); }

  void StoreStoreFence() override { fence(kWrite, kWrite); }

  void SetPrologueOffset() {
    if (prologue_offset_ == -1) {
      prologue_offset_ = CodeSize();
    }
  }

  void ReserveAlignedFrameSpace(intptr_t frame_space);

  // In debug mode, this generates code to check that:
  //   FP + kExitLinkSlotFromEntryFp == SP
  // or triggers breakpoint otherwise.
  void EmitEntryFrameVerification();

  // Instruction pattern from entrypoint is used in Dart frame prologs
  // to set up the frame and save a PC which can be used to figure out the
  // RawInstruction object corresponding to the code running in the frame.
  static constexpr intptr_t kEntryPointToPcMarkerOffset = 0;
  static intptr_t EntryPointToPcMarkerOffset() {
    return kEntryPointToPcMarkerOffset;
  }

  // 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); }

  void CompareRegisters(Register rn, Register rm);
  void CompareObjectRegisters(Register rn, Register rm);
  void TestRegisters(Register rn, Register rm);

  // Branches to the given label if the condition holds.
  void BranchIf(Condition condition,
                Label* label,
                JumpDistance distance = kFarJump);
  void BranchIfZero(Register rn,
                    Label* label,
                    JumpDistance distance = kFarJump);
  void BranchIfBit(Register rn,
                   intptr_t bit_number,
                   Condition condition,
                   Label* label,
                   JumpDistance distance = kFarJump);
  // rd <- cond ? 1 : 0
  void SetIf(Condition condition, Register rd);
  // rd <- cond ? 0 : rs
  void ZeroIf(Condition condition, Register rd, Register rs);

  void SmiUntag(Register reg) { SmiUntag(reg, reg); }
  void SmiUntag(Register dst, Register src) { srai(dst, src, kSmiTagSize); }
  void SmiTag(Register reg) override { SmiTag(reg, reg); }
  void SmiTag(Register dst, Register src) { slli(dst, src, kSmiTagSize); }

  // 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);
    BranchIfSmi(value, &done, compiler::Assembler::kNearJump);
    LoadFieldFromOffset(result, value, target::Mint::value_offset(),
                        compiler::kFourBytes);
    Bind(&done);
  }

#if XLEN != 32
  void LoadInt64FromBoxOrSmi(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);
    BranchIfSmi(value, &done, compiler::Assembler::kNearJump);
    LoadFieldFromOffset(result, value, target::Mint::value_offset());
    Bind(&done);
  }
#endif

  void BranchIfNotSmi(Register reg,
                      Label* label,
                      JumpDistance distance = kFarJump);
  void BranchIfSmi(Register reg,
                   Label* label,
                   JumpDistance distance = kFarJump) override;

  void ArithmeticShiftRightImmediate(Register dst,
                                     Register src,
                                     int32_t shift,
                                     OperandSize sz = kWordBytes) override;
  void ArithmeticShiftRightImmediate(Register reg,
                                     int32_t shift,
                                     OperandSize sz = kWordBytes) override {
    ArithmeticShiftRightImmediate(reg, reg, shift);
  }
  void CompareWords(Register reg1,
                    Register reg2,
                    intptr_t offset,
                    Register count,
                    Register temp,
                    Label* equals) override;

  void JumpAndLink(const Code& code,
                   ObjectPoolBuilderEntry::Patchability patchable =
                       ObjectPoolBuilderEntry::kNotPatchable,
                   CodeEntryKind entry_kind = CodeEntryKind::kNormal,
                   ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
                       ObjectPoolBuilderEntry::kSnapshotable);

  void JumpAndLinkPatchable(
      const Code& code,
      CodeEntryKind entry_kind = CodeEntryKind::kNormal,
      ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
          ObjectPoolBuilderEntry::kSnapshotable) {
    JumpAndLink(code, ObjectPoolBuilderEntry::kPatchable, entry_kind,
                snapshot_behavior);
  }

  // Emit a call that shares its object pool entries with other calls
  // that have the same equivalence marker.
  void JumpAndLinkWithEquivalence(
      const Code& code,
      const Object& equivalence,
      CodeEntryKind entry_kind = CodeEntryKind::kNormal);

  void Call(Address target);
  void Call(Register target);
  void Call(const Code& code) { JumpAndLink(code); }

  void CallCFunction(Address target) { Call(target); }
  void CallCFunction(Register target) { Call(target); }

  void AddImmediate(Register dest, intx_t imm) {
    AddImmediate(dest, dest, imm);
  }
  void MulImmediate(Register dest,
                    intx_t imm,
                    OperandSize width = kWordBytes) override {
    MulImmediate(dest, dest, imm, width);
  }
  void AddRegisters(Register dest, Register src) { add(dest, dest, src); }
  void AddScaled(Register dest,
                 Register base,
                 Register index,
                 ScaleFactor scale,
                 int32_t disp) override {
    if (base == kNoRegister || base == ZR) {
      if (scale == TIMES_1) {
        AddImmediate(dest, index, disp);
      } else {
        slli(dest, index, scale);
        AddImmediate(dest, disp);
      }
    } else {
      AddShifted(dest, base, index, scale);
      AddImmediate(dest, disp);
    }
  }
  void AddShifted(Register dest, Register base, Register index, intx_t shift);
  void SubRegisters(Register dest, Register src) { sub(dest, dest, src); }

  // Macros accepting a pp Register argument may attempt to load values from
  // the object pool when possible. Unless you are sure that the untagged object
  // pool pointer is in another register, or that it is not available at all,
  // PP should be passed for pp. `dest` can be TMP2, `rn` cannot. `dest` can be
  // TMP.
  void AddImmediate(Register dest,
                    Register rn,
                    intx_t imm,
                    OperandSize sz = kWordBytes);
  void MulImmediate(Register dest,
                    Register rn,
                    intx_t imm,
                    OperandSize width = kWordBytes);
  void AndImmediate(Register rd,
                    Register rn,
                    intx_t imm,
                    OperandSize sz = kWordBytes) override;
  void AndImmediate(Register rd,
                    intx_t imm,
                    OperandSize sz = kWordBytes) override {
    AndImmediate(rd, rd, imm, sz);
  }
  void AndRegisters(Register dst,
                    Register src1,
                    Register src2 = kNoRegister) override {
    ASSERT(src1 != src2);  // Likely a mistake.
    if (src2 == kNoRegister) {
      src2 = dst;
    }
    and_(dst, src2, src1);
  }
  void OrImmediate(Register rd,
                   Register rn,
                   intx_t imm,
                   OperandSize sz = kWordBytes);
  void OrImmediate(Register rd, intx_t imm) { OrImmediate(rd, rd, imm); }
  void XorImmediate(Register rd,
                    Register rn,
                    intx_t imm,
                    OperandSize sz = kWordBytes);
  void LslImmediate(Register rd,
                    Register rn,
                    int32_t shift,
                    OperandSize sz = kWordBytes) override;
  void LslImmediate(Register rd,
                    int32_t shift,
                    OperandSize sz = kWordBytes) override {
    LslImmediate(rd, rd, shift, sz);
  }
  void LslRegister(Register dst, Register shift) override {
    sll(dst, dst, shift);
  }
  void LsrImmediate(Register rd, int32_t shift) override {
    srli(rd, rd, shift);
  }
  void TestImmediate(Register rn, intx_t imm, OperandSize sz = kWordBytes);
  void CompareImmediate(Register rn,
                        intx_t imm,
                        OperandSize sz = kWordBytes) override;

  Address PrepareLargeOffset(Register base, int32_t offset);
  Address PrepareAtomicOffset(Register base, int32_t offset);
  void Load(Register dest,
            const Address& address,
            OperandSize sz = kWordBytes) override;
  // 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 dest,
                          Register base,
                          int32_t payload_offset,
                          Register index,
                          ScaleFactor scale,
                          OperandSize sz = kWordBytes) override;
  void LoadS(FRegister dest, const Address& address) {
    LoadSFromOffset(dest, address.base(), address.offset());
  }
  void LoadD(FRegister dest, const Address& address) {
    LoadDFromOffset(dest, address.base(), address.offset());
  }
  void LoadSFromOffset(FRegister dest, Register base, int32_t offset);
  void LoadDFromOffset(FRegister dest, Register base, int32_t offset);
  void LoadSFieldFromOffset(FRegister dest, Register base, int32_t offset) {
    LoadSFromOffset(dest, base, offset - kHeapObjectTag);
  }
  void LoadDFieldFromOffset(FRegister dest, Register base, int32_t offset) {
    LoadDFromOffset(dest, base, offset - kHeapObjectTag);
  }

  void LoadFromStack(Register dst, intptr_t depth);
  void StoreToStack(Register src, intptr_t depth);
  void CompareToStack(Register src, intptr_t depth);

  void Store(Register src,
             const Address& address,
             OperandSize sz = kWordBytes) override;
  void StoreZero(const Address& address, Register temp = kNoRegister) {
    Store(ZR, address);
  }
  void StoreS(FRegister src, const Address& address) {
    StoreSToOffset(src, address.base(), address.offset());
  }
  void StoreD(FRegister src, const Address& address) {
    StoreDToOffset(src, address.base(), address.offset());
  }
  void StoreSToOffset(FRegister src, Register base, int32_t offset);
  void StoreSFieldToOffset(FRegister src, Register base, int32_t offset) {
    StoreSToOffset(src, base, offset - kHeapObjectTag);
  }
  void StoreDToOffset(FRegister src, Register base, int32_t offset);
  void StoreDFieldToOffset(FRegister src, Register base, int32_t offset) {
    StoreDToOffset(src, base, offset - kHeapObjectTag);
  }

  void LoadUnboxedDouble(FpuRegister dst, Register base, int32_t offset) {
    LoadDFromOffset(dst, base, offset);
  }
  void StoreUnboxedDouble(FpuRegister src, Register base, int32_t offset) {
    StoreDToOffset(src, base, offset);
  }
  void MoveUnboxedDouble(FpuRegister dst, FpuRegister src) { fmvd(dst, src); }

  void LoadUnboxedSimd128(FpuRegister dst, Register base, int32_t offset) {
    // No single register SIMD on RISC-V.
    UNREACHABLE();
  }
  void StoreUnboxedSimd128(FpuRegister src, Register base, int32_t offset) {
    // No single register SIMD on RISC-V.
    UNREACHABLE();
  }
  void MoveUnboxedSimd128(FpuRegister dst, FpuRegister src) {
    // No single register SIMD on RISC-V.
    UNREACHABLE();
  }

  void InitializeHeader(Register tags, Register object) {
    sx(tags, FieldAddress(object, target::Object::tags_offset()));
#if defined(TARGET_HAS_FAST_WRITE_WRITE_FENCE)
    fence(kWrite, kWrite);
#endif
  }
  void InitializeHeaderUntagged(Register tags, Register object) {
    sx(tags, Address(object, target::Object::tags_offset()));
#if defined(TARGET_HAS_FAST_WRITE_WRITE_FENCE)
    fence(kWrite, kWrite);
#endif
  }

  void StoreBarrier(Register object,
                    Register value,
                    CanBeSmi can_value_be_smi,
                    Register scratch) override;
  void ArrayStoreBarrier(Register object,
                         Register slot,
                         Register value,
                         CanBeSmi can_value_be_smi,
                         Register scratch) override;
  void VerifyStoreNeedsNoWriteBarrier(Register object, Register value) override;

  void StoreObjectIntoObjectNoBarrier(
      Register object,
      const Address& dest,
      const Object& value,
      MemoryOrder memory_order = kRelaxedNonAtomic,
      OperandSize size = kWordBytes) override;

  // Stores a non-tagged value into a heap object.
  void StoreInternalPointer(Register object,
                            const Address& dest,
                            Register value);

  // Object pool, loading from pool, etc.
  void LoadPoolPointer(Register pp = PP);

  bool constant_pool_allowed() const { return constant_pool_allowed_; }
  void set_constant_pool_allowed(bool b) { constant_pool_allowed_ = b; }

  bool CanLoadFromObjectPool(const Object& object) const;
  void LoadNativeEntry(Register dst,
                       const ExternalLabel* label,
                       ObjectPoolBuilderEntry::Patchability patchable);
  void LoadIsolate(Register dst);
  void LoadIsolateGroup(Register dst);

  // Note: the function never clobbers TMP, TMP2 scratch registers.
  void LoadObject(Register dst, const Object& obj) {
    LoadObjectHelper(dst, obj, false);
  }
  // Note: the function never clobbers TMP, TMP2 scratch registers.
  void LoadUniqueObject(
      Register dst,
      const Object& obj,
      ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
          ObjectPoolBuilderEntry::kSnapshotable) {
    LoadObjectHelper(dst, obj, true, snapshot_behavior);
  }
  // Note: the function never clobbers TMP, TMP2 scratch registers.
  void LoadImmediate(Register reg, intx_t imm) override;

  void LoadSImmediate(FRegister reg, float imms);
  void LoadDImmediate(FRegister reg, double immd);
  void LoadQImmediate(FRegister reg, simd128_value_t immq);

  // Load word from pool from the given offset using encoding that
  // InstructionPattern::DecodeLoadWordFromPool can decode.
  //
  // Note: the function never clobbers TMP, TMP2 scratch registers.
  void LoadWordFromPoolIndex(Register dst, intptr_t index, Register pp = PP);

  // Store word to pool at the given offset.
  //
  // Note: clobbers TMP, does not clobber TMP2.
  void StoreWordToPoolIndex(Register src, intptr_t index, Register pp = PP);

  void PushObject(const Object& object) {
    if (IsSameObject(compiler::NullObject(), object)) {
      PushRegister(NULL_REG);
    } else if (target::IsSmi(object) && (target::ToRawSmi(object) == 0)) {
      PushRegister(ZR);
    } else {
      LoadObject(TMP, object);
      PushRegister(TMP);
    }
  }
  void PushImmediate(int64_t immediate) {
    if (immediate == 0) {
      PushRegister(ZR);
    } else {
      LoadImmediate(TMP, immediate);
      PushRegister(TMP);
    }
  }
  void CompareObject(Register reg, const Object& object);

  void ExtractBitField(Register dst,
                       Register src,
                       intptr_t low_bit,
                       intptr_t width) override;

  void ExtractClassIdFromTags(Register result, Register tags);
  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);
  void LoadClassById(Register result, Register class_id);
  void CompareClassId(Register object,
                      intptr_t class_id,
                      Register scratch = kNoRegister);
  // Note: input and output registers must be different.
  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;

  void EnterFrame(intptr_t frame_size);
  void LeaveFrame();
  void Ret() { ret(); }

  // Sets the return address to [value] as if there was a call.
  // On RISC-V sets RA.
  void SetReturnAddress(Register value) { mv(RA, value); }

  // Emit code to transition between generated mode and native mode.
  //
  // These require and ensure that CSP and SP are equal and aligned and require
  // a scratch register (in addition to TMP/TMP2).

  void TransitionGeneratedToNative(Register destination_address,
                                   Register new_exit_frame,
                                   Register new_exit_through_ffi,
                                   bool enter_safepoint);
  void TransitionNativeToGenerated(Register scratch,
                                   bool exit_safepoint,
                                   bool set_tag = true);
  void VerifyInGenerated(Register scratch);
  void VerifyNotInGenerated(Register scratch);
  void EnterFullSafepoint(Register scratch);
  void ExitFullSafepoint(Register scratch);

  void CheckFpSpDist(intptr_t fp_sp_dist);

  void CheckCodePointer();
  void RestoreCodePointer();
  void RestorePoolPointer();

  // Restores the values of the registers that are blocked to cache some values
  // e.g. WRITE_BARRIER_STATE and NULL_REG.
  void RestorePinnedRegisters();

  void SetupGlobalPoolAndDispatchTable();

  void EnterDartFrame(intptr_t frame_size, Register new_pp = kNoRegister);
  void EnterOsrFrame(intptr_t extra_size, Register new_pp = kNoRegister);
  void LeaveDartFrame();
  void LeaveDartFrame(intptr_t fp_sp_dist);

  // For non-leaf runtime calls. For leaf runtime calls, use LeafRuntimeScope,
  void CallRuntime(const RuntimeEntry& entry,
                   intptr_t argument_count,
                   bool tsan_enter_exit = true);

  // Set up a stub frame so that the stack traversal code can easily identify
  // a stub frame.
  void EnterStubFrame() { EnterDartFrame(0); }
  void LeaveStubFrame() { LeaveDartFrame(); }

  // 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;

  // 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;
    lx(tmp, Address(top, 0));
    subi(tmp, tmp, kAllocationCanary);
    beqz(tmp, &okay, Assembler::kNearJump);
    Stop("Allocation canary");
    Bind(&okay);
#endif
  }
  void WriteAllocationCanary(Register top) {
#if defined(DEBUG)
    ASSERT(top != TMP);
    li(TMP, kAllocationCanary);
    sx(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 "bl <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(intptr_t offset_into_target = 0);

  // This emits an PC-relative tail call of the form "b <offset>".
  //
  // See also above for the pc-relative call.
  void GenerateUnRelocatedPcRelativeTailCall(intptr_t offset_into_target = 0);

  static bool AddressCanHoldConstantIndex(const Object& constant,
                                          bool is_external,
                                          intptr_t cid,
                                          intptr_t index_scale);

  Address ElementAddressForIntIndex(bool is_external,
                                    intptr_t cid,
                                    intptr_t index_scale,
                                    Register array,
                                    intptr_t index) const;
  void ComputeElementAddressForIntIndex(Register address,
                                        bool is_external,
                                        intptr_t cid,
                                        intptr_t index_scale,
                                        Register array,
                                        intptr_t index);
  Address ElementAddressForRegIndex(bool is_external,
                                    intptr_t cid,
                                    intptr_t index_scale,
                                    bool index_unboxed,
                                    Register array,
                                    Register index,
                                    Register temp);

  void ComputeElementAddressForRegIndex(Register address,
                                        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);
  }

  // Returns object data offset for address calculation; for heap objects also
  // accounts for the tag.
  static int32_t HeapDataOffset(bool is_external, intptr_t cid) {
    return is_external
               ? 0
               : (target::Instance::DataOffsetFor(cid) - kHeapObjectTag);
  }

  void AddImmediateBranchOverflow(Register rd,
                                  Register rs1,
                                  intx_t imm,
                                  Label* overflow);
  void SubtractImmediateBranchOverflow(Register rd,
                                       Register rs1,
                                       intx_t imm,
                                       Label* overflow);
  void MultiplyImmediateBranchOverflow(Register rd,
                                       Register rs1,
                                       intx_t imm,
                                       Label* overflow);
  void AddBranchOverflow(Register rd,
                         Register rs1,
                         Register rs2,
                         Label* overflow);
  void SubtractBranchOverflow(Register rd,
                              Register rs1,
                              Register rs2,
                              Label* overflow);
  void MultiplyBranchOverflow(Register rd,
                              Register rs1,
                              Register rs2,
                              Label* overflow);

  // Clobbers [rs].
  void CountLeadingZeroes(Register rd, Register rs);

 private:
  bool constant_pool_allowed_;

  enum DeferredCompareType {
    kNone,
    kCompareReg,
    kCompareImm,
    kTestReg,
    kTestImm,
  };
  DeferredCompareType deferred_compare_ = kNone;
  Register deferred_left_ = kNoRegister;
  Register deferred_reg_ = kNoRegister;
  intptr_t deferred_imm_ = 0;

  // Note: the function never clobbers TMP, TMP2 scratch registers.
  void LoadObjectHelper(
      Register dst,
      const Object& obj,
      bool is_unique,
      ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
          ObjectPoolBuilderEntry::kSnapshotable);

  void JumpAndLink(intptr_t target_code_pool_index, CodeEntryKind entry_kind);

  friend class dart::FlowGraphCompiler;
  std::function<void(Register reg)> generate_invoke_write_barrier_wrapper_;
  std::function<void()> generate_invoke_array_write_barrier_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(Assembler);
};

}  // namespace compiler
}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_RISCV_H_
