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

#include "vm/globals.h"  // NOLINT
#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)

#define SHOULD_NOT_INCLUDE_RUNTIME

#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/backend/locations.h"
#include "vm/cpu.h"
#include "vm/instructions.h"
#include "vm/simulator.h"
#include "vm/tags.h"

namespace dart {

DECLARE_FLAG(bool, check_code_pointer);
DECLARE_FLAG(bool, precompiled_mode);

DEFINE_FLAG(int, far_branch_level, 0, "Always use far branches");

namespace compiler {

MicroAssembler::MicroAssembler(ObjectPoolBuilder* object_pool_builder,
                               intptr_t far_branch_level,
                               ExtensionSet extensions)
    : AssemblerBase(object_pool_builder),
      extensions_(extensions),
      far_branch_level_(far_branch_level) {
  ASSERT(far_branch_level >= 0);
  ASSERT(far_branch_level <= 2);
}

MicroAssembler::~MicroAssembler() {}

void MicroAssembler::Bind(Label* label) {
  ASSERT(!label->IsBound());
  intptr_t target_position = Position();
  intptr_t branch_position;

#define BIND(head, update)                                                     \
  branch_position = label->head;                                               \
  while (branch_position >= 0) {                                               \
    ASSERT(Utils::IsAligned(branch_position, Supports(RV_C) ? 2 : 4));         \
    intptr_t new_offset = target_position - branch_position;                   \
    ASSERT(Utils::IsAligned(new_offset, Supports(RV_C) ? 2 : 4));              \
    intptr_t old_offset = update(branch_position, new_offset);                 \
    if (old_offset == 0) break;                                                \
    branch_position -= old_offset;                                             \
  }                                                                            \
  label->head = -1

  BIND(unresolved_cb_, UpdateCBOffset);
  BIND(unresolved_cj_, UpdateCJOffset);
  BIND(unresolved_b_, UpdateBOffset);
  BIND(unresolved_j_, UpdateJOffset);
  BIND(unresolved_far_, UpdateFarOffset);

  label->BindTo(target_position);
}

intptr_t MicroAssembler::UpdateCBOffset(intptr_t branch_position,
                                        intptr_t new_offset) {
  CInstr instr(Read16(branch_position));
  ASSERT((instr.opcode() == C_BEQZ) || (instr.opcode() == C_BNEZ));
  intptr_t old_offset = instr.b_imm();
  if (!IsCBImm(new_offset)) {
    FATAL("Incorrect Assembler::kNearJump");
  }
  Write16(branch_position,
          instr.opcode() | EncodeCRs1p(instr.rs1p()) | EncodeCBImm(new_offset));
  return old_offset;
}

intptr_t MicroAssembler::UpdateCJOffset(intptr_t branch_position,
                                        intptr_t new_offset) {
  CInstr instr(Read16(branch_position));
  ASSERT((instr.opcode() == C_J) || (instr.opcode() == C_JAL));
  intptr_t old_offset = instr.j_imm();
  if (!IsCJImm(new_offset)) {
    FATAL("Incorrect Assembler::kNearJump");
  }
  Write16(branch_position, instr.opcode() | EncodeCJImm(new_offset));
  return old_offset;
}

intptr_t MicroAssembler::UpdateBOffset(intptr_t branch_position,
                                       intptr_t new_offset) {
  Instr instr(Read32(branch_position));
  ASSERT(instr.opcode() == BRANCH);
  intptr_t old_offset = instr.btype_imm();
  if (!IsBTypeImm(new_offset)) {
    BailoutWithBranchOffsetError();
  }
  Write32(branch_position, EncodeRs2(instr.rs2()) | EncodeRs1(instr.rs1()) |
                               EncodeFunct3(instr.funct3()) |
                               EncodeOpcode(instr.opcode()) |
                               EncodeBTypeImm(new_offset));
  return old_offset;
}

intptr_t MicroAssembler::UpdateJOffset(intptr_t branch_position,
                                       intptr_t new_offset) {
  Instr instr(Read32(branch_position));
  ASSERT(instr.opcode() == JAL);
  intptr_t old_offset = instr.jtype_imm();
  if (!IsJTypeImm(new_offset)) {
    BailoutWithBranchOffsetError();
  }
  Write32(branch_position, EncodeRd(instr.rd()) | EncodeOpcode(instr.opcode()) |
                               EncodeJTypeImm(new_offset));
  return old_offset;
}

intptr_t MicroAssembler::UpdateFarOffset(intptr_t branch_position,
                                         intptr_t new_offset) {
  Instr auipc_instr(Read32(branch_position));
  ASSERT(auipc_instr.opcode() == AUIPC);
  ASSERT(auipc_instr.rd() == FAR_TMP);
  Instr jr_instr(Read32(branch_position + 4));
  ASSERT(jr_instr.opcode() == JALR);
  ASSERT(jr_instr.rd() == ZR);
  ASSERT(jr_instr.funct3() == F3_0);
  ASSERT(jr_instr.rs1() == FAR_TMP);
  intptr_t old_offset = auipc_instr.utype_imm() + jr_instr.itype_imm();
  intx_t lo = ImmLo(new_offset);
  intx_t hi = ImmHi(new_offset);
  if (!IsUTypeImm(hi)) {
    FATAL("Jump/branch distance exceeds 2GB!");
  }
  Write32(branch_position,
          EncodeUTypeImm(hi) | EncodeRd(FAR_TMP) | EncodeOpcode(AUIPC));
  Write32(branch_position + 4, EncodeITypeImm(lo) | EncodeRs1(FAR_TMP) |
                                   EncodeFunct3(F3_0) | EncodeRd(ZR) |
                                   EncodeOpcode(JALR));
  return old_offset;
}

void MicroAssembler::lui(Register rd, intptr_t imm) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C) && (rd != ZR) && (rd != SP) && IsCUImm(imm)) {
    c_lui(rd, imm);
    return;
  }
  EmitUType(imm, rd, LUI);
}

void MicroAssembler::lui_fixed(Register rd, intptr_t imm) {
  ASSERT(Supports(RV_I));
  EmitUType(imm, rd, LUI);
}

void MicroAssembler::auipc(Register rd, intptr_t imm) {
  ASSERT(Supports(RV_I));
  EmitUType(imm, rd, AUIPC);
}

void MicroAssembler::jal(Register rd, Label* label, JumpDistance distance) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C) &&
      ((distance == kNearJump) ||
       (label->IsBound() && IsCJImm(label->Position() - Position())))) {
    if (rd == ZR) {
      c_j(label);
      return;
    }
#if XLEN == 32
    if (rd == RA) {
      c_jal(label);
      return;
    }
#endif  // XLEN == 32
  }
  EmitJump(rd, label, JAL, distance);
}

void MicroAssembler::jalr(Register rd, Register rs1, intptr_t offset) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if (rs1 != ZR && offset == 0) {
      if (rd == ZR) {
        c_jr(rs1);
        return;
      } else if (rd == RA) {
        c_jalr(rs1);
        return;
      }
    }
  }
  EmitIType(offset, rs1, F3_0, rd, JALR);
}

void MicroAssembler::jalr_fixed(Register rd, Register rs1, intptr_t offset) {
  ASSERT(Supports(RV_I));
  EmitIType(offset, rs1, F3_0, rd, JALR);
}

void MicroAssembler::beq(Register rs1,
                         Register rs2,
                         Label* label,
                         JumpDistance distance) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C) &&
      ((distance == kNearJump) ||
       (label->IsBound() && IsCBImm(label->Position() - Position())))) {
    if ((rs1 == ZR) && IsCRs1p(rs2)) {
      c_beqz(rs2, label);
      return;
    } else if ((rs2 == ZR) && IsCRs1p(rs1)) {
      c_beqz(rs1, label);
      return;
    }
  }
  EmitBranch(rs1, rs2, label, BEQ, distance);
}

void MicroAssembler::bne(Register rs1,
                         Register rs2,
                         Label* label,
                         JumpDistance distance) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C) &&
      ((distance == kNearJump) ||
       (label->IsBound() && IsCBImm(label->Position() - Position())))) {
    if ((rs1 == ZR) && IsCRs1p(rs2)) {
      c_bnez(rs2, label);
      return;
    } else if ((rs2 == ZR) && IsCRs1p(rs1)) {
      c_bnez(rs1, label);
      return;
    }
  }
  EmitBranch(rs1, rs2, label, BNE, distance);
}

void MicroAssembler::blt(Register rs1,
                         Register rs2,
                         Label* label,
                         JumpDistance distance) {
  ASSERT(Supports(RV_I));
  EmitBranch(rs1, rs2, label, BLT, distance);
}

void MicroAssembler::bge(Register rs1,
                         Register rs2,
                         Label* label,
                         JumpDistance distance) {
  ASSERT(Supports(RV_I));
  EmitBranch(rs1, rs2, label, BGE, distance);
}

void MicroAssembler::bltu(Register rs1,
                          Register rs2,
                          Label* label,
                          JumpDistance distance) {
  ASSERT(Supports(RV_I));
  EmitBranch(rs1, rs2, label, BLTU, distance);
}

void MicroAssembler::bgeu(Register rs1,
                          Register rs2,
                          Label* label,
                          JumpDistance distance) {
  EmitBranch(rs1, rs2, label, BGEU, distance);
}

void MicroAssembler::lb(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  EmitIType(addr.offset(), addr.base(), LB, rd, LOAD);
}

void MicroAssembler::lh(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_Zcb) && IsCRdp(rd) && IsCRs1p(addr.base()) &&
      IsCMem2Imm(addr.offset())) {
    c_lh(rd, addr);
    return;
  }
  EmitIType(addr.offset(), addr.base(), LH, rd, LOAD);
}

void MicroAssembler::lw(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd != ZR) && (addr.base() == SP) && IsCSPLoad4Imm(addr.offset())) {
      c_lwsp(rd, addr);
      return;
    }
    if (IsCRdp(rd) && IsCRs1p(addr.base()) && IsCMem4Imm(addr.offset())) {
      c_lw(rd, addr);
      return;
    }
  }
  EmitIType(addr.offset(), addr.base(), LW, rd, LOAD);
}

void MicroAssembler::lbu(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_Zcb) && IsCRdp(rd) && IsCRs1p(addr.base()) &&
      IsCMem1Imm(addr.offset())) {
    c_lbu(rd, addr);
    return;
  }
  EmitIType(addr.offset(), addr.base(), LBU, rd, LOAD);
}

void MicroAssembler::lhu(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_Zcb) && IsCRdp(rd) && IsCRs1p(addr.base()) &&
      IsCMem2Imm(addr.offset())) {
    c_lhu(rd, addr);
    return;
  }
  EmitIType(addr.offset(), addr.base(), LHU, rd, LOAD);
}

void MicroAssembler::sb(Register rs2, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_Zcb) && IsCRs2p(rs2) && IsCRs1p(addr.base()) &&
      IsCMem1Imm(addr.offset())) {
    c_sb(rs2, addr);
    return;
  }
  EmitSType(addr.offset(), rs2, addr.base(), SB, STORE);
}

void MicroAssembler::sh(Register rs2, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_Zcb) && IsCRs2p(rs2) && IsCRs1p(addr.base()) &&
      IsCMem2Imm(addr.offset())) {
    c_sh(rs2, addr);
    return;
  }
  EmitSType(addr.offset(), rs2, addr.base(), SH, STORE);
}

void MicroAssembler::sw(Register rs2, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((addr.base() == SP) && IsCSPStore4Imm(addr.offset())) {
      c_swsp(rs2, addr);
      return;
    }
    if (IsCRs2p(rs2) && IsCRs1p(addr.base()) && IsCMem4Imm(addr.offset())) {
      c_sw(rs2, addr);
      return;
    }
  }
  EmitSType(addr.offset(), rs2, addr.base(), SW, STORE);
}

void MicroAssembler::addi(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd != ZR) && (rs1 == ZR) && IsCIImm(imm)) {
      c_li(rd, imm);
      return;
    }
    if ((rd == rs1) && IsCIImm(imm) && (imm != 0)) {
      c_addi(rd, rs1, imm);
      return;
    }
    if ((rd == SP) && (rs1 == SP) && IsCI16Imm(imm) && (imm != 0)) {
      c_addi16sp(rd, rs1, imm);
      return;
    }
    if (IsCRdp(rd) && (rs1 == SP) && IsCI4SPNImm(imm) && (imm != 0)) {
      c_addi4spn(rd, rs1, imm);
      return;
    }
    if (imm == 0) {
      if ((rd == ZR) && (rs1 == ZR)) {
        c_nop();
        return;
      }
      if ((rd != ZR) && (rs1 != ZR)) {
        c_mv(rd, rs1);
        return;
      }
    }
  }
  EmitIType(imm, rs1, ADDI, rd, OPIMM);
}

void MicroAssembler::slti(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  EmitIType(imm, rs1, SLTI, rd, OPIMM);
}

void MicroAssembler::sltiu(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  EmitIType(imm, rs1, SLTIU, rd, OPIMM);
}

void MicroAssembler::xori(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_Zcb)) {
    if ((rd == rs1) && IsCRs1p(rs1) && (imm == -1)) {
      c_not(rd, rs1);
      return;
    }
  }
  EmitIType(imm, rs1, XORI, rd, OPIMM);
}

void MicroAssembler::ori(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  EmitIType(imm, rs1, ORI, rd, OPIMM);
}

void MicroAssembler::andi(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCIImm(imm)) {
      c_andi(rd, rs1, imm);
      return;
    }
    if (Supports(RV_Zcb)) {
      if ((rd == rs1) && IsCRs1p(rs1) && (imm == 0xFF)) {
        c_zextb(rd, rs1);
        return;
      }
    }
  }
  EmitIType(imm, rs1, ANDI, rd, OPIMM);
}

void MicroAssembler::slli(Register rd, Register rs1, intptr_t shamt) {
  ASSERT((shamt > 0) && (shamt < XLEN));
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && (rd != ZR) && IsCShamt(shamt)) {
      c_slli(rd, rs1, shamt);
      return;
    }
  }
  EmitRType(F7_0, shamt, rs1, SLLI, rd, OPIMM);
}

void MicroAssembler::srli(Register rd, Register rs1, intptr_t shamt) {
  ASSERT((shamt > 0) && (shamt < XLEN));
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCShamt(shamt)) {
      c_srli(rd, rs1, shamt);
      return;
    }
  }
  EmitRType(F7_0, shamt, rs1, SRI, rd, OPIMM);
}

void MicroAssembler::srai(Register rd, Register rs1, intptr_t shamt) {
  ASSERT((shamt > 0) && (shamt < XLEN));
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCShamt(shamt)) {
      c_srai(rd, rs1, shamt);
      return;
    }
  }
  EmitRType(SRA, shamt, rs1, SRI, rd, OPIMM);
}

void MicroAssembler::add(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && (rs2 != ZR)) {
      c_add(rd, rs1, rs2);
      return;
    }
    if ((rd == rs2) && (rs1 != ZR)) {
      c_add(rd, rs2, rs1);
      return;
    }
  }
  EmitRType(F7_0, rs2, rs1, ADD, rd, OP);
}

void MicroAssembler::sub(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_sub(rd, rs1, rs2);
      return;
    }
  }
  EmitRType(SUB, rs2, rs1, ADD, rd, OP);
}

void MicroAssembler::sll(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, rs2, rs1, SLL, rd, OP);
}

void MicroAssembler::slt(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, rs2, rs1, SLT, rd, OP);
}

void MicroAssembler::sltu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, rs2, rs1, SLTU, rd, OP);
}

void MicroAssembler::xor_(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_xor(rd, rs1, rs2);
      return;
    }
    if ((rd == rs2) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_xor(rd, rs2, rs1);
      return;
    }
  }
  EmitRType(F7_0, rs2, rs1, XOR, rd, OP);
}

void MicroAssembler::srl(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, rs2, rs1, SR, rd, OP);
}

void MicroAssembler::sra(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(SRA, rs2, rs1, SR, rd, OP);
}

void MicroAssembler::or_(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_or(rd, rs1, rs2);
      return;
    }
    if ((rd == rs2) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_or(rd, rs2, rs1);
      return;
    }
  }
  EmitRType(F7_0, rs2, rs1, OR, rd, OP);
}

void MicroAssembler::and_(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_and(rd, rs1, rs2);
      return;
    }
    if ((rd == rs2) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_and(rd, rs2, rs1);
      return;
    }
  }
  EmitRType(F7_0, rs2, rs1, AND, rd, OP);
}

void MicroAssembler::fence(HartEffects predecessor, HartEffects successor) {
  ASSERT((predecessor & kAll) == predecessor);
  ASSERT((successor & kAll) == successor);
  ASSERT(Supports(RV_I));
  EmitIType((predecessor << 4) | successor, ZR, FENCE, ZR, MISCMEM);
}

void MicroAssembler::fencei() {
  ASSERT(Supports(RV_I));
  EmitIType(0, ZR, FENCEI, ZR, MISCMEM);
}

void MicroAssembler::ecall() {
  ASSERT(Supports(RV_I));
  EmitIType(ECALL, ZR, PRIV, ZR, SYSTEM);
}
void MicroAssembler::ebreak() {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    c_ebreak();
    return;
  }
  EmitIType(EBREAK, ZR, PRIV, ZR, SYSTEM);
}
void MicroAssembler::SimulatorPrintObject(Register rs1) {
  ASSERT(Supports(RV_I));
  EmitIType(ECALL, rs1, PRIV, ZR, SYSTEM);
}

void MicroAssembler::csrrw(Register rd, uint32_t csr, Register rs1) {
  ASSERT(Supports(RV_I));
  EmitIType(csr, rs1, CSRRW, rd, SYSTEM);
}

void MicroAssembler::csrrs(Register rd, uint32_t csr, Register rs1) {
  ASSERT(Supports(RV_I));
  EmitIType(csr, rs1, CSRRS, rd, SYSTEM);
}

void MicroAssembler::csrrc(Register rd, uint32_t csr, Register rs1) {
  ASSERT(Supports(RV_I));
  EmitIType(csr, rs1, CSRRC, rd, SYSTEM);
}

void MicroAssembler::csrrwi(Register rd, uint32_t csr, uint32_t imm) {
  ASSERT(Supports(RV_I));
  EmitIType(csr, Register(imm), CSRRWI, rd, SYSTEM);
}

void MicroAssembler::csrrsi(Register rd, uint32_t csr, uint32_t imm) {
  ASSERT(Supports(RV_I));
  EmitIType(csr, Register(imm), CSRRSI, rd, SYSTEM);
}

void MicroAssembler::csrrci(Register rd, uint32_t csr, uint32_t imm) {
  ASSERT(Supports(RV_I));
  EmitIType(csr, Register(imm), CSRRCI, rd, SYSTEM);
}

void MicroAssembler::trap() {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    Emit16(0);  // Permanently reserved illegal instruction.
  } else {
    Emit32(0);  // Permanently reserved illegal instruction.
  }
}

#if XLEN >= 64
void MicroAssembler::lwu(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  EmitIType(addr.offset(), addr.base(), LWU, rd, LOAD);
}

void MicroAssembler::ld(Register rd, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd != ZR) && (addr.base() == SP) && IsCSPLoad8Imm(addr.offset())) {
      c_ldsp(rd, addr);
      return;
    }
    if (IsCRdp(rd) && IsCRs1p(addr.base()) && IsCMem8Imm(addr.offset())) {
      c_ld(rd, addr);
      return;
    }
  }
  EmitIType(addr.offset(), addr.base(), LD, rd, LOAD);
}

void MicroAssembler::sd(Register rs2, Address addr) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((addr.base() == SP) && IsCSPStore8Imm(addr.offset())) {
      c_sdsp(rs2, addr);
      return;
    }
    if (IsCRs2p(rs2) && IsCRs1p(addr.base()) && IsCMem8Imm(addr.offset())) {
      c_sd(rs2, addr);
      return;
    }
  }
  EmitSType(addr.offset(), rs2, addr.base(), SD, STORE);
}

void MicroAssembler::addiw(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd != ZR) && (rs1 == ZR) && IsCIImm(imm)) {
      c_li(rd, imm);
      return;
    }
    if ((rd == rs1) && (rd != ZR) && IsCIImm(imm)) {
      c_addiw(rd, rs1, imm);
      return;
    }
  }
  EmitIType(imm, rs1, ADDI, rd, OPIMM32);
}

void MicroAssembler::slliw(Register rd, Register rs1, intptr_t shamt) {
  ASSERT((shamt > 0) && (shamt < 32));
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, shamt, rs1, SLLI, rd, OPIMM32);
}

void MicroAssembler::srliw(Register rd, Register rs1, intptr_t shamt) {
  ASSERT((shamt > 0) && (shamt < 32));
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, shamt, rs1, SRI, rd, OPIMM32);
}

void MicroAssembler::sraiw(Register rd, Register rs1, intptr_t shamt) {
  ASSERT((shamt > 0) && (shamt < XLEN));
  ASSERT(Supports(RV_I));
  EmitRType(SRA, shamt, rs1, SRI, rd, OPIMM32);
}

void MicroAssembler::addw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_addw(rd, rs1, rs2);
      return;
    }
    if ((rd == rs2) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_addw(rd, rs2, rs1);
      return;
    }
  }
  EmitRType(F7_0, rs2, rs1, ADD, rd, OP32);
}

void MicroAssembler::subw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  if (Supports(RV_C)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_subw(rd, rs1, rs2);
      return;
    }
  }
  EmitRType(SUB, rs2, rs1, ADD, rd, OP32);
}

void MicroAssembler::sllw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, rs2, rs1, SLL, rd, OP32);
}

void MicroAssembler::srlw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(F7_0, rs2, rs1, SR, rd, OP32);
}
void MicroAssembler::sraw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_I));
  EmitRType(SRA, rs2, rs1, SR, rd, OP32);
}
#endif  // XLEN >= 64

void MicroAssembler::mul(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  if (Supports(RV_Zcb)) {
    if ((rd == rs1) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_mul(rd, rs1, rs2);
      return;
    }
    if ((rd == rs2) && IsCRs1p(rs1) && IsCRs2p(rs2)) {
      c_mul(rd, rs2, rs1);
      return;
    }
  }
  EmitRType(MULDIV, rs2, rs1, MUL, rd, OP);
}

void MicroAssembler::mulh(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, MULH, rd, OP);
}

