#include <biscuit/assert.hpp>
#include <biscuit/assembler.hpp>

#include <array>
#include <cmath>

#include "assembler_util.hpp"

// RVC Extension Instructions

namespace biscuit {
namespace {
// Emits a compressed branch instruction. These consist of:
// funct3 | imm[8|4:3] | rs | imm[7:6|2:1|5] | op
void EmitCompressedBranch(CodeBuffer& buffer, uint32_t funct3, int32_t offset, GPR rs, uint32_t op) {
    BISCUIT_ASSERT(IsValidCBTypeImm(offset));
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rs));

    const auto transformed_imm = TransformToCBTypeImm(static_cast<uint32_t>(offset));
    const auto rs_san = CompressedRegTo3BitEncoding(rs);
    buffer.Emit16(((funct3 & 0b111) << 13) | transformed_imm | (rs_san << 7) | (op & 0b11));
}

// Emits a compressed jump instruction. These consist of:
// funct3 | imm | op
void EmitCompressedJump(CodeBuffer& buffer, uint32_t funct3, int32_t offset, uint32_t op) {
    BISCUIT_ASSERT(IsValidCJTypeImm(offset));
    BISCUIT_ASSERT((offset % 2) == 0);

    buffer.Emit16(TransformToCJTypeImm(static_cast<uint32_t>(offset)) |
                  ((funct3 & 0b111) << 13) | (op & 0b11));
}

// Emits a compress immediate instruction. These consist of:
// funct3 | imm | rd | imm | op
void EmitCompressedImmediate(CodeBuffer& buffer, uint32_t funct3, uint32_t imm, GPR rd, uint32_t op) {
    BISCUIT_ASSERT(rd != x0);

    const auto new_imm = ((imm & 0b11111) << 2) | ((imm & 0b100000) << 7);
    buffer.Emit16(((funct3 & 0b111) << 13) | new_imm | (rd.Index() << 7) | (op & 0b11));
}

// Emits a compressed load instruction. These consist of:
// funct3 | imm | rs1 | imm | rd | op
void EmitCompressedLoad(CodeBuffer& buffer, uint32_t funct3, uint32_t imm, GPR rs,
                        Register rd, uint32_t op) {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rs));
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));

    imm &= 0xF8;

    const auto imm_enc = ((imm & 0x38) << 7) | ((imm & 0xC0) >> 1);
    const auto rd_san = CompressedRegTo3BitEncoding(rd);
    const auto rs_san = CompressedRegTo3BitEncoding(rs);
    buffer.Emit16(((funct3 & 0b111) << 13) | imm_enc | (rs_san << 7) | (rd_san << 2) | (op & 0b11));
}

// Emits a compressed register arithmetic instruction. These consist of:
// funct6 | rd | funct2 | rs | op
void EmitCompressedRegArith(CodeBuffer& buffer, uint32_t funct6, GPR rd, uint32_t funct2,
                            GPR rs, uint32_t op) {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rs));
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));

    const auto rd_san = CompressedRegTo3BitEncoding(rd);
    const auto rs_san = CompressedRegTo3BitEncoding(rs);
    buffer.Emit16(((funct6 & 0b111111) << 10) | (rd_san << 7) | ((funct2 & 0b11) << 5) |
                  (rs_san << 2) | (op & 0b11));
}

// Emits a compressed store instruction. These consist of:
// funct3 | imm | rs1 | imm | rs2 | op
void EmitCompressedStore(CodeBuffer& buffer, uint32_t funct3, uint32_t imm, GPR rs1,
                         Register rs2, uint32_t op) {
    // This has the same format as a compressed load, with rs2 taking the place of rd.
    // We can reuse the code we've already written to handle this.
    EmitCompressedLoad(buffer, funct3, imm, rs1, rs2, op);
}

// Emits a compressed wide immediate instruction. These consist of:
// funct3 | imm | rd | opcode
void EmitCompressedWideImmediate(CodeBuffer& buffer, uint32_t funct3, uint32_t imm,
                                 GPR rd, uint32_t op) {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));

    const auto rd_sanitized = CompressedRegTo3BitEncoding(rd);
    buffer.Emit16(((funct3 & 0b111) << 13) | ((imm & 0xFF) << 5) |
                  (rd_sanitized << 2) | (op & 0b11));
}

