diff --git a/CMakeLists.txt b/CMakeLists.txt index adda5c75a2..6dcea5c466 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,6 +477,8 @@ source_group(MIPS FILES ${CommonMIPS}) set(CommonRISCV64 ${CommonJIT} Common/RiscVCPUDetect.cpp + Common/RiscVEmitter.cpp + Common/RiscVEmitter.h Core/MIPS/fake/FakeJit.cpp Core/MIPS/fake/FakeJit.h ) diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 411eda5a1f..341ce1e394 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -475,6 +475,7 @@ + @@ -896,6 +897,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 4e1156f830..cbd9c417b6 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -421,6 +421,7 @@ GPU\Vulkan + @@ -798,6 +799,7 @@ GPU\Vulkan + diff --git a/Common/RiscVEmitter.cpp b/Common/RiscVEmitter.cpp new file mode 100644 index 0000000000..4f64ac793c --- /dev/null +++ b/Common/RiscVEmitter.cpp @@ -0,0 +1,1155 @@ +// Copyright (c) 2022- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "ppsspp_config.h" +#include +#include +#include "Common/RiscVEmitter.h" + +namespace RiscVGen { + +static inline bool SupportsCompressed() { + // TODO + return true; +} + +static inline uint8_t BitsSupported() { + // TODO + return 64; +} + +static inline uint8_t FloatBitsSupported() { + // TODO: 0 if not. + return 64; +} + +static inline bool SupportsMulDiv() { + // TODO + return true; +} + +static inline bool SupportsAtomic() { + // TODO + return true; +} + +static inline bool SupportsZicsr() { + // TODO + return true; +} + +enum class Opcode32 { + // Note: invalid, just used for FixupBranch. + ZERO = 0b0000000, + 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, + FMADD = 0b1000011, + FMSUB = 0b1000111, + FNMSUB = 0b1001011, + FNMADD = 0b1001111, + OP_FP = 0b1010011, + BRANCH = 0b1100011, + JALR = 0b1100111, + JAL = 0b1101111, + SYSTEM = 0b1110011, +}; + +enum class Funct3 { + // Note: invalid, just used for FixupBranch. + ZERO = 0b000, + + PRIV = 0b000, + + FENCE = 0b000, + FENCE_I = 0b001, + + BEQ = 0b000, + BNE = 0b001, + BLT = 0b100, + BGE = 0b101, + BLTU = 0b110, + BGEU = 0b111, + + LS_B = 0b000, + LS_H = 0b001, + LS_W = 0b010, + LS_D = 0b011, + LS_BU = 0b100, + LS_HU = 0b101, + LS_WU = 0b110, + + ADD = 0b000, + SLL = 0b001, + SLT = 0b010, + SLTU = 0b011, + XOR = 0b100, + SRL = 0b101, + OR = 0b110, + AND = 0b111, + + MUL = 0b000, + MULH = 0b001, + MULHSU = 0b010, + MULHU = 0b011, + DIV = 0b100, + DIVU = 0b101, + REM = 0b110, + REMU = 0b111, + + FSGNJ = 0b000, + FSGNJN = 0b001, + FSGNJX = 0b010, + + FMIN = 0b000, + FMAX = 0b001, + + FMV = 0b000, + FCLASS = 0b001, + + FLE = 0b000, + FLT = 0b001, + FEQ = 0b010, + + CSRRW = 0b001, + CSRRS = 0b010, + CSRRC = 0b011, + CSRRWI = 0b101, + CSRRSI = 0b110, + CSRRCI = 0b111, +}; + +enum class Funct2 { + S = 0b00, + D = 0b01, + Q = 0b11, +}; + +enum class Funct7 { + ZERO = 0b0000000, + + SUB = 0b0100000, + SRA = 0b0100000, + + MULDIV = 0b0000001, +}; + +enum class Funct5 { + AMOADD = 0b00000, + AMOSWAP = 0b00001, + LR = 0b00010, + SC = 0b00011, + AMOXOR = 0b00100, + AMOAND = 0b01100, + AMOOR = 0b01000, + AMOMIN = 0b10000, + AMOMAX = 0b10100, + AMOMINU = 0b11000, + AMOMAXU = 0b11100, + + FADD = 0b00000, + FSUB = 0b00001, + FMUL = 0b00010, + FDIV = 0b00011, + FSGNJ = 0b00100, + FMINMAX = 0b00101, + FCVT_SZ = 0b01000, + FSQRT = 0b01011, + FCMP = 0b10100, + FCVT_TOX = 0b11000, + FCVT_FROMX = 0b11010, + FMV_TOX = 0b11100, + FMV_FROMX = 0b11110, +}; + +enum class Funct12 { + ECALL = 0b000000000000, + EBREAK = 0b000000000001, +}; + +static inline RiscVReg DecodeReg(RiscVReg reg) { return (RiscVReg)(reg & 0x1F); } +static inline bool IsGPR(RiscVReg reg) { return reg < 0x20; } +static inline bool IsFPR(RiscVReg reg) { return (reg & 0x20) != 0 && (int)reg < 0x40; } + +static inline u32 EncodeR(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Funct7 funct7) { + return (u32)opcode | ((u32)DecodeReg(rd) << 7) | ((u32)funct3 << 12) | ((u32)DecodeReg(rs1) << 15) | ((u32)DecodeReg(rs2) << 20) | ((u32)funct7 << 25); +} + +static inline u32 EncodeGR(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Funct7 funct7) { + _assert_msg_(IsGPR(rd), "R instruction rd must be GPR"); + _assert_msg_(IsGPR(rs1), "R instruction rs1 must be GPR"); + _assert_msg_(IsGPR(rs2), "R instruction rs2 must be GPR"); + return EncodeR(opcode, rd, funct3, rs1, rs2, funct7); +} + +static inline u32 EncodeAtomicR(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Atomic ordering, Funct5 funct5) { + u32 funct7 = ((u32)funct5 << 2) | (u32)ordering; + return EncodeGR(opcode, rd, funct3, rs1, rs2, (Funct7)funct7); +} + +static inline u32 EncodeR4(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Funct2 funct2, RiscVReg rs3) { + return (u32)opcode | ((u32)DecodeReg(rd) << 7) | ((u32)funct3 << 12) | ((u32)DecodeReg(rs1) << 15) | ((u32)DecodeReg(rs2) << 20) | ((u32)funct2 << 25) | ((u32)DecodeReg(rs3) << 27); +} + +static inline u32 EncodeFR4(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Funct2 funct2, RiscVReg rs3) { + _assert_msg_(IsFPR(rd), "R4 instruction rd must be FPR"); + _assert_msg_(IsFPR(rs1), "R4 instruction rs1 must be FPR"); + _assert_msg_(IsFPR(rs2), "R4 instruction rs2 must be FPR"); + _assert_msg_(IsFPR(rs3), "R4 instruction rs3 must be FPR"); + return EncodeR4(opcode, rd, funct3, rs1, rs2, funct2, rs3); +} + +static inline u32 EncodeR(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Funct2 funct2, Funct5 funct5) { + return EncodeR(opcode, rd, funct3, rs1, rs2, (Funct7)(((u32)funct5 << 2) | (u32)funct2)); +} + +static inline u32 EncodeFR(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, Funct2 funct2, Funct5 funct5) { + _assert_msg_(IsFPR(rd), "R4 instruction rd must be FPR"); + _assert_msg_(IsFPR(rs1), "R4 instruction rs1 must be FPR"); + _assert_msg_(IsFPR(rs2), "R4 instruction rs2 must be FPR"); + return EncodeR(opcode, rd, funct3, rs1, rs2, (Funct7)(((u32)funct5 << 2) | (u32)funct2)); +} + +static inline u32 EncodeI(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, s32 simm12) { + _assert_msg_(((simm12 << 20) >> 20) == simm12, "I immediate must be signed s11.0"); + return (u32)opcode | ((u32)DecodeReg(rd) << 7) | ((u32)funct3 << 12) | ((u32)DecodeReg(rs1) << 15) | ((u32)simm12 << 20); +} + +static inline u32 EncodeGI(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, s32 simm12) { + _assert_msg_(IsGPR(rd), "I instruction rd must be GPR"); + _assert_msg_(IsGPR(rs1), "I instruction rs1 must be GPR"); + return EncodeI(opcode, rd, funct3, rs1, simm12); +} + +static inline u32 EncodeI(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, Funct12 funct12) { + return EncodeI(opcode, rd, funct3, rs1, ((s32)funct12 << 20) >> 20); +} + +static inline u32 EncodeGI(Opcode32 opcode, RiscVReg rd, Funct3 funct3, RiscVReg rs1, Funct12 funct12) { + _assert_msg_(IsGPR(rd), "I instruction rd must be GPR"); + _assert_msg_(IsGPR(rs1), "I instruction rs1 must be GPR"); + return EncodeI(opcode, rd, funct3, rs1, funct12); +} + +static inline u32 EncodeS(Opcode32 opcode, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, s32 simm12) { + _assert_msg_(((simm12 << 20) >> 20) == simm12, "S immediate must be signed s11.0"); + u32 imm4_0 = simm12 & 0x1F; + u32 imm11_5 = (simm12 >> 5) & 0x7F; + return (u32)opcode | ((u32)imm4_0 << 7) | ((u32)funct3 << 12) | ((u32)DecodeReg(rs1) << 15) | ((u32)DecodeReg(rs2) << 20) | ((u32)imm11_5 << 25); +} + +static inline u32 EncodeGS(Opcode32 opcode, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, s32 simm12) { + _assert_msg_(IsGPR(rs1), "S instruction rs1 must be GPR"); + _assert_msg_(IsGPR(rs2), "S instruction rs2 must be GPR"); + return EncodeS(opcode, funct3, rs1, rs2, simm12); +} + +static inline u32 EncodeB(Opcode32 opcode, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, s32 simm13) { + _assert_msg_(((simm13 << 19) >> 19) == simm13, "B immediate must be signed s12.0"); + _assert_msg_((simm13 & 1) == 0, "B immediate must be even"); + u32 imm11 = (simm13 >> 11) & 1; + u32 imm12 = (simm13 >> 12) & 1; + // This weird encoding scheme is to keep most bits the same as S, but keep sign at 31. + u32 imm4_1_11 = (simm13 & 0x1E) | imm11; + u32 imm12_10_5 = (imm12 << 6) | ((simm13 >> 5) & 0x3F); + return (u32)opcode | ((u32)imm4_1_11 << 7) | ((u32)funct3 << 12) | ((u32)DecodeReg(rs1) << 15) | ((u32)DecodeReg(rs2) << 20) | ((u32)imm12_10_5 << 25); +} + +static inline u32 EncodeGB(Opcode32 opcode, Funct3 funct3, RiscVReg rs1, RiscVReg rs2, s32 simm13) { + _assert_msg_(IsGPR(rs1), "B instruction rs1 must be GPR"); + _assert_msg_(IsGPR(rs2), "B instruction rs2 must be GPR"); + return EncodeB(opcode, funct3, rs1, rs2, simm13); +} + +static inline u32 EncodeU(Opcode32 opcode, RiscVReg rd, s32 simm32) { + _assert_msg_((simm32 & 0x0FFF) == 0, "U immediate must not have lower 12 bits set"); + return (u32)opcode | ((u32)DecodeReg(rd) << 7) | (u32)simm32; +} + +static inline u32 EncodeGU(Opcode32 opcode, RiscVReg rd, s32 simm32) { + _assert_msg_(IsGPR(rd), "I instruction rd must be GPR"); + return EncodeU(opcode, rd, simm32); +} + +static inline u32 EncodeJ(Opcode32 opcode, RiscVReg rd, s32 simm21) { + _assert_msg_(((simm21 << 11) >> 11) == simm21, "J immediate must be signed s20.0"); + _assert_msg_((simm21 & 1) == 0, "J immediate must be even"); + u32 imm11 = (simm21 >> 11) & 1; + u32 imm20 = (simm21 >> 20) & 1; + u32 imm10_1 = (simm21 >> 1) & 0x03FF; + u32 imm19_12 = (simm21 >> 12) & 0x00FF; + // This encoding scheme tries to keep the bits from B in the same places, plus sign. + u32 imm20_10_1_11_19_12 = (imm20 << 19) | (imm10_1 << 9) | (imm11 << 8) | imm19_12; + return (u32)opcode | ((u32)DecodeReg(rd) << 7) | ((u32)imm20_10_1_11_19_12 << 12); +} + +static inline u32 EncodeGJ(Opcode32 opcode, RiscVReg rd, s32 simm21) { + _assert_msg_(IsGPR(rd), "J instruction rd must be GPR"); + return EncodeJ(opcode, rd, simm21); +} + +static inline Funct3 BitsToFunct3(int bits, bool useFloat = false) { + int bitsSupported = useFloat ? FloatBitsSupported() : BitsSupported(); + _assert_msg_(bitsSupported >= bits, "Cannot use funct3 width %d, only have %d", bits, bitsSupported); + switch (bits) { + case 32: + return Funct3::LS_W; + case 64: + return Funct3::LS_D; + default: + _assert_msg_(false, "Invalid funct3 width %d", bits); + return Funct3::LS_W; + } +} + +static inline Funct2 BitsToFunct2(int bits) { + _assert_msg_(FloatBitsSupported() >= bits, "Cannot use funct2 width %d, only have %d", bits, FloatBitsSupported()); + switch (bits) { + case 32: + return Funct2::S; + case 64: + return Funct2::D; + case 128: + return Funct2::Q; + default: + _assert_msg_(false, "Invalid funct2 width %d", bits); + return Funct2::S; + } +} + +static inline int FConvToFloatBits(FConv c) { + switch (c) { + case FConv::W: + case FConv::WU: + case FConv::L: + case FConv::LU: + break; + + case FConv::S: + return 32; + case FConv::D: + return 64; + case FConv::Q: + return 128; + } + return 0; +} + +static inline int FConvToIntegerBits(FConv c) { + switch (c) { + case FConv::S: + case FConv::D: + case FConv::Q: + break; + + case FConv::W: + case FConv::WU: + return 32; + case FConv::L: + case FConv::LU: + return 64; + } + return 0; +} + +RiscVEmitter::RiscVEmitter(const u8 *ptr, u8 *writePtr) { + SetCodePointer(ptr, writePtr); +} + +void RiscVEmitter::SetCodePointer(const u8 *ptr, u8 *writePtr) { + code_ = ptr; + writable_ = writePtr; + lastCacheFlushEnd_ = ptr; +} + +const u8 *RiscVEmitter::GetCodePointer() const { + return code_; +} + +u8 *RiscVEmitter::GetWritableCodePtr() { + return writable_; +} + +void RiscVEmitter::ReserveCodeSpace(u32 bytes) { + _assert_msg_((bytes & 1) == 0, "Code space should be aligned"); + _assert_msg_((bytes & 3) == 0 || SupportsCompressed(), "Code space should be aligned (no compressed)"); + for (u32 i = 0; i < bytes / 4; i++) + EBREAK(); + if (bytes & 2) + Write16(0); +} + +const u8 *RiscVEmitter::AlignCode16() { + int c = int((u64)code_ & 15); + if (c) + ReserveCodeSpace(16 - c); + return code_; +} + +const u8 *RiscVEmitter::AlignCodePage() { + int page_size = GetMemoryProtectPageSize(); + int c = int((u64)code_ & (page_size - 1)); + if (c) + ReserveCodeSpace(page_size - c); + return code_; +} + +void RiscVEmitter::FlushIcache() { + FlushIcacheSection(lastCacheFlushEnd_, code_); + lastCacheFlushEnd_ = code_; +} + +void RiscVEmitter::FlushIcacheSection(const u8 *start, const u8 *end) { +#if PPSSPP_ARCH(RISCV64) + __builtin___clear_cache(start, end); +#endif +} + +FixupBranch::~FixupBranch() { + _assert_msg_(ptr == nullptr, "FixupBranch never set (left infinite loop)"); +} + +void RiscVEmitter::SetJumpTarget(FixupBranch &branch) { + SetJumpTarget(branch, code_); +} + +void RiscVEmitter::SetJumpTarget(FixupBranch &branch, const void *dst) { + _assert_msg_(branch.ptr != nullptr, "Invalid FixupBranch (SetJumpTarget twice?)"); + + const intptr_t srcp = (intptr_t)branch.ptr; + const intptr_t dstp = (intptr_t)dst; + const ptrdiff_t writable_delta = writable_ - code_; + u32 *writableSrc = (u32 *)(branch.ptr + writable_delta); + + // If compressed, this may be an unaligned 32-bit value. + u32 fixup; + memcpy(&fixup, writableSrc, sizeof(u32)); + + _assert_msg_((dstp & 1) == 0, "Destination should be aligned"); + _assert_msg_((dstp & 3) == 0 || SupportsCompressed(), "Destination should be aligned (no compressed)"); + + ptrdiff_t distance = dstp - srcp; + _assert_msg_((distance & 1) == 0, "Distance should be aligned"); + _assert_msg_((distance & 3) == 0 || SupportsCompressed(), "Distance should be aligned (no compressed)"); + + switch (branch.type) { + case FixupBranchType::B: + _assert_msg_(BInRange(branch.ptr, dst), "B destination is too far away (%p -> %p)", branch.ptr, dst); + fixup = (fixup & 0x01FFF07F) | EncodeB(Opcode32::ZERO, Funct3::ZERO, R_ZERO, R_ZERO, (s32)distance); + break; + + case FixupBranchType::J: + _assert_msg_(JInRange(branch.ptr, dst), "J destination is too far away (%p -> %p)", branch.ptr, dst); + fixup = (fixup & 0x00000FFF) | EncodeJ(Opcode32::ZERO, R_ZERO, (s32)distance); + break; + } + + memcpy(writableSrc, &fixup, sizeof(u32)); + branch.ptr = nullptr; +} + +bool RiscVEmitter::BInRange(const void *func) const { + return BInRange(code_, func); +} + +bool RiscVEmitter::JInRange(const void *func) const { + return JInRange(code_, func); +} + +bool RiscVEmitter::BInRange(const void *src, const void *dst) const { + const intptr_t srcp = (intptr_t)src; + const intptr_t dstp = (intptr_t)dst; + ptrdiff_t distance = dstp - srcp; + + return distance <= 0x00000FFE && -distance <= 0x00000FFE; +} + +bool RiscVEmitter::JInRange(const void *src, const void *dst) const { + const intptr_t srcp = (intptr_t)src; + const intptr_t dstp = (intptr_t)dst; + ptrdiff_t distance = dstp - srcp; + + return distance <= 0x000FFFFE && -distance <= 0x000FFFFE; +} + +void RiscVEmitter::LUI(RiscVReg rd, s32 simm32) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGU(Opcode32::LUI, rd, simm32)); +} + +void RiscVEmitter::AUIPC(RiscVReg rd, s32 simm32) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGU(Opcode32::AUIPC, rd, simm32)); +} + +void RiscVEmitter::JAL(RiscVReg rd, const void *dst) { + _assert_msg_(JInRange(GetCodePointer(), dst), "JAL destination is too far away (%p -> %p)", GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 1) == 0, "JAL destination should be aligned"); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "JAL destination should be aligned (no compressed)"); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGJ(Opcode32::JAL, rd, (s32)distance)); +} + +void RiscVEmitter::JALR(RiscVReg rd, RiscVReg rs1, s32 simm12) { + Write32(EncodeGI(Opcode32::JALR, rd, Funct3::ZERO, rs1, simm12)); +} + +FixupBranch RiscVEmitter::JAL(RiscVReg rd) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::J }; + Write32(EncodeGJ(Opcode32::JAL, rd, 0)); + return fixup; +} + +void RiscVEmitter::BEQ(RiscVReg rs1, RiscVReg rs2, const void *dst) { + _assert_msg_(BInRange(GetCodePointer(), dst), "%s destination is too far away (%p -> %p)", __func__, GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "%s destination should be aligned (no compressed)", __func__); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BEQ, rs1, rs2, (s32)distance)); +} + +void RiscVEmitter::BNE(RiscVReg rs1, RiscVReg rs2, const void *dst) { + _assert_msg_(BInRange(GetCodePointer(), dst), "%s destination is too far away (%p -> %p)", __func__, GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "%s destination should be aligned (no compressed)", __func__); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BNE, rs1, rs2, (s32)distance)); +} + +void RiscVEmitter::BLT(RiscVReg rs1, RiscVReg rs2, const void *dst) { + _assert_msg_(BInRange(GetCodePointer(), dst), "%s destination is too far away (%p -> %p)", __func__, GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "%s destination should be aligned (no compressed)", __func__); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BLT, rs1, rs2, (s32)distance)); +} + +void RiscVEmitter::BGE(RiscVReg rs1, RiscVReg rs2, const void *dst) { + _assert_msg_(BInRange(GetCodePointer(), dst), "%s destination is too far away (%p -> %p)", __func__, GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "%s destination should be aligned (no compressed)", __func__); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BGE, rs1, rs2, (s32)distance)); +} + +void RiscVEmitter::BLTU(RiscVReg rs1, RiscVReg rs2, const void *dst) { + _assert_msg_(BInRange(GetCodePointer(), dst), "%s destination is too far away (%p -> %p)", __func__, GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "%s destination should be aligned (no compressed)", __func__); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BLTU, rs1, rs2, (s32)distance)); +} + +void RiscVEmitter::BGEU(RiscVReg rs1, RiscVReg rs2, const void *dst) { + _assert_msg_(BInRange(GetCodePointer(), dst), "%s destination is too far away (%p -> %p)", __func__, GetCodePointer(), dst); + _assert_msg_(((intptr_t)dst & 3) == 0 || SupportsCompressed(), "%s destination should be aligned (no compressed)", __func__); + ptrdiff_t distance = (intptr_t)dst - (intptr_t)GetCodePointer(); + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BGEU, rs1, rs2, (s32)distance)); +} + +FixupBranch RiscVEmitter::BEQ(RiscVReg rs1, RiscVReg rs2) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::B }; + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BEQ, rs1, rs2, 0)); + return fixup; +} + +FixupBranch RiscVEmitter::BNE(RiscVReg rs1, RiscVReg rs2) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::B }; + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BNE, rs1, rs2, 0)); + return fixup; +} + +FixupBranch RiscVEmitter::BLT(RiscVReg rs1, RiscVReg rs2) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::B }; + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BLT, rs1, rs2, 0)); + return fixup; +} + +FixupBranch RiscVEmitter::BGE(RiscVReg rs1, RiscVReg rs2) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::B }; + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BGE, rs1, rs2, 0)); + return fixup; +} + +FixupBranch RiscVEmitter::BLTU(RiscVReg rs1, RiscVReg rs2) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::B }; + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BLTU, rs1, rs2, 0)); + return fixup; +} + +FixupBranch RiscVEmitter::BGEU(RiscVReg rs1, RiscVReg rs2) { + FixupBranch fixup{ GetCodePointer(), FixupBranchType::B }; + Write32(EncodeGB(Opcode32::BRANCH, Funct3::BGEU, rs1, rs2, 0)); + return fixup; +} + +void RiscVEmitter::LB(RiscVReg rd, RiscVReg rs1, s32 simm12) { + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_B, rs1, simm12)); +} + +void RiscVEmitter::LH(RiscVReg rd, RiscVReg rs1, s32 simm12) { + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_H, rs1, simm12)); +} + +void RiscVEmitter::LW(RiscVReg rd, RiscVReg rs1, s32 simm12) { + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_W, rs1, simm12)); +} + +void RiscVEmitter::LBU(RiscVReg rd, RiscVReg rs1, s32 simm12) { + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_BU, rs1, simm12)); +} + +void RiscVEmitter::LHU(RiscVReg rd, RiscVReg rs1, s32 simm12) { + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_HU, rs1, simm12)); +} + +void RiscVEmitter::SB(RiscVReg rs2, RiscVReg rs1, s32 simm12) { + Write32(EncodeGS(Opcode32::STORE, Funct3::LS_B, rs1, rs2, simm12)); +} + +void RiscVEmitter::SH(RiscVReg rs2, RiscVReg rs1, s32 simm12) { + Write32(EncodeGS(Opcode32::STORE, Funct3::LS_H, rs1, rs2, simm12)); +} + +void RiscVEmitter::SW(RiscVReg rs2, RiscVReg rs1, s32 simm12) { + Write32(EncodeGS(Opcode32::STORE, Funct3::LS_W, rs1, rs2, simm12)); +} + +void RiscVEmitter::ADDI(RiscVReg rd, RiscVReg rs1, s32 simm12) { + // Allow NOP form of ADDI. + _assert_msg_(rd != R_ZERO || (rs1 == R_ZERO && simm12 == 0), "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::ADD, rs1, simm12)); +} + +void RiscVEmitter::SLTI(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::SLT, rs1, simm12)); +} + +void RiscVEmitter::SLTIU(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::SLTU, rs1, simm12)); +} + +void RiscVEmitter::XORI(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::XOR, rs1, simm12)); +} + +void RiscVEmitter::ORI(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::OR, rs1, simm12)); +} + +void RiscVEmitter::ANDI(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::AND, rs1, simm12)); +} + +void RiscVEmitter::SLLI(RiscVReg rd, RiscVReg rs1, u32 shamt) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + // Not sure if shamt=0 is legal or not, let's play it safe. + _assert_msg_(shamt > 0 && shamt < BitsSupported(), "Shift out of range"); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::SLL, rs1, shamt)); +} + +void RiscVEmitter::SRLI(RiscVReg rd, RiscVReg rs1, u32 shamt) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + // Not sure if shamt=0 is legal or not, let's play it safe. + _assert_msg_(shamt > 0 && shamt < BitsSupported(), "Shift out of range"); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::SRL, rs1, shamt)); +} + +void RiscVEmitter::SRAI(RiscVReg rd, RiscVReg rs1, u32 shamt) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + // Not sure if shamt=0 is legal or not, let's play it safe. + _assert_msg_(shamt > 0 && shamt < BitsSupported(), "Shift out of range"); + Write32(EncodeGI(Opcode32::OP_IMM, rd, Funct3::SRL, rs1, shamt | (1 << 10))); +} + +void RiscVEmitter::ADD(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::ADD, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SUB(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::ADD, rs1, rs2, Funct7::SUB)); +} + +void RiscVEmitter::SLL(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::SLL, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SLT(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::SLT, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SLTU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::SLTU, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::XOR(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::XOR, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SRL(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::SRL, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SRA(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::SRL, rs1, rs2, Funct7::SRA)); +} + +void RiscVEmitter::OR(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::OR, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::AND(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::AND, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::FENCE(Fence predecessor, Fence successor) { + _assert_msg_((u32)predecessor != 0 && (u32)successor != 0, "FENCE missing pred/succ"); + s32 simm12 = ((u32)predecessor << 4) | (u32)successor; + Write32(EncodeI(Opcode32::MISC_MEM, R_ZERO, Funct3::FENCE, R_ZERO, simm12)); +} + +void RiscVEmitter::FENCE_TSO() { + s32 simm12 = (0b1000 << 28) | ((u32)Fence::RW << 4) | (u32)Fence::RW; + Write32(EncodeI(Opcode32::MISC_MEM, R_ZERO, Funct3::FENCE, R_ZERO, simm12)); +} + +void RiscVEmitter::ECALL() { + Write32(EncodeI(Opcode32::SYSTEM, R_ZERO, Funct3::PRIV, R_ZERO, Funct12::ECALL)); +} + +void RiscVEmitter::EBREAK() { + Write32(EncodeI(Opcode32::SYSTEM, R_ZERO, Funct3::PRIV, R_ZERO, Funct12::EBREAK)); +} + +void RiscVEmitter::LWU(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_WU, rs1, simm12)); +} + +void RiscVEmitter::LD(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + Write32(EncodeGI(Opcode32::LOAD, rd, Funct3::LS_D, rs1, simm12)); +} + +void RiscVEmitter::SD(RiscVReg rs2, RiscVReg rs1, s32 simm12) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + Write32(EncodeGS(Opcode32::STORE, Funct3::LS_D, rs1, rs2, simm12)); +} + +void RiscVEmitter::ADDIW(RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGI(Opcode32::OP_IMM_32, rd, Funct3::ADD, rs1, simm12)); +} + +void RiscVEmitter::SLLIW(RiscVReg rd, RiscVReg rs1, u32 shamt) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + // Not sure if shamt=0 is legal or not, let's play it safe. + _assert_msg_(shamt > 0 && shamt < 32, "Shift out of range"); + Write32(EncodeGI(Opcode32::OP_IMM_32, rd, Funct3::SLL, rs1, shamt)); +} + +void RiscVEmitter::SRLIW(RiscVReg rd, RiscVReg rs1, u32 shamt) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + // Not sure if shamt=0 is legal or not, let's play it safe. + _assert_msg_(shamt > 0 && shamt < 32, "Shift out of range"); + Write32(EncodeGI(Opcode32::OP_IMM_32, rd, Funct3::SRL, rs1, shamt)); +} + +void RiscVEmitter::SRAIW(RiscVReg rd, RiscVReg rs1, u32 shamt) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + // Not sure if shamt=0 is legal or not, let's play it safe. + _assert_msg_(shamt > 0 && shamt < 32, "Shift out of range"); + Write32(EncodeGI(Opcode32::OP_IMM_32, rd, Funct3::SRL, rs1, shamt | (1 << 10))); +} + +void RiscVEmitter::ADDW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::ADD, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SUBW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::ADD, rs1, rs2, Funct7::SUB)); +} + +void RiscVEmitter::SLLW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::SLL, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SRLW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::SRL, rs1, rs2, Funct7::ZERO)); +} + +void RiscVEmitter::SRAW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64, "%s is only valid with R64I", __func__); + _assert_msg_(rd != R_ZERO, "%s write to zero is a HINT", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::SRL, rs1, rs2, Funct7::SRA)); +} + +void RiscVEmitter::MUL(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::MUL, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::MULH(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::MULH, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::MULHSU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::MULHSU, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::MULHU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::MULHU, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::DIV(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::DIV, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::DIVU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::DIVU, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::REM(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::REM, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::REMU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(SupportsMulDiv(), "%s is only valid with R32M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP, rd, Funct3::REMU, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::MULW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64 && SupportsMulDiv(), "%s is only valid with R64M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::MUL, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::DIVW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64 && SupportsMulDiv(), "%s is only valid with R64M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::DIV, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::DIVUW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64 && SupportsMulDiv(), "%s is only valid with R64M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::DIVU, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::REMW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64 && SupportsMulDiv(), "%s is only valid with R64M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::REM, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::REMUW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(BitsSupported() >= 64 && SupportsMulDiv(), "%s is only valid with R64M", __func__); + // Not explicitly a HINT, but seems sensible to restrict just in case. + _assert_msg_(rd != R_ZERO, "%s write to zero", __func__); + Write32(EncodeGR(Opcode32::OP_32, rd, Funct3::REMU, rs1, rs2, Funct7::MULDIV)); +} + +void RiscVEmitter::LR(int bits, RiscVReg rd, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + _assert_msg_(ordering != Atomic::RELEASE, "%s should not use RELEASE ordering", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, R_ZERO, ordering, Funct5::LR)); +} + +void RiscVEmitter::SC(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + _assert_msg_(ordering != Atomic::ACQUIRE, "%s should not use ACQUIRE ordering", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::SC)); +} + +void RiscVEmitter::AMOSWAP(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOSWAP)); +} + +void RiscVEmitter::AMOADD(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOADD)); +} + +void RiscVEmitter::AMOAND(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOAND)); +} + +void RiscVEmitter::AMOOR(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOOR)); +} + +void RiscVEmitter::AMOXOR(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOXOR)); +} + +void RiscVEmitter::AMOMIN(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOMIN)); +} + +void RiscVEmitter::AMOMAX(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOMAX)); +} + +void RiscVEmitter::AMOMINU(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOMINU)); +} + +void RiscVEmitter::AMOMAXU(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg rs1, Atomic ordering) { + _assert_msg_(SupportsAtomic(), "%s is only valid with R32A", __func__); + Write32(EncodeAtomicR(Opcode32::AMO, rd, BitsToFunct3(bits), rs1, rs2, ordering, Funct5::AMOMAXU)); +} + +void RiscVEmitter::FL(int bits, RiscVReg rd, RiscVReg rs1, s32 simm12) { + _assert_msg_(IsGPR(rs1) && IsFPR(rd), "FL with incorrect register types"); + Write32(EncodeI(Opcode32::LOAD_FP, rd, BitsToFunct3(bits, true), rs1, simm12)); +} + +void RiscVEmitter::FS(int bits, RiscVReg rs2, RiscVReg rs1, s32 simm12) { + _assert_msg_(IsGPR(rs1) && IsFPR(rs2), "FS with incorrect register types"); + Write32(EncodeS(Opcode32::STORE_FP, BitsToFunct3(bits, true), rs1, rs2, simm12)); +} + +void RiscVEmitter::FMADD(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm) { + Write32(EncodeFR4(Opcode32::FMADD, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), rs3)); +} + +void RiscVEmitter::FMSUB(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm) { + Write32(EncodeFR4(Opcode32::FMSUB, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), rs3)); +} + +void RiscVEmitter::FNMSUB(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm) { + Write32(EncodeFR4(Opcode32::FNMSUB, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), rs3)); +} + +void RiscVEmitter::FNMADD(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm) { + Write32(EncodeFR4(Opcode32::FNMADD, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), rs3)); +} + +void RiscVEmitter::FADD(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm) { + Write32(EncodeFR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), Funct5::FADD)); +} + +void RiscVEmitter::FSUB(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm) { + Write32(EncodeFR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), Funct5::FSUB)); +} + +void RiscVEmitter::FMUL(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm) { + Write32(EncodeFR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), Funct5::FMUL)); +} + +void RiscVEmitter::FDIV(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm) { + Write32(EncodeFR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, rs2, BitsToFunct2(bits), Funct5::FDIV)); +} + +void RiscVEmitter::FSQRT(int bits, RiscVReg rd, RiscVReg rs1, Round rm) { + Write32(EncodeFR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, F0, BitsToFunct2(bits), Funct5::FSQRT)); +} + +void RiscVEmitter::FSGNJ(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + Write32(EncodeFR(Opcode32::OP_FP, rd, Funct3::FSGNJ, rs1, rs2, BitsToFunct2(bits), Funct5::FSGNJ)); +} + +void RiscVEmitter::FSGNJN(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + Write32(EncodeFR(Opcode32::OP_FP, rd, Funct3::FSGNJN, rs1, rs2, BitsToFunct2(bits), Funct5::FSGNJ)); +} + +void RiscVEmitter::FSGNJX(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + Write32(EncodeFR(Opcode32::OP_FP, rd, Funct3::FSGNJX, rs1, rs2, BitsToFunct2(bits), Funct5::FSGNJ)); +} + +void RiscVEmitter::FMIN(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + Write32(EncodeFR(Opcode32::OP_FP, rd, Funct3::FMIN, rs1, rs2, BitsToFunct2(bits), Funct5::FMINMAX)); +} + +void RiscVEmitter::FMAX(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + Write32(EncodeFR(Opcode32::OP_FP, rd, Funct3::FMAX, rs1, rs2, BitsToFunct2(bits), Funct5::FMINMAX)); +} + +void RiscVEmitter::FCVT(FConv to, FConv from, RiscVReg rd, RiscVReg rs1, Round rm) { + int floatBits = std::max(FConvToFloatBits(from), FConvToFloatBits(to)); + int integerBits = std::max(FConvToIntegerBits(from), FConvToIntegerBits(to)); + + _assert_msg_(floatBits > 0, "FCVT can't be used with only GPRs"); + _assert_msg_(integerBits <= BitsSupported(), "FCVT for %d integer bits, only %d supported", integerBits, BitsSupported()); + _assert_msg_(floatBits <= FloatBitsSupported(), "FCVT for %d float bits, only %d supported", floatBits, FloatBitsSupported()); + + if (integerBits == 0) { + // Convert between float widths. + Funct2 fromFmt = BitsToFunct2(FConvToFloatBits(from)); + Funct2 toFmt = BitsToFunct2(FConvToFloatBits(to)); + if (FConvToFloatBits(to) > FConvToFloatBits(from)) { + _assert_msg_(rm == Round::DYNAMIC || rm == Round::NEAREST_EVEN, "Invalid rounding mode for widening FCVT"); + rm = Round::NEAREST_EVEN; + } + Write32(EncodeR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, (RiscVReg)fromFmt, toFmt, Funct5::FCVT_SZ)); + } else { + Funct5 funct5 = FConvToIntegerBits(to) == 0 ? Funct5::FCVT_FROMX : Funct5::FCVT_TOX; + FConv integerFmt = FConvToIntegerBits(to) == 0 ? from : to; + Funct2 floatFmt = BitsToFunct2(floatBits); + _assert_msg_(((int)integerFmt & ~3) == 0, "Got wrong integer bits"); + Write32(EncodeR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, (RiscVReg)integerFmt, floatFmt, funct5)); + } +} + +void RiscVEmitter::FMV(FMv to, FMv from, RiscVReg rd, RiscVReg rs1) { + int bits = 0; + switch (to == FMv::X ? from : to) { + case FMv::D: bits = 64; break; + case FMv::W: bits = 32; break; + case FMv::X: bits = 0; break; + } + + _assert_msg_(BitsSupported() >= bits && FloatBitsSupported() >= bits, "FMV cannot be used for %d bits, only %d/%d supported", bits, BitsSupported(), FloatBitsSupported()); + _assert_msg_((to == FMv::X && from != FMv::X) || (to != FMv::X && from == FMv::X), "%s can only transfer between FPR/GPR", __func__); + _assert_msg_(to == FMv::X ? IsGPR(rd) : IsFPR(rd), "%s rd of wrong type", __func__); + _assert_msg_(from == FMv::X ? IsGPR(rs1) : IsFPR(rs1), "%s rs1 of wrong type", __func__); + + Funct5 funct5 = to == FMv::X ? Funct5::FMV_TOX : Funct5::FMV_FROMX; + Write32(EncodeR(Opcode32::OP_FP, rd, Funct3::FMV, rs1, F0, BitsToFunct2(bits), funct5)); +} + +void RiscVEmitter::FEQ(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(IsGPR(rd), "%s rd must be GPR", __func__); + _assert_msg_(IsFPR(rs1), "%s rs1 must be FPR", __func__); + _assert_msg_(IsFPR(rs2), "%s rs2 must be FPR", __func__); + Write32(EncodeR(Opcode32::OP_FP, rd, Funct3::FEQ, rs1, rs2, BitsToFunct2(bits), Funct5::FCMP)); +} + +void RiscVEmitter::FLT(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(IsGPR(rd), "%s rd must be GPR", __func__); + _assert_msg_(IsFPR(rs1), "%s rs1 must be FPR", __func__); + _assert_msg_(IsFPR(rs2), "%s rs2 must be FPR", __func__); + Write32(EncodeR(Opcode32::OP_FP, rd, Funct3::FLT, rs1, rs2, BitsToFunct2(bits), Funct5::FCMP)); +} + +void RiscVEmitter::FLE(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2) { + _assert_msg_(IsGPR(rd), "%s rd must be GPR", __func__); + _assert_msg_(IsFPR(rs1), "%s rs1 must be FPR", __func__); + _assert_msg_(IsFPR(rs2), "%s rs2 must be FPR", __func__); + Write32(EncodeR(Opcode32::OP_FP, rd, Funct3::FLE, rs1, rs2, BitsToFunct2(bits), Funct5::FCMP)); +} + +void RiscVEmitter::FCLASS(int bits, RiscVReg rd, RiscVReg rs1) { + _assert_msg_(IsGPR(rd), "%s rd must be GPR", __func__); + _assert_msg_(IsFPR(rs1), "%s rs1 must be FPR", __func__); + Write32(EncodeR(Opcode32::OP_FP, rd, Funct3::FCLASS, rs1, F0, BitsToFunct2(bits), Funct5::FMV_TOX)); +} + +void RiscVEmitter::CSRRW(RiscVReg rd, Csr csr, RiscVReg rs1) { + _assert_msg_(SupportsZicsr(), "%s instruction not supported", __func__); + _assert_msg_((u32)csr <= 0x00000FFF, "%s with invalid CSR number", __func__); + Write32(EncodeGI(Opcode32::SYSTEM, rd, Funct3::CSRRW, rs1, (Funct12)csr)); +} + +void RiscVEmitter::CSRRS(RiscVReg rd, Csr csr, RiscVReg rs1) { + _assert_msg_(SupportsZicsr(), "%s instruction not supported", __func__); + _assert_msg_((u32)csr <= 0x00000FFF, "%s with invalid CSR number", __func__); + Write32(EncodeGI(Opcode32::SYSTEM, rd, Funct3::CSRRS, rs1, (Funct12)csr)); +} + +void RiscVEmitter::CSRRC(RiscVReg rd, Csr csr, RiscVReg rs1) { + _assert_msg_(SupportsZicsr(), "%s instruction not supported", __func__); + _assert_msg_((u32)csr <= 0x00000FFF, "%s with invalid CSR number", __func__); + Write32(EncodeGI(Opcode32::SYSTEM, rd, Funct3::CSRRC, rs1, (Funct12)csr)); +} + +void RiscVEmitter::CSRRWI(RiscVReg rd, Csr csr, u8 uimm5) { + _assert_msg_(SupportsZicsr(), "%s instruction not supported", __func__); + _assert_msg_((u32)csr <= 0x00000FFF, "%s with invalid CSR number", __func__); + _assert_msg_((u32)uimm5 <= 0x1F, "%s can only specify lowest 5 bits", __func__); + Write32(EncodeGI(Opcode32::SYSTEM, rd, Funct3::CSRRWI, (RiscVReg)uimm5, (Funct12)csr)); +} + +void RiscVEmitter::CSRRSI(RiscVReg rd, Csr csr, u8 uimm5) { + _assert_msg_(SupportsZicsr(), "%s instruction not supported", __func__); + _assert_msg_((u32)csr <= 0x00000FFF, "%s with invalid CSR number", __func__); + _assert_msg_((u32)uimm5 <= 0x1F, "%s can only set lowest 5 bits", __func__); + Write32(EncodeGI(Opcode32::SYSTEM, rd, Funct3::CSRRSI, (RiscVReg)uimm5, (Funct12)csr)); +} + +void RiscVEmitter::CSRRCI(RiscVReg rd, Csr csr, u8 uimm5) { + _assert_msg_(SupportsZicsr(), "%s instruction not supported", __func__); + _assert_msg_((u32)csr <= 0x00000FFF, "%s with invalid CSR number", __func__); + _assert_msg_((u32)uimm5 <= 0x1F, "%s can only clear lowest 5 bits", __func__); + Write32(EncodeGI(Opcode32::SYSTEM, rd, Funct3::CSRRCI, (RiscVReg)uimm5, (Funct12)csr)); +} + +}; diff --git a/Common/RiscVEmitter.h b/Common/RiscVEmitter.h new file mode 100644 index 0000000000..c1d06739a6 --- /dev/null +++ b/Common/RiscVEmitter.h @@ -0,0 +1,361 @@ +// Copyright (c) 2022- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include "Common/CodeBlock.h" +#include "Common/Common.h" + +namespace RiscVGen { + +enum RiscVReg { + X0 = 0, X1, X2, X3, X4, X5, X6, X7, + X8, X9, X10, X11, X12, X13, X14, X15, + X16, X17, X18, X19, X20, X21, X22, X23, + X24, X25, X26, X27, X28, X29, X30, X31, + + R_ZERO = 0, + R_RA = 1, + R_SP = 2, + R_GP = 3, + R_TP = 4, + R_FP = 8, + + F0 = 0x20, F1, F2, F3, F4, F5, F6, F7, + F8, F9, F10, F11, F12, F13, F14, F15, + F16, F17, F18, F19, F20, F21, F22, F23, + F24, F25, F26, F27, F28, F29, F30, F31, +}; + +enum class FixupBranchType { + B, + J, +}; + +enum class Fence { + I = 0b1000, + O = 0b0100, + R = 0b0010, + W = 0b0001, + RW = R | W, + IO = I | O, +}; +ENUM_CLASS_BITOPS(Fence); + +enum class Atomic { + NONE = 0b00, + ACQUIRE = 0b10, + RELEASE = 0b01, + SEQUENTIAL = 0b11, +}; + +enum class Round { + NEAREST_EVEN = 0b000, + TOZERO = 0b001, + DOWN = 0b010, + UP = 0b011, + NEAREST_MAX = 0b100, + DYNAMIC = 0b111, +}; + +enum class FConv { + W = 0x0000, + WU = 0x0001, + L = 0x0002, + LU = 0x0003, + + S = 0x1000, + D = 0x1001, + Q = 0x1003, +}; + +enum class FMv { + X, + W, + D, +}; + +enum class Csr { + FFlags = 0x001, + FRm = 0x002, + FCsr = 0x003, + + Cycle = 0xC00, + Time = 0xC01, + InstRet = 0xC02, + CycleH = 0xC80, + TimeH = 0xC81, + InstRetH = 0xC82, +}; + +struct FixupBranch { + FixupBranch(const u8 *p, FixupBranchType t) : ptr(p), type(t) {} + ~FixupBranch(); + + const u8 *ptr = nullptr; + FixupBranchType type; +}; + +class RiscVEmitter { +public: + RiscVEmitter() {} + RiscVEmitter(const u8 *codePtr, u8 *writablePtr); + virtual ~RiscVEmitter() {} + + void SetCodePointer(const u8 *ptr, u8 *writePtr); + const u8 *GetCodePointer() const; + u8 *GetWritableCodePtr(); + + void ReserveCodeSpace(u32 bytes); + const u8 *AlignCode16(); + const u8 *AlignCodePage(); + void FlushIcache(); + void FlushIcacheSection(const u8 *start, const u8 *end); + + void SetJumpTarget(FixupBranch &branch); + bool BInRange(const void *func) const; + bool JInRange(const void *func) const; + + void LUI(RiscVReg rd, s32 simm32); + void AUIPC(RiscVReg rd, s32 simm32); + + void JAL(RiscVReg rd, const void *dst); + void JALR(RiscVReg rd, RiscVReg rs1, s32 simm12); + FixupBranch JAL(RiscVReg rd); + + // Psuedo-instructions for convenience/clarity. + void J(const void *dst) { + JAL(R_ZERO, dst); + } + void JR(RiscVReg rs1, u32 simm12 = 0) { + JALR(R_ZERO, rs1, simm12); + } + void RET() { + JR(R_RA); + } + FixupBranch J() { + return JAL(R_ZERO); + } + + void BEQ(RiscVReg rs1, RiscVReg rs2, const void *dst); + void BNE(RiscVReg rs1, RiscVReg rs2, const void *dst); + void BLT(RiscVReg rs1, RiscVReg rs2, const void *dst); + void BGE(RiscVReg rs1, RiscVReg rs2, const void *dst); + void BLTU(RiscVReg rs1, RiscVReg rs2, const void *dst); + void BGEU(RiscVReg rs1, RiscVReg rs2, const void *dst); + FixupBranch BEQ(RiscVReg rs1, RiscVReg rs2); + FixupBranch BNE(RiscVReg rs1, RiscVReg rs2); + FixupBranch BLT(RiscVReg rs1, RiscVReg rs2); + FixupBranch BGE(RiscVReg rs1, RiscVReg rs2); + FixupBranch BLTU(RiscVReg rs1, RiscVReg rs2); + FixupBranch BGEU(RiscVReg rs1, RiscVReg rs2); + + void LB(RiscVReg rd, RiscVReg addr, s32 simm12); + void LH(RiscVReg rd, RiscVReg addr, s32 simm12); + void LW(RiscVReg rd, RiscVReg addr, s32 simm12); + void LBU(RiscVReg rd, RiscVReg addr, s32 simm12); + void LHU(RiscVReg rd, RiscVReg addr, s32 simm12); + + void SB(RiscVReg rs2, RiscVReg addr, s32 simm12); + void SH(RiscVReg rs2, RiscVReg addr, s32 simm12); + void SW(RiscVReg rs2, RiscVReg addr, s32 simm12); + + void ADDI(RiscVReg rd, RiscVReg rs1, s32 simm12); + void SLTI(RiscVReg rd, RiscVReg rs1, s32 simm12); + void SLTIU(RiscVReg rd, RiscVReg rs1, s32 simm12); + void XORI(RiscVReg rd, RiscVReg rs1, s32 simm12); + void ORI(RiscVReg rd, RiscVReg rs1, s32 simm12); + void ANDI(RiscVReg rd, RiscVReg rs1, s32 simm12); + + void NOP() { + ADDI(R_ZERO, R_ZERO, 0); + } + void MV(RiscVReg rd, RiscVReg rs1) { + ADDI(rd, rs1, 0); + } + void NOT(RiscVReg rd, RiscVReg rs1) { + XORI(rd, rs1, -1); + } + + void SLLI(RiscVReg rd, RiscVReg rs1, u32 shamt); + void SRLI(RiscVReg rd, RiscVReg rs1, u32 shamt); + void SRAI(RiscVReg rd, RiscVReg rs1, u32 shamt); + + void ADD(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SUB(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SLL(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SLT(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SLTU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void XOR(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SRL(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SRA(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void OR(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void AND(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + + void NEG(RiscVReg rd, RiscVReg rs) { + SUB(rd, R_ZERO, rs); + } + + void FENCE(Fence predecessor, Fence successor); + void FENCE_TSO(); + + void ECALL(); + void EBREAK(); + + // 64-bit instructions - oens ending in W sign extend result to 32 bits. + void LWU(RiscVReg rd, RiscVReg addr, s32 simm12); + void LD(RiscVReg rd, RiscVReg addr, s32 simm12); + void SD(RiscVReg rs2, RiscVReg addr, s32 simm12); + void ADDIW(RiscVReg rd, RiscVReg rs1, s32 simm12); + void SLLIW(RiscVReg rd, RiscVReg rs1, u32 shamt); + void SRLIW(RiscVReg rd, RiscVReg rs1, u32 shamt); + void SRAIW(RiscVReg rd, RiscVReg rs1, u32 shamt); + void ADDW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SUBW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SLLW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SRLW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void SRAW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + + void NEGW(RiscVReg rd, RiscVReg rs) { + SUBW(rd, R_ZERO, rs); + } + + // Integer multiplication and division. + void MUL(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void MULH(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void MULHSU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void MULHU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void DIV(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void DIVU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void REM(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void REMU(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + // 64-bit only multiply and divide. + void MULW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void DIVW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void DIVUW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void REMW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void REMUW(RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + + // Atomic memory operations. + void LR(int bits, RiscVReg rd, RiscVReg addr, Atomic ordering); + void SC(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOSWAP(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOADD(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOAND(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOOR(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOXOR(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOMIN(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOMAX(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOMINU(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + void AMOMAXU(int bits, RiscVReg rd, RiscVReg rs2, RiscVReg addr, Atomic ordering); + + // Floating point (same funcs for single/double/quad, if supported.) + void FL(int bits, RiscVReg rd, RiscVReg addr, s32 simm12); + void FS(int bits, RiscVReg rs2, RiscVReg addr, s32 simm12); + void FLW(RiscVReg rd, RiscVReg addr, s32 simm12) { + FL(32, rd, addr, simm12); + } + void FSW(RiscVReg rs2, RiscVReg addr, s32 simm12) { + FS(32, rs2, addr, simm12); + } + + void FMADD(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm = Round::DYNAMIC); + void FMSUB(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm = Round::DYNAMIC); + void FNMSUB(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm = Round::DYNAMIC); + void FNMADD(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, RiscVReg rs3, Round rm = Round::DYNAMIC); + + void FADD(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm = Round::DYNAMIC); + void FSUB(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm = Round::DYNAMIC); + void FMUL(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm = Round::DYNAMIC); + void FDIV(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2, Round rm = Round::DYNAMIC); + void FSQRT(int bits, RiscVReg rd, RiscVReg rs1, Round rm = Round::DYNAMIC); + + void FSGNJ(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void FSGNJN(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void FSGNJX(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + + void FMV(int bits, RiscVReg rd, RiscVReg rs) { + FSGNJ(bits, rd, rs, rs); + } + void FNEG(int bits, RiscVReg rd, RiscVReg rs) { + FSGNJN(bits, rd, rs, rs); + } + void FABS(int bits, RiscVReg rd, RiscVReg rs) { + FSGNJX(bits, rd, rs, rs); + } + + void FMIN(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void FMAX(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + + void FCVT(FConv to, FConv from, RiscVReg rd, RiscVReg rs1, Round rm = Round::DYNAMIC); + void FMV(FMv to, FMv from, RiscVReg rd, RiscVReg rs1); + + void FEQ(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void FLT(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void FLE(int bits, RiscVReg rd, RiscVReg rs1, RiscVReg rs2); + void FCLASS(int bits, RiscVReg rd, RiscVReg rs1); + + // Control state register manipulation. + void CSRRW(RiscVReg rd, Csr csr, RiscVReg rs1); + void CSRRS(RiscVReg rd, Csr csr, RiscVReg rs1); + void CSRRC(RiscVReg rd, Csr csr, RiscVReg rs1); + void CSRRWI(RiscVReg rd, Csr csr, u8 uimm5); + void CSRRSI(RiscVReg rd, Csr csr, u8 uimm5); + void CSRRCI(RiscVReg rd, Csr csr, u8 uimm5); + + void FRRM(RiscVReg rd) { + CSRRS(rd, Csr::FRm, X0); + } + void FSRM(RiscVReg rs) { + CSRRW(X0, Csr::FRm, rs); + } + void FSRMI(RiscVReg rd, Round rm) { + _assert_msg_(rm != Round::DYNAMIC, "Cannot set FRm to DYNAMIC"); + CSRRWI(rd, Csr::FRm, (uint8_t)rm); + } + void FSRMI(Round rm) { + FSRMI(X0, rm); + } + +private: + void SetJumpTarget(FixupBranch &branch, const void *dst); + bool BInRange(const void *src, const void *dst) const; + bool JInRange(const void *src, const void *dst) const; + + inline void Write32(u32 value) { + *(u32 *)writable_ = value; + code_ += 4; + writable_ += 4; + } + inline void Write16(u16 value) { + *(u16 *)writable_ = value; + code_ += 2; + writable_ += 2; + } + + const u8 *code_ = nullptr; + u8 *writable_ = nullptr; + const u8 *lastCacheFlushEnd_ = nullptr; +}; + +class MIPSCodeBlock : public CodeBlock { +private: + void PoisonMemory(int offset) override; +}; + +}; diff --git a/Core/Config.cpp b/Core/Config.cpp index cdf306155d..dd191593cb 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -331,7 +331,7 @@ struct ConfigSetting { section->Get(ini5_, &ptr_.customButton->repeat, default_.customButton.repeat); return true; default: - _dbg_assert_msg_(false, "Unexpected ini setting type"); + _dbg_assert_msg_(false, "Get(%s): Unexpected ini setting type: %d", iniKey_, (int)type_); return false; } } @@ -375,7 +375,7 @@ struct ConfigSetting { section->Set(ini5_, ptr_.customButton->repeat); return; default: - _dbg_assert_msg_(false, "Unexpected ini setting type"); + _dbg_assert_msg_(false, "Set(%s): Unexpected ini setting type: %d", iniKey_, (int)type_); return; } } @@ -406,22 +406,22 @@ struct ConfigSetting { // Doesn't report. return; default: - _dbg_assert_msg_(false, "Unexpected ini setting type"); + _dbg_assert_msg_(false, "Report(%s): Unexpected ini setting type: %d", iniKey_, (int)type_); return; } } - const char *iniKey_; - const char *ini2_; - const char *ini3_; - const char *ini4_; - const char *ini5_; + const char *iniKey_ = nullptr; + const char *ini2_ = nullptr; + const char *ini3_ = nullptr; + const char *ini4_ = nullptr; + const char *ini5_ = nullptr; Type type_; bool report_; bool save_; bool perGame_; SettingPtr ptr_; - DefaultValue default_; + DefaultValue default_{}; Callback cb_; // We only support transform for ints. @@ -1208,6 +1208,8 @@ static ConfigSetting vrSettings[] = { ConfigSetting("VREnableStereo", &g_Config.bEnableStereo, true), ConfigSetting("VRCanvasDistance", &g_Config.iCanvasDistance, 6), ConfigSetting("VRFieldOfView", &g_Config.iFieldOfViewPercentage, 100), + + ConfigSetting(false), }; static ConfigSectionSettings sections[] = { diff --git a/GPU/Common/DepalettizeShaderCommon.cpp b/GPU/Common/DepalettizeShaderCommon.cpp index 902b180510..e98c424858 100644 --- a/GPU/Common/DepalettizeShaderCommon.cpp +++ b/GPU/Common/DepalettizeShaderCommon.cpp @@ -104,9 +104,9 @@ void GenerateDepalShader300(ShaderWriter &writer, const DepalConfig &config) { if (config.bufferFormat == GE_FORMAT_DEPTH16 && config.textureFormat == GE_TFMT_5650) { // Convert depth to 565, without going through a CLUT. writer.C(" int idepth = int(clamp(depth, 0.0, 65535.0));\n"); - writer.C(" float r = (idepth & 31) / 31.0f;\n"); - writer.C(" float g = ((idepth >> 5) & 63) / 63.0f;\n"); - writer.C(" float b = ((idepth >> 11) & 31) / 31.0f;\n"); + writer.C(" float r = float(idepth & 31) / 31.0f;\n"); + writer.C(" float g = float((idepth >> 5) & 63) / 63.0f;\n"); + writer.C(" float b = float((idepth >> 11) & 31) / 31.0f;\n"); writer.C(" vec4 outColor = vec4(r, g, b, 1.0);\n"); return; } diff --git a/GPU/Common/Draw2D.cpp b/GPU/Common/Draw2D.cpp index 1043858891..1164eb6552 100644 --- a/GPU/Common/Draw2D.cpp +++ b/GPU/Common/Draw2D.cpp @@ -253,6 +253,20 @@ Draw2DPipeline *Draw2D::Create2DPipeline(std::function generate); void DrawStrip2D(Draw::Texture *tex, Draw2DVertex *verts, int vertexCount, bool linearFilter, Draw2DPipeline *pipeline, float texW = 0.0f, float texH = 0.0f, int scaleFactor = 0); + + void Blit(Draw2DPipeline *pipeline, float srcX1, float srcY1, float srcX2, float srcY2, float dstX1, float dstY1, float dstX2, float dstY2, float srcWidth, float srcHeight, float dstWidth, float dstHeight, bool linear, int scaleFactor); void Ensure2DResources(); private: diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 581d485e6e..d8c63701a9 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -113,12 +113,11 @@ void FramebufferManagerCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, G VirtualFramebuffer *FramebufferManagerCommon::GetVFBAt(u32 addr) const { addr &= 0x3FFFFFFF; VirtualFramebuffer *match = nullptr; - for (size_t i = 0; i < vfbs_.size(); ++i) { - VirtualFramebuffer *v = vfbs_[i]; - if (v->fb_address == addr) { + for (auto vfb : vfbs_) { + if (vfb->fb_address == addr) { // Could check w too but whatever (actually, might very well make sense to do so, depending on context). - if (!match || v->last_frame_render > match->last_frame_render) { - match = v; + if (!match || vfb->last_frame_render > match->last_frame_render) { + match = vfb; } } } @@ -182,8 +181,8 @@ void FramebufferManagerCommon::EstimateDrawingSize(u32 fb_address, int fb_stride if (viewport_width != region_width) { // The majority of the time, these are equal. If not, let's check what we know. u32 nearest_address = 0xFFFFFFFF; - for (size_t i = 0; i < vfbs_.size(); ++i) { - const u32 other_address = vfbs_[i]->fb_address & 0x3FFFFFFF; + for (auto vfb : vfbs_) { + const u32 other_address = vfb->fb_address & 0x3FFFFFFF; if (other_address > fb_address && other_address < nearest_address) { nearest_address = other_address; } @@ -306,10 +305,9 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame // As there are no clear "framebuffer width" and "framebuffer height" registers, // we need to infer the size of the current framebuffer somehow. int drawing_width, drawing_height; - EstimateDrawingSize(params.fb_address, std::max(params.fb_stride, (u16)16), params.fb_format, params.viewportWidth, params.viewportHeight, params.regionWidth, params.regionHeight, params.scissorWidth, params.scissorHeight, drawing_width, drawing_height); + EstimateDrawingSize(params.fb_address, std::max(params.fb_stride, (u16)4), params.fb_format, params.viewportWidth, params.viewportHeight, params.regionWidth, params.regionHeight, params.scissorWidth, params.scissorHeight, drawing_width, drawing_height); gstate_c.SetCurRTOffset(0, 0); - bool vfbStrideChanged = false; if (params.fb_address == params.z_address) { // Most likely Z will not be used in this pass, as that would wreak havoc (undefined behavior for sure) @@ -319,24 +317,12 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame // Find a matching framebuffer VirtualFramebuffer *vfb = nullptr; - for (size_t i = 0; i < vfbs_.size(); ++i) { - VirtualFramebuffer *v = vfbs_[i]; - + for (auto v : vfbs_) { const u32 bpp = BufferFormatBytesPerPixel(v->fb_format); if (params.fb_address == v->fb_address && params.fb_format == v->fb_format && params.fb_stride == v->fb_stride) { vfb = v; - // Update fb stride in case it changed. - // - // In reality, this is probably a new different framebuffer... Can't really share - // data between framebuffers with different strides! (or well, we can, with complex - // conversion shaders mapping back to and from memory addresses). - if (vfb->fb_stride != params.fb_stride) { - vfb->fb_stride = params.fb_stride; - vfbStrideChanged = true; - } - if (vfb->z_address == 0 && vfb->z_stride == 0 && params.z_stride != 0) { // Got one that was created by CreateRAMFramebuffer. Since it has no depth buffer, // we just recreate it immediately. @@ -460,7 +446,7 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame // TODO: Is it worth trying to upload the depth buffer (only if it wasn't copied above..?) } - // Let's check for depth buffer overlap. Might be interesting. + // Let's check for depth buffer overlap. Might be interesting (not that interesting anymore..) bool sharingReported = false; for (size_t i = 0, end = vfbs_.size(); i < end; ++i) { if (vfbs_[i]->z_stride != 0 && params.fb_address == vfbs_[i]->z_address) { @@ -510,7 +496,7 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(const Frame vfb->dirtyAfterDisplay = true; if ((skipDrawReason & SKIPDRAW_SKIPFRAME) == 0) vfb->reallyDirtyAfterDisplay = true; - NotifyRenderFramebufferUpdated(vfb, vfbStrideChanged); + NotifyRenderFramebufferUpdated(vfb); } vfb->colorBindSeq = GetBindSeqCount(); @@ -862,21 +848,10 @@ void FramebufferManagerCommon::NotifyRenderFramebufferCreated(VirtualFramebuffer textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_CREATED); - // Ugly... - if (gstate_c.curRTWidth != vfb->width || gstate_c.curRTHeight != vfb->height) { - gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE); - } - if (gstate_c.curRTRenderWidth != vfb->renderWidth || gstate_c.curRTRenderHeight != vfb->renderHeight) { - gstate_c.Dirty(DIRTY_PROJMATRIX); - gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX); - } + NotifyRenderFramebufferUpdated(vfb); } -void FramebufferManagerCommon::NotifyRenderFramebufferUpdated(VirtualFramebuffer *vfb, bool vfbStrideChanged) { - if (vfbStrideChanged) { - textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_UPDATED); - } - +void FramebufferManagerCommon::NotifyRenderFramebufferUpdated(VirtualFramebuffer *vfb) { // ugly... if (gstate_c.curRTWidth != vfb->width || gstate_c.curRTHeight != vfb->height) { gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE); @@ -922,14 +897,7 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe } textureCache_->NotifyFramebuffer(vfb, NOTIFY_FB_UPDATED); - // ugly... is all this needed? - if (gstate_c.curRTWidth != vfb->width || gstate_c.curRTHeight != vfb->height) { - gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE); - } - if (gstate_c.curRTRenderWidth != vfb->renderWidth || gstate_c.curRTRenderHeight != vfb->renderHeight) { - gstate_c.Dirty(DIRTY_PROJMATRIX); - gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX); - } + NotifyRenderFramebufferUpdated(vfb); } void FramebufferManagerCommon::NotifyVideoUpload(u32 addr, int size, int width, GEBufferFormat fmt) { @@ -1255,8 +1223,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { // "framebuffers" sitting in RAM (created from block transfer or similar) so we only take off the kernel // and uncached bits of the address when comparing. const u32 addr = fbaddr & 0x3FFFFFFF; - for (size_t i = 0; i < vfbs_.size(); ++i) { - VirtualFramebuffer *v = vfbs_[i]; + for (auto v : vfbs_) { const u32 v_addr = v->fb_address & 0x3FFFFFFF; const u32 v_size = ColorBufferByteSize(v); if (addr >= v_addr && addr < v_addr + v_size) { @@ -1531,14 +1498,15 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, dst &= 0x3FFFFFFF; src &= 0x3FFFFFFF; + // TODO: Merge the below into FindTransferFramebuffer + VirtualFramebuffer *dstBuffer = 0; VirtualFramebuffer *srcBuffer = 0; u32 dstY = (u32)-1; u32 dstH = 0; u32 srcY = (u32)-1; u32 srcH = 0; - for (size_t i = 0; i < vfbs_.size(); ++i) { - VirtualFramebuffer *vfb = vfbs_[i]; + for (auto vfb : vfbs_) { if (vfb->fb_stride == 0) { continue; } @@ -1640,119 +1608,68 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, } } -// Can't be const, in case it has to create a vfb unfortunately. -void FramebufferManagerCommon::FindTransferFramebuffers(VirtualFramebuffer *&dstBuffer, VirtualFramebuffer *&srcBuffer, u32 dstBasePtr, int dstStride, int &dstX, int &dstY, u32 srcBasePtr, int srcStride, int &srcX, int &srcY, int &srcWidth, int &srcHeight, int &dstWidth, int &dstHeight, int bpp) { - u32 dstYOffset = -1; - u32 dstXOffset = -1; - u32 srcYOffset = -1; - u32 srcXOffset = -1; - int width = srcWidth; - int height = srcHeight; +void FramebufferManagerCommon::FindTransferFramebuffer(VirtualFramebuffer *&buffer, u32 basePtr, int stride, int &x, int &y, int &width, int &height, int bpp, bool destination) { + u32 xOffset = -1; + u32 yOffset = -1; + int transferWidth = width; + int transferHeight = height; - dstBasePtr &= 0x3FFFFFFF; - srcBasePtr &= 0x3FFFFFFF; + basePtr &= 0x3FFFFFFF; - for (size_t i = 0; i < vfbs_.size(); ++i) { - VirtualFramebuffer *vfb = vfbs_[i]; + for (auto vfb : vfbs_) { const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF; const u32 vfb_size = ColorBufferByteSize(vfb); const u32 vfb_bpp = BufferFormatBytesPerPixel(vfb->fb_format); const u32 vfb_byteStride = vfb->fb_stride * vfb_bpp; const u32 vfb_byteWidth = vfb->width * vfb_bpp; - // These heuristics are a bit annoying. - // The goal is to avoid using GPU block transfers for things that ought to be memory. - // Maybe we should even check for textures at these places instead? + if (vfb_address <= basePtr && basePtr < vfb_address + vfb_size) { + const u32 byteOffset = basePtr - vfb_address; + const u32 byteStride = stride * bpp; + const u32 memYOffset = byteOffset / byteStride; - if (vfb_address <= dstBasePtr && dstBasePtr < vfb_address + vfb_size) { - const u32 byteOffset = dstBasePtr - vfb_address; - const u32 byteStride = dstStride * bpp; - const u32 yOffset = byteOffset / byteStride; - - // Some games use mismatching bitdepths. But make sure the stride matches. + // Some games use mismatching bitdepths. But make sure the stride matches. // If it doesn't, generally this means we detected the framebuffer with too large a height. // Use bufferHeight in case of buffers that resize up and down often per frame (Valkyrie Profile.) - bool match = yOffset < dstYOffset && (int)yOffset <= (int)vfb->bufferHeight - dstHeight; + + // TODO: Surely this first comparison should be <= ? + // Or does the exact match (byteOffset == 0) case get handled elsewhere? + bool match = memYOffset < yOffset && (int)memYOffset <= (int)vfb->bufferHeight - height; if (match && vfb_byteStride != byteStride) { // Grand Knights History copies with a mismatching stride but a full line at a time. + // That's why we multiply by height, not width - this copy is a rectangle with the wrong stride but a line with the correct one. // Makes it hard to detect the wrong transfers in e.g. God of War. - if (width != dstStride || (byteStride * height != vfb_byteStride && byteStride * height != vfb_byteWidth)) { - // However, some other games write cluts to framebuffers. - // Let's catch this and upload. Otherwise reject the match. - match = (vfb->usageFlags & FB_USAGE_CLUT) != 0; - if (match) { - dstWidth = byteStride * height / vfb_bpp; - dstHeight = 1; + if (transferWidth != stride || (byteStride * transferHeight != vfb_byteStride && byteStride * transferHeight != vfb_byteWidth)) { + if (destination) { + // However, some other games write cluts to framebuffers. + // Let's catch this and upload. Otherwise reject the match. + match = (vfb->usageFlags & FB_USAGE_CLUT) != 0; + if (match) { + width = byteStride * transferHeight / vfb_bpp; + height = 1; + } + } else { + match = false; } } else { - dstWidth = byteStride * height / vfb_bpp; - dstHeight = 1; + width = byteStride * transferHeight / vfb_bpp; + height = 1; } } else if (match) { - dstWidth = width; - dstHeight = height; + width = transferWidth; + height = transferHeight; } if (match) { - dstYOffset = yOffset; - dstXOffset = dstStride == 0 ? 0 : (byteOffset / bpp) % dstStride; - dstBuffer = vfb; - } - } - if (vfb_address <= srcBasePtr && srcBasePtr < vfb_address + vfb_size) { - const u32 byteOffset = srcBasePtr - vfb_address; - const u32 byteStride = srcStride * bpp; - const u32 yOffset = byteOffset / byteStride; - bool match = yOffset < srcYOffset && (int)yOffset <= (int)vfb->bufferHeight - srcHeight; - if (match && vfb_byteStride != byteStride) { - if (width != srcStride || (byteStride * height != vfb_byteStride && byteStride * height != vfb_byteWidth)) { - match = false; - } else { - srcWidth = byteStride * height / vfb_bpp; - srcHeight = 1; - } - } else if (match) { - srcWidth = width; - srcHeight = height; - } - if (match) { - srcYOffset = yOffset; - srcXOffset = srcStride == 0 ? 0 : (byteOffset / bpp) % srcStride; - srcBuffer = vfb; + xOffset = stride == 0 ? 0 : (byteOffset / bpp) % stride; + yOffset = memYOffset; + buffer = vfb; } } } - if (srcBuffer && !dstBuffer) { - if (PSP_CoreParameter().compat.flags().BlockTransferAllowCreateFB || - (PSP_CoreParameter().compat.flags().IntraVRAMBlockTransferAllowCreateFB && - Memory::IsVRAMAddress(srcBuffer->fb_address) && Memory::IsVRAMAddress(dstBasePtr))) { - GEBufferFormat ramFormat; - // Try to guess the appropriate format. We only know the bpp from the block transfer command (16 or 32 bit). - if (bpp == 4) { - // Only one possibility unless it's doing split pixel tricks (which we could detect through stride maybe). - ramFormat = GE_FORMAT_8888; - } else if (srcBuffer->fb_format != GE_FORMAT_8888) { - // We guess that the game will interpret the data the same as it was in the source of the copy. - // Seems like a likely good guess, and works in Test Drive Unlimited. - ramFormat = srcBuffer->fb_format; - } else { - // No info left - just fall back to something. But this is definitely split pixel tricks. - ramFormat = GE_FORMAT_5551; - } - dstBuffer = CreateRAMFramebuffer(dstBasePtr, dstWidth, dstHeight, dstStride, ramFormat); - } - } - - if (dstBuffer) - dstBuffer->last_frame_used = gpuStats.numFlips; - - if (dstYOffset != (u32)-1) { - dstY += dstYOffset; - dstX += dstXOffset; - } - if (srcYOffset != (u32)-1) { - srcY += srcYOffset; - srcX += srcXOffset; + if (yOffset != (u32)-1) { + x += xOffset; + y += yOffset; } } @@ -1956,8 +1873,33 @@ bool FramebufferManagerCommon::NotifyBlockTransferBefore(u32 dstBasePtr, int dst int dstWidth = width; int dstHeight = height; - // This looks at the compat flags BlockTransferAllowCreateFB*. - FindTransferFramebuffers(dstBuffer, srcBuffer, dstBasePtr, dstStride, dstX, dstY, srcBasePtr, srcStride, srcX, srcY, srcWidth, srcHeight, dstWidth, dstHeight, bpp); + // These modify the X/Y/W/H parameters depending on the memory offset of the base pointers from the actual buffers. + FindTransferFramebuffer(srcBuffer, srcBasePtr, srcStride, srcX, srcY, srcWidth, srcHeight, bpp, false); + FindTransferFramebuffer(dstBuffer, dstBasePtr, dstStride, dstX, dstY, dstWidth, dstHeight, bpp, true); + + if (srcBuffer && !dstBuffer) { + if (PSP_CoreParameter().compat.flags().BlockTransferAllowCreateFB || + (PSP_CoreParameter().compat.flags().IntraVRAMBlockTransferAllowCreateFB && + Memory::IsVRAMAddress(srcBuffer->fb_address) && Memory::IsVRAMAddress(dstBasePtr))) { + GEBufferFormat ramFormat; + // Try to guess the appropriate format. We only know the bpp from the block transfer command (16 or 32 bit). + if (bpp == 4) { + // Only one possibility unless it's doing split pixel tricks (which we could detect through stride maybe). + ramFormat = GE_FORMAT_8888; + } else if (srcBuffer->fb_format != GE_FORMAT_8888) { + // We guess that the game will interpret the data the same as it was in the source of the copy. + // Seems like a likely good guess, and works in Test Drive Unlimited. + ramFormat = srcBuffer->fb_format; + } else { + // No info left - just fall back to something. But this is definitely split pixel tricks. + ramFormat = GE_FORMAT_5551; + } + dstBuffer = CreateRAMFramebuffer(dstBasePtr, dstWidth, dstHeight, dstStride, ramFormat); + } + } + + if (dstBuffer) + dstBuffer->last_frame_used = gpuStats.numFlips; if (dstBuffer && srcBuffer) { if (srcBuffer == dstBuffer) { @@ -2032,13 +1974,15 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS } if (MayIntersectFramebuffer(srcBasePtr) || MayIntersectFramebuffer(dstBasePtr)) { + // TODO: Figure out how we can avoid repeating the search here. VirtualFramebuffer *dstBuffer = 0; VirtualFramebuffer *srcBuffer = 0; int srcWidth = width; int srcHeight = height; int dstWidth = width; int dstHeight = height; - FindTransferFramebuffers(dstBuffer, srcBuffer, dstBasePtr, dstStride, dstX, dstY, srcBasePtr, srcStride, srcX, srcY, srcWidth, srcHeight, dstWidth, dstHeight, bpp); + FindTransferFramebuffer(srcBuffer, srcBasePtr, srcStride, srcX, srcY, srcWidth, srcHeight, bpp, false); + FindTransferFramebuffer(dstBuffer, dstBasePtr, dstStride, dstX, dstY, dstWidth, dstHeight, bpp, true); // A few games use this INSTEAD of actually drawing the video image to the screen, they just blast it to // the backbuffer. Detect this and have the framebuffermanager draw the pixels. @@ -2499,9 +2443,7 @@ void FramebufferManagerCommon::RebindFramebuffer(const char *tag) { std::vector FramebufferManagerCommon::GetFramebufferList() const { std::vector list; - for (size_t i = 0; i < vfbs_.size(); ++i) { - VirtualFramebuffer *vfb = vfbs_[i]; - + for (auto vfb : vfbs_) { FramebufferInfo info; info.fb_address = vfb->fb_address; info.z_address = vfb->z_address; @@ -2724,17 +2666,6 @@ void FramebufferManagerCommon::BlitUsingRaster( draw_->GetFramebufferDimensions(src, &srcW, &srcH); draw_->GetFramebufferDimensions(dest, &destW, &destH); - float dX = 1.0f / (float)destW; - float dY = 1.0f / (float)destH; - float sX = 1.0f / (float)srcW; - float sY = 1.0f / (float)srcH; - Draw2DVertex vtx[4] = { - { -1.0f + 2.0f * dX * destX1, -(1.0f - 2.0f * dY * destY1), sX * srcX1, sY * srcY1 }, - { -1.0f + 2.0f * dX * destX2, -(1.0f - 2.0f * dY * destY1), sX * srcX2, sY * srcY1 }, - { -1.0f + 2.0f * dX * destX1, -(1.0f - 2.0f * dY * destY2), sX * srcX1, sY * srcY2 }, - { -1.0f + 2.0f * dX * destX2, -(1.0f - 2.0f * dY * destY2), sX * srcX2, sY * srcY2 }, - }; - // Unbind the texture first to avoid the D3D11 hazard check (can't set render target to things bound as textures and vice versa, not even temporarily). draw_->BindTexture(0, nullptr); // This will get optimized away in case it's already bound (in VK and GL at least..) @@ -2745,7 +2676,7 @@ void FramebufferManagerCommon::BlitUsingRaster( draw_->SetViewports(1, &vp); draw_->SetScissorRect(0, 0, (int)dest->Width(), (int)dest->Height()); - draw2D_.DrawStrip2D(nullptr, vtx, 4, linearFilter, pipeline, src->Width(), src->Height(), renderScaleFactor_); + draw2D_.Blit(pipeline, srcX1, srcY1, srcX2, srcY2, destX1, destY1, destX2, destY2, (float)srcW, (float)srcH, (float)destW, (float)destH, linearFilter , renderScaleFactor_); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); } diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 4775d07e98..eb070b8347 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -275,10 +275,15 @@ public: void UpdateFromMemory(u32 addr, int size, bool safe); void ApplyClearToMemory(int x1, int y1, int x2, int y2, u32 clearColor); bool PerformStencilUpload(u32 addr, int size, StencilUpload flags); + // Returns true if it's sure this is a direct FBO->FBO transfer and it has already handle it. // In that case we hardly need to actually copy the bytes in VRAM, they will be wrong anyway (unless // read framebuffers is on, in which case this should always return false). + // If this returns false, a memory copy will happen and NotifyBlockTransferAfter will be called. bool NotifyBlockTransferBefore(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int w, int h, int bpp, u32 skipDrawReason); + + // This gets called after the memory copy, in case NotifyBlockTransferBefore returned false. + // Otherwise it doesn't get called. void NotifyBlockTransferAfter(u32 dstBasePtr, int dstStride, int dstX, int dstY, u32 srcBasePtr, int srcStride, int srcX, int srcY, int w, int h, int bpp, u32 skipDrawReason); bool BindFramebufferAsColorTexture(int stage, VirtualFramebuffer *framebuffer, int flags); @@ -402,7 +407,7 @@ protected: u32 ColorBufferByteSize(const VirtualFramebuffer *vfb) const; void NotifyRenderFramebufferCreated(VirtualFramebuffer *vfb); - void NotifyRenderFramebufferUpdated(VirtualFramebuffer *vfb, bool vfbFormatChanged); + void NotifyRenderFramebufferUpdated(VirtualFramebuffer *vfb); void NotifyRenderFramebufferSwitched(VirtualFramebuffer *prevVfb, VirtualFramebuffer *vfb, bool isClearingDepth); void BlitFramebufferDepth(VirtualFramebuffer *src, VirtualFramebuffer *dst); @@ -412,7 +417,9 @@ protected: bool ShouldDownloadFramebuffer(const VirtualFramebuffer *vfb) const; void DownloadFramebufferOnSwitch(VirtualFramebuffer *vfb); - void FindTransferFramebuffers(VirtualFramebuffer *&dstBuffer, VirtualFramebuffer *&srcBuffer, u32 dstBasePtr, int dstStride, int &dstX, int &dstY, u32 srcBasePtr, int srcStride, int &srcX, int &srcY, int &srcWidth, int &srcHeight, int &dstWidth, int &dstHeight, int bpp); + + void FindTransferFramebuffer(VirtualFramebuffer *&srcBuffer, u32 srcBasePtr, int srcStride, int &srcX, int &srcY, int &srcWidth, int &srcHeight, int bpp, bool destination); + VirtualFramebuffer *FindDownloadTempBuffer(VirtualFramebuffer *vfb); virtual void UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) {} diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 87746043cb..b3cd653dbf 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -1967,9 +1967,26 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer draw_->BindSamplerStates(0, 1, &nearest); draw_->BindSamplerStates(1, 1, &clutSampler); - textureShaderCache_->ApplyShader(textureShader, - framebuffer->bufferWidth, framebuffer->bufferHeight, framebuffer->renderWidth, framebuffer->renderHeight, - gstate_c.vertBounds, gstate_c.curTextureXOffset, gstate_c.curTextureYOffset); + // If min is not < max, then we don't have values (wasn't set during decode.) + const KnownVertexBounds &bounds = gstate_c.vertBounds; + float u1 = 0.0f; + float v1 = 0.0f; + float u2 = framebuffer->renderWidth; + float v2 = framebuffer->renderHeight; + if (bounds.minV < bounds.maxV) { + u1 = bounds.minU + gstate_c.curTextureXOffset; + v1 = bounds.minV + gstate_c.curTextureYOffset; + u2 = bounds.maxU + gstate_c.curTextureXOffset; + v2 = bounds.maxV + gstate_c.curTextureYOffset; + // We need to reapply the texture next time since we cropped UV. + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); + } + u1 *= framebuffer->renderScaleFactor; + v1 *= framebuffer->renderScaleFactor; + u2 *= framebuffer->renderScaleFactor; + v2 *= framebuffer->renderScaleFactor; + + draw2D_->Blit(textureShader, u1, v1, u2, v2, u1, v1, u2, v2, framebuffer->renderWidth, framebuffer->renderHeight, framebuffer->renderWidth, framebuffer->renderHeight, false, framebuffer->renderScaleFactor); draw_->BindTexture(0, nullptr); framebufferManager_->RebindFramebuffer("ApplyTextureFramebuffer"); diff --git a/GPU/Common/TextureShaderCommon.cpp b/GPU/Common/TextureShaderCommon.cpp index ab7a280a53..f88838fad6 100644 --- a/GPU/Common/TextureShaderCommon.cpp +++ b/GPU/Common/TextureShaderCommon.cpp @@ -247,51 +247,3 @@ std::string TextureShaderCache::DebugGetShaderString(std::string idstr, DebugSha return ""; } } - -void TextureShaderCache::ApplyShader(Draw2DPipeline *pipeline, float bufferW, float bufferH, int renderW, int renderH, const KnownVertexBounds &bounds, u32 uoff, u32 voff) { - Draw2DVertex verts[4] = { - {-1, -1, 0, 0 }, - { 1, -1, 1, 0 }, - {-1, 1, 0, 1 }, - { 1, 1, 1, 1 }, - }; - - // If min is not < max, then we don't have values (wasn't set during decode.) - if (bounds.minV < bounds.maxV) { - const float invWidth = 1.0f / bufferW; - const float invHeight = 1.0f / bufferH; - // Inverse of half = double. - const float invHalfWidth = invWidth * 2.0f; - const float invHalfHeight = invHeight * 2.0f; - - const int u1 = bounds.minU + uoff; - const int v1 = bounds.minV + voff; - const int u2 = bounds.maxU + uoff; - const int v2 = bounds.maxV + voff; - - const float left = u1 * invHalfWidth - 1.0f; - const float right = u2 * invHalfWidth - 1.0f; - const float top = v1 * invHalfHeight - 1.0f; - const float bottom = v2 * invHalfHeight - 1.0f; - - const float uvleft = u1 * invWidth; - const float uvright = u2 * invWidth; - const float uvtop = v1 * invHeight; - const float uvbottom = v2 * invHeight; - - // Points are: BL, BR, TR, TL. - verts[0] = Draw2DVertex{ left, bottom, uvleft, uvbottom }; - verts[1] = Draw2DVertex{ right, bottom, uvright, uvbottom }; - verts[2] = Draw2DVertex{ left, top, uvleft, uvtop }; - verts[3] = Draw2DVertex{ right, top, uvright, uvtop }; - - // We need to reapply the texture next time since we cropped UV. - gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); - } - - Draw::Viewport vp{ 0.0f, 0.0f, (float)renderW, (float)renderH, 0.0f, 1.0f }; - draw_->BindPipeline(pipeline->pipeline); - draw_->SetViewports(1, &vp); - draw_->SetScissorRect(0, 0, renderW, renderH); - draw_->DrawUP((const uint8_t *)verts, 4); -} diff --git a/GPU/Common/TextureShaderCommon.h b/GPU/Common/TextureShaderCommon.h index 5e0c5fe476..2176fea37d 100644 --- a/GPU/Common/TextureShaderCommon.h +++ b/GPU/Common/TextureShaderCommon.h @@ -48,8 +48,6 @@ public: Draw::SamplerState *GetSampler(bool linearFilter); - void ApplyShader(Draw2DPipeline *pipeline, float bufferW, float bufferH, int renderW, int renderH, const KnownVertexBounds &bounds, u32 uoff, u32 voff); - void Clear(); void Decimate(); std::vector DebugGetShaderIDs(DebugShaderType type); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 8ceac22d47..4f1ebadf2e 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -2773,15 +2773,6 @@ void GPUCommon::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat f } void GPUCommon::DoBlockTransfer(u32 skipDrawReason) { - // TODO: This is used a lot to copy data around between render targets and textures, - // and also to quickly load textures from RAM to VRAM. So we should do checks like the following: - // * Does dstBasePtr point to an existing texture? If so maybe reload it immediately. - // - // * Does srcBasePtr point to a render target, and dstBasePtr to a texture? If so - // either copy between rt and texture or reassign the texture to point to the render target - // - // etc.... - u32 srcBasePtr = gstate.getTransferSrcAddress(); u32 srcStride = gstate.getTransferSrcStride(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index f4f2b330c5..bc89ed7b4f 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -594,7 +594,7 @@ void GameSettingsScreen::CreateViews() { static const char *bufFilters[] = { "Linear", "Nearest", }; graphicsSettings->Add(new PopupMultiChoice(&g_Config.iBufFilter, gr->T("Screen Scaling Filter"), bufFilters, 1, ARRAY_SIZE(bufFilters), gr->GetName(), screenManager())); -#ifdef PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) bool showCardboardSettings = deviceType != DEVICE_TYPE_VR; #else // If you enabled it through the ini, you can see this. Useful for testing. diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index fa40f7130c..eea439c9fd 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -479,6 +479,7 @@ + @@ -594,6 +595,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index b663fdb11c..2c14f4a533 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -107,6 +107,7 @@ + @@ -404,6 +405,7 @@ +