void MicroAssembler::mulhsu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, MULHSU, rd, OP);
}

void MicroAssembler::mulhu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, MULHU, rd, OP);
}

void MicroAssembler::div(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, DIV, rd, OP);
}

void MicroAssembler::divu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, DIVU, rd, OP);
}

void MicroAssembler::rem(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, REM, rd, OP);
}

void MicroAssembler::remu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, REMU, rd, OP);
}

#if XLEN >= 64
void MicroAssembler::mulw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, MULW, rd, OP32);
}

void MicroAssembler::divw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, DIVW, rd, OP32);
}

void MicroAssembler::divuw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, DIVUW, rd, OP32);
}

void MicroAssembler::remw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, REMW, rd, OP32);
}

void MicroAssembler::remuw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_M));
  EmitRType(MULDIV, rs2, rs1, REMUW, rd, OP32);
}
#endif  // XLEN >= 64

void MicroAssembler::lrw(Register rd, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(LR, order, ZR, addr.base(), WIDTH32, rd, AMO);
}
void MicroAssembler::scw(Register rd,
                         Register rs2,
                         Address addr,
                         std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(SC, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amoswapw(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOSWAP, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amoaddw(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOADD, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amoxorw(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOXOR, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amoandw(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOAND, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amoorw(Register rd,
                            Register rs2,
                            Address addr,
                            std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOOR, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amominw(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMIN, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amomaxw(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMAX, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amominuw(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMINU, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amomaxuw(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMAXU, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

#if XLEN >= 64
void MicroAssembler::lrd(Register rd, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(LR, order, ZR, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::scd(Register rd,
                         Register rs2,
                         Address addr,
                         std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(SC, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amoswapd(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOSWAP, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amoaddd(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOADD, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amoxord(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOXOR, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amoandd(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOAND, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amoord(Register rd,
                            Register rs2,
                            Address addr,
                            std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOOR, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amomind(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMIN, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amomaxd(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMAX, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amominud(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMINU, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::amomaxud(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_A));
  EmitRType(AMOMAXU, order, rs2, addr.base(), WIDTH64, rd, AMO);
}
#endif  // XLEN >= 64

void MicroAssembler::flw(FRegister rd, Address addr) {
  ASSERT(Supports(RV_F));
#if XLEN == 32
  if (Supports(RV_C)) {
    if ((addr.base() == SP) && IsCSPLoad4Imm(addr.offset())) {
      c_flwsp(rd, addr);
      return;
    }
    if (IsCFRdp(rd) && IsCRs1p(addr.base()) && IsCMem4Imm(addr.offset())) {
      c_flw(rd, addr);
      return;
    }
  }
#endif  // XLEN == 32
  EmitIType(addr.offset(), addr.base(), S, rd, LOADFP);
}

void MicroAssembler::fsw(FRegister rs2, Address addr) {
  ASSERT(Supports(RV_F));
#if XLEN == 32
  if (Supports(RV_C)) {
    if ((addr.base() == SP) && IsCSPStore4Imm(addr.offset())) {
      c_fswsp(rs2, addr);
      return;
    }
    if (IsCFRs2p(rs2) && IsCRs1p(addr.base()) && IsCMem4Imm(addr.offset())) {
      c_fsw(rs2, addr);
      return;
    }
  }
#endif  // XLEN == 32
  EmitSType(addr.offset(), rs2, addr.base(), S, STOREFP);
}

void MicroAssembler::fmadds(FRegister rd,
                            FRegister rs1,
                            FRegister rs2,
                            FRegister rs3,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitR4Type(rs3, F2_S, rs2, rs1, rounding, rd, FMADD);
}

void MicroAssembler::fmsubs(FRegister rd,
                            FRegister rs1,
                            FRegister rs2,
                            FRegister rs3,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitR4Type(rs3, F2_S, rs2, rs1, rounding, rd, FMSUB);
}

void MicroAssembler::fnmsubs(FRegister rd,
                             FRegister rs1,
                             FRegister rs2,
                             FRegister rs3,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitR4Type(rs3, F2_S, rs2, rs1, rounding, rd, FNMSUB);
}

void MicroAssembler::fnmadds(FRegister rd,
                             FRegister rs1,
                             FRegister rs2,
                             FRegister rs3,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitR4Type(rs3, F2_S, rs2, rs1, rounding, rd, FNMADD);
}

void MicroAssembler::fadds(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FADDS, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fsubs(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FSUBS, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fmuls(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FMULS, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fdivs(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FDIVS, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fsqrts(FRegister rd,
                            FRegister rs1,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FSQRTS, FRegister(0), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fsgnjs(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FSGNJS, rs2, rs1, J, rd, OPFP);
}

void MicroAssembler::fsgnjns(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FSGNJS, rs2, rs1, JN, rd, OPFP);
}

void MicroAssembler::fsgnjxs(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FSGNJS, rs2, rs1, JX, rd, OPFP);
}

void MicroAssembler::fmins(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FMINMAXS, rs2, rs1, FMIN, rd, OPFP);
}

void MicroAssembler::fmaxs(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FMINMAXS, rs2, rs1, FMAX, rd, OPFP);
}

void MicroAssembler::feqs(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FCMPS, rs2, rs1, FEQ, rd, OPFP);
}

void MicroAssembler::flts(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FCMPS, rs2, rs1, FLT, rd, OPFP);
}

void MicroAssembler::fles(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_F));
  EmitRType(FCMPS, rs2, rs1, FLE, rd, OPFP);
}

void MicroAssembler::fclasss(Register rd, FRegister rs1) {
  ASSERT(Supports(RV_F));
  EmitRType(FCLASSS, FRegister(0), rs1, F3_1, rd, OPFP);
}

void MicroAssembler::fcvtws(Register rd, FRegister rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTintS, FRegister(W), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtwus(Register rd,
                             FRegister rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTintS, FRegister(WU), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtsw(FRegister rd, Register rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTSint, Register(W), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtswu(FRegister rd,
                             Register rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTSint, Register(WU), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fmvxw(Register rd, FRegister rs1) {
  ASSERT(Supports(RV_F));
  EmitRType(FMVXW, FRegister(0), rs1, F3_0, rd, OPFP);
}

void MicroAssembler::fmvwx(FRegister rd, Register rs1) {
  ASSERT(Supports(RV_F));
  EmitRType(FMVWX, Register(0), rs1, F3_0, rd, OPFP);
}

#if XLEN >= 64
void MicroAssembler::fcvtls(Register rd, FRegister rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTintS, FRegister(L), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtlus(Register rd,
                             FRegister rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTintS, FRegister(LU), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtsl(FRegister rd, Register rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTSint, Register(L), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtslu(FRegister rd,
                             Register rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_F));
  EmitRType(FCVTSint, Register(LU), rs1, rounding, rd, OPFP);
}
#endif  // XLEN >= 64

void MicroAssembler::fld(FRegister rd, Address addr) {
  ASSERT(Supports(RV_D));
  if (Supports(RV_C)) {
    if ((addr.base() == SP) && IsCSPLoad8Imm(addr.offset())) {
      c_fldsp(rd, addr);
      return;
    }
    if (IsCFRdp(rd) && IsCRs1p(addr.base()) && IsCMem8Imm(addr.offset())) {
      c_fld(rd, addr);
      return;
    }
  }
  EmitIType(addr.offset(), addr.base(), D, rd, LOADFP);
}

void MicroAssembler::fsd(FRegister rs2, Address addr) {
  ASSERT(Supports(RV_D));
  if (Supports(RV_C)) {
    if ((addr.base() == SP) && IsCSPStore8Imm(addr.offset())) {
      c_fsdsp(rs2, addr);
      return;
    }
    if (IsCFRs2p(rs2) && IsCRs1p(addr.base()) && IsCMem8Imm(addr.offset())) {
      c_fsd(rs2, addr);
      return;
    }
  }
  EmitSType(addr.offset(), rs2, addr.base(), D, STOREFP);
}

void MicroAssembler::fmaddd(FRegister rd,
                            FRegister rs1,
                            FRegister rs2,
                            FRegister rs3,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitR4Type(rs3, F2_D, rs2, rs1, rounding, rd, FMADD);
}

void MicroAssembler::fmsubd(FRegister rd,
                            FRegister rs1,
                            FRegister rs2,
                            FRegister rs3,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitR4Type(rs3, F2_D, rs2, rs1, rounding, rd, FMSUB);
}

void MicroAssembler::fnmsubd(FRegister rd,
                             FRegister rs1,
                             FRegister rs2,
                             FRegister rs3,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitR4Type(rs3, F2_D, rs2, rs1, rounding, rd, FNMSUB);
}

void MicroAssembler::fnmaddd(FRegister rd,
                             FRegister rs1,
                             FRegister rs2,
                             FRegister rs3,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitR4Type(rs3, F2_D, rs2, rs1, rounding, rd, FNMADD);
}

void MicroAssembler::faddd(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FADDD, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fsubd(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FSUBD, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fmuld(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FMULD, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fdivd(FRegister rd,
                           FRegister rs1,
                           FRegister rs2,
                           RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FDIVD, rs2, rs1, rounding, rd, OPFP);
}

void MicroAssembler::fsqrtd(FRegister rd,
                            FRegister rs1,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FSQRTD, FRegister(0), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fsgnjd(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FSGNJD, rs2, rs1, J, rd, OPFP);
}

void MicroAssembler::fsgnjnd(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FSGNJD, rs2, rs1, JN, rd, OPFP);
}

void MicroAssembler::fsgnjxd(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FSGNJD, rs2, rs1, JX, rd, OPFP);
}

void MicroAssembler::fmind(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FMINMAXD, rs2, rs1, FMIN, rd, OPFP);
}

void MicroAssembler::fmaxd(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FMINMAXD, rs2, rs1, FMAX, rd, OPFP);
}

void MicroAssembler::fcvtsd(FRegister rd,
                            FRegister rs1,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTS, FRegister(1), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtds(FRegister rd,
                            FRegister rs1,
                            RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTD, FRegister(0), rs1, rounding, rd, OPFP);
}

void MicroAssembler::feqd(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FCMPD, rs2, rs1, FEQ, rd, OPFP);
}

void MicroAssembler::fltd(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FCMPD, rs2, rs1, FLT, rd, OPFP);
}

void MicroAssembler::fled(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_D));
  EmitRType(FCMPD, rs2, rs1, FLE, rd, OPFP);
}

void MicroAssembler::fclassd(Register rd, FRegister rs1) {
  ASSERT(Supports(RV_D));
  EmitRType(FCLASSD, FRegister(0), rs1, F3_1, rd, OPFP);
}

void MicroAssembler::fcvtwd(Register rd, FRegister rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTintD, FRegister(W), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtwud(Register rd,
                             FRegister rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTintD, FRegister(WU), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtdw(FRegister rd, Register rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTDint, Register(W), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtdwu(FRegister rd,
                             Register rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTDint, Register(WU), rs1, rounding, rd, OPFP);
}

#if XLEN >= 64
void MicroAssembler::fcvtld(Register rd, FRegister rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTintD, FRegister(L), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtlud(Register rd,
                             FRegister rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTintD, FRegister(LU), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fmvxd(Register rd, FRegister rs1) {
  ASSERT(Supports(RV_D));
  EmitRType(FMVXD, FRegister(0), rs1, F3_0, rd, OPFP);
}

void MicroAssembler::fcvtdl(FRegister rd, Register rs1, RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTDint, Register(L), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtdlu(FRegister rd,
                             Register rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_D));
  EmitRType(FCVTDint, Register(LU), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fmvdx(FRegister rd, Register rs1) {
  ASSERT(Supports(RV_D));
  EmitRType(FMVDX, Register(0), rs1, F3_0, rd, OPFP);
}
#endif  // XLEN >= 64

#if XLEN >= 64
void MicroAssembler::adduw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  if (Supports(RV_Zcb)) {
    if ((rd == rs1) && IsCRs1p(rs1) && (rs2 == ZR)) {
      return c_zextw(rd, rs1);
    }
  }
  EmitRType(ADDUW, rs2, rs1, F3_0, rd, OP32);
}
#endif

void MicroAssembler::sh1add(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  EmitRType(SHADD, rs2, rs1, SH1ADD, rd, OP);
}

#if XLEN >= 64
void MicroAssembler::sh1adduw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  EmitRType(SHADD, rs2, rs1, SH1ADD, rd, OP32);
}
#endif

void MicroAssembler::sh2add(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  EmitRType(SHADD, rs2, rs1, SH2ADD, rd, OP);
}

#if XLEN >= 64
void MicroAssembler::sh2adduw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  EmitRType(SHADD, rs2, rs1, SH2ADD, rd, OP32);
}
#endif

void MicroAssembler::sh3add(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  EmitRType(SHADD, rs2, rs1, SH3ADD, rd, OP);
}

#if XLEN >= 64
void MicroAssembler::sh3adduw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zba));
  EmitRType(SHADD, rs2, rs1, SH3ADD, rd, OP32);
}

void MicroAssembler::slliuw(Register rd, Register rs1, intx_t shamt) {
  ASSERT((shamt > 0) && (shamt < 32));
  ASSERT(Supports(RV_Zba));
  EmitRType(SLLIUW, shamt, rs1, SLLI, rd, OPIMM32);
}
#endif

void MicroAssembler::andn(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(SUB, rs2, rs1, AND, rd, OP);
}

void MicroAssembler::orn(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(SUB, rs2, rs1, OR, rd, OP);
}

void MicroAssembler::xnor(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(SUB, rs2, rs1, XOR, rd, OP);
}

void MicroAssembler::clz(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(COUNT, 0b00000, rs1, F3_COUNT, rd, OPIMM);
}

void MicroAssembler::clzw(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(COUNT, 0b00000, rs1, F3_COUNT, rd, OPIMM32);
}

void MicroAssembler::ctz(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(COUNT, 0b00001, rs1, F3_COUNT, rd, OPIMM);
}

void MicroAssembler::ctzw(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(COUNT, 0b00001, rs1, F3_COUNT, rd, OPIMM32);
}

void MicroAssembler::cpop(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(COUNT, 0b00010, rs1, F3_COUNT, rd, OPIMM);
}

void MicroAssembler::cpopw(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(COUNT, 0b00010, rs1, F3_COUNT, rd, OPIMM32);
}

void MicroAssembler::max(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(MINMAXCLMUL, rs2, rs1, MAX, rd, OP);
}

void MicroAssembler::maxu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(MINMAXCLMUL, rs2, rs1, MAXU, rd, OP);
}

void MicroAssembler::min(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(MINMAXCLMUL, rs2, rs1, MIN, rd, OP);
}

void MicroAssembler::minu(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(MINMAXCLMUL, rs2, rs1, MINU, rd, OP);
}

void MicroAssembler::sextb(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  if (Supports(RV_Zcb)) {
    if ((rd == rs1) && IsCRs1p(rs1)) {
      return c_sextb(rd, rs1);
    }
  }
  EmitRType((Funct7)0b0110000, 0b00100, rs1, SEXT, rd, OPIMM);
}

void MicroAssembler::sexth(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  if (Supports(RV_Zcb)) {
    if ((rd == rs1) && IsCRs1p(rs1)) {
      return c_sexth(rd, rs1);
    }
  }
  EmitRType((Funct7)0b0110000, 0b00101, rs1, SEXT, rd, OPIMM);
}

void MicroAssembler::zexth(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  if (Supports(RV_Zcb)) {
    if ((rd == rs1) && IsCRs1p(rs1)) {
      return c_zexth(rd, rs1);
    }
  }
#if XLEN == 32
  EmitRType((Funct7)0b0000100, 0b00000, rs1, ZEXT, rd, OP);
#elif XLEN == 64
  EmitRType((Funct7)0b0000100, 0b00000, rs1, ZEXT, rd, OP32);
#else
  UNIMPLEMENTED();
#endif
}

void MicroAssembler::rol(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(ROTATE, rs2, rs1, ROL, rd, OP);
}

void MicroAssembler::rolw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(ROTATE, rs2, rs1, ROL, rd, OP32);
}

void MicroAssembler::ror(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(ROTATE, rs2, rs1, ROR, rd, OP);
}

void MicroAssembler::rori(Register rd, Register rs1, intx_t shamt) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(ROTATE, shamt, rs1, ROR, rd, OPIMM);
}

void MicroAssembler::roriw(Register rd, Register rs1, intx_t shamt) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(ROTATE, shamt, rs1, ROR, rd, OPIMM32);
}

void MicroAssembler::rorw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbb));
  EmitRType(ROTATE, rs2, rs1, ROR, rd, OP32);
}

void MicroAssembler::orcb(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
  EmitRType((Funct7)0b0010100, 0b00111, rs1, (Funct3)0b101, rd, OPIMM);
}

void MicroAssembler::rev8(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zbb));
#if XLEN == 32
  EmitRType((Funct7)0b0110100, 0b11000, rs1, (Funct3)0b101, rd, OPIMM);
#elif XLEN == 64
  EmitRType((Funct7)0b0110101, 0b11000, rs1, (Funct3)0b101, rd, OPIMM);
#else
  UNIMPLEMENTED();
#endif
}

void MicroAssembler::clmul(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbc));
  EmitRType(MINMAXCLMUL, rs2, rs1, CLMUL, rd, OP);
}

void MicroAssembler::clmulh(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbc));
  EmitRType(MINMAXCLMUL, rs2, rs1, CLMULH, rd, OP);
}

void MicroAssembler::clmulr(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbc));
  EmitRType(MINMAXCLMUL, rs2, rs1, CLMULR, rd, OP);
}

void MicroAssembler::bclr(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BCLRBEXT, rs2, rs1, BCLR, rd, OP);
}

void MicroAssembler::bclri(Register rd, Register rs1, intx_t shamt) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BCLRBEXT, shamt, rs1, BCLR, rd, OPIMM);
}

void MicroAssembler::bext(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BCLRBEXT, rs2, rs1, BEXT, rd, OP);
}

void MicroAssembler::bexti(Register rd, Register rs1, intx_t shamt) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BCLRBEXT, shamt, rs1, BEXT, rd, OPIMM);
}

void MicroAssembler::binv(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BINV, rs2, rs1, F3_BINV, rd, OP);
}

void MicroAssembler::binvi(Register rd, Register rs1, intx_t shamt) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BINV, shamt, rs1, F3_BINV, rd, OPIMM);
}

void MicroAssembler::bset(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BSET, rs2, rs1, F3_BSET, rd, OP);
}

void MicroAssembler::bseti(Register rd, Register rs1, intx_t shamt) {
  ASSERT(Supports(RV_Zbs));
  EmitRType(BSET, shamt, rs1, F3_BSET, rd, OPIMM);
}

void MicroAssembler::czeroeqz(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zicond));
  EmitRType(CZERO, rs2, rs1, CZEROEQZ, rd, OP);
}

void MicroAssembler::czeronez(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zicond));
  EmitRType(CZERO, rs2, rs1, CZERONEZ, rd, OP);
}

void MicroAssembler::amoswapb(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOSWAP, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amoaddb(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOADD, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amoxorb(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOXOR, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amoandb(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOAND, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amoorb(Register rd,
                            Register rs2,
                            Address addr,
                            std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOOR, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amominb(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMIN, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amomaxb(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMAX, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amominub(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMINU, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amomaxub(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMAXU, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amoswaph(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOSWAP, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amoaddh(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOADD, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amoxorh(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOXOR, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amoandh(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOAND, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amoorh(Register rd,
                            Register rs2,
                            Address addr,
                            std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOOR, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amominh(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMIN, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amomaxh(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMAX, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amominuh(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMINU, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amomaxuh(Register rd,
                              Register rs2,
                              Address addr,
                              std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zabha));
  EmitRType(AMOMAXU, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::flis(FRegister rd, intptr_t index) {
  ASSERT((index >= 0) && (index < 32));
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMVWX, FRegister(1), FRegister(index), F3_0, rd, OPFP);
}

void MicroAssembler::flid(FRegister rd, intptr_t index) {
  ASSERT((index >= 0) && (index < 32));
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMVDX, FRegister(1), FRegister(index), F3_0, rd, OPFP);
}

void MicroAssembler::fminms(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMINMAXS, rs2, rs1, FMINM, rd, OPFP);
}
void MicroAssembler::fmaxms(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMINMAXS, rs2, rs1, FMAXM, rd, OPFP);
}

void MicroAssembler::fminmd(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMINMAXD, rs2, rs1, FMINM, rd, OPFP);
}

void MicroAssembler::fmaxmd(FRegister rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMINMAXD, rs2, rs1, FMAXM, rd, OPFP);
}

void MicroAssembler::frounds(FRegister rd,
                             FRegister rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCVTS, FRegister(4), rs1, rounding, rd, OPFP);
}

void MicroAssembler::froundnxs(FRegister rd,
                               FRegister rs1,
                               RoundingMode rounding) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCVTS, FRegister(5), rs1, rounding, rd, OPFP);
}

void MicroAssembler::froundd(FRegister rd,
                             FRegister rs1,
                             RoundingMode rounding) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCVTD, FRegister(4), rs1, rounding, rd, OPFP);
}

void MicroAssembler::froundnxd(FRegister rd,
                               FRegister rs1,
                               RoundingMode rounding) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCVTD, FRegister(5), rs1, rounding, rd, OPFP);
}

void MicroAssembler::fcvtmodwd(Register rd, FRegister rs1) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCVTintD, FRegister(8), rs1, RTZ, rd, OPFP);
}

#if XLEN == 32
void MicroAssembler::fmvhxd(Register rd, FRegister rs1) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMVHXD, FRegister(1), rs1, F3_0, rd, OPFP);
}

void MicroAssembler::fmvpdx(FRegister rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FMVPDX, rs2, rs1, F3_0, rd, OPFP);
}
#endif  // XLEN == 32

void MicroAssembler::fltqs(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCMPS, rs2, rs1, FLTQ, rd, OPFP);
}

void MicroAssembler::fleqs(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCMPS, rs2, rs1, FLEQ, rd, OPFP);
}

void MicroAssembler::fltqd(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCMPD, rs2, rs1, FLTQ, rd, OPFP);
}

void MicroAssembler::fleqd(Register rd, FRegister rs1, FRegister rs2) {
  ASSERT(Supports(RV_Zfa));
  EmitRType(FCMPD, rs2, rs1, FLEQ, rd, OPFP);
}