void EmitCLBType(CodeBuffer& buffer, uint32_t funct6, GPR rs, uint32_t uimm, GPR rd,
                 uint32_t op, uint32_t b6) {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rs));
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));
    BISCUIT_ASSERT(uimm <= 3);

    const auto rd_san = CompressedRegTo3BitEncoding(rd);
    const auto rs_san = CompressedRegTo3BitEncoding(rs);

    buffer.Emit16((funct6 << 10) |  (rs_san << 7) | (b6 << 6) | (uimm << 5) | (rd_san << 2) | op);
}

void EmitCLHType(CodeBuffer& buffer, uint32_t funct6, GPR rs, uint32_t uimm, GPR rd,
                 uint32_t op, uint32_t b6) {
    BISCUIT_ASSERT((uimm % 2) == 0);
    BISCUIT_ASSERT(uimm <= 2);

    // Only have 1 bit of encoding space for the immediate.
    const uint32_t uimm_fixed = uimm >> 1;
    EmitCLBType(buffer, funct6, rs, uimm_fixed, rd, op, b6);
}

// These have the same layout as the equivalent loads, we just essentially alias
// the name of those to provide better intent at the call site.
void EmitCSBType(CodeBuffer& buffer, uint32_t funct6, GPR rs, uint32_t uimm, GPR rd, uint32_t op) {
    EmitCLBType(buffer, funct6, rs, uimm, rd, op, 0);
}
void EmitCSHType(CodeBuffer& buffer, uint32_t funct6, GPR rs, uint32_t uimm, GPR rd, uint32_t op) {
    EmitCLHType(buffer, funct6, rs, uimm, rd, op, 0);
}

void EmitCUType(CodeBuffer& buffer, uint32_t funct6, GPR rd, uint32_t funct5, uint32_t op) {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));
    const auto rd_san = CompressedRegTo3BitEncoding(rd);

    buffer.Emit16((funct6 << 10) | (rd_san << 7) | (funct5 << 2) | op);
}

void EmitCMJTType(CodeBuffer& buffer, uint32_t funct6, uint32_t index, uint32_t op) {
    buffer.Emit16((funct6 << 10) | (index << 2) | op);
}

void EmitCMMVType(CodeBuffer& buffer, uint32_t funct6, GPR r1s, uint32_t funct2, GPR r2s, uint32_t op) {
    const auto is_valid_s_register = [](GPR reg) {
        return reg == s0 || reg == s1 || (reg >= s2 && reg <= s7);
    };

    BISCUIT_ASSERT(r1s != r2s);
    BISCUIT_ASSERT(is_valid_s_register(r1s));
    BISCUIT_ASSERT(is_valid_s_register(r2s));

    const auto r1s_san = r1s.Index() & 0b111;
    const auto r2s_san = r2s.Index() & 0b111;

    buffer.Emit16((funct6 << 10) | (r1s_san << 7) | (funct2 << 5) | (r2s_san << 2) | op);
}

void EmitCMPPType(CodeBuffer& buffer, uint32_t funct6, uint32_t funct2, PushPopList reglist,
                  int32_t stack_adj, uint32_t op, ArchFeature feature) {
    BISCUIT_ASSERT(stack_adj % 16 == 0);

    static constexpr std::array stack_adj_bases_rv32{
        0U, 0U, 0U, 0U, 16U, 16U, 16U, 16U,
        32U, 32U, 32U, 32U, 48U, 48U, 48U, 64U,
    };
    static constexpr std::array stack_adj_bases_rv64{
        0U, 0U, 0U, 0U, 16U, 16U, 32U, 32U,
        48U, 48U, 64U, 64U, 80U, 80U, 96U, 112U
    };

    const auto bitmask = reglist.GetBitmask();
    const auto stack_adj_base = IsRV64(feature) ? stack_adj_bases_rv64[bitmask]
                                                : stack_adj_bases_rv32[bitmask];
    const auto stack_adj_u = static_cast<uint32_t>(std::abs(stack_adj));
    const auto spimm = (stack_adj_u - stack_adj_base) / 16U;

    // We can only encode up to three differenct values as the upper spimm bits.
    // Ensure we catch any cases where we end up going outside of them.
    BISCUIT_ASSERT(stack_adj_u == stack_adj_base ||
                   stack_adj_u == stack_adj_base + 16 ||
                   stack_adj_u == stack_adj_base + 32 ||
                   stack_adj_u == stack_adj_base + 48);

    buffer.Emit16((funct6 << 10) | (funct2 << 8) | (bitmask << 4) | (spimm << 2) | op);
}
} // Anonymous namespace

