blob: 98b6a0b63b5948c5461843bf1f648a87b1e37b8e [file] [log] [blame]
/*
* Copyright (C) 2021 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if ENABLE(ASSEMBLER) && CPU(RISCV64)
#include "AssemblerBuffer.h"
#include "RISCV64Registers.h"
#include <tuple>
namespace JSC {
namespace RISCV64Registers {
typedef enum : int8_t {
#define REGISTER_ID(id, name, r, cs) id,
FOR_EACH_GP_REGISTER(REGISTER_ID)
#undef REGISTER_ID
#define REGISTER_ALIAS(id, name, alias) id = alias,
FOR_EACH_REGISTER_ALIAS(REGISTER_ALIAS)
#undef REGISTER_ALIAS
InvalidGPRReg = -1,
} RegisterID;
typedef enum : int8_t {
#define REGISTER_ID(id, name) id,
FOR_EACH_SP_REGISTER(REGISTER_ID)
#undef REGISTER_ID
InvalidSPReg = -1,
} SPRegisterID;
typedef enum : int8_t {
#define REGISTER_ID(id, name, r, cs) id,
FOR_EACH_FP_REGISTER(REGISTER_ID)
#undef REGISTER_ID
InvalidFPRReg = -1,
} FPRegisterID;
} // namespace RISCV64Registers
namespace RISCV64Instructions {
enum class Opcode : unsigned {
LOAD = 0b0000011,
LOAD_FP = 0b0000111,
MISC_MEM = 0b0001111,
OP_IMM = 0b0010011,
AUIPC = 0b0010111,
OP_IMM_32 = 0b0011011,
STORE = 0b0100011,
STORE_FP = 0b0100111,
AMO = 0b0101111,
OP = 0b0110011,
LUI = 0b0110111,
OP_32 = 0b0111011,
MADD = 0b1000011,
MSUB = 0b1000111,
NMSUB = 0b1001011,
NMADD = 0b1001111,
OP_FP = 0b1010011,
BRANCH = 0b1100011,
JALR = 0b1100111,
JAL = 0b1101111,
SYSTEM = 0b1110011,
};
enum class FCVTType {
W, WU,
L, LU,
S, D,
};
enum class FMVType {
X, W, D,
};
enum class FPRoundingMode : unsigned {
RNE = 0b000,
RTZ = 0b001,
RDN = 0b010,
RUP = 0b011,
RMM = 0b100,
DYN = 0b111,
};
enum class MemoryOperation : uint8_t {
I = 1 << 3,
O = 1 << 2,
R = 1 << 1,
W = 1 << 0,
RW = R | W,
IORW = I | O | R | W,
};
enum class MemoryAccess : uint8_t {
Acquire = 1 << 1,
Release = 1 << 0,
AcquireRelease = Acquire | Release,
};
// Register helpers
using RegisterID = RISCV64Registers::RegisterID;
using FPRegisterID = RISCV64Registers::FPRegisterID;
template<typename T>
auto registerValue(T registerID)
-> std::enable_if_t<(std::is_same_v<T, RegisterID> || std::is_same_v<T, FPRegisterID>), unsigned>
{
return unsigned(registerID) & ((1 << 5) - 1);
}
// InstructionValue contains the 32-bit instruction value and also provides access into the desired field.
struct InstructionValue {
explicit InstructionValue(uint32_t value)
: value(value)
{ }
template<unsigned fieldStart, unsigned fieldSize>
uint32_t field()
{
static_assert(fieldStart + fieldSize <= (sizeof(uint32_t) * 8));
return (value >> fieldStart) & ((1 << fieldSize) - 1);
}
uint32_t opcode() { return field<0, 7>(); }
uint32_t value;
};
// Immediate types
// ImmediateBase acts as the base struct for the different types. The bit-size of the immediate is determined as the
// template parameter on the ImmediateBase struct. Internally, every immediate value is represented through a uint32_t
// from which the appropriate bit-sets are then copied into the target instruction.
// ImmediateBase provides three ways to construct the target immediate (the type of which is specified as a template
// parameter to these construction methods):
// ImmediateBase<N>::v<ImmediateType, int32_t>() -- for constant immediates
// ImmediateBase<N>::v<ImmediateType>(int32_t/int64_t) -- for variable immediates whose values were validated beforehand
// ImmediateBase<N>::ImmediateBase(uint32_t) -- for immediate values already packed in the uint32_t format
// There's also ImmediateType::value(InstructionValue) helpers that for a given instruction value retrieve the
// appropriate signed immediate value that was encoded in that instruction (except for the U-type immediate which is
// a 32-bit unsigned value).
template<unsigned immediateSize>
struct ImmediateBase {
static_assert(immediateSize <= sizeof(uint32_t) * 8);
template<typename T>
static constexpr T immediateMask()
{
if constexpr(immediateSize < sizeof(uint32_t) * 8)
return ((T(1) << immediateSize) - 1);
return T(~0);
}
template<typename T>
static auto isValid(T immValue)
-> std::enable_if_t<(std::is_same_v<T, int32_t> || std::is_same_v<T, int64_t>), bool>
{
constexpr unsigned shift = sizeof(T) * 8 - immediateSize;
return immValue == ((immValue << shift) >> shift);
}
template<typename ImmediateType, int32_t immValue>
static ImmediateType v()
{
static_assert((-(1 << (immediateSize - 1)) <= immValue) && (immValue <= ((1 << (immediateSize - 1)) - 1)));
int32_t value = immValue;
return ImmediateType((*reinterpret_cast<uint32_t*>(&value)) & immediateMask<uint32_t>());
}
template<typename ImmediateType>
static ImmediateType v(int32_t immValue)
{
ASSERT(isValid(immValue));
uint32_t value = *reinterpret_cast<uint32_t*>(&immValue);
return ImmediateType(value & immediateMask<uint32_t>());
}
template<typename ImmediateType>
static ImmediateType v(int64_t immValue)
{
ASSERT(isValid(immValue));
uint64_t value = *reinterpret_cast<uint64_t*>(&immValue);
return ImmediateType(uint32_t(value & immediateMask<uint64_t>()));
}
explicit ImmediateBase(uint32_t immValue)
: imm(immValue)
{
if constexpr (immediateSize < sizeof(uint32_t) * 8)
ASSERT(imm < (1 << immediateSize));
}
template<unsigned fieldStart, unsigned fieldSize>
uint32_t field()
{
static_assert(fieldStart + fieldSize <= immediateSize);
return (imm >> fieldStart) & ((1 << fieldSize) - 1);
}
uint32_t imm;
};
struct IImmediate : ImmediateBase<12> {
explicit IImmediate(uint32_t immValue)
: ImmediateBase<12>(immValue)
{ }
static int32_t value(InstructionValue insn)
{
uint32_t base = insn.field<20, 12>();
int32_t imm = *reinterpret_cast<int32_t*>(&base);
return ((imm << 20) >> 20);
}
};
struct SImmediate : ImmediateBase<12> {
explicit SImmediate(uint32_t immValue)
: ImmediateBase<12>(immValue)
{ }
static int32_t value(InstructionValue insn)
{
uint32_t base = 0
| (insn.field<31, 1>() << 11)
| (insn.field<25, 6>() << 5)
| (insn.field< 8, 4>() << 1)
| (insn.field< 7, 1>() << 0);
int32_t imm = *reinterpret_cast<int32_t*>(&base);
return ((imm << 20) >> 20);
}
};
struct BImmediate : ImmediateBase<13> {
explicit BImmediate(uint32_t immValue)
: ImmediateBase<13>(immValue)
{ }
static int32_t value(InstructionValue insn)
{
uint32_t base = 0
| (insn.field<31, 1>() << 12)
| (insn.field< 7, 1>() << 11)
| (insn.field<25, 6>() << 5)
| (insn.field< 8, 4>() << 1);
int32_t imm = *reinterpret_cast<int32_t*>(&base);
return ((imm << 19) >> 19);
}
};
struct UImmediate : ImmediateBase<32> {
explicit UImmediate(uint32_t immValue)
: ImmediateBase((immValue >> 12) << 12)
{ }
static uint32_t value(InstructionValue insn)
{
return insn.field<12, 20>() << 12;
}
};
struct JImmediate : ImmediateBase<21> {
explicit JImmediate(uint32_t immValue)
: ImmediateBase<21>(immValue)
{ }
static int32_t value(InstructionValue insn)
{
uint32_t base = 0
| (insn.field<31, 1>() << 20)
| (insn.field<12, 8>() << 12)
| (insn.field<20, 1>() << 11)
| (insn.field<25, 6>() << 5)
| (insn.field<21, 4>() << 1);
int32_t imm = *reinterpret_cast<int32_t*>(&base);
return ((imm << 11) >> 11);
}
};
struct ImmediateDecomposition {
template<typename T, typename = std::enable_if_t<(std::is_same_v<T, int32_t> || std::is_same_v<T, int64_t>)>>
explicit ImmediateDecomposition(T immediate)
: upper(UImmediate(0))
, lower(IImmediate(0))
{
ASSERT(ImmediateBase<32>::isValid(immediate));
int32_t value = int32_t(immediate);
if (value & (1 << 11))
value += (1 << 12);
upper = UImmediate::v<UImmediate>(value);
lower = IImmediate::v<IImmediate>((value << 20) >> 20);
}
UImmediate upper;
IImmediate lower;
};
// Instruction types
// Helper struct that provides different groupings of register types as required for different instructions.
// The tuple size and contained types are used for compile-time checks of matching register types being passed
// to those instructions.
struct RegistersBase {
struct GType { }; // General-purpose register
struct FType { }; // Floating-point register
struct ZType { }; // Zero-value unused register
template<typename... RTypes>
using Tuple = std::tuple<RTypes...>;
template<size_t I, typename TupleType>
using Type = std::tuple_element_t<I, TupleType>;
template<typename TupleType>
static constexpr size_t Size()
{
return std::tuple_size_v<TupleType>;
}
using G = Tuple<GType>;
using GG = Tuple<GType, GType>;
using GF = Tuple<GType, FType>;
using GGG = Tuple<GType, GType, GType>;
using GGZ = Tuple<GType, GType, ZType>;
using GFF = Tuple<GType, FType, FType>;
using GFZ = Tuple<GType, FType, ZType>;
using FG = Tuple<FType, GType>;
using FF = Tuple<FType, FType>;
using FGZ = Tuple<FType, GType, ZType>;
using FFF = Tuple<FType, FType, FType>;
using FFZ = Tuple<FType, FType, ZType>;
using FFFF = Tuple<FType, FType, FType, FType>;
using ZZ = Tuple<ZType, ZType>;
};
// These are the base instruction structs. For R-type instructions, additional variations are provided.
// Opcode, different spec-defined constant instruction fields and the required register types are specified through the
// template parameters. The construct() static methods compose and return the instruction value in the 32-bit unsigned
// format.
// The matches() methods are usable to match a given InstructionValue against the target instruction type. Baseline
// implementations test the opcode and constant fields, but different instruction specializations can provide a better
// matching technique if necessary.
// For each base instruction type there's also static getters for dynamic bit-fields like register values, rounding mode
// or different flag types. These should be used on an InstructionValue after a matching instruction type was already
// confirmed. These are mostly used for disassembly, leaving it to that implementation to handle the returned raw
// bit-field values.
template<typename RegisterTypes>
struct RTypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 3);
using RD = RegistersBase::Type<0, RegisterTypes>;
using RS1 = RegistersBase::Type<1, RegisterTypes>;
using RS2 = RegistersBase::Type<2, RegisterTypes>;
};
template<Opcode opcode, unsigned funct3, unsigned funct7, typename RegisterTypes>
struct RTypeBase {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct3 < (1 << 3));
static_assert(funct7 < (1 << 7));
using Base = RTypeBase<opcode, funct3, funct7, RegisterTypes>;
using Registers = RTypeRegisters<RegisterTypes>;
template<typename RDType, typename RS1Type, typename RS2Type>
static uint32_t construct(RDType rd, RS1Type rs1, RS2Type rs2)
{
uint32_t instruction = 0
| (funct7 << 25)
| (registerValue(rs2) << 20)
| (registerValue(rs1) << 15)
| (funct3 << 12)
| (registerValue(rd) << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct3 == insn.field<12, 3>() && funct7 == insn.field<25, 7>();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
static uint8_t rs2(InstructionValue insn) { return insn.field<20, 5>(); }
};
template<Opcode opcode, unsigned funct7, typename RegisterTypes>
struct RTypeBaseWithRoundingMode {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct7 < (1 << 7));
using Base = RTypeBaseWithRoundingMode<opcode, funct7, RegisterTypes>;
using Registers = RTypeRegisters<RegisterTypes>;
template<typename RDType, typename RS1Type, typename RS2Type>
static uint32_t construct(RDType rd, RS1Type rs1, RS2Type rs2, FPRoundingMode rm)
{
ASSERT(unsigned(rm) < (1 << 3));
uint32_t instruction = 0
| (funct7 << 25)
| (registerValue(rs2) << 20)
| (registerValue(rs1) << 15)
| (unsigned(rm) << 12)
| (registerValue(rd) << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct7 == insn.field<25, 7>();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
static uint8_t rs2(InstructionValue insn) { return insn.field<20, 5>(); }
static uint8_t rm(InstructionValue insn) { return insn.field<12, 3>(); }
};
template<Opcode opcode, unsigned funct3, unsigned funct7, typename RegisterTypes>
struct RTypeBaseWithAqRl {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct3 < (1 << 3));
static_assert(funct7 < (1 << 7));
using Base = RTypeBaseWithAqRl<opcode, funct3, funct7, RegisterTypes>;
using Registers = RTypeRegisters<RegisterTypes>;
template<typename RDType, typename RS1Type, typename RS2Type>
static uint32_t construct(RDType rd, RS1Type rs1, RS2Type rs2, const std::initializer_list<MemoryAccess>& access)
{
unsigned aqrl = 0;
for (auto& value : access)
aqrl |= unsigned(value);
ASSERT(aqrl < (1 << 2));
uint32_t instruction = 0
| ((funct7 | aqrl) << 25)
| (registerValue(rs2) << 20)
| (registerValue(rs1) << 15)
| (funct3 << 12)
| (registerValue(rd) << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct3 == insn.field<12, 3>() && (funct7 >> 2) == insn.field<27, 5>();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
static uint8_t rs2(InstructionValue insn) { return insn.field<20, 5>(); }
static uint8_t aqrl(InstructionValue insn) { return insn.field<25, 2>(); }
};
template<typename RegisterTypes>
struct R4TypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 4);
using RD = RegistersBase::Type<0, RegisterTypes>;
using RS1 = RegistersBase::Type<1, RegisterTypes>;
using RS2 = RegistersBase::Type<2, RegisterTypes>;
using RS3 = RegistersBase::Type<3, RegisterTypes>;
};
template<Opcode opcode, unsigned funct2, typename RegisterTypes>
struct R4TypeBaseWithRoundingMode {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct2 < (1 << 2));
using Base = R4TypeBaseWithRoundingMode<opcode, funct2, RegisterTypes>;
using Registers = R4TypeRegisters<RegisterTypes>;
template<typename RDType, typename RS1Type, typename RS2Type, typename RS3Type>
static uint32_t construct(RDType rd, RS1Type rs1, RS2Type rs2, RS3Type rs3, FPRoundingMode rm)
{
ASSERT(unsigned(rm) < (1 << 3));
uint32_t instruction = 0
| (registerValue(rs3) << 27)
| (funct2 << 25)
| (registerValue(rs2) << 20)
| (registerValue(rs1) << 15)
| (unsigned(rm) << 12)
| (registerValue(rd) << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct2 == insn.field<25, 2>();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
static uint8_t rs2(InstructionValue insn) { return insn.field<20, 5>(); }
static uint8_t rs3(InstructionValue insn) { return insn.field<27, 5>(); }
static uint8_t rm(InstructionValue insn) { return insn.field<12, 3>(); }
};
template<typename RegisterTypes>
struct ITypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 2);
using RD = RegistersBase::Type<0, RegisterTypes>;
using RS1 = RegistersBase::Type<1, RegisterTypes>;
};
template<Opcode opcode, unsigned funct3, typename RegisterTypes>
struct ITypeBase {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct3 < (1 << 3));
using Base = ITypeBase<opcode, funct3, RegisterTypes>;
using Registers = ITypeRegisters<RegisterTypes>;
template<typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1, IImmediate imm)
{
uint32_t instruction = 0
| (imm.field<0, 12>() << 20)
| (registerValue(rs1) << 15)
| (funct3 << 12)
| (registerValue(rd) << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct3 == insn.field<12, 3>();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
};
template<typename RegisterTypes>
struct STypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 2);
using RS1 = RegistersBase::Type<0, RegisterTypes>;
using RS2 = RegistersBase::Type<1, RegisterTypes>;
};
template<Opcode opcode, unsigned funct3, typename RegisterTypes>
struct STypeBase {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct3 < (1 << 3));
using Base = STypeBase<opcode, funct3, RegisterTypes>;
using Registers = STypeRegisters<RegisterTypes>;
template<typename RS1Type, typename RS2Type>
static uint32_t construct(RS1Type rs1, RS2Type rs2, SImmediate imm)
{
uint32_t instruction = 0
| (imm.field<5, 7>() << 25)
| (registerValue(rs2) << 20)
| (registerValue(rs1) << 15)
| (funct3 << 12)
| (imm.field<0, 5>() << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct3 == insn.field<12, 3>();
}
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
static uint8_t rs2(InstructionValue insn) { return insn.field<20, 5>(); }
};
template<typename RegisterTypes>
struct BTypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 2);
using RS1 = RegistersBase::Type<0, RegisterTypes>;
using RS2 = RegistersBase::Type<1, RegisterTypes>;
};
template<Opcode opcode, unsigned funct3, typename RegisterTypes>
struct BTypeBase {
static_assert(unsigned(opcode) < (1 << 7));
static_assert(funct3 < (1 << 3));
using Base = BTypeBase<opcode, funct3, RegisterTypes>;
using Registers = BTypeRegisters<RegisterTypes>;
static constexpr unsigned funct3Value = funct3;
template<typename RS1Type, typename RS2Type>
static uint32_t construct(RS1Type rs1, RS2Type rs2, BImmediate imm)
{
uint32_t instruction = 0
| (imm.field<12, 1>() << 31)
| (imm.field< 5, 6>() << 25)
| (registerValue(rs2) << 20)
| (registerValue(rs1) << 15)
| (funct3 << 12)
| (imm.field< 1, 4>() << 8)
| (imm.field<11, 1>() << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode() && funct3 == insn.field<12, 3>();
}
static uint8_t rs1(InstructionValue insn) { return insn.field<15, 5>(); }
static uint8_t rs2(InstructionValue insn) { return insn.field<20, 5>(); }
};
template<typename RegisterTypes>
struct UTypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 1);
using RD = RegistersBase::Type<0, RegisterTypes>;
};
template<Opcode opcode, typename RegisterTypes>
struct UTypeBase {
static_assert(unsigned(opcode) < (1 << 7));
using Base = UTypeBase<opcode, RegisterTypes>;
using Registers = UTypeRegisters<RegisterTypes>;
template<typename RDType>
static uint32_t construct(RDType rd, UImmediate imm)
{
uint32_t instruction = imm.imm | (registerValue(rd) << 7) | unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
};
template<typename RegisterTypes>
struct JTypeRegisters {
static_assert(RegistersBase::Size<RegisterTypes>() == 1);
using RD = RegistersBase::Type<0, RegisterTypes>;
};
template<Opcode opcode, typename RegisterTypes>
struct JTypeBase {
static_assert(unsigned(opcode) < (1 << 7));
using Base = JTypeBase<opcode, RegisterTypes>;
using Registers = UTypeRegisters<RegisterTypes>;
template<typename RDType>
static uint32_t construct(RDType rd, JImmediate imm)
{
uint32_t instruction = 0
| (imm.field<20, 1>() << 31)
| (imm.field< 1, 10>() << 21)
| (imm.field<11, 1>() << 20)
| (imm.field<12, 8>() << 12)
| (registerValue(rd) << 7)
| unsigned(opcode);
return instruction;
}
static bool matches(InstructionValue insn)
{
return unsigned(opcode) == insn.opcode();
}
static uint8_t rd(InstructionValue insn) { return insn.field<7, 5>(); }
};
// The following instruction definitions utilize the base instruction structs, in most cases specifying everything
// necessary in the template parameters of the base instruction struct they are inheriting from. For each instruction
// there's also a pretty-print name constant included in the definition, for use by the disassembler.
// RV32I Base Instruction Set
struct LUI : UTypeBase<Opcode::LUI, RegistersBase::G> {
static constexpr const char* name = "lui";
};
struct AUIPC : UTypeBase<Opcode::AUIPC, RegistersBase::G> {
static constexpr const char* name = "auipc";
};
struct JAL : JTypeBase<Opcode::JAL, RegistersBase::G> {
static constexpr const char* name = "jal";
};
struct JALR : ITypeBase<Opcode::JALR, 0b000, RegistersBase::GG> {
static constexpr const char* name = "jalr";
};
struct BEQ : BTypeBase<Opcode::BRANCH, 0b000, RegistersBase::GG> {
static constexpr const char* name = "beq";
};
struct BNE : BTypeBase<Opcode::BRANCH, 0b001, RegistersBase::GG> {
static constexpr const char* name = "bne";
};
struct BLT : BTypeBase<Opcode::BRANCH, 0b100, RegistersBase::GG> {
static constexpr const char* name = "blt";
};
struct BGE : BTypeBase<Opcode::BRANCH, 0b101, RegistersBase::GG> {
static constexpr const char* name = "bge";
};
struct BLTU : BTypeBase<Opcode::BRANCH, 0b110, RegistersBase::GG> {
static constexpr const char* name = "bltu";
};
struct BGEU : BTypeBase<Opcode::BRANCH, 0b111, RegistersBase::GG> {
static constexpr const char* name = "bgeu";
};
struct LB : ITypeBase<Opcode::LOAD, 0b000, RegistersBase::GG> {
static constexpr const char* name = "lb";
};
struct LH : ITypeBase<Opcode::LOAD, 0b001, RegistersBase::GG> {
static constexpr const char* name = "lh";
};
struct LW : ITypeBase<Opcode::LOAD, 0b010, RegistersBase::GG> {
static constexpr const char* name = "lw";
};
struct LBU : ITypeBase<Opcode::LOAD, 0b100, RegistersBase::GG> {
static constexpr const char* name = "lbu";
};
struct LHU : ITypeBase<Opcode::LOAD, 0b101, RegistersBase::GG> {
static constexpr const char* name = "lhu";
};
struct SB : STypeBase<Opcode::STORE, 0b000, RegistersBase::GG> {
static constexpr const char* name = "sb";
};
struct SH : STypeBase<Opcode::STORE, 0b001, RegistersBase::GG> {
static constexpr const char* name = "sh";
};
struct SW : STypeBase<Opcode::STORE, 0b010, RegistersBase::GG> {
static constexpr const char* name = "sw";
};
struct ADDI : ITypeBase<Opcode::OP_IMM, 0b000, RegistersBase::GG> {
static constexpr const char* name = "addi";
};
struct SLTI : ITypeBase<Opcode::OP_IMM, 0b010, RegistersBase::GG> {
static constexpr const char* name = "slti";
};
struct SLTIU : ITypeBase<Opcode::OP_IMM, 0b011, RegistersBase::GG> {
static constexpr const char* name = "sltiu";
};
struct XORI : ITypeBase<Opcode::OP_IMM, 0b100, RegistersBase::GG> {
static constexpr const char* name = "xori";
};
struct ORI : ITypeBase<Opcode::OP_IMM, 0b110, RegistersBase::GG> {
static constexpr const char* name = "ori";
};
struct ANDI : ITypeBase<Opcode::OP_IMM, 0b111, RegistersBase::GG> {
static constexpr const char* name = "andi";
};
struct SLLI : ITypeBase<Opcode::OP_IMM, 0b001, RegistersBase::GG> {
static constexpr const char* name = "slli";
using Base::construct;
template<unsigned shiftAmount, typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
static_assert(shiftAmount < (1 << 6));
return Base::construct(rd, rs1, IImmediate::v<IImmediate, (0b000000 << 6) | shiftAmount>());
}
};
struct SRLI : ITypeBase<Opcode::OP_IMM, 0b101, RegistersBase::GG> {
static constexpr const char* name = "srli";
using Base::construct;
template<unsigned shiftAmount, typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
static_assert(shiftAmount < (1 << 6));
return Base::construct(rd, rs1, IImmediate::v<IImmediate, (0b000000 << 6) | shiftAmount>());
}
};
struct SRAI : ITypeBase<Opcode::OP_IMM, 0b101, RegistersBase::GG> {
static constexpr const char* name = "srai";
using Base::construct;
template<unsigned shiftAmount, typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
static_assert(shiftAmount < (1 << 6));
return Base::construct(rd, rs1, IImmediate::v<IImmediate, (0b010000 << 6) | shiftAmount>());
}
};
struct ADD : RTypeBase<Opcode::OP, 0b000, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "add";
};
struct SUB : RTypeBase<Opcode::OP, 0b000, 0b0100000, RegistersBase::GGG> {
static constexpr const char* name = "sub";
};
struct SLL : RTypeBase<Opcode::OP, 0b001, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "sll";
};
struct SLT : RTypeBase<Opcode::OP, 0b010, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "slt";
};
struct SLTU : RTypeBase<Opcode::OP, 0b011, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "sltu";
};
struct XOR : RTypeBase<Opcode::OP, 0b100, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "xor";
};
struct SRL : RTypeBase<Opcode::OP, 0b101, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "srl";
};
struct SRA : RTypeBase<Opcode::OP, 0b101, 0b0100000, RegistersBase::GGG> {
static constexpr const char* name = "sra";
};
struct OR : RTypeBase<Opcode::OP, 0b110, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "or";
};
struct AND : RTypeBase<Opcode::OP, 0b111, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "and";
};
struct FENCE : ITypeBase<Opcode::MISC_MEM, 0b000, RegistersBase::ZZ> {
static constexpr const char* name = "fence";
};
struct ECALL : ITypeBase<Opcode::SYSTEM, 0b000, RegistersBase::ZZ> {
static constexpr const char* name = "ecall";
};
struct EBREAK : ITypeBase<Opcode::SYSTEM, 0b000, RegistersBase::ZZ> {
static constexpr const char* name = "ebreak";
};
// RV64I Base Instruction Set (in addition to RV32I)
struct LWU : ITypeBase<Opcode::LOAD, 0b110, RegistersBase::GG> {
static constexpr const char* name = "lwu";
};
struct LD : ITypeBase<Opcode::LOAD, 0b011, RegistersBase::GG> {
static constexpr const char* name = "ld";
};
struct SD : STypeBase<Opcode::STORE, 0b011, RegistersBase::GG> {
static constexpr const char* name = "sd";
};
struct ADDIW : ITypeBase<Opcode::OP_IMM_32, 0b000, RegistersBase::GG> {
static constexpr const char* name = "addiw";
};
struct SLLIW : ITypeBase<Opcode::OP_IMM_32, 0b001, RegistersBase::GG> {
static constexpr const char* name = "slliw";
using Base::construct;
template<unsigned shiftAmount, typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
static_assert(shiftAmount < (1 << 5));
return Base::construct(rd, rs1, IImmediate::v<IImmediate, (0b0000000 << 5) | shiftAmount>());
}
};
struct SRLIW : ITypeBase<Opcode::OP_IMM_32, 0b101, RegistersBase::GG> {
static constexpr const char* name = "srliw";
using Base::construct;
template<unsigned shiftAmount, typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
static_assert(shiftAmount < (1 << 5));
return Base::construct(rd, rs1, IImmediate::v<IImmediate, (0b0000000 << 5) | shiftAmount>());
}
};
struct SRAIW : ITypeBase<Opcode::OP_IMM_32, 0b101, RegistersBase::GG> {
static constexpr const char* name = "sraiw";
using Base::construct;
template<unsigned shiftAmount, typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
static_assert(shiftAmount < (1 << 5));
return Base::construct(rd, rs1, IImmediate::v<IImmediate, (0b0100000 << 5) | shiftAmount>());
}
};
struct ADDW : RTypeBase<Opcode::OP_32, 0b000, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "addw";
};
struct SUBW : RTypeBase<Opcode::OP_32, 0b000, 0b0100000, RegistersBase::GGG> {
static constexpr const char* name = "subw";
};
struct SLLW : RTypeBase<Opcode::OP_32, 0b001, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "sllw";
};
struct SRLW : RTypeBase<Opcode::OP_32, 0b101, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "srlw";
};
struct SRAW : RTypeBase<Opcode::OP_32, 0b101, 0b0100000, RegistersBase::GGG> {
static constexpr const char* name = "sraw";
};
// RV32/RV64 Zifencei Standard Extension
struct FENCE_I : ITypeBase<Opcode::MISC_MEM, 0b001, RegistersBase::ZZ> {
static constexpr const char* name = "fence.i";
};
// RV32M Standard Extension
struct MUL : RTypeBase<Opcode::OP, 0b000, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "mul";
};
struct MULH : RTypeBase<Opcode::OP, 0b001, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "mulh";
};
struct MULHSU : RTypeBase<Opcode::OP, 0b010, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "mulhsu";
};
struct MULHU : RTypeBase<Opcode::OP, 0b011, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "mulhu";
};
struct DIV : RTypeBase<Opcode::OP, 0b100, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "div";
};
struct DIVU : RTypeBase<Opcode::OP, 0b101, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "divu";
};
struct REM : RTypeBase<Opcode::OP, 0b110, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "rem";
};
struct REMU : RTypeBase<Opcode::OP, 0b111, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "remu";
};
// RV64M Standard Extension (in addition to RV32M)
struct MULW : RTypeBase<Opcode::OP_32, 0b000, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "mulw";
};
struct DIVW : RTypeBase<Opcode::OP_32, 0b100, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "divw";
};
struct DIVUW : RTypeBase<Opcode::OP_32, 0b101, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "divuw";
};
struct REMW : RTypeBase<Opcode::OP_32, 0b110, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "remw";
};
struct REMUW : RTypeBase<Opcode::OP_32, 0b111, 0b0000001, RegistersBase::GGG> {
static constexpr const char* name = "remuw";
};
// RV32A Standard Extension
struct LR_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0001000, RegistersBase::GGZ> {
static constexpr const char* name = "lr.w";
};
struct SC_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0001100, RegistersBase::GGG> {
static constexpr const char* name = "sc.w";
};
struct AMOSWAP_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0000100, RegistersBase::GGG> {
static constexpr const char* name = "amoswap.w";
};
struct AMOADD_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "amoadd.w";
};
struct AMOXOR_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0010000, RegistersBase::GGG> {
static constexpr const char* name = "amoxor.w";
};
struct AMOAND_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0110000, RegistersBase::GGG> {
static constexpr const char* name = "amoand.w";
};
struct AMOOR_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b0100000, RegistersBase::GGG> {
static constexpr const char* name = "amoor.w";
};
struct AMOMIN_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b1000000, RegistersBase::GGG> {
static constexpr const char* name = "amomin.w";
};
struct AMOMAX_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b1010000, RegistersBase::GGG> {
static constexpr const char* name = "amomax.w";
};
struct AMOMINU_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b1100000, RegistersBase::GGG> {
static constexpr const char* name = "amominu.w";
};
struct AMOMAXU_W : RTypeBaseWithAqRl<Opcode::AMO, 0b010, 0b1110000, RegistersBase::GGG> {
static constexpr const char* name = "amomaxu.w";
};
// RV64A Standard Extension (in addition to RV32A)
struct LR_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0001000, RegistersBase::GGZ> {
static constexpr const char* name = "lr.d";
};
struct SC_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0001100, RegistersBase::GGG> {
static constexpr const char* name = "sc.d";
};
struct AMOSWAP_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0000100, RegistersBase::GGG> {
static constexpr const char* name = "amoswap.d";
};
struct AMOADD_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0000000, RegistersBase::GGG> {
static constexpr const char* name = "amoadd.d";
};
struct AMOXOR_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0010000, RegistersBase::GGG> {
static constexpr const char* name = "amoxor.d";
};
struct AMOAND_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0110000, RegistersBase::GGG> {
static constexpr const char* name = "amoand.d";
};
struct AMOOR_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b0100000, RegistersBase::GGG> {
static constexpr const char* name = "amoor.d";
};
struct AMOMIN_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b1000000, RegistersBase::GGG> {
static constexpr const char* name = "amomin.d";
};
struct AMOMAX_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b1010000, RegistersBase::GGG> {
static constexpr const char* name = "amomax.d";
};
struct AMOMINU_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b1100000, RegistersBase::GGG> {
static constexpr const char* name = "amominu.d";
};
struct AMOMAXU_D : RTypeBaseWithAqRl<Opcode::AMO, 0b011, 0b1110000, RegistersBase::GGG> {
static constexpr const char* name = "amomaxu.d";
};
// RV32F Standard Extension
template<FCVTType ToType, FCVTType FromType>
struct FCVTBase {
static constexpr bool valid = false;
};
template<typename RDRegisterType, typename RS1RegisterType, unsigned rs2, unsigned funct7, typename RegisterTypes>
struct FCVTImpl : RTypeBaseWithRoundingMode<Opcode::OP_FP, funct7, RegisterTypes> {
static constexpr bool valid = true;
using RDType = RDRegisterType;
using RS1Type = RS1RegisterType;
template<typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1, FPRoundingMode rm)
{
static_assert(rs2 < (1 << 5));
return RTypeBaseWithRoundingMode<Opcode::OP_FP, funct7, RegisterTypes>::construct(rd, rs1, RegisterID(rs2), rm);
}
};
template<FMVType ToType, FMVType FromType>
struct FMVBase {
static constexpr bool valid = false;
};
template<typename RDRegisterType, typename RS1RegisterType, unsigned funct7, typename RegisterTypes>
struct FMVImpl : RTypeBase<Opcode::OP_FP, 0b000, funct7, RegisterTypes> {
static constexpr bool valid = true;
using RDType = RDRegisterType;
using RS1Type = RS1RegisterType;
template<typename RDType, typename RS1Type>
static uint32_t construct(RDType rd, RS1Type rs1)
{
return RTypeBase<Opcode::OP_FP, 0b000, funct7, RegisterTypes>::construct(rd, rs1, RegisterID(0));
}
};
struct FLW : ITypeBase<Opcode::LOAD_FP, 0b010, RegistersBase::FG> {
static constexpr const char* name = "flw";
};
struct FSW : STypeBase<Opcode::STORE_FP, 0b010, RegistersBase::GF> {
static constexpr const char* name = "fsw";
};
struct FMADD_S : R4TypeBaseWithRoundingMode<Opcode::MADD, 0b00, RegistersBase::FFFF> {
static constexpr const char* name = "fmadd.s";
};
struct FMSUB_S : R4TypeBaseWithRoundingMode<Opcode::MSUB, 0b00, RegistersBase::FFFF> {
static constexpr const char* name = "fmsub.s";
};
struct FNMSUB_S : R4TypeBaseWithRoundingMode<Opcode::NMSUB, 0b00, RegistersBase::FFFF> {
static constexpr const char* name = "fnmsub.s";
};
struct FNMADD_S : R4TypeBaseWithRoundingMode<Opcode::NMADD, 0b00, RegistersBase::FFFF> {
static constexpr const char* name = "fnmadd.s";
};
struct FADD_S : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0000000, RegistersBase::FFF> {
static constexpr const char* name = "fadd.s";
};
struct FSUB_S : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0000100, RegistersBase::FFF> {
static constexpr const char* name = "fsub.s";
};
struct FMUL_S : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0001000, RegistersBase::FFF> {
static constexpr const char* name = "fmul.s";
};
struct FDIV_S : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0001100, RegistersBase::FFF> {
static constexpr const char* name = "fdiv.s";
};
struct FSQRT_S : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0101100, RegistersBase::FFZ> {
static constexpr const char* name = "fsqrt.s";
};
struct FSGNJ_S : RTypeBase<Opcode::OP_FP, 0b000, 0b0010000, RegistersBase::FFF> {
static constexpr const char* name = "fsgnj.s";
};
struct FSGNJN_S : RTypeBase<Opcode::OP_FP, 0b001, 0b0010000, RegistersBase::FFF> {
static constexpr const char* name = "fsgnjn.s";
};
struct FSGNJX_S : RTypeBase<Opcode::OP_FP, 0b010, 0b0010000, RegistersBase::FFF> {
static constexpr const char* name = "fsgnjx.s";
};
struct FMIN_S : RTypeBase<Opcode::OP_FP, 0b000, 0b0010100, RegistersBase::FFF> {
static constexpr const char* name = "fmin.s";
};
struct FMAX_S : RTypeBase<Opcode::OP_FP, 0b001, 0b0010100, RegistersBase::FFF> {
static constexpr const char* name = "fmax.s";
};
template<>
struct FCVTBase<FCVTType::W, FCVTType::S> : FCVTImpl<RegisterID, FPRegisterID, 0b00000, 0b1100000, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.w.s";
};
using FCVT_W_S = FCVTBase<FCVTType::W, FCVTType::S>;
template<>
struct FCVTBase<FCVTType::WU, FCVTType::S> : FCVTImpl<RegisterID, FPRegisterID, 0b00001, 0b1100000, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.wu.s";
};
using FCVT_WU_S = FCVTBase<FCVTType::WU, FCVTType::S>;
template<>
struct FMVBase<FMVType::X, FMVType::W> : FMVImpl<RegisterID, FPRegisterID, 0b1110000, RegistersBase::GFZ> {
static constexpr const char* name = "fmv.x.w";
};
using FMV_X_W = FMVBase<FMVType::X, FMVType::W>;
struct FEQ_S : RTypeBase<Opcode::OP_FP, 0b010, 0b1010000, RegistersBase::GFF> {
static constexpr const char* name = "feq.s";
};
struct FLT_S : RTypeBase<Opcode::OP_FP, 0b001, 0b1010000, RegistersBase::GFF> {
static constexpr const char* name = "flt.s";
};
struct FLE_S : RTypeBase<Opcode::OP_FP, 0b000, 0b1010000, RegistersBase::GFF> {
static constexpr const char* name = "fle.s";
};
struct FCLASS_S : RTypeBase<Opcode::OP_FP, 0b001, 0b1110000, RegistersBase::GFZ> {
static constexpr const char* name = "fclass.s";
};
template<>
struct FCVTBase<FCVTType::S, FCVTType::W> : FCVTImpl<FPRegisterID, RegisterID, 0b00000, 0b1101000, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.s.w";
};
using FCVT_S_W = FCVTBase<FCVTType::S, FCVTType::W>;
template<>
struct FCVTBase<FCVTType::S, FCVTType::WU> : FCVTImpl<FPRegisterID, RegisterID, 0b00001, 0b1101000, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.s.wu";
};
using FCVT_S_WU = FCVTBase<FCVTType::S, FCVTType::WU>;
template<>
struct FMVBase<FMVType::W, FMVType::X> : FMVImpl<FPRegisterID, RegisterID, 0b1111000, RegistersBase::FGZ> {
static constexpr const char* name = "fmv.w.x";
};
using FMV_W_X = FMVBase<FMVType::W, FMVType::X>;
// RV64F Standard Extension (in addition to RV32F)
template<>
struct FCVTBase<FCVTType::L, FCVTType::S> : FCVTImpl<RegisterID, FPRegisterID, 0b00010, 0b1100000, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.l.s";
};
using FCVT_L_S = FCVTBase<FCVTType::L, FCVTType::S>;
template<>
struct FCVTBase<FCVTType::LU, FCVTType::S> : FCVTImpl<RegisterID, FPRegisterID, 0b00011, 0b1100000, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.lu.s";
};
using FCVT_LU_S = FCVTBase<FCVTType::LU, FCVTType::S>;
template<>
struct FCVTBase<FCVTType::S, FCVTType::L> : FCVTImpl<FPRegisterID, RegisterID, 0b00010, 0b1101000, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.s.l";
};
using FCVT_S_L = FCVTBase<FCVTType::S, FCVTType::L>;
template<>
struct FCVTBase<FCVTType::S, FCVTType::LU> : FCVTImpl<FPRegisterID, RegisterID, 0b00011, 0b1101000, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.s.lu";
};
using FCVT_S_LU = FCVTBase<FCVTType::S, FCVTType::LU>;
// RV32D Standard Extension
struct FLD : ITypeBase<Opcode::LOAD_FP, 0b011, RegistersBase::FG> {
static constexpr const char* name = "fld";
};
struct FSD : STypeBase<Opcode::STORE_FP, 0b011, RegistersBase::GF> {
static constexpr const char* name = "fsd";
};
struct FMADD_D : R4TypeBaseWithRoundingMode<Opcode::MADD, 0b01, RegistersBase::FFFF> {
static constexpr const char* name = "fmadd.d";
};
struct FMSUB_D : R4TypeBaseWithRoundingMode<Opcode::MSUB, 0b01, RegistersBase::FFFF> {
static constexpr const char* name = "fmsub.d";
};
struct FNMSUB_D : R4TypeBaseWithRoundingMode<Opcode::NMSUB, 0b01, RegistersBase::FFFF> {
static constexpr const char* name = "fnmsub.d";
};
struct FNMADD_D : R4TypeBaseWithRoundingMode<Opcode::NMADD, 0b01, RegistersBase::FFFF> {
static constexpr const char* name = "fnmadd.d";
};
struct FADD_D : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0000001, RegistersBase::FFF> {
static constexpr const char* name = "fadd.d";
};
struct FSUB_D : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0000101, RegistersBase::FFF> {
static constexpr const char* name = "fsub.d";
};
struct FMUL_D : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0001001, RegistersBase::FFF> {
static constexpr const char* name = "fmul.d";
};
struct FDIV_D : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0001101, RegistersBase::FFF> {
static constexpr const char* name = "fdiv.d";
};
struct FSQRT_D : RTypeBaseWithRoundingMode<Opcode::OP_FP, 0b0101101, RegistersBase::FFZ> {
static constexpr const char* name = "fsqrt.d";
};
struct FSGNJ_D : RTypeBase<Opcode::OP_FP, 0b000, 0b0010001, RegistersBase::FFF> {
static constexpr const char* name = "fsgnj.d";
};
struct FSGNJN_D : RTypeBase<Opcode::OP_FP, 0b001, 0b0010001, RegistersBase::FFF> {
static constexpr const char* name = "fsgnjn.d";
};
struct FSGNJX_D : RTypeBase<Opcode::OP_FP, 0b010, 0b0010001, RegistersBase::FFF> {
static constexpr const char* name = "fsgnjx.d";
};
struct FMIN_D : RTypeBase<Opcode::OP_FP, 0b000, 0b0010101, RegistersBase::FFF> {
static constexpr const char* name = "fmin.d";
};
struct FMAX_D : RTypeBase<Opcode::OP_FP, 0b001, 0b0010101, RegistersBase::FFF> {
static constexpr const char* name = "fmax.d";
};
template<>
struct FCVTBase<FCVTType::S, FCVTType::D> : FCVTImpl<FPRegisterID, FPRegisterID, 0b00001, 0b0100000, RegistersBase::FFZ> {
static constexpr const char* name = "fcvt.s.d";
};
using FCVT_S_D = FCVTBase<FCVTType::S, FCVTType::D>;
template<>
struct FCVTBase<FCVTType::D, FCVTType::S> : FCVTImpl<FPRegisterID, FPRegisterID, 0b00000, 0b0100001, RegistersBase::FFZ> {
static constexpr const char* name = "fcvt.d.s";
};
using FCVT_D_S = FCVTBase<FCVTType::D, FCVTType::S>;
struct FEQ_D : RTypeBase<Opcode::OP_FP, 0b010, 0b1010001, RegistersBase::GFF> {
static constexpr const char* name = "feq.d";
};
struct FLT_D : RTypeBase<Opcode::OP_FP, 0b001, 0b1010001, RegistersBase::GFF> {
static constexpr const char* name = "flt.d";
};
struct FLE_D : RTypeBase<Opcode::OP_FP, 0b000, 0b1010001, RegistersBase::GFF> {
static constexpr const char* name = "fle.d";
};
struct FCLASS_D : RTypeBase<Opcode::OP_FP, 0b001, 0b1110001, RegistersBase::GFZ> {
static constexpr const char* name = "fclass.d";
};
template<>
struct FCVTBase<FCVTType::W, FCVTType::D> : FCVTImpl<RegisterID, FPRegisterID, 0b00000, 0b1100001, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.w.d";
};
using FCVT_W_D = FCVTBase<FCVTType::W, FCVTType::D>;
template<>
struct FCVTBase<FCVTType::WU, FCVTType::D> : FCVTImpl<RegisterID, FPRegisterID, 0b00001, 0b1100001, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.wu.d";
};
using FCVT_WU_D = FCVTBase<FCVTType::WU, FCVTType::D>;
template<>
struct FCVTBase<FCVTType::D, FCVTType::W> : FCVTImpl<FPRegisterID, RegisterID, 0b00000, 0b1101001, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.d.w";
};
using FCVT_D_W = FCVTBase<FCVTType::D, FCVTType::W>;
template<>
struct FCVTBase<FCVTType::D, FCVTType::WU> : FCVTImpl<FPRegisterID, RegisterID, 0b00001, 0b1101001, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.d.wu";
};
using FCVT_D_WU = FCVTBase<FCVTType::D, FCVTType::WU>;
// RV64D Standard Extension (in addition to RV32D)
template<>
struct FCVTBase<FCVTType::L, FCVTType::D> : FCVTImpl<RegisterID, FPRegisterID, 0b00010, 0b1100001, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.l.d";
};
using FCVT_L_D = FCVTBase<FCVTType::L, FCVTType::D>;
template<>
struct FCVTBase<FCVTType::LU, FCVTType::D> : FCVTImpl<RegisterID, FPRegisterID, 0b00011, 0b1100001, RegistersBase::GFZ> {
static constexpr const char* name = "fcvt.lu.d";
};
using FCVT_LU_D = FCVTBase<FCVTType::LU, FCVTType::D>;
template<>
struct FMVBase<FMVType::X, FMVType::D> : FMVImpl<RegisterID, FPRegisterID, 0b1110001, RegistersBase::GFZ> {
static constexpr const char* name = "fmv.x.d";
};
using FMV_X_D = FMVBase<FMVType::X, FMVType::D>;
template<>
struct FCVTBase<FCVTType::D, FCVTType::L> : FCVTImpl<FPRegisterID, RegisterID, 0b00010, 0b1101001, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.d.l";
};
using FCVT_D_L = FCVTBase<FCVTType::D, FCVTType::L>;
template<>
struct FCVTBase<FCVTType::D, FCVTType::LU> : FCVTImpl<FPRegisterID, RegisterID, 0b00011, 0b1101001, RegistersBase::FGZ> {
static constexpr const char* name = "fcvt.d.lu";
};
using FCVT_D_LU = FCVTBase<FCVTType::D, FCVTType::LU>;
template<>
struct FMVBase<FMVType::D, FMVType::X> : FMVImpl<FPRegisterID, RegisterID, 0b1111001, RegistersBase::FGZ> {
static constexpr const char* name = "fmv.d.x";
};
using FMV_D_X = FMVBase<FMVType::D, FMVType::X>;
} // namespace RISCV64Instructions
class RISCV64Assembler {
public:
using RegisterID = RISCV64Registers::RegisterID;
using SPRegisterID = RISCV64Registers::SPRegisterID;
using FPRegisterID = RISCV64Registers::FPRegisterID;
static constexpr RegisterID firstRegister() { return RISCV64Registers::x0; }
static constexpr RegisterID lastRegister() { return RISCV64Registers::x31; }
static constexpr unsigned numberOfRegisters() { return lastRegister() - firstRegister() + 1; }
static constexpr SPRegisterID firstSPRegister() { return RISCV64Registers::pc; }
static constexpr SPRegisterID lastSPRegister() { return RISCV64Registers::pc; }
static constexpr unsigned numberOfSPRegisters() { return lastSPRegister() - firstSPRegister() + 1; }
static constexpr FPRegisterID firstFPRegister() { return RISCV64Registers::f0; }
static constexpr FPRegisterID lastFPRegister() { return RISCV64Registers::f31; }
static constexpr unsigned numberOfFPRegisters() { return lastFPRegister() - firstFPRegister() + 1; }
static const char* gprName(RegisterID id)
{
ASSERT(id >= firstRegister() && id <= lastRegister());
static const char* const nameForRegister[numberOfRegisters()] = {
#define REGISTER_NAME(id, name, r, cs) name,
FOR_EACH_GP_REGISTER(REGISTER_NAME)
#undef REGISTER_NAME
};
return nameForRegister[id];
}
static const char* sprName(SPRegisterID id)
{
ASSERT(id >= firstSPRegister() && id <= lastSPRegister());
static const char* const nameForRegister[numberOfSPRegisters()] = {
#define REGISTER_NAME(id, name) name,
FOR_EACH_SP_REGISTER(REGISTER_NAME)
#undef REGISTER_NAME
};
return nameForRegister[id];
}
static const char* fprName(FPRegisterID id)
{
ASSERT(id >= firstFPRegister() && id <= lastFPRegister());
static const char* const nameForRegister[numberOfFPRegisters()] = {
#define REGISTER_NAME(id, name, r, cs) name,
FOR_EACH_FP_REGISTER(REGISTER_NAME)
#undef REGISTER_NAME
};
return nameForRegister[id];
}
RISCV64Assembler() { }
AssemblerBuffer& buffer() { return m_buffer; }
static void* getRelocatedAddress(void* code, AssemblerLabel label)
{
ASSERT(label.isSet());
return reinterpret_cast<void*>(reinterpret_cast<ptrdiff_t>(code) + label.offset());
}
static int getDifferenceBetweenLabels(AssemblerLabel a, AssemblerLabel b)
{
return b.offset() - a.offset();
}
size_t codeSize() const { return m_buffer.codeSize(); }
static unsigned getCallReturnOffset(AssemblerLabel call)
{
ASSERT(call.isSet());
return call.offset();
}
AssemblerLabel labelIgnoringWatchpoints()
{
return m_buffer.label();
}
AssemblerLabel labelForWatchpoint()
{
AssemblerLabel result = m_buffer.label();
if (static_cast<int>(result.offset()) != m_indexOfLastWatchpoint)
result = label();
m_indexOfLastWatchpoint = result.offset();
m_indexOfTailOfLastWatchpoint = result.offset() + maxJumpReplacementSize();
return result;
}
AssemblerLabel label()
{
AssemblerLabel result = m_buffer.label();
while (UNLIKELY(static_cast<int>(result.offset()) < m_indexOfTailOfLastWatchpoint)) {
nop();
result = m_buffer.label();
}
return result;
}
static void linkJump(void* code, AssemblerLabel from, void* to)
{
ASSERT(from.isSet());
if (!from.isSet())
return;
uint32_t* location = reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(code) + from.offset());
if (location[0] == LinkJumpImpl::placeholderInsn()) {
LinkJumpImpl::apply(location, to);
return;
}
if (location[0] == LinkBranchImpl::placeholderInsn()) {
LinkBranchImpl::apply(location, to);
return;
}
}
static void linkCall(void* code, AssemblerLabel from, void* to)
{
uint32_t* location = reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(code) + from.offset());
RELEASE_ASSERT(location[0] == LinkCallImpl::placeholderInsn());
LinkCallImpl::apply(location, to);
}
static void linkPointer(void* code, AssemblerLabel where, void* valuePtr)
{
uint32_t* location = reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(code) + where.offset());
PatchPointerImpl::apply(location, valuePtr);
}
void linkJump(AssemblerLabel from, AssemblerLabel to)
{
RELEASE_ASSERT(from.isSet() && to.isSet());
if (!from.isSet() || !to.isSet())
return;
uint32_t* location = reinterpret_cast<uint32_t*>(reinterpret_cast<uintptr_t>(m_buffer.data()) + to.offset());
linkJump(m_buffer.data(), from, location);
}
static ptrdiff_t maxJumpReplacementSize()
{
return sizeof(uint32_t) * 8;
}
static constexpr ptrdiff_t patchableJumpSize()
{
return sizeof(uint32_t) * 8;
}
static void repatchPointer(void* where, void* valuePtr)
{
uint32_t* location = reinterpret_cast<uint32_t*>(where);
PatchPointerImpl::apply(location, valuePtr);
cacheFlush(location, sizeof(uint32_t) * 8);
}
static void relinkJump(void* from, void* to)
{
uint32_t* location = reinterpret_cast<uint32_t*>(from);
LinkJumpImpl::apply(location, to);
cacheFlush(location, sizeof(uint32_t) * 2);
}
static void relinkCall(void* from, void* to)
{
uint32_t* location = reinterpret_cast<uint32_t*>(from);
LinkCallImpl::apply(location, to);
cacheFlush(location, sizeof(uint32_t) * 2);
}
static void relinkTailCall(void* from, void* to)
{
relinkJump(from, to);
}
static void replaceWithVMHalt(void* where)
{
uint32_t* location = reinterpret_cast<uint32_t*>(where);
location[0] = RISCV64Instructions::SD::construct(RISCV64Registers::zero, RISCV64Registers::zero, SImmediate::v<SImmediate, 0>());
cacheFlush(location, sizeof(uint32_t));
}
static void replaceWithJump(void* from, void* to)
{
uint32_t* location = reinterpret_cast<uint32_t*>(from);
intptr_t offset = uintptr_t(to) - uintptr_t(from);
if (JImmediate::isValid(offset)) {
location[0] = RISCV64Instructions::JAL::construct(RISCV64Registers::zero, JImmediate::v<JImmediate>(offset));
cacheFlush(from, sizeof(uint32_t));
return;
}
RELEASE_ASSERT(ImmediateBase<32>::isValid(offset));
RISCV64Instructions::ImmediateDecomposition immediate(offset);
location[0] = RISCV64Instructions::AUIPC::construct(RISCV64Registers::x30, immediate.upper);
location[1] = RISCV64Instructions::JALR::construct(RISCV64Registers::zero, RISCV64Registers::x30, immediate.lower);
cacheFlush(from, sizeof(uint32_t) * 2);
}
static void revertJumpReplacementToPatch(void* from, void* valuePtr)
{
uint32_t* location = reinterpret_cast<uint32_t*>(from);
PatchPointerImpl::apply(location, RISCV64Registers::x30, valuePtr);
cacheFlush(location, sizeof(uint32_t) * 8);
}
static void* readCallTarget(void* from)
{
uint32_t* location = reinterpret_cast<uint32_t*>(from);
return PatchPointerImpl::read(location);
}
unsigned debugOffset() { return m_buffer.debugOffset(); }
static void cacheFlush(void* code, size_t size)
{
intptr_t end = reinterpret_cast<intptr_t>(code) + size;
__builtin___clear_cache(reinterpret_cast<char*>(code), reinterpret_cast<char*>(end));
}
using CopyFunction = void*(&)(void*, const void*, size_t);
template <CopyFunction copy>
static void fillNops(void* base, size_t size)
{
uint32_t* ptr = reinterpret_cast<uint32_t*>(base);
RELEASE_ASSERT(roundUpToMultipleOf<sizeof(uint32_t)>(ptr) == ptr);
RELEASE_ASSERT(!(size % sizeof(uint32_t)));
uint32_t nop = RISCV64Instructions::ADDI::construct(RISCV64Registers::zero, RISCV64Registers::zero, IImmediate::v<IImmediate, 0>());
for (size_t i = 0, n = size / sizeof(uint32_t); i < n; ++i)
copy(&ptr[i], &nop, sizeof(uint32_t));
}
typedef enum {
ConditionEQ,
ConditionNE,
ConditionGTU,
ConditionLEU,
ConditionGEU,
ConditionLTU,
ConditionGT,
ConditionLE,
ConditionGE,
ConditionLT,
} Condition;
static constexpr Condition invert(Condition cond)
{
return static_cast<Condition>(cond ^ 1);
}
template<unsigned immediateSize> using ImmediateBase = RISCV64Instructions::ImmediateBase<immediateSize>;
using IImmediate = RISCV64Instructions::IImmediate;
using SImmediate = RISCV64Instructions::SImmediate;
using BImmediate = RISCV64Instructions::BImmediate;
using UImmediate = RISCV64Instructions::UImmediate;
using JImmediate = RISCV64Instructions::JImmediate;
void luiInsn(RegisterID rd, UImmediate imm) { insn(RISCV64Instructions::LUI::construct(rd, imm)); }
void auipcInsn(RegisterID rd, UImmediate imm) { insn(RISCV64Instructions::AUIPC::construct(rd, imm)); }
void jalInsn(RegisterID rd, JImmediate imm) { insn(RISCV64Instructions::JAL::construct(rd, imm)); }
void jalrInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::JALR::construct(rd, rs1, imm)); }
void beqInsn(RegisterID rs1, RegisterID rs2, BImmediate imm) { insn(RISCV64Instructions::BEQ::construct(rs1, rs2, imm)); }
void bneInsn(RegisterID rs1, RegisterID rs2, BImmediate imm) { insn(RISCV64Instructions::BNE::construct(rs1, rs2, imm)); }
void bltInsn(RegisterID rs1, RegisterID rs2, BImmediate imm) { insn(RISCV64Instructions::BLT::construct(rs1, rs2, imm)); }
void bgeInsn(RegisterID rs1, RegisterID rs2, BImmediate imm) { insn(RISCV64Instructions::BGE::construct(rs1, rs2, imm)); }
void bltuInsn(RegisterID rs1, RegisterID rs2, BImmediate imm) { insn(RISCV64Instructions::BLTU::construct(rs1, rs2, imm)); }
void bgeuInsn(RegisterID rs1, RegisterID rs2, BImmediate imm) { insn(RISCV64Instructions::BGEU::construct(rs1, rs2, imm)); }
void lbInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LB::construct(rd, rs1, imm)); }
void lhInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LH::construct(rd, rs1, imm)); }
void lwInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LW::construct(rd, rs1, imm)); }
void ldInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LD::construct(rd, rs1, imm)); }
void lbuInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LBU::construct(rd, rs1, imm)); }
void lhuInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LHU::construct(rd, rs1, imm)); }
void lwuInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::LWU::construct(rd, rs1, imm)); }
void sbInsn(RegisterID rs1, RegisterID rs2, SImmediate imm) { insn(RISCV64Instructions::SB::construct(rs1, rs2, imm)); }
void shInsn(RegisterID rs1, RegisterID rs2, SImmediate imm) { insn(RISCV64Instructions::SH::construct(rs1, rs2, imm)); }
void swInsn(RegisterID rs1, RegisterID rs2, SImmediate imm) { insn(RISCV64Instructions::SW::construct(rs1, rs2, imm)); }
void sdInsn(RegisterID rs1, RegisterID rs2, SImmediate imm) { insn(RISCV64Instructions::SD::construct(rs1, rs2, imm)); }
void addiInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::ADDI::construct(rd, rs1, imm)); }
void sltiInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::SLTI::construct(rd, rs1, imm)); }
void sltiuInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::SLTIU::construct(rd, rs1, imm)); }
void xoriInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::XORI::construct(rd, rs1, imm)); }
void oriInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::ORI::construct(rd, rs1, imm)); }
void andiInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::ANDI::construct(rd, rs1, imm)); }
void slliInsn(RegisterID rd, RegisterID rs1, uint32_t shamt)
{
ASSERT(isValidShiftAmount<6>(shamt));
insn(RISCV64Instructions::SLLI::construct(rd, rs1, IImmediate((0b000000 << 6) | shamt)));
}
void srliInsn(RegisterID rd, RegisterID rs1, uint32_t shamt)
{
ASSERT(isValidShiftAmount<6>(shamt));
insn(RISCV64Instructions::SRLI::construct(rd, rs1, IImmediate((0b000000 << 6) | shamt)));
}
void sraiInsn(RegisterID rd, RegisterID rs1, uint32_t shamt)
{
ASSERT(isValidShiftAmount<6>(shamt));
insn(RISCV64Instructions::SRAI::construct(rd, rs1, IImmediate((0b010000 << 6) | shamt)));
}
void addInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::ADD::construct(rd, rs1, rs2)); }
void subInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SUB::construct(rd, rs1, rs2)); }
void sllInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SLL::construct(rd, rs1, rs2)); }
void sltInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SLT::construct(rd, rs1, rs2)); }
void sltuInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SLTU::construct(rd, rs1, rs2)); }
void xorInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::XOR::construct(rd, rs1, rs2)); }
void srlInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SRL::construct(rd, rs1, rs2)); }
void sraInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SRA::construct(rd, rs1, rs2)); }
void orInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::OR::construct(rd, rs1, rs2)); }
void andInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::AND::construct(rd, rs1, rs2)); }
void ecallInsn() { insn(RISCV64Instructions::ECALL::construct(RegisterID::zero, RegisterID::zero, IImmediate::v<IImmediate, 0>())); }
void ebreakInsn() { insn(RISCV64Instructions::EBREAK::construct(RegisterID::zero, RegisterID::zero, IImmediate::v<IImmediate, 1>())); }
void addiwInsn(RegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::ADDIW::construct(rd, rs1, imm)); }
void slliwInsn(RegisterID rd, RegisterID rs1, uint32_t shamt)
{
ASSERT(isValidShiftAmount<5>(shamt));
insn(RISCV64Instructions::SLLIW::construct(rd, rs1, IImmediate((0b0000000 << 5) | shamt)));
}
void srliwInsn(RegisterID rd, RegisterID rs1, uint32_t shamt)
{
ASSERT(isValidShiftAmount<5>(shamt));
insn(RISCV64Instructions::SRLIW::construct(rd, rs1, IImmediate((0b0000000 << 5) | shamt)));
}
void sraiwInsn(RegisterID rd, RegisterID rs1, uint32_t shamt)
{
ASSERT(isValidShiftAmount<5>(shamt));
insn(RISCV64Instructions::SRAIW::construct(rd, rs1, IImmediate((0b0100000 << 5) | shamt)));
}
void addwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::ADDW::construct(rd, rs1, rs2)); }
void subwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SUBW::construct(rd, rs1, rs2)); }
void sllwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SLLW::construct(rd, rs1, rs2)); }
void srlwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SRLW::construct(rd, rs1, rs2)); }
void srawInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::SRAW::construct(rd, rs1, rs2)); }
void mulInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::MUL::construct(rd, rs1, rs2)); }
void mulhInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::MULH::construct(rd, rs1, rs2)); }
void mulhsuInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::MULHSU::construct(rd, rs1, rs2)); }
void mulhuInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::MULHU::construct(rd, rs1, rs2)); }
void divInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::DIV::construct(rd, rs1, rs2)); }
void divuInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::DIVU::construct(rd, rs1, rs2)); }
void remInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::REM::construct(rd, rs1, rs2)); }
void remuInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::REMU::construct(rd, rs1, rs2)); }
void mulwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::MULW::construct(rd, rs1, rs2)); }
void divwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::DIVW::construct(rd, rs1, rs2)); }
void divuwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::DIVUW::construct(rd, rs1, rs2)); }
void remwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::REMW::construct(rd, rs1, rs2)); }
void remuwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2) { insn(RISCV64Instructions::REMUW::construct(rd, rs1, rs2)); }
using FCVTType = RISCV64Instructions::FCVTType;
using FMVType = RISCV64Instructions::FMVType;
using FPRoundingMode = RISCV64Instructions::FPRoundingMode;
void flwInsn(FPRegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::FLW::construct(rd, rs1, imm)); }
void fldInsn(FPRegisterID rd, RegisterID rs1, IImmediate imm) { insn(RISCV64Instructions::FLD::construct(rd, rs1, imm)); }
void fswInsn(RegisterID rs1, FPRegisterID rs2, SImmediate imm) { insn(RISCV64Instructions::FSW::construct(rs1, rs2, imm)); }
void fsdInsn(RegisterID rs1, FPRegisterID rs2, SImmediate imm) { insn(RISCV64Instructions::FSD::construct(rs1, rs2, imm)); }
template<unsigned fpSize>
void fmaddInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2, FPRegisterID rs3)
{
insnFP<fpSize, RISCV64Instructions::FMADD_S, RISCV64Instructions::FMADD_D>(rd, rs1, rs2, rs3, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fmsubInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2, FPRegisterID rs3)
{
insnFP<fpSize, RISCV64Instructions::FMSUB_S, RISCV64Instructions::FMSUB_D>(rd, rs1, rs2, rs3, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fnmsubInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2, FPRegisterID rs3)
{
insnFP<fpSize, RISCV64Instructions::FNMSUB_S, RISCV64Instructions::FNMSUB_D>(rd, rs1, rs2, rs3, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fnmaddInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2, FPRegisterID rs3)
{
insnFP<fpSize, RISCV64Instructions::FNMADD_S, RISCV64Instructions::FNMADD_D>(rd, rs1, rs2, rs3, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void faddInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FADD_S, RISCV64Instructions::FADD_D>(rd, rs1, rs2, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fsubInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FSUB_S, RISCV64Instructions::FSUB_D>(rd, rs1, rs2, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fmulInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FMUL_S, RISCV64Instructions::FMUL_D>(rd, rs1, rs2, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fdivInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FDIV_S, RISCV64Instructions::FDIV_D>(rd, rs1, rs2, FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fsqrtInsn(FPRegisterID rd, FPRegisterID rs1)
{
insnFP<fpSize, RISCV64Instructions::FSQRT_S, RISCV64Instructions::FSQRT_D>(rd, rs1, FPRegisterID(0), FPRoundingMode::DYN);
}
template<unsigned fpSize>
void fsgnjInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FSGNJ_S, RISCV64Instructions::FSGNJ_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fsgnjnInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FSGNJN_S, RISCV64Instructions::FSGNJN_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fsgnjxInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FSGNJX_S, RISCV64Instructions::FSGNJX_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fminInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FMIN_S, RISCV64Instructions::FMIN_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fmaxInsn(FPRegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FMAX_S, RISCV64Instructions::FMAX_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void feqInsn(RegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FEQ_S, RISCV64Instructions::FEQ_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fltInsn(RegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FLT_S, RISCV64Instructions::FLT_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fleInsn(RegisterID rd, FPRegisterID rs1, FPRegisterID rs2)
{
insnFP<fpSize, RISCV64Instructions::FLE_S, RISCV64Instructions::FLE_D>(rd, rs1, rs2);
}
template<unsigned fpSize>
void fclassInsn(RegisterID rd, FPRegisterID rs1)
{
insnFP<fpSize, RISCV64Instructions::FCLASS_S, RISCV64Instructions::FCLASS_D>(rd, rs1, FPRegisterID(0));
}
template<FPRoundingMode RM, FCVTType ToType, FCVTType FromType, typename RDType, typename RS1Type>
void fcvtInsn(RDType rd, RS1Type rs1)
{
using FCVTType = RISCV64Instructions::FCVTBase<ToType, FromType>;
static_assert(FCVTType::valid);
static_assert(std::is_same_v<std::decay_t<RDType>, typename FCVTType::RDType>);
static_assert(std::is_same_v<std::decay_t<RS1Type>, typename FCVTType::RS1Type>);
insn(FCVTType::construct(rd, rs1, RM));
}
template<FCVTType ToType, FCVTType FromType, typename RDType, typename RS1Type>
void fcvtInsn(RDType rd, RS1Type rs1)
{
fcvtInsn<FPRoundingMode::DYN, ToType, FromType, RDType, RS1Type>(rd, rs1);
}
template<FMVType ToType, FMVType FromType, typename RDType, typename RS1Type>
void fmvInsn(RDType rd, RS1Type rs1)
{
using FMVType = RISCV64Instructions::FMVBase<ToType, FromType>;
static_assert(FMVType::valid);
static_assert(std::is_same_v<std::decay_t<RDType>, typename FMVType::RDType>);
static_assert(std::is_same_v<std::decay_t<RS1Type>, typename FMVType::RS1Type>);
insn(FMVType::construct(rd, rs1));
}
using MemoryOperation = RISCV64Instructions::MemoryOperation;
using MemoryAccess = RISCV64Instructions::MemoryAccess;
void fenceInsn(std::initializer_list<MemoryOperation> predecessor, std::initializer_list<MemoryOperation> successor)
{
unsigned predecessorValue = 0;
for (auto& op : predecessor)
predecessorValue |= unsigned(op);
unsigned successorValue = 0;
for (auto& op : successor)
successorValue |= unsigned(op);
uint32_t immediate = 0
| (0b0000 << 8)
| ((predecessorValue & ((1 << 4) - 1)) << 4)
| ((successorValue & ((1 << 4) - 1)) << 0);
insn(RISCV64Instructions::FENCE::construct(RISCV64Registers::zero, RISCV64Registers::zero, IImmediate(immediate)));
}
void lrwInsn(RegisterID rd, RegisterID rs1, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::LR_W::construct(rd, rs1, RISCV64Registers::zero, access));
}
void scwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::SC_W::construct(rd, rs1, rs2, access));
}
void lrdInsn(RegisterID rd, RegisterID rs1, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::LR_D::construct(rd, rs1, RISCV64Registers::zero, access));
}
void scdInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::SC_D::construct(rd, rs1, rs2, access));
}
void amoswapwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOSWAP_W::construct(rd, rs1, rs2, access));
}
void amoaddwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOADD_W::construct(rd, rs1, rs2, access));
}
void amoxorwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOXOR_W::construct(rd, rs1, rs2, access));
}
void amoandwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOAND_W::construct(rd, rs1, rs2, access));
}
void amoorwInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOOR_W::construct(rd, rs1, rs2, access));
}
void amoswapdInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOSWAP_D::construct(rd, rs1, rs2, access));
}
void amoadddInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOADD_D::construct(rd, rs1, rs2, access));
}
void amoxordInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOXOR_D::construct(rd, rs1, rs2, access));
}
void amoanddInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOAND_D::construct(rd, rs1, rs2, access));
}
void amoordInsn(RegisterID rd, RegisterID rs1, RegisterID rs2, std::initializer_list<MemoryAccess> access)
{
insn(RISCV64Instructions::AMOOR_D::construct(rd, rs1, rs2, access));
}
template<unsigned shiftAmount>
void slliInsn(RegisterID rd, RegisterID rs1)
{
insn(RISCV64Instructions::SLLI::construct<shiftAmount>(rd, rs1));
}
template<unsigned shiftAmount>
void srliInsn(RegisterID rd, RegisterID rs1)
{
insn(RISCV64Instructions::SRLI::construct<shiftAmount>(rd, rs1));
}
template<unsigned shiftAmount>
void sraiInsn(RegisterID rd, RegisterID rs1)
{
insn(RISCV64Instructions::SRAI::construct<shiftAmount>(rd, rs1));
}
template<unsigned shiftAmount>
void slliwInsn(RegisterID rd, RegisterID rs1)
{
insn(RISCV64Instructions::SLLIW::construct<shiftAmount>(rd, rs1));
}
template<unsigned shiftAmount>
void srliwInsn(RegisterID rd, RegisterID rs1)
{
insn(RISCV64Instructions::SRLIW::construct<shiftAmount>(rd, rs1));
}
template<unsigned shiftAmount>
void sraiwInsn(RegisterID rd, RegisterID rs1)
{
insn(RISCV64Instructions::SRAIW::construct<shiftAmount>(rd, rs1));
}
void nop()
{
addiInsn(RISCV64Registers::zero, RISCV64Registers::zero, IImmediate::v<IImmediate, 0>());
}
template<unsigned maskSize>
void maskRegister(RegisterID rd, RegisterID rs)
{
static_assert(maskSize < 64);
slliInsn<64 - maskSize>(rd, rs);
srliInsn<64 - maskSize>(rd, rd);
}
template<unsigned maskSize>
void maskRegister(RegisterID rd)
{
maskRegister<maskSize>(rd, rd);
}
template<unsigned bitSize>
void signExtend(RegisterID rd)
{
signExtend<bitSize>(rd, rd);
}
template<unsigned bitSize, typename = std::enable_if_t<bitSize == 8 || bitSize == 16 || bitSize == 32 || bitSize == 64>>
void signExtend(RegisterID rd, RegisterID rs)
{
if constexpr (bitSize == 64)
return;
if constexpr (bitSize == 32) {
addiwInsn(rd, rs, IImmediate::v<IImmediate, 0>());
return;
}
slliInsn<64 - bitSize>(rd, rs);
sraiInsn<64 - bitSize>(rd, rd);
}
template<unsigned bitSize>
void zeroExtend(RegisterID rd)
{
zeroExtend<bitSize>(rd, rd);
}
template<unsigned bitSize, typename = std::enable_if_t<bitSize == 8 || bitSize == 16 || bitSize == 32 || bitSize == 64>>
void zeroExtend(RegisterID rd, RegisterID rs)
{
if constexpr (bitSize == 64)
return;
slliInsn<64 - bitSize>(rd, rs);
srliInsn<64 - bitSize>(rd, rd);
}
template<typename F>
void jumpPlaceholder(const F& functor)
{
LinkJumpImpl::generatePlaceholder(*this, functor);
}
template<typename F>
void branchPlaceholder(const F& functor)
{
LinkBranchImpl::generatePlaceholder(*this, functor);
}
template<typename F>
void pointerCallPlaceholder(const F& functor)
{
PatchPointerImpl::generatePlaceholder(*this, functor);
}
template<typename F>
void nearCallPlaceholder(const F& functor)
{
LinkCallImpl::generatePlaceholder(*this, functor);
}
struct ImmediateLoader {
enum PlaceholderTag { Placeholder };
ImmediateLoader(int32_t imm)
: ImmediateLoader(int64_t(imm))
{ }
ImmediateLoader(PlaceholderTag, int32_t imm)
: ImmediateLoader(Placeholder, int64_t(imm))
{ }
ImmediateLoader(int64_t imm)
{
// If the immediate value fits into the IImmediate mold, we can short-cut to just generating that through a single ADDI.
if (IImmediate::isValid(imm)) {
m_ops[m_opCount++] = { Op::Type::IImmediate, IImmediate::v<IImmediate>(imm).imm };
return;
}
// The immediate is larger than 12 bits, so it has to be loaded through the initial LUI and then additional shift-and-addi pairs.
// This sequence is generated in reverse. moveInto() or other users traverse the sequence accordingly.
int64_t value = imm;
while (true) {
uint32_t addiImm = value & ((1 << 12) - 1);
// The addi will be sign-extending the 12-bit value and adding it to the register-contained value. If the addi-immediate
// is negative, the remaining immediate has to be increased by 2^12 to offset the subsequent subtraction.
if (addiImm & (1 << 11))
value += (1 << 12);
m_ops[m_opCount++] = { Op::Type::ADDI, addiImm };
// Shift out the bits incorporated into the just-added addi.
value = value >> 12;
// If the remainder of the immediate can fit into a 20-bit immediate, we can generate the LUI instruction that will end up
// loading the initial higher bits of the desired immediate.
if (ImmediateBase<20>::isValid(value)) {
m_ops[m_opCount++] = { Op::Type::LUI, uint32_t((value & ((1 << 20) - 1)) << 12) };
return;
}
// Otherwise, generate the lshift operation that will make room for lower parts of the immediate value.
m_ops[m_opCount++] = { Op::Type::LSHIFT12, 0 };
}
}
ImmediateLoader(PlaceholderTag, int64_t imm)
: ImmediateLoader(imm)
{
// The non-placeholder constructor already generated the necessary operations to load this immediate.
// This constructor still fills out the remaining potential operations as nops. This enables future patching
// of these instructions with other immediate-load sequences.
for (unsigned i = m_opCount; i < m_ops.size(); ++i)
m_ops[i] = { Op::Type::NOP, 0 };
m_opCount = m_ops.size();
}
void moveInto(RISCV64Assembler& assembler, RegisterID dest)
{
// This is a helper method that generates the necessary instructions through the RISCV64Assembler infrastructure.
// Operations are traversed in reverse in order to match the generation process.
for (unsigned i = 0; i < m_opCount; ++i) {
auto& op = m_ops[m_opCount - (i + 1)];
switch (op.type) {
case Op::Type::IImmediate:
assembler.addiInsn(dest, RISCV64Registers::zero, IImmediate(op.value));
break;
case Op::Type::LUI:
assembler.luiInsn(dest, UImmediate(op.value));
break;
case Op::Type::ADDI:
assembler.addiInsn(dest, dest, IImmediate(op.value));
break;
case Op::Type::LSHIFT12:
assembler.slliInsn<12>(dest, dest);
break;
case Op::Type::NOP:
assembler.addiInsn(RISCV64Registers::zero, RISCV64Registers::zero, IImmediate::v<IImmediate, 0>());
break;
}
}
}
struct Op {
enum class Type {
IImmediate,
LUI,
ADDI,
LSHIFT12,
NOP,
};
Type type;
uint32_t value;
};
std::array<Op, 8> m_ops;
unsigned m_opCount { 0 };
};
protected:
void insn(uint32_t instruction)
{
m_buffer.putInt(instruction);
}
template<unsigned fpSize, typename FP32Type, typename FP64Type, typename... Args>
void insnFP(Args&&... args)
{
static_assert(fpSize == 32 || fpSize == 64);
using InstructionType = std::conditional_t<(fpSize == 32), FP32Type, FP64Type>;
insn(InstructionType::construct(std::forward<Args>(args)...));
}
template<unsigned shiftBitsize>
static constexpr bool isValidShiftAmount(unsigned amount)
{
return amount < (1 << shiftBitsize);
}
struct LinkJumpOrCallImpl {
static void apply(uint32_t* location, void* target)
{
RISCV64Instructions::InstructionValue instruction(location[1]);
RELEASE_ASSERT(instruction.opcode() == unsigned(RISCV64Instructions::Opcode::JALR) || instruction.opcode() == unsigned(RISCV64Instructions::Opcode::JAL));
auto destination = RegisterID(instruction.field<7, 5>());
intptr_t offset = uintptr_t(target) - uintptr_t(&location[1]);
if (JImmediate::isValid(offset)) {
location[0] = RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
location[1] = RISCV64Instructions::JAL::construct(destination, JImmediate::v<JImmediate>(offset));
return;
}
offset += sizeof(uint32_t);
RELEASE_ASSERT(ImmediateBase<32>::isValid(offset));
RISCV64Instructions::ImmediateDecomposition immediate(offset);
location[0] = RISCV64Instructions::AUIPC::construct(RISCV64Registers::x30, immediate.upper);
location[1] = RISCV64Instructions::JALR::construct(destination, RISCV64Registers::x30, immediate.lower);
}
};
struct LinkJumpImpl : LinkJumpOrCallImpl {
static constexpr unsigned PlaceholderValue = 1;
static uint32_t placeholderInsn()
{
return RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, PlaceholderValue>());
}
template<typename F>
static void generatePlaceholder(RISCV64Assembler& assembler, const F& functor)
{
assembler.insn(placeholderInsn());
functor();
}
};
struct LinkCallImpl : LinkJumpOrCallImpl {
static constexpr unsigned PlaceholderValue = 2;
static uint32_t placeholderInsn()
{
return RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, PlaceholderValue>());
}
template<typename F>
static void generatePlaceholder(RISCV64Assembler& assembler, const F& functor)
{
assembler.insn(placeholderInsn());
functor();
}
};
struct LinkBranchImpl {
static constexpr unsigned PlaceholderValue = 3;
static uint32_t placeholderInsn()
{
return RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, PlaceholderValue>());
}
template<typename F>
static void generatePlaceholder(RISCV64Assembler& assembler, const F& functor)
{
auto insnValue = placeholderInsn();
for (unsigned i = 0; i < 2; ++i)
assembler.insn(insnValue);
functor();
}
static void apply(uint32_t* location, void* target)
{
RISCV64Instructions::InstructionValue instruction(location[2]);
RELEASE_ASSERT(instruction.opcode() == unsigned(RISCV64Instructions::Opcode::BRANCH));
auto branchInstructionForFunct3 =
[](uint32_t funct3, RegisterID rs1, RegisterID rs2, BImmediate imm)
{
switch (funct3) {
case RISCV64Instructions::BEQ::funct3Value:
return RISCV64Instructions::BEQ::construct(rs1, rs2, imm);
case RISCV64Instructions::BNE::funct3Value:
return RISCV64Instructions::BNE::construct(rs1, rs2, imm);
case RISCV64Instructions::BLT::funct3Value:
return RISCV64Instructions::BLT::construct(rs1, rs2, imm);
case RISCV64Instructions::BGE::funct3Value:
return RISCV64Instructions::BGE::construct(rs1, rs2, imm);
case RISCV64Instructions::BLTU::funct3Value:
return RISCV64Instructions::BLTU::construct(rs1, rs2, imm);
case RISCV64Instructions::BGEU::funct3Value:
return RISCV64Instructions::BGEU::construct(rs1, rs2, imm);
default:
break;
}
RELEASE_ASSERT_NOT_REACHED();
return RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
};
auto lhs = RegisterID(instruction.field<15, 5>());
auto rhs = RegisterID(instruction.field<20, 5>());
intptr_t offset = uintptr_t(target) - uintptr_t(&location[2]);
if (BImmediate::isValid(offset)) {
location[0] = RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
location[1] = RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
location[2] = branchInstructionForFunct3(instruction.field<12, 3>(), lhs, rhs, BImmediate::v<BImmediate>(offset));
return;
}
if (JImmediate::isValid(offset)) {
location[0] = RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
location[1] = branchInstructionForFunct3(instruction.field<12, 3>() ^ 0b001, lhs, rhs, BImmediate::v<BImmediate, 8>());
location[2] = RISCV64Instructions::JAL::construct(RISCV64Registers::x0, JImmediate::v<JImmediate>(offset));
return;
}
offset += sizeof(uint32_t);
RELEASE_ASSERT(ImmediateBase<32>::isValid(offset));
RISCV64Instructions::ImmediateDecomposition immediate(offset);
location[0] = branchInstructionForFunct3(instruction.field<12, 3>() ^ 0b001, lhs, rhs, BImmediate::v<BImmediate, 12>());
location[1] = RISCV64Instructions::AUIPC::construct(RISCV64Registers::x31, immediate.upper);
location[2] = RISCV64Instructions::JALR::construct(RISCV64Registers::x0, RISCV64Registers::x31, immediate.lower);
}
};
struct PatchPointerImpl {
static constexpr unsigned PlaceholderValue = 4;
static uint32_t placeholderInsn()
{
return RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, PlaceholderValue>());
}
template<typename F>
static void generatePlaceholder(RISCV64Assembler& assembler, const F& functor)
{
auto insnValue = placeholderInsn();
for (unsigned i = 0; i < 7; ++i)
assembler.insn(insnValue);
functor();
}
static void apply(uint32_t* location, void* value)
{
RISCV64Instructions::InstructionValue instruction(location[7]);
// RELEASE_ASSERT, being a macro, gets confused by the comma-separated template parameters.
bool validLocation = instruction.opcode() == unsigned(RISCV64Instructions::Opcode::OP_IMM) && instruction.field<12, 3>() == 0b000;
RELEASE_ASSERT(validLocation);
apply(location, RegisterID(instruction.field<7, 5>()), value);
}
static void apply(uint32_t* location, RegisterID destination, void* value)
{
using ImmediateLoader = RISCV64Assembler::ImmediateLoader;
ImmediateLoader imml(ImmediateLoader::Placeholder, reinterpret_cast<intptr_t>(value));
RELEASE_ASSERT(imml.m_opCount == 8);
for (unsigned i = 0; i < imml.m_opCount; ++i) {
auto& op = imml.m_ops[imml.m_opCount - (i + 1)];
switch (op.type) {
case ImmediateLoader::Op::Type::IImmediate:
location[i] = RISCV64Instructions::ADDI::construct(destination, RISCV64Registers::zero, IImmediate(op.value));
break;
case ImmediateLoader::Op::Type::LUI:
location[i] = RISCV64Instructions::LUI::construct(destination, UImmediate(op.value));
break;
case ImmediateLoader::Op::Type::ADDI:
location[i] = RISCV64Instructions::ADDI::construct(destination, destination, IImmediate(op.value));
break;
case ImmediateLoader::Op::Type::LSHIFT12:
location[i] = RISCV64Instructions::SLLI::construct<12>(destination, destination);
break;
case ImmediateLoader::Op::Type::NOP:
location[i] = RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
break;
}
}
}
static void* read(uint32_t* location)
{
RISCV64Instructions::InstructionValue instruction(location[7]);
// RELEASE_ASSERT, being a macro, gets confused by the comma-separated template parameters.
bool validLocation = instruction.opcode() == unsigned(RISCV64Instructions::Opcode::OP_IMM) && instruction.field<12, 3>() == 0b000;
RELEASE_ASSERT(validLocation);
auto dest = RegisterID(instruction.field<7, 5>());
unsigned i = 0;
{
// Iterate through all ImmediateLoader::Op::Type::NOP instructions generated for the purposes of the placeholder.
uint32_t nopInsn = RISCV64Instructions::ADDI::construct(RISCV64Registers::x0, RISCV64Registers::x0, IImmediate::v<IImmediate, 0>());
for (; i < 8; ++i) {
if (location[i] != nopInsn)
break;
}
}
intptr_t target = 0;
for (; i < 8; ++i) {
RISCV64Instructions::InstructionValue insn(location[i]);
// Counterpart to ImmediateLoader::Op::Type::LUI.
if (insn.opcode() == unsigned(RISCV64Instructions::Opcode::LUI) && insn.field<7, 5>() == unsigned(dest)) {
target = int32_t(UImmediate::value(insn));
continue;
}
// Counterpart to ImmediateLoader::Op::Type::{IImmediate, ADDI}.
if (insn.opcode() == unsigned(RISCV64Instructions::Opcode::OP_IMM) && insn.field<12, 3>() == 0b000
&& insn.field<7, 5>() == unsigned(dest) && insn.field<15, 5>() == unsigned(dest)) {
target += IImmediate::value(insn);
continue;
}
// Counterpart to ImmediateLoader::Op::Type::LSHIFT12.
if (insn.value == RISCV64Instructions::SLLI::construct<12>(dest, dest)) {
target <<= 12;
continue;
}
RELEASE_ASSERT_NOT_REACHED();
return nullptr;
}
return reinterpret_cast<void*>(target);
}
};
AssemblerBuffer m_buffer;
int m_indexOfLastWatchpoint { INT_MIN };
int m_indexOfTailOfLastWatchpoint { INT_MIN };
};
} // namespace JSC
#endif // ENABLE(ASSEMBLER) && CPU(RISCV64)