void MicroAssembler::mopr(intptr_t n, Register rd, Register rs1) {
  ASSERT(Supports(RV_Zimop));
  Emit32(EncodeMoprn(n) | EncodeFunct12(MOP_R) | EncodeRs1(rs1) |
         EncodeFunct3(MOP) | EncodeRd(rd) | EncodeOpcode(SYSTEM));
}

void MicroAssembler::moprr(intptr_t n,
                           Register rd,
                           Register rs1,
                           Register rs2) {
  ASSERT(Supports(RV_Zimop));
  Emit32(EncodeMoprrn(n) | EncodeFunct7(MOP_RR) | EncodeRs2(rs2) |
         EncodeRs1(rs1) | EncodeFunct3(MOP) | EncodeRd(rd) |
         EncodeOpcode(SYSTEM));
}

void MicroAssembler::cmop(intptr_t n) {
  ASSERT(Supports(RV_Zcmop));
  ASSERT(Utils::IsUint(4, n) && ((n & 1) == 1));
  Emit16(C_LUI | EncodeCMopn(n));
}

void MicroAssembler::sspush(Register rs2) {
  ASSERT((rs2 == Register(1)) || (rs2 == Register(5)));
  if (Supports(RV_Zcmop) && (rs2 == Register(1))) {
    cmop(1);
  } else {
    moprr(7, ZR, ZR, rs2);
  }
}

void MicroAssembler::sspopchk(Register rs1) {
  ASSERT((rs1 == Register(1)) || (rs1 == Register(5)));
  if (Supports(RV_Zcmop) && (rs1 == Register(5))) {
    cmop(5);
  } else {
    mopr(28, ZR, rs1);
  }
}

void MicroAssembler::ssrdp(Register rd) {
  mopr(28, rd, ZR);
}

void MicroAssembler::ssamoswapw(Register rd,
                                Register rs2,
                                Address addr,
                                std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zicfiss));
  EmitRType(SSAMOSWAP, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

#if XLEN >= 64
void MicroAssembler::ssamoswapd(Register rd,
                                Register rs2,
                                Address addr,
                                std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zicfiss));
  EmitRType(SSAMOSWAP, order, rs2, addr.base(), WIDTH64, rd, AMO);
}
#endif  // XLEN >= 64

void MicroAssembler::vsetvli(Register rd,
                             Register rs1,
                             ElementWidth sew,
                             LengthMultiplier lmul,
                             TailMode vta,
                             MaskMode vma) {
  ASSERT(Supports(RV_V));
  intx_t vtypei = (vma << 7) | (vta << 6) | (sew << 3) | (lmul << 0);
  EmitIType(vtypei, rs1, OPCFG, rd, OPV);
}

void MicroAssembler::vle8v(VRegister vd, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(LOADFP) | EncodeVd(vd) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E8) | vm);
}

void MicroAssembler::vle16v(VRegister vd, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(LOADFP) | EncodeVd(vd) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E16) | vm);
}

void MicroAssembler::vle32v(VRegister vd, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(LOADFP) | EncodeVd(vd) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E32) | vm);
}

void MicroAssembler::vle64v(VRegister vd, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(LOADFP) | EncodeVd(vd) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E64) | vm);
}

void MicroAssembler::vse8v(VRegister vs3, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(STOREFP) | EncodeVs3(vs3) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E8) | vm);
}

void MicroAssembler::vse16v(VRegister vs3, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(STOREFP) | EncodeVs3(vs3) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E16) | vm);
}

void MicroAssembler::vse32v(VRegister vs3, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(STOREFP) | EncodeVs3(vs3) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E32) | vm);
}

void MicroAssembler::vse64v(VRegister vs3, Address rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  ASSERT(rs1.offset() == 0);
  Emit32(EncodeOpcode(STOREFP) | EncodeVs3(vs3) | EncodeRs1(rs1.base()) |
         EncodeFunct3(E64) | vm);
}

void MicroAssembler::vmvvx(VRegister vd, Register rs1, VectorMask vm) {
  ASSERT(Supports(RV_V));
  Emit32(EncodeOpcode(OPV) | EncodeVd(vd) | EncodeRs1(rs1) |
         EncodeFunct3(OPIVX) | EncodeFunct6(VMV) | vm);
}

void MicroAssembler::lb(Register rd, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_acquire) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(LOADORDERED, order, ZR, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::lh(Register rd, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_acquire) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(LOADORDERED, order, ZR, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::lw(Register rd, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_acquire) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(LOADORDERED, order, ZR, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::sb(Register rs2, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_release) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(STOREORDERED, order, rs2, addr.base(), WIDTH8, ZR, AMO);
}

void MicroAssembler::sh(Register rs2, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_release) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(STOREORDERED, order, rs2, addr.base(), WIDTH16, ZR, AMO);
}

void MicroAssembler::sw(Register rs2, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_release) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(STOREORDERED, order, rs2, addr.base(), WIDTH32, ZR, AMO);
}

#if XLEN >= 64
void MicroAssembler::ld(Register rd, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_acquire) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(LOADORDERED, order, ZR, addr.base(), WIDTH64, rd, AMO);
}

void MicroAssembler::sd(Register rs2, Address addr, std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT((order == std::memory_order_release) ||
         (order == std::memory_order_acq_rel));
  ASSERT(Supports(RV_Zalasr));
  EmitRType(STOREORDERED, order, rs2, addr.base(), WIDTH64, ZR, AMO);
}
#endif

void MicroAssembler::amocasb(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zacas | RV_Zabha));
  EmitRType(AMOCAS, order, rs2, addr.base(), WIDTH8, rd, AMO);
}

void MicroAssembler::amocash(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zacas | RV_Zabha));
  EmitRType(AMOCAS, order, rs2, addr.base(), WIDTH16, rd, AMO);
}

void MicroAssembler::amocasw(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
  ASSERT(Supports(RV_Zacas));
  EmitRType(AMOCAS, order, rs2, addr.base(), WIDTH32, rd, AMO);
}

void MicroAssembler::amocasd(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
#if XLEN == 32
  ASSERT((rd % 2) == 0);
  ASSERT((rs2 % 2) == 0);
#endif
  ASSERT(Supports(RV_Zacas));
  EmitRType(AMOCAS, order, rs2, addr.base(), WIDTH64, rd, AMO);
}

#if XLEN >= 64
void MicroAssembler::amocasq(Register rd,
                             Register rs2,
                             Address addr,
                             std::memory_order order) {
  ASSERT(addr.offset() == 0);
#if XLEN == 64
  ASSERT((rd % 2) == 0);
  ASSERT((rs2 % 2) == 0);
#endif
  ASSERT(Supports(RV_Zacas));
  EmitRType(AMOCAS, order, rs2, addr.base(), WIDTH128, rd, AMO);
}
#endif

void MicroAssembler::wrsnto() {
  ASSERT(Supports(RV_Zawrs));
  EmitIType(WRS_NTO, ZR, PRIV, ZR, SYSTEM);
}

void MicroAssembler::wrssto() {
  ASSERT(Supports(RV_Zawrs));
  EmitIType(WRS_STO, ZR, PRIV, ZR, SYSTEM);
}

void MicroAssembler::c_lwsp(Register rd, Address addr) {
  ASSERT(rd != ZR);
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  Emit16(C_LWSP | EncodeCRd(rd) | EncodeCSPLoad4Imm(addr.offset()));
}

#if XLEN == 32
void MicroAssembler::c_flwsp(FRegister rd, Address addr) {
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_F));
  Emit16(C_FLWSP | EncodeCFRd(rd) | EncodeCSPLoad4Imm(addr.offset()));
}
#else
void MicroAssembler::c_ldsp(Register rd, Address addr) {
  ASSERT(rd != ZR);
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  Emit16(C_LDSP | EncodeCRd(rd) | EncodeCSPLoad8Imm(addr.offset()));
}
#endif

void MicroAssembler::c_fldsp(FRegister rd, Address addr) {
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_D));
  Emit16(C_FLDSP | EncodeCFRd(rd) | EncodeCSPLoad8Imm(addr.offset()));
}

void MicroAssembler::c_swsp(Register rs2, Address addr) {
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  Emit16(C_SWSP | EncodeCRs2(rs2) | EncodeCSPStore4Imm(addr.offset()));
}

#if XLEN == 32
void MicroAssembler::c_fswsp(FRegister rs2, Address addr) {
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_F));
  Emit16(C_FSWSP | EncodeCFRs2(rs2) | EncodeCSPStore4Imm(addr.offset()));
}
#else
void MicroAssembler::c_sdsp(Register rs2, Address addr) {
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  Emit16(C_SDSP | EncodeCRs2(rs2) | EncodeCSPStore8Imm(addr.offset()));
}
#endif
void MicroAssembler::c_fsdsp(FRegister rs2, Address addr) {
  ASSERT(addr.base() == SP);
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_D));
  Emit16(C_FSDSP | EncodeCFRs2(rs2) | EncodeCSPStore8Imm(addr.offset()));
}

void MicroAssembler::c_lw(Register rd, Address addr) {
  ASSERT(Supports(RV_C));
  Emit16(C_LW | EncodeCRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem4Imm(addr.offset()));
}

void MicroAssembler::c_ld(Register rd, Address addr) {
  ASSERT(Supports(RV_C));
  Emit16(C_LD | EncodeCRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem8Imm(addr.offset()));
}

void MicroAssembler::c_flw(FRegister rd, Address addr) {
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_F));
  Emit16(C_FLW | EncodeCFRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem4Imm(addr.offset()));
}

void MicroAssembler::c_fld(FRegister rd, Address addr) {
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_D));
  Emit16(C_FLD | EncodeCFRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem8Imm(addr.offset()));
}

void MicroAssembler::c_sw(Register rs2, Address addr) {
  ASSERT(Supports(RV_C));
  Emit16(C_SW | EncodeCRs1p(addr.base()) | EncodeCRs2p(rs2) |
         EncodeCMem4Imm(addr.offset()));
}

void MicroAssembler::c_sd(Register rs2, Address addr) {
  ASSERT(Supports(RV_C));
  Emit16(C_SD | EncodeCRs1p(addr.base()) | EncodeCRs2p(rs2) |
         EncodeCMem8Imm(addr.offset()));
}

void MicroAssembler::c_fsw(FRegister rs2, Address addr) {
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_F));
  Emit16(C_FSW | EncodeCRs1p(addr.base()) | EncodeCFRs2p(rs2) |
         EncodeCMem4Imm(addr.offset()));
}

void MicroAssembler::c_fsd(FRegister rs2, Address addr) {
  ASSERT(Supports(RV_C));
  ASSERT(Supports(RV_D));
  Emit16(C_FSD | EncodeCRs1p(addr.base()) | EncodeCFRs2p(rs2) |
         EncodeCMem8Imm(addr.offset()));
}

void MicroAssembler::c_j(Label* label) {
  ASSERT(Supports(RV_C));
  EmitCJump(label, C_J);
}

#if XLEN == 32
void MicroAssembler::c_jal(Label* label) {
  ASSERT(Supports(RV_C));
  EmitCJump(label, C_JAL);
}
#endif  // XLEN == 32

void MicroAssembler::c_jr(Register rs1) {
  ASSERT(Supports(RV_C));
  ASSERT(rs1 != ZR);
  Emit16(C_JR | EncodeCRs1(rs1) | EncodeCRs2(ZR));
}

void MicroAssembler::c_jalr(Register rs1) {
  ASSERT(Supports(RV_C));
  Emit16(C_JALR | EncodeCRs1(rs1) | EncodeCRs2(ZR));
}

void MicroAssembler::c_beqz(Register rs1p, Label* label) {
  ASSERT(Supports(RV_C));
  EmitCBranch(rs1p, label, C_BEQZ);
}

void MicroAssembler::c_bnez(Register rs1p, Label* label) {
  ASSERT(Supports(RV_C));
  EmitCBranch(rs1p, label, C_BNEZ);
}

void MicroAssembler::c_li(Register rd, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd != ZR);
  Emit16(C_LI | EncodeCRd(rd) | EncodeCIImm(imm));
}

void MicroAssembler::c_lui(Register rd, uintptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd != ZR);
  ASSERT(rd != SP);
  ASSERT(imm != 0);
  Emit16(C_LUI | EncodeCRd(rd) | EncodeCUImm(imm));
}

void MicroAssembler::c_addi(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(imm != 0);
  ASSERT(rd == rs1);
  Emit16(C_ADDI | EncodeCRd(rd) | EncodeCIImm(imm));
}

#if XLEN >= 64
void MicroAssembler::c_addiw(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  Emit16(C_ADDIW | EncodeCRd(rd) | EncodeCIImm(imm));
}
#endif
void MicroAssembler::c_addi16sp(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  Emit16(C_ADDI16SP | EncodeCRd(rd) | EncodeCI16Imm(imm));
}

void MicroAssembler::c_addi4spn(Register rdp, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rs1 == SP);
  ASSERT(imm != 0);
  Emit16(C_ADDI4SPN | EncodeCRdp(rdp) | EncodeCI4SPNImm(imm));
}

void MicroAssembler::c_slli(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  ASSERT(rd != ZR);
  Emit16(C_SLLI | EncodeCRd(rd) | EncodeCShamt(imm));
}

void MicroAssembler::c_srli(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  Emit16(C_SRLI | EncodeCRs1p(rd) | EncodeCShamt(imm));
}

void MicroAssembler::c_srai(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  Emit16(C_SRAI | EncodeCRs1p(rd) | EncodeCShamt(imm));
}

void MicroAssembler::c_andi(Register rd, Register rs1, intptr_t imm) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  Emit16(C_ANDI | EncodeCRs1p(rd) | EncodeCIImm(imm));
}

void MicroAssembler::c_mv(Register rd, Register rs2) {
  ASSERT(Supports(RV_C));
  ASSERT(rd != ZR);
  ASSERT(rs2 != ZR);
  Emit16(C_MV | EncodeCRd(rd) | EncodeCRs2(rs2));
}

void MicroAssembler::c_add(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  ASSERT(rs2 != ZR);
  Emit16(C_ADD | EncodeCRd(rd) | EncodeCRs2(rs2));
}