void Assembler::C_ADD(GPR rd, GPR rs) noexcept {
    BISCUIT_ASSERT(rs != x0);
    m_buffer.Emit16(0x9002 | (rd.Index() << 7) | (rs.Index() << 2));
}

void Assembler::C_ADDI(GPR rd, int32_t imm) noexcept {
    BISCUIT_ASSERT(imm != 0);
    BISCUIT_ASSERT(IsValidSigned6BitImm(imm));
    EmitCompressedImmediate(m_buffer, 0b000, static_cast<uint32_t>(imm), rd, 0b01);
}

void Assembler::C_ADDIW(GPR rd, int32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    BISCUIT_ASSERT(IsValidSigned6BitImm(imm));
    EmitCompressedImmediate(m_buffer, 0b001, static_cast<uint32_t>(imm), rd, 0b01);
}

void Assembler::C_ADDI4SPN(GPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(imm != 0);
    BISCUIT_ASSERT(imm <= 1020);
    BISCUIT_ASSERT(imm % 4 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x030) << 2) |
                         ((imm & 0x3C0) >> 4) |
                         ((imm & 0x004) >> 1) |
                         ((imm & 0x008) >> 3);
    // clang-format on

    EmitCompressedWideImmediate(m_buffer, 0b000, new_imm, rd, 0b00);
}

void Assembler::C_ADDW(GPR rd, GPR rs) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    EmitCompressedRegArith(m_buffer, 0b100111, rd, 0b01, rs, 0b01);
}

void Assembler::C_ADDI16SP(int32_t imm) noexcept {
    BISCUIT_ASSERT(imm != 0);
    BISCUIT_ASSERT(imm >= -512 && imm <= 496);
    BISCUIT_ASSERT(imm % 16 == 0);

    // clang-format off
    const auto uimm = static_cast<uint32_t>(imm);
    const auto new_imm = ((uimm & 0x020) >> 3) |
                         ((uimm & 0x180) >> 4) |
                         ((uimm & 0x040) >> 1) |
                         ((uimm & 0x010) << 2) |
                         ((uimm & 0x200) << 3);
    // clang-format on

    m_buffer.Emit16(0x6000U | new_imm | (x2.Index() << 7) | 0b01U);
}

void Assembler::C_AND(GPR rd, GPR rs) noexcept {
    EmitCompressedRegArith(m_buffer, 0b100011, rd, 0b11, rs, 0b01);
}

void Assembler::C_ANDI(GPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));

    constexpr auto base = 0x8801U;
    const auto shift_enc = ((imm & 0b11111) << 2) | ((imm & 0b100000) << 7);
    const auto reg = CompressedRegTo3BitEncoding(rd);

    m_buffer.Emit16(base | shift_enc | (reg << 7));
}

void Assembler::C_BEQZ(GPR rs, int32_t offset) noexcept {
    EmitCompressedBranch(m_buffer, 0b110, offset, rs, 0b01);
}

void Assembler::C_BEQZ(GPR rs, Label* label) noexcept {
    const auto address = LinkAndGetOffset(label);
    C_BEQZ(rs, static_cast<int32_t>(address));
}

void Assembler::C_BNEZ(GPR rs, int32_t offset) noexcept {
    EmitCompressedBranch(m_buffer, 0b111, offset, rs, 0b01);
}

void Assembler::C_BNEZ(GPR rs, Label* label) noexcept {
    const auto address = LinkAndGetOffset(label);
    C_BNEZ(rs, static_cast<int32_t>(address));
}

void Assembler::C_EBREAK() noexcept {
    m_buffer.Emit16(0x9002);
}

void Assembler::C_FLD(FPR rd, uint32_t imm, GPR rs) noexcept {
    BISCUIT_ASSERT(IsRV32OrRV64(m_features));
    BISCUIT_ASSERT(imm <= 248);
    BISCUIT_ASSERT(imm % 8 == 0);

    EmitCompressedLoad(m_buffer, 0b001, imm, rs, rd, 0b00);
}

