Merge pull request #751 from caldog20/aa64-collab

Arm64 Dynarec PR
This commit is contained in:
Nicolas Noble
2022-04-01 22:23:41 -07:00
committed by GitHub
27 changed files with 3509 additions and 9 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: Linux CI
name: Linux CI coverage
on:
push:
+30
View File
@@ -0,0 +1,30 @@
name: Linux CI cross
on:
push:
branches:
- main
pull_request:
jobs:
cross-arm64:
runs-on: ubuntu-latest
container:
image: grumpycoders/pcsx-redux-build-cross:latest
env:
CROSS: arm64
PKG_CONFIG_SYSROOT_DIR: /opt/cross/sysroot
PKG_CONFIG_PATH:
PKG_CONFIG_LIBDIR: /opt/cross/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
CC: aarch64-linux-gnu-gcc --sysroot=/opt/cross/sysroot
CXX: aarch64-linux-gnu-g++ --sysroot=/opt/cross/sysroot
TEST_RESULTS: /tmp/test-results
steps:
- uses: actions/checkout@v1
- uses: n1hility/cancel-previous-runs@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch submodules
run: git submodule update --init --recursive
- run: |
make -j 2 all pcsx-redux-tests
+3
View File
@@ -73,3 +73,6 @@
[submodule "third_party/curl"]
path = third_party/curl
url = https://github.com/curl/curl
[submodule "third_party/vixl"]
path = third_party/vixl
url = https://github.com/wheremyfoodat/vixl
+1
View File
@@ -12,6 +12,7 @@ Ryusan
Peach / wheremyfoodat
Mariano
John "Skitchin" Baumann
Caleb Yates / caldog20
PCSX-Reloaded
+30 -2
View File
@@ -1,8 +1,10 @@
TARGET := pcsx-redux
BUILD ?= Release
DESTDIR ?= /usr/local
CROSS ?= none
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))
CC_IS_CLANG := $(shell $(CC) --version | grep -q clang && echo true || echo false)
@@ -83,11 +85,17 @@ LDFLAGS_asan += -fsanitize=address
CPPFLAGS += $(CPPFLAGS_$(BUILD)) -pthread
LDFLAGS += $(LDFLAGS_$(BUILD)) -pthread
ifeq ($(CROSS),arm64)
CPPFLAGS += -fPIC -Wl,-rpath-link,/opt/cross/sysroot/usr/lib/aarch64-linux-gnu -L/opt/cross/sysroot/usr/lib/aarch64-linux-gnu
LDFLAGS += -fPIC -Wl,-rpath-link,/opt/cross/sysroot/usr/lib/aarch64-linux-gnu -L/opt/cross/sysroot/usr/lib/aarch64-linux-gnu
endif
LD := $(CXX)
SRCS := $(call rwildcard,src/,*.cc)
SRCS += third_party/fmt/src/os.cc third_party/fmt/src/format.cc
IMGUI_SRCS += $(wildcard third_party/imgui/*.cpp)
VIXL_SRCS := $(call rwildcard, third_party/vixl/src,*.cc)
SRCS += $(IMGUI_SRCS)
SRCS += $(wildcard third_party/libelfin/*.cc)
SRCS += third_party/clip/clip.cpp
@@ -111,6 +119,21 @@ ifeq ($(UNAME_S),Darwin)
else
SRCS += third_party/clip/clip_x11.cpp
endif
ifeq ($(UNAME_M),aarch64)
SRCS += $(VIXL_SRCS)
CPPFLAGS += -DVIXL_INCLUDE_TARGET_AARCH64 -DVIXL_CODE_BUFFER_MMAP
CPPFLAGS += -Ithird_party/vixl/src -Ithird_party/vixl/src/aarch64
endif
ifeq ($(UNAME_M),arm64)
SRCS += $(VIXL_SRCS)
CPPFLAGS += -DVIXL_INCLUDE_TARGET_AARCH64 -DVIXL_CODE_BUFFER_MMAP
CPPFLAGS += -Ithird_party/vixl/src -Ithird_party/vixl/src/aarch64
endif
ifeq ($(CROSS),arm64)
SRCS += $(VIXL_SRCS)
CPPFLAGS += -DVIXL_INCLUDE_TARGET_AARCH64 -DVIXL_CODE_BUFFER_MMAP
CPPFLAGS += -Ithird_party/vixl/src -Ithird_party/vixl/src/aarch64
endif
SUPPORT_SRCS := $(call rwildcard,src/support/,*.cc)
SUPPORT_SRCS += third_party/fmt/src/os.cc third_party/fmt/src/format.cc
SUPPORT_SRCS += third_party/ucl/src/n2e_99.c third_party/ucl/src/alloc.c
@@ -125,7 +148,7 @@ SUPPORT_OBJECTS += $(patsubst %.cc,%.o,$(filter %.cc,$(SUPPORT_SRCS)))
SUPPORT_OBJECTS += third_party/luajit/src/libluajit.a
NONMAIN_OBJECTS := $(filter-out src/main/mainthunk.o,$(OBJECTS))
IMGUI_OBJECTS := $(patsubst %.cpp,%.o,$(filter %.cpp,$(IMGUI_SRCS)))
VIXL_OBJECTS := $(patsubst %.cc,%.o,$(filter %.cc,$(VIXL_SRCS)))
$(IMGUI_OBJECTS): EXTRA_CPPFLAGS := $(IMGUI_CPPFLAGS)
TESTS_SRC := $(call rwildcard,tests/,*.cc)
@@ -177,8 +200,13 @@ appimage:
DESTDIR=AppDir/usr $(MAKE) $(MAKEOPTS) install
appimage-builder --skip-tests
ifeq ($(CROSS),arm64)
third_party/luajit/src/libluajit.a:
$(MAKE) $(MAKEOPTS) -C third_party/luajit/src amalg HOST_CC=gcc-10 CROSS=aarch64-linux-gnu- TARGET_CFLAGS=--sysroot=/opt/cross/sysroot BUILDMODE=static CFLAGS=$(LUAJIT_CFLAGS) XCFLAGS=-DLUAJIT_ENABLE_GC64 MACOSX_DEPLOYMENT_TARGET=10.15
else
third_party/luajit/src/libluajit.a:
$(MAKE) $(MAKEOPTS) -C third_party/luajit/src amalg CC=$(CC) BUILDMODE=static CFLAGS=$(LUAJIT_CFLAGS) XCFLAGS=-DLUAJIT_ENABLE_GC64 MACOSX_DEPLOYMENT_TARGET=10.15
endif
$(TARGET): $(OBJECTS)
$(LD) -o $@ $(OBJECTS) $(LDFLAGS)
@@ -254,4 +282,4 @@ ifeq ($(HAS_SUBMODULES), true)
endif
endif
endif
endif
endif
+1 -1
View File
@@ -115,7 +115,7 @@ When Sony released the Playstation mini recently, I came to realize two things:
The codebase still requires a lot of cleanup, and the current product isn't properly usable yet. Despite that, a lot can already be achieved using the product in its current state. If you want to help with localization, you can find the translation project [on transifex](https://www.transifex.com/grumpycoders/pcsx-redux/languages/).
### What works?
- x86 dynarec, x86-64 dynarec
- Dynamic Recompiler (x86-32, x86-64, experimental arm64 support)
- interpreted CPU
- software GPU
- VRAM viewer and debugger
+5
View File
@@ -0,0 +1,5 @@
PKG_CONFIG_SYSROOT_DIR=/opt/cross/sysroot
PKG_CONFIG_PATH=
PKG_CONFIG_LIBDIR=/opt/cross/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig
CC=aarch64-linux-gnu-gcc --sysroot=/opt/cross/sysroot
CXX=aarch64-linux-gnu-g++ --sysroot=/opt/cross/sysroot
+23
View File
@@ -0,0 +1,23 @@
@echo off
set OLDCWD=%cd%
cd %~dp0
set ROOT=%cd%
cd %OLDCWD%
set REL=%OLDCWD%
SETLOCAL EnableDelayedExpansion
set MAT=
set UPP=
for /f "tokens=*" %%a in ('echo.%ROOT:\=^&echo.%') do (
set SUB=!SUB!%%a\
call set TMP=%%src:!SUB!=%%
set MAT=!SUB!
)
set REL=%UPP%!REL:%MAT%=!
if "!REL!" EQU "!ROOT!" (set REL=) ELSE (set "REL=!REL:\=/!")
set REL=/!REL!
docker pull grumpycoders/pcsx-redux-build-cross:latest
docker run --rm --env-file "%ROOT%/cross-env.list" -i -w"/project%REL%" -v "%ROOT%:/project" grumpycoders/pcsx-redux-build-cross make CROSS=arm64 %*
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
ROOT=$(dirname $0)
CWD=$(pwd)
cd $ROOT
ROOT=$(pwd)
cd $CWD
docker pull grumpycoders/pcsx-redux-build-cross:latest
docker run --rm --env-file ${ROOT}/cross-env.list -i -w/project${CWD#$ROOT} -v "${ROOT}:/project" -u `id -u`:`id -g` grumpycoders/pcsx-redux-build-cross make CROSS=arm64 $@
+176
View File
@@ -0,0 +1,176 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* 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; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#pragma once
#include "core/r3000a.h"
#ifdef DYNAREC_AA64
#include "support/windowswrapper.h"
#ifndef _WIN32
#include <sys/mman.h> // For mmap/mprotect
#endif
#include "vixl/src/aarch64/macro-assembler-aarch64.h"
using namespace vixl::aarch64;
// Allocate 32MB for the code cache.
constexpr size_t codeCacheSize = 32 * 1024 * 1024;
constexpr size_t allocSize = codeCacheSize + 0x1000;
// Allocate a bit more memory to be safe.
// This has to be static so JIT code will be close enough to the executable to address stuff with pc-relative accesses
alignas(4096) static uint8_t s_codeCache[allocSize];
class Emitter : public MacroAssembler {
public:
#ifdef __APPLE__
Emitter() : MacroAssembler(reinterpret_cast<uint8_t*>(makeBuffer()), allocSize) {}
static void* makeBuffer() {
void *ptr = mmap(s_codeCache, allocSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return (ptr == MAP_FAILED) ? nullptr : ptr;
}
#else
Emitter() : MacroAssembler(s_codeCache, allocSize) {}
#endif
void L(Label& l) { Bind(&l); }
template <typename T = void*>
T getCurr() {
return GetCursorAddress<T>();
}
template <typename T = void*>
T getCode() {
return GetBuffer()->GetStartAddress<T>();
}
size_t getSize() { return GetCursorOffset(); }
void ready() { FinalizeCode(); }
bool setRWX() {
#if defined(_WIN32)
DWORD oldProtect; // Unused, but VirtualProtect wants somewhere to store it anyways.
return VirtualProtect(s_codeCache, allocSize, PAGE_EXECUTE_READWRITE, &oldProtect) != 0;
#elif !defined(__APPLE__)
return mprotect(s_codeCache, allocSize, PROT_READ | PROT_WRITE | PROT_EXEC) != -1;
#endif
}
void setRW() {
GetBuffer()->SetWritable();
}
void setRX() {
GetBuffer()->SetExecutable();
}
void align() { GetBuffer()->Align(); }
#define MAKE_CONDITIONAL_BRANCH(properName, alias) \
void b##properName(Label& l) { b(&l, properName); } \
void b##alias(Label& l) { b##properName(l); }
MAKE_CONDITIONAL_BRANCH(ne, nz);
MAKE_CONDITIONAL_BRANCH(eq, z);
MAKE_CONDITIONAL_BRANCH(mi, s);
MAKE_CONDITIONAL_BRANCH(pl, ns);
MAKE_CONDITIONAL_BRANCH(cs, hs);
MAKE_CONDITIONAL_BRANCH(cc, lo);
void bvc(Label& l) { b(&l, vc); }
void bvs(Label& l) { b(&l, vs); }
void bhi(Label& l) { b(&l, hi); }
void bls(Label& l) { b(&l, ls); }
void bge(Label& l) { b(&l, ge); }
void blt(Label& l) { b(&l, lt); }
void bgt(Label& l) { b(&l, gt); }
void ble(Label& l) { b(&l, le); }
void bal(Label& l) { b(&l); }
#undef MAKE_CONDITIONAL_BRANCH
void dumpBuffer() {
std::ofstream file("DynarecOutput.dump", std::ios::binary); // Make a file for our dump
file.write(getCode<const char*>(), getSize()); // Write the code buffer to the dump
}
// Returns a signed integer that shows how many bytes of free space are left in the code buffer
int64_t getRemainingSize() { return (int64_t)codeCacheSize - (int64_t)getSize(); }
// Adds "value" to "source" and stores the result in dest
// Uses add if the value is non-zero, or mov otherwise
void moveAndAdd(Register dest, Register source, uint32_t value) {
if (value != 0) {
Add(dest, source, value);
} else {
Mov(dest, source);
}
}
// dest = source & value
// Optimizes to Uxt or xor wherever possible
void andImm(Register dest, Register source, uint32_t value) {
switch (value) {
case 0:
Mov(dest, 0);
break;
case 0xFF:
Uxtb(dest, source);
break;
case 0xFFFF:
Uxth(dest, source);
break;
default:
And(dest, source, value);
break;
}
}
// Logical OR dest by value (Skip the OR if value == 0)
void orImm(Register dest, uint32_t value) {
if (value != 0) {
Orr(dest, dest, value);
}
}
// Logical OR source by value (
void orImm(Register dest, Register source, uint32_t value) {
if (value != 0) {
Orr(dest, source, value);
} else if (!dest.Is(source)) {
Mov(dest, source);
}
}
// dest = value - source
// Optimizes the value == 0 case, thrases w0 but not FLAGs
void reverseSub(Register dest, Register source, uint32_t value) {
if (value == 0) {
Neg(dest, source);
} else {
Mov(w0, value);
Sub(dest, w0, source);
}
}
// Emit a trap instruction that gdb/lldb/Visual Studio can interpret as a breakpoint
void breakpoint() { Brk(0); }
};
#endif // DYNAREC_AA64
+334
View File
@@ -0,0 +1,334 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* 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; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "recompiler.h"
#if defined(DYNAREC_AA64)
#include "core/gte.h"
#define COP2_CONTROL_OFFSET(reg) ((uintptr_t)&m_regs.CP2C.r[(reg)] - (uintptr_t)this)
#define COP2_DATA_OFFSET(reg) ((uintptr_t)&m_regs.CP2D.r[(reg)] - (uintptr_t)this)
void DynaRecCPU::recCOP2() {
const auto func = m_recGTE[m_regs.code & 0x3F]; // Look up the opcode in our decoding LUT
(*this.*func)(); // Jump into the handler to recompile it
}
void DynaRecCPU::recGTEMove() {
switch (_Rs_) {
case 0:
recMFC2();
break;
case 2:
recCFC2();
break;
case 4:
recMTC2();
break;
case 6:
recCTC2();
break;
default:
recUnknown();
break;
}
}
void DynaRecCPU::recCTC2() {
if (m_gprs[_Rt_].isConst()) {
switch (_Rd_) {
case 4: // These registers are signed 16-bit values. Reading from them returns their value sign-extended to
// 32 bits
case 12:
case 20:
case 26:
case 27:
case 29:
case 30:
gen.Mov(w0, (uint32_t)(int16_t)m_gprs[_Rt_].val);
gen.Str(w0, MemOperand(contextPointer, COP2_CONTROL_OFFSET(_Rd_)));
break;
case 31: { // Write to FLAG - Set low 12 bits to 0 and fix up the error flag
uint32_t value = m_gprs[_Rt_].val & 0x7ffff000;
if ((value & 0x7f87e000) != 0) {
value |= 0x80000000;
}
gen.Mov(w0, value);
gen.Str(w0, MemOperand(contextPointer, COP2_CONTROL_OFFSET(31)));
break;
}
default:
gen.Mov(w0, m_gprs[_Rt_].val);
gen.Str(w0, MemOperand(contextPointer, COP2_CONTROL_OFFSET(_Rd_)));
break;
}
} else {
allocateReg(_Rt_);
switch (_Rd_) {
case 4: // These registers are signed 16-bit values. Reading from them returns their value sign-extended to
// 32 bits
case 12:
case 20:
case 26:
case 27:
case 29:
case 30:
gen.Sxth(w0, m_gprs[_Rt_].allocatedReg); // Sign extend value from 16 to 32 bits
gen.Str(w0, MemOperand(contextPointer, COP2_CONTROL_OFFSET(_Rd_)));
break;
case 31: // Write to FLAG - Set low 12 bits to 0 and fix up the error flag
gen.And(w1, m_gprs[_Rt_].allocatedReg, 0x7fffe000);
gen.And(w0, m_gprs[_Rt_].allocatedReg, 0x7ffff000);
gen.Orr(w2, w0, 0x80000000);
gen.Ands(w1, w1, 0xff87ffff);
gen.Csel(w0, w2, w0, ne);
gen.Str(w0, MemOperand(contextPointer, COP2_CONTROL_OFFSET(31)));
break;
default:
gen.Str(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_CONTROL_OFFSET(_Rd_)));
break;
}
}
}
void DynaRecCPU::recMTC2() {
switch (_Rd_) {
case 15:
gen.Ldr(x0, MemOperand(contextPointer, COP2_DATA_OFFSET(13))); // SXY0 = SXY1 and SXY1 = SXY2
gen.Str(x0, MemOperand(contextPointer, COP2_DATA_OFFSET(12)));
// SXY2 = val
if (m_gprs[_Rt_].isConst()) {
gen.Mov(w1, m_gprs[_Rt_].val);
gen.Str(w1, MemOperand(contextPointer, COP2_DATA_OFFSET(14)));
} else {
allocateReg(_Rt_);
gen.Str(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_DATA_OFFSET(14)));
}
break;
case 28: // IRGB
if (m_gprs[_Rt_].isConst()) { // Calculate IR1/IR2/IR3 values and write them back
const auto value = m_gprs[_Rt_].val;
const auto IR1 = (value & 0x1f) << 7;
const auto IR2 = (value & 0x3e0) << 2;
const auto IR3 = (value & 0x7c00) >> 3;
gen.Mov(w1, IR1);
gen.Mov(w2, IR2);
gen.Mov(w3, IR3);
gen.Str(w1, MemOperand(contextPointer, COP2_DATA_OFFSET(9)));
gen.Str(w2, MemOperand(contextPointer, COP2_DATA_OFFSET(10)));
gen.Str(w3, MemOperand(contextPointer, COP2_DATA_OFFSET(11)));
} else {
allocateReg(_Rt_);
gen.And(w0, m_gprs[_Rt_].allocatedReg, 0x1f); // Calculate IR1
gen.Lsl(w0, w0, 7);
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(9)));
gen.Lsl(w0, m_gprs[_Rt_].allocatedReg, 2); // Calculate IR2
gen.And(w0, w0, 0xf80); // The above LSL shifted w0 by 2 first, so we adjust the mask
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(10)));
gen.Lsr(w0, m_gprs[_Rt_].allocatedReg, 3); // Calculate IR3
gen.And(w0, w0, 0xf80);
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(11)));
}
break;
case 30:
if (m_gprs[_Rt_].isConst()) {
const auto result = PCSX::GTE::countLeadingBits(m_gprs[_Rt_].val);
gen.Mov(w0, result);
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(31))); // Set LZCR
} else {
allocateReg(_Rt_);
// value = ~value if the msb is set, then store value in w0
gen.Eor(w0, m_gprs[_Rt_].allocatedReg, Operand(m_gprs[_Rt_].allocatedReg, ASR, 31));
gen.Clz(w0, w0); // Count leading Zeros
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(31))); // Write result to LZCR
}
break;
case 31:
return;
}
if (m_gprs[_Rt_].isConst()) {
gen.Mov(w0, m_gprs[_Rt_].val);
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(_Rd_)));
} else {
allocateReg(_Rt_);
gen.Str(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_DATA_OFFSET(_Rd_)));
}
}
static uint32_t MFC2Wrapper(int reg) { return PCSX::g_emulator->m_gte->MFC2(reg); }
void DynaRecCPU::recMFC2() {
if (_Rt_) {
allocateRegWithoutLoad(_Rt_);
m_gprs[_Rt_].setWriteback(true);
}
switch (_Rd_) {
case 1:
case 3:
case 5:
case 8:
case 9:
case 10:
case 11:
if (_Rt_) {
gen.Ldrsh(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_DATA_OFFSET(_Rd_)));
}
break;
case 7:
case 16:
case 17:
case 18:
case 19:
if (_Rt_) {
gen.Ldrh(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_DATA_OFFSET(_Rd_)));
}
break;
case 15: // Return SXY2 from SXYP
if (_Rt_) {
gen.Ldr(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_DATA_OFFSET(14)));
}
break;
case 28:
case 29: // Fallback for IRGB/ORGB
gen.Mov(arg1, _Rd_);
call(MFC2Wrapper); // result in w0
if (_Rt_) {
allocateRegWithoutLoad(_Rt_); // Reallocate the reg in case the call thrashed it
m_gprs[_Rt_].setWriteback(true);
gen.Mov(m_gprs[_Rt_].allocatedReg, w0);
}
break;
default:
if (_Rt_) {
gen.Ldr(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_DATA_OFFSET(_Rd_)));
}
break;
}
}
void DynaRecCPU::recCFC2() {
if (_Rt_) {
maybeCancelDelayedLoad(_Rt_);
allocateRegWithoutLoad(_Rt_);
m_gprs[_Rt_].setWriteback(true);
gen.Ldr(m_gprs[_Rt_].allocatedReg, MemOperand(contextPointer, COP2_CONTROL_OFFSET(_Rd_)));
}
}
void DynaRecCPU::recLWC2() {
if (m_gprs[_Rs_].isConst()) { // Store address in arg1
gen.Mov(arg1, m_gprs[_Rs_].val + _Imm_);
} else {
allocateReg(_Rs_);
gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_);
}
call(read32Wrapper);
switch (_Rt_) {
case 15:
case 30:
fmt::print("Unimplemented LWC2 to GTE data register {}\n", _Rt_);
abort();
break;
case 28: // IRGB
gen.And(w1, w0, 0x1f); // Calculate IR1
gen.Lsl(w1, w1, 7);
gen.Str(w1, MemOperand(contextPointer, COP2_DATA_OFFSET(9)));
gen.Lsl(w1, w0, 2); // Calculate IR2
gen.And(w1, w1, 0xf80); // The above w0 shifted left by 2 first, so we adjust the mask
gen.Str(w1, MemOperand(contextPointer, COP2_DATA_OFFSET(10)));
gen.Lsr(w1, w0, 3); // Calculate IR3
gen.And(w1, w1, 0xf80);
gen.Str(w1, MemOperand(contextPointer, COP2_DATA_OFFSET(11)));
break;
}
if (_Rt_ != 31) {
gen.Str(w0, MemOperand(contextPointer, COP2_DATA_OFFSET(_Rt_)));
}
}
void DynaRecCPU::recSWC2() {
gen.Mov(arg1, _Rt_);
call(MFC2Wrapper); // Fetch the COP2 data reg in w0
gen.Mov(arg2, w0); // Value to write in arg2
// Address in arg1
if (m_gprs[_Rs_].isConst()) {
gen.Mov(arg1, m_gprs[_Rs_].val + _Imm_);
} else {
allocateReg(_Rs_);
gen.moveAndAdd(arg1, m_gprs[_Rs_].allocatedReg, _Imm_);
}
call(write32Wrapper);
}
#define GTE_FALLBACK(name) \
static void name##Wrapper(uint32_t instruction) { PCSX::g_emulator->m_gte->name(instruction); } \
\
void DynaRecCPU::rec##name() { \
gen.Mov(arg1, m_regs.code); \
call(name##Wrapper); \
}
GTE_FALLBACK(AVSZ3);
GTE_FALLBACK(AVSZ4);
GTE_FALLBACK(CC);
GTE_FALLBACK(CDP);
GTE_FALLBACK(DCPL);
GTE_FALLBACK(DPCS);
GTE_FALLBACK(DPCT);
GTE_FALLBACK(GPF);
GTE_FALLBACK(GPL);
GTE_FALLBACK(INTPL);
GTE_FALLBACK(MVMVA);
GTE_FALLBACK(NCCS);
GTE_FALLBACK(NCCT);
GTE_FALLBACK(NCDS);
GTE_FALLBACK(NCDT);
GTE_FALLBACK(NCLIP);
GTE_FALLBACK(NCS);
GTE_FALLBACK(NCT);
GTE_FALLBACK(OP);
GTE_FALLBACK(RTPS);
GTE_FALLBACK(RTPT);
GTE_FALLBACK(SQR);
#endif // DYNAREC_X86_64
File diff suppressed because it is too large Load Diff
+431
View File
@@ -0,0 +1,431 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* 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; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "recompiler.h"
#if defined(DYNAREC_AA64)
bool DynaRecCPU::Init() {
// Initialize recompiler memory
// Check for 8MB RAM expansion
const bool ramExpansion = PCSX::g_emulator->settings.get<PCSX::Emulator::Setting8MB>();
m_ramSize = ramExpansion ? 0x800000 : 0x200000;
const auto biosSize = 0x80000;
// The amount of 64KB RAM pages. 0x80 with the ram expansion, 0x20 otherwise
const int ramPages = m_ramSize >> 16;
// Split the 32-bit address space into 64KB pages, so 0x10000 pages in total
m_recompilerLUT = new DynarecCallback*[0x10000]();
// Instructions need to be on 4-byte boundaries. So the amount of valid block entrypoints
// in a region of memory is REGION_SIZE / 4
m_ramBlocks = new DynarecCallback[m_ramSize / 4];
m_biosBlocks = new DynarecCallback[biosSize / 4];
m_dummyBlocks = new DynarecCallback[0x10000 / 4]; // Allocate one page worth of dummy blocks
gen.Reset(); // Reset code generator
for (int page = 0; page < 0x10000; page++) { // Default all pages to dummy blocks
m_recompilerLUT[page] = &m_dummyBlocks[0];
}
// For every 64KB page of memory, we can have 64*1024/4 unique blocks = 0x4000
// Hence the multiplications below
for (int page = 0; page < ramPages; page++) { // Map RAM to the recompiler LUT
const auto pointer = &m_ramBlocks[page * 0x4000]; // Get a pointer to the page of RAM blocks
m_recompilerLUT[page + 0x0000] = pointer; // Map KUSEG, KSEG0 and KSEG1 RAM respectively
m_recompilerLUT[page + 0x8000] = pointer;
m_recompilerLUT[page + 0xA000] = pointer;
}
for (int page = 0; page < 8; page++) { // Map BIOS to recompiler LUT
const auto pointer = &m_biosBlocks[page * 0x4000];
m_recompilerLUT[page + 0x1FC0] = pointer; // Map KUSEG, KSEG0 and KSEG1 BIOS respectively
m_recompilerLUT[page + 0x9FC0] = pointer;
m_recompilerLUT[page + 0xBFC0] = pointer;
}
#if !defined(__APPLE__)
if (!gen.setRWX()) { // Mark code cache as readable/writeable/executable
PCSX::g_system->message("[Dynarec] Failed to allocate executable memory.\nTry disabling the Dynarec CPU.");
return false;
}
#endif
#if defined(__APPLE__)
gen.setRW(); // M1 wants buffer marked as readable/writable with mprotect before emitting code
#endif
emitDispatcher(); // Emit our assembly dispatcher
uncompileAll(); // Mark all blocks as uncompiled
for (int i = 0; i < 0x10000 / 4; i++) { // Mark all dummy blocks as invalid
m_dummyBlocks[i] = m_invalidBlock;
}
m_gprs[0].markConst(0); // $zero is always zero
#if defined(__APPLE__)
// Check to make sure code buffer memory was allocated
if (gen.getCode<void*>() == nullptr) {
PCSX::g_system->message("[Dynarec] Failed to allocate memory for Dynarec.\nTry disabling the Dynarec CPU.");
return false;
}
gen.setRX(); // Mark code cache as readable/executable before jumping into dispatcher
#endif
return true;
}
void DynaRecCPU::Reset() {
R3000Acpu::Reset(); // Reset CPU registers
Shutdown(); // Deinit and re-init dynarec
Init();
}
void DynaRecCPU::Shutdown() {
delete[] m_recompilerLUT;
delete[] m_ramBlocks;
delete[] m_biosBlocks;
delete[] m_dummyBlocks;
gen.dumpBuffer(); // dump buffer on shutdown/hard-reset for diagnostics
}
/// Params: A program counter value
/// Returns: A pointer to the host aa64 code that points to the block that starts from the given PC
DynarecCallback* DynaRecCPU::getBlockPointer(uint32_t pc) {
const auto base = m_recompilerLUT[pc >> 16];
const auto offset = (pc & 0xFFFF) >> 2; // Remove the 2 lower bits, they're guaranteed to be 0
return &base[offset];
}
void DynaRecCPU::signalShellReached(DynaRecCPU* that) {
if (!that->m_shellStarted) {
that->m_shellStarted = true;
PCSX::g_system->m_eventBus->signal(PCSX::Events::ExecutionFlow::ShellReached{});
}
}
void DynaRecCPU::error() {
PCSX::g_system->hardReset();
PCSX::g_system->stop();
PCSX::g_system->message("Unrecoverable error while running recompiler\nProgram counter: %08X\n", m_pc);
}
void DynaRecCPU::uncompileAll() {
constexpr int biosSize = 0x80000;
for (auto i = 0; i < m_ramSize / 4; i++) { // Mark all RAM blocks as uncompiled
m_ramBlocks[i] = m_uncompiledBlock;
}
for (auto i = 0; i < biosSize / 4; i++) { // Mark all BIOS blocks as uncompiled
m_biosBlocks[i] = m_uncompiledBlock;
}
}
void DynaRecCPU::flushCache() {
gen.Reset(); // Reset the emitter's code pointer and code size variables
emitDispatcher(); // Re-emit dispatcher
uncompileAll(); // Mark all blocks as uncompiled
}
void DynaRecCPU::emitBlockLookup() {
gen.Ldr(w4, MemOperand(contextPointer, PC_OFFSET)); // w4 = pc
// w3 = index into the recompiler LUT page. Calculated like ((pc >> 2) & 0x3fff)
gen.Ubfx(w3, w4, 2, 14);
gen.Lsr(w4, w4, 16); // w4 = pc >> 16
// Load base pointer to recompiler LUT page in x0
gen.Mov(x0, (uintptr_t)m_recompilerLUT);
gen.Ldr(x0, MemOperand(x0, x4, LSL, 3));
// Load pointer to block in x5 and jump to it
gen.Ldr(x5, MemOperand(x0, x3, LSL, 3));
gen.Br(x5);
}
void DynaRecCPU::emitDispatcher() {
Label done;
gen.align();
m_dispatcher = gen.getCurr<DynarecCallback>();
gen.Str(x30, MemOperand(sp, -16, PreIndex)); // Backup link register
gen.Str(runningPointer,
MemOperand(sp, -16, PreIndex)); // Save runningPointer register in stack
gen.Str(contextPointer,
MemOperand(sp, -16, PreIndex)); // Save contextPointer register in stack (also align stack pointer)
gen.Mov(runningPointer, (uintptr_t)PCSX::g_system->runningPtr()); // Move runningPtr to runningPointer register
gen.Mov(contextPointer, (uintptr_t)this); // Load context pointer
// Back up all our allocateable volatile regs
static_assert((ALLOCATEABLE_NON_VOLATILE_COUNT & 1) == 0); // Make sure we've got an even number of regs
for (auto i = 0; i < ALLOCATEABLE_NON_VOLATILE_COUNT; i += 2) {
const auto reg = allocateableNonVolatiles[i];
const auto reg2 = allocateableNonVolatiles[i + 1];
gen.Stp(reg2.X(), reg.X(), MemOperand(sp, -16, PreIndex));
}
emitBlockLookup(); // Look up block
// Code to be executed after each block.
gen.align();
m_returnFromBlock = gen.getCurr<DynarecCallback>();
loadThisPointer(arg1.X()); // Poll events
call(recBranchTestWrapper);
gen.Ldrb(w0, MemOperand(runningPointer)); // Check if PCSX::g_system->running is true
gen.Cbz(w0, &done); // If it's not, return
emitBlockLookup(); // Otherwise, look up next block
gen.align();
// Code for exiting JIT context
gen.L(done);
// Restore all non-volatiles
for (int i = ALLOCATEABLE_NON_VOLATILE_COUNT - 1; i >= 0; i -= 2) {
const auto reg = allocateableNonVolatiles[i];
const auto reg2 = allocateableNonVolatiles[i - 1];
gen.Ldp(reg.X(), reg2.X(), MemOperand(sp, 16, PostIndex));
}
gen.Ldr(contextPointer, MemOperand(sp, 16, PostIndex)); // Restore contextPointer register from stack
gen.Ldr(runningPointer, MemOperand(sp, 16, PostIndex)); // Restore runningPointer register from stack
gen.Ldr(x30, MemOperand(sp, 16, PostIndex)); // Restore link register before returning
gen.Ret();
// Code for when the block to be executed needs to be compiled.
// x0 = Base pointer to the page of m_recompilerLUT we're executing from
// x3 = Index into the page
// Doing x0 + (x3 << 3) gets us the pointer to where the block callback should be stored
gen.align();
m_uncompiledBlock = gen.getCurr<DynarecCallback>();
// Do arg2 = x0 + (x3 << 3). Now arg2 points to the address we'll store the block callback to.
gen.Add(arg2.X(), x0, Operand(x3, LSL, 3));
loadThisPointer(arg1.X());
call(recRecompileWrapper);
gen.Br(x0); // Jump to compiled block
// Code for when the block we've jumped to is invalid. Throws an error and exits
gen.align();
m_invalidBlock = gen.getCurr<DynarecCallback>();
loadThisPointer(arg1.X()); // Throw recompiler error
call(recErrorWrapper);
gen.B(&done); // Exit
gen.ready(); // Ready code buffer before emulator jumps into dispatcher for the first time
}
// Compile a block, write address of compiled code to *callback
// Returns the address of the compiled block
DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bool align) {
m_stopCompiling = false;
m_inDelaySlot = false;
m_nextIsDelaySlot = false;
m_delayedLoadInfo[0].active = false;
m_delayedLoadInfo[1].active = false;
m_pcWrittenBack = false;
m_linkedPC = std::nullopt;
m_pc = pc & ~3;
const auto startingPC = m_pc;
int count = 0; // How many instructions have we compiled?
#if defined(__APPLE__)
gen.setRW(); // Mark code cache as readable/writeable before emitting code
#endif
if (align) {
gen.align(); // Align next block
}
if (gen.getSize() > codeCacheSize) { // Flush JIT cache if we've gone above the acceptable size
flushCache();
}
const auto blockStart = gen.getCurr<DynarecCallback>();
*callback = blockStart;
handleKernelCall(); // Check if this is a kernel call vector, emit some extra code in that case.
auto shouldContinue = [&]() {
if (m_nextIsDelaySlot) {
return true;
}
if (m_stopCompiling) {
return false;
}
if (count >= MAX_BLOCK_SIZE) { // TODO: Check delay slots here
return false;
}
return true;
};
while (shouldContinue()) {
m_inDelaySlot = m_nextIsDelaySlot;
m_nextIsDelaySlot = false;
const auto p = (uint32_t*)PSXM(m_pc); // Fetch instruction
if (p == nullptr) { // Error if it can't be fetched
return m_invalidBlock;
}
m_regs.code = *p; // Actually read the instruction
m_pc += 4; // Increment recompiler PC
count++; // Increment instruction count
const auto func = m_recBSC[m_regs.code >> 26]; // Look up the opcode in our decoding LUT
(*this.*func)(); // Jump into the handler to recompile it
}
flushRegs();
if (!m_pcWrittenBack) { // Write PC back if needed
gen.Mov(w0, m_pc);
gen.Str(w0, MemOperand(contextPointer, PC_OFFSET));
}
// If this was the block at 0x8003'0000 (Start of shell) send the GUI a "shell reached" signal
// This must happen after the PC is written back, otherwise our PC after sideloading will be overriden.
if (startingPC == 0x80030000) {
loadThisPointer(arg1.X());
call(signalShellReached);
m_linkedPC = std::nullopt;
}
gen.Ldr(w0, MemOperand(contextPointer, CYCLE_OFFSET)); // Fetch cycle count from memory
gen.Add(w0, w0, count * PCSX::Emulator::BIAS); // Add block cycles
gen.Str(w0, MemOperand(contextPointer, CYCLE_OFFSET)); // Store cycles back to memory
// Link block else return to dispatcher
if (m_linkedPC && ENABLE_BLOCK_LINKING && m_linkedPC.value() != startingPC) {
handleLinking();
} else {
jmp((void*)m_returnFromBlock);
}
// Clear stale instruction cache contents.
__builtin___clear_cache(reinterpret_cast<char*>(blockStart), gen.getCurr<char*>());
gen.ready();
#if defined(__APPLE__)
gen.setRX(); // Mark code cache as readable/executable before returning to dispatcher
#endif
// The block might have been invalidated by handleLinking, so re-read the pointer from *callback
return *callback;
}
// Checks if the block being compiled is one of the kernel call vectors
// If so, emit a call to "InterceptBIOS", which handles the kernel call debugger features
// Also handles fast booting by intercepting the shell reached signal and setting pc to $ra if fastboot is on
void DynaRecCPU::handleKernelCall() {
if (m_pc == 0x80030000) {
handleFastboot();
return;
}
const uint32_t pc = m_pc & 0x1fffff;
const uint32_t base = (m_pc >> 20) & 0xffc;
if ((base != 0x000) && (base != 0x800) && (base != 0xa00))
return; // Mask out the segment, return if not a kernel call vector
switch (pc) { // Handle the A0/B0/C0 vectors
case 0xA0:
loadThisPointer(arg1.X());
call(interceptKernelCallWrapper<0xA0>);
break;
case 0xB0:
loadThisPointer(arg1.X());
call(interceptKernelCallWrapper<0xB0>);
break;
case 0xC0:
loadThisPointer(arg1.X());
call(interceptKernelCallWrapper<0xC0>);
break;
}
}
// Emits a jump to the dispatcher if there's no block to link to.
// Otherwise, handle linking blocks
void DynaRecCPU::handleLinking() {
vixl::aarch64::Label returnFromBlock;
// Don't link unless the next PC is valid, and there's over 1MB of free space in the code cache
if (isPcValid(m_linkedPC.value()) && gen.getRemainingSize() > 0x100000) {
const auto nextPC = m_linkedPC.value();
const auto nextBlockPointer = getBlockPointer(nextPC);
const auto nextBlockOffset = (size_t)nextBlockPointer - (size_t)this;
if (*nextBlockPointer == m_uncompiledBlock) { // If the next block hasn't been compiled yet
// Check that the block hasn't been invalidated/moved
// The value will be patched later. Since all code is within the same 32MB segment,
// We can get away with only checking the low 32 bits of the block pointer
gen.Mov(x0, (uintptr_t)nextBlockPointer);
gen.Ldr(x0, MemOperand(x0));
// Move value to compare against into w1
gen.Mov(w1, 0xcccccccc);
gen.Cmp(w0, w1);
const auto pointer = gen.getCurr<uint8_t*>();
gen.bne(returnFromBlock); // Return if the block addr changed
recompile(nextBlockPointer, nextPC, false); // Fallthrough to next block
*(uint32_t*)(pointer - 4) = (uint32_t)(uintptr_t)*nextBlockPointer; // Patch comparison value
} else { // If it has already been compiled, link by jumping to the compiled code
gen.Mov(x0, (uintptr_t)nextBlockPointer);
gen.Ldr(x0, MemOperand(x0));
// Move value to compare against into w1
gen.Mov(w1, (uint32_t)(uintptr_t)*nextBlockPointer);
gen.Cmp(w0, w1);
gen.bne(returnFromBlock); // Return if the block addr changed
jmp((void*)*nextBlockPointer); // Jump to linked block otherwise
gen.L(returnFromBlock);
jmp((void*)m_returnFromBlock);
}
} else { // Can't link, so return to dispatcher
jmp((void*)m_returnFromBlock);
}
}
void DynaRecCPU::handleFastboot() {
Label noFastBoot;
gen.Mov(x0, (uintptr_t)&m_shellStarted); // Check if shell has already been reached
gen.Ldrb(w0, MemOperand(x0));
gen.Cbnz(w0, &noFastBoot); // Don't fastboot if so
gen.Mov(x0,
(uintptr_t)&PCSX::g_emulator->settings.get<PCSX::Emulator::SettingFastBoot>()); // Check if fastboot is on
gen.Ldrb(w0, MemOperand(x0));
gen.Cbz(w0, &noFastBoot);
loadThisPointer(arg1.X()); // If fastbooting, call the signalShellReached function, set pc, and exit the block
call(signalShellReached);
gen.Ldr(w0, MemOperand(contextPointer, GPR_OFFSET(31)));
gen.Str(w0, MemOperand(contextPointer, PC_OFFSET));
jmp((void*)m_returnFromBlock);
gen.L(noFastBoot);
}
std::unique_ptr<PCSX::R3000Acpu> PCSX::Cpus::getDynaRec() { return std::unique_ptr<PCSX::R3000Acpu>(new DynaRecCPU()); }
#endif // DYNAREC_AA64
+471
View File
@@ -0,0 +1,471 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* 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; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#pragma once
#include "core/r3000a.h"
#if defined(DYNAREC_AA64)
#include <array>
#include <fstream>
#include <optional>
#include <stdexcept>
#include <string>
#include "emitter.h"
#include "fmt/format.h"
#include "regAllocation.h"
#include "spu/interface.h"
#include "tracy/Tracy.hpp"
#define HOST_REG_CACHE_OFFSET(x) ((uintptr_t)&m_hostRegisterCache[(x)] - (uintptr_t)this)
#define GPR_OFFSET(x) ((uintptr_t)&m_regs.GPR.r[(x)] - (uintptr_t)this)
#define COP0_OFFSET(x) ((uintptr_t)&m_regs.CP0.r[(x)] - (uintptr_t)this)
#define PC_OFFSET ((uintptr_t)&m_regs.pc - (uintptr_t)this)
#define LO_OFFSET ((uintptr_t)&m_regs.GPR.n.lo - (uintptr_t)this)
#define HI_OFFSET ((uintptr_t)&m_regs.GPR.n.hi - (uintptr_t)this)
#define CYCLE_OFFSET ((uintptr_t)&m_regs.cycle - (uintptr_t)this)
static uint8_t read8Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read8(address); }
static uint16_t read16Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read16(address); }
static uint32_t read32Wrapper(uint32_t address) { return PCSX::g_emulator->m_mem->read32(address); }
static void SPU_writeRegisterWrapper(uint32_t addr, uint16_t value) {
PCSX::g_emulator->m_spu->writeRegister(addr, value);
}
static void write8Wrapper(uint32_t address, uint32_t value) {
PCSX::g_emulator->m_mem->write8(address, value);
}
static void write16Wrapper(uint32_t address, uint32_t value) {
PCSX::g_emulator->m_mem->write16(address, value);
}
static void write32Wrapper(uint32_t address, uint32_t value) {
PCSX::g_emulator->m_mem->write32(address, value);
}
using DynarecCallback = void (*)(); // A function pointer to JIT-emitted code
class DynaRecCPU final : public PCSX::R3000Acpu {
using recompilationFunc = void (DynaRecCPU::*)(); // A function pointer to a dynarec member function
private:
uint64_t m_hostRegisterCache[16]; // An array to backup non-volatile regs temporarily
DynarecCallback** m_recompilerLUT;
DynarecCallback* m_ramBlocks; // Pointers to compiled RAM blocks (If nullptr then this block needs to be compiled)
DynarecCallback* m_biosBlocks; // Pointers to compiled BIOS blocks
DynarecCallback* m_dummyBlocks; // This is where invalid pages will point
// Functions written in raw assembly
DynarecCallback m_dispatcher; // Pointer to our assembly dispatcher
DynarecCallback m_returnFromBlock; // Pointer to the code that will be executed when returning from a block
DynarecCallback m_uncompiledBlock; // Pointer to the code that will be executed when jumping to an uncompiled block
DynarecCallback m_invalidBlock; // Pointer to the code that will be executed the PC is invalid
uint32_t m_pc;
Emitter gen;
bool m_stopCompiling; // Should we stop compiling code?
bool m_pcWrittenBack; // Has the PC been written back already by a jump?
uint32_t m_ramSize; // RAM is 2MB on retail units, 8MB on some DTL units (Can be toggled in GUI)
const int MAX_BLOCK_SIZE = 50;
enum class RegState { Unknown, Constant };
enum class LoadingMode { DoNotLoad, Load };
// Renamed temporarily to Reg since it collides with Register in vixl::aarch64 namespace
struct Reg {
uint32_t val = 0; // The register's cached value used for constant propagation
RegState state = RegState::Unknown; // Is this register's value a constant, or an unknown value?
bool allocated = false; // Has this guest register been allocated to a host reg?
bool writeback = false; // Does this register need to be written back to memory at the end of the block?
Register allocatedReg; // If a host reg has been allocated to this register, which reg is it?
int allocatedRegIndex = 0;
inline bool isConst() { return state == RegState::Constant; }
inline bool isAllocated() { return allocated; }
inline void markConst(uint32_t value) {
val = value;
state = RegState::Constant;
writeback = false; // Disable writeback in case the reg was previously allocated with writeback
allocated = false; // Unallocate register
}
// Note: It's important that markUnknown does not modify the val field as that would mess up codegen
inline void markUnknown() { state = RegState::Unknown; }
inline void setWriteback(bool wb) { writeback = wb; }
};
inline void markConst(int index, uint32_t value) {
m_gprs[index].markConst(value);
if (m_hostRegs[m_gprs[index].allocatedRegIndex].mappedReg == index) {
m_hostRegs[m_gprs[index].allocatedRegIndex].mappedReg =
std::nullopt; // Unmap the register on the host reg side too
}
}
struct HostRegister {
std::optional<int> mappedReg = std::nullopt; // The register this is allocated to, if any
};
Reg m_gprs[32];
std::array<HostRegister, ALLOCATEABLE_REG_COUNT> m_hostRegs;
std::optional<uint32_t> m_linkedPC = std::nullopt;
template <LoadingMode mode = LoadingMode::Load>
void reserveReg(int index);
void allocateReg(int reg);
void allocateRegWithoutLoad(int reg);
template <int T, int U>
void allocateRegisters(std::array<int, T> regsWithoutWb, std::array<int, U> regsWithWb);
void alloc_rt_rs();
void alloc_rt_wb_rd();
void alloc_rs_wb_rd();
void alloc_rs_wb_rt();
void alloc_rt_rs_wb_rd();
void flushRegs();
void spillRegisterCache();
void prepareForCall();
unsigned int m_allocatedRegisters = 0; // how many registers have been allocated in this block?
// Check if we're executing from valid memory
inline bool isPcValid(uint32_t addr) { return m_recompilerLUT[addr >> 16] != m_dummyBlocks; }
DynarecCallback* getBlockPointer(uint32_t pc);
DynarecCallback recompile(DynarecCallback* callback, uint32_t pc, bool align = true);
void error();
void flushCache();
void handleLinking();
void handleFastboot();
void handleKernelCall();
void emitDispatcher();
void emitBlockLookup();
void uncompileAll();
// Class Wrapper Functions
static void exceptionWrapper(DynaRecCPU* that, int32_t e, int32_t bd) { that->exception(e, bd); }
static void recClearWrapper(DynaRecCPU* that, uint32_t address) { that->Clear(address, 1); }
static void recBranchTestWrapper(DynaRecCPU* that) { that->branchTest(); }
static void recErrorWrapper(DynaRecCPU* that) { that->error(); }
static void signalShellReached(DynaRecCPU* that);
static DynarecCallback recRecompileWrapper(DynaRecCPU* that, DynarecCallback* callback) {
return that->recompile(callback, that->m_regs.pc);
}
template <uint32_t pc>
static void interceptKernelCallWrapper(DynaRecCPU* that) {
that->InterceptBIOS<false>(pc);
}
// TODO: This is currently un-unsed in x64 DynaRec. Check this.
void inlineClear(uint32_t address) {
if (isPcValid(address & ~3)) {
loadThisPointer(arg1.X());
gen.Mov(arg2, address & ~3);
call(recClearWrapper);
}
}
public:
DynaRecCPU() : R3000Acpu("Dynarec (arm64)") {}
virtual bool Implemented() final { return true; }
virtual bool Init() final;
virtual void Reset() final;
virtual void Execute() final {
ZoneScoped; // Tell the Tracy profiler to do its thing
(*m_dispatcher)(); // Jump to assembly dispatcher
}
virtual void Clear(uint32_t Addr, uint32_t Size) final {
auto pointer = getBlockPointer(Addr);
for (auto i = 0; i < Size; i++) {
*pointer++ = m_uncompiledBlock;
}
}
virtual void Shutdown() final;
virtual bool isDynarec() final { return true; }
virtual void SetPGXPMode(uint32_t pgxpMode) final {
if (pgxpMode != 0) {
throw std::runtime_error("PGXP not supported in x64 JIT");
}
}
// For the GUI dynarec disassembly widget
virtual const uint8_t* getBufferPtr() final { return gen.getCode<const uint8_t*>(); }
virtual const size_t getBufferSize() final { return gen.getSize(); }
private:
// Calculate the number of instructions between the current PC and the branch target
// Returns a negative number for backwards jumps
int64_t getPCOffset(const void* current, const void* target) {
return (int64_t)((ptrdiff_t)target - (ptrdiff_t)current) >> 2;
}
// Load a pointer to the JIT object in "reg" using lea with the context pointer
void loadThisPointer(Register reg) { gen.Mov(reg, contextPointer); }
// Loads a value into dest from the given pointer.
template <int size, bool signExtend>
void load(Register dest, const void* pointer) {
// const auto distance = (intptr_t)pointer - (intptr_t)this;
gen.Mov(x4, (uintptr_t)pointer);
switch (size) {
case 8:
signExtend ? gen.Ldrsb(dest, MemOperand(x4)) : gen.Ldrb(dest, MemOperand(x4));
break;
case 16:
signExtend ? gen.Ldrsh(dest, MemOperand(x4)) : gen.Ldrh(dest, MemOperand(x4));
break;
case 32:
gen.Ldr(dest, MemOperand(x4));
break;
}
}
// Stores a value of "size" bits from "source" to the given pointer
template <int size, typename T>
void store(T source, const void* pointer) {
gen.Mov(x4, (uintptr_t)pointer);
gen.Mov(w5, source);
switch (size) {
case 8:
gen.Strb(w5, MemOperand(x4));
break;
case 16:
gen.Strh(w5, MemOperand(x4));
break;
case 32:
gen.Str(w5, MemOperand(x4));
break;
}
}
// Prepare for a call to a C++ function and then actually emit it
template <typename T>
void call(T& func) {
prepareForCall();
const auto ptr = reinterpret_cast<const void*>(func);
const int64_t disp = getPCOffset(gen.getCurr<const void*>(), ptr);
// If the displacement can fit in a 26-bit int, that means we can emit a direct call to the address
// Otherwise, load the address into a register and emit a blr
const bool canDoDirectCall = vixl::IsInt26(disp);
if (canDoDirectCall) {
gen.bl(disp);
} else {
gen.Mov(x4, (uintptr_t)ptr);
gen.Blr(x4);
}
}
// jmp function using same method as call to attempt direct jump
void jmp(void* pointer) {
const int64_t disp = getPCOffset(gen.getCurr<const void*>(), pointer);
// If the displacement can fit in a 26-bit int, that means we can emit a direct call to the address
// Otherwise, load the address into a register and emit a br
const bool canDoDirectJump = vixl::IsInt26(disp);
if (canDoDirectJump) {
gen.b(disp);
} else {
gen.Mov(x4, (uintptr_t)pointer);
gen.Br(x4);
}
}
void maybeCancelDelayedLoad(uint32_t index) {
const unsigned other = m_currentDelayedLoad ^ 1;
if (m_delayedLoadInfo[other].index == index) {
m_delayedLoadInfo[other].active = false;
}
}
// Instruction definitions
void recUnknown();
void recSpecial();
void recADD();
void recADDIU();
void recADDU();
void recAND();
void recANDI();
void recBEQ();
void recBGTZ();
void recBLEZ();
void recBNE();
void recBREAK();
void recCFC2();
void recCOP0();
void recCOP2();
void recCTC2();
void recDIV();
void recDIVU();
void recJ();
void recJAL();
void recJALR();
void recJR();
void recLB();
void recLBU();
void recLH();
void recLHU();
void recLUI();
void recLW();
void recLWC2();
void recLWL();
void recLWR();
void recMFC0();
void recMFC2();
void recMFHI();
void recMFLO();
void recMTC0();
void recMTC2();
void recMTHI();
void recMTLO();
void recMULT();
void recMULTU();
void recNOR();
void recOR();
void recORI();
void recREGIMM();
void recRFE();
void recSB();
void recSH();
void recSLL();
void recSLLV();
void recSLT();
void recSLTI();
void recSLTIU();
void recSLTU();
void recSRA();
void recSRAV();
void recSRL();
void recSRLV();
void recSUB();
void recSUBU();
void recSW();
void recSWC2();
void recSWL();
void recSWR();
void recSYSCALL();
void recXOR();
void recXORI();
void recException(Exception e);
// GTE instructions
void recGTEMove();
void recAVSZ3();
void recAVSZ4();
void recCC();
void recCDP();
void recDCPL();
void recDPCS();
void recDPCT();
void recGPF();
void recGPL();
void recINTPL();
void recMVMVA();
void recNCCS();
void recNCCT();
void recNCDS();
void recNCDT();
void recNCLIP();
void recNCS();
void recNCT();
void recOP();
void recRTPS();
void recRTPT();
void recSQR();
template <bool isAVSZ4>
void recAVSZ();
template <bool loadSR>
void testSoftwareInterrupt();
template <int size, bool signExtend>
void recompileLoad();
const recompilationFunc m_recBSC[64] = {
&DynaRecCPU::recSpecial, &DynaRecCPU::recREGIMM, &DynaRecCPU::recJ, &DynaRecCPU::recJAL, // 00
&DynaRecCPU::recBEQ, &DynaRecCPU::recBNE, &DynaRecCPU::recBLEZ, &DynaRecCPU::recBGTZ, // 04
&DynaRecCPU::recADDIU, &DynaRecCPU::recADDIU, &DynaRecCPU::recSLTI, &DynaRecCPU::recSLTIU, // 08
&DynaRecCPU::recANDI, &DynaRecCPU::recORI, &DynaRecCPU::recXORI, &DynaRecCPU::recLUI, // 0c
&DynaRecCPU::recCOP0, &DynaRecCPU::recUnknown, &DynaRecCPU::recCOP2, &DynaRecCPU::recUnknown, // 10
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 14
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 18
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 1c
&DynaRecCPU::recLB, &DynaRecCPU::recLH, &DynaRecCPU::recLWL, &DynaRecCPU::recLW, // 20
&DynaRecCPU::recLBU, &DynaRecCPU::recLHU, &DynaRecCPU::recLWR, &DynaRecCPU::recUnknown, // 24
&DynaRecCPU::recSB, &DynaRecCPU::recSH, &DynaRecCPU::recSWL, &DynaRecCPU::recSW, // 28
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recSWR, &DynaRecCPU::recUnknown, // 2c
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recLWC2, &DynaRecCPU::recUnknown, // 30
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 34
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recSWC2, &DynaRecCPU::recUnknown, // 38
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 3c
};
const recompilationFunc m_recSPC[64] = {
&DynaRecCPU::recSLL, &DynaRecCPU::recUnknown, &DynaRecCPU::recSRL, &DynaRecCPU::recSRA, // 00
&DynaRecCPU::recSLLV, &DynaRecCPU::recUnknown, &DynaRecCPU::recSRLV, &DynaRecCPU::recSRAV, // 04
&DynaRecCPU::recJR, &DynaRecCPU::recJALR, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 08
&DynaRecCPU::recSYSCALL, &DynaRecCPU::recBREAK, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 0c
&DynaRecCPU::recMFHI, &DynaRecCPU::recMTHI, &DynaRecCPU::recMFLO, &DynaRecCPU::recMTLO, // 10
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 14
&DynaRecCPU::recMULT, &DynaRecCPU::recMULTU, &DynaRecCPU::recDIV, &DynaRecCPU::recDIVU, // 18
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 1c
&DynaRecCPU::recADD, &DynaRecCPU::recADDU, &DynaRecCPU::recSUB, &DynaRecCPU::recSUBU, // 20
&DynaRecCPU::recAND, &DynaRecCPU::recOR, &DynaRecCPU::recXOR, &DynaRecCPU::recNOR, // 24
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recSLT, &DynaRecCPU::recSLTU, // 28
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 2c
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 30
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 34
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 38
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 3c
};
const recompilationFunc m_recGTE[64] = {
&DynaRecCPU::recGTEMove, &DynaRecCPU::recRTPS, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 00
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recNCLIP, &DynaRecCPU::recUnknown, // 04
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 08
&DynaRecCPU::recOP, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 0c
&DynaRecCPU::recDPCS, &DynaRecCPU::recINTPL, &DynaRecCPU::recMVMVA, &DynaRecCPU::recNCDS, // 10
&DynaRecCPU::recCDP, &DynaRecCPU::recUnknown, &DynaRecCPU::recNCDT, &DynaRecCPU::recUnknown, // 14
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recNCCS, // 18
&DynaRecCPU::recCC, &DynaRecCPU::recUnknown, &DynaRecCPU::recNCS, &DynaRecCPU::recUnknown, // 1c
&DynaRecCPU::recNCT, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 20
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 24
&DynaRecCPU::recSQR, &DynaRecCPU::recDCPL, &DynaRecCPU::recDPCT, &DynaRecCPU::recUnknown, // 28
&DynaRecCPU::recUnknown, &DynaRecCPU::recAVSZ3, &DynaRecCPU::recAVSZ4, &DynaRecCPU::recUnknown, // 2c
&DynaRecCPU::recRTPT, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 30
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 34
&DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, &DynaRecCPU::recUnknown, // 38
&DynaRecCPU::recUnknown, &DynaRecCPU::recGPF, &DynaRecCPU::recGPL, &DynaRecCPU::recNCCT, // 3c
};
static constexpr bool ENABLE_BLOCK_LINKING = false;
};
#endif // DYNAREC_AA64
+203
View File
@@ -0,0 +1,203 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* 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; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#include "regAllocation.h"
#include <cassert>
#include "recompiler.h"
#if defined(DYNAREC_AA64)
// Map the guest register corresponding to the index to a host register
// Used internally by the allocateReg functions. Don't use it directly
template <DynaRecCPU::LoadingMode mode>
void DynaRecCPU::reserveReg(int index) {
const auto regToAllocate = allocateableRegisters[m_allocatedRegisters]; // Fetch the next host reg to be allocated
m_gprs[index].allocatedReg = regToAllocate;
m_gprs[index].markUnknown(); // Mark the register's value as unknown if it were previously const propagated
m_gprs[index].allocated = true; // Mark register as allocated
m_gprs[index].allocatedRegIndex = m_allocatedRegisters;
// For certain instructions like loads, we don't want to load the reg because it'll get instantly overwritten
if constexpr (mode == LoadingMode::Load) {
gen.Ldr(regToAllocate, MemOperand(contextPointer, GPR_OFFSET(index))); // Load reg
}
m_hostRegs[m_allocatedRegisters].mappedReg = index;
m_allocatedRegisters++; // Advance our register allcoator
}
// Flush constants and allocated registers to host regs at the end of a block
void DynaRecCPU::flushRegs() {
for (auto i = 1; i < 32; i++) {
if (m_gprs[i].isConst()) { // If const: Write the value directly, mark as unknown. Possibly change when
// constants will be stored in host regs
if (m_gprs[i].val != 0) {
gen.Mov(w4, m_gprs[i].val);
gen.Str(w4, MemOperand(contextPointer, GPR_OFFSET(i)));
} else {
gen.Str(wzr, MemOperand(contextPointer, GPR_OFFSET(i)));
}
m_gprs[i].markUnknown();
}
else if (m_gprs[i].isAllocated()) { // If it's been allocated to a register, unallocate
m_gprs[i].allocated = false;
if (m_gprs[i].writeback) { // And if writeback was specified, write the value back
gen.Str(m_gprs[i].allocatedReg, MemOperand(contextPointer, GPR_OFFSET(i)));
m_gprs[i].writeback = false; // And turn writeback off
}
}
}
for (auto i = 0; i < ALLOCATEABLE_REG_COUNT; i++) { // Unallocate all regs
m_hostRegs[i].mappedReg = std::nullopt;
}
m_allocatedRegisters = 0;
}
// Spill the volatile allocated registers into guest registers in preparation for a call to a C++ function
void DynaRecCPU::prepareForCall() {
if (m_allocatedRegisters > ALLOCATEABLE_NON_VOLATILE_COUNT) { // Check if there's any allocated volatiles to flush
for (auto i = ALLOCATEABLE_NON_VOLATILE_COUNT; i < m_allocatedRegisters; i++) { // iterate volatile regs
if (m_hostRegs[i].mappedReg) { // Unallocate and spill to guest regs as appropriate
const auto previous = m_hostRegs[i].mappedReg.value(); // Get previously allocated register
if (m_gprs[previous].writeback) { // Spill to guest reg if writeback is enabled
gen.Str(allocateableRegisters[i], MemOperand(contextPointer, GPR_OFFSET(previous)));
m_gprs[previous].writeback = false;
}
m_gprs[previous].allocated = false; // Unallocate it
m_hostRegs[i].mappedReg = std::nullopt;
}
}
// Since we just flushed all our volatiles, we can perform an optimization by making the allocator start
// allocating from the first volatile again. This makes it so we have to flush less often, as we free up
// regs every time we call a C++ function instead of letting them linger and go to waste.
m_allocatedRegisters = ALLOCATEABLE_NON_VOLATILE_COUNT;
}
}
// Used when our register cache overflows. Spill the entirety of it to host registers.
void DynaRecCPU::spillRegisterCache() {
for (auto i = 0; i < m_allocatedRegisters; i++) {
if (m_hostRegs[i].mappedReg) { // Check if the register is still allocated to a guest register
const auto previous = m_hostRegs[i].mappedReg.value(); // Get the reg it's allocated to
if (m_gprs[previous].writeback) { // Spill to guest register if writeback is enabled and disable writeback
gen.Str(allocateableRegisters[i], MemOperand(contextPointer, GPR_OFFSET(previous)));
m_gprs[previous].writeback = false;
}
m_hostRegs[i].mappedReg = std::nullopt; // Unallocate it
m_gprs[previous].allocated = false;
}
}
m_allocatedRegisters = 0; // Nothing is allocated anymore
}
void DynaRecCPU::allocateReg(int reg) {
if (!m_gprs[reg].isAllocated()) {
if (m_allocatedRegisters >= ALLOCATEABLE_REG_COUNT) {
spillRegisterCache();
}
reserveReg<LoadingMode::Load>(reg);
}
}
void DynaRecCPU::allocateRegWithoutLoad(int reg) {
if (!m_gprs[reg].isAllocated()) {
if (m_allocatedRegisters >= ALLOCATEABLE_REG_COUNT) {
spillRegisterCache();
}
reserveReg<LoadingMode::DoNotLoad>(reg);
}
}
// T: Number of regs without writeback we must allocate
// U: Number of regs with writeback we must allocate
// We want both of them to be compile-time constants for efficiency
template <int T, int U>
void DynaRecCPU::allocateRegisters(std::array<int, T> regsWithoutWb, std::array<int, U> regsWithWb) {
static_assert(T + U < ALLOCATEABLE_REG_COUNT, "Trying to allocate too many registers");
start:
// Which specific regs we need to load
uint32_t regsToLoad = 0;
// Which specific regs we need to allocate without loading, with writeback
uint32_t regsToWriteback = 0;
// How many registers we need to load
int regsToAllocateCount = 0;
for (int i = 0; i < T; i++) {
const auto reg = regsWithoutWb[i];
if (!m_gprs[reg].allocated && (regsToLoad & (1 << reg)) == 0) {
regsToLoad |= 1 << reg;
regsToAllocateCount++;
}
}
for (int i = 0; i < U; i++) {
const auto reg = regsWithWb[i];
if (!m_gprs[reg].allocated && (regsToWriteback & (1 << reg)) == 0 && (regsToLoad & (1 << reg)) == 0) {
regsToWriteback |= 1 << reg;
regsToAllocateCount++;
}
}
if (regsToAllocateCount != 0) {
// Flush register cache if we're going to overflow it and restart alloc process
if (m_allocatedRegisters + regsToAllocateCount >= ALLOCATEABLE_REG_COUNT) {
flushRegs();
goto start;
}
// Check which registers we need to load
for (int i = 0; i < T; i++) {
const auto reg = regsWithoutWb[i];
if ((regsToLoad & (1 << reg)) != 0 && !m_gprs[reg].allocated) {
reserveReg<LoadingMode::Load>(reg);
}
}
}
// Specify writeback for whatever regs we need to
for (int i = 0; i < U; i++) {
const auto reg = regsWithWb[i];
if (!m_gprs[reg].allocated) {
reserveReg<LoadingMode::DoNotLoad>(reg);
}
m_gprs[reg].writeback = true;
}
}
void DynaRecCPU::alloc_rt_rs() { allocateRegisters<2, 0>({(int)_Rt_, (int)_Rs_}, {}); }
void DynaRecCPU::alloc_rt_wb_rd() { allocateRegisters<1, 1>({(int)_Rt_}, {(int)_Rd_}); }
void DynaRecCPU::alloc_rs_wb_rd() { allocateRegisters<1, 1>({(int)_Rs_}, {(int)_Rd_}); }
void DynaRecCPU::alloc_rs_wb_rt() { allocateRegisters<1, 1>({(int)_Rs_}, {(int)_Rt_}); }
void DynaRecCPU::alloc_rt_rs_wb_rd() { allocateRegisters<2, 1>({(int)_Rt_, (int)_Rs_}, {(int)_Rd_}); }
#endif // DYNAREC_AA64
+51
View File
@@ -0,0 +1,51 @@
/***************************************************************************
* Copyright (C) 2022 PCSX-Redux authors *
* *
* 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; either version 2 of the License, or *
* (at your option) any later version. *
* *
* 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
***************************************************************************/
#pragma once
#include "core/r3000a.h"
#if defined(DYNAREC_AA64)
#include <array>
#include "vixl/src/aarch64/macro-assembler-aarch64.h"
using namespace vixl::aarch64;
// Volatile = caller-saved
// Non-Volatile = callee-saved
const Register contextPointer = x19; // Pointer to the JIT object
const Register runningPointer = x20; // Pointer to "running" variable
constexpr int ALLOCATEABLE_REG_COUNT = 15;
constexpr int ALLOCATEABLE_NON_VOLATILE_COUNT = 8;
// Our allocateable registers and the order they should be allocated
// We prefer using non-volatile regs first
const std::array<Register, ALLOCATEABLE_REG_COUNT> allocateableRegisters = {w21, w22, w23, w24, w25, w26, w27, w28,
w9, w10, w11, w12, w13, w14, w15};
// Which of our allocateables are volatile?
const std::array<Register, 7> allocateableVolatiles = {w9, w10, w11, w12, w13, w14, w15};
// Which of them are not volatile?
const std::array<Register, 8> allocateableNonVolatiles = {w21, w22, w23, w24, w25, w26, w27, w28};
const Register arg1 = w0; // register where first arg is stored
const Register arg2 = w1; // register where second arg is stored
const Register arg3 = w2; // register where third arg is stored
const Register arg4 = w3; // register where fourth arg is stored
#define IS_VOLATILE(x) ((x) >= ALLOCATEABLE_NON_VOLATILE_COUNT)
#endif // DYNAREC_AA64
+5
View File
@@ -214,6 +214,11 @@ struct Emitter final : public CodeGenerator {
// Returns a signed integer that shows how many bytes of free space are left in the code buffer
int64_t getRemainingSize() { return (int64_t)codeCacheSize - (int64_t)getSize(); }
// Emit a trap instruction that gdb/lldb/Visual Studio can interpret as a breakpoint
void breakpoint() {
int3();
}
// Tries to mark the emitter memory as readable/writeable/executable without throwing an exception.
// Returns whether or not it succeeded
bool setRWX() {
+1
View File
@@ -1888,4 +1888,5 @@ void DynaRecCPU::recBREAK() {
recException(Exception::Break);
}
#undef BAILZERO
#endif // DYNAREC_X86_64
+3
View File
@@ -325,6 +325,9 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
m_firstInstruction = true;
m_fullLoadDelayEmulation = fullLoadDelayEmulation;
// If we somehow ended up compiling a block at an invalid PC, throw an error.
if (!isPcValid(m_pc)) return m_invalidBlock;
const auto startingPC = m_pc;
int count = 0; // How many instructions have we compiled?
DynarecCallback* callback = getBlockPointer(m_pc); // Pointer to where we'll store the addr of the emitted code
+1 -1
View File
@@ -167,7 +167,7 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
void uncompileAll();
public:
DynaRecCPU() : R3000Acpu("x86-64 DynaRec") {}
DynaRecCPU() : R3000Acpu("Dynarec (x86-64)") {}
virtual bool Implemented() final { return true; }
virtual bool Init() final;
+1 -1
View File
@@ -3213,7 +3213,7 @@ void DynaRecCPU::SetPGXPMode(uint32_t pgxpMode) {
class DynaRecCPU : public PCSX::R3000Acpu {
public:
DynaRecCPU() : R3000Acpu("x86 DynaRec") {}
DynaRecCPU() : R3000Acpu("Dynarec (x86-32)") {}
virtual bool Implemented() final { return false; }
virtual bool Init() final { return false; }
virtual void Reset() final { abort(); }
+11 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include <atomic>
#include <cassert>
#include <cstdint>
#include <memory>
#include <type_traits>
@@ -36,7 +37,7 @@
#elif defined(__x86_64) || defined(_M_AMD64)
#define DYNAREC_X86_64
#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__ARM_ARCH_ISA_A64)
#define DYNAREC_NONE // Placeholder for AA64
#define DYNAREC_AA64
#elif defined(__arm__) || defined(_M_ARM)
#define DYNAREC_NONE // Placeholder for AA32
#elif defined(__powerpc__) || defined(_M_PPC)
@@ -45,6 +46,12 @@
#define DYNAREC_NONE
#endif
// Disable AA64 JIT on Windows for now
#if defined(DYNAREC_AA64) && (defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__))
#undef DYNAREC_AA64
#define DYNAREC_NONE
#endif
namespace PCSX {
typedef union {
@@ -91,6 +98,9 @@ typedef union {
PAIR p[34];
} psxGPRRegs;
// Make sure no packing is inserted anywhere
static_assert(sizeof(psxGPRRegs) == 34 * sizeof(uint32_t));
typedef union {
struct {
uint32_t Index, Random, EntryLo0, BPC, Context, BDA, PIDMask, DCIC;
+1 -1
View File
@@ -993,7 +993,7 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
}
ImGui::Separator();
ImGui::Separator();
ImGui::Text(_("CPU: %s"), g_emulator->m_cpu->isDynarec() ? "DynaRec" : "Interpreted");
ImGui::Text(_("CPU: %s"), g_emulator->m_cpu->getName().c_str());
ImGui::Separator();
ImGui::Text(_("GAME ID: %s"), g_emulator->m_cdromId);
ImGui::Separator();
+4 -1
View File
@@ -18,8 +18,11 @@
***************************************************************************/
#include "gui/widgets/dynarec_disassembly.h"
#ifdef _WIN32
#include <capstone/capstone.h>
#else
#include <capstone.h>
#endif
#include <cinttypes>
#include <fstream>
Vendored Submodule
+1
Submodule third_party/vixl added at 28907a91cc
+69
View File
@@ -0,0 +1,69 @@
# Dockerfile for grumpycoders/pcsx-redux-build-cross
# IMPORTANT: to build this file, the package qemu-user-static needs to be
# installed on the host machine.
FROM debian:bullseye-slim
# The tzdata package isn't docker-friendly, and something pulls it.
ENV DEBIAN_FRONTEND noninteractive
ENV TZ Etc/GMT
RUN apt update
RUN apt dist-upgrade -y
# Utility packages
RUN apt install -y git
RUN apt install -y make
RUN apt install -y pkg-config
# Compilers & base libraries
RUN apt install -y g++-10
RUN apt install -y gcc-10
RUN apt install -y gcc
# CI utilities
RUN apt install -y curl wget
RUN apt install -y python
# Cross Compiler stuff
RUN apt install -y debootstrap
RUN apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
RUN apt install -y qemu-user-static
WORKDIR /
# Create directory to hold aarch64 sysroot
RUN mkdir -p /opt/cross/sysroot
# Run debootstrap to pull down Debian bullseye aarch64 rootfs
RUN debootstrap --arch=arm64 --variant=minbase \
--include=\
build-essential,\
pkg-config,\
libavcodec-dev,\
libavformat-dev,\
libavutil-dev,\
libcapstone-dev,\
libcurl4-openssl-dev,\
libfreetype-dev,\
libglfw3-dev,\
libswresample-dev,\
libuv1-dev,\
zlib1g-dev --foreign bullseye /opt/cross/sysroot
# Copy qemu-user-static over to sysroot so second stage can run
RUN cp /usr/bin/qemu-aarch64-static /opt/cross/sysroot/usr/bin/
# chroot into aarch64 sysroot and run debootstrap second stage to unpack packages and set up symlinks
RUN chroot /opt/cross/sysroot /usr/bin/qemu-aarch64-static /bin/sh -c "/debootstrap/debootstrap --second-stage"
# Fix symlinks in the sysroot
WORKDIR /opt/cross
RUN wget https://raw.githubusercontent.com/abhiTronix/rpi_rootfs/master/scripts/sysroot-relativelinks.py
RUN chmod +x sysroot-relativelinks.py
RUN ./sysroot-relativelinks.py sysroot
RUN mkdir /project
RUN mkdir -p /home/coder/dconf
RUN chmod a+rwx /home/coder/dconf
WORKDIR /project
@@ -0,0 +1,62 @@
# How to Use:
# Change the string below to point to the .map file generated by the recompiler
# Open the JIT code dump in IDA
# Go to File -> Script File and pick this script.
file_to_open = "D:/IDA/DynarecOutput.map" # **PATH TO YOUR MAP FILE GOES HERE**
import sys
from idautils import *
from idaapi import *
codeSegmentNumber = 0
def parse_symbol_line(line): # Parse symbols and add them to the db
words = line.split()
address = int(words[0], 16) # Symbol address
symbolName = words[1] # Symbol name
if (idc.set_name(address, symbolName) == 0): # Check if the set_name call failed, try to work around it (needed for calls to C++ functions)
print("Failed to set", symbolName, "symbol")
global codeSegmentNumber
idaapi.add_segm(0, address, address + 0x4000, "code" + str(codeSegmentNumber), ".code") # Create segment
idc.set_name(address, symbolName) # Attempt to name the address again
codeSegmentNumber += 1
if (len(words) >= 3):
size = int(words[2]) # If available, the size of the symbol (for variables)
if (size == 1):
idaapi.apply_tinfo(address, tinfo_t(BTF_UINT8), idaapi.TINFO_DEFINITE)
elif (size == 2):
idaapi.apply_tinfo(address, tinfo_t(BTF_UINT16), idaapi.TINFO_DEFINITE)
elif (size == 4):
idaapi.apply_tinfo(address, tinfo_t(BTF_UINT32), idaapi.TINFO_DEFINITE)
elif (size == 8):
idaapi.apply_tinfo(address, tinfo_t(BTF_UINT64), idaapi.TINFO_DEFINITE)
elif (size == 16):
idaapi.apply_tinfo(address, tinfo_t(BTF_UINT128), idaapi.TINFO_DEFINITE)
def extract_segments(file):
codeCacheSegment = int(file.readline().split()[0], 16) # Read the base of the code buffer (first line of file)
rebase_program(codeCacheSegment, MSF_FIXONCE) # "Move" the code segment to the proper address
for line in file: # Read segment info
if line.strip() == "endsegs()":
return
else:
words = line.split()
address = int(words[0], 16) # Base segment address (hex)
name = words[1] # Segment name
size = int(words[2], 16) # Segment size (hex)
type = words[3] # .data, .text, etc
idaapi.add_segm(0, address, address + size, name, type) # Create segment
print (f"Created {name} segment at {hex(address)}-{hex(address + size)} ({type})")
with open(file_to_open, "r") as file:
extract_segments(file)
for line in file: # load symbols
parse_symbol_line(line)
print ("Complete")