void MicroAssembler::c_and(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  ASSERT(rd == rs1);
  Emit16(C_AND | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}

void MicroAssembler::c_or(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  Emit16(C_OR | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}

void MicroAssembler::c_xor(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  Emit16(C_XOR | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}

void MicroAssembler::c_sub(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  Emit16(C_SUB | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}

#if XLEN >= 64
void MicroAssembler::c_addw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  Emit16(C_ADDW | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}

void MicroAssembler::c_subw(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_C));
  Emit16(C_SUBW | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}
#endif  // XLEN >= 64

void MicroAssembler::c_nop() {
  ASSERT(Supports(RV_C));
  Emit16(C_NOP);
}

void MicroAssembler::c_ebreak() {
  ASSERT(Supports(RV_C));
  Emit16(C_EBREAK);
}

void MicroAssembler::c_lbu(Register rd, Address addr) {
  ASSERT(Supports(RV_Zcb));
  Emit16(C_LBU | EncodeCRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem1Imm(addr.offset()));
}

void MicroAssembler::c_lhu(Register rd, Address addr) {
  ASSERT(Supports(RV_Zcb));
  Emit16(C_LHU | EncodeCRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem2Imm(addr.offset()));
}

void MicroAssembler::c_lh(Register rd, Address addr) {
  ASSERT(Supports(RV_Zcb));
  Emit16(C_LH | EncodeCRdp(rd) | EncodeCRs1p(addr.base()) |
         EncodeCMem2Imm(addr.offset()));
}

void MicroAssembler::c_sb(Register rs2, Address addr) {
  ASSERT(Supports(RV_Zcb));
  Emit16(C_SB | EncodeCRs1p(addr.base()) | EncodeCRs2p(rs2) |
         EncodeCMem1Imm(addr.offset()));
}

void MicroAssembler::c_sh(Register rs2, Address addr) {
  ASSERT(Supports(RV_Zcb));
  Emit16(C_SH | EncodeCRs1p(addr.base()) | EncodeCRs2p(rs2) |
         EncodeCMem2Imm(addr.offset()));
}

void MicroAssembler::c_zextb(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(rd == rs1);
  Emit16(C_ZEXTB | EncodeCRs1p(rs1));
}

void MicroAssembler::c_sextb(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(rd == rs1);
  Emit16(C_SEXTB | EncodeCRs1p(rs1));
}

void MicroAssembler::c_zexth(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(Supports(RV_Zbb));
  ASSERT(rd == rs1);
  Emit16(C_ZEXTH | EncodeCRs1p(rs1));
}

void MicroAssembler::c_sexth(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(Supports(RV_Zbb));
  ASSERT(rd == rs1);
  Emit16(C_SEXTH | EncodeCRs1p(rs1));
}

#if XLEN >= 64
void MicroAssembler::c_zextw(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(Supports(RV_Zba));
  ASSERT(rd == rs1);
  Emit16(C_ZEXTW | EncodeCRs1p(rs1));
}
#endif

void MicroAssembler::c_mul(Register rd, Register rs1, Register rs2) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(Supports(RV_M));
  ASSERT(rd == rs1);
  Emit16(C_MUL | EncodeCRs1p(rs1) | EncodeCRs2p(rs2));
}

void MicroAssembler::c_not(Register rd, Register rs1) {
  ASSERT(Supports(RV_Zcb));
  ASSERT(rd == rs1);
  Emit16(C_NOT | EncodeCRs1p(rs1));
}

static Funct3 InvertFunct3(Funct3 func) {
  switch (func) {
    case BEQ:
      return BNE;
    case BNE:
      return BEQ;
    case BGE:
      return BLT;
    case BGEU:
      return BLTU;
    case BLT:
      return BGE;
    case BLTU:
      return BGEU;
    default:
      UNREACHABLE();
  }
}

void MicroAssembler::EmitBranch(Register rs1,
                                Register rs2,
                                Label* label,
                                Funct3 func,
                                JumpDistance distance) {
  intptr_t offset;
  if (label->IsBound()) {
    // Backward branch: use near or far branch based on actual distance.
    offset = label->Position() - Position();
    if (IsBTypeImm(offset)) {
      EmitBType(offset, rs2, rs1, func, BRANCH);
      return;
    }

    if (IsJTypeImm(offset + 4)) {
      intptr_t start = Position();
      const intptr_t kFarBranchLength = 8;
      EmitBType(kFarBranchLength, rs2, rs1, InvertFunct3(func), BRANCH);
      offset = label->Position() - Position();
      EmitJType(offset, ZR, JAL);
      intptr_t end = Position();
      ASSERT_EQUAL(end - start, kFarBranchLength);
      return;
    }

    intptr_t start = Position();
    const intptr_t kFarBranchLength = 12;
    EmitBType(kFarBranchLength, rs2, rs1, InvertFunct3(func), BRANCH);
    offset = label->Position() - Position();
    intx_t lo = ImmLo(offset);
    intx_t hi = ImmHi(offset);
    if (!IsUTypeImm(hi)) {
      FATAL("Branch distance exceeds 2GB!");
    }
    EmitUType(hi, FAR_TMP, AUIPC);
    EmitIType(lo, FAR_TMP, F3_0, ZR, JALR);
    intptr_t end = Position();
    ASSERT_EQUAL(end - start, kFarBranchLength);
    return;
  } else {
    // Forward branch: speculatively use near branches and re-assemble with far
    // branches if any need greater length.
    if (distance == kNearJump) {
      offset = label->link_b(Position());
      if (!IsBTypeImm(offset)) {
        FATAL("Incorrect Assembler::kNearJump");
      }
      EmitBType(offset, rs2, rs1, func, BRANCH);
    } else if (far_branch_level() == 0) {
      offset = label->link_b(Position());
      if (!IsBTypeImm(offset)) {
        // TODO(riscv): This isn't so much because the branch is out of range
        // as some previous jump to the same target would be out of B-type
        // range... A possible alternative is to have separate lists on Labels
        // for pending B-type and J-type instructions.
        BailoutWithBranchOffsetError();
      }
      EmitBType(offset, rs2, rs1, func, BRANCH);
    } else if (far_branch_level() == 1) {
      intptr_t start = Position();
      intptr_t kFarBranchLength;
      if (Supports(RV_C) && (func == BEQ || func == BNE) &&
          ((rs1 == ZR && IsCRs1p(rs2)) || (rs2 == ZR && IsCRs1p(rs1)))) {
        kFarBranchLength = 6;
        Emit16((func == BEQ ? C_BNEZ : C_BEQZ) |
               EncodeCRs1p(rs1 == ZR ? rs2 : rs1) |
               EncodeCBImm(kFarBranchLength));
      } else {
        kFarBranchLength = 8;
        EmitBType(kFarBranchLength, rs2, rs1, InvertFunct3(func), BRANCH);
      }
      offset = label->link_j(Position());
      EmitJType(offset, ZR, JAL);
      intptr_t end = Position();
      ASSERT_EQUAL(end - start, kFarBranchLength);
    } else {
      intptr_t start = Position();
      intptr_t kFarBranchLength;
      if (Supports(RV_C) && (func == BEQ || func == BNE) &&
          ((rs1 == ZR && IsCRs1p(rs2)) || (rs2 == ZR && IsCRs1p(rs1)))) {
        kFarBranchLength = 10;
        Emit16((func == BEQ ? C_BNEZ : C_BEQZ) |
               EncodeCRs1p(rs1 == ZR ? rs2 : rs1) |
               EncodeCBImm(kFarBranchLength));
      } else {
        kFarBranchLength = 12;
        EmitBType(kFarBranchLength, rs2, rs1, InvertFunct3(func), BRANCH);
      }
      offset = label->link_far(Position());
      intx_t lo = ImmLo(offset);
      intx_t hi = ImmHi(offset);
      if (!IsUTypeImm(hi)) {
        FATAL("Branch distance exceeds 2GB!");
      }
      EmitUType(hi, FAR_TMP, AUIPC);
      EmitIType(lo, FAR_TMP, F3_0, ZR, JALR);
      intptr_t end = Position();
      ASSERT_EQUAL(end - start, kFarBranchLength);
    }
  }
}

void MicroAssembler::EmitJump(Register rd,
                              Label* label,
                              Opcode op,
                              JumpDistance distance) {
  intptr_t offset;
  if (label->IsBound()) {
    // Backward jump: use near or far jump based on actual distance.
    offset = label->Position() - Position();

    if (IsJTypeImm(offset)) {
      EmitJType(offset, rd, JAL);
      return;
    }
    intx_t lo = ImmLo(offset);
    intx_t hi = ImmHi(offset);
    if (!IsUTypeImm(hi)) {
      FATAL("Jump distance exceeds 2GB!");
    }
    EmitUType(hi, FAR_TMP, AUIPC);
    EmitIType(lo, FAR_TMP, F3_0, ZR, JALR);
    return;
  } else {
    // Forward jump: speculatively use near jumps and re-assemble with far
    // jumps if any need greater length.
    if (distance == kNearJump) {
      offset = label->link_j(Position());
      if (!IsJTypeImm(offset)) {
        FATAL("Incorrect Assembler::kNearJump");
      }
      EmitJType(offset, rd, JAL);
    } else if (far_branch_level() < 2) {
      offset = label->link_j(Position());
      if (!IsJTypeImm(offset)) {
        BailoutWithBranchOffsetError();
      }
      EmitJType(offset, rd, JAL);
    } else {
      offset = label->link_far(Position());
      intx_t lo = ImmLo(offset);
      intx_t hi = ImmHi(offset);
      if (!IsUTypeImm(hi)) {
        FATAL("Jump distance exceeds 2GB!");
      }
      EmitUType(hi, FAR_TMP, AUIPC);
      EmitIType(lo, FAR_TMP, F3_0, ZR, JALR);
    }
  }
}

void MicroAssembler::EmitCBranch(Register rs1p, Label* label, COpcode op) {
  intptr_t offset;
  if (label->IsBound()) {
    offset = label->Position() - Position();
  } else {
    offset = label->link_cb(Position());
  }
  if (!IsCBImm(offset)) {
    FATAL("Incorrect Assembler::kNearJump");
  }
  Emit16(op | EncodeCRs1p(rs1p) | EncodeCBImm(offset));
}

void MicroAssembler::EmitCJump(Label* label, COpcode op) {
  intptr_t offset;
  if (label->IsBound()) {
    offset = label->Position() - Position();
  } else {
    offset = label->link_cj(Position());
  }
  if (!IsCJImm(offset)) {
    FATAL("Incorrect Assembler::kNearJump");
  }
  Emit16(op | EncodeCJImm(offset));
}

void MicroAssembler::EmitRType(Funct5 funct5,
                               std::memory_order order,
                               Register rs2,
                               Register rs1,
                               Funct3 funct3,
                               Register rd,
                               Opcode opcode) {
  intptr_t funct7 = funct5 << 2;
  switch (order) {
    case std::memory_order_acq_rel:
      funct7 |= 0b11;
      break;
    case std::memory_order_acquire:
      funct7 |= 0b10;
      break;
    case std::memory_order_release:
      funct7 |= 0b01;
      break;
    case std::memory_order_relaxed:
      funct7 |= 0b00;
      break;
    default:
      FATAL("Invalid memory order");
  }
  EmitRType((Funct7)funct7, rs2, rs1, funct3, rd, opcode);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               Register rs2,
                               Register rs1,
                               Funct3 funct3,
                               Register rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeRs2(rs2);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               FRegister rs2,
                               FRegister rs1,
                               Funct3 funct3,
                               FRegister rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeFRs2(rs2);
  e |= EncodeFRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeFRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               FRegister rs2,
                               FRegister rs1,
                               RoundingMode round,
                               FRegister rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeFRs2(rs2);
  e |= EncodeFRs1(rs1);
  e |= EncodeRoundingMode(round);
  e |= EncodeFRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               Register rs2,
                               Register rs1,
                               RoundingMode round,
                               FRegister rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeRs2(rs2);
  e |= EncodeRs1(rs1);
  e |= EncodeRoundingMode(round);
  e |= EncodeFRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               Register rs2,
                               Register rs1,
                               Funct3 funct3,
                               FRegister rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeRs2(rs2);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeFRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               FRegister rs2,
                               FRegister rs1,
                               Funct3 funct3,
                               Register rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeFRs2(rs2);
  e |= EncodeFRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               FRegister rs2,
                               FRegister rs1,
                               RoundingMode round,
                               Register rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeFRs2(rs2);
  e |= EncodeFRs1(rs1);
  e |= EncodeRoundingMode(round);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitRType(Funct7 funct7,
                               intptr_t shamt,
                               Register rs1,
                               Funct3 funct3,
                               Register rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct7(funct7);
  e |= EncodeShamt(shamt);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitR4Type(FRegister rs3,
                                Funct2 funct2,
                                FRegister rs2,
                                FRegister rs1,
                                RoundingMode round,
                                FRegister rd,
                                Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFRs3(rs3);
  e |= EncodeFunct2(funct2);
  e |= EncodeFRs2(rs2);
  e |= EncodeFRs1(rs1);
  e |= EncodeRoundingMode(round);
  e |= EncodeFRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitIType(Funct12 funct12,
                               Register rs1,
                               Funct3 funct3,
                               Register rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeFunct12(funct12);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitIType(intptr_t imm,
                               Register rs1,
                               Funct3 funct3,
                               Register rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeITypeImm(imm);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitIType(intptr_t imm,
                               Register rs1,
                               Funct3 funct3,
                               FRegister rd,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeITypeImm(imm);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeFRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitSType(intptr_t imm,
                               Register rs2,
                               Register rs1,
                               Funct3 funct3,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeSTypeImm(imm);
  e |= EncodeRs2(rs2);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitSType(intptr_t imm,
                               FRegister rs2,
                               Register rs1,
                               Funct3 funct3,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeSTypeImm(imm);
  e |= EncodeFRs2(rs2);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitBType(intptr_t imm,
                               Register rs2,
                               Register rs1,
                               Funct3 funct3,
                               Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeBTypeImm(imm);
  e |= EncodeRs2(rs2);
  e |= EncodeRs1(rs1);
  e |= EncodeFunct3(funct3);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitUType(intptr_t imm, Register rd, Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeUTypeImm(imm);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

void MicroAssembler::EmitJType(intptr_t imm, Register rd, Opcode opcode) {
  uint32_t e = 0;
  e |= EncodeJTypeImm(imm);
  e |= EncodeRd(rd);
  e |= EncodeOpcode(opcode);
  Emit32(e);
}

Assembler::Assembler(ObjectPoolBuilder* object_pool_builder,
                     intptr_t far_branch_level)
    : MicroAssembler(object_pool_builder, far_branch_level, RV_baseline),
      constant_pool_allowed_(false) {
  generate_invoke_write_barrier_wrapper_ = [&](Register reg) {
    // Note this does not destroy RA.
    lx(TMP,
       Address(THR, target::Thread::write_barrier_wrappers_thread_offset(reg)));
    jalr(TMP, TMP);
  };
  generate_invoke_array_write_barrier_ = [&]() {
    Call(
        Address(THR, target::Thread::array_write_barrier_entry_point_offset()));
  };
}

void Assembler::PushRegister(Register r) {
  ASSERT(r != SP);
  subi(SP, SP, target::kWordSize);
  sx(r, Address(SP, 0));
}
void Assembler::PopRegister(Register r) {
  ASSERT(r != SP);
  lx(r, Address(SP, 0));
  addi(SP, SP, target::kWordSize);
}

void Assembler::PushRegisterPair(Register r0, Register r1) {
  ASSERT(r0 != SP);
  ASSERT(r1 != SP);
  subi(SP, SP, 2 * target::kWordSize);
  sx(r1, Address(SP, target::kWordSize));
  sx(r0, Address(SP, 0));
}

void Assembler::PopRegisterPair(Register r0, Register r1) {
  ASSERT(r0 != SP);
  ASSERT(r1 != SP);
  lx(r1, Address(SP, target::kWordSize));
  lx(r0, Address(SP, 0));
  addi(SP, SP, 2 * target::kWordSize);
}

void Assembler::PushRegisters(const RegisterSet& regs) {
  // The order in which the registers are pushed must match the order
  // in which the registers are encoded in the safepoint's stack map.

  intptr_t size = regs.SpillSize();
  if (size == 0) {
    return;  // Skip no-op SP update.
  }

  subi(SP, SP, size);
  intptr_t offset = size;
  for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; i--) {
    FRegister reg = static_cast<FRegister>(i);
    if (regs.ContainsFpuRegister(reg)) {
      offset -= kFpuRegisterSize;
      fsd(reg, Address(SP, offset));
    }
  }
  for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; i--) {
    Register reg = static_cast<Register>(i);
    if (regs.ContainsRegister(reg)) {
      offset -= target::kWordSize;
      sx(reg, Address(SP, offset));
    }
  }
  ASSERT(offset == 0);
}

void Assembler::PopRegisters(const RegisterSet& regs) {
  // The order in which the registers are pushed must match the order
  // in which the registers are encoded in the safepoint's stack map.

  intptr_t size = regs.SpillSize();
  if (size == 0) {
    return;  // Skip no-op SP update.
  }
  intptr_t offset = 0;
  for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
    Register reg = static_cast<Register>(i);
    if (regs.ContainsRegister(reg)) {
      lx(reg, Address(SP, offset));
      offset += target::kWordSize;
    }
  }
  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
    FRegister reg = static_cast<FRegister>(i);
    if (regs.ContainsFpuRegister(reg)) {
      fld(reg, Address(SP, offset));
      offset += kFpuRegisterSize;
    }
  }
  ASSERT(offset == size);
  addi(SP, SP, size);
}

void Assembler::PushRegistersAligned(const RegisterSet& register_set,
                                     intptr_t space) {
  PushRegisters(register_set);
  intptr_t aligned_space = Utils::RoundUp(register_set.SpillSize() + space,
                                          OS::ActivationFrameAlignment()) -
                           register_set.SpillSize();
  if (aligned_space != 0) {
    subi(SP, SP, aligned_space);
  }
}
void Assembler::PopRegistersAligned(const RegisterSet& register_set,
                                    intptr_t space) {
  intptr_t aligned_space = Utils::RoundUp(register_set.SpillSize() + space,
                                          OS::ActivationFrameAlignment()) -
                           register_set.SpillSize();
  if (aligned_space != 0) {
    addi(SP, SP, aligned_space);
  }
  PopRegisters(register_set);
}

void Assembler::PushRegistersInOrder(std::initializer_list<Register> regs) {
  intptr_t offset = regs.size() * target::kWordSize;
  subi(SP, SP, offset);
  for (Register reg : regs) {
    ASSERT(reg != SP);
    offset -= target::kWordSize;
    sx(reg, Address(SP, offset));
  }
}

void Assembler::PushNativeCalleeSavedRegisters() {
  RegisterSet regs(kAbiPreservedCpuRegs, kAbiPreservedFpuRegs);
  intptr_t size = (regs.CpuRegisterCount() * target::kWordSize) +
                  (regs.FpuRegisterCount() * sizeof(double));
  subi(SP, SP, size);
  intptr_t offset = 0;
  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
    FRegister reg = static_cast<FRegister>(i);
    if (regs.ContainsFpuRegister(reg)) {
      fsd(reg, Address(SP, offset));
      offset += sizeof(double);
    }
  }
  for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
    Register reg = static_cast<Register>(i);
    if (regs.ContainsRegister(reg)) {
      sx(reg, Address(SP, offset));
      offset += target::kWordSize;
    }
  }
  ASSERT(offset == size);
}

void Assembler::PopNativeCalleeSavedRegisters() {
  RegisterSet regs(kAbiPreservedCpuRegs, kAbiPreservedFpuRegs);
  intptr_t size = (regs.CpuRegisterCount() * target::kWordSize) +
                  (regs.FpuRegisterCount() * sizeof(double));
  intptr_t offset = 0;
  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
    FRegister reg = static_cast<FRegister>(i);
    if (regs.ContainsFpuRegister(reg)) {
      fld(reg, Address(SP, offset));
      offset += sizeof(double);
    }
  }
  for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
    Register reg = static_cast<Register>(i);
    if (regs.ContainsRegister(reg)) {
      lx(reg, Address(SP, offset));
      offset += target::kWordSize;
    }
  }
  ASSERT(offset == size);
  addi(SP, SP, size);
}

void Assembler::ExtendValue(Register rd, Register rn, OperandSize sz) {
  // For non-word bitsizes, use slli + srli for zero extension and slli + srai
  // for sign extension when there is no single instruction to perform it.
  switch (sz) {
#if XLEN == 64
    case kEightBytes:
      if (rd == rn) return;  // No operation needed.
      return mv(rd, rn);
    case kUnsignedFourBytes:
      if (Supports(RV_Zba)) {
        return zextw(rd, rn);
      }
      slli(rd, rn, XLEN - 32);
      return srli(rd, rn, XLEN - 32);
    case kFourBytes:
      return sextw(rd, rn);
#elif XLEN == 32
    case kUnsignedFourBytes:
    case kFourBytes:
      if (rd == rn) return;  // No operation needed.
      return mv(rd, rn);
#else
#error Unexpected XLEN.
#endif
    case kUnsignedTwoBytes:
      if (Supports(RV_Zbb)) {
        return zexth(rd, rn);
      }
      slli(rd, rn, XLEN - 16);
      return srli(rd, rn, XLEN - 16);
    case kTwoBytes:
      if (Supports(RV_Zbb)) {
        return sexth(rd, rn);
      }
      slli(rd, rn, XLEN - 16);
      return srai(rd, rn, XLEN - 16);
    case kUnsignedByte:
      return andi(rd, rn, kMaxUint8);
    case kByte:
      if (Supports(RV_Zbb)) {
        return sextb(rd, rn);
      }
      slli(rd, rn, XLEN - 8);
      return srai(rd, rn, XLEN - 8);
    default:
      UNIMPLEMENTED();
      break;
  }
  UNIMPLEMENTED();
}
void Assembler::ExtendAndSmiTagValue(Register rd, Register rn, OperandSize sz) {
  if (sz == kWordBytes) {
    SmiTag(rd, rn);
    return;
  }

  switch (sz) {
#if XLEN == 64
    case kUnsignedFourBytes:
      slli(rd, rn, XLEN - kBitsPerInt32);
      srli(rd, rd, XLEN - kBitsPerInt32 - kSmiTagShift);
      return;
    case kFourBytes:
      slli(rd, rn, XLEN - kBitsPerInt32);
      srai(rd, rd, XLEN - kBitsPerInt32 - kSmiTagShift);
      return;
#endif
    case kUnsignedTwoBytes:
      slli(rd, rn, XLEN - kBitsPerInt16);
      srli(rd, rd, XLEN - kBitsPerInt16 - kSmiTagShift);
      return;
    case kTwoBytes:
      slli(rd, rn, XLEN - kBitsPerInt16);
      srai(rd, rd, XLEN - kBitsPerInt16 - kSmiTagShift);
      return;
    case kUnsignedByte:
      slli(rd, rn, XLEN - kBitsPerInt8);
      srli(rd, rd, XLEN - kBitsPerInt8 - kSmiTagShift);
      return;
    case kByte:
      slli(rd, rn, XLEN - kBitsPerInt8);
      srai(rd, rd, XLEN - kBitsPerInt8 - kSmiTagShift);
      return;
    default:
      UNIMPLEMENTED();
      break;
  }
}

// Unconditional jump to a given address in memory. Clobbers TMP.
void Assembler::Jump(const Address& address) {
  lx(TMP2, address);
  jr(TMP2);
}

void Assembler::TsanLoadAcquire(Register dst,
                                const Address& addr,
                                OperandSize size) {
  ASSERT(addr.base() != SP);
  ASSERT(addr.base() != FP);
  ASSERT(dst != SP);
  ASSERT(dst != FP);
  Comment("TsanLoadAcquire");

  RegisterSet registers(kDartVolatileCpuRegs & ~(1 << dst),
                        kAbiVolatileFpuRegs);

  subi(SP, SP, 4 * target::kWordSize);
  sx(RA, Address(SP, 3 * target::kWordSize));
  sx(FP, Address(SP, 2 * target::kWordSize));
  sx(CODE_REG, Address(SP, 1 * target::kWordSize));
  sx(PP, Address(SP, 0 * target::kWordSize));
  addi(FP, SP, 4 * target::kWordSize);

  PushRegisters(registers);
  ReserveAlignedFrameSpace(0);

  AddImmediate(A0, addr.base(), addr.offset());
  LoadImmediate(A1, static_cast<intx_t>(std::memory_order_acquire));

  switch (size) {
    case kEightBytes:
      lx(TMP2, compiler::Address(
                   THR, kTsanAtomic64LoadRuntimeEntry.OffsetFromThread()));
      break;
    case kUnsignedFourBytes:
      lx(TMP2, compiler::Address(
                   THR, kTsanAtomic32LoadRuntimeEntry.OffsetFromThread()));
      break;
    default:
      UNIMPLEMENTED();
      break;
  }
  sx(TMP2, compiler::Address(THR, target::Thread::vm_tag_offset()));
  jalr(TMP2);
  LoadImmediate(TMP2, VMTag::kDartTagId);
  sx(TMP2, compiler::Address(THR, target::Thread::vm_tag_offset()));

  MoveRegister(dst, A0);

  subi(SP, FP, registers.SpillSize() + 4 * target::kWordSize);
  PopRegisters(registers);

  subi(SP, FP, 4 * target::kWordSize);
  lx(PP, Address(SP, 0 * target::kWordSize));
  lx(CODE_REG, Address(SP, 1 * target::kWordSize));
  lx(FP, Address(SP, 2 * target::kWordSize));
  lx(RA, Address(SP, 3 * target::kWordSize));
  addi(SP, SP, 4 * target::kWordSize);
}

void Assembler::TsanStoreRelease(Register src,
                                 const Address& addr,
                                 OperandSize size) {
  ASSERT(addr.base() != SP);
  ASSERT(addr.base() != FP);
  ASSERT(src != SP);
  ASSERT(src != FP);
  Comment("TsanStoreRelease");

  LeafRuntimeScope rt(this, /*frame_size=*/0, /*preserve_registers=*/true);

  if (src == A0) {
    MoveRegister(A1, src);
    AddImmediate(A0, addr.base(), addr.offset());
  } else {
    AddImmediate(A0, addr.base(), addr.offset());
    MoveRegister(A1, src);
  }
  LoadImmediate(A2, static_cast<intx_t>(std::memory_order_release));

  switch (size) {
    case kEightBytes:
      rt.Call(kTsanAtomic64StoreRuntimeEntry, /*argument_count=*/3);
      break;
    case kFourBytes:
    case kUnsignedFourBytes:
      rt.Call(kTsanAtomic32StoreRuntimeEntry, /*argument_count=*/3);
      break;
    default:
      UNIMPLEMENTED();
      break;
  }
}

void Assembler::TsanFuncEntry(bool preserve_registers) {
  LeafRuntimeScope rt(this, /*frame_size=*/0, preserve_registers);
  lx(A0, Address(FP, target::frame_layout.saved_caller_fp_from_fp *
                         target::kWordSize));
  lx(A0, Address(A0, target::frame_layout.saved_caller_pc_from_fp *
                         target::kWordSize));
  rt.Call(kTsanFuncEntryRuntimeEntry, /*argument_count=*/1);
}

void Assembler::TsanFuncExit(bool preserve_registers) {
  LeafRuntimeScope rt(this, /*frame_size=*/0, preserve_registers);
  rt.Call(kTsanFuncExitRuntimeEntry, /*argument_count=*/0);
}

void Assembler::LoadAcquire(Register dst,
                            const Address& address,
                            OperandSize size) {
  ASSERT(dst != address.base());

  if (FLAG_target_thread_sanitizer) {
    TsanLoadAcquire(dst, address, size);
    return;
  }

  if (Supports(RV_Zalasr)) {
    Address addr = PrepareAtomicOffset(address.base(), address.offset());
    switch (size) {
#if XLEN == 64
      case kEightBytes:
        ld(dst, addr, std::memory_order_acquire);
        break;
#endif
      case kFourBytes:
        lw(dst, addr, std::memory_order_acquire);
        break;
      case kTwoBytes:
        lh(dst, addr, std::memory_order_acquire);
        break;
      case kByte:
        lb(dst, addr, std::memory_order_acquire);
        break;
      default:
        UNREACHABLE();
    }
  } else {
    Load(dst, address, size);
    fence(HartEffects::kRead, HartEffects::kMemory);
  }
}

void Assembler::StoreRelease(Register src,
                             const Address& address,
                             OperandSize size) {
  if (FLAG_target_thread_sanitizer) {
    TsanStoreRelease(src, address, size);
    return;
  }

  if (Supports(RV_Zalasr)) {
    Address addr = PrepareAtomicOffset(address.base(), address.offset());
    switch (size) {
#if XLEN == 64
      case kEightBytes:
        sd(src, addr, std::memory_order_release);
        break;
#endif
      case kUnsignedFourBytes:
      case kFourBytes:
        sw(src, addr, std::memory_order_release);
        break;
      case kUnsignedTwoBytes:
      case kTwoBytes:
        sh(src, addr, std::memory_order_release);
        break;
      case kUnsignedByte:
      case kByte:
        sb(src, addr, std::memory_order_release);
        break;
      default:
        UNREACHABLE();
    }
  } else {
    fence(HartEffects::kMemory, HartEffects::kWrite);
    Store(src, address, size);
  }
}

void Assembler::CompareWithMemoryValue(Register value,
                                       Address address,
                                       OperandSize size) {
#if XLEN >= 64
  ASSERT(size == kEightBytes || size == kFourBytes);
  if (size == kFourBytes) {
    lw(TMP2, address);
  } else {
    ld(TMP2, address);
  }
#else
  ASSERT_EQUAL(size, kFourBytes);
  lx(TMP2, address);
#endif
  CompareRegisters(value, TMP2);
}

void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) {
  if (frame_space != 0) {
    addi(SP, SP, -frame_space);
  }
  const intptr_t kAbiStackAlignment = 16;  // For both 32 and 64 bit.
  andi(SP, SP, ~(kAbiStackAlignment - 1));
}

// In debug mode, this generates code to check that:
//   FP + kExitLinkSlotFromEntryFp == SP
// or triggers breakpoint otherwise.
void Assembler::EmitEntryFrameVerification() {
#if defined(DEBUG)
  Label done;
  ASSERT(!constant_pool_allowed());
  LoadImmediate(TMP, target::frame_layout.exit_link_slot_from_entry_fp *
                         target::kWordSize);
  add(TMP, TMP, FPREG);
  beq(TMP, SPREG, &done, kNearJump);

  Breakpoint();

  Bind(&done);
#endif
}

void Assembler::CompareRegisters(Register rn, Register rm) {
  ASSERT(deferred_compare_ == kNone);
  deferred_compare_ = kCompareReg;
  deferred_left_ = rn;
  deferred_reg_ = rm;
}
void Assembler::CompareObjectRegisters(Register rn, Register rm) {
  CompareRegisters(rn, rm);
}
void Assembler::TestRegisters(Register rn, Register rm) {
  ASSERT(deferred_compare_ == kNone);
  deferred_compare_ = kTestReg;
  deferred_left_ = rn;
  deferred_reg_ = rm;
}

void Assembler::BranchIf(Condition condition,
                         Label* label,
                         JumpDistance distance) {
  ASSERT(deferred_compare_ != kNone);

  if (deferred_compare_ == kCompareImm || deferred_compare_ == kCompareReg) {
    Register left = deferred_left_;
    Register right;
    if (deferred_compare_ == kCompareImm) {
      if (deferred_imm_ == 0) {
        right = ZR;
      } else {
        LoadImmediate(TMP2, deferred_imm_);
        right = TMP2;
      }
    } else {
      right = deferred_reg_;
    }
    switch (condition) {
      case EQUAL:
        beq(left, right, label, distance);
        break;
      case NOT_EQUAL:
        bne(left, right, label, distance);
        break;
      case LESS:
        blt(left, right, label, distance);
        break;
      case LESS_EQUAL:
        ble(left, right, label, distance);
        break;
      case GREATER_EQUAL:
        bge(left, right, label, distance);
        break;
      case GREATER:
        bgt(left, right, label, distance);
        break;
      case UNSIGNED_LESS:
        bltu(left, right, label, distance);
        break;
      case UNSIGNED_LESS_EQUAL:
        bleu(left, right, label, distance);
        break;
      case UNSIGNED_GREATER_EQUAL:
        bgeu(left, right, label, distance);
        break;
      case UNSIGNED_GREATER:
        bgtu(left, right, label, distance);
        break;
      case OVERFLOW:
      case NO_OVERFLOW:
        FATAL("Use Add/Subtract/MultiplyBranchOverflow instead.");
      default:
        UNREACHABLE();
    }
  } else if (deferred_compare_ == kTestImm || deferred_compare_ == kTestReg) {
    uintx_t uimm = deferred_imm_;
    if (deferred_compare_ == kTestReg) {
      and_(TMP2, deferred_left_, deferred_reg_);
    } else if (IsITypeImm(deferred_imm_)) {
      andi(TMP2, deferred_left_, deferred_imm_);
    } else if (Utils::IsPowerOfTwo(uimm)) {
      intptr_t shift = Utils::ShiftForPowerOfTwo(uimm);
      Register sign;
      if (shift == XLEN - 1) {
        sign = deferred_left_;
      } else {
        slli(TMP2, deferred_left_, XLEN - 1 - shift);
        sign = TMP2;
      }
      switch (condition) {
        case ZERO:
          bgez(sign, label, distance);
          break;
        case NOT_ZERO:
          bltz(sign, label, distance);
          break;
        default:
          UNREACHABLE();
      }
      deferred_compare_ = kNone;  // Consumed.
      return;
    } else {
      AndImmediate(TMP2, deferred_left_, deferred_imm_);
    }
    switch (condition) {
      case ZERO:
        beqz(TMP2, label, distance);
        break;
      case NOT_ZERO:
        bnez(TMP2, label, distance);
        break;
      default:
        UNREACHABLE();
    }
  } else {
    UNREACHABLE();
  }
  deferred_compare_ = kNone;  // Consumed.
}

void Assembler::SetIf(Condition condition, Register rd) {
  ASSERT(deferred_compare_ != kNone);

  if (deferred_compare_ == kCompareImm) {
    if (deferred_imm_ == 0) {
      deferred_compare_ = kCompareReg;
      deferred_reg_ = ZR;
      SetIf(condition, rd);
      return;
    }
    if (!IsITypeImm(deferred_imm_) || !IsITypeImm(deferred_imm_ + 1)) {
      LoadImmediate(TMP2, deferred_imm_);
      deferred_compare_ = kCompareReg;
      deferred_reg_ = TMP2;
      SetIf(condition, rd);
      return;
    }
    Register left = deferred_left_;
    intx_t right = deferred_imm_;
    switch (condition) {
      case EQUAL:
        subi(rd, left, right);
        seqz(rd, rd);
        break;
      case NOT_EQUAL:
        subi(rd, left, right);
        snez(rd, rd);
        break;
      case LESS:
        slti(rd, left, right);
        break;
      case LESS_EQUAL:
        slti(rd, left, right + 1);
        break;
      case GREATER_EQUAL:
        slti(rd, left, right);
        xori(rd, rd, 1);
        break;
      case GREATER:
        slti(rd, left, right + 1);
        xori(rd, rd, 1);
        break;
      case UNSIGNED_LESS:
        sltiu(rd, left, right);
        break;
      case UNSIGNED_LESS_EQUAL:
        sltiu(rd, left, right + 1);
        break;
      case UNSIGNED_GREATER_EQUAL:
        sltiu(rd, left, right);
        xori(rd, rd, 1);
        break;
      case UNSIGNED_GREATER:
        sltiu(rd, left, right + 1);
        xori(rd, rd, 1);
        break;
      default:
        UNREACHABLE();
    }
  } else if (deferred_compare_ == kCompareReg) {
    Register left = deferred_left_;
    Register right = deferred_reg_;
    switch (condition) {
      case EQUAL:
        if (right == ZR) {
          seqz(rd, left);
        } else {
          xor_(rd, left, right);
          seqz(rd, rd);
        }
        break;
      case NOT_EQUAL:
        if (right == ZR) {
          snez(rd, left);
        } else {
          xor_(rd, left, right);
          snez(rd, rd);
        }
        break;
      case LESS:
        slt(rd, left, right);
        break;
      case LESS_EQUAL:
        slt(rd, right, left);
        xori(rd, rd, 1);
        break;
      case GREATER_EQUAL:
        slt(rd, left, right);
        xori(rd, rd, 1);
        break;
      case GREATER:
        slt(rd, right, left);
        break;
      case UNSIGNED_LESS:
        sltu(rd, left, right);
        break;
      case UNSIGNED_LESS_EQUAL:
        sltu(rd, right, left);
        xori(rd, rd, 1);
        break;
      case UNSIGNED_GREATER_EQUAL:
        sltu(rd, left, right);
        xori(rd, rd, 1);
        break;
      case UNSIGNED_GREATER:
        sltu(rd, right, left);
        break;
      default:
        UNREACHABLE();
    }
  } else if (deferred_compare_ == kTestImm) {
    uintx_t uimm = deferred_imm_;
    if (Utils::IsPowerOfTwo(uimm)) {
      int shamt = Utils::ShiftForPowerOfTwo(uimm);
      if (shamt == 0) {
        andi(rd, deferred_left_, 1);
      } else if (shamt == XLEN - 1) {
        sltz(rd, deferred_left_);
      } else if (Supports(RV_Zbs)) {
        bexti(rd, deferred_left_, shamt);
      } else {
        srli(rd, deferred_left_, shamt);
        andi(rd, rd, 1);
      }
      switch (condition) {
        case ZERO:
          xori(rd, rd, 1);
          break;
        case NOT_ZERO:
          break;
        default:
          UNREACHABLE();
      }
    } else {
      AndImmediate(rd, deferred_left_, deferred_imm_);
      switch (condition) {
        case ZERO:
          seqz(rd, rd);
          break;
        case NOT_ZERO:
          snez(rd, rd);
          break;
        default:
          UNREACHABLE();
      }
    }
  } else if (deferred_compare_ == kTestReg) {
    and_(rd, deferred_left_, deferred_reg_);
    switch (condition) {
      case ZERO:
        seqz(rd, rd);
        break;
      case NOT_ZERO:
        snez(rd, rd);
        break;
      default:
        UNREACHABLE();
    }
  } else {
    UNREACHABLE();
  }

  deferred_compare_ = kNone;  // Consumed.
}

void Assembler::ZeroIf(Condition condition, Register rd, Register otherwise) {
  ASSERT(deferred_compare_ != kNone);
  ASSERT(rd != otherwise);

  if (deferred_compare_ == kCompareImm) {
    if (deferred_imm_ == 0) {
      deferred_compare_ = kCompareReg;
      deferred_reg_ = ZR;
      ZeroIf(condition, rd, otherwise);
      return;
    }
    if (!IsITypeImm(deferred_imm_) || !IsITypeImm(deferred_imm_ + 1)) {
      LoadImmediate(TMP2, deferred_imm_);
      deferred_compare_ = kCompareReg;
      deferred_reg_ = TMP2;
      ZeroIf(condition, rd, otherwise);
      return;
    }
    Register left = deferred_left_;
    intx_t right = deferred_imm_;
    switch (condition) {
      case EQUAL:
        subi(rd, left, right);
        czeroeqz(rd, otherwise, rd);
        break;
      case NOT_EQUAL:
        subi(rd, left, right);
        czeronez(rd, otherwise, rd);
        break;
      case LESS:
        slti(rd, left, right);
        czeronez(rd, otherwise, rd);
        break;
      case LESS_EQUAL:
        slti(rd, left, right + 1);
        czeronez(rd, otherwise, rd);
        break;
      case GREATER_EQUAL:
        slti(rd, left, right);
        czeroeqz(rd, otherwise, rd);
        break;
      case GREATER:
        slti(rd, left, right + 1);
        czeroeqz(rd, otherwise, rd);
        break;
      case UNSIGNED_LESS:
        sltiu(rd, left, right);
        czeronez(rd, otherwise, rd);
        break;
      case UNSIGNED_LESS_EQUAL:
        sltiu(rd, left, right + 1);
        czeronez(rd, otherwise, rd);
        break;
      case UNSIGNED_GREATER_EQUAL:
        sltiu(rd, left, right);
        czeroeqz(rd, otherwise, rd);
        break;
      case UNSIGNED_GREATER:
        sltiu(rd, left, right + 1);
        czeroeqz(rd, otherwise, rd);
        break;
      default:
        UNREACHABLE();
    }
  } else if (deferred_compare_ == kCompareReg) {
    Register left = deferred_left_;
    Register right = deferred_reg_;
    switch (condition) {
      case EQUAL:
        if (right == ZR) {
          czeroeqz(rd, otherwise, left);
        } else {
          xor_(rd, left, right);
          czeroeqz(rd, otherwise, rd);
        }
        break;
      case NOT_EQUAL:
        if (right == ZR) {
          czeronez(rd, otherwise, left);
        } else {
          xor_(rd, left, right);
          czeronez(rd, otherwise, rd);
        }
        break;
      case LESS:
        slt(rd, left, right);
        czeronez(rd, otherwise, rd);
        break;
      case LESS_EQUAL:
        slt(rd, right, left);
        czeroeqz(rd, otherwise, rd);
        break;
      case GREATER_EQUAL:
        slt(rd, left, right);
        czeroeqz(rd, otherwise, rd);
        break;
      case GREATER:
        slt(rd, right, left);
        czeronez(rd, otherwise, rd);
        break;
      case UNSIGNED_LESS:
        sltu(rd, left, right);
        czeronez(rd, otherwise, rd);
        break;
      case UNSIGNED_LESS_EQUAL:
        sltu(rd, right, left);
        czeroeqz(rd, otherwise, rd);
        break;
      case UNSIGNED_GREATER_EQUAL:
        sltu(rd, left, right);
        czeroeqz(rd, otherwise, rd);
        break;
      case UNSIGNED_GREATER:
        sltu(rd, right, left);
        czeronez(rd, otherwise, rd);
        break;
      default:
        UNREACHABLE();
    }
  } else if (deferred_compare_ == kTestImm) {
    uintx_t uimm = deferred_imm_;
    if (IsITypeImm(deferred_imm_)) {
      andi(rd, deferred_left_, deferred_imm_);
    } else if (Utils::IsPowerOfTwo(uimm)) {
      int shamt = Utils::ShiftForPowerOfTwo(uimm);
      if (shamt == XLEN - 1) {
        sltz(rd, deferred_left_);
      } else if (Supports(RV_Zbs)) {
        bexti(rd, deferred_left_, shamt);
      } else {
        srli(rd, deferred_left_, shamt);
        andi(rd, rd, 1);
      }
    } else {
      AndImmediate(rd, deferred_left_, deferred_imm_);
    }
    switch (condition) {
      case ZERO:
        czeroeqz(rd, otherwise, rd);
        break;
      case NOT_ZERO:
        czeronez(rd, otherwise, rd);
        break;
      default:
        UNREACHABLE();
    }
  } else if (deferred_compare_ == kTestReg) {
    and_(rd, deferred_left_, deferred_reg_);
    switch (condition) {
      case ZERO:
        czeroeqz(rd, otherwise, rd);
        break;
      case NOT_ZERO:
        czeronez(rd, otherwise, rd);
        break;
      default:
        UNREACHABLE();
    }
  } else {
    UNREACHABLE();
  }

  deferred_compare_ = kNone;  // Consumed.
}

void Assembler::BranchIfZero(Register rn, Label* label, JumpDistance distance) {
  beqz(rn, label, distance);
}

void Assembler::BranchIfBit(Register rn,
                            intptr_t bit_number,
                            Condition condition,
                            Label* label,
                            JumpDistance distance) {
  ASSERT(rn != TMP2);
  andi(TMP2, rn, 1 << bit_number);
  if (condition == ZERO) {
    beqz(TMP2, label, distance);
  } else if (condition == NOT_ZERO) {
    bnez(TMP2, label, distance);
  } else {
    UNREACHABLE();
  }
}

void Assembler::BranchIfNotSmi(Register reg,
                               Label* label,
                               JumpDistance distance) {
  ASSERT(reg != TMP2);
  andi(TMP2, reg, kSmiTagMask);
  bnez(TMP2, label, distance);
}
void Assembler::BranchIfSmi(Register reg, Label* label, JumpDistance distance) {
  ASSERT(reg != TMP2);
  andi(TMP2, reg, kSmiTagMask);
  beqz(TMP2, label, distance);
}

void Assembler::ArithmeticShiftRightImmediate(Register dst,
                                              Register src,
                                              int32_t shift,
                                              OperandSize sz) {
  ASSERT(sz == kEightBytes || sz == kFourBytes);
  ASSERT((shift >= 0) && (shift < OperandSizeInBits(sz)));
  if (shift == 0) {
    return ExtendValue(dst, src, sz);
  }
#if XLEN >= 64
  if (sz == kFourBytes) {
    return sraiw(dst, src, shift);
  }
#endif
  srai(dst, src, shift);
}

void Assembler::CompareWords(Register reg1,
                             Register reg2,
                             intptr_t offset,
                             Register count,
                             Register temp,
                             Label* equals) {
  Label loop;
  Bind(&loop);
  BranchIfZero(count, equals, Assembler::kNearJump);
  AddImmediate(count, -1);
  lx(temp, FieldAddress(reg1, offset));
  lx(TMP, FieldAddress(reg2, offset));
  addi(reg1, reg1, target::kWordSize);
  addi(reg2, reg2, target::kWordSize);
  beq(temp, TMP, &loop, Assembler::kNearJump);
}

void Assembler::JumpAndLink(intptr_t target_code_pool_index,
                            CodeEntryKind entry_kind) {
  // Avoid clobbering CODE_REG when invoking code in precompiled mode.
  // We don't actually use CODE_REG in the callee and caller might
  // be using CODE_REG for a live value (e.g. a value that is alive
  // across invocation of a shared stub like the one we use for
  // allocating Mint boxes).
  const Register code_reg = FLAG_precompiled_mode ? TMP : CODE_REG;
  LoadWordFromPoolIndex(code_reg, target_code_pool_index);
  Call(FieldAddress(code_reg, target::Code::entry_point_offset(entry_kind)));
}

void Assembler::JumpAndLink(
    const Code& target,
    ObjectPoolBuilderEntry::Patchability patchable,
    CodeEntryKind entry_kind,
    ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior) {
  const intptr_t index = object_pool_builder().FindObject(
      ToObject(target), patchable, snapshot_behavior);
  JumpAndLink(index, entry_kind);
}

void Assembler::JumpAndLinkWithEquivalence(const Code& target,
                                           const Object& equivalence,
                                           CodeEntryKind entry_kind) {
  const intptr_t index =
      object_pool_builder().FindObject(ToObject(target), equivalence);
  JumpAndLink(index, entry_kind);
}

void Assembler::Call(Address target) {
  lx(RA, target);
  jalr(RA);
}

void Assembler::Call(Register target) {
  jalr(target);
}

void Assembler::AddShifted(Register dest,
                           Register base,
                           Register index,
                           intx_t shift) {
  if (shift == 0) {
    add(dest, index, base);
  } else if (Supports(RV_Zba) && (shift == 1)) {
    sh1add(dest, index, base);
  } else if (Supports(RV_Zba) && (shift == 2)) {
    sh2add(dest, index, base);
  } else if (Supports(RV_Zba) && (shift == 3)) {
    sh3add(dest, index, base);
  } else if (shift < 0) {
    if (base != dest) {
      srai(dest, index, -shift);
      add(dest, dest, base);
    } else {
      srai(TMP2, index, -shift);
      add(dest, TMP2, base);
    }
  } else {
    if (base != dest) {
      slli(dest, index, shift);
      add(dest, dest, base);
    } else {
      slli(TMP2, index, shift);
      add(dest, TMP2, base);
    }
  }
}

void Assembler::AddImmediate(Register rd,
                             Register rs1,
                             intx_t imm,
                             OperandSize sz) {
  if ((imm == 0) && (rd == rs1)) {
    return;
  }
  if (IsITypeImm(imm)) {
    addi(rd, rs1, imm);
  } else {
    ASSERT(rs1 != TMP2);
    LoadImmediate(TMP2, imm);
    add(rd, rs1, TMP2);
  }
}

void Assembler::MulImmediate(Register rd,
                             Register rs1,
                             intx_t imm,
                             OperandSize sz) {
  if (Utils::IsPowerOfTwo(imm)) {
    const intx_t shift = Utils::ShiftForPowerOfTwo(imm);
#if XLEN >= 64
    ASSERT(sz == kFourBytes || sz == kEightBytes);
    if (sz == kFourBytes) {
      slliw(rd, rs1, shift);
    } else {
      slli(rd, rs1, shift);
    }
#else
    ASSERT(sz == kFourBytes);
    slli(rd, rs1, shift);
#endif
  } else {
    LoadImmediate(TMP, imm);
#if XLEN >= 64
    ASSERT(sz == kFourBytes || sz == kEightBytes);
    if (sz == kFourBytes) {
      mulw(rd, rs1, TMP);
    } else {
      mul(rd, rs1, TMP);
    }
#else
    ASSERT(sz == kFourBytes);
    mul(rd, rs1, TMP);
#endif
  }
}

void Assembler::AndImmediate(Register rd,
                             Register rs1,
                             intx_t imm,
                             OperandSize sz) {
  ASSERT(sz == kEightBytes || sz == kUnsignedFourBytes || sz == kFourBytes);
  const intptr_t bit_size = OperandSizeInBits(sz);
  ASSERT(Utils::IsInt(bit_size, imm) || Utils::IsUint(bit_size, imm));
  // Clear the upper bits of the immediate for non-word-sized uses.
  uintx_t uimm = bit_size == XLEN ? imm : static_cast<uint32_t>(imm);
  if (bit_size == XLEN && imm == -1) {
    // All bits set, so allow this operation to be a no-op if rd == rs1.
    MoveRegister(rd, rs1);
  } else if (bit_size < XLEN && static_cast<int32_t>(imm) == -1) {
    ExtendValue(rd, rs1, kUnsignedFourBytes);
  } else if (IsITypeImm(uimm)) {
    andi(rd, rs1, uimm);
  } else if (Supports(RV_Zbs) && Utils::IsPowerOfTwo(~uimm)) {
    bclri(rd, rs1, Utils::ShiftForPowerOfTwo(~uimm));
  } else if (Utils::IsPowerOfTwo(uimm + 1)) {
    intptr_t shift = Utils::ShiftForPowerOfTwo(uimm + 1);
    if (Supports(RV_Zbb) && (shift == 16)) {
      zexth(rd, rs1);
    } else {
      slli(rd, rs1, XLEN - shift);
      srli(rd, rd, XLEN - shift);
    }
  } else {
    ASSERT(rs1 != TMP2);
    LoadImmediate(TMP2, uimm);
    and_(rd, rs1, TMP2);
  }
}
void Assembler::OrImmediate(Register rd,
                            Register rs1,
                            intx_t imm,
                            OperandSize sz) {
  uintx_t uimm = imm;
  if (imm == 0) {
    MoveRegister(rd, rs1);
  } else if (IsITypeImm(imm)) {
    ori(rd, rs1, imm);
  } else if (Supports(RV_Zbs) && Utils::IsPowerOfTwo(uimm)) {
    bseti(rd, rs1, Utils::ShiftForPowerOfTwo(uimm));
  } else {
    ASSERT(rs1 != TMP2);
    LoadImmediate(TMP2, imm);
    or_(rd, rs1, TMP2);
  }
}
void Assembler::XorImmediate(Register rd,
                             Register rs1,
                             intx_t imm,
                             OperandSize sz) {
  uintx_t uimm = imm;
  if (imm == 0) {
    MoveRegister(rd, rs1);
  } else if (IsITypeImm(imm)) {
    xori(rd, rs1, imm);
  } else if (Supports(RV_Zbs) && Utils::IsPowerOfTwo(uimm)) {
    binvi(rd, rs1, Utils::ShiftForPowerOfTwo(uimm));
  } else {
    ASSERT(rs1 != TMP2);
    LoadImmediate(TMP2, imm);
    xor_(rd, rs1, TMP2);
  }
}

void Assembler::LslImmediate(Register rd,
                             Register rn,
                             int32_t shift,
                             OperandSize sz) {
  ASSERT(sz == kEightBytes || sz == kFourBytes || sz == kUnsignedFourBytes);
  ASSERT((shift >= 0) && (shift < OperandSizeInBits(sz)));
  if (shift == 0) {
    return ExtendValue(rd, rn, sz);
  }
#if XLEN >= 64
  if (sz == kFourBytes) {
    return slliw(rd, rn, shift);
  }
  if (sz == kUnsignedFourBytes) {
    // Not slliuw even when available. That zero extends the input, not the
    // output.

    // Clear upper bits in addition to the shift.
    slli(rd, rn, shift + (XLEN / 2));
    return srli(rd, rn, XLEN / 2);
  }
#endif
  slli(rd, rn, shift);
}

void Assembler::TestImmediate(Register rn, intx_t imm, OperandSize sz) {
  ASSERT(deferred_compare_ == kNone);
  deferred_compare_ = kTestImm;
  deferred_left_ = rn;
  deferred_imm_ = imm;
}
void Assembler::CompareImmediate(Register rn, intx_t imm, OperandSize sz) {
  ASSERT(deferred_compare_ == kNone);
  deferred_compare_ = kCompareImm;
  deferred_left_ = rn;
  deferred_imm_ = imm;
}

Address Assembler::PrepareLargeOffset(Register base, int32_t offset) {
  ASSERT(base != TMP2);
  if (IsITypeImm(offset)) {
    return Address(base, offset);
  }
  intx_t lo = ImmLo(offset);
  intx_t hi = ImmHi(offset);
  ASSERT(hi != 0);
  lui(TMP2, hi);
  add(TMP2, TMP2, base);
  return Address(TMP2, lo);
}

Address Assembler::PrepareAtomicOffset(Register base, int32_t offset) {
  ASSERT(base != TMP2);
  if (offset == 0) {
    return Address(base, 0);
  }
  AddImmediate(TMP2, base, offset);
  return Address(TMP2, 0);
}

void Assembler::Load(Register dest, const Address& address, OperandSize sz) {
  Address addr = PrepareLargeOffset(address.base(), address.offset());
  switch (sz) {
#if XLEN == 64
    case kEightBytes:
      return ld(dest, addr);
    case kUnsignedFourBytes:
      return lwu(dest, addr);
#elif XLEN == 32
    case kUnsignedFourBytes:
      return lw(dest, addr);
#endif
    case kFourBytes:
      return lw(dest, addr);
    case kUnsignedTwoBytes:
      return lhu(dest, addr);
    case kTwoBytes:
      return lh(dest, addr);
    case kUnsignedByte:
      return lbu(dest, addr);
    case kByte:
      return lb(dest, addr);
    default:
      UNREACHABLE();
  }
}
// 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 Assembler::LoadIndexedPayload(Register dest,
                                   Register base,
                                   int32_t payload_offset,
                                   Register index,
                                   ScaleFactor scale,
                                   OperandSize sz) {
  AddShifted(TMP, base, index, scale);
  LoadFromOffset(dest, TMP, payload_offset - kHeapObjectTag, sz);
}

void Assembler::LoadSFromOffset(FRegister dest, Register base, int32_t offset) {
  flw(dest, PrepareLargeOffset(base, offset));
}

void Assembler::LoadDFromOffset(FRegister dest, Register base, int32_t offset) {
  fld(dest, PrepareLargeOffset(base, offset));
}

void Assembler::LoadFromStack(Register dst, intptr_t depth) {
  LoadFromOffset(dst, SPREG, target::kWordSize * depth);
}
void Assembler::StoreToStack(Register src, intptr_t depth) {
  StoreToOffset(src, SPREG, target::kWordSize * depth);
}
void Assembler::CompareToStack(Register src, intptr_t depth) {
  CompareWithMemoryValue(src, Address(SPREG, target::kWordSize * depth));
}

void Assembler::Store(Register src, const Address& address, OperandSize sz) {
  Address addr = PrepareLargeOffset(address.base(), address.offset());
  switch (sz) {
#if XLEN == 64
    case kEightBytes:
      return sd(src, addr);
#endif
    case kUnsignedFourBytes:
    case kFourBytes:
      return sw(src, addr);
    case kUnsignedTwoBytes:
    case kTwoBytes:
      return sh(src, addr);
    case kUnsignedByte:
    case kByte:
      return sb(src, addr);
    default:
      UNREACHABLE();
  }
}

void Assembler::StoreSToOffset(FRegister src, Register base, int32_t offset) {
  fsw(src, PrepareLargeOffset(base, offset));
}

void Assembler::StoreDToOffset(FRegister src, Register base, int32_t offset) {
  fsd(src, PrepareLargeOffset(base, offset));
}

void Assembler::StoreBarrier(Register object,
                             Register value,
                             CanBeSmi can_value_be_smi,
                             Register scratch) {
  // x.slot = x. Barrier should have be removed at the IL level.
  ASSERT(object != value);
  ASSERT(object != scratch);
  ASSERT(value != scratch);
  ASSERT(object != RA);
  ASSERT(value != RA);
  ASSERT(scratch != RA);
  ASSERT(object != TMP2);
  ASSERT(value != TMP2);
  ASSERT(scratch != TMP2);
  ASSERT(scratch != kNoRegister);

  // In parallel, test whether
  //  - object is old and not remembered and value is new, or
  //  - object is old and value is old and not marked and concurrent marking is
  //    in progress
  // If so, call the WriteBarrier stub, which will either add object to the
  // store buffer (case 1) or add value to the marking stack (case 2).
  // See RestorePinnedRegisters for why this can be `ble`.
  // Compare UntaggedObject::StorePointer.
  Label done;
  if (can_value_be_smi == kValueCanBeSmi) {
    BranchIfSmi(value, &done, kNearJump);
  } else {
#if defined(DEBUG)
    Label passed_check;
    BranchIfNotSmi(value, &passed_check, kNearJump);
    Breakpoint();
    Bind(&passed_check);
#endif
  }
  lbu(scratch, FieldAddress(object, target::Object::tags_offset()));
  lbu(TMP2, FieldAddress(value, target::Object::tags_offset()));
  srli(scratch, scratch, target::UntaggedObject::kBarrierOverlapShift);
  and_(scratch, scratch, TMP2);
  ble(scratch, WRITE_BARRIER_STATE, &done, kNearJump);

  Register objectForCall = object;
  if (value != kWriteBarrierValueReg) {
    // Unlikely. Only non-graph intrinsics.
    // TODO(rmacnak): Shuffle registers in intrinsics.
    if (object != kWriteBarrierValueReg) {
      PushRegister(kWriteBarrierValueReg);
    } else {
      COMPILE_ASSERT(S3 != kWriteBarrierValueReg);
      COMPILE_ASSERT(S4 != kWriteBarrierValueReg);
      objectForCall = (value == S3) ? S4 : S3;
      PushRegisterPair(kWriteBarrierValueReg, objectForCall);
      mv(objectForCall, object);
    }
    mv(kWriteBarrierValueReg, value);
  }

  // Note this uses TMP as the link register, so RA remains preserved.
  generate_invoke_write_barrier_wrapper_(objectForCall);

  if (value != kWriteBarrierValueReg) {
    if (object != kWriteBarrierValueReg) {
      PopRegister(kWriteBarrierValueReg);
    } else {
      PopRegisterPair(kWriteBarrierValueReg, objectForCall);
    }
  }
  Bind(&done);
}

void Assembler::ArrayStoreBarrier(Register object,
                                  Register slot,
                                  Register value,
                                  CanBeSmi can_value_be_smi,
                                  Register scratch) {
  // TODO(riscv): Use RA2 to avoid spilling RA inline?
  const bool spill_lr = true;
  ASSERT(object != slot);
  ASSERT(object != value);
  ASSERT(object != scratch);
  ASSERT(slot != value);
  ASSERT(slot != scratch);
  ASSERT(value != scratch);
  ASSERT(object != RA);
  ASSERT(slot != RA);
  ASSERT(value != RA);
  ASSERT(scratch != RA);
  ASSERT(object != TMP2);
  ASSERT(slot != TMP2);
  ASSERT(value != TMP2);
  ASSERT(scratch != TMP2);
  ASSERT(scratch != kNoRegister);

  // In parallel, test whether
  //  - object is old and not remembered and value is new, or
  //  - object is old and value is old and not marked and concurrent marking is
  //    in progress
  // If so, call the WriteBarrier stub, which will either add object to the
  // store buffer (case 1) or add value to the marking stack (case 2).
  // See RestorePinnedRegisters for why this can be `ble`.
  // Compare UntaggedObject::StorePointer.
  Label done;
  if (can_value_be_smi == kValueCanBeSmi) {
    BranchIfSmi(value, &done, kNearJump);
  } else {
#if defined(DEBUG)
    Label passed_check;
    BranchIfNotSmi(value, &passed_check, kNearJump);
    Breakpoint();
    Bind(&passed_check);
#endif
  }
  lbu(scratch, FieldAddress(object, target::Object::tags_offset()));
  lbu(TMP2, FieldAddress(value, target::Object::tags_offset()));
  srli(scratch, scratch, target::UntaggedObject::kBarrierOverlapShift);
  and_(scratch, scratch, TMP2);
  ble(scratch, WRITE_BARRIER_STATE, &done, kNearJump);
  if (spill_lr) {
    PushRegister(RA);
  }
  if ((object != kWriteBarrierObjectReg) || (value != kWriteBarrierValueReg) ||
      (slot != kWriteBarrierSlotReg)) {
    // Spill and shuffle unimplemented. Currently StoreIntoArray is only used
    // from StoreIndexInstr, which gets these exact registers from the register
    // allocator.
    UNIMPLEMENTED();
  }
  generate_invoke_array_write_barrier_();
  if (spill_lr) {
    PopRegister(RA);
  }
  Bind(&done);
}

void Assembler::VerifyStoreNeedsNoWriteBarrier(Register object,
                                               Register value) {
  if (value == ZR) return;
  // We can't assert the incremental barrier is not needed here, only the
  // generational barrier. We sometimes omit the write barrier when 'value' is
  // a constant, but we don't eagerly mark 'value' and instead assume it is also
  // reachable via a constant pool, so it doesn't matter if it is not traced via
  // 'object'.
  Label done;
  BranchIfSmi(value, &done, kNearJump);
  lbu(TMP2, FieldAddress(value, target::Object::tags_offset()));
  andi(TMP2, TMP2, 1 << target::UntaggedObject::kNewOrEvacuationCandidateBit);
  beqz(TMP2, &done, kNearJump);
  lbu(TMP2, FieldAddress(object, target::Object::tags_offset()));
  andi(TMP2, TMP2, 1 << target::UntaggedObject::kOldAndNotRememberedBit);
  beqz(TMP2, &done, kNearJump);
  Stop("Write barrier is required");
  Bind(&done);
}

void Assembler::StoreObjectIntoObjectNoBarrier(Register object,
                                               const Address& dest,
                                               const Object& value,
                                               MemoryOrder memory_order,
                                               OperandSize size) {
  ASSERT(IsOriginalObject(value));
  DEBUG_ASSERT(IsNotTemporaryScopedHandle(value));
  // No store buffer update.
  Register value_reg;
  if (IsSameObject(compiler::NullObject(), value)) {
    value_reg = NULL_REG;
  } else if (target::IsSmi(value) && (target::ToRawSmi(value) == 0)) {
    value_reg = ZR;
  } else {
    ASSERT(object != TMP);
    LoadObject(TMP, value);
    value_reg = TMP;
  }
  if (memory_order == kRelease) {
    fence(HartEffects::kMemory, HartEffects::kWrite);
  }
  Store(value_reg, dest, size);
}

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

// Object pool, loading from pool, etc.
void Assembler::LoadPoolPointer(Register pp) {
  CheckCodePointer();
  lx(pp, FieldAddress(CODE_REG, target::Code::object_pool_offset()));

  // When in the PP register, the pool pointer is untagged. When we
  // push it on the stack with TagAndPushPP it is tagged again. PopAndUntagPP
  // then untags when restoring from the stack. This will make loading from the
  // object pool only one instruction for the first 4096 entries. Otherwise,
  // because the offset wouldn't be aligned, it would be only one instruction
  // for the first 64 entries.
  subi(pp, pp, kHeapObjectTag);
  set_constant_pool_allowed(pp == PP);
}

bool Assembler::CanLoadFromObjectPool(const Object& object) const {
  ASSERT(IsOriginalObject(object));
  if (!constant_pool_allowed()) {
    return false;
  }

  DEBUG_ASSERT(IsNotTemporaryScopedHandle(object));
  ASSERT(IsInOldSpace(object));
  return true;
}
void Assembler::LoadNativeEntry(
    Register dst,
    const ExternalLabel* label,
    ObjectPoolBuilderEntry::Patchability patchable) {
  const intptr_t index =
      object_pool_builder().FindNativeFunction(label, patchable);
  LoadWordFromPoolIndex(dst, index);
}
void Assembler::LoadIsolate(Register dst) {
  lx(dst, Address(THR, target::Thread::isolate_offset()));
}
void Assembler::LoadIsolateGroup(Register dst) {
  lx(dst, Address(THR, target::Thread::isolate_group_offset()));
}

void Assembler::LoadImmediate(Register reg, intx_t imm) {
#if XLEN > 32
  if (!Utils::IsInt(32, imm)) {
    if (Supports(RV_Zbs) && Utils::IsPowerOfTwo(imm)) {
      bseti(reg, ZR, Utils::ShiftForPowerOfTwo(imm));
      return;
    }

    int shift = Utils::CountTrailingZeros64(imm);
    if (IsITypeImm(imm >> shift)) {
      li(reg, imm >> shift);
      slli(reg, reg, shift);
      return;
    }
    if ((shift >= 12) && IsUTypeImm(imm >> (shift - 12))) {
      lui(reg, imm >> (shift - 12));
      slli(reg, reg, shift - 12);
      return;
    }

    if (constant_pool_allowed()) {
      intptr_t index = object_pool_builder().FindImmediate(imm);
      LoadWordFromPoolIndex(reg, index);
      return;
    }

    intx_t lo = ImmLo(imm);
    intx_t hi = imm - lo;
    shift = Utils::CountTrailingZeros64(hi);
    ASSERT(shift != 0);
    LoadImmediate(reg, hi >> shift);
    slli(reg, reg, shift);
    if (lo != 0) {
      addi(reg, reg, lo);
    }
    return;
  }
#endif

  intx_t lo = ImmLo(imm);
  intx_t hi = ImmHi(imm);
  if (hi == 0) {
    addi(reg, ZR, lo);
  } else {
    lui(reg, hi);
    if (lo != 0) {
#if XLEN == 32
      addi(reg, reg, lo);
#else
      addiw(reg, reg, lo);
#endif
    }
  }
}

void Assembler::LoadSImmediate(FRegister reg, float imms) {
  uint32_t imm = bit_cast<uint32_t, float>(imms);
  if (imm == 0) {
    fmvwx(reg, ZR);  // bit_cast uint32_t -> float
  } else {
    if (Supports(RV_Zfa)) {
      for (intptr_t i = 0; i < 32; i++) {
        if (kFlisConstants[i] == imm) {
          flis(reg, i);
          return;
        }
      }
      uint32_t nimm = imm ^ 0x80000000;
      for (intptr_t i = 0; i < 32; i++) {
        if (kFlisConstants[i] == nimm) {
          flis(reg, i);
          fnegs(reg, reg);
          return;
        }
      }
    }

    int32_t immi = imms;
    if (IsITypeImm(immi) &&
        imm == bit_cast<uint32_t, float>(static_cast<float>(immi))) {
      li(TMP2, immi);
      fcvtsw(reg, TMP2);  // static_cast int32_t -> float
      return;
    }

    ASSERT(constant_pool_allowed());
    intptr_t index = object_pool_builder().FindImmediate(imm);
    intptr_t offset = target::ObjectPool::element_offset(index);
    LoadSFromOffset(reg, PP, offset);
  }
}

void Assembler::LoadDImmediate(FRegister reg, double immd) {
  uint64_t imm = bit_cast<uint64_t, double>(immd);
  if (imm == 0) {
#if XLEN >= 64
    fmvdx(reg, ZR);  // bit_cast uint64_t -> double
#else
    fcvtdwu(reg, ZR);  // static_cast uint32_t -> double
#endif
  } else {
    if (Supports(RV_Zfa)) {
      for (intptr_t i = 0; i < 32; i++) {
        if (kFlidConstants[i] == imm) {
          flid(reg, i);
          return;
        }
      }
      uint64_t nimm = imm ^ 0x8000000000000000;
      for (intptr_t i = 0; i < 32; i++) {
        if (kFlidConstants[i] == nimm) {
          flid(reg, i);
          fnegd(reg, reg);
          return;
        }
      }
    }

    int32_t immi = immd;
    if (IsITypeImm(immi) &&
        imm == bit_cast<uint64_t, double>(static_cast<double>(immi))) {
      li(TMP2, immi);
      fcvtdw(reg, TMP2);  // static_cast int32_t -> double
      return;
    }

    ASSERT(constant_pool_allowed());
    intptr_t index = object_pool_builder().FindImmediate64(imm);
    intptr_t offset = target::ObjectPool::element_offset(index);
    LoadDFromOffset(reg, PP, offset);
  }
}

void Assembler::LoadQImmediate(FRegister reg, simd128_value_t immq) {
  UNREACHABLE();  // F registers cannot represent SIMD128.
}

// 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 Assembler::LoadWordFromPoolIndex(Register dst,
                                      intptr_t index,
                                      Register pp) {
  ASSERT((pp != PP) || constant_pool_allowed());
  ASSERT(dst != pp);
  const uint32_t offset = target::ObjectPool::element_offset(index);
  // PP is untagged.
  intx_t lo = ImmLo(offset);
  intx_t hi = ImmHi(offset);
  if (hi == 0) {
    lx(dst, Address(pp, lo));
  } else {
    lui(dst, hi);
    add(dst, dst, pp);
    lx(dst, Address(dst, lo));
  }
}

void Assembler::StoreWordToPoolIndex(Register src,
                                     intptr_t index,
                                     Register pp) {
  ASSERT((pp != PP) || constant_pool_allowed());
  ASSERT(src != pp);
  const uint32_t offset = target::ObjectPool::element_offset(index);
  // PP is untagged.
  intx_t lo = ImmLo(offset);
  intx_t hi = ImmHi(offset);
  if (hi == 0) {
    sx(src, Address(pp, lo));
  } else {
    lui(TMP, hi);
    add(TMP, TMP, pp);
    sx(src, Address(TMP, lo));
  }
}

void Assembler::CompareObject(Register reg, const Object& object) {
  ASSERT(IsOriginalObject(object));
  if (IsSameObject(compiler::NullObject(), object)) {
    CompareObjectRegisters(reg, NULL_REG);
  } else if (target::IsSmi(object)) {
    CompareImmediate(reg, target::ToRawSmi(object), kObjectBytes);
  } else {
    LoadObject(TMP2, object);
    CompareObjectRegisters(reg, TMP2);
  }
}

void Assembler::ExtractBitField(Register dst,
                                Register src,
                                intptr_t low_bit,
                                intptr_t width) {
  ASSERT((0 <= low_bit) && (low_bit + width <= XLEN));
  if (width == 1) {
    if (low_bit == 0) {
      andi(dst, src, 1);
      return;
    } else if (Supports(RV_Zbs)) {
      bexti(dst, src, low_bit);
      return;
    }
  }
  if (low_bit + width < XLEN) {
    slli(dst, src, XLEN - (low_bit + width));
  }
  srli(dst, dst, XLEN - width);
}

void Assembler::ExtractClassIdFromTags(Register result, Register tags) {
  ASSERT(target::UntaggedObject::kClassIdTagPos == 12);
  ASSERT(target::UntaggedObject::kClassIdTagSize == 20);
#if XLEN == 64
  srliw(result, tags, target::UntaggedObject::kClassIdTagPos);
#else
  srli(result, tags, target::UntaggedObject::kClassIdTagPos);
#endif
}

void Assembler::ExtractInstanceSizeFromTags(Register result, Register tags) {
  ASSERT(target::UntaggedObject::kSizeTagPos == 8);
  ASSERT(target::UntaggedObject::kSizeTagSize == 4);
  srli(result, tags, target::UntaggedObject::kSizeTagPos);
  andi(result, result, (1 << target::UntaggedObject::kSizeTagSize) - 1);
  slli(result, result, target::ObjectAlignment::kObjectAlignmentLog2);
}

void Assembler::LoadClassId(Register result, Register object) {
  ASSERT(target::UntaggedObject::kClassIdTagPos == 12);
  ASSERT(target::UntaggedObject::kClassIdTagSize == 20);
#if XLEN == 64
  lwu(result, FieldAddress(object, target::Object::tags_offset()));
#else
  lw(result, FieldAddress(object, target::Object::tags_offset()));
#endif
  srli(result, result, target::UntaggedObject::kClassIdTagPos);
}

void Assembler::LoadClassById(Register result, Register class_id) {
  ASSERT(result != class_id);

  const intptr_t table_offset =
      target::IsolateGroup::cached_class_table_table_offset();

  LoadIsolateGroup(result);
  LoadFromOffset(result, result, table_offset);
  AddShifted(result, result, class_id, target::kWordSizeLog2);
  lx(result, Address(result, 0));
}
void Assembler::CompareClassId(Register object,
                               intptr_t class_id,
                               Register scratch) {
  ASSERT(scratch != kNoRegister);
  LoadClassId(scratch, object);
  CompareImmediate(scratch, class_id);
}
// Note: input and output registers must be different.
void Assembler::LoadClassIdMayBeSmi(Register result, Register object) {
  ASSERT(result != object);
  ASSERT(result != TMP2);
  ASSERT(object != TMP2);
  li(result, kSmiCid);
  Label done;
  BranchIfSmi(object, &done, kNearJump);
  LoadClassId(result, object);
  Bind(&done);
}
void Assembler::LoadTaggedClassIdMayBeSmi(Register result, Register object) {
  LoadClassIdMayBeSmi(result, object);
  SmiTag(result);
}
void Assembler::EnsureHasClassIdInDEBUG(intptr_t cid,
                                        Register src,
                                        Register scratch,
                                        bool can_be_null) {
#if defined(DEBUG)
  Comment("Check that object in register has cid %" Pd "", cid);
  Label matches;
  LoadClassIdMayBeSmi(scratch, src);
  CompareImmediate(scratch, cid);
  BranchIf(EQUAL, &matches, Assembler::kNearJump);
  if (can_be_null) {
    CompareImmediate(scratch, kNullCid);
    BranchIf(EQUAL, &matches, Assembler::kNearJump);
  }
  trap();
  Bind(&matches);
#endif
}

void Assembler::EnterFrame(intptr_t frame_size) {
  // N.B. The ordering here is important. We must never write beyond SP or
  // it can be clobbered by a signal handler.
  subi(SP, SP, frame_size + 2 * target::kWordSize);
  sx(RA, Address(SP, frame_size + 1 * target::kWordSize));
  sx(FP, Address(SP, frame_size + 0 * target::kWordSize));
  addi(FP, SP, frame_size + 2 * target::kWordSize);
}
void Assembler::LeaveFrame() {
  // N.B. The ordering here is important. We must never read beyond SP or
  // it may have already been clobbered by a signal handler.
  subi(SP, FP, 2 * target::kWordSize);
  lx(FP, Address(SP, 0 * target::kWordSize));
  lx(RA, Address(SP, 1 * target::kWordSize));
  addi(SP, SP, 2 * target::kWordSize);
}

void Assembler::TransitionGeneratedToNative(Register destination,
                                            Register new_exit_frame,
                                            Register new_exit_through_ffi,
                                            bool enter_safepoint) {
  // Save exit frame information to enable stack walking.
  sx(new_exit_frame,
     Address(THR, target::Thread::top_exit_frame_info_offset()));

  sx(new_exit_through_ffi,
     Address(THR, target::Thread::exit_through_ffi_offset()));
  Register tmp = new_exit_through_ffi;

#if defined(DEBUG)
  ASSERT(S8 != TMP2);
  mv(S8, TMP2);  // BranchIf in VerifyInGenerated clobbers TMP2.
  VerifyInGenerated(tmp);
  mv(TMP2, S8);
#endif
  // Mark that the thread is executing native code.
  sx(destination, Address(THR, target::Thread::vm_tag_offset()));
  li(tmp, target::Thread::native_execution_state());
  sx(tmp, Address(THR, target::Thread::execution_state_offset()));

  if (enter_safepoint) {
    EnterFullSafepoint(tmp);
  }
}

void Assembler::TransitionNativeToGenerated(Register state,
                                            bool exit_safepoint,
                                            bool set_tag) {
  if (exit_safepoint) {
    ExitFullSafepoint(state);
  } else {
#if defined(DEBUG)
    // Ensure we've already left the safepoint.
    ASSERT(target::Thread::native_safepoint_state_acquired() != 0);
    li(state, target::Thread::native_safepoint_state_acquired());
    lx(RA, Address(THR, target::Thread::safepoint_state_offset()));
    and_(RA, RA, state);
    Label ok;
    beqz(RA, &ok, Assembler::kNearJump);
    Breakpoint();
    Bind(&ok);
#endif
  }

  VerifyNotInGenerated(state);
  // Mark that the thread is executing Dart code.
  if (set_tag) {
    li(state, target::Thread::vm_tag_dart_id());
    sx(state, Address(THR, target::Thread::vm_tag_offset()));
  }
  li(state, target::Thread::generated_execution_state());
  sx(state, Address(THR, target::Thread::execution_state_offset()));

  // Reset exit frame information in Isolate's mutator thread structure.
  sx(ZR, Address(THR, target::Thread::top_exit_frame_info_offset()));
  sx(ZR, Address(THR, target::Thread::exit_through_ffi_offset()));
}

void Assembler::VerifyInGenerated(Register scratch) {
#if defined(DEBUG)
  // Verify the thread is in generated.
  Comment("VerifyInGenerated");
  lx(scratch, Address(THR, target::Thread::execution_state_offset()));
  Label ok;
  CompareImmediate(scratch, target::Thread::generated_execution_state());
  BranchIf(EQUAL, &ok, Assembler::kNearJump);
  Breakpoint();
  Bind(&ok);
#endif
}

void Assembler::VerifyNotInGenerated(Register scratch) {
#if defined(DEBUG)
  // Verify the thread is in native or VM.
  Comment("VerifyNotInGenerated");
  lx(scratch, Address(THR, target::Thread::execution_state_offset()));
  CompareImmediate(scratch, target::Thread::generated_execution_state());
  Label ok;
  BranchIf(NOT_EQUAL, &ok, Assembler::kNearJump);
  Breakpoint();
  Bind(&ok);
#endif
}

void Assembler::EnterFullSafepoint(Register state) {
  // We generate the same number of instructions whether or not the slow-path is
  // forced. This simplifies GenerateJitCallbackTrampolines.
  // For TSAN, we always go to the runtime so TSAN is aware of the release
  // semantics of entering the safepoint.

  Register addr = RA;
  ASSERT(addr != state);

  Label slow_path, done, retry;
  if (FLAG_use_slow_path || FLAG_target_thread_sanitizer) {
    j(&slow_path, Assembler::kNearJump);
  }

  addi(addr, THR, target::Thread::safepoint_state_offset());
  Bind(&retry);
  lrx(state, Address(addr, 0));
  subi(state, state, target::Thread::native_safepoint_state_unacquired());
  bnez(state, &slow_path, Assembler::kNearJump);

  li(state, target::Thread::native_safepoint_state_acquired());
  scx(state, state, Address(addr, 0));
  beqz(state, &done, Assembler::kNearJump);  // 0 means sc was successful.

  if (!FLAG_use_slow_path && !FLAG_target_thread_sanitizer) {
    j(&retry, Assembler::kNearJump);
  }

  Bind(&slow_path);
  lx(addr, Address(THR, target::Thread::enter_safepoint_stub_offset()));
  lx(addr, FieldAddress(addr, target::Code::entry_point_offset()));
  jalr(addr);

  Bind(&done);
}

void Assembler::ExitFullSafepoint(Register state) {
  // We generate the same number of instructions whether or not the slow-path is
  // forced, for consistency with EnterFullSafepoint.
  // For TSAN, we always go to the runtime so TSAN is aware of the acquire
  // semantics of leaving the safepoint.
  Register addr = RA;
  ASSERT(addr != state);

  Label slow_path, done, retry;
  if (FLAG_use_slow_path || FLAG_target_thread_sanitizer) {
    j(&slow_path, Assembler::kNearJump);
  }

  addi(addr, THR, target::Thread::safepoint_state_offset());
  Bind(&retry);
  lrx(state, Address(addr, 0));
  subi(state, state, target::Thread::native_safepoint_state_acquired());
  bnez(state, &slow_path, Assembler::kNearJump);

  li(state, target::Thread::native_safepoint_state_unacquired());
  scx(state, state, Address(addr, 0));
  beqz(state, &done, Assembler::kNearJump);  // 0 means sc was successful.

  if (!FLAG_use_slow_path && !FLAG_target_thread_sanitizer) {
    j(&retry, Assembler::kNearJump);
  }

  Bind(&slow_path);
  lx(addr, Address(THR, target::Thread::exit_safepoint_stub_offset()));
  lx(addr, FieldAddress(addr, target::Code::entry_point_offset()));
  jalr(addr);

  Bind(&done);
}

void Assembler::CheckFpSpDist(intptr_t fp_sp_dist) {
  ASSERT(fp_sp_dist <= 0);
#if defined(DEBUG)
  Label ok;
  Comment("CheckFpSpDist");
  sub(TMP, SP, FP);
  CompareImmediate(TMP, fp_sp_dist);
  BranchIf(EQ, &ok, compiler::Assembler::kNearJump);
  ebreak();
  Bind(&ok);
#endif
}

void Assembler::CheckCodePointer() {
#ifdef DEBUG
  if (!FLAG_check_code_pointer) {
    return;
  }
  Comment("CheckCodePointer");
  Label cid_ok, instructions_ok;
  CompareClassId(CODE_REG, kCodeCid, TMP);
  BranchIf(EQ, &cid_ok, kNearJump);
  ebreak();
  Bind(&cid_ok);

  const intptr_t entry_offset =
      CodeSize() + target::Instructions::HeaderSize() - kHeapObjectTag;
  intx_t imm = -entry_offset;
  intx_t lo = ImmLo(imm);
  intx_t hi = ImmHi(imm);
  auipc(TMP, hi);
  addi(TMP, TMP, lo);
  lx(TMP2, FieldAddress(CODE_REG, target::Code::instructions_offset()));
  beq(TMP, TMP2, &instructions_ok, kNearJump);
  ebreak();
  Bind(&instructions_ok);
#endif
}

void Assembler::RestoreCodePointer() {
  lx(CODE_REG,
     Address(FP, target::frame_layout.code_from_fp * target::kWordSize));
  CheckCodePointer();
}

void Assembler::RestorePoolPointer() {
  if (FLAG_precompiled_mode) {
    lx(PP, Address(THR, target::Thread::global_object_pool_offset()));
  } else {
    lx(PP, Address(FP, target::frame_layout.code_from_fp * target::kWordSize));
    lx(PP, FieldAddress(PP, target::Code::object_pool_offset()));
  }
  subi(PP, PP, kHeapObjectTag);  // Pool in PP is untagged!
}

void Assembler::RestorePinnedRegisters() {
  lx(WRITE_BARRIER_STATE,
     Address(THR, target::Thread::write_barrier_mask_offset()));
  lx(NULL_REG, Address(THR, target::Thread::object_null_offset()));

  // Our write barrier usually uses mask-and-test,
  //   01b6f6b3  and tmp, tmp, mask
  //       c689  beqz tmp, +10
  // but on RISC-V compare-and-branch is shorter,
  //   00ddd663  ble tmp, wbs, +12
  //
  // TMP bit 4+ = 0
  // TMP bit 3  = object is old-and-not-remembered AND value is new (genr bit)
  // TMP bit 2  = object is old AND value is old-and-not-marked     (incr bit)
  // TMP bit 1  = garbage
  // TMP bit 0  = garbage
  //
  // Thread::wbm | WRITE_BARRIER_STATE | TMP/combined headers | result
  // generational only
  // 0b1000        0b0111                0b11xx                 impossible
  //                                     0b10xx                 call stub
  //                                     0b01xx                 skip
  //                                     0b00xx                 skip
  // generational and incremental
  // 0b1100        0b0011                0b11xx                 impossible
  //                                     0b10xx                 call stub
  //                                     0b01xx                 call stub
  //                                     0b00xx                 skip
  xori(WRITE_BARRIER_STATE, WRITE_BARRIER_STATE,
       (target::UntaggedObject::kGenerationalBarrierMask << 1) - 1);

  // Generational bit must be higher than incremental bit, with no other bits
  // between.
  ASSERT(target::UntaggedObject::kGenerationalBarrierMask ==
         (target::UntaggedObject::kIncrementalBarrierMask << 1));
  // Other header bits must be lower.
  ASSERT(target::UntaggedObject::kIncrementalBarrierMask >
         target::UntaggedObject::kCanonicalBit);
  ASSERT(target::UntaggedObject::kIncrementalBarrierMask >
         target::UntaggedObject::kCardRememberedBit);
}

void Assembler::SetupGlobalPoolAndDispatchTable() {
  ASSERT(FLAG_precompiled_mode);
  lx(PP, Address(THR, target::Thread::global_object_pool_offset()));
  subi(PP, PP, kHeapObjectTag);  // Pool in PP is untagged!
  lx(DISPATCH_TABLE_REG,
     Address(THR, target::Thread::dispatch_table_array_offset()));
}

void Assembler::EnterDartFrame(intptr_t frame_size, Register new_pp) {
  ASSERT(!constant_pool_allowed());

  if (!IsITypeImm(frame_size + 4 * target::kWordSize)) {
    EnterDartFrame(0, new_pp);
    AddImmediate(SP, SP, -frame_size);
    return;
  }

  // N.B. The ordering here is important. We must never write beyond SP or
  // it can be clobbered by a signal handler.
  if (FLAG_precompiled_mode) {
    subi(SP, SP, frame_size + 2 * target::kWordSize);
    sx(RA, Address(SP, frame_size + 1 * target::kWordSize));
    sx(FP, Address(SP, frame_size + 0 * target::kWordSize));
    addi(FP, SP, frame_size + 2 * target::kWordSize);
  } else {
    subi(SP, SP, frame_size + 4 * target::kWordSize);
    sx(RA, Address(SP, frame_size + 3 * target::kWordSize));
    sx(FP, Address(SP, frame_size + 2 * target::kWordSize));
    sx(CODE_REG, Address(SP, frame_size + 1 * target::kWordSize));
    addi(PP, PP, kHeapObjectTag);
    sx(PP, Address(SP, frame_size + 0 * target::kWordSize));
    addi(FP, SP, frame_size + 4 * target::kWordSize);
    if (new_pp == kNoRegister) {
      LoadPoolPointer();
    } else {
      mv(PP, new_pp);
    }
  }
  set_constant_pool_allowed(true);
}

// On entry to a function compiled for OSR, the caller's frame pointer, the
// stack locals, and any copied parameters are already in place.  The frame
// pointer is already set up.  The PC marker is not correct for the
// optimized function and there may be extra space for spill slots to
// allocate. We must also set up the pool pointer for the function.
void Assembler::EnterOsrFrame(intptr_t extra_size, Register new_pp) {
  ASSERT(!constant_pool_allowed());
  Comment("EnterOsrFrame");
  RestoreCodePointer();
  LoadPoolPointer();

  if (extra_size > 0) {
    AddImmediate(SP, -extra_size);
  }
}

void Assembler::LeaveDartFrame() {
  // N.B. The ordering here is important. We must never read beyond SP or
  // it may have already been clobbered by a signal handler.
  if (!FLAG_precompiled_mode) {
    lx(PP, Address(FP, target::frame_layout.saved_caller_pp_from_fp *
                           target::kWordSize));
    subi(PP, PP, kHeapObjectTag);
  }
  set_constant_pool_allowed(false);
  subi(SP, FP, 2 * target::kWordSize);
  // Update RA first so the profiler can identify the frame as the entry frame
  // after FP is updated but before the return instruction.
  lx(RA, Address(SP, 1 * target::kWordSize));
  lx(FP, Address(SP, 0 * target::kWordSize));
  addi(SP, SP, 2 * target::kWordSize);
}

void Assembler::LeaveDartFrame(intptr_t fp_sp_dist) {
  intptr_t pp_offset =
      target::frame_layout.saved_caller_pp_from_fp * target::kWordSize -
      fp_sp_dist;
  intptr_t fp_offset =
      target::frame_layout.saved_caller_fp_from_fp * target::kWordSize -
      fp_sp_dist;
  intptr_t ra_offset =
      target::frame_layout.saved_caller_pc_from_fp * target::kWordSize -
      fp_sp_dist;
  if (!IsITypeImm(pp_offset) || !IsITypeImm(fp_offset) ||
      !IsITypeImm(ra_offset)) {
    // Shorter to update SP twice than generate large immediates.
    LeaveDartFrame();
    return;
  }

  if (!FLAG_precompiled_mode) {
    lx(PP, Address(SP, pp_offset));
    subi(PP, PP, kHeapObjectTag);
  }
  set_constant_pool_allowed(false);
  // Update RA first so the profiler can identify the frame as the entry frame
  // after FP is updated but before the return instruction.
  lx(RA, Address(SP, ra_offset));
  lx(FP, Address(SP, fp_offset));
  addi(SP, SP, -fp_sp_dist);
}

void Assembler::CallRuntime(const RuntimeEntry& entry,
                            intptr_t argument_count,
                            bool tsan_enter_exit) {
  ASSERT(!entry.is_leaf());
  // Argument count is not checked here, but in the runtime entry for a more
  // informative error message.
  if (FLAG_target_thread_sanitizer && tsan_enter_exit) {
    TsanFuncEntry(/*preserve_registers=*/false);
  }
  lx(T5, compiler::Address(THR, entry.OffsetFromThread()));
  li(T4, argument_count);
  Comment("Runtime call: %s", entry.name());
  Call(Address(THR, target::Thread::call_to_runtime_entry_point_offset()));
  if (FLAG_target_thread_sanitizer && tsan_enter_exit) {
    TsanFuncExit(/*preserve_registers=*/false);
  }
}

static const RegisterSet kRuntimeCallSavedRegisters(kDartVolatileCpuRegs,
                                                    kDartVolatileFpuRegs);

#define __ assembler_->

LeafRuntimeScope::LeafRuntimeScope(Assembler* assembler,
                                   intptr_t frame_size,
                                   bool preserve_registers)
    : assembler_(assembler), preserve_registers_(preserve_registers) {
  // N.B. The ordering here is important. We must never write beyond SP or
  // it can be clobbered by a signal handler.
  __ subi(SP, SP, 4 * target::kWordSize);
  __ sx(RA, Address(SP, 3 * target::kWordSize));
  __ sx(FP, Address(SP, 2 * target::kWordSize));
  __ sx(CODE_REG, Address(SP, 1 * target::kWordSize));
  __ sx(PP, Address(SP, 0 * target::kWordSize));
  __ addi(FP, SP, 4 * target::kWordSize);

  if (preserve_registers) {
    __ PushRegisters(kRuntimeCallSavedRegisters);
  } else {
    // Or no reason to save above.
    COMPILE_ASSERT(!IsAbiPreservedRegister(CODE_REG));
    COMPILE_ASSERT(!IsAbiPreservedRegister(PP));
    // Or would need to save above.
    COMPILE_ASSERT(IsCalleeSavedRegister(THR));
    COMPILE_ASSERT(IsCalleeSavedRegister(NULL_REG));
    COMPILE_ASSERT(IsCalleeSavedRegister(WRITE_BARRIER_STATE));
    COMPILE_ASSERT(IsCalleeSavedRegister(DISPATCH_TABLE_REG));
  }

  __ ReserveAlignedFrameSpace(frame_size);
}

void LeafRuntimeScope::Call(const RuntimeEntry& entry,
                            intptr_t argument_count) {
  ASSERT(argument_count == entry.argument_count());
  __ Load(TMP2, compiler::Address(THR, entry.OffsetFromThread()));
  __ sx(TMP2, compiler::Address(THR, target::Thread::vm_tag_offset()));
  __ Comment("Leaf runtime call: %s", entry.name());
  __ jalr(TMP2);
  __ LoadImmediate(TMP2, VMTag::kDartTagId);
  __ sx(TMP2, compiler::Address(THR, target::Thread::vm_tag_offset()));
}

LeafRuntimeScope::~LeafRuntimeScope() {
  if (preserve_registers_) {
    __ subi(SP, FP,
            kRuntimeCallSavedRegisters.SpillSize() + 4 * target::kWordSize);
    __ PopRegisters(kRuntimeCallSavedRegisters);
  }

  __ subi(SP, FP, 4 * target::kWordSize);
  __ lx(PP, Address(SP, 0 * target::kWordSize));
  __ lx(CODE_REG, Address(SP, 1 * target::kWordSize));
  __ lx(FP, Address(SP, 2 * target::kWordSize));
  __ lx(RA, Address(SP, 3 * target::kWordSize));
  __ addi(SP, SP, 4 * target::kWordSize);
}

#undef __

void Assembler::EnterCFrame(intptr_t frame_space) {
  // Already saved.
  COMPILE_ASSERT(IsCalleeSavedRegister(THR));
  COMPILE_ASSERT(IsCalleeSavedRegister(NULL_REG));
  COMPILE_ASSERT(IsCalleeSavedRegister(WRITE_BARRIER_STATE));
  COMPILE_ASSERT(IsCalleeSavedRegister(DISPATCH_TABLE_REG));
  // Need to save.
  COMPILE_ASSERT(!IsCalleeSavedRegister(PP));

  // N.B. The ordering here is important. We must never read beyond SP or
  // it may have already been clobbered by a signal handler.
  subi(SP, SP, frame_space + 3 * target::kWordSize);
  sx(RA, Address(SP, frame_space + 2 * target::kWordSize));
  sx(FP, Address(SP, frame_space + 1 * target::kWordSize));
  sx(PP, Address(SP, frame_space + 0 * target::kWordSize));
  addi(FP, SP, frame_space + 3 * target::kWordSize);
  const intptr_t kAbiStackAlignment = 16;  // For both 32 and 64 bit.
  andi(SP, SP, ~(kAbiStackAlignment - 1));
}

void Assembler::LeaveCFrame() {
  // N.B. The ordering here is important. We must never read beyond SP or
  // it may have already been clobbered by a signal handler.
  subi(SP, FP, 3 * target::kWordSize);
  lx(PP, Address(SP, 0 * target::kWordSize));
  lx(FP, Address(SP, 1 * target::kWordSize));
  lx(RA, Address(SP, 2 * target::kWordSize));
  addi(SP, SP, 3 * target::kWordSize);
}

// A0: Receiver
// S5: ICData entry array
// PP: Caller's PP (preserved)
void Assembler::MonomorphicCheckedEntryJIT() {
  has_monomorphic_entry_ = true;
  const intptr_t saved_far_branch_level = far_branch_level();
  set_far_branch_level(0);
  const intptr_t start = CodeSize();

  Label immediate, miss;
  Bind(&miss);
  lx(TMP, Address(THR, target::Thread::switchable_call_miss_entry_offset()));
  jr(TMP);

  Comment("MonomorphicCheckedEntry");
  ASSERT_EQUAL(CodeSize() - start,
               target::Instructions::kMonomorphicEntryOffsetJIT);

  Register entries_reg = IC_DATA_REG;  // Contains ICData::entries().
  const intptr_t cid_offset = target::Array::element_offset(0);
  const intptr_t count_offset = target::Array::element_offset(1);
  ASSERT(A1 != PP);
  ASSERT(A1 != entries_reg);
  ASSERT(A1 != CODE_REG);

  lx(TMP, FieldAddress(entries_reg, cid_offset));
  LoadTaggedClassIdMayBeSmi(A1, A0);
  bne(TMP, A1, &miss, kNearJump);

  lx(TMP, FieldAddress(entries_reg, count_offset));
  addi(TMP, TMP, target::ToRawSmi(1));
  sx(TMP, FieldAddress(entries_reg, count_offset));

  li(ARGS_DESC_REG, 0);  // GC-safe for OptimizeInvokedFunction

  // Fall through to unchecked entry.
  ASSERT_EQUAL(CodeSize() - start,
               target::Instructions::kPolymorphicEntryOffsetJIT);

  set_far_branch_level(saved_far_branch_level);
}

// A0 receiver, S5 guarded cid as Smi.
// Preserve S4 (ARGS_DESC_REG), not required today, but maybe later.
// PP: Caller's PP (preserved)
void Assembler::MonomorphicCheckedEntryAOT() {
  has_monomorphic_entry_ = true;
  intptr_t saved_far_branch_level = far_branch_level();
  set_far_branch_level(0);

  const intptr_t start = CodeSize();

  Label immediate, miss;
  Bind(&miss);
  lx(TMP, Address(THR, target::Thread::switchable_call_miss_entry_offset()));
  jr(TMP);

  Comment("MonomorphicCheckedEntry");
  ASSERT_EQUAL(CodeSize() - start,
               target::Instructions::kMonomorphicEntryOffsetAOT);
  LoadClassId(TMP, A0);
  SmiTag(TMP);
  bne(S5, TMP, &miss, kNearJump);

  // Fall through to unchecked entry.
  ASSERT_EQUAL(CodeSize() - start,
               target::Instructions::kPolymorphicEntryOffsetAOT);

  set_far_branch_level(saved_far_branch_level);
}

void Assembler::BranchOnMonomorphicCheckedEntryJIT(Label* label) {
  has_monomorphic_entry_ = true;
  while (CodeSize() < target::Instructions::kMonomorphicEntryOffsetJIT) {
    ebreak();
  }
  j(label);
  while (CodeSize() < target::Instructions::kPolymorphicEntryOffsetJIT) {
    ebreak();
  }
}

void Assembler::CombineHashes(Register hash, Register other) {
#if XLEN >= 64
  // hash += other_hash
  addw(hash, hash, other);
  // hash += hash << 10
  slliw(other, hash, 10);
  addw(hash, hash, other);
  // hash ^= hash >> 6
  srliw(other, hash, 6);
  xor_(hash, hash, other);
#else
  // hash += other_hash
  add(hash, hash, other);
  // hash += hash << 10
  slli(other, hash, 10);
  add(hash, hash, other);
  // hash ^= hash >> 6
  srli(other, hash, 6);
  xor_(hash, hash, other);
#endif
}

void Assembler::FinalizeHashForSize(intptr_t bit_size,
                                    Register hash,
                                    Register scratch) {
  ASSERT(bit_size > 0);  // Can't avoid returning 0 if there are no hash bits!
  // While any 32-bit hash value fits in X bits, where X > 32, the caller may
  // reasonably expect that the returned values fill the entire bit space.
  ASSERT(bit_size <= kBitsPerInt32);
  ASSERT(scratch != kNoRegister);
#if XLEN >= 64
  // hash += hash << 3;
  slliw(scratch, hash, 3);
  addw(hash, hash, scratch);
  // hash ^= hash >> 11;  // Logical shift, unsigned hash.
  srliw(scratch, hash, 11);
  xor_(hash, hash, scratch);
  // hash += hash << 15;
  slliw(scratch, hash, 15);
  addw(hash, hash, scratch);
#else
  // hash += hash << 3;
  slli(scratch, hash, 3);
  add(hash, hash, scratch);
  // hash ^= hash >> 11;  // Logical shift, unsigned hash.
  srli(scratch, hash, 11);
  xor_(hash, hash, scratch);
  // hash += hash << 15;
  slli(scratch, hash, 15);
  add(hash, hash, scratch);
#endif
  // Size to fit.
  if (bit_size < kBitsPerInt32) {
    AndImmediate(hash, hash, Utils::NBitMask(bit_size));
  }
  // return (hash == 0) ? 1 : hash;
  seqz(scratch, hash);
  add(hash, hash, scratch);
}

#ifndef PRODUCT
void Assembler::MaybeTraceAllocation(Register cid,
                                     Label* trace,
                                     Register temp_reg,
                                     JumpDistance distance) {
  LoadIsolateGroup(temp_reg);
  lx(temp_reg, Address(temp_reg, target::IsolateGroup::class_table_offset()));
  lx(temp_reg,
     Address(temp_reg,
             target::ClassTable::allocation_tracing_state_table_offset()));
  add(temp_reg, temp_reg, cid);
  LoadFromOffset(temp_reg, temp_reg,
                 target::ClassTable::AllocationTracingStateSlotOffsetFor(0),
                 kUnsignedByte);
  bnez(temp_reg, trace);
}

void Assembler::MaybeTraceAllocation(intptr_t cid,
                                     Label* trace,
                                     Register temp_reg,
                                     JumpDistance distance) {
  ASSERT(cid > 0);
  LoadIsolateGroup(temp_reg);
  lx(temp_reg, Address(temp_reg, target::IsolateGroup::class_table_offset()));
  lx(temp_reg,
     Address(temp_reg,
             target::ClassTable::allocation_tracing_state_table_offset()));
  LoadFromOffset(temp_reg, temp_reg,
                 target::ClassTable::AllocationTracingStateSlotOffsetFor(cid),
                 kUnsignedByte);
  bnez(temp_reg, trace);
}
#endif  // !PRODUCT

void Assembler::TryAllocateObject(intptr_t cid,
                                  intptr_t instance_size,
                                  Label* failure,
                                  JumpDistance distance,
                                  Register instance_reg,
                                  Register temp_reg) {
  ASSERT(failure != nullptr);
  ASSERT(instance_size != 0);
  ASSERT(instance_reg != temp_reg);
  ASSERT(temp_reg != kNoRegister);
  ASSERT(Utils::IsAligned(instance_size,
                          target::ObjectAlignment::kObjectAlignment));
  if (FLAG_inline_alloc &&
      target::Heap::IsAllocatableInNewSpace(instance_size)) {
    // If this allocation is traced, program will jump to failure path
    // (i.e. the allocation stub) which will allocate the object and trace the
    // allocation call site.
    NOT_IN_PRODUCT(MaybeTraceAllocation(cid, failure, temp_reg));

    lx(instance_reg, Address(THR, target::Thread::top_offset()));
    lx(temp_reg, Address(THR, target::Thread::end_offset()));
    // instance_reg: current top (next object start).
    // temp_reg: heap end

    // TODO(koda): Protect against unsigned overflow here.
    AddImmediate(instance_reg, instance_size);
    // instance_reg: potential top (next object start).
    // fail if heap end unsigned less than or equal to new heap top.
    bleu(temp_reg, instance_reg, failure, distance);
    CheckAllocationCanary(instance_reg, temp_reg);

    // Successfully allocated the object, now update temp to point to
    // next object start and store the class in the class field of object.
    sx(instance_reg, Address(THR, target::Thread::top_offset()));
    // Move instance_reg back to the start of the object and tag it.
    AddImmediate(instance_reg, -instance_size + kHeapObjectTag);

    const uword tags = target::MakeTagWordForNewSpaceObject(cid, instance_size);
    LoadImmediate(temp_reg, tags);
    InitializeHeader(temp_reg, instance_reg);
  } else {
    j(failure, distance);
  }
}

void Assembler::TryAllocateArray(intptr_t cid,
                                 intptr_t instance_size,
                                 Label* failure,
                                 Register instance,
                                 Register end_address,
                                 Register temp1,
                                 Register temp2) {
  if (FLAG_inline_alloc &&
      target::Heap::IsAllocatableInNewSpace(instance_size)) {
    // If this allocation is traced, program will jump to failure path
    // (i.e. the allocation stub) which will allocate the object and trace the
    // allocation call site.
    NOT_IN_PRODUCT(MaybeTraceAllocation(cid, failure, temp1));
    // Potential new object start.
    lx(instance, Address(THR, target::Thread::top_offset()));
    AddImmediate(end_address, instance, instance_size);
    bltu(end_address, instance, failure);  // Fail on unsigned overflow.

    // Check if the allocation fits into the remaining space.
    // instance: potential new object start.
    // end_address: potential next object start.
    lx(temp2, Address(THR, target::Thread::end_offset()));
    bgeu(end_address, temp2, failure);
    CheckAllocationCanary(instance, temp2);

    // Successfully allocated the object(s), now update top to point to
    // next object start and initialize the object.
    sx(end_address, Address(THR, target::Thread::top_offset()));
    addi(instance, instance, kHeapObjectTag);
    NOT_IN_PRODUCT(LoadImmediate(temp2, instance_size));

    // Initialize the tags.
    // instance: new object start as a tagged pointer.
    const uword tags = target::MakeTagWordForNewSpaceObject(cid, instance_size);
    LoadImmediate(temp2, tags);
    InitializeHeader(temp2, instance);
  } else {
    j(failure);
  }
}

void Assembler::CopyMemoryWords(Register src,
                                Register dst,
                                Register size,
                                Register temp) {
  Label loop, done;
  beqz(size, &done, kNearJump);
  Bind(&loop);
  lx(temp, Address(src));
  addi(src, src, target::kWordSize);
  sx(temp, Address(dst));
  addi(dst, dst, target::kWordSize);
  subi(size, size, target::kWordSize);
  bnez(size, &loop, kNearJump);
  Bind(&done);
}

void Assembler::GenerateUnRelocatedPcRelativeCall(intptr_t offset_into_target) {
  // JAL only has a +/- 1MB range. AUIPC+JALR has a +/- 2GB range.
  intx_t lo = ImmLo(offset_into_target);
  intx_t hi = ImmHi(offset_into_target);
  auipc(RA, hi);
  jalr_fixed(RA, RA, lo);
}

void Assembler::GenerateUnRelocatedPcRelativeTailCall(
    intptr_t offset_into_target) {
  // J only has a +/- 1MB range. AUIPC+JR has a +/- 2GB range.
  intx_t lo = ImmLo(offset_into_target);
  intx_t hi = ImmHi(offset_into_target);
  auipc(TMP, hi);
  jalr_fixed(ZR, TMP, lo);
}

bool Assembler::AddressCanHoldConstantIndex(const Object& constant,
                                            bool is_external,
                                            intptr_t cid,
                                            intptr_t index_scale) {
  if (!IsSafeSmi(constant)) return false;
  const int64_t index = target::SmiValue(constant);
  const int64_t offset = index * index_scale + HeapDataOffset(is_external, cid);
#if XLEN >= 64
  return Utils::IsInt(32, offset);
#else
  return Utils::IsInt(32, offset) && Utils::IsInt(32, offset + 4);
#endif
}

Address Assembler::ElementAddressForIntIndex(bool is_external,
                                             intptr_t cid,
                                             intptr_t index_scale,
                                             Register array,
                                             intptr_t index) const {
  const int64_t offset = index * index_scale + HeapDataOffset(is_external, cid);
  ASSERT(Utils::IsInt(32, offset));
  return Address(array, static_cast<int32_t>(offset));
}
void Assembler::ComputeElementAddressForIntIndex(Register address,
                                                 bool is_external,
                                                 intptr_t cid,
                                                 intptr_t index_scale,
                                                 Register array,
                                                 intptr_t index) {
  const int64_t offset = index * index_scale + HeapDataOffset(is_external, cid);
  AddImmediate(address, array, offset);
}

Address Assembler::ElementAddressForRegIndex(bool is_external,
                                             intptr_t cid,
                                             intptr_t index_scale,
                                             bool index_unboxed,
                                             Register array,
                                             Register index,
                                             Register temp) {
  // If unboxed, index is expected smi-tagged, (i.e, LSL 1) for all arrays.
  const intptr_t boxing_shift = index_unboxed ? 0 : -kSmiTagShift;
  const intptr_t shift = Utils::ShiftForPowerOfTwo(index_scale) + boxing_shift;
  const int32_t offset = HeapDataOffset(is_external, cid);
  ASSERT(array != temp);
  ASSERT(index != temp);
  AddShifted(temp, array, index, shift);
  return Address(temp, offset);
}

void Assembler::ComputeElementAddressForRegIndex(Register address,
                                                 bool is_external,
                                                 intptr_t cid,
                                                 intptr_t index_scale,
                                                 bool index_unboxed,
                                                 Register array,
                                                 Register index) {
  // If unboxed, index is expected smi-tagged, (i.e, LSL 1) for all arrays.
  const intptr_t boxing_shift = index_unboxed ? 0 : -kSmiTagShift;
  const intptr_t shift = Utils::ShiftForPowerOfTwo(index_scale) + boxing_shift;
  const int32_t offset = HeapDataOffset(is_external, cid);
  ASSERT(array != address);
  ASSERT(index != address);
  AddShifted(address, array, index, shift);
  if (offset != 0) {
    AddImmediate(address, address, offset);
  }
}

void Assembler::LoadStaticFieldAddress(Register address,
                                       Register field,
                                       Register scratch,
                                       bool is_shared) {
  LoadCompressedSmiFieldFromOffset(
      scratch, field, target::Field::host_offset_or_field_id_offset());
  const intptr_t field_table_offset =
      is_shared ? compiler::target::Thread::shared_field_table_values_offset()
                : compiler::target::Thread::field_table_values_offset();
  LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset));
  slli(scratch, scratch, target::kWordSizeLog2 - kSmiTagShift);
  add(address, address, scratch);
}

void Assembler::LoadFieldAddressForRegOffset(Register address,
                                             Register instance,
                                             Register offset_in_words_as_smi) {
  AddShifted(address, instance, offset_in_words_as_smi,
             target::kWordSizeLog2 - kSmiTagShift);
  addi(address, address, -kHeapObjectTag);
}

// Note: the function never clobbers TMP, TMP2 scratch registers.
void Assembler::LoadObjectHelper(
    Register dst,
    const Object& object,
    bool is_unique,
    ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior) {
  ASSERT(IsOriginalObject(object));
  // `is_unique == true` effectively means object has to be patchable.
  // (even if the object is null)
  if (!is_unique) {
    if (IsSameObject(compiler::NullObject(), object)) {
      mv(dst, NULL_REG);
      return;
    }
    if (IsSameObject(CastHandle<Object>(compiler::TrueObject()), object)) {
      addi(dst, NULL_REG, kTrueOffsetFromNull);
      return;
    }
    if (IsSameObject(CastHandle<Object>(compiler::FalseObject()), object)) {
      addi(dst, NULL_REG, kFalseOffsetFromNull);
      return;
    }
    word offset = 0;
    if (target::CanLoadFromThread(object, &offset)) {
      lx(dst, Address(THR, offset));
      return;
    }
    if (target::IsSmi(object)) {
      LoadImmediate(dst, target::ToRawSmi(object));
      return;
    }
  }
  RELEASE_ASSERT(CanLoadFromObjectPool(object));
  const intptr_t index =
      is_unique
          ? object_pool_builder().AddObject(
                object, ObjectPoolBuilderEntry::kPatchable, snapshot_behavior)
          : object_pool_builder().FindObject(
                object, ObjectPoolBuilderEntry::kNotPatchable,
                snapshot_behavior);
  LoadWordFromPoolIndex(dst, index);
}