void Assembler::C_FLDSP(FPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV32OrRV64(m_features));
    BISCUIT_ASSERT(imm <= 504);
    BISCUIT_ASSERT(imm % 8 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x018) << 2) |
                         ((imm & 0x1C0) >> 4) |
                         ((imm & 0x020) << 7);
    // clang-format on

    m_buffer.Emit16(0x2002U | (rd.Index() << 7) | new_imm);
}

void Assembler::C_FLW(FPR rd, uint32_t imm, GPR rs) noexcept {
    BISCUIT_ASSERT(IsRV32(m_features));
    BISCUIT_ASSERT(imm <= 124);
    BISCUIT_ASSERT(imm % 4 == 0);

    imm &= 0x7C;
    const auto new_imm = ((imm & 0b0100) << 5) | (imm & 0x78);
    EmitCompressedLoad(m_buffer, 0b011, new_imm, rs, rd, 0b00);
}

void Assembler::C_FLWSP(FPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV32(m_features));
    BISCUIT_ASSERT(imm <= 252);
    BISCUIT_ASSERT(imm % 4 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x020) << 7) |
                         ((imm & 0x0C0) >> 4) |
                         ((imm & 0x01C) << 2);
    // clang-format on

    m_buffer.Emit16(0x6002U | (rd.Index() << 7) | new_imm);
}

void Assembler::C_FSD(FPR rs2, uint32_t imm, GPR rs1) noexcept {
    BISCUIT_ASSERT(IsRV32OrRV64(m_features));
    BISCUIT_ASSERT(imm <= 248);
    BISCUIT_ASSERT(imm % 8 == 0);

    EmitCompressedStore(m_buffer, 0b101, imm, rs1, rs2, 0b00);
}

void Assembler::C_FSDSP(FPR rs, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV32OrRV64(m_features));
    BISCUIT_ASSERT(imm <= 504);
    BISCUIT_ASSERT(imm % 8 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x038) << 7) |
                         ((imm & 0x1C0) << 1);
    // clang-format on

    m_buffer.Emit16(0xA002U | (rs.Index() << 2) | new_imm);
}

void Assembler::C_J(Label* label) noexcept {
    const auto address = LinkAndGetOffset(label);
    C_J(static_cast<int32_t>(address));
}

void Assembler::C_J(int32_t offset) noexcept {
    EmitCompressedJump(m_buffer, 0b101, offset, 0b01);
}

void Assembler::C_JAL(Label* label) noexcept {
    const auto address = LinkAndGetOffset(label);
    C_JAL(static_cast<int32_t>(address));
}

void Assembler::C_JAL(int32_t offset) noexcept {
    BISCUIT_ASSERT(IsRV32(m_features));
    EmitCompressedJump(m_buffer, 0b001, offset, 0b01);
}

void Assembler::C_FSW(FPR rs2, uint32_t imm, GPR rs1) noexcept {
    BISCUIT_ASSERT(IsRV32(m_features));
    BISCUIT_ASSERT(imm <= 124);
    BISCUIT_ASSERT(imm % 4 == 0);

    imm &= 0x7C;
    const auto new_imm = ((imm & 0b0100) << 5) | (imm & 0x78);
    EmitCompressedStore(m_buffer, 0b111, new_imm, rs1, rs2, 0b00);
}

void Assembler::C_FSWSP(FPR rs, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV32(m_features));
    BISCUIT_ASSERT(imm <= 252);
    BISCUIT_ASSERT(imm % 4 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x0C0) << 1) |
                         ((imm & 0x03C) << 7);
    // clang-format on

    m_buffer.Emit16(0xE002U | (rs.Index() << 2) | new_imm);
}

void Assembler::C_JALR(GPR rs) noexcept {
    BISCUIT_ASSERT(rs != x0);
    m_buffer.Emit16(0x9002 | (rs.Index() << 7));
}

void Assembler::C_JR(GPR rs) noexcept {
    BISCUIT_ASSERT(rs != x0);
    m_buffer.Emit16(0x8002 | (rs.Index() << 7));
}

void Assembler::C_LD(GPR rd, uint32_t imm, GPR rs) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    BISCUIT_ASSERT(imm <= 248);
    BISCUIT_ASSERT(imm % 8 == 0);

    EmitCompressedLoad(m_buffer, 0b011, imm, rs, rd, 0b00);
}

