mirror of
https://github.com/grumpycoders/pcsx-redux.git
synced 2026-10-12 02:15:13 +00:00
@@ -1,4 +1,4 @@
|
||||
name: Linux CI
|
||||
name: Linux CI coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,7 @@ Ryusan
|
||||
Peach / wheremyfoodat
|
||||
Mariano
|
||||
John "Skitchin" Baumann
|
||||
Caleb Yates / caldog20
|
||||
|
||||
|
||||
PCSX-Reloaded
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 %*
|
||||
Executable
+10
@@ -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 $@
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
|
||||
@@ -1888,4 +1888,5 @@ void DynaRecCPU::recBREAK() {
|
||||
recException(Exception::Break);
|
||||
}
|
||||
|
||||
#undef BAILZERO
|
||||
#endif // DYNAREC_X86_64
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
Submodule third_party/vixl added at 28907a91cc
@@ -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")
|
||||
Reference in New Issue
Block a user