void Assembler::AddImmediateBranchOverflow(Register rd,
                                           Register rs1,
                                           intx_t imm,
                                           Label* overflow) {
  ASSERT(rd != TMP2);
  if (rd == rs1) {
    mv(TMP2, rs1);
    AddImmediate(rd, rs1, imm);
    if (imm > 0) {
      blt(rd, TMP2, overflow);
    } else if (imm < 0) {
      bgt(rd, TMP2, overflow);
    }
  } else {
    AddImmediate(rd, rs1, imm);
    if (imm > 0) {
      blt(rd, rs1, overflow);
    } else if (imm < 0) {
      bgt(rd, rs1, overflow);
    }
  }
}
void Assembler::SubtractImmediateBranchOverflow(Register rd,
                                                Register rs1,
                                                intx_t imm,
                                                Label* overflow) {
  // TODO(riscv): Incorrect for MIN_INTX_T!
  AddImmediateBranchOverflow(rd, rs1, -imm, overflow);
}
void Assembler::MultiplyImmediateBranchOverflow(Register rd,
                                                Register rs1,
                                                intx_t imm,
                                                Label* overflow) {
  ASSERT(rd != TMP);
  ASSERT(rd != TMP2);
  ASSERT(rs1 != TMP);
  ASSERT(rs1 != TMP2);

  LoadImmediate(TMP2, imm);
  // Macro-op fusion: when both products are needed, the recommended sequence
  // is mulh first.
  mulh(TMP, rs1, TMP2);
  mul(rd, rs1, TMP2);
  srai(TMP2, rd, XLEN - 1);
  bne(TMP, TMP2, overflow);
}
void Assembler::AddBranchOverflow(Register rd,
                                  Register rs1,
                                  Register rs2,
                                  Label* overflow) {
  ASSERT(rd != TMP);
  ASSERT(rd != TMP2);
  ASSERT(rs1 != TMP);
  ASSERT(rs1 != TMP2);
  ASSERT(rs2 != TMP);
  ASSERT(rs2 != TMP2);

  if ((rd == rs1) && (rd == rs2)) {
    ASSERT(rs1 == rs2);
    mv(TMP, rs1);
    add(rd, rs1, rs2);   // rs1, rs2 destroyed
    xor_(TMP, TMP, rd);  // TMP negative if sign changed
    bltz(TMP, overflow);
  } else if (rs1 == rs2) {
    ASSERT(rd != rs1);
    ASSERT(rd != rs2);
    add(rd, rs1, rs2);
    xor_(TMP, rd, rs1);  // TMP negative if sign changed
    bltz(TMP, overflow);
  } else if (rd == rs1) {
    ASSERT(rs1 != rs2);
    slti(TMP, rs1, 0);
    add(rd, rs1, rs2);  // rs1 destroyed
    slt(TMP2, rd, rs2);
    bne(TMP, TMP2, overflow);
  } else if (rd == rs2) {
    ASSERT(rs1 != rs2);
    slti(TMP, rs2, 0);
    add(rd, rs1, rs2);  // rs2 destroyed
    slt(TMP2, rd, rs1);
    bne(TMP, TMP2, overflow);
  } else {
    add(rd, rs1, rs2);
    slti(TMP, rs2, 0);
    slt(TMP2, rd, rs1);
    bne(TMP, TMP2, overflow);
  }
}