void Assembler::C_LDSP(GPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    BISCUIT_ASSERT(rd != x0);
    BISCUIT_ASSERT(imm <= 504);
    BISCUIT_ASSERT(imm % 8 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x018) << 2) |
                         ((imm & 0x1C0) >> 4) |
                         ((imm & 0x020) << 7);
    // clang-format on

    m_buffer.Emit16(0x6002U | (rd.Index() << 7) | new_imm);
}

void Assembler::C_LI(GPR rd, int32_t imm) noexcept {
    BISCUIT_ASSERT(IsValidSigned6BitImm(imm));
    EmitCompressedImmediate(m_buffer, 0b010, static_cast<uint32_t>(imm), rd, 0b01);
}

void Assembler::C_LQ(GPR rd, uint32_t imm, GPR rs) noexcept {
    BISCUIT_ASSERT(IsRV128(m_features));
    BISCUIT_ASSERT(imm <= 496);
    BISCUIT_ASSERT(imm % 16 == 0);

    imm &= 0x1F0;
    const auto new_imm = ((imm & 0x100) >> 5) | (imm & 0xF0);
    EmitCompressedLoad(m_buffer, 0b001, new_imm, rs, rd, 0b00);
}

void Assembler::C_LQSP(GPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV128(m_features));
    BISCUIT_ASSERT(rd != x0);
    BISCUIT_ASSERT(imm <= 1008);
    BISCUIT_ASSERT(imm % 16 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x020) << 7) |
                         ((imm & 0x010) << 2) |
                         ((imm & 0x3C0) >> 4);
    // clang-format on

    m_buffer.Emit16(0x2002U | (rd.Index() << 7) | new_imm);
}

void Assembler::C_LUI(GPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(imm != 0);
    BISCUIT_ASSERT(rd != x0 && rd != x2);

    const auto new_imm = (imm & 0x3F000) >> 12;
    EmitCompressedImmediate(m_buffer, 0b011, new_imm, rd, 0b01);
}

void Assembler::C_LW(GPR rd, uint32_t imm, GPR rs) noexcept {
    BISCUIT_ASSERT(imm <= 124);
    BISCUIT_ASSERT(imm % 4 == 0);

    imm &= 0x7C;
    const auto new_imm = ((imm & 0b0100) << 5) | (imm & 0x78);
    EmitCompressedLoad(m_buffer, 0b010, new_imm, rs, rd, 0b00);
}

void Assembler::C_LWSP(GPR rd, uint32_t imm) noexcept {
    BISCUIT_ASSERT(rd != x0);
    BISCUIT_ASSERT(imm <= 252);
    BISCUIT_ASSERT(imm % 4 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x020) << 7) |
                         ((imm & 0x0C0) >> 4) |
                         ((imm & 0x01C) << 2);
    // clang-format on

    m_buffer.Emit16(0x4002U | (rd.Index() << 7) | new_imm);
}

void Assembler::C_MV(GPR rd, GPR rs) noexcept {
    BISCUIT_ASSERT(rd != x0);
    BISCUIT_ASSERT(rs != x0);
    m_buffer.Emit16(0x8002 | (rd.Index() << 7) | (rs.Index() << 2));
}

void Assembler::C_NOP() noexcept {
    m_buffer.Emit16(1);
}

void Assembler::C_OR(GPR rd, GPR rs) noexcept {
    EmitCompressedRegArith(m_buffer, 0b100011, rd, 0b10, rs, 0b01);
}

void Assembler::C_SD(GPR rs2, uint32_t imm, GPR rs1) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    BISCUIT_ASSERT(imm <= 248);
    BISCUIT_ASSERT(imm % 8 == 0);

    EmitCompressedLoad(m_buffer, 0b111, imm, rs1, rs2, 0b00);
}

void Assembler::C_SDSP(GPR rs, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    BISCUIT_ASSERT(imm <= 504);
    BISCUIT_ASSERT(imm % 8 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x038) << 7) |
                         ((imm & 0x1C0) << 1);
    // clang-format on

    m_buffer.Emit16(0xE002U | (rs.Index() << 2) | new_imm);
}

void Assembler::C_SLLI(GPR rd, uint32_t shift) noexcept {
    BISCUIT_ASSERT(rd != x0);
    BISCUIT_ASSERT(IsValidCompressedShiftAmount(shift));

    // RV128C encodes a 64-bit shift with an encoding of 0.
    if (shift == 64) {
        BISCUIT_ASSERT(IsRV128(m_features));
        shift = 0;
    }

    const auto shift_enc = ((shift & 0b11111) << 2) | ((shift & 0b100000) << 7);
    m_buffer.Emit16(0x0002U | shift_enc | (rd.Index() << 7));
}

