| /* |
| * 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) |