void Assembler::SubtractBranchOverflow(Register rd,
                                       Register rs1,
                                       Register rs2,
                                       Label* overflow) {
  ASSERT(rd != TMP);
  ASSERT(rd != TMP2);
  ASSERT(rs1 != TMP);
  ASSERT(rs1 != TMP2);
  ASSERT(rs2 != TMP);
  ASSERT(rs2 != TMP2);

  if ((rd == rs1) && (rd == rs2)) {
    ASSERT(rs1 == rs2);
    mv(TMP, rs1);
    sub(rd, rs1, rs2);   // rs1, rs2 destroyed
    xor_(TMP, TMP, rd);  // TMP negative if sign changed
    bltz(TMP, overflow);
  } else if (rs1 == rs2) {
    ASSERT(rd != rs1);
    ASSERT(rd != rs2);
    sub(rd, rs1, rs2);
    xor_(TMP, rd, rs1);  // TMP negative if sign changed
    bltz(TMP, overflow);
  } else if (rd == rs1) {
    ASSERT(rs1 != rs2);
    slti(TMP, rs1, 0);
    sub(rd, rs1, rs2);  // rs1 destroyed
    slt(TMP2, rd, rs2);
    bne(TMP, TMP2, overflow);
  } else if (rd == rs2) {
    ASSERT(rs1 != rs2);
    slti(TMP, rs2, 0);
    sub(rd, rs1, rs2);  // rs2 destroyed
    slt(TMP2, rd, rs1);
    bne(TMP, TMP2, overflow);
  } else {
    sub(rd, rs1, rs2);
    slti(TMP, rs2, 0);
    slt(TMP2, rs1, rd);
    bne(TMP, TMP2, overflow);
  }
}