void Assembler::C_SQ(GPR rs2, uint32_t imm, GPR rs1) noexcept {
    BISCUIT_ASSERT(IsRV128(m_features));
    BISCUIT_ASSERT(imm <= 496);
    BISCUIT_ASSERT(imm % 16 == 0);

    imm &= 0x1F0;
    const auto new_imm = ((imm & 0x100) >> 5) | (imm & 0xF0);
    EmitCompressedStore(m_buffer, 0b101, new_imm, rs1, rs2, 0b00);
}

void Assembler::C_SQSP(GPR rs, uint32_t imm) noexcept {
    BISCUIT_ASSERT(IsRV128(m_features));
    BISCUIT_ASSERT(imm <= 1008);
    BISCUIT_ASSERT(imm % 16 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x3C0) << 1) |
                         ((imm & 0x030) << 7);
    // clang-format on

    m_buffer.Emit16(0xA002U | (rs.Index() << 2) | new_imm);
}

void Assembler::C_SRAI(GPR rd, uint32_t shift) noexcept {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));
    BISCUIT_ASSERT(IsValidCompressedShiftAmount(shift));

    // RV128C encodes a 64-bit shift with an encoding of 0.
    if (shift == 64) {
        BISCUIT_ASSERT(IsRV128(m_features));
        shift = 0;
    }

    constexpr auto base = 0x8401U;
    const auto shift_enc = ((shift & 0b11111) << 2) | ((shift & 0b100000) << 7);
    const auto reg = CompressedRegTo3BitEncoding(rd);

    m_buffer.Emit16(base | shift_enc | (reg << 7));
}

void Assembler::C_SRLI(GPR rd, uint32_t shift) noexcept {
    BISCUIT_ASSERT(IsValid3BitCompressedReg(rd));
    BISCUIT_ASSERT(IsValidCompressedShiftAmount(shift));

    // RV128C encodes a 64-bit shift with an encoding of 0.
    if (shift == 64) {
        BISCUIT_ASSERT(IsRV128(m_features));
        shift = 0;
    }

    constexpr auto base = 0x8001U;
    const auto shift_enc = ((shift & 0b11111) << 2) | ((shift & 0b100000) << 7);
    const auto reg = CompressedRegTo3BitEncoding(rd);

    m_buffer.Emit16(base | shift_enc | (reg << 7));
}

void Assembler::C_SUB(GPR rd, GPR rs) noexcept {
    EmitCompressedRegArith(m_buffer, 0b100011, rd, 0b00, rs, 0b01);
}

void Assembler::C_SUBW(GPR rd, GPR rs) noexcept {
    BISCUIT_ASSERT(IsRV64OrRV128(m_features));
    EmitCompressedRegArith(m_buffer, 0b100111, rd, 0b00, rs, 0b01);
}

void Assembler::C_SW(GPR rs2, uint32_t imm, GPR rs1) noexcept {
    BISCUIT_ASSERT(imm <= 124);
    BISCUIT_ASSERT(imm % 4 == 0);

    imm &= 0x7C;
    const auto new_imm = ((imm & 0b0100) << 5) | (imm & 0x78);
    EmitCompressedStore(m_buffer, 0b110, new_imm, rs1, rs2, 0b00);
}

void Assembler::C_SWSP(GPR rs, uint32_t imm) noexcept {
    BISCUIT_ASSERT(imm <= 252);
    BISCUIT_ASSERT(imm % 4 == 0);

    // clang-format off
    const auto new_imm = ((imm & 0x0C0) << 1) |
                         ((imm & 0x03C) << 7);
    // clang-format on

    m_buffer.Emit16(0xC002U | (rs.Index() << 2) | new_imm);
}

void Assembler::C_UNDEF() noexcept {
    m_buffer.Emit16(0);
}

void Assembler::C_XOR(GPR rd, GPR rs) noexcept {
    EmitCompressedRegArith(m_buffer, 0b100011, rd, 0b01, rs, 0b01);
}

// Zc Extension Instructions

void Assembler::C_LBU(GPR rd, uint32_t uimm, GPR rs) noexcept {
    // C.LBU swaps the ordering of the immediate.
    const auto uimm_fixed = ((uimm & 0b01) << 1) | ((uimm & 0b10) >> 1);

    EmitCLBType(m_buffer, 0b100000, rs, uimm_fixed, rd, 0b00, 0);
}
void Assembler::C_LH(GPR rd, uint32_t uimm, GPR rs) noexcept {
    EmitCLHType(m_buffer, 0b100001, rs, uimm, rd, 0b00, 1);
}
void Assembler::C_LHU(GPR rd, uint32_t uimm, GPR rs) noexcept {
    EmitCLHType(m_buffer, 0b100001, rs, uimm, rd, 0b00, 0);
}
void Assembler::C_SB(GPR rs2, uint32_t uimm, GPR rs1) noexcept {
    // C.SB swaps the ordering of the immediate.
    const auto uimm_fixed = ((uimm & 0b01) << 1) | ((uimm & 0b10) >> 1);

    EmitCSBType(m_buffer, 0b100010, rs1, uimm_fixed, rs2, 0b00);
}
void Assembler::C_SH(GPR rs2, uint32_t uimm, GPR rs1) noexcept {
    EmitCSHType(m_buffer, 0b100011, rs1, uimm, rs2, 0b00);
}

void Assembler::C_SEXT_B(GPR rd) noexcept {
    EmitCUType(m_buffer, 0b100111, rd, 0b11001, 0b01);
}
void Assembler::C_SEXT_H(GPR rd) noexcept {
    EmitCUType(m_buffer, 0b100111, rd, 0b11011, 0b01);
}
void Assembler::C_ZEXT_B(GPR rd) noexcept {
    EmitCUType(m_buffer, 0b100111, rd, 0b11000, 0b01);
}
void Assembler::C_ZEXT_H(GPR rd) noexcept {
    EmitCUType(m_buffer, 0b100111, rd, 0b11010, 0b01);
}
void Assembler::C_ZEXT_W(GPR rd) noexcept {
    BISCUIT_ASSERT(IsRV64(m_features));
    EmitCUType(m_buffer, 0b100111, rd, 0b11100, 0b01);
}

void Assembler::C_MUL(GPR rsd, GPR rs2) noexcept {
    EmitCompressedRegArith(m_buffer, 0b100111, rsd, 0b10, rs2, 0b01);
}
void Assembler::C_NOT(GPR rd) noexcept {
    EmitCUType(m_buffer, 0b100111, rd, 0b11101, 0b01);
}

void Assembler::CM_JALT(uint32_t index) noexcept {
    BISCUIT_ASSERT(index >= 32 && index <= 255);
    EmitCMJTType(m_buffer, 0b101000, index, 0b10);
}
void Assembler::CM_JT(uint32_t index) noexcept {
    BISCUIT_ASSERT(index <= 31);
    EmitCMJTType(m_buffer, 0b101000, index, 0b10);
}

void Assembler::CM_MVA01S(GPR r1s, GPR r2s) noexcept {
    EmitCMMVType(m_buffer, 0b101011, r1s, 0b11, r2s, 0b10);
}
void Assembler::CM_MVSA01(GPR r1s, GPR r2s) noexcept {
    EmitCMMVType(m_buffer, 0b101011, r1s, 0b01, r2s, 0b10);
}

void Assembler::CM_POP(PushPopList reg_list, int32_t stack_adj) noexcept {
    BISCUIT_ASSERT(stack_adj > 0);
    EmitCMPPType(m_buffer, 0b101110, 0b10, reg_list, stack_adj, 0b10, m_features);
}
void Assembler::CM_POPRET(PushPopList reg_list, int32_t stack_adj) noexcept {
    BISCUIT_ASSERT(stack_adj > 0);
    EmitCMPPType(m_buffer, 0b101111, 0b10, reg_list, stack_adj, 0b10, m_features);
}
void Assembler::CM_POPRETZ(PushPopList reg_list, int32_t stack_adj) noexcept {
    BISCUIT_ASSERT(stack_adj > 0);
    EmitCMPPType(m_buffer, 0b101111, 0b00, reg_list, stack_adj, 0b10, m_features);
}
void Assembler::CM_PUSH(PushPopList reg_list, int32_t stack_adj) noexcept {
    BISCUIT_ASSERT(stack_adj < 0);
    EmitCMPPType(m_buffer, 0b101110, 0b00, reg_list, stack_adj, 0b10, m_features);
}

} // namespace biscuit