void Assembler::MultiplyBranchOverflow(Register rd,
                                       Register rs1,
                                       Register rs2,
                                       Label* overflow) {
  ASSERT(rd != TMP);
  ASSERT(rd != TMP2);
  ASSERT(rs1 != TMP);
  ASSERT(rs1 != TMP2);
  ASSERT(rs2 != TMP);
  ASSERT(rs2 != TMP2);

  // Macro-op fusion: when both products are needed, the recommended sequence
  // is mulh first.
  mulh(TMP, rs1, rs2);
  mul(rd, rs1, rs2);
  srai(TMP2, rd, XLEN - 1);
  bne(TMP, TMP2, overflow);
}

void Assembler::CountLeadingZeroes(Register rd, Register rs) {
  if (Supports(RV_Zbb)) {
    clz(rd, rs);
    return;
  }

  //  n = XLEN
  //  y = x >>32; if (y != 0) { n = n - 32; x = y; }
  //  y = x >>16; if (y != 0) { n = n - 16; x = y; }
  //  y = x >> 8; if (y != 0) { n = n - 8; x = y; }
  //  y = x >> 4; if (y != 0) { n = n - 4; x = y; }
  //  y = x >> 2; if (y != 0) { n = n - 2; x = y; }
  //  y = x >> 1; if (y != 0) { return n - 2; }
  //  return n - x;
  Label l0, l1, l2, l3, l4, l5;
  li(TMP2, XLEN);
#if XLEN == 64
  srli(TMP, rs, 32);
  beqz(TMP, &l0, Assembler::kNearJump);
  subi(TMP2, TMP2, 32);
  mv(rs, TMP);
  Bind(&l0);
#endif
  srli(TMP, rs, 16);
  beqz(TMP, &l1, Assembler::kNearJump);
  subi(TMP2, TMP2, 16);
  mv(rs, TMP);
  Bind(&l1);
  srli(TMP, rs, 8);
  beqz(TMP, &l2, Assembler::kNearJump);
  subi(TMP2, TMP2, 8);
  mv(rs, TMP);
  Bind(&l2);
  srli(TMP, rs, 4);
  beqz(TMP, &l3, Assembler::kNearJump);
  subi(TMP2, TMP2, 4);
  mv(rs, TMP);
  Bind(&l3);
  srli(TMP, rs, 2);
  beqz(TMP, &l4, Assembler::kNearJump);
  subi(TMP2, TMP2, 2);
  mv(rs, TMP);
  Bind(&l4);
  srli(TMP, rs, 1);
  sub(rd, TMP2, rs);
  beqz(TMP, &l5, Assembler::kNearJump);
  subi(rd, TMP2, 2);
  Bind(&l5);
}

void Assembler::RangeCheck(Register value,
                           Register temp,
                           intptr_t low,
                           intptr_t high,
                           RangeCheckCondition condition,
                           Label* target) {
  auto cc = condition == kIfInRange ? LS : HI;
  Register to_check = temp != kNoRegister ? temp : value;
  AddImmediate(to_check, value, -low);
  CompareImmediate(to_check, high - low);
  BranchIf(cc, target);
}

}  // namespace compiler

}  // namespace dart

#endif  // defined(TARGET_ARCH_RISCV)
