Compare commits

..

No commits in common. "main" and "v2.0-71" have entirely different histories.

489 changed files with 26084 additions and 43197 deletions

View file

@ -15,7 +15,6 @@ BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterExternBlock: true

9
.github/getversion.cpp vendored Normal file
View file

@ -0,0 +1,9 @@
#include <stdio.h>
#include "./../src/Common/version.h"
// output current Cemu version for CI workflow. Do not modify
int main()
{
printf("%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR);
return 0;
}

View file

@ -3,10 +3,10 @@ name: Build Cemu
on:
workflow_call:
inputs:
next_version_major:
deploymode:
required: false
type: string
next_version_minor:
experimentalversion:
required: false
type: string
@ -19,32 +19,41 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: "Checkout repo"
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "recursive"
fetch-depth: 0
- name: Setup release mode parameters
- name: "Fetch full history for vcpkg submodule"
run: |
cd dependencies/vcpkg
git fetch --unshallow
git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release"
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
- name: Setup debug mode parameters (for continous build)
if: ${{ inputs.deploymode != 'release' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
echo "BUILD_MODE=debug" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is debug"
- name: Setup version for experimental
if: ${{ inputs.experimentalversion != '' }}
run: |
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV
- name: "Install system dependencies"
run: |
sudo apt update -qq
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build
- name: "Bootstrap vcpkg"
run: |
@ -73,10 +82,12 @@ jobs:
cmake --build build
- name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: mv bin/Cemu_release bin/Cemu
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-linux-x64
path: ./bin/Cemu
@ -86,9 +97,9 @@ jobs:
needs: build-ubuntu
steps:
- name: Checkout Upstream Repo
uses: actions/checkout@v4
uses: actions/checkout@v3
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: cemu-bin-linux-x64
path: bin
@ -96,7 +107,7 @@ jobs:
- name: "Install system dependencies"
run: |
sudo apt update -qq
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev
sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream
- name: "Build AppImage"
run: |
@ -105,7 +116,7 @@ jobs:
dist/linux/appimage.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: cemu-appimage-x64
path: artifacts
@ -114,26 +125,34 @@ jobs:
runs-on: windows-2022
steps:
- name: "Checkout repo"
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Setup release mode parameters
- name: "Fetch full history for vcpkg submodule"
run: |
cd dependencies/vcpkg
git fetch --unshallow
git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
run: |
echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is release"
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
- name: Setup debug mode parameters (for continous build)
if: ${{ inputs.deploymode != 'release' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is debug"
- name: Setup version for experimental
if: ${{ inputs.experimentalversion != '' }}
run: |
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: "Bootstrap vcpkg"
run: |
@ -167,53 +186,53 @@ jobs:
cmake --build . --config ${{ env.BUILD_MODE }}
- name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: Rename-Item bin/Cemu_release.exe Cemu.exe
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-windows-x64
path: ./bin/Cemu.exe
build-macos:
runs-on: macos-14
strategy:
matrix:
arch: [x86_64, arm64]
runs-on: macos-12
steps:
- name: "Checkout repo"
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Setup release mode parameters
- name: "Fetch full history for vcpkg submodule"
run: |
cd dependencies/vcpkg
git fetch --unshallow
git pull --all
- name: Setup release mode parameters (for deploy)
if: ${{ inputs.deploymode == 'release' }}
run: |
echo "BUILD_MODE=release" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is release"
- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
- name: Setup debug mode parameters (for continous build)
if: ${{ inputs.deploymode != 'release' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV
echo "BUILD_MODE=debug" >> $GITHUB_ENV
echo "BUILD_FLAGS=" >> $GITHUB_ENV
echo "Build mode is debug"
- name: Setup version for experimental
if: ${{ inputs.experimentalversion != '' }}
run: |
echo "[INFO] Experimental version ${{ inputs.experimentalversion }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV
- name: "Install system dependencies"
run: |
brew update
brew install ninja nasm automake libtool
- name: "Install molten-vk"
run: |
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.3.0/MoltenVK-macos.tar
tar xf MoltenVK-macos.tar
sudo mkdir -p /usr/local/lib
sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib
- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'
brew install llvm@15 ninja nasm molten-vk automake libtool
- name: "Bootstrap vcpkg"
run: |
@ -239,8 +258,9 @@ jobs:
cd build
cmake .. ${{ env.BUILD_FLAGS }} \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \
-DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \
-DMACOS_BUNDLE=ON \
-DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \
-G Ninja
- name: "Build Cemu"
@ -248,19 +268,21 @@ jobs:
cmake --build build
- name: Prepare artifact
if: ${{ inputs.deploymode == 'release' }}
run: |
mkdir bin/Cemu_app
mv bin/Cemu_release.app bin/Cemu_app/Cemu.app
mv bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu_release bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
sed -i '' 's/Cemu_release/Cemu/g' bin/Cemu_app/Cemu.app/Contents/Info.plist
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh}
chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/Cemu
ln -s /Applications bin/Cemu_app/Applications
hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app"
hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg
rm bin/tmp.dmg
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
if: ${{ inputs.deploymode == 'release' }}
with:
name: cemu-bin-macos-${{ matrix.arch }}
name: cemu-bin-macos-x64
path: ./bin/Cemu.dmg

View file

@ -16,3 +16,6 @@ on:
jobs:
build:
uses: ./.github/workflows/build.yml
with:
deploymode: release
experimentalversion: 999999

View file

@ -0,0 +1,86 @@
name: Deploy experimental release
on:
workflow_dispatch:
jobs:
call-release-build:
uses: ./.github/workflows/build.yml
with:
deploymode: release
experimentalversion: ${{ github.run_number }}
deploy:
name: Deploy experimental release
runs-on: ubuntu-22.04
needs: call-release-build
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: cemu-bin-linux-x64
path: cemu-bin-linux-x64
- uses: actions/download-artifact@v3
with:
name: cemu-appimage-x64
path: cemu-appimage-x64
- uses: actions/download-artifact@v3
with:
name: cemu-bin-windows-x64
path: cemu-bin-windows-x64
- uses: actions/download-artifact@v3
with:
name: cemu-bin-macos-x64
path: cemu-bin-macos-x64
- name: Initialize
run: |
mkdir upload
sudo apt install zip
- name: Get version
run: |
echo "Experimental version: ${{ github.run_number }}"
ls
gcc -o getversion .github/getversion.cpp
./getversion
echo "Cemu CI version: $(./getversion)"
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
echo "CEMU_VERSION=$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV
- name: Create release from windows-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create appimage
run: |
VERSION=${{ env.CEMU_VERSION }}
echo "Cemu Version is $VERSION"
ls cemu-appimage-x64
mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage
- name: Create release from linux-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-22.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create release from macos-bin
run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
- name: Create release
run: |
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz
tar xvzf ghr.tar.gz; rm ghr.tar.gz
echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}"
ghr_v0.15.0_linux_amd64/ghr -prerelease -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }} (Experimental)" -b "Cemu experimental release" "v${{ env.CEMU_VERSION }}" ./upload

View file

@ -1,151 +0,0 @@
name: Deploy release
on:
workflow_dispatch:
inputs:
changelog0:
description: 'Enter the changelog lines for this release. Each line is a feature / bullet point. Do not use dash.'
required: true
type: string
changelog1:
description: 'Feature 2'
required: false
type: string
changelog2:
description: 'Feature 3'
required: false
type: string
changelog3:
description: 'Feature 4'
required: false
type: string
changelog4:
description: 'Feature 5'
required: false
type: string
changelog5:
description: 'Feature 6'
required: false
type: string
changelog6:
description: 'Feature 7'
required: false
type: string
changelog7:
description: 'Feature 8'
required: false
type: string
changelog8:
description: 'Feature 9'
required: false
type: string
changelog9:
description: 'Feature 10'
required: false
type: string
jobs:
calculate-version:
name: Calculate Version
uses: ./.github/workflows/determine_release_version.yml
call-release-build:
uses: ./.github/workflows/build.yml
needs: calculate-version
with:
next_version_major: ${{ needs.calculate-version.outputs.next_version_major }}
next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }}
deploy:
name: Deploy release
runs-on: ubuntu-22.04
needs: [call-release-build, calculate-version]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: generate_changelog
run: |
CHANGELOG=""
if [ -n "${{ github.event.inputs.changelog0 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog0 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog1 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog1 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog2 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog2 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog3 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog3 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog4 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog4 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog5 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog5 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog6 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog6 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog7 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog7 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog8 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog8 }}\n"; fi
if [ -n "${{ github.event.inputs.changelog9 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog9 }}\n"; fi
echo -e "$CHANGELOG"
echo "RELEASE_BODY=$CHANGELOG" >> $GITHUB_ENV
- uses: actions/download-artifact@v4
with:
name: cemu-bin-linux-x64
path: cemu-bin-linux-x64
- uses: actions/download-artifact@v4
with:
name: cemu-appimage-x64
path: cemu-appimage-x64
- uses: actions/download-artifact@v4
with:
name: cemu-bin-windows-x64
path: cemu-bin-windows-x64
- uses: actions/download-artifact@v4
with:
name: cemu-bin-macos-x64
path: cemu-bin-macos-x64
- name: Initialize
run: |
mkdir upload
sudo apt install zip
- name: Set version dependent vars
run: |
echo "Version: ${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}"
echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV
- name: Create release from windows-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create appimage
run: |
VERSION=${{ env.CEMU_VERSION }}
echo "Cemu Version is $VERSION"
ls cemu-appimage-x64
mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage
- name: Create release from linux-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-22.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create release from macos-bin
run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
- name: Create release
run: |
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz
tar xvzf ghr.tar.gz; rm ghr.tar.gz
echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}"
CHANGELOG_UNESCAPED=$(printf "%s\n" "${{ env.RELEASE_BODY }}" | sed 's/\\n/\n/g')
RELEASE_BODY=$(printf "%s\n%s" \
"**Changelog:**" \
"$CHANGELOG_UNESCAPED")
ghr_v0.15.0_linux_amd64/ghr -draft -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "$RELEASE_BODY" "v${{ env.CEMU_VERSION }}" ./upload

View file

@ -0,0 +1,85 @@
name: Create new release
on:
workflow_dispatch:
inputs:
PlaceholderInput:
description: PlaceholderInput
required: false
jobs:
call-release-build:
uses: ./.github/workflows/build.yml
with:
deploymode: release
deploy:
name: Deploy release
runs-on: ubuntu-20.04
needs: call-release-build
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: cemu-bin-linux-x64
path: cemu-bin-linux-x64
- uses: actions/download-artifact@v3
with:
name: cemu-appimage-x64
path: cemu-appimage-x64
- uses: actions/download-artifact@v3
with:
name: cemu-bin-windows-x64
path: cemu-bin-windows-x64
- uses: actions/download-artifact@v3
with:
name: cemu-bin-macos-x64
path: cemu-bin-macos-x64
- name: Initialize
run: |
mkdir upload
sudo apt update -qq
sudo apt install -y zip
- name: Get Cemu release version
run: |
gcc -o getversion .github/getversion.cpp
echo "Cemu CI version: $(./getversion)"
echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)" >> $GITHUB_ENV
echo "CEMU_VERSION=$(./getversion)" >> $GITHUB_ENV
- name: Create release from windows-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create appimage
run: |
VERSION=${{ env.CEMU_VERSION }}
echo "Cemu Version is $VERSION"
ls cemu-appimage-x64
mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage
- name: Create release from ubuntu-bin
run: |
ls ./
ls ./bin/
cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }}
mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu
zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }}
rm -r ./${{ env.CEMU_FOLDER_NAME }}
- name: Create release from macos-bin
run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg
- name: Create release
run: |
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz
tar xvzf ghr.tar.gz; rm ghr.tar.gz
ghr_v0.15.0_linux_amd64/ghr -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "Changelog:" v${{ env.CEMU_VERSION }} ./upload

View file

@ -1,74 +0,0 @@
name: Calculate Next Version from release history
on:
workflow_dispatch:
workflow_call:
outputs:
next_version:
description: "The next semantic version"
value: ${{ jobs.calculate-version.outputs.next_version }}
next_version_major:
description: "The next semantic version (major)"
value: ${{ jobs.calculate-version.outputs.next_version_major }}
next_version_minor:
description: "The next semantic version (minor)"
value: ${{ jobs.calculate-version.outputs.next_version_minor }}
jobs:
calculate-version:
runs-on: ubuntu-latest
outputs:
next_version: ${{ steps.calculate_next_version.outputs.next_version }}
next_version_major: ${{ steps.calculate_next_version.outputs.next_version_major }}
next_version_minor: ${{ steps.calculate_next_version.outputs.next_version_minor }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Get all releases
id: get_all_releases
run: |
# Fetch all releases and check for API errors
RESPONSE=$(curl -s -o response.json -w "%{http_code}" "https://api.github.com/repos/${{ github.repository }}/releases?per_page=100")
if [ "$RESPONSE" -ne 200 ]; then
echo "Failed to fetch releases. HTTP status: $RESPONSE"
cat response.json
exit 1
fi
# Extract and sort tags
ALL_TAGS=$(jq -r '.[].tag_name' response.json | grep -E '^v[0-9]+\.[0-9]+(-[0-9]+)?$' | sed 's/-.*//' | sort -V | tail -n 1)
# Exit if no tags were found
if [ -z "$ALL_TAGS" ]; then
echo "No valid tags found."
exit 1
fi
echo "::set-output name=tag::$ALL_TAGS"
# echo "tag=$ALL_TAGS" >> $GITHUB_STATE
- name: Calculate next semver minor
id: calculate_next_version
run: |
LATEST_VERSION=${{ steps.get_all_releases.outputs.tag }}
# strip 'v' prefix and split into major.minor
LATEST_VERSION=${LATEST_VERSION//v/}
IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
# increment the minor version
MINOR=$((MINOR + 1))
NEXT_VERSION="${MAJOR}.${MINOR}"
echo "Major: $MAJOR"
echo "Minor: $MINOR"
echo "Next version: $NEXT_VERSION"
echo "::set-output name=next_version::$NEXT_VERSION"
echo "::set-output name=next_version_major::$MAJOR"
echo "::set-output name=next_version_minor::$MINOR"

View file

@ -35,7 +35,7 @@ jobs:
-o cemu.pot
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: POT file
path: ./cemu.pot

1
.gitignore vendored
View file

@ -39,7 +39,6 @@ bin/sdcard/*
bin/screenshots/*
bin/dump/*
bin/cafeLibs/*
bin/portable/*
bin/keys.txt
!bin/shaderCache/info.txt

7
.gitmodules vendored
View file

@ -9,15 +9,10 @@
[submodule "dependencies/vcpkg"]
path = dependencies/vcpkg
url = https://github.com/microsoft/vcpkg
shallow = false
shallow = true
[submodule "dependencies/Vulkan-Headers"]
path = dependencies/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers
shallow = true
[submodule "dependencies/imgui"]
path = dependencies/imgui
url = https://github.com/ocornut/imgui
shallow = true
[submodule "dependencies/xbyak_aarch64"]
path = dependencies/xbyak_aarch64
url = https://github.com/fujitsu/xbyak_aarch64

View file

@ -16,11 +16,11 @@
- [Compiling Errors](#compiling-errors)
- [Building Errors](#building-errors)
- [macOS](#macos)
- [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used)
- [Installing brew](#installing-brew)
- [Installing Tool Dependencies](#installing-tool-dependencies)
- [Installing Library Dependencies](#installing-library-dependencies)
- [Build Cemu using CMake](#build-cemu-using-cmake)
- [Updating Cemu and source code](#updating-cemu-and-source-code)
- [Installing Dependencies](#installing-dependencies)
- [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang)
- [Updating Cemu and source code](#updating-cemu-and-source-code)
## Windows
@ -46,10 +46,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required
### Dependencies
#### For Arch and derivatives:
`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
`sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip`
#### For Debian, Ubuntu and derivatives:
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build`
`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build`
You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package.
@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan
`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja`
#### For Fedora and derivatives:
`sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static`
`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel`
### Build Cemu
@ -120,9 +120,6 @@ This section refers to running `cmake -S...` (truncated).
* Compiling failed during rebuild after `git pull` with an error that mentions RPATH
* Add the following and try running the command again:
* `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON`
* Environment variable `VCPKG_FORCE_SYSTEM_BINARIES` must be set.
* Execute the folowing and then try running the command again:
* `export VCPKG_FORCE_SYSTEM_BINARIES=1`
* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency.
@ -144,41 +141,31 @@ If you are getting a different error than any of the errors listed above, you ma
## macOS
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below
don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that
you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't
supported on macOS, so Vulkan must be used through the Molten-VK compatibility layer.
To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and
below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics
API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the
Molten-VK compatibility layer
### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used
You can skip this section if you have an Intel Mac. Every time you compile, you need to perform steps 2.
1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once
2. `arch -x86_64 zsh` # run an x64 shell
### Installing brew
1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
2. Set up the Homebrew shell environment:
1. **On an Intel Mac:** `eval "$(/usr/local/Homebrew/bin/brew shellenv)"`
2. **On an Apple Silicon Mac:** eval `"$(/opt/homebrew/bin/brew shellenv)"`
2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env
### Installing Tool Dependencies
### Installing Dependencies
The native versions of these can be used regardless of what type of Mac you have.
`brew install git cmake ninja nasm automake libtool`
### Installing Library Dependencies
**On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used to install these dependencies:**
1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once
2. `arch -x86_64 zsh` # run an x64 shell
3. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
4. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"`
Then install the dependencies:
`brew install boost molten-vk`
### Build Cemu using CMake
`brew install boost git cmake llvm ninja nasm molten-vk automake libtool`
### Build Cemu using CMake and Clang
1. `git clone --recursive https://github.com/cemu-project/Cemu`
2. `cd Cemu`
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Ninja`
3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja`
4. `cmake --build build`
5. You should now have a Cemu executable file in the /bin folder, which you can run using `./bin/Cemu_release`.
@ -192,41 +179,3 @@ Then install the dependencies:
If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building.
## CMake configure flags
Some flags can be passed during CMake configure to customise which features are enabled on build.
Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -DENABLE_VULKAN=OFF`
### All platforms
| Flag | | Description | Default | Note |
|--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------|
| ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | |
| CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | |
| ENABLE_CUBEB | | Enable cubeb audio backend | ON | |
| ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | |
| ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | Currently required |
| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | |
| ENABLE_SDL | | Enable SDLController controller API | ON | Currently required |
| ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | |
| ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | |
| ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required |
### Windows
| Flag | Description | Default | Note |
|--------------------|-----------------------------------|---------|--------------------|
| ENABLE_DIRECTAUDIO | Enable DirectAudio audio backend | ON | Currently required |
| ENABLE_DIRECTINPUT | Enable DirectInput controller API | ON | Currently required |
| ENABLE_XAUDIO | Enable XAudio audio backend | ON | |
| ENABLE_XINPUT | Enable XInput controller API | ON | |
### Linux
| Flag | Description | Default |
|-----------------------|----------------------------------------------------|---------|
| ENABLE_BLUEZ | Build with Bluez (used for Wiimote controller API) | ON |
| ENABLE_FERAL_GAMEMODE | Enable Feral Interactive GameMode support | ON |
| ENABLE_WAYLAND | Enable Wayland support | ON |
### macOS
| Flag | Description | Default |
|--------------|------------------------------------------------|---------|
| MACOS_BUNDLE | MacOS executable will be an application bundle | OFF |

View file

@ -2,40 +2,20 @@ cmake_minimum_required(VERSION 3.21.1)
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON)
set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version
# used by CI script to set version:
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
set(EMULATOR_VERSION_MINOR "0" CACHE STRING "")
set(EMULATOR_VERSION_PATCH "0" CACHE STRING "")
execute_process(
if (EXPERIMENTAL_VERSION)
add_definitions(-DEMULATOR_VERSION_MINOR=${EXPERIMENTAL_VERSION})
execute_process(
COMMAND git log --format=%h -1
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
)
add_definitions(-DEMULATOR_HASH=${GIT_HASH})
endif()
if (ENABLE_VCPKG)
# check if vcpkg is shallow and unshallow it if necessary
execute_process(
COMMAND git rev-parse --is-shallow-repository
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg
OUTPUT_VARIABLE is_vcpkg_shallow
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(is_vcpkg_shallow STREQUAL "true")
message(STATUS "vcpkg is shallow. Unshallowing it now...")
execute_process(
COMMAND git fetch --unshallow
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
)
endif()
if(UNIX AND NOT APPLE)
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux")
elseif(APPLE)
@ -64,10 +44,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_definitions($<$<CONFIG:Debug>:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT
add_definitions(-DEMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR})
add_definitions(-DEMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR})
add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# enable link time optimization for release builds
@ -93,13 +69,11 @@ endif()
if (APPLE)
enable_language(OBJC OBJCXX)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
endif()
if (UNIX AND NOT APPLE)
option(ENABLE_WAYLAND "Build with Wayland support" ON)
option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON)
option(ENABLE_BLUEZ "Build with Bluez support" ON)
endif()
option(ENABLE_OPENGL "Enables the OpenGL backend" ON)
@ -124,6 +98,23 @@ if (WIN32)
endif()
option(ENABLE_CUBEB "Enabled cubeb backend" ON)
# usb hid backends
if (WIN32)
option(ENABLE_NSYSHID_WINDOWS_HID "Enables the native Windows HID backend for nsyshid" ON)
endif ()
# libusb and windows hid backends shouldn't be active at the same time; otherwise we'd see all devices twice!
if (NOT ENABLE_NSYSHID_WINDOWS_HID)
option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON)
else ()
set(ENABLE_NSYSHID_LIBUSB OFF CACHE BOOL "" FORCE)
endif ()
if (ENABLE_NSYSHID_WINDOWS_HID)
add_compile_definitions(NSYSHID_ENABLE_BACKEND_WINDOWS_HID)
endif ()
if (ENABLE_NSYSHID_LIBUSB)
add_compile_definitions(NSYSHID_ENABLE_BACKEND_LIBUSB)
endif ()
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
set(THREADS_PREFER_PTHREAD_FLAG true)
@ -164,12 +155,6 @@ if (UNIX AND NOT APPLE)
endif()
find_package(GTK3 REQUIRED)
if(ENABLE_BLUEZ)
find_package(bluez REQUIRED)
set(SUPPORTS_WIIMOTE ON)
add_compile_definitions(HAS_BLUEZ)
endif()
endif()
if (ENABLE_VULKAN)
@ -188,7 +173,7 @@ endif()
if (ENABLE_HIDAPI)
find_package(hidapi REQUIRED)
set(SUPPORTS_WIIMOTE ON)
set(ENABLE_WIIMOTE ON)
add_compile_definitions(HAS_HIDAPI)
endif ()
@ -213,7 +198,7 @@ if (ENABLE_CUBEB)
option(BUILD_TOOLS "" OFF)
option(BUNDLE_SPEEX "" OFF)
set(USE_WINMM OFF CACHE BOOL "")
add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL SYSTEM)
add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL)
set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_library(cubeb::cubeb ALIAS cubeb)
endif()
@ -222,15 +207,6 @@ endif()
add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL)
if (CMAKE_OSX_ARCHITECTURES)
set(CEMU_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES})
else()
set(CEMU_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
endif()
if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)")
add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL)
endif()
find_package(ZArchive)
if (NOT ZArchive_FOUND)
add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
bin/shaderCache/info.txt Normal file
View file

@ -0,0 +1 @@
If you plan to transfer the shader cache to a different PC or Cemu installation you only need to copy the 'transferable' directory.

View file

@ -1,26 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="boost::container::small_vector&lt;*&gt;">
<Expand>
<Item Name="[size]">m_holder.m_size</Item>
<ArrayItems>
<Size>m_holder.m_size</Size>
<ValuePointer>m_holder.m_start</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="boost::container::static_vector&lt;*&gt;">
<DisplayString>{{ size={m_holder.m_size} }}</DisplayString>
<Expand>
<Item Name="[size]" ExcludeView="simple">m_holder.m_size</Item>
<Item Name="[capacity]" ExcludeView="simple">static_capacity</Item>
<ArrayItems>
<Size>m_holder.m_size</Size>
<ValuePointer>($T1*)m_holder.storage.data</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>

View file

@ -1,20 +0,0 @@
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
# SPDX-License-Identifier: ISC
find_package(bluez CONFIG)
if (NOT bluez_FOUND)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez)
if (bluez_FOUND)
add_library(bluez::bluez ALIAS PkgConfig::bluez)
endif ()
endif ()
endif ()
find_package_handle_standard_args(bluez
REQUIRED_VARS
bluez_LINK_LIBRARIES
bluez_FOUND
VERSION_VAR bluez_VERSION
)

View file

@ -117,13 +117,7 @@ add_library (ih264d
"decoder/ivd.h"
)
if (CMAKE_OSX_ARCHITECTURES)
set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES})
else()
set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
endif()
if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64")
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86")
include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES})
target_sources(ih264d PRIVATE
@ -146,7 +140,7 @@ target_sources(ih264d PRIVATE
"decoder/x86/ih264d_function_selector_sse42.c"
"decoder/x86/ih264d_function_selector_ssse3.c"
)
elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64")
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
enable_language( C CXX ASM )
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
@ -183,11 +177,8 @@ target_sources(ih264d PRIVATE
"decoder/arm/ih264d_function_selector.c"
)
target_compile_options(ih264d PRIVATE -DARMV8)
if(APPLE)
target_sources(ih264d PRIVATE "common/armv8/macos_arm_symbol_aliases.s")
endif()
else()
message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}")
message(FATAL_ERROR "ih264d unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif()
if(MSVC)

View file

@ -429,13 +429,8 @@ ih264_intra_pred_chroma_8x8_mode_plane_av8:
rev64 v7.4h, v2.4h
ld1 {v3.2s}, [x10]
sub x5, x3, #8
#ifdef __APPLE__
adrp x12, _ih264_gai1_intrapred_chroma_plane_coeffs1@GOTPAGE
ldr x12, [x12, _ih264_gai1_intrapred_chroma_plane_coeffs1@GOTPAGEOFF]
#else
adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs1
ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs1]
#endif
usubl v10.8h, v5.8b, v1.8b
ld1 {v8.8b, v9.8b}, [x12] // Load multiplication factors 1 to 8 into D3
mov v8.d[1], v9.d[0]
@ -489,13 +484,10 @@ ih264_intra_pred_chroma_8x8_mode_plane_av8:
zip1 v1.8h, v0.8h, v2.8h
zip2 v2.8h, v0.8h, v2.8h
mov v0.16b, v1.16b
#ifdef __APPLE__
adrp x12, _ih264_gai1_intrapred_chroma_plane_coeffs2@GOTPAGE
ldr x12, [x12, _ih264_gai1_intrapred_chroma_plane_coeffs2@GOTPAGEOFF]
#else
adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs2
ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs2]
#endif
ld1 {v8.2s, v9.2s}, [x12]
mov v8.d[1], v9.d[0]
mov v10.16b, v8.16b

View file

@ -431,13 +431,10 @@ ih264_intra_pred_luma_16x16_mode_plane_av8:
mov x10, x1 //top_left
mov x4, #-1
ld1 {v2.2s}, [x1], x8
#ifdef __APPLE__
adrp x7, _ih264_gai1_intrapred_luma_plane_coeffs@GOTPAGE
ldr x7, [x7, _ih264_gai1_intrapred_luma_plane_coeffs@GOTPAGEOFF]
#else
adrp x7, :got:ih264_gai1_intrapred_luma_plane_coeffs
ldr x7, [x7, #:got_lo12:ih264_gai1_intrapred_luma_plane_coeffs]
#endif
ld1 {v0.2s}, [x1]
rev64 v2.8b, v2.8b
ld1 {v6.2s, v7.2s}, [x7]

View file

@ -1029,13 +1029,9 @@ ih264_intra_pred_luma_8x8_mode_horz_u_av8:
mov v3.d[0], v2.d[1]
ext v4.16b, v2.16b , v2.16b , #1
mov v5.d[0], v4.d[1]
#ifdef __APPLE__
adrp x12, _ih264_gai1_intrapred_luma_8x8_horz_u@GOTPAGE
ldr x12, [x12, _ih264_gai1_intrapred_luma_8x8_horz_u@GOTPAGEOFF]
#else
adrp x12, :got:ih264_gai1_intrapred_luma_8x8_horz_u
ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_luma_8x8_horz_u]
#endif
uaddl v20.8h, v0.8b, v2.8b
uaddl v22.8h, v1.8b, v3.8b
uaddl v24.8h, v2.8b, v4.8b

View file

@ -142,22 +142,14 @@ ih264_weighted_bi_pred_luma_av8:
sxtw x4, w4
sxtw x5, w5
stp x19, x20, [sp, #-16]!
#ifndef __APPLE__
ldr w8, [sp, #80] //Load wt2 in w8
ldr w9, [sp, #88] //Load ofst1 in w9
ldr w10, [sp, #96] //Load ofst2 in w10
ldr w11, [sp, #104] //Load ht in w11
ldr w12, [sp, #112] //Load wd in w12
#else
ldr w8, [sp, #80] //Load wt2 in w8
ldr w9, [sp, #84] //Load ofst1 in w9
ldr w10, [sp, #88] //Load ofst2 in w10
ldr w11, [sp, #92] //Load ht in w11
ldr w12, [sp, #96] //Load wd in w12
#endif
add w6, w6, #1 //w6 = log_WD + 1
neg w10, w6 //w10 = -(log_WD + 1)
dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit)
ldr w10, [sp, #96] //Load ofst2 in w10
ldr w11, [sp, #104] //Load ht in w11
ldr w12, [sp, #112] //Load wd in w12
add w9, w9, #1 //w9 = ofst1 + 1
add w9, w9, w10 //w9 = ofst1 + ofst2 + 1
mov v2.s[0], w7
@ -432,24 +424,17 @@ ih264_weighted_bi_pred_chroma_av8:
sxtw x5, w5
stp x19, x20, [sp, #-16]!
#ifndef __APPLE__
ldr w8, [sp, #80] //Load wt2 in w8
ldr w9, [sp, #88] //Load ofst1 in w9
ldr w10, [sp, #96] //Load ofst2 in w10
ldr w11, [sp, #104] //Load ht in w11
ldr w12, [sp, #112] //Load wd in w12
#else
ldr w8, [sp, #80] //Load wt2 in w8
ldr w9, [sp, #84] //Load ofst1 in w9
ldr w10, [sp, #88] //Load ofst2 in w10
ldr w11, [sp, #92] //Load ht in w11
ldr w12, [sp, #96] //Load wd in w12
#endif
dup v4.4s, w8 //Q2 = (wt2_u, wt2_v) (32-bit)
dup v2.4s, w7 //Q1 = (wt1_u, wt1_v) (32-bit)
add w6, w6, #1 //w6 = log_WD + 1
ldr w9, [sp, #88] //Load ofst1 in w9
ldr w10, [sp, #96] //Load ofst2 in w10
neg w20, w6 //w20 = -(log_WD + 1)
dup v0.8h, w20 //Q0 = -(log_WD + 1) (16-bit)
ldr w11, [sp, #104] //Load ht in x11
ldr w12, [sp, #112] //Load wd in x12
dup v20.8h, w9 //0ffset1
dup v21.8h, w10 //0ffset2
srhadd v6.8b, v20.8b, v21.8b

View file

@ -1,185 +0,0 @@
// macOS clang compilers append preceding underscores to function names, this is to prevent
// mismatches with the assembly function names and the C functions as defined in the header.
.global _ih264_deblk_chroma_horz_bs4_av8
_ih264_deblk_chroma_horz_bs4_av8 = ih264_deblk_chroma_horz_bs4_av8
.global _ih264_deblk_chroma_horz_bslt4_av8
_ih264_deblk_chroma_horz_bslt4_av8 = ih264_deblk_chroma_horz_bslt4_av8
.global _ih264_deblk_chroma_vert_bs4_av8
_ih264_deblk_chroma_vert_bs4_av8 = ih264_deblk_chroma_vert_bs4_av8
.global _ih264_deblk_chroma_vert_bslt4_av8
_ih264_deblk_chroma_vert_bslt4_av8 = ih264_deblk_chroma_vert_bslt4_av8
.global _ih264_deblk_luma_horz_bs4_av8
_ih264_deblk_luma_horz_bs4_av8 = ih264_deblk_luma_horz_bs4_av8
.global _ih264_deblk_luma_horz_bslt4_av8
_ih264_deblk_luma_horz_bslt4_av8 = ih264_deblk_luma_horz_bslt4_av8
.global _ih264_deblk_luma_vert_bs4_av8
_ih264_deblk_luma_vert_bs4_av8 = ih264_deblk_luma_vert_bs4_av8
.global _ih264_deblk_luma_vert_bslt4_av8
_ih264_deblk_luma_vert_bslt4_av8 = ih264_deblk_luma_vert_bslt4_av8
.global _ih264_default_weighted_pred_chroma_av8
_ih264_default_weighted_pred_chroma_av8 = ih264_default_weighted_pred_chroma_av8
.global _ih264_default_weighted_pred_luma_av8
_ih264_default_weighted_pred_luma_av8 = ih264_default_weighted_pred_luma_av8
.global _ih264_ihadamard_scaling_4x4_av8
_ih264_ihadamard_scaling_4x4_av8 = ih264_ihadamard_scaling_4x4_av8
.global _ih264_inter_pred_chroma_av8
_ih264_inter_pred_chroma_av8 = ih264_inter_pred_chroma_av8
.global _ih264_inter_pred_luma_copy_av8
_ih264_inter_pred_luma_copy_av8 = ih264_inter_pred_luma_copy_av8
.global _ih264_inter_pred_luma_horz_av8
_ih264_inter_pred_luma_horz_av8 = ih264_inter_pred_luma_horz_av8
.global _ih264_inter_pred_luma_horz_hpel_vert_hpel_av8
_ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 = ih264_inter_pred_luma_horz_hpel_vert_hpel_av8
.global _ih264_inter_pred_luma_horz_hpel_vert_qpel_av8
_ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8
.global _ih264_inter_pred_luma_horz_qpel_av8
_ih264_inter_pred_luma_horz_qpel_av8 = ih264_inter_pred_luma_horz_qpel_av8
.global _ih264_inter_pred_luma_horz_qpel_vert_hpel_av8
_ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8
.global _ih264_inter_pred_luma_horz_qpel_vert_qpel_av8
_ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8
.global _ih264_inter_pred_luma_vert_av8
_ih264_inter_pred_luma_vert_av8 = ih264_inter_pred_luma_vert_av8
.global _ih264_inter_pred_luma_vert_qpel_av8
_ih264_inter_pred_luma_vert_qpel_av8 = ih264_inter_pred_luma_vert_qpel_av8
.global _ih264_intra_pred_chroma_8x8_mode_horz_av8
_ih264_intra_pred_chroma_8x8_mode_horz_av8 = ih264_intra_pred_chroma_8x8_mode_horz_av8
.global _ih264_intra_pred_chroma_8x8_mode_plane_av8
_ih264_intra_pred_chroma_8x8_mode_plane_av8 = ih264_intra_pred_chroma_8x8_mode_plane_av8
.global _ih264_intra_pred_chroma_8x8_mode_vert_av8
_ih264_intra_pred_chroma_8x8_mode_vert_av8 = ih264_intra_pred_chroma_8x8_mode_vert_av8
.global _ih264_intra_pred_luma_16x16_mode_dc_av8
_ih264_intra_pred_luma_16x16_mode_dc_av8 = ih264_intra_pred_luma_16x16_mode_dc_av8
.global _ih264_intra_pred_luma_16x16_mode_horz_av8
_ih264_intra_pred_luma_16x16_mode_horz_av8 = ih264_intra_pred_luma_16x16_mode_horz_av8
.global _ih264_intra_pred_luma_16x16_mode_plane_av8
_ih264_intra_pred_luma_16x16_mode_plane_av8 = ih264_intra_pred_luma_16x16_mode_plane_av8
.global _ih264_intra_pred_luma_16x16_mode_vert_av8
_ih264_intra_pred_luma_16x16_mode_vert_av8 = ih264_intra_pred_luma_16x16_mode_vert_av8
.global _ih264_intra_pred_luma_4x4_mode_dc_av8
_ih264_intra_pred_luma_4x4_mode_dc_av8 = ih264_intra_pred_luma_4x4_mode_dc_av8
.global _ih264_intra_pred_luma_4x4_mode_diag_dl_av8
_ih264_intra_pred_luma_4x4_mode_diag_dl_av8 = ih264_intra_pred_luma_4x4_mode_diag_dl_av8
.global _ih264_intra_pred_luma_4x4_mode_diag_dr_av8
_ih264_intra_pred_luma_4x4_mode_diag_dr_av8 = ih264_intra_pred_luma_4x4_mode_diag_dr_av8
.global _ih264_intra_pred_luma_4x4_mode_horz_av8
_ih264_intra_pred_luma_4x4_mode_horz_av8 = ih264_intra_pred_luma_4x4_mode_horz_av8
.global _ih264_intra_pred_luma_4x4_mode_horz_d_av8
_ih264_intra_pred_luma_4x4_mode_horz_d_av8 = ih264_intra_pred_luma_4x4_mode_horz_d_av8
.global _ih264_intra_pred_luma_4x4_mode_horz_u_av8
_ih264_intra_pred_luma_4x4_mode_horz_u_av8 = ih264_intra_pred_luma_4x4_mode_horz_u_av8
.global _ih264_intra_pred_luma_4x4_mode_vert_av8
_ih264_intra_pred_luma_4x4_mode_vert_av8 = ih264_intra_pred_luma_4x4_mode_vert_av8
.global _ih264_intra_pred_luma_4x4_mode_vert_l_av8
_ih264_intra_pred_luma_4x4_mode_vert_l_av8 = ih264_intra_pred_luma_4x4_mode_vert_l_av8
.global _ih264_intra_pred_luma_4x4_mode_vert_r_av8
_ih264_intra_pred_luma_4x4_mode_vert_r_av8 = ih264_intra_pred_luma_4x4_mode_vert_r_av8
.global _ih264_intra_pred_luma_8x8_mode_dc_av8
_ih264_intra_pred_luma_8x8_mode_dc_av8 = ih264_intra_pred_luma_8x8_mode_dc_av8
.global _ih264_intra_pred_luma_8x8_mode_diag_dl_av8
_ih264_intra_pred_luma_8x8_mode_diag_dl_av8 = ih264_intra_pred_luma_8x8_mode_diag_dl_av8
.global _ih264_intra_pred_luma_8x8_mode_diag_dr_av8
_ih264_intra_pred_luma_8x8_mode_diag_dr_av8 = ih264_intra_pred_luma_8x8_mode_diag_dr_av8
.global _ih264_intra_pred_luma_8x8_mode_horz_av8
_ih264_intra_pred_luma_8x8_mode_horz_av8 = ih264_intra_pred_luma_8x8_mode_horz_av8
.global _ih264_intra_pred_luma_8x8_mode_horz_d_av8
_ih264_intra_pred_luma_8x8_mode_horz_d_av8 = ih264_intra_pred_luma_8x8_mode_horz_d_av8
.global _ih264_intra_pred_luma_8x8_mode_horz_u_av8
_ih264_intra_pred_luma_8x8_mode_horz_u_av8 = ih264_intra_pred_luma_8x8_mode_horz_u_av8
.global _ih264_intra_pred_luma_8x8_mode_vert_av8
_ih264_intra_pred_luma_8x8_mode_vert_av8 = ih264_intra_pred_luma_8x8_mode_vert_av8
.global _ih264_intra_pred_luma_8x8_mode_vert_l_av8
_ih264_intra_pred_luma_8x8_mode_vert_l_av8 = ih264_intra_pred_luma_8x8_mode_vert_l_av8
.global _ih264_intra_pred_luma_8x8_mode_vert_r_av8
_ih264_intra_pred_luma_8x8_mode_vert_r_av8 = ih264_intra_pred_luma_8x8_mode_vert_r_av8
.global _ih264_iquant_itrans_recon_4x4_av8
_ih264_iquant_itrans_recon_4x4_av8 = ih264_iquant_itrans_recon_4x4_av8
.global _ih264_iquant_itrans_recon_4x4_dc_av8
_ih264_iquant_itrans_recon_4x4_dc_av8 = ih264_iquant_itrans_recon_4x4_dc_av8
.global _ih264_iquant_itrans_recon_8x8_av8
_ih264_iquant_itrans_recon_8x8_av8 = ih264_iquant_itrans_recon_8x8_av8
.global _ih264_iquant_itrans_recon_8x8_dc_av8
_ih264_iquant_itrans_recon_8x8_dc_av8 = ih264_iquant_itrans_recon_8x8_dc_av8
.global _ih264_iquant_itrans_recon_chroma_4x4_av8
_ih264_iquant_itrans_recon_chroma_4x4_av8 = ih264_iquant_itrans_recon_chroma_4x4_av8
.global _ih264_iquant_itrans_recon_chroma_4x4_dc_av8
_ih264_iquant_itrans_recon_chroma_4x4_dc_av8 = ih264_iquant_itrans_recon_chroma_4x4_dc_av8
.global _ih264_pad_left_chroma_av8
_ih264_pad_left_chroma_av8 = ih264_pad_left_chroma_av8
.global _ih264_pad_left_luma_av8
_ih264_pad_left_luma_av8 = ih264_pad_left_luma_av8
.global _ih264_pad_right_chroma_av8
_ih264_pad_right_chroma_av8 = ih264_pad_right_chroma_av8
.global _ih264_pad_right_luma_av8
_ih264_pad_right_luma_av8 = ih264_pad_right_luma_av8
.global _ih264_pad_top_av8
_ih264_pad_top_av8 = ih264_pad_top_av8
.global _ih264_weighted_bi_pred_chroma_av8
_ih264_weighted_bi_pred_chroma_av8 = ih264_weighted_bi_pred_chroma_av8
.global _ih264_weighted_bi_pred_luma_av8
_ih264_weighted_bi_pred_luma_av8 = ih264_weighted_bi_pred_luma_av8
.global _ih264_weighted_pred_chroma_av8
_ih264_weighted_pred_chroma_av8 = ih264_weighted_pred_chroma_av8
.global _ih264_weighted_pred_luma_av8
_ih264_weighted_pred_luma_av8 = ih264_weighted_pred_luma_av8

2
dependencies/vcpkg vendored

@ -1 +1 @@
Subproject commit 533a5fda5c0646d1771345fb572e759283444d5f
Subproject commit 53bef8994c541b6561884a8395ea35715ece75db

View file

@ -0,0 +1,13 @@
diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in
index cc8bcf26d..ead829767 100644
--- a/SDL2Config.cmake.in
+++ b/SDL2Config.cmake.in
@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake")
set(SDL_ALSA @SDL_ALSA@)
set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@)
-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static)
+if(SDL_ALSA)
sdlFindALSA()
endif()
unset(SDL_ALSA)

View file

@ -0,0 +1,13 @@
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index 65a98efbe..2f99f28f1 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -352,7 +352,7 @@ endmacro()
# - HAVE_SDL_LOADSO opt
macro(CheckLibSampleRate)
if(SDL_LIBSAMPLERATE)
- find_package(SampleRate QUIET)
+ find_package(SampleRate CONFIG REQUIRED)
if(SampleRate_FOUND AND TARGET SampleRate::samplerate)
set(HAVE_LIBSAMPLERATE TRUE)
set(HAVE_LIBSAMPLERATE_H TRUE)

View file

@ -0,0 +1,137 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO libsdl-org/SDL
REF "release-${VERSION}"
SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c
HEAD_REF main
PATCHES
deps.patch
alsa-dep-fix.patch
)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED)
string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT)
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
alsa SDL_ALSA
alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA
ibus SDL_IBUS
samplerate SDL_LIBSAMPLERATE
vulkan SDL_VULKAN
wayland SDL_WAYLAND
x11 SDL_X11
INVERTED_FEATURES
alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA
)
if ("x11" IN_LIST FEATURES)
message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n")
endif()
if ("wayland" IN_LIST FEATURES)
message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n")
endif()
if ("ibus" IN_LIST FEATURES)
message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n")
endif()
if(VCPKG_TARGET_IS_UWP)
set(configure_opts WINDOWS_USE_MSBUILD)
endif()
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
${configure_opts}
OPTIONS ${FEATURE_OPTIONS}
-DSDL_STATIC=${SDL_STATIC}
-DSDL_SHARED=${SDL_SHARED}
-DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT}
-DSDL_LIBC=ON
-DSDL_TEST=OFF
-DSDL_INSTALL_CMAKEDIR="cmake"
-DCMAKE_DISABLE_FIND_PACKAGE_Git=ON
-DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON
-DSDL_LIBSAMPLERATE_SHARED=OFF
MAYBE_UNUSED_VARIABLES
SDL_FORCE_STATIC_VCRT
PKG_CONFIG_USE_CMAKE_PREFIX_PATH
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(CONFIG_PATH cmake)
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/debug/include"
"${CURRENT_PACKAGES_DIR}/debug/share"
"${CURRENT_PACKAGES_DIR}/bin/sdl2-config"
"${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config"
"${CURRENT_PACKAGES_DIR}/SDL2.framework"
"${CURRENT_PACKAGES_DIR}/debug/SDL2.framework"
"${CURRENT_PACKAGES_DIR}/share/licenses"
"${CURRENT_PACKAGES_DIR}/share/aclocal"
)
file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*")
if(NOT BINS)
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/bin"
"${CURRENT_PACKAGES_DIR}/debug/bin"
)
endif()
if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link")
file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link")
file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib")
endif()
file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake")
foreach(SHARE_FILE ${SHARE_FILES})
vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main")
endforeach()
endif()
vcpkg_copy_pdbs()
set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)")
set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)")
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX})
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX})
string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}")
string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}")
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ")
endif()
if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ")
endif()
endif()
if(VCPKG_TARGET_IS_UWP)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
endif()
endif()
vcpkg_fixup_pkgconfig()
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt")

View file

@ -0,0 +1,8 @@
sdl2 provides CMake targets:
find_package(SDL2 CONFIG REQUIRED)
target_link_libraries(main
PRIVATE
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)

View file

@ -0,0 +1,68 @@
{
"name": "sdl2",
"version": "2.30.0",
"description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.",
"homepage": "https://www.libsdl.org/download-2.0.php",
"license": "Zlib",
"dependencies": [
{
"name": "dbus",
"default-features": false,
"platform": "linux"
},
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
],
"default-features": [
{
"name": "ibus",
"platform": "linux"
},
{
"name": "wayland",
"platform": "linux"
},
{
"name": "x11",
"platform": "linux"
}
],
"features": {
"alsa": {
"description": "Support for alsa audio",
"dependencies": [
{
"name": "alsa",
"platform": "linux"
}
]
},
"ibus": {
"description": "Build with ibus IME support",
"supports": "linux"
},
"samplerate": {
"description": "Use libsamplerate for audio rate conversion",
"dependencies": [
"libsamplerate"
]
},
"vulkan": {
"description": "Vulkan functionality for SDL"
},
"wayland": {
"description": "Build with Wayland support",
"supports": "linux"
},
"x11": {
"description": "Build with X11 support",
"supports": "!windows"
}
}
}

View file

@ -0,0 +1,13 @@
diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in
index cc8bcf26d..ead829767 100644
--- a/SDL2Config.cmake.in
+++ b/SDL2Config.cmake.in
@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake")
set(SDL_ALSA @SDL_ALSA@)
set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@)
-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static)
+if(SDL_ALSA)
sdlFindALSA()
endif()
unset(SDL_ALSA)

View file

@ -0,0 +1,13 @@
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index 65a98efbe..2f99f28f1 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -352,7 +352,7 @@ endmacro()
# - HAVE_SDL_LOADSO opt
macro(CheckLibSampleRate)
if(SDL_LIBSAMPLERATE)
- find_package(SampleRate QUIET)
+ find_package(SampleRate CONFIG REQUIRED)
if(SampleRate_FOUND AND TARGET SampleRate::samplerate)
set(HAVE_LIBSAMPLERATE TRUE)
set(HAVE_LIBSAMPLERATE_H TRUE)

View file

@ -0,0 +1,137 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO libsdl-org/SDL
REF "release-${VERSION}"
SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c
HEAD_REF main
PATCHES
deps.patch
alsa-dep-fix.patch
)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED)
string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT)
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
alsa SDL_ALSA
alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA
ibus SDL_IBUS
samplerate SDL_LIBSAMPLERATE
vulkan SDL_VULKAN
wayland SDL_WAYLAND
x11 SDL_X11
INVERTED_FEATURES
alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA
)
if ("x11" IN_LIST FEATURES)
message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n")
endif()
if ("wayland" IN_LIST FEATURES)
message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n")
endif()
if ("ibus" IN_LIST FEATURES)
message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n")
endif()
if(VCPKG_TARGET_IS_UWP)
set(configure_opts WINDOWS_USE_MSBUILD)
endif()
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
${configure_opts}
OPTIONS ${FEATURE_OPTIONS}
-DSDL_STATIC=${SDL_STATIC}
-DSDL_SHARED=${SDL_SHARED}
-DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT}
-DSDL_LIBC=ON
-DSDL_TEST=OFF
-DSDL_INSTALL_CMAKEDIR="cmake"
-DCMAKE_DISABLE_FIND_PACKAGE_Git=ON
-DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON
-DSDL_LIBSAMPLERATE_SHARED=OFF
MAYBE_UNUSED_VARIABLES
SDL_FORCE_STATIC_VCRT
PKG_CONFIG_USE_CMAKE_PREFIX_PATH
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(CONFIG_PATH cmake)
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/debug/include"
"${CURRENT_PACKAGES_DIR}/debug/share"
"${CURRENT_PACKAGES_DIR}/bin/sdl2-config"
"${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config"
"${CURRENT_PACKAGES_DIR}/SDL2.framework"
"${CURRENT_PACKAGES_DIR}/debug/SDL2.framework"
"${CURRENT_PACKAGES_DIR}/share/licenses"
"${CURRENT_PACKAGES_DIR}/share/aclocal"
)
file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*")
if(NOT BINS)
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/bin"
"${CURRENT_PACKAGES_DIR}/debug/bin"
)
endif()
if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link")
file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link")
file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib")
endif()
file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake")
foreach(SHARE_FILE ${SHARE_FILES})
vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main")
endforeach()
endif()
vcpkg_copy_pdbs()
set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)")
set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)")
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX})
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX})
string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}")
string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}")
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ")
endif()
if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ")
endif()
endif()
if(VCPKG_TARGET_IS_UWP)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
endif()
endif()
vcpkg_fixup_pkgconfig()
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt")

View file

@ -0,0 +1,8 @@
sdl2 provides CMake targets:
find_package(SDL2 CONFIG REQUIRED)
target_link_libraries(main
PRIVATE
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)

View file

@ -0,0 +1,68 @@
{
"name": "sdl2",
"version": "2.30.0",
"description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.",
"homepage": "https://www.libsdl.org/download-2.0.php",
"license": "Zlib",
"dependencies": [
{
"name": "dbus",
"default-features": false,
"platform": "linux"
},
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
],
"default-features": [
{
"name": "ibus",
"platform": "linux"
},
{
"name": "wayland",
"platform": "linux"
},
{
"name": "x11",
"platform": "linux"
}
],
"features": {
"alsa": {
"description": "Support for alsa audio",
"dependencies": [
{
"name": "alsa",
"platform": "linux"
}
]
},
"ibus": {
"description": "Build with ibus IME support",
"supports": "linux"
},
"samplerate": {
"description": "Use libsamplerate for audio rate conversion",
"dependencies": [
"libsamplerate"
]
},
"vulkan": {
"description": "Vulkan functionality for SDL"
},
"wayland": {
"description": "Build with Wayland support",
"supports": "linux"
},
"x11": {
"description": "Build with X11 support",
"supports": "!windows"
}
}
}

View file

@ -0,0 +1,13 @@
diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in
index cc8bcf26d..ead829767 100644
--- a/SDL2Config.cmake.in
+++ b/SDL2Config.cmake.in
@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake")
set(SDL_ALSA @SDL_ALSA@)
set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@)
-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static)
+if(SDL_ALSA)
sdlFindALSA()
endif()
unset(SDL_ALSA)

View file

@ -0,0 +1,13 @@
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index 65a98efbe..2f99f28f1 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -352,7 +352,7 @@ endmacro()
# - HAVE_SDL_LOADSO opt
macro(CheckLibSampleRate)
if(SDL_LIBSAMPLERATE)
- find_package(SampleRate QUIET)
+ find_package(SampleRate CONFIG REQUIRED)
if(SampleRate_FOUND AND TARGET SampleRate::samplerate)
set(HAVE_LIBSAMPLERATE TRUE)
set(HAVE_LIBSAMPLERATE_H TRUE)

View file

@ -0,0 +1,137 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO libsdl-org/SDL
REF "release-${VERSION}"
SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c
HEAD_REF main
PATCHES
deps.patch
alsa-dep-fix.patch
)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED)
string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT)
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
alsa SDL_ALSA
alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA
ibus SDL_IBUS
samplerate SDL_LIBSAMPLERATE
vulkan SDL_VULKAN
wayland SDL_WAYLAND
x11 SDL_X11
INVERTED_FEATURES
alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA
)
if ("x11" IN_LIST FEATURES)
message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n")
endif()
if ("wayland" IN_LIST FEATURES)
message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n")
endif()
if ("ibus" IN_LIST FEATURES)
message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n")
endif()
if(VCPKG_TARGET_IS_UWP)
set(configure_opts WINDOWS_USE_MSBUILD)
endif()
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
${configure_opts}
OPTIONS ${FEATURE_OPTIONS}
-DSDL_STATIC=${SDL_STATIC}
-DSDL_SHARED=${SDL_SHARED}
-DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT}
-DSDL_LIBC=ON
-DSDL_TEST=OFF
-DSDL_INSTALL_CMAKEDIR="cmake"
-DCMAKE_DISABLE_FIND_PACKAGE_Git=ON
-DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON
-DSDL_LIBSAMPLERATE_SHARED=OFF
MAYBE_UNUSED_VARIABLES
SDL_FORCE_STATIC_VCRT
PKG_CONFIG_USE_CMAKE_PREFIX_PATH
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(CONFIG_PATH cmake)
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/debug/include"
"${CURRENT_PACKAGES_DIR}/debug/share"
"${CURRENT_PACKAGES_DIR}/bin/sdl2-config"
"${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config"
"${CURRENT_PACKAGES_DIR}/SDL2.framework"
"${CURRENT_PACKAGES_DIR}/debug/SDL2.framework"
"${CURRENT_PACKAGES_DIR}/share/licenses"
"${CURRENT_PACKAGES_DIR}/share/aclocal"
)
file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*")
if(NOT BINS)
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/bin"
"${CURRENT_PACKAGES_DIR}/debug/bin"
)
endif()
if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link")
file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link")
file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib")
endif()
file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake")
foreach(SHARE_FILE ${SHARE_FILES})
vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main")
endforeach()
endif()
vcpkg_copy_pdbs()
set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)")
set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)")
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX})
file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX})
string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}")
string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}")
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ")
endif()
if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ")
endif()
endif()
if(VCPKG_TARGET_IS_UWP)
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
endif()
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d")
vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:")
endif()
endif()
vcpkg_fixup_pkgconfig()
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt")

View file

@ -0,0 +1,8 @@
sdl2 provides CMake targets:
find_package(SDL2 CONFIG REQUIRED)
target_link_libraries(main
PRIVATE
$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)

View file

@ -0,0 +1,68 @@
{
"name": "sdl2",
"version": "2.30.0",
"description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.",
"homepage": "https://www.libsdl.org/download-2.0.php",
"license": "Zlib",
"dependencies": [
{
"name": "dbus",
"default-features": false,
"platform": "linux"
},
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
],
"default-features": [
{
"name": "ibus",
"platform": "linux"
},
{
"name": "wayland",
"platform": "linux"
},
{
"name": "x11",
"platform": "linux"
}
],
"features": {
"alsa": {
"description": "Support for alsa audio",
"dependencies": [
{
"name": "alsa",
"platform": "linux"
}
]
},
"ibus": {
"description": "Build with ibus IME support",
"supports": "linux"
},
"samplerate": {
"description": "Use libsamplerate for audio rate conversion",
"dependencies": [
"libsamplerate"
]
},
"vulkan": {
"description": "Vulkan functionality for SDL"
},
"wayland": {
"description": "Build with Wayland support",
"supports": "linux"
},
"x11": {
"description": "Build with X11 support",
"supports": "!windows"
}
}
}

@ -1 +0,0 @@
Subproject commit 904b8923457f3ec0d6f82ea2d6832a792851194d

View file

@ -10,8 +10,6 @@ curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/re
chmod a+x mkappimage.AppImage
curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh"
chmod a+x linuxdeploy-plugin-gtk.sh
curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh"
chmod a+x linuxdeploy-plugin-checkrt.sh
if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then
sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh
@ -35,14 +33,12 @@ chmod +x AppDir/usr/bin/Cemu
cp /usr/lib/x86_64-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib
export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync"
export NO_STRIP=1
./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \
--appdir="${GITHUB_WORKSPACE}"/AppDir/ \
-d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \
-i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \
-e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \
--plugin gtk \
--plugin checkrt
--plugin gtk
if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then
GITVERSION=experimental
@ -50,6 +46,7 @@ fi
echo "Cemu Version Cemu-${GITVERSION}"
rm AppDir/usr/lib/libwayland-client.so.0
cp /lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/lib/
echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-hooks/linuxdeploy-plugin-gtk.sh
VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir

View file

@ -24,4 +24,3 @@ Comment[it]=Software per emulare giochi e applicazioni per Wii U su PC
Categories=Game;Emulator;
Keywords=Nintendo;
MimeType=application/x-wii-u-rom;
StartupWMClass=Cemu

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<content>
<networkname>CustomExample</networkname>
<disablesslverification>0</disablesslverification>
<urls>
<act>https://account.nintendo.net</act>
<ecs>https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP</ecs>
<nus>https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP</nus>
<ias>https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP</ias>
<ccsu>https://ccs.wup.shop.nintendo.net/ccs/download</ccsu>
<ccs>http://ccs.cdn.wup.shop.nintendo.net/ccs/download</ccs>
<idbe>https://idbe-wup.cdn.nintendo.net/icondata</idbe>
<boss>https://npts.app.nintendo.net/p01/tasksheet</boss>
<tagaya>https://tagaya.wup.shop.nintendo.net/tagaya/versionlist</tagaya>
<olv>https://discovery.olv.nintendo.net/v1/endpoint</olv>
</urls>
</content>

View file

@ -49,18 +49,13 @@ add_subdirectory(audio)
add_subdirectory(util)
add_subdirectory(imgui)
add_subdirectory(resource)
add_subdirectory(asm)
add_executable(CemuBin
main.cpp
mainLLE.cpp
)
if(MSVC AND MSVC_VERSION EQUAL 1940)
# workaround for an msvc issue on VS 17.10 where generated ILK files are too large
# see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511
set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO")
endif()
if(WIN32)
target_sources(CemuBin PRIVATE
resource/cemu.rc
@ -81,8 +76,8 @@ if (MACOS_BUNDLE)
set(MACOSX_BUNDLE_ICON_FILE "cemu.icns")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu")
set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION})
set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project")
set(MACOSX_BUNDLE_CATEGORY "public.app-category.games")
@ -100,26 +95,11 @@ if (MACOS_BUNDLE)
COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}")
endforeach(folder)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/debug/lib/libusb-1.0.0.dylib")
else()
set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib/libusb-1.0.0.dylib")
endif()
if (EXISTS "/usr/local/lib/libMoltenVK.dylib")
set(MOLTENVK_PATH "/usr/local/lib/libMoltenVK.dylib")
elseif (EXISTS "/opt/homebrew/lib/libMoltenVK.dylib")
set(MOLTENVK_PATH "/opt/homebrew/lib/libMoltenVK.dylib")
else()
message(FATAL_ERROR "failed to find libMoltenVK.dylib")
endif ()
add_custom_command (TARGET CemuBin POST_BUILD
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${MOLTENVK_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib"
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib"
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh"
COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib"
COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib"
COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}"
COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}")
endif()
set_target_properties(CemuBin PROPERTIES

View file

@ -10,7 +10,6 @@ add_library(CemuCafe
Filesystem/fscDeviceRedirect.cpp
Filesystem/fscDeviceWua.cpp
Filesystem/fscDeviceWud.cpp
Filesystem/fscDeviceWuhb.cpp
Filesystem/fsc.h
Filesystem/FST/FST.cpp
Filesystem/FST/FST.h
@ -19,9 +18,6 @@ add_library(CemuCafe
Filesystem/FST/KeyCache.h
Filesystem/WUD/wud.cpp
Filesystem/WUD/wud.h
Filesystem/WUHB/RomFSStructs.h
Filesystem/WUHB/WUHBReader.cpp
Filesystem/WUHB/WUHBReader.h
GamePatch.cpp
GamePatch.h
GameProfile/GameProfile.cpp
@ -67,31 +63,24 @@ add_library(CemuCafe
HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h
HW/Espresso/Recompiler/PPCRecompiler.cpp
HW/Espresso/Recompiler/PPCRecompiler.h
HW/Espresso/Recompiler/IML/IML.h
HW/Espresso/Recompiler/IML/IMLSegment.cpp
HW/Espresso/Recompiler/IML/IMLSegment.h
HW/Espresso/Recompiler/IML/IMLInstruction.cpp
HW/Espresso/Recompiler/IML/IMLInstruction.h
HW/Espresso/Recompiler/IML/IMLDebug.cpp
HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp
HW/Espresso/Recompiler/IML/IMLOptimizer.cpp
HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp
HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h
HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp
HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h
HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp
HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp
HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp
HW/Espresso/Recompiler/PPCRecompilerIml.h
HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp
HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp
HW/Espresso/Recompiler/PPCRecompilerImlRanges.h
HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp
HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp
HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp
HW/Espresso/Recompiler/BackendX64/BackendX64.h
HW/Espresso/Recompiler/BackendX64/X64Emit.hpp
HW/Espresso/Recompiler/BackendX64/x86Emitter.h
HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp
HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp
HW/Espresso/Recompiler/PPCRecompilerX64.cpp
HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp
HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp
HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp
HW/Espresso/Recompiler/PPCRecompilerX64.h
HW/Espresso/Recompiler/x64Emit.hpp
HW/Latte/Common/RegisterSerializer.cpp
HW/Latte/Common/RegisterSerializer.h
HW/Latte/Common/ShaderSerializer.cpp
@ -225,8 +214,6 @@ add_library(CemuCafe
HW/SI/SI.cpp
HW/SI/si.h
HW/VI/VI.cpp
IOSU/ccr_nfc/iosu_ccr_nfc.cpp
IOSU/ccr_nfc/iosu_ccr_nfc.h
IOSU/fsa/fsa_types.h
IOSU/fsa/iosu_fsa.cpp
IOSU/fsa/iosu_fsa.h
@ -381,24 +368,12 @@ add_library(CemuCafe
OS/libs/gx2/GX2_Texture.h
OS/libs/gx2/GX2_TilingAperture.cpp
OS/libs/h264_avc/H264Dec.cpp
OS/libs/h264_avc/H264DecBackendAVC.cpp
OS/libs/h264_avc/h264dec.h
OS/libs/h264_avc/H264DecInternal.h
OS/libs/h264_avc/parser
OS/libs/h264_avc/parser/H264Parser.cpp
OS/libs/h264_avc/parser/H264Parser.h
OS/libs/mic/mic.cpp
OS/libs/mic/mic.h
OS/libs/nfc/ndef.cpp
OS/libs/nfc/ndef.h
OS/libs/nfc/nfc.cpp
OS/libs/nfc/nfc.h
OS/libs/nfc/stream.cpp
OS/libs/nfc/stream.h
OS/libs/nfc/TagV0.cpp
OS/libs/nfc/TagV0.h
OS/libs/nfc/TLV.cpp
OS/libs/nfc/TLV.h
OS/libs/nlibcurl/nlibcurl.cpp
OS/libs/nlibcurl/nlibcurlDebug.hpp
OS/libs/nlibcurl/nlibcurl.h
@ -429,8 +404,6 @@ add_library(CemuCafe
OS/libs/nn_ndm/nn_ndm.h
OS/libs/nn_spm/nn_spm.cpp
OS/libs/nn_spm/nn_spm.h
OS/libs/nn_sl/nn_sl.cpp
OS/libs/nn_sl/nn_sl.h
OS/libs/nn_nfp/AmiiboCrypto.h
OS/libs/nn_nfp/nn_nfp.cpp
OS/libs/nn_nfp/nn_nfp.h
@ -466,26 +439,14 @@ add_library(CemuCafe
OS/libs/nsyshid/AttachDefaultBackends.cpp
OS/libs/nsyshid/Whitelist.cpp
OS/libs/nsyshid/Whitelist.h
OS/libs/nsyshid/BackendEmulated.cpp
OS/libs/nsyshid/BackendEmulated.h
OS/libs/nsyshid/BackendLibusb.cpp
OS/libs/nsyshid/BackendLibusb.h
OS/libs/nsyshid/Dimensions.cpp
OS/libs/nsyshid/Dimensions.h
OS/libs/nsyshid/Infinity.cpp
OS/libs/nsyshid/Infinity.h
OS/libs/nsyshid/Skylander.cpp
OS/libs/nsyshid/Skylander.h
OS/libs/nsyshid/SkylanderXbox360.cpp
OS/libs/nsyshid/SkylanderXbox360.h
OS/libs/nsyshid/g721/g721.cpp
OS/libs/nsyshid/g721/g721.h
OS/libs/nsyshid/BackendWindowsHID.cpp
OS/libs/nsyshid/BackendWindowsHID.h
OS/libs/nsyskbd/nsyskbd.cpp
OS/libs/nsyskbd/nsyskbd.h
OS/libs/nsysnet/nsysnet.cpp
OS/libs/nsysnet/nsysnet.h
OS/libs/ntag/ntag.cpp
OS/libs/ntag/ntag.h
OS/libs/padscore/padscore.cpp
OS/libs/padscore/padscore.h
OS/libs/proc_ui/proc_ui.cpp
@ -537,25 +498,12 @@ if(APPLE)
target_sources(CemuCafe PRIVATE "HW/Latte/Renderer/Vulkan/CocoaSurface.mm")
endif()
if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)")
target_sources(CemuCafe PRIVATE
HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp
HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h
)
target_link_libraries(CemuCafe PRIVATE xbyak_aarch64)
endif()
set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
target_include_directories(CemuCafe PUBLIC "../")
if (glslang_VERSION VERSION_LESS "15.0.0")
set(glslang_target "glslang::SPIRV")
else()
set(glslang_target "glslang::glslang")
endif()
target_link_libraries(CemuCafe PRIVATE
CemuAsm
CemuAudio
CemuCommon
CemuComponents
@ -569,7 +517,7 @@ target_link_libraries(CemuCafe PRIVATE
Boost::nowide
CURL::libcurl
fmt::fmt
${glslang_target}
glslang::SPIRV
ih264d
OpenSSL::Crypto
OpenSSL::SSL
@ -585,16 +533,15 @@ if (ENABLE_WAYLAND)
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
endif()
if (ENABLE_VCPKG)
if(WIN32)
set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe")
endif()
if (ENABLE_NSYSHID_LIBUSB)
if (ENABLE_VCPKG)
find_package(PkgConfig REQUIRED)
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb)
else ()
else ()
find_package(libusb MODULE REQUIRED)
target_link_libraries(CemuCafe PRIVATE libusb::libusb)
endif ()
endif ()
if (ENABLE_WXWIDGETS)

View file

@ -9,7 +9,6 @@
#include "audio/IAudioAPI.h"
#include "audio/IAudioInputAPI.h"
#include "config/ActiveSettings.h"
#include "config/LaunchSettings.h"
#include "Cafe/TitleList/GameInfo.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "util/helpers/SystemException.h"
@ -36,7 +35,6 @@
#include "Cafe/IOSU/legacy/iosu_boss.h"
#include "Cafe/IOSU/legacy/iosu_nim.h"
#include "Cafe/IOSU/PDM/iosu_pdm.h"
#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h"
// IOSU initializer functions
#include "Cafe/IOSU/kernel/iosu_kernel.h"
@ -53,8 +51,6 @@
#include "Cafe/OS/libs/gx2/GX2.h"
#include "Cafe/OS/libs/gx2/GX2_Misc.h"
#include "Cafe/OS/libs/mic/mic.h"
#include "Cafe/OS/libs/nfc/nfc.h"
#include "Cafe/OS/libs/ntag/ntag.h"
#include "Cafe/OS/libs/nn_aoc/nn_aoc.h"
#include "Cafe/OS/libs/nn_pdm/nn_pdm.h"
#include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h"
@ -397,7 +393,7 @@ void cemu_initForGame()
// replace any known function signatures with our HLE implementations and patch bugs in the games
GamePatch_scan();
}
LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled();
LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled();
InfoLog_PrintActiveSettings();
Latte_Start();
// check for debugger entrypoint bp
@ -534,10 +530,8 @@ namespace CafeSystem
{
// entries in this list are ordered by initialization order. Shutdown in reverse order
iosu::kernel::GetModule(),
iosu::acp::GetModule(),
iosu::fpd::GetModule(),
iosu::pdm::GetModule(),
iosu::ccr_nfc::GetModule(),
};
// initialize all subsystems which are persistent and don't depend on a game running
@ -592,8 +586,6 @@ namespace CafeSystem
H264::Initialize();
snd_core::Initialize();
mic::Initialize();
nfc::Initialize();
ntag::Initialize();
// init hardware register interfaces
HW_SI::Initialize();
}
@ -638,40 +630,40 @@ namespace CafeSystem
fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE);
}
PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId)
{
cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId);
sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId);
if (!sGameInfo_ForegroundTitle.IsValid())
{
cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)");
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
}
// check base
TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase();
if (!titleBase.IsValid())
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
if(!titleBase.ParseXmlInfo())
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath());
// mount base
if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE))
{
cemuLog_log(LogType::Force, "Mounting failed");
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
}
// check update
TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate();
if (titleUpdate.IsValid())
{
if (!titleUpdate.ParseXmlInfo())
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath());
// mount update
if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH))
{
cemuLog_log(LogType::Force, "Mounting failed");
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
}
}
else
@ -683,20 +675,20 @@ namespace CafeSystem
// todo - support for multi-title AOC
TitleInfo& titleAOC = aocList[0];
if (!titleAOC.ParseXmlInfo())
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
cemu_assert_debug(titleAOC.IsValid());
cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath());
// mount AOC
if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH))
{
cemuLog_log(LogType::Force, "Mounting failed");
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
}
}
else
cemuLog_log(LogType::Force, "DLC: Not present");
sForegroundTitleId = titleId;
return PREPARE_STATUS_CODE::SUCCESS;
return STATUS_CODE::SUCCESS;
}
void UnmountForegroundTitle()
@ -724,7 +716,7 @@ namespace CafeSystem
}
}
PREPARE_STATUS_CODE SetupExecutable()
STATUS_CODE SetupExecutable()
{
// set rpx path from cos.xml if available
_pathToBaseExecutable = _pathToExecutable;
@ -756,7 +748,7 @@ namespace CafeSystem
}
}
LoadMainExecutable();
return PREPARE_STATUS_CODE::SUCCESS;
return STATUS_CODE::SUCCESS;
}
void SetupMemorySpace()
@ -770,7 +762,7 @@ namespace CafeSystem
memory_unmapForCurrentTitle();
}
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId)
STATUS_CODE PrepareForegroundTitle(TitleId titleId)
{
CafeTitleList::WaitForMandatoryScan();
sLaunchModeIsStandalone = false;
@ -781,21 +773,21 @@ namespace CafeSystem
// mount mlc storage
MountBaseDirectories();
// mount title folders
PREPARE_STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
if (r != PREPARE_STATUS_CODE::SUCCESS)
STATUS_CODE r = LoadAndMountForegroundTitle(titleId);
if (r != STATUS_CODE::SUCCESS)
return r;
gameProfile_load();
// setup memory space and PPC recompiler
SetupMemorySpace();
PPCRecompiler_init();
r = SetupExecutable(); // load RPX
if (r != PREPARE_STATUS_CODE::SUCCESS)
if (r != STATUS_CODE::SUCCESS)
return r;
InitVirtualMlcStorage();
return PREPARE_STATUS_CODE::SUCCESS;
return STATUS_CODE::SUCCESS;
}
PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path)
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path)
{
sLaunchModeIsStandalone = true;
cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files");
@ -813,7 +805,7 @@ namespace CafeSystem
if (!r)
{
cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath));
return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT;
return STATUS_CODE::UNABLE_TO_MOUNT;
}
}
}
@ -825,7 +817,7 @@ namespace CafeSystem
// since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash
auto execData = fsc_extractFile(_pathToExecutable.c_str());
if (!execData)
return PREPARE_STATUS_CODE::INVALID_RPX;
return STATUS_CODE::INVALID_RPX;
uint32 h = generateHashFromRawRPXData(execData->data(), execData->size());
sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h;
cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId);
@ -835,7 +827,7 @@ namespace CafeSystem
// load executable
SetupExecutable();
InitVirtualMlcStorage();
return PREPARE_STATUS_CODE::SUCCESS;
return STATUS_CODE::SUCCESS;
}
void _LaunchTitleThread()
@ -844,7 +836,7 @@ namespace CafeSystem
module->TitleStart();
cemu_initForGame();
// enter scheduler
if ((ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler || LaunchSettings::ForceMultiCoreInterpreter()) && !LaunchSettings::ForceInterpreter())
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler)
coreinit::OSSchedulerBegin(3);
else
coreinit::OSSchedulerBegin(1);
@ -922,27 +914,6 @@ namespace CafeSystem
return sGameInfo_ForegroundTitle.GetBase().GetArgStr();
}
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group)
{
if (sLaunchModeIsStandalone)
return CosCapabilityBits::All;
auto& update = sGameInfo_ForegroundTitle.GetUpdate();
if (update.IsValid())
{
ParsedCosXml* cosXml = update.GetCosInfo();
if (cosXml)
return cosXml->GetCapabilityBits(group);
}
auto& base = sGameInfo_ForegroundTitle.GetBase();
if(base.IsValid())
{
ParsedCosXml* cosXml = base.GetCosInfo();
if (cosXml)
return cosXml->GetCapabilityBits(group);
}
return CosCapabilityBits::All;
}
// when switching titles custom parameters can be passed, returns true if override args are used
bool GetOverrideArgStr(std::vector<std::string>& args)
{

View file

@ -4,9 +4,6 @@
#include "Cafe/TitleList/TitleId.h"
#include "config/CemuConfig.h"
enum class CosCapabilityBits : uint64;
enum class CosCapabilityGroup : uint32;
namespace CafeSystem
{
class SystemImplementation
@ -15,19 +12,20 @@ namespace CafeSystem
virtual void CafeRecreateCanvas() = 0;
};
enum class PREPARE_STATUS_CODE
enum class STATUS_CODE
{
SUCCESS,
INVALID_RPX,
UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path)
//BAD_META_DATA, - the title list only stores titles with valid meta, so this error code is impossible
};
void Initialize();
void SetImplementation(SystemImplementation* impl);
void Shutdown();
PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId);
PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
STATUS_CODE PrepareForegroundTitle(TitleId titleId);
STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path);
void LaunchForegroundTitle();
bool IsTitleRunning();
@ -43,7 +41,6 @@ namespace CafeSystem
std::string GetForegroundTitleName();
std::string GetForegroundTitleArgStr();
uint32 GetForegroundTitleOlvAccesskey();
CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group);
void ShutdownTitle();

View file

@ -3,7 +3,8 @@
#include "Cemu/ncrypto/ncrypto.h"
#include "Cafe/Filesystem/WUD/wud.h"
#include "util/crypto/aes128.h"
#include "openssl/sha.h" /* SHA1 / SHA256 */
#include "openssl/evp.h" /* EVP_Digest */
#include "openssl/sha.h" /* SHA1 / SHA256_DIGEST_LENGTH */
#include "fstUtil.h"
#include "FST.h"
@ -13,8 +14,6 @@
#define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code
static_assert(sizeof(NCrypto::AesIv) == 16); // make sure IV is actually 16 bytes
class FSTDataSource
{
public:
@ -142,7 +141,7 @@ struct DiscPartitionTableHeader
static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B;
/* +0x00 */ uint32be magic;
/* +0x04 */ uint32be blockSize; // must be 0x8000?
/* +0x04 */ uint32be sectorSize; // must be 0x8000?
/* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000)
/* +0x1C */ uint32be numPartitions;
};
@ -165,10 +164,10 @@ struct DiscPartitionHeader
static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5;
/* +0x00 */ uint32be magic;
/* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE for hashed blocks
/* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE
/* +0x08 */ uint32be ukn008;
/* +0x0C */ uint32be ukn00C; // h3 array size?
/* +0x0C */ uint32be ukn00C;
/* +0x10 */ uint32be h3HashNum;
/* +0x14 */ uint32be fstSize; // in bytes
/* +0x18 */ uint32be fstSector; // relative to partition start
@ -179,15 +178,13 @@ struct DiscPartitionHeader
/* +0x24 */ uint8 fstHashType;
/* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key)
/* +0x26 */ uint8be versionA;
/* +0x27 */ uint8be ukn027; // also a version field?
/* +0x26 */ uint8 versionA;
/* +0x27 */ uint8 ukn027; // also a version field?
// there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum
/* +0x28 */ uint8be _uknOrPadding028[0x18];
/* +0x40 */ uint8be h3HashArray[32]; // dynamic size. Only present if fstHashType != 0
};
static_assert(sizeof(DiscPartitionHeader) == 0x40+0x20);
static_assert(sizeof(DiscPartitionHeader) == 0x28);
bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey)
{
@ -272,7 +269,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
cemuLog_log(LogType::Force, "Disc image rejected because decryption failed");
return nullptr;
}
if (partitionHeader->blockSize != DISC_SECTOR_SIZE)
if (partitionHeader->sectorSize != DISC_SECTOR_SIZE)
{
cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid");
return nullptr;
@ -339,9 +336,6 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1);
// todo - check other fields?
if(partitionHeaderSI.fstHashType == 0 && partitionHeaderSI.h3HashNum != 0)
cemuLog_log(LogType::Force, "FST: Partition uses unhashed blocks but stores a non-zero amount of H3 hashes");
// GM partition
DiscPartitionHeader partitionHeaderGM{};
if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex))
@ -355,10 +349,9 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
// if decryption is necessary
// load SI FST
dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType), nullptr);
auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType));
if (!siFST)
return nullptr;
cemu_assert_debug(!(siFST->HashIsDisabled() && partitionHeaderSI.h3HashNum != 0)); // if hash is disabled, no H3 data may be present
// load ticket file for partition that we want to decrypt
NCrypto::ETicketParser ticketParser;
std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex));
@ -367,32 +360,16 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d
cemuLog_log(LogType::Force, "Disc image ticket file is invalid");
return nullptr;
}
#if 0
// each SI partition seems to contain a title.tmd that we could parse and which should have information about the associated GM partition
// but the console seems to ignore this file for disc images, at least when mounting, so we shouldn't rely on it either
std::vector<uint8> tmdData = siFST->ExtractFile(fmt::format("{:02x}/title.tmd", gmPartitionIndex));
if (tmdData.empty())
{
cemuLog_log(LogType::Force, "Disc image TMD file is missing");
return nullptr;
}
// parse TMD
NCrypto::TMDParser tmdParser;
if (!tmdParser.parse(tmdData.data(), tmdData.size()))
{
cemuLog_log(LogType::Force, "Disc image TMD file is invalid");
return nullptr;
}
#endif
delete siFST;
NCrypto::AesKey gmTitleKey;
ticketParser.GetTitleKey(gmTitleKey);
// load GM partition
dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE);
FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType), nullptr);
FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType));
if (r)
SET_FST_ERROR(OK);
cemu_assert_debug(!(r->HashIsDisabled() && partitionHeaderGM.h3HashNum != 0)); // if hash is disabled, no H3 data may be present
return r;
}
@ -449,15 +426,15 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* erro
}
// load FST
// fstSize = size of first cluster?
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode, &tmdParser);
FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode);
if (fstVolume)
SET_FST_ERROR(OK);
return fstVolume;
}
FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD)
FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode)
{
cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW_STREAM);
cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW2);
if (fstSize < sizeof(FSTHeader))
return nullptr;
constexpr uint64 FST_CLUSTER_OFFSET = 0;
@ -488,34 +465,6 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3
clusterTable[i].offset = clusterDataTable[i].offset;
clusterTable[i].size = clusterDataTable[i].size;
clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode);
clusterTable[i].hasContentHash = false; // from the TMD file (H4?)
}
// if the TMD is available (when opening .app files) we can use the extra info from it to validate unhashed clusters
// each content entry in the TMD corresponds to one cluster used by the FST
if(optionalTMD)
{
if(numCluster != optionalTMD->GetContentList().size())
{
cemuLog_log(LogType::Force, "FST: Number of clusters does not match TMD content list");
return nullptr;
}
auto& contentList = optionalTMD->GetContentList();
for(size_t i=0; i<contentList.size(); i++)
{
auto& cluster = clusterTable[i];
auto& content = contentList[i];
cluster.hasContentHash = true;
cluster.contentHashIsSHA1 = HAS_FLAG(contentList[i].contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1);
cluster.contentSize = content.size;
static_assert(sizeof(content.hash32) == sizeof(cluster.contentHash32));
memcpy(cluster.contentHash32, content.hash32, sizeof(cluster.contentHash32));
// if unhashed mode, then initialize the hash context
if(cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM)
{
cluster.singleHashCtx.reset(EVP_MD_CTX_new());
EVP_DigestInit_ex(cluster.singleHashCtx.get(), cluster.contentHashIsSHA1 ? EVP_sha1() : EVP_sha256(), nullptr);
}
}
}
// preprocess FST table
FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster);
@ -542,17 +491,16 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3
fstVolume->m_offsetFactor = fstHeader->offsetFactor;
fstVolume->m_sectorSize = DISC_SECTOR_SIZE;
fstVolume->m_partitionTitlekey = *partitionTitleKey;
fstVolume->m_hashIsDisabled = fstHeader->hashIsDisabled != 0;
fstVolume->m_cluster = std::move(clusterTable);
fstVolume->m_entries = std::move(fstEntries);
fstVolume->m_nameStringTable = std::move(nameStringTable);
std::swap(fstVolume->m_cluster, clusterTable);
std::swap(fstVolume->m_entries, fstEntries);
std::swap(fstVolume->m_nameStringTable, nameStringTable);
return fstVolume;
}
FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD)
FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode)
{
FSTDataSource* ds = dataSource.release();
FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode, optionalTMD);
FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode);
if (!fstVolume)
{
delete ds;
@ -809,7 +757,7 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size
return 0;
cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK));
FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex];
if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM)
if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW2)
return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED)
return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut);
@ -817,15 +765,87 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size
return 0;
}
uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
{
const uint32 readSizeInput = readSize;
uint8* dataOutU8 = (uint8*)dataOut;
if (readOffset >= entry.fileInfo.fileSize)
return 0;
else if ((readOffset + readSize) >= entry.fileInfo.fileSize)
readSize = (entry.fileInfo.fileSize - readOffset);
const FSTCluster& cluster = m_cluster[clusterIndex];
uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize;
uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
// make sure the raw range we read is aligned to AES block size (16)
uint64 readAddrStart = absFileOffset & ~0xF;
uint64 readAddrEnd = (absFileOffset + readSize + 0xF) & ~0xF;
bool usesInitialIV = readOffset < 16;
if (!usesInitialIV)
readAddrStart -= 16; // read previous AES block since we require it for the IV
uint32 prePadding = (uint32)(absFileOffset - readAddrStart); // number of extra bytes we read before readOffset (for AES alignment and IV calculation)
uint32 postPadding = (uint32)(readAddrEnd - (absFileOffset + readSize));
uint8 readBuffer[64 * 1024];
// read first chunk
// if file read offset (readOffset) is within the first AES-block then use initial IV calculated from cluster index
// otherwise read previous AES-block is the IV (AES-CBC)
uint64 readAddrCurrent = readAddrStart;
uint32 rawBytesToRead = (uint32)std::min((readAddrEnd - readAddrStart), (uint64)sizeof(readBuffer));
if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, rawBytesToRead) != rawBytesToRead)
{
cemuLog_log(LogType::Force, "FST read error in raw content");
return 0;
}
readAddrCurrent += rawBytesToRead;
uint8 iv[16]{};
if (usesInitialIV)
{
// for the first AES block, the IV is initialized from cluster index
iv[0] = (uint8)(clusterIndex >> 8);
iv[1] = (uint8)(clusterIndex >> 0);
AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, rawBytesToRead, m_partitionTitlekey.b, iv);
std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding);
dataOutU8 += (rawBytesToRead - prePadding - postPadding);
readSize -= (rawBytesToRead - prePadding - postPadding);
}
else
{
// IV is initialized from previous AES block (AES-CBC)
std::memcpy(iv, readBuffer, 16);
AES128_CBC_decrypt_updateIV(readBuffer + 16, readBuffer + 16, rawBytesToRead - 16, m_partitionTitlekey.b, iv);
std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding);
dataOutU8 += (rawBytesToRead - prePadding - postPadding);
readSize -= (rawBytesToRead - prePadding - postPadding);
}
// read remaining chunks
while (readSize > 0)
{
uint32 bytesToRead = (uint32)std::min((uint32)sizeof(readBuffer), readSize);
uint32 alignedBytesToRead = (bytesToRead + 15) & ~0xF;
if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, alignedBytesToRead) != alignedBytesToRead)
{
cemuLog_log(LogType::Force, "FST read error in raw content");
return 0;
}
AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, alignedBytesToRead, m_partitionTitlekey.b, iv);
std::memcpy(dataOutU8, readBuffer, bytesToRead);
dataOutU8 += bytesToRead;
readSize -= bytesToRead;
readAddrCurrent += alignedBytesToRead;
}
return readSizeInput - readSize;
}
constexpr size_t BLOCK_SIZE = 0x10000;
constexpr size_t BLOCK_HASH_SIZE = 0x0400;
constexpr size_t BLOCK_FILE_SIZE = 0xFC00;
struct FSTRawBlock
{
std::vector<uint8> rawData; // unhashed block size depends on sector size field in partition header
};
struct FSTHashedBlock
{
uint8 rawData[BLOCK_SIZE];
@ -867,160 +887,12 @@ struct FSTHashedBlock
static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE);
struct FSTCachedRawBlock
{
FSTRawBlock blockData;
NCrypto::AesIv ivForNextBlock;
uint64 lastAccess;
};
struct FSTCachedHashedBlock
{
FSTHashedBlock blockData;
uint64 lastAccess;
};
// Checks cache fill state and if necessary drops least recently accessed block from the cache. Optionally allows to recycle the released cache entry to cut down cost of memory allocation and clearing
void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCachedHashedBlock** droppedHashedBlock)
{
// calculate size used by cache
size_t cacheSize = 0;
for (auto& itr : m_cacheDecryptedRawBlocks)
cacheSize += itr.second->blockData.rawData.size();
for (auto& itr : m_cacheDecryptedHashedBlocks)
cacheSize += sizeof(FSTCachedHashedBlock) + sizeof(FSTHashedBlock);
// only trim if cache is full (larger than 2MB)
if (cacheSize < 2*1024*1024) // 2MB
return;
// scan both cache lists to find least recently accessed block to drop
auto dropRawItr = std::min_element(m_cacheDecryptedRawBlocks.begin(), m_cacheDecryptedRawBlocks.end(), [](const auto& a, const auto& b) -> bool
{ return a.second->lastAccess < b.second->lastAccess; });
auto dropHashedItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool
{ return a.second->lastAccess < b.second->lastAccess; });
uint64 lastAccess = std::numeric_limits<uint64>::max();
if(dropRawItr != m_cacheDecryptedRawBlocks.end())
lastAccess = dropRawItr->second->lastAccess;
if(dropHashedItr != m_cacheDecryptedHashedBlocks.end())
lastAccess = std::min<uint64>(lastAccess, dropHashedItr->second->lastAccess);
if(dropRawItr != m_cacheDecryptedRawBlocks.end() && dropRawItr->second->lastAccess == lastAccess)
{
if (droppedRawBlock)
*droppedRawBlock = dropRawItr->second;
else
delete dropRawItr->second;
m_cacheDecryptedRawBlocks.erase(dropRawItr);
return;
}
else if(dropHashedItr != m_cacheDecryptedHashedBlocks.end() && dropHashedItr->second->lastAccess == lastAccess)
{
if (droppedHashedBlock)
*droppedHashedBlock = dropHashedItr->second;
else
delete dropHashedItr->second;
m_cacheDecryptedHashedBlocks.erase(dropHashedItr);
}
}
void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut)
{
ivOut = {};
if(blockIndex == 0)
{
ivOut.iv[0] = (uint8)(clusterIndex >> 8);
ivOut.iv[1] = (uint8)(clusterIndex >> 0);
}
else
{
// the last 16 encrypted bytes of the previous block are the IV (AES CBC)
// if the previous block is cached we can grab the IV from there. Otherwise we have to read the 16 bytes from the data source
uint32 prevBlockIndex = blockIndex - 1;
uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)prevBlockIndex;
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
if (itr != m_cacheDecryptedRawBlocks.end())
{
ivOut = itr->second->ivForNextBlock;
}
else
{
cemu_assert(m_sectorSize >= NCrypto::AesIv::SIZE);
uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize;
NCrypto::AesIv prevIV{};
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - NCrypto::AesIv::SIZE, prevIV.iv, NCrypto::AesIv::SIZE) != NCrypto::AesIv::SIZE)
{
cemuLog_log(LogType::Force, "Failed to read IV for raw FST block");
m_detectedCorruption = true;
return;
}
ivOut = prevIV;
}
}
}
FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex)
{
FSTCluster& cluster = m_cluster[clusterIndex];
uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize;
// generate id for cache
uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex;
// lookup block in cache
FSTCachedRawBlock* block = nullptr;
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
if (itr != m_cacheDecryptedRawBlocks.end())
{
block = itr->second;
block->lastAccess = ++m_cacheAccessCounter;
return block;
}
// if cache already full, drop least recently accessed block and recycle FSTCachedRawBlock object if possible
TrimCacheIfRequired(&block, nullptr);
if (!block)
block = new FSTCachedRawBlock();
block->blockData.rawData.resize(m_sectorSize);
// block not cached, read new
block->lastAccess = ++m_cacheAccessCounter;
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize, block->blockData.rawData.data(), m_sectorSize) != m_sectorSize)
{
cemuLog_log(LogType::Force, "Failed to read raw FST block");
delete block;
m_detectedCorruption = true;
return nullptr;
}
// decrypt hash data
NCrypto::AesIv iv{};
DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv);
std::copy(block->blockData.rawData.data() + m_sectorSize - NCrypto::AesIv::SIZE, block->blockData.rawData.data() + m_sectorSize, block->ivForNextBlock.iv);
AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv.iv);
// if this is the next block, then hash it
if(cluster.hasContentHash)
{
if(cluster.singleHashNumBlocksHashed == blockIndex)
{
cemu_assert_debug(!(cluster.contentSize % m_sectorSize)); // size should be multiple of sector size? Regardless, the hashing code below can handle non-aligned sizes
bool isLastBlock = blockIndex == (std::max<uint32>(cluster.contentSize / m_sectorSize, 1) - 1);
uint32 hashSize = m_sectorSize;
if(isLastBlock)
hashSize = cluster.contentSize - (uint64)blockIndex*m_sectorSize;
EVP_DigestUpdate(cluster.singleHashCtx.get(), block->blockData.rawData.data(), hashSize);
cluster.singleHashNumBlocksHashed++;
if(isLastBlock)
{
uint8 hash[32];
EVP_DigestFinal_ex(cluster.singleHashCtx.get(), hash, nullptr);
if(memcmp(hash, cluster.contentHash32, cluster.contentHashIsSHA1 ? 20 : 32) != 0)
{
cemuLog_log(LogType::Force, "FST: Raw section hash mismatch");
delete block;
m_detectedCorruption = true;
return nullptr;
}
}
}
}
// register in cache
m_cacheDecryptedRawBlocks.emplace(cacheBlockId, block);
return block;
}
FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex)
{
const FSTCluster& cluster = m_cluster[clusterIndex];
@ -1036,17 +908,22 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui
block->lastAccess = ++m_cacheAccessCounter;
return block;
}
// if cache already full, drop least recently accessed block and recycle FSTCachedHashedBlock object if possible
TrimCacheIfRequired(nullptr, &block);
if (!block)
// if cache already full, drop least recently accessed block (but recycle the FSTHashedBlock* object)
if (m_cacheDecryptedHashedBlocks.size() >= 16)
{
auto dropItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool
{ return a.second->lastAccess < b.second->lastAccess; });
block = dropItr->second;
m_cacheDecryptedHashedBlocks.erase(dropItr);
}
else
block = new FSTCachedHashedBlock();
// block not cached, read new
block->lastAccess = ++m_cacheAccessCounter;
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE)
{
cemuLog_log(LogType::Force, "Failed to read hashed FST block");
cemuLog_log(LogType::Force, "Failed to read FST block");
delete block;
m_detectedCorruption = true;
return nullptr;
}
// decrypt hash data
@ -1054,46 +931,11 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui
AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv);
// decrypt file data
AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16));
// compare with H0 to verify data integrity
NCrypto::CHash160 h0;
SHA1(block->blockData.getFileData(), BLOCK_FILE_SIZE, h0.b);
uint32 h0Index = (blockIndex % 4096);
if (memcmp(h0.b, block->blockData.getH0Hash(h0Index & 0xF), sizeof(h0.b)) != 0)
{
cemuLog_log(LogType::Force, "FST: Hash H0 mismatch in hashed block (section {} index {})", clusterIndex, blockIndex);
delete block;
m_detectedCorruption = true;
return nullptr;
}
// register in cache
m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block);
return block;
}
uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
{
uint8* dataOutU8 = (uint8*)dataOut;
if (readOffset >= entry.fileInfo.fileSize)
return 0;
else if ((readOffset + readSize) >= entry.fileInfo.fileSize)
readSize = (entry.fileInfo.fileSize - readOffset);
uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
uint32 remainingReadSize = readSize;
while (remainingReadSize > 0)
{
const FSTCachedRawBlock* rawBlock = this->GetDecryptedRawBlock(clusterIndex, absFileOffset/m_sectorSize);
if (!rawBlock)
break;
uint32 blockOffset = (uint32)(absFileOffset % m_sectorSize);
uint32 bytesToRead = std::min<uint32>(remainingReadSize, m_sectorSize - blockOffset);
std::memcpy(dataOutU8, rawBlock->blockData.rawData.data() + blockOffset, bytesToRead);
dataOutU8 += bytesToRead;
remainingReadSize -= bytesToRead;
absFileOffset += bytesToRead;
}
return readSize - remainingReadSize;
}
uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut)
{
/*
@ -1124,6 +966,7 @@ uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry,
*/
const FSTCluster& cluster = m_cluster[clusterIndex];
uint64 clusterBaseOffset = (uint64)cluster.offset * m_sectorSize;
uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset;
uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE);
uint32 bytesRemaining = readSize;
@ -1176,8 +1019,6 @@ bool FSTVolume::Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fil
FSTVolume::~FSTVolume()
{
for (auto& itr : m_cacheDecryptedRawBlocks)
delete itr.second;
for (auto& itr : m_cacheDecryptedHashedBlocks)
delete itr.second;
if (m_sourceIsOwned)

View file

@ -1,6 +1,5 @@
#pragma once
#include "Cemu/ncrypto/ncrypto.h"
#include "openssl/evp.h"
struct FSTFileHandle
{
@ -46,7 +45,6 @@ public:
~FSTVolume();
uint32 GetFileCount() const;
bool HasCorruption() const { return m_detectedCorruption; }
bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false);
@ -83,29 +81,20 @@ public:
}
private:
/* FST data (in memory) */
enum class ClusterHashMode : uint8
{
RAW = 0, // raw data + encryption, no hashing?
RAW_STREAM = 1, // raw data + encryption, with hash stored in tmd?
RAW2 = 1, // raw data + encryption, with hash stored in tmd?
HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data)
};
struct FSTCluster
{
FSTCluster() : singleHashCtx(nullptr, &EVP_MD_CTX_free) {}
uint32 offset;
uint32 size;
ClusterHashMode hashMode;
// extra data if TMD is available
bool hasContentHash;
uint8 contentHash32[32];
bool contentHashIsSHA1; // if true then it's SHA1 (with extra bytes zeroed out), otherwise it's SHA256
uint64 contentSize; // size of the content (in blocks)
// hash context for single hash mode (content hash must be available)
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> singleHashCtx; // unique_ptr to make this move-only
uint32 singleHashNumBlocksHashed{0};
};
struct FSTEntry
@ -175,30 +164,17 @@ private:
bool m_sourceIsOwned{};
uint32 m_sectorSize{}; // for cluster offsets
uint32 m_offsetFactor{}; // for file offsets
bool m_hashIsDisabled{}; // disables hash verification (for all clusters of this volume?)
std::vector<FSTCluster> m_cluster;
std::vector<FSTEntry> m_entries;
std::vector<char> m_nameStringTable;
NCrypto::AesKey m_partitionTitlekey;
bool m_detectedCorruption{false};
bool HashIsDisabled() const
{
return m_hashIsDisabled;
}
/* Cache for decrypted raw and hashed blocks */
std::unordered_map<uint64, struct FSTCachedRawBlock*> m_cacheDecryptedRawBlocks;
/* Cache for decrypted hashed blocks */
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
uint64 m_cacheAccessCounter{};
void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut);
struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex);
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
void TrimCacheIfRequired(struct FSTCachedRawBlock** droppedRawBlock, struct FSTCachedHashedBlock** droppedHashedBlock);
/* File reading */
uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut);
@ -209,10 +185,7 @@ private:
/* +0x00 */ uint32be magic;
/* +0x04 */ uint32be offsetFactor;
/* +0x08 */ uint32be numCluster;
/* +0x0C */ uint8be hashIsDisabled;
/* +0x0D */ uint8be ukn0D;
/* +0x0E */ uint8be ukn0E;
/* +0x0F */ uint8be ukn0F;
/* +0x0C */ uint32be ukn0C;
/* +0x10 */ uint32be ukn10;
/* +0x14 */ uint32be ukn14;
/* +0x18 */ uint32be ukn18;
@ -289,8 +262,8 @@ private:
static_assert(sizeof(FSTHeader_FileEntry) == 0x10);
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD);
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD);
static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode);
static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries);
bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName)

View file

@ -3,8 +3,6 @@
#include <boost/container/small_vector.hpp>
#include "../fsc.h"
// path parser and utility class for Wii U paths
// optimized to be allocation-free for common path lengths
class FSCPath
@ -121,7 +119,9 @@ public:
template<typename F>
class FSAFileTree
{
private:
public:
private:
enum NODETYPE : uint8
{
@ -133,7 +133,6 @@ class FSAFileTree
{
std::string name;
std::vector<node_t*> subnodes;
size_t fileSize;
F* custom;
NODETYPE type;
};
@ -180,54 +179,13 @@ class FSAFileTree
return newNode;
}
class DirectoryIterator : public FSCVirtualFile
{
public:
DirectoryIterator(node_t* node)
: m_node(node), m_subnodeIndex(0)
{
}
sint32 fscGetType() override
{
return FSC_TYPE_DIRECTORY;
}
bool fscDirNext(FSCDirEntry* dirEntry) override
{
if (m_subnodeIndex >= m_node->subnodes.size())
return false;
const node_t* subnode = m_node->subnodes[m_subnodeIndex];
strncpy(dirEntry->path, subnode->name.c_str(), sizeof(dirEntry->path) - 1);
dirEntry->path[sizeof(dirEntry->path) - 1] = '\0';
dirEntry->isDirectory = subnode->type == FSAFileTree::NODETYPE_DIRECTORY;
dirEntry->isFile = subnode->type == FSAFileTree::NODETYPE_FILE;
dirEntry->fileSize = subnode->type == FSAFileTree::NODETYPE_FILE ? subnode->fileSize : 0;
++m_subnodeIndex;
return true;
}
bool fscRewindDir() override
{
m_subnodeIndex = 0;
return true;
}
private:
node_t* m_node;
size_t m_subnodeIndex;
};
public:
FSAFileTree()
{
rootNode.type = NODETYPE_DIRECTORY;
}
bool addFile(std::string_view path, size_t fileSize, F* custom)
bool addFile(std::string_view path, F* custom)
{
FSCPath p(path);
if (p.GetNodeCount() == 0)
@ -238,7 +196,6 @@ public:
return false; // node already exists
// add file node
node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1));
fileNode->fileSize = fileSize;
fileNode->custom = custom;
return true;
}
@ -257,20 +214,6 @@ public:
return true;
}
bool getDirectory(std::string_view path, FSCVirtualFile*& dirIterator)
{
FSCPath p(path);
if (p.GetNodeCount() == 0)
return false;
node_t* node = getByNodePath(p, p.GetNodeCount(), false);
if (node == nullptr)
return false;
if (node->type != NODETYPE_DIRECTORY)
return false;
dirIterator = new DirectoryIterator(node);
return true;
}
bool removeFile(std::string_view path)
{
FSCPath p(path);

View file

@ -1,40 +0,0 @@
#pragma once
struct romfs_header_t
{
uint32 header_magic;
uint32be header_size;
uint64be dir_hash_table_ofs;
uint64be dir_hash_table_size;
uint64be dir_table_ofs;
uint64be dir_table_size;
uint64be file_hash_table_ofs;
uint64be file_hash_table_size;
uint64be file_table_ofs;
uint64be file_table_size;
uint64be file_partition_ofs;
};
struct romfs_direntry_t
{
uint32be parent;
uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling")
uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child")
uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file")
uint32be hash;
uint32be name_size;
std::string name;
};
struct romfs_fentry_t
{
uint32be parent;
uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling")
uint64be offset;
uint64be size;
uint32be hash;
uint32be name_size;
std::string name;
};
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF

View file

@ -1,224 +0,0 @@
#include "WUHBReader.h"
WUHBReader* WUHBReader::FromPath(const fs::path& path)
{
FileStream* fileIn{FileStream::openFile2(path)};
if (!fileIn)
return nullptr;
WUHBReader* ret = new WUHBReader(fileIn);
if (!ret->CheckMagicValue())
{
delete ret;
return nullptr;
}
if (!ret->ReadHeader())
{
delete ret;
return nullptr;
}
return ret;
}
static const romfs_direntry_t fallbackDirEntry{
.parent = ROMFS_ENTRY_EMPTY,
.listNext = ROMFS_ENTRY_EMPTY,
.dirListHead = ROMFS_ENTRY_EMPTY,
.fileListHead = ROMFS_ENTRY_EMPTY,
.hash = ROMFS_ENTRY_EMPTY,
.name_size = 0,
.name = ""
};
static const romfs_fentry_t fallbackFileEntry{
.parent = ROMFS_ENTRY_EMPTY,
.listNext = ROMFS_ENTRY_EMPTY,
.offset = 0,
.size = 0,
.hash = ROMFS_ENTRY_EMPTY,
.name_size = 0,
.name = ""
};
template<bool File>
const WUHBReader::EntryType<File>& WUHBReader::GetFallback()
{
if constexpr (File)
return fallbackFileEntry;
else
return fallbackDirEntry;
}
template<bool File>
WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const
{
auto fallback = GetFallback<File>();
if(offset == ROMFS_ENTRY_EMPTY)
return fallback;
const char* typeName = File ? "fentry" : "direntry";
EntryType<File> ret;
if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size))
{
cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName);
return fallback;
}
// read the entry
m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset);
auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name));
if (read != offsetof(EntryType<File>, name))
{
cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset);
return fallback;
}
// read the name
ret.name.resize(ret.name_size);
read = m_fileIn->readData(ret.name.data(), ret.name_size);
if (read != ret.name_size)
{
cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName);
return fallback;
}
return ret;
}
romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const
{
return GetEntry<false>(offset);
}
romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const
{
return GetEntry<true>(offset);
}
uint64 WUHBReader::GetFileSize(uint32 entryOffset) const
{
return GetFileEntry(entryOffset).size;
}
uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const
{
const auto fileEntry = GetFileEntry(entryOffset);
if (fileOffset >= fileEntry.size)
return 0;
const uint64 readAmount = std::min(length, fileEntry.size - fileOffset);
const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset;
m_fileIn->SetPosition(wuhbOffset);
return m_fileIn->readData(buffer, readAmount);
}
uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const
{
const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size);
const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs);
const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32);
const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32);
m_fileIn->SetPosition(hash_table_entry_offset);
uint32 tableOffset;
if (!m_fileIn->readU32(tableOffset))
{
cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset);
return ROMFS_ENTRY_EMPTY;
}
return uint32be::from_bevalue(tableOffset);
}
template<bool T>
bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const
{
for (;;)
{
if (entryOffset == ROMFS_ENTRY_EMPTY)
return false;
auto entry = GetEntry<T>(entryOffset);
if (entry.name == targetName)
return true;
entryOffset = entry.hash;
}
return false;
}
uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const
{
uint32 currentEntryOffset = 0;
auto look = [&](const fs::path& part, bool lookInFileHT) {
const auto partString = part.string();
currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT);
if (lookInFileHT)
return SearchHashList<true>(currentEntryOffset, part);
else
return SearchHashList<false>(currentEntryOffset, part);
};
// look for the root entry
if (!look("", false))
return ROMFS_ENTRY_EMPTY;
auto it = path.begin();
while (it != path.end())
{
fs::path part = *it;
++it;
// no need to recurse after trailing forward slash (e.g. directory/)
if (part.empty() && !isFile)
break;
// skip leading forward slash
if (part == "/")
continue;
// if the lookup target is a file and this is the last iteration, look in the file hash table instead.
if (!look(part, it == path.end() && isFile))
return ROMFS_ENTRY_EMPTY;
}
return currentEntryOffset;
}
bool WUHBReader::CheckMagicValue() const
{
uint8 magic[4];
m_fileIn->SetPosition(0);
int read = m_fileIn->readData(magic, 4);
if (read != 4)
{
cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers");
return false;
}
static_assert(sizeof(magic) == s_headerMagicValue.size());
return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0;
}
bool WUHBReader::ReadHeader()
{
m_fileIn->SetPosition(0);
auto read = m_fileIn->readData(&m_header, sizeof(m_header));
auto readSuccess = read == sizeof(m_header);
if (!readSuccess)
cemuLog_log(LogType::Force, "Failed to read WUHB header");
return readSuccess;
}
unsigned char WUHBReader::NormalizeChar(unsigned char c)
{
if (c >= 'a' && c <= 'z')
{
return c + 'A' - 'a';
}
else
{
return c;
}
}
uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len)
{
cemu_assert(path != nullptr || path_len == 0);
uint32 hash = parent ^ 123456789;
for (uint32 i = 0; i < path_len; i++)
{
hash = (hash >> 5) | (hash << 27);
hash ^= NormalizeChar(path[start + i]);
}
return hash;
}

View file

@ -1,45 +0,0 @@
#pragma once
#include <Common/FileStream.h>
#include "RomFSStructs.h"
class WUHBReader
{
public:
static WUHBReader* FromPath(const fs::path& path);
romfs_direntry_t GetDirEntry(uint32 offset) const;
romfs_fentry_t GetFileEntry(uint32 offset) const;
uint64 GetFileSize(uint32 entryOffset) const;
uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const;
uint32 Lookup(const std::filesystem::path& path, bool isFile) const;
private:
WUHBReader(FileStream* file)
: m_fileIn(file)
{
cemu_assert_debug(file != nullptr);
};
WUHBReader() = delete;
romfs_header_t m_header;
std::unique_ptr<FileStream> m_fileIn;
constexpr static std::string_view s_headerMagicValue = "WUHB";
bool ReadHeader();
bool CheckMagicValue() const;
static inline unsigned char NormalizeChar(unsigned char c);
static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len);
template<bool File>
using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>;
template<bool File>
static const EntryType<File>& GetFallback();
template<bool File>
EntryType<File> GetEntry(uint32 offset) const;
template<bool T>
bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const;
uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const;
};

View file

@ -204,12 +204,9 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination
// wua device
bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority);
// wuhb device
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority);
// hostFS device
bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority);
// redirect device
void fscDeviceRedirect_map();
void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority);
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority);

View file

@ -11,7 +11,7 @@ struct RedirectEntry
FSAFileTree<RedirectEntry> redirectTree;
void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority)
void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority)
{
// check if source already has a redirection
RedirectEntry* existingEntry;
@ -24,7 +24,7 @@ void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize,
delete existingEntry;
}
RedirectEntry* entry = new RedirectEntry(targetFilePath, priority);
redirectTree.addFile(virtualSourcePath, fileSize, entry);
redirectTree.addFile(virtualSourcePath, entry);
}
class fscDeviceTypeRedirect : public fscDeviceC
@ -32,15 +32,8 @@ class fscDeviceTypeRedirect : public fscDeviceC
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
{
RedirectEntry* redirectionEntry;
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && redirectTree.getFile(path, redirectionEntry))
if (redirectTree.getFile(path, redirectionEntry))
return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus);
FSCVirtualFile* dirIterator;
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR) && redirectTree.getDirectory(path, dirIterator))
return dirIterator;
return nullptr;
}

View file

@ -1,151 +0,0 @@
#include "Filesystem/WUHB/WUHBReader.h"
#include "Cafe/Filesystem/fsc.h"
#include "Cafe/Filesystem/FST/FST.h"
class FSCDeviceWuhbFileCtx : public FSCVirtualFile
{
public:
FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType)
: m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType)
{
cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY);
if (fscType == FSC_TYPE_DIRECTORY)
{
romfs_direntry_t entry = reader->GetDirEntry(entryOffset);
m_dirIterOffset = entry.dirListHead;
m_fileIterOffset = entry.fileListHead;
}
}
sint32 fscGetType() override
{
return m_fscType;
}
uint64 fscQueryValueU64(uint32 id) override
{
if (m_fscType == FSC_TYPE_FILE)
{
if (id == FSC_QUERY_SIZE)
return m_wuhbReader->GetFileSize(m_entryOffset);
else if (id == FSC_QUERY_WRITEABLE)
return 0; // WUHB images are read-only
else
cemu_assert_error();
}
else
{
cemu_assert_unimplemented();
}
return 0;
}
uint32 fscWriteData(void* buffer, uint32 size) override
{
cemu_assert_error();
return 0;
}
uint32 fscReadData(void* buffer, uint32 size) override
{
if (m_fscType != FSC_TYPE_FILE)
return 0;
auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer);
m_seek += read;
return read;
}
void fscSetSeek(uint64 seek) override
{
m_seek = seek;
}
uint64 fscGetSeek() override
{
if (m_fscType != FSC_TYPE_FILE)
return 0;
return m_seek;
}
void fscSetFileLength(uint64 endOffset) override
{
cemu_assert_error();
}
bool fscDirNext(FSCDirEntry* dirEntry) override
{
if (m_dirIterOffset != ROMFS_ENTRY_EMPTY)
{
romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset);
m_dirIterOffset = entry.listNext;
if(entry.name_size > 0)
{
dirEntry->isDirectory = true;
dirEntry->isFile = false;
dirEntry->fileSize = 0;
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
return true;
}
}
if (m_fileIterOffset != ROMFS_ENTRY_EMPTY)
{
romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset);
m_fileIterOffset = entry.listNext;
if(entry.name_size > 0)
{
dirEntry->isDirectory = false;
dirEntry->isFile = true;
dirEntry->fileSize = entry.size;
std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH);
return true;
}
}
return false;
}
private:
WUHBReader* m_wuhbReader{};
uint32 m_fscType;
uint32 m_entryOffset = ROMFS_ENTRY_EMPTY;
uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY;
uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY;
uint64 m_seek = 0;
};
class fscDeviceWUHB : public fscDeviceC
{
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
{
WUHBReader* reader = (WUHBReader*)ctx;
cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported
bool isFile;
uint32 table_offset = ROMFS_ENTRY_EMPTY;
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
{
table_offset = reader->Lookup(path, false);
isFile = false;
}
if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
{
table_offset = reader->Lookup(path, true);
isFile = true;
}
if (table_offset == ROMFS_ENTRY_EMPTY)
{
*fscStatus = FSC_STATUS_FILE_NOT_FOUND;
return nullptr;
}
*fscStatus = FSC_STATUS_OK;
return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY);
}
// singleton
public:
static fscDeviceWUHB& instance()
{
static fscDeviceWUHB _instance;
return _instance;
}
};
bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority)
{
return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK;
}

View file

@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T&
for(const T& v : T())
{
// test integer option
if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value))
if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value))
{
option = v;
return true;

View file

@ -280,10 +280,6 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
m_enabled = m_default_enabled;
}
auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware");
if (option_allowRendertargetSizeOptimization)
m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1");
auto option_vendorFilter = rules.FindOption("vendorFilter");
if (option_vendorFilter)
{
@ -345,7 +341,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
const auto preset_name = rules.FindOption("name");
if (!preset_name)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber());
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber());
continue;
}
@ -369,7 +365,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
}
catch (const std::exception & ex)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what());
cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", m_name, *preset_name, ex.what());
}
}
else if (boost::iequals(currentSectionName, "RAM"))
@ -383,7 +379,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
{
if (m_version <= 5)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", m_name, optionNameBuf);
throw std::exception();
}
@ -393,12 +389,12 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
{
if (addrEnd <= addrStart)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", m_name, addrStart, addrEnd, optionNameBuf);
throw std::exception();
}
else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0)
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", m_name, optionNameBuf);
throw std::exception();
}
else
@ -408,7 +404,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
}
else
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf);
cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", m_name, optionNameBuf);
throw std::exception();
}
}
@ -422,30 +418,22 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map;
// all vars must be defined in the default preset vars before
std::vector<std::pair<std::string, std::string>> mismatchingPresetVars;
for (const auto& presetEntry : m_presets)
for (const auto& entry : m_presets)
{
tmp_map[presetEntry->category].emplace_back(presetEntry);
tmp_map[entry->category].emplace_back(entry);
for (auto& presetVar : presetEntry->variables)
for (auto& kv : entry->variables)
{
const auto it = m_preset_vars.find(presetVar.first);
const auto it = m_preset_vars.find(kv.first);
if (it == m_preset_vars.cend())
{
mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first);
continue;
}
// overwrite var type with default var type
presetVar.second.first = it->second.first;
}
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains preset variables which are not defined in the default section", m_name);
throw std::exception();
}
if(!mismatchingPresetVars.empty())
{
cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString());
for (const auto& [presetName, varName] : mismatchingPresetVars)
cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName);
throw std::exception();
// overwrite var type with default var type
kv.second.first = it->second.first;
}
}
// have first entry be default active for every category if no default= is set
@ -477,7 +465,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
auto& p2 = kv.second[i + 1];
if (p1->variables.size() != p2->variables.size())
{
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
throw std::exception();
}
@ -485,14 +473,14 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules)
std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end());
if (keys1 != keys2)
{
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString());
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name);
throw std::exception();
}
if(p1->is_default)
{
if(has_default)
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name);
cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", m_name, p1->name);
p1->active = true;
has_default = true;
}
@ -821,7 +809,7 @@ void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep)
}
}
void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase)
void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC)
{
uint64 currentTitleId = CafeSystem::GetForegroundTitleId();
uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull;
@ -836,9 +824,9 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC
}
else
{
virtualMountPath = fs::path(virtualMountBase) / virtualMountPath;
virtualMountPath = fs::path("vol/content/") / virtualMountPath;
}
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority);
fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority);
}
}
}
@ -861,7 +849,7 @@ void GraphicPack2::LoadReplacedFiles()
{
// setup redirections
fscDeviceRedirect_map();
_iterateReplacedFiles(contentPath, false, "vol/content/");
_iterateReplacedFiles(contentPath, false);
}
// /aoc/
fs::path aocPath(gfxPackPath);
@ -874,18 +862,7 @@ void GraphicPack2::LoadReplacedFiles()
aocTitleId |= 0x0005000c00000000ULL;
// setup redirections
fscDeviceRedirect_map();
_iterateReplacedFiles(aocPath, true, nullptr);
}
// /code/
fs::path codePath(gfxPackPath);
codePath.append("code");
if (fs::exists(codePath, ec))
{
// setup redirections
fscDeviceRedirect_map();
_iterateReplacedFiles(codePath, false, CafeSystem::GetInternalVirtualCodeFolder().c_str());
_iterateReplacedFiles(aocPath, true);
}
}
@ -901,6 +878,9 @@ bool GraphicPack2::Activate()
if (m_gfx_vendor.has_value())
{
auto vendor = g_renderer->GetVendor();
if (vendor == GfxVendor::IntelLegacy || vendor == GfxVendor::IntelNoLegacy)
vendor = GfxVendor::Intel;
if (m_gfx_vendor.value() != vendor)
return false;
}
@ -979,7 +959,7 @@ bool GraphicPack2::Activate()
auto option_upscale = rules.FindOption("upscaleMagFilter");
if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor"))
m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
auto option_downscale = rules.FindOption("downscaleMinFilter");
auto option_downscale = rules.FindOption("NearestNeighbor");
if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor"))
m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor;
}

View file

@ -113,7 +113,6 @@ public:
const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy
const std::string& GetDescription() const { return m_description; }
bool IsDefaultEnabled() const { return m_default_enabled; }
bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; }
void SetEnabled(bool state) { m_enabled = state; }
@ -218,8 +217,6 @@ private:
bool m_default_enabled = false;
bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74
// filter
std::optional<RendererAPI> m_renderer_api;
std::optional<GfxVendor> m_gfx_vendor;
@ -260,7 +257,7 @@ private:
CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const;
void ApplyShaderPresets(std::string& shader_source) const;
void LoadReplacedFiles();
void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase);
void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC);
// ram mappings
std::vector<std::pair<MPTR, MPTR>> m_ramMappings;

View file

@ -45,17 +45,12 @@ public:
static void ClearRange(MPTR address, uint32 length)
{
if (length == 0)
return;
s_lock.lock();
for (;;)
while (length > 0)
{
auto itr = s_typeStorage.find(address);
if (itr != s_typeStorage.end())
s_typeStorage.erase(itr);
if (length <= 4)
break;
address += 4;
length -= 4;
}

View file

@ -8,7 +8,6 @@
#include "gui/debugger/DebuggerWindow2.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#include "util/helpers/helpers.h"
#if BOOST_OS_WINDOWS
#include <Windows.h>
@ -137,6 +136,11 @@ void debugger_createCodeBreakpoint(uint32 address, uint8 bpType)
debugger_updateExecutionBreakpoint(address);
}
void debugger_createExecuteBreakpoint(uint32 address)
{
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}
namespace coreinit
{
std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads();
@ -290,23 +294,8 @@ void debugger_toggleExecuteBreakpoint(uint32 address)
}
else
{
// create new execution breakpoint
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL);
}
}
void debugger_toggleLoggingBreakpoint(uint32 address)
{
auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING);
if (existingBP)
{
// delete existing breakpoint
debugger_deleteBreakpoint(existingBP);
}
else
{
// create new logging breakpoint
debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING);
// create new breakpoint
debugger_createExecuteBreakpoint(address);
}
}
@ -458,34 +447,6 @@ bool debugger_hasPatch(uint32 address)
return false;
}
void debugger_removePatch(uint32 address)
{
for (sint32 i = 0; i < debuggerState.patches.size(); i++)
{
auto& patch = debuggerState.patches[i];
if (address < patch->address || address >= (patch->address + patch->length))
continue;
MPTR startAddress = patch->address;
MPTR endAddress = patch->address + patch->length;
// remove any breakpoints overlapping with the patch
for (auto& bp : debuggerState.breakpoints)
{
if (bp->address + 4 > startAddress && bp->address < endAddress)
{
bp->enabled = false;
debugger_updateExecutionBreakpoint(bp->address);
}
}
// restore original data
memcpy(MEMPTR<void>(startAddress).GetPtr(), patch->origData.data(), patch->length);
PPCRecompiler_invalidateRange(startAddress, endAddress);
// remove patch
delete patch;
debuggerState.patches.erase(debuggerState.patches.begin() + i);
return;
}
}
void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
{
bool isRecEnabled = ppcRecompilerEnabled;
@ -540,6 +501,8 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU)
debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i];
}
void DebugLogStackTrace(OSThread_t* thread, MPTR sp);
void debugger_enterTW(PPCInterpreter_t* hCPU)
{
// handle logging points
@ -549,48 +512,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
{
if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled)
{
std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc)
{
size_t pos = 0;
while ((pos = comment.find(prefix, pos)) != std::string::npos)
{
size_t endPos = comment.find('}', pos);
if (endPos == std::string::npos)
break;
try
{
if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32)
{
std::string replacement = formatFunc(regNum);
comment.replace(pos, endPos - pos + 1, replacement);
pos += replacement.length();
}
else
{
pos = endPos + 1;
}
}
catch (...)
{
pos = endPos + 1;
}
}
};
// Replace integer register placeholders {rX}
replacePlaceholders("{r", [&](int regNum) {
return fmt::format("0x{:08X}", hCPU->gpr[regNum]);
});
// Replace floating point register placeholders {fX}
replacePlaceholders("{f", [&](int regNum) {
return fmt::format("{}", hCPU->fpr[regNum].fpr);
});
std::string logName = "Breakpoint '" + comment + "'";
std::string logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address);
std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : "");
cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext);
if (cemuLog_advancedPPCLoggingEnabled())
@ -627,7 +549,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
debuggerState.debugSession.stepInto = false;
debuggerState.debugSession.stepOver = false;
debuggerState.debugSession.run = false;
while (debuggerState.debugSession.isTrapped)
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// check for step commands

View file

@ -100,8 +100,8 @@ extern debuggerState_t debuggerState;
// new API
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
void debugger_createExecuteBreakpoint(uint32 address);
void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint
void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint
void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp);
void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite);
@ -114,7 +114,6 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = fals
void debugger_createPatch(uint32 address, std::span<uint8> patchData);
bool debugger_hasPatch(uint32 address);
void debugger_removePatch(uint32 address);
void debugger_forceBreak(); // force breakpoint at the next possible instruction
bool debugger_isTrapped();

View file

@ -297,7 +297,7 @@ bool GDBServer::Initialize()
void GDBServer::ThreadFunc()
{
SetThreadName("GDBServer");
SetThreadName("GDBServer::ThreadFunc");
while (!m_stopRequested)
{

View file

@ -10,18 +10,6 @@ namespace Espresso
CR_BIT_INDEX_SO = 3,
};
enum class PSQ_LOAD_TYPE
{
TYPE_F32 = 0,
TYPE_UNUSED1 = 1,
TYPE_UNUSED2 = 2,
TYPE_UNUSED3 = 3,
TYPE_U8 = 4,
TYPE_U16 = 5,
TYPE_S8 = 6,
TYPE_S16 = 7,
};
enum class PrimaryOpcode
{
// underscore at the end of the name means that this instruction always updates CR0 (as if RC bit is set)
@ -103,15 +91,13 @@ namespace Espresso
BCCTR = 528
};
enum class Opcode31
enum class OPCODE_31
{
TW = 4,
MFTB = 371,
};
inline PrimaryOpcode GetPrimaryOpcode(uint32 opcode) { return (PrimaryOpcode)(opcode >> 26); };
inline Opcode19 GetGroup19Opcode(uint32 opcode) { return (Opcode19)((opcode >> 1) & 0x3FF); };
inline Opcode31 GetGroup31Opcode(uint32 opcode) { return (Opcode31)((opcode >> 1) & 0x3FF); };
struct BOField
{
@ -146,12 +132,6 @@ namespace Espresso
uint8 bo;
};
// returns true if LK bit is set, only valid for branch instructions
inline bool DecodeLK(uint32 opcode)
{
return (opcode & 1) != 0;
}
inline void _decodeForm_I(uint32 opcode, uint32& LI, bool& AA, bool& LK)
{
LI = opcode & 0x3fffffc;
@ -203,7 +183,13 @@ namespace Espresso
_decodeForm_D_branch(opcode, BD, BO, BI, AA, LK);
}
inline void decodeOp_BCSPR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) // BCLR and BCSPR
inline void decodeOp_BCLR(uint32 opcode, BOField& BO, uint32& BI, bool& LK)
{
// form XL (with BD field expected to be zero)
_decodeForm_XL(opcode, BO, BI, LK);
}
inline void decodeOp_BCCTR(uint32 opcode, BOField& BO, uint32& BI, bool& LK)
{
// form XL (with BD field expected to be zero)
_decodeForm_XL(opcode, BO, BI, LK);

View file

@ -3,12 +3,12 @@ static void PPCInterpreter_setXerOV(PPCInterpreter_t* hCPU, bool hasOverflow)
{
if (hasOverflow)
{
hCPU->xer_so = 1;
hCPU->xer_ov = 1;
hCPU->spr.XER |= XER_SO;
hCPU->spr.XER |= XER_OV;
}
else
{
hCPU->xer_ov = 0;
hCPU->spr.XER &= ~XER_OV;
}
}
@ -41,7 +41,7 @@ static void PPCInterpreter_ADD(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_ADDO(PPCInterpreter_t* hCPU, uint32 opcode)
{
// Don't Starve Giant Edition uses this instruction + BSO
// untested (Don't Starve Giant Edition uses this instruction + BSO)
PPC_OPC_TEMPL3_XO();
uint32 result = hCPU->gpr[rA] + hCPU->gpr[rB];
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(hCPU->gpr[rA], hCPU->gpr[rB], result));
@ -113,6 +113,7 @@ static void PPCInterpreter_ADDEO(PPCInterpreter_t* hCPU, uint32 opcode)
else
hCPU->xer_ca = 0;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, b, hCPU->gpr[rD]));
// update CR
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -129,7 +130,7 @@ static void PPCInterpreter_ADDI(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_ADDIC(PPCInterpreter_t* hCPU, uint32 opcode)
{
sint32 rD, rA;
int rD, rA;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm);
uint32 a = hCPU->gpr[rA];
@ -144,7 +145,7 @@ static void PPCInterpreter_ADDIC(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_ADDIC_(PPCInterpreter_t* hCPU, uint32 opcode)
{
sint32 rD, rA;
int rD, rA;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm);
uint32 a = hCPU->gpr[rA];
@ -154,13 +155,14 @@ static void PPCInterpreter_ADDIC_(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
// update cr0 flags
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_ADDIS(PPCInterpreter_t* hCPU, uint32 opcode)
{
sint32 rD, rA;
int rD, rA;
uint32 imm;
PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm);
hCPU->gpr[rD] = (rA ? hCPU->gpr[rA] : 0) + imm;
@ -183,23 +185,6 @@ static void PPCInterpreter_ADDZE(PPCInterpreter_t* hCPU, uint32 opcode)
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_ADDZEO(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
PPC_ASSERT(rB == 0);
uint32 a = hCPU->gpr[rA];
uint32 ca = hCPU->xer_ca;
hCPU->gpr[rD] = a + ca;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, 0, hCPU->gpr[rD]));
if ((a == 0xffffffff) && ca)
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_ADDME(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
@ -216,23 +201,6 @@ static void PPCInterpreter_ADDME(PPCInterpreter_t* hCPU, uint32 opcode)
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_ADDMEO(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
PPC_ASSERT(rB == 0);
uint32 a = hCPU->gpr[rA];
uint32 ca = hCPU->xer_ca;
hCPU->gpr[rD] = a + ca + 0xffffffff;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, 0xffffffff, hCPU->gpr[rD]));
if (a || ca)
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
@ -244,12 +212,11 @@ static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode)
{
// Seen in Don't Starve Giant Edition and Teslagrad
// untested (Don't Starve Giant Edition uses this)
// also used by DS Virtual Console (Super Mario 64 DS)
PPC_OPC_TEMPL3_XO();
uint32 result = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], result));
hCPU->gpr[rD] = result;
hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], hCPU->gpr[rD]));
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -278,7 +245,7 @@ static void PPCInterpreter_SUBFCO(PPCInterpreter_t* hCPU, uint32 opcode)
uint32 a = hCPU->gpr[rA];
uint32 b = hCPU->gpr[rB];
hCPU->gpr[rD] = ~a + b + 1;
// update carry
// update xer
if (ppc_carry_3(~a, b, 1))
hCPU->xer_ca = 1;
else
@ -292,7 +259,7 @@ static void PPCInterpreter_SUBFCO(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_SUBFIC(PPCInterpreter_t* hCPU, uint32 opcode)
{
sint32 rD, rA;
int rD, rA;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm);
uint32 a = hCPU->gpr[rA];
@ -316,6 +283,7 @@ static void PPCInterpreter_SUBFE(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
// update cr0
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -335,6 +303,7 @@ static void PPCInterpreter_SUBFEO(PPCInterpreter_t* hCPU, uint32 opcode)
else
hCPU->xer_ca = 0;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, b, result));
// update cr0
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -356,25 +325,9 @@ static void PPCInterpreter_SUBFZE(PPCInterpreter_t* hCPU, uint32 opcode)
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_SUBFZEO(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
PPC_ASSERT(rB == 0);
uint32 a = hCPU->gpr[rA];
uint32 ca = hCPU->xer_ca;
hCPU->gpr[rD] = ~a + ca;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, 0, hCPU->gpr[rD]));
if (a == 0 && ca)
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_SUBFME(PPCInterpreter_t* hCPU, uint32 opcode)
{
// untested
PPC_OPC_TEMPL3_XO();
PPC_ASSERT(rB == 0);
uint32 a = hCPU->gpr[rA];
@ -385,24 +338,7 @@ static void PPCInterpreter_SUBFME(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
if (opcode & PPC_OPC_RC)
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_SUBFMEO(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
PPC_ASSERT(rB == 0);
uint32 a = hCPU->gpr[rA];
uint32 ca = hCPU->xer_ca;
hCPU->gpr[rD] = ~a + 0xFFFFFFFF + ca;
PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, 0xFFFFFFFF, hCPU->gpr[rD]));
// update xer carry
if (ppc_carry_3(~a, 0xFFFFFFFF, ca))
hCPU->xer_ca = 1;
else
hCPU->xer_ca = 0;
// update cr0
if (opcode & PPC_OPC_RC)
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -415,8 +351,13 @@ static void PPCInterpreter_MULHW_(PPCInterpreter_t* hCPU, uint32 opcode)
sint64 b = (sint32)hCPU->gpr[rB];
sint64 c = a * b;
hCPU->gpr[rD] = ((uint64)c) >> 32;
if (opHasRC())
if (opcode & PPC_OPC_RC) {
// update cr0 flags
#ifdef CEMU_DEBUG_ASSERT
assert_dbg();
#endif
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
}
PPCInterpreter_nextInstruction(hCPU);
}
@ -467,13 +408,13 @@ static void PPCInterpreter_MULLI(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_DIVW(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
sint32 a = (sint32)hCPU->gpr[rA];
sint32 b = (sint32)hCPU->gpr[rB];
sint32 a = hCPU->gpr[rA];
sint32 b = hCPU->gpr[rB];
if (b == 0)
hCPU->gpr[rD] = a < 0 ? 0xFFFFFFFF : 0;
else if (a == 0x80000000 && b == 0xFFFFFFFF)
hCPU->gpr[rD] = 0xFFFFFFFF;
else
{
cemuLog_logDebug(LogType::Force, "Error: Division by zero! [{:08x}]", (uint32)hCPU->instructionPointer);
b++;
}
hCPU->gpr[rD] = a / b;
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
@ -483,23 +424,16 @@ static void PPCInterpreter_DIVW(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_DIVWO(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
sint32 a = (sint32)hCPU->gpr[rA];
sint32 b = (sint32)hCPU->gpr[rB];
sint32 a = hCPU->gpr[rA];
sint32 b = hCPU->gpr[rB];
if (b == 0)
{
PPCInterpreter_setXerOV(hCPU, true);
hCPU->gpr[rD] = a < 0 ? 0xFFFFFFFF : 0;
PPCInterpreter_nextInstruction(hCPU);
return;
}
else if(a == 0x80000000 && b == 0xFFFFFFFF)
{
PPCInterpreter_setXerOV(hCPU, true);
hCPU->gpr[rD] = 0xFFFFFFFF;
}
else
{
hCPU->gpr[rD] = a / b;
PPCInterpreter_setXerOV(hCPU, false);
}
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -508,14 +442,12 @@ static void PPCInterpreter_DIVWO(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_DIVWU(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
uint32 a = hCPU->gpr[rA];
uint32 b = hCPU->gpr[rB];
if (b == 0)
hCPU->gpr[rD] = 0;
else if (a == 0x80000000 && b == 0xFFFFFFFF)
hCPU->gpr[rD] = 0;
else
hCPU->gpr[rD] = a / b;
if (hCPU->gpr[rB] == 0)
{
PPCInterpreter_nextInstruction(hCPU);
return;
}
hCPU->gpr[rD] = hCPU->gpr[rA] / hCPU->gpr[rB];
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -524,23 +456,14 @@ static void PPCInterpreter_DIVWU(PPCInterpreter_t* hCPU, uint32 opcode)
static void PPCInterpreter_DIVWUO(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL3_XO();
uint32 a = hCPU->gpr[rA];
uint32 b = hCPU->gpr[rB];
if (b == 0)
if (hCPU->gpr[rB] == 0)
{
PPCInterpreter_setXerOV(hCPU, true);
hCPU->gpr[rD] = 0;
PPCInterpreter_nextInstruction(hCPU);
return;
}
else if(a == 0x80000000 && b == 0xFFFFFFFF)
{
hCPU->gpr[rD] = hCPU->gpr[rA] / hCPU->gpr[rB];
PPCInterpreter_setXerOV(hCPU, false);
hCPU->gpr[rD] = 0;
}
else
{
hCPU->gpr[rD] = a / b;
PPCInterpreter_setXerOV(hCPU, false);
}
if (opHasRC())
ppc_update_cr0(hCPU, hCPU->gpr[rD]);
PPCInterpreter_nextInstruction(hCPU);
@ -567,13 +490,6 @@ static void PPCInterpreter_CRANDC(PPCInterpreter_t* hCPU, uint32 opcode)
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_CRNAND(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL_X_CR();
ppc_setCRBit(hCPU, crD, (ppc_getCRBit(hCPU, crA)&ppc_getCRBit(hCPU, crB)) ^ 1);
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_CROR(PPCInterpreter_t* hCPU, uint32 opcode)
{
PPC_OPC_TEMPL_X_CR();
@ -931,7 +847,8 @@ static void PPCInterpreter_CMP(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->cr[cr * 4 + CR_BIT_GT] = 1;
else
hCPU->cr[cr * 4 + CR_BIT_EQ] = 1;
hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so;
if ((hCPU->spr.XER & XER_SO) != 0)
hCPU->cr[cr * 4 + CR_BIT_SO] = 1;
PPCInterpreter_nextInstruction(hCPU);
}
@ -953,7 +870,8 @@ static void PPCInterpreter_CMPL(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->cr[cr * 4 + CR_BIT_GT] = 1;
else
hCPU->cr[cr * 4 + CR_BIT_EQ] = 1;
hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so;
if ((hCPU->spr.XER & XER_SO) != 0)
hCPU->cr[cr * 4 + CR_BIT_SO] = 1;
PPCInterpreter_nextInstruction(hCPU);
}
@ -976,7 +894,8 @@ static void PPCInterpreter_CMPI(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->cr[cr * 4 + CR_BIT_GT] = 1;
else
hCPU->cr[cr * 4 + CR_BIT_EQ] = 1;
hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so;
if (hCPU->spr.XER & XER_SO)
hCPU->cr[cr * 4 + CR_BIT_SO] = 1;
PPCInterpreter_nextInstruction(hCPU);
}
@ -999,7 +918,8 @@ static void PPCInterpreter_CMPLI(PPCInterpreter_t* hCPU, uint32 opcode)
hCPU->cr[cr * 4 + CR_BIT_GT] = 1;
else
hCPU->cr[cr * 4 + CR_BIT_EQ] = 1;
hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so;
if (hCPU->spr.XER & XER_SO)
hCPU->cr[cr * 4 + CR_BIT_SO] = 1;
PPCInterpreter_nextInstruction(hCPU);
}

View file

@ -32,7 +32,7 @@ espresso_frsqrte_entry_t frsqrteLookupTable[32] =
{0x20c1000, 0x35e},{0x1f12000, 0x332},{0x1d79000, 0x30a},{0x1bf4000, 0x2e6},
};
ATTR_MS_ABI double frsqrte_espresso(double input)
double frsqrte_espresso(double input)
{
unsigned long long x = *(unsigned long long*)&input;
@ -111,7 +111,7 @@ espresso_fres_entry_t fresLookupTable[32] =
{0x88400, 0x11a}, {0x65000, 0x11a}, {0x41c00, 0x108}, {0x20c00, 0x106}
};
ATTR_MS_ABI double fres_espresso(double input)
double fres_espresso(double input)
{
// based on testing we know that fres uses only the first 15 bits of the mantissa
// seee eeee eeee mmmm mmmm mmmm mmmx xxxx .... (s = sign, e = exponent, m = mantissa, x = not used)

View file

@ -2,70 +2,62 @@
#include "PPCInterpreterInternal.h"
#include "PPCInterpreterHelper.h"
std::unordered_set<std::string> s_unsupportedHLECalls;
std::unordered_set<std::string> sUnsupportedHLECalls;
void PPCInterpreter_handleUnsupportedHLECall(PPCInterpreter_t* hCPU)
{
const char* libFuncName = (char*)memory_getPointerFromVirtualOffset(hCPU->instructionPointer + 8);
std::string tempString = fmt::format("Unsupported lib call: {}", libFuncName);
if (s_unsupportedHLECalls.find(tempString) == s_unsupportedHLECalls.end())
if (sUnsupportedHLECalls.find(tempString) == sUnsupportedHLECalls.end())
{
cemuLog_log(LogType::UnsupportedAPI, "{}", tempString);
s_unsupportedHLECalls.emplace(tempString);
sUnsupportedHLECalls.emplace(tempString);
}
hCPU->gpr[3] = 0;
PPCInterpreter_nextInstruction(hCPU);
}
static constexpr size_t HLE_TABLE_CAPACITY = 0x4000;
HLECALL s_ppcHleTable[HLE_TABLE_CAPACITY]{};
sint32 s_ppcHleTableWriteIndex = 0;
std::mutex s_ppcHleTableMutex;
std::vector<void(*)(PPCInterpreter_t* hCPU)>* sPPCHLETable{};
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName)
{
std::unique_lock _l(s_ppcHleTableMutex);
if (s_ppcHleTableWriteIndex >= HLE_TABLE_CAPACITY)
{
cemuLog_log(LogType::Force, "HLE table is full");
cemu_assert(false);
}
for (sint32 i = 0; i < s_ppcHleTableWriteIndex; i++)
{
if (s_ppcHleTable[i] == hleCall)
if (!sPPCHLETable)
sPPCHLETable = new std::vector<void(*)(PPCInterpreter_t* hCPU)>();
for (sint32 i = 0; i < sPPCHLETable->size(); i++)
{
if ((*sPPCHLETable)[i] == hleCall)
return i;
}
}
cemu_assert(s_ppcHleTableWriteIndex < HLE_TABLE_CAPACITY);
s_ppcHleTable[s_ppcHleTableWriteIndex] = hleCall;
HLEIDX funcIndex = s_ppcHleTableWriteIndex;
s_ppcHleTableWriteIndex++;
return funcIndex;
HLEIDX newFuncIndex = (sint32)sPPCHLETable->size();
sPPCHLETable->resize(sPPCHLETable->size() + 1);
(*sPPCHLETable)[newFuncIndex] = hleCall;
return newFuncIndex;
}
HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex)
{
if (funcIndex < 0 || funcIndex >= HLE_TABLE_CAPACITY)
if (funcIndex < 0 || funcIndex >= sPPCHLETable->size())
return nullptr;
return s_ppcHleTable[funcIndex];
return sPPCHLETable->data()[funcIndex];
}
std::mutex s_hleLogMutex;
std::mutex g_hleLogMutex;
void PPCInterpreter_virtualHLE(PPCInterpreter_t* hCPU, unsigned int opcode)
{
uint32 hleFuncId = opcode & 0xFFFF;
if (hleFuncId == 0xFFD0) [[unlikely]]
if (hleFuncId == 0xFFD0)
{
s_hleLogMutex.lock();
g_hleLogMutex.lock();
PPCInterpreter_handleUnsupportedHLECall(hCPU);
s_hleLogMutex.unlock();
g_hleLogMutex.unlock();
return;
}
else
{
// os lib function
auto hleCall = PPCInterpreter_getHLECall(hleFuncId);
cemu_assert(hleFuncId < sPPCHLETable->size());
auto hleCall = (*sPPCHLETable)[hleFuncId];
cemu_assert(hleCall);
hleCall(hCPU);
}

View file

@ -428,6 +428,9 @@ public:
}
};
uint32 testIP[100];
uint32 testIPC = 0;
template <typename ppcItpCtrl>
class PPCInterpreterContainer
{
@ -463,10 +466,6 @@ public:
case 1: // virtual HLE
PPCInterpreter_virtualHLE(hCPU, opcode);
break;
case 3:
cemuLog_logDebug(LogType::Force, "Unsupported TWI instruction executed at {:08x}", hCPU->instructionPointer);
PPCInterpreter_nextInstruction(hCPU);
break;
case 4:
switch (PPC_getBits(opcode, 30, 5))
{
@ -483,9 +482,8 @@ public:
PPCInterpreter_PS_CMPU1(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->0] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer);
debug_printf("Unknown execute %04X as [4->0] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer);
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
break;
}
break;
@ -511,9 +509,8 @@ public:
PPCInterpreter_PS_ABS(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->8] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer);
debug_printf("Unknown execute %04X as [4->8] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer);
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
break;
}
break;
@ -551,9 +548,8 @@ public:
PPCInterpreter_PS_MERGE11(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->16] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer);
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
debug_printf("Unknown execute %04X as [4->16] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer);
debugBreakpoint();
break;
}
break;
@ -594,9 +590,8 @@ public:
PPCInterpreter_PS_NMADD(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4] at {:08x}", PPC_getBits(opcode, 30, 5), hCPU->instructionPointer);
debug_printf("Unknown execute %04X as [4] at %08X\n", PPC_getBits(opcode, 30, 5), hCPU->instructionPointer);
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
break;
}
break;
@ -628,15 +623,12 @@ public:
PPCInterpreter_BCX(hCPU, opcode);
break;
case 17:
if (PPC_getBits(opcode, 30, 1) == 1)
{
if (PPC_getBits(opcode, 30, 1) == 1) {
PPCInterpreter_SC(hCPU, opcode);
}
else
{
cemuLog_logDebug(LogType::Force, "Unsupported Opcode [0x17 --> 0x0]");
else {
debug_printf("Unsupported Opcode [0x17 --> 0x0]\n");
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
}
break;
case 18:
@ -666,9 +658,6 @@ public:
case 193:
PPCInterpreter_CRXOR(hCPU, opcode);
break;
case 225:
PPCInterpreter_CRNAND(hCPU, opcode);
break;
case 257:
PPCInterpreter_CRAND(hCPU, opcode);
break;
@ -685,9 +674,8 @@ public:
PPCInterpreter_BCCTR(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [19] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
debug_printf("Unknown execute %04X as [19] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
break;
}
break;
@ -725,6 +713,9 @@ public:
PPCInterpreter_CMP(hCPU, opcode);
break;
case 4:
#ifdef CEMU_DEBUG_ASSERT
debug_printf("TW instruction executed at %08x\n", hCPU->instructionPointer);
#endif
PPCInterpreter_TW(hCPU, opcode);
break;
case 8:
@ -904,12 +895,6 @@ public:
case 522:
PPCInterpreter_ADDCO(hCPU, opcode);
break;
case 523: // 11 | OE
PPCInterpreter_MULHWU_(hCPU, opcode); // OE is ignored
break;
case 533:
PPCInterpreter_LSWX(hCPU, opcode);
break;
case 534:
PPCInterpreter_LWBRX(hCPU, opcode);
break;
@ -928,9 +913,6 @@ public:
case 567:
PPCInterpreter_LFSUX(hCPU, opcode);
break;
case 587: // 75 | OE
PPCInterpreter_MULHW_(hCPU, opcode); // OE is ignored for MULHW
break;
case 595:
PPCInterpreter_MFSR(hCPU, opcode);
break;
@ -961,30 +943,15 @@ public:
case 663:
PPCInterpreter_STFSX(hCPU, opcode);
break;
case 661:
PPCInterpreter_STSWX(hCPU, opcode);
break;
case 695:
PPCInterpreter_STFSUX(hCPU, opcode);
break;
case 712: // 200 | OE
PPCInterpreter_SUBFZEO(hCPU, opcode);
break;
case 714: // 202 | OE
PPCInterpreter_ADDZEO(hCPU, opcode);
break;
case 725:
PPCInterpreter_STSWI(hCPU, opcode);
break;
case 727:
PPCInterpreter_STFDX(hCPU, opcode);
break;
case 744: // 232 | OE
PPCInterpreter_SUBFMEO(hCPU, opcode);
break;
case 746: // 234 | OE
PPCInterpreter_ADDMEO(hCPU, opcode);
break;
case 747:
PPCInterpreter_MULLWO(hCPU, opcode);
break;
@ -1031,8 +998,10 @@ public:
PPCInterpreter_DCBZ(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [31] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
cemu_assert_unimplemented();
debug_printf("Unknown execute %04X as [31] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
#ifdef CEMU_DEBUG_ASSERT
assert_dbg();
#endif
hCPU->instructionPointer += 4;
break;
}
@ -1115,7 +1084,7 @@ public:
case 57:
PPCInterpreter_PSQ_LU(hCPU, opcode);
break;
case 59: // opcode category
case 59: //Opcode category
switch (PPC_getBits(opcode, 30, 5))
{
case 18:
@ -1146,9 +1115,8 @@ public:
PPCInterpreter_FNMADDS(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [59] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
debug_printf("Unknown execute %04X as [59] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
cemu_assert_unimplemented();
hCPU->instructionPointer += 4;
break;
}
break;
@ -1227,19 +1195,18 @@ public:
case 583:
PPCInterpreter_MFFS(hCPU, opcode);
break;
case 711:
case 711: // IBM documentation has this wrong as 771?
PPCInterpreter_MTFSF(hCPU, opcode);
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [63] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
debug_printf("Unknown execute %04X as [63] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer);
cemu_assert_unimplemented();
PPCInterpreter_nextInstruction(hCPU);
break;
}
}
break;
default:
cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} at {:08x}\n", PPC_getBits(opcode, 5, 6), (unsigned int)hCPU->instructionPointer);
debug_printf("Unknown execute %04X at %08X\n", PPC_getBits(opcode, 5, 6), (unsigned int)hCPU->instructionPointer);
cemu_assert_unimplemented();
}
}

View file

@ -50,9 +50,9 @@
#define CR_BIT_EQ 2
#define CR_BIT_SO 3
#define XER_SO (1<<31) // summary overflow bit
#define XER_OV (1<<30) // overflow bit
#define XER_BIT_CA (29) // carry bit index. To accelerate frequent access, this bit is stored as a separate uint8
#define XER_BIT_SO (31) // summary overflow, counterpart to CR SO
#define XER_BIT_OV (30)
// FPSCR
#define FPSCR_VXSNAN (1<<24)
@ -118,8 +118,7 @@
static inline void ppc_update_cr0(PPCInterpreter_t* hCPU, uint32 r)
{
cemu_assert_debug(hCPU->xer_so <= 1);
hCPU->cr[CR_BIT_SO] = hCPU->xer_so;
hCPU->cr[CR_BIT_SO] = (hCPU->spr.XER&XER_SO) ? 1 : 0;
hCPU->cr[CR_BIT_LT] = ((r != 0) ? 1 : 0) & ((r & 0x80000000) ? 1 : 0);
hCPU->cr[CR_BIT_EQ] = (r == 0);
hCPU->cr[CR_BIT_GT] = hCPU->cr[CR_BIT_EQ] ^ hCPU->cr[CR_BIT_LT] ^ 1; // this works because EQ and LT can never be set at the same time. So the only case where GT becomes 1 is when LT=0 and EQ=0
@ -191,8 +190,8 @@ inline double roundTo25BitAccuracy(double d)
return *(double*)&v;
}
ATTR_MS_ABI double fres_espresso(double input);
ATTR_MS_ABI double frsqrte_espresso(double input);
double fres_espresso(double input);
double frsqrte_espresso(double input);
void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b);

View file

@ -31,7 +31,7 @@ static void PPCInterpreter_STW(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STWU(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS;
int rA, rS;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm);
ppcItpCtrl::ppcMem_writeDataU32(hCPU, hCPU->gpr[rA] + imm, hCPU->gpr[rS]);
@ -42,7 +42,7 @@ static void PPCInterpreter_STWU(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STWX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]);
PPCInterpreter_nextInstruction(hCPU);
@ -85,8 +85,7 @@ static void PPCInterpreter_STWCX(PPCInterpreter_t* hCPU, uint32 Opcode)
ppc_setCRBit(hCPU, CR_BIT_GT, 0);
ppc_setCRBit(hCPU, CR_BIT_EQ, 1);
}
cemu_assert_debug(hCPU->xer_so <= 1);
ppc_setCRBit(hCPU, CR_BIT_SO, hCPU->xer_so);
ppc_setCRBit(hCPU, CR_BIT_SO, (hCPU->spr.XER&XER_SO) != 0 ? 1 : 0);
// remove reservation
hCPU->reservedMemAddr = 0;
hCPU->reservedMemValue = 0;
@ -103,7 +102,7 @@ static void PPCInterpreter_STWCX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STWUX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]);
if (rA)
@ -113,7 +112,7 @@ static void PPCInterpreter_STWUX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STWBRX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU32(hCPU->gpr[rS]));
PPCInterpreter_nextInstruction(hCPU);
@ -121,7 +120,7 @@ static void PPCInterpreter_STWBRX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STMW(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rS, rA;
int rS, rA;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm);
uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm;
@ -136,7 +135,7 @@ static void PPCInterpreter_STMW(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STH(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS;
int rA, rS;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm);
ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]);
@ -145,7 +144,7 @@ static void PPCInterpreter_STH(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STHU(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS;
int rA, rS;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm);
ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]);
@ -156,7 +155,7 @@ static void PPCInterpreter_STHU(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STHX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]);
PPCInterpreter_nextInstruction(hCPU);
@ -164,7 +163,7 @@ static void PPCInterpreter_STHX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STHUX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]);
if (rA)
@ -174,7 +173,7 @@ static void PPCInterpreter_STHUX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STHBRX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU16((uint16)hCPU->gpr[rS]));
PPCInterpreter_nextInstruction(hCPU);
@ -182,7 +181,7 @@ static void PPCInterpreter_STHBRX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STB(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS;
int rA, rS;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm);
ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint8)hCPU->gpr[rS]);
@ -191,7 +190,7 @@ static void PPCInterpreter_STB(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STBU(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS;
int rA, rS;
uint32 imm;
PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm);
ppcItpCtrl::ppcMem_writeDataU8(hCPU, hCPU->gpr[rA] + imm, (uint8)hCPU->gpr[rS]);
@ -201,7 +200,7 @@ static void PPCInterpreter_STBU(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STBX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]);
PPCInterpreter_nextInstruction(hCPU);
@ -209,7 +208,7 @@ static void PPCInterpreter_STBX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STBUX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
int rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]);
if (rA)
@ -219,7 +218,7 @@ static void PPCInterpreter_STBUX(PPCInterpreter_t* hCPU, uint32 Opcode)
static void PPCInterpreter_STSWI(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, nb;
int rA, rS, nb;
PPC_OPC_TEMPL_X(Opcode, rS, rA, nb);
if (nb == 0) nb = 32;
uint32 ea = rA ? hCPU->gpr[rA] : 0;
@ -229,39 +228,7 @@ static void PPCInterpreter_STSWI(PPCInterpreter_t* hCPU, uint32 Opcode)
{
if (i == 0)
{
r = rS < 32 ? hCPU->gpr[rS] : 0; // what happens if rS is out of bounds?
rS++;
rS %= 32;
i = 4;
}
ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, (r >> 24));
r <<= 8;
ea++;
i--;
nb--;
}
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_STSWX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rS, rB;
PPC_OPC_TEMPL_X(Opcode, rS, rA, rB);
sint32 nb = hCPU->spr.XER&0x7F;
if (nb == 0)
{
PPCInterpreter_nextInstruction(hCPU);
return;
}
uint32 ea = rA ? hCPU->gpr[rA] : 0;
ea += hCPU->gpr[rB];
uint32 r = 0;
int i = 0;
while (nb > 0)
{
if (i == 0)
{
r = rS < 32 ? hCPU->gpr[rS] : 0; // what happens if rS is out of bounds?
r = hCPU->gpr[rS];
rS++;
rS %= 32;
i = 4;
@ -492,51 +459,8 @@ static void PPCInterpreter_LSWI(PPCInterpreter_t* hCPU, uint32 Opcode)
PPC_OPC_TEMPL_X(Opcode, rD, rA, nb);
if (nb == 0)
nb = 32;
uint32 ea = rA ? hCPU->gpr[rA] : 0;
uint32 r = 0;
int i = 4;
uint8 v;
while (nb>0)
{
if (i == 0)
{
i = 4;
if(rD < 32)
hCPU->gpr[rD] = r;
rD++;
rD %= 32;
r = 0;
}
v = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea);
r <<= 8;
r |= v;
ea++;
i--;
nb--;
}
while (i)
{
r <<= 8;
i--;
}
if(rD < 32)
hCPU->gpr[rD] = r;
PPCInterpreter_nextInstruction(hCPU);
}
static void PPCInterpreter_LSWX(PPCInterpreter_t* hCPU, uint32 Opcode)
{
sint32 rA, rD, rB;
PPC_OPC_TEMPL_X(Opcode, rD, rA, rB);
// byte count comes from XER
uint32 nb = (hCPU->spr.XER>>0)&0x7F;
if (nb == 0)
{
PPCInterpreter_nextInstruction(hCPU);
return; // no-op
}
uint32 ea = rA ? hCPU->gpr[rA] : 0;
ea += hCPU->gpr[rB];
uint32 r = 0;
int i = 4;
uint8 v;
@ -545,7 +469,6 @@ static void PPCInterpreter_LSWX(PPCInterpreter_t* hCPU, uint32 Opcode)
if (i == 0)
{
i = 4;
if(rD < 32)
hCPU->gpr[rD] = r;
rD++;
rD %= 32;
@ -563,7 +486,6 @@ static void PPCInterpreter_LSWX(PPCInterpreter_t* hCPU, uint32 Opcode)
r <<= 8;
i--;
}
if(rD < 32)
hCPU->gpr[rD] = r;
PPCInterpreter_nextInstruction(hCPU);
}

View file

@ -63,25 +63,16 @@ void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue)
uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU)
{
uint32 xerValue = hCPU->spr.XER;
xerValue &= ~(1 << XER_BIT_CA);
xerValue &= ~(1 << XER_BIT_SO);
xerValue &= ~(1 << XER_BIT_OV);
if (hCPU->xer_ca)
xerValue |= (1 << XER_BIT_CA);
if (hCPU->xer_so)
xerValue |= (1 << XER_BIT_SO);
if (hCPU->xer_ov)
xerValue |= (1 << XER_BIT_OV);
xerValue &= ~(1<<XER_BIT_CA);
if( hCPU->xer_ca )
xerValue |= (1<<XER_BIT_CA);
return xerValue;
}
void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v)
{
const uint32 XER_MASK = 0xE0FFFFFF; // some bits are masked out. Figure out which ones exactly
hCPU->spr.XER = v & XER_MASK;
hCPU->xer_ca = (v >> XER_BIT_CA) & 1;
hCPU->xer_so = (v >> XER_BIT_SO) & 1;
hCPU->xer_ov = (v >> XER_BIT_OV) & 1;
hCPU->spr.XER = v;
hCPU->xer_ca = (v>>XER_BIT_CA)&1;
}
uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU)
@ -99,7 +90,7 @@ uint8* PPCInterpreterGetStackPointer()
return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]);
}
uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset)
uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset)
{
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset);

View file

@ -5,6 +5,7 @@
#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h"
#include "../Recompiler/PPCRecompiler.h"
#include "../Recompiler/PPCRecompilerX64.h"
#include <float.h>
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
@ -93,6 +94,7 @@ void PPCInterpreter_MTCRF(PPCInterpreter_t* hCPU, uint32 Opcode)
{
// frequently used by GCC compiled code (e.g. SM64 port)
// tested
uint32 rS;
uint32 crfMask;
PPC_OPC_TEMPL_XFX(Opcode, rS, crfMask);

View file

@ -68,8 +68,6 @@ static void PPCInterpreter_TW(PPCInterpreter_t* hCPU, uint32 opcode)
PPC_OPC_TEMPL_X(opcode, to, rA, rB);
cemu_assert_debug(to == 0);
if(to != 0)
PPCInterpreter_nextInstruction(hCPU);
if (rA == DEBUGGER_BP_T_DEBUGGER)
debugger_enterTW(hCPU);

View file

@ -5,28 +5,8 @@ struct PPCCoreCallbackData_t
{
sint32 gprCount = 0;
sint32 floatCount = 0;
sint32 stackCount = 0;
};
inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value)
{
if (data.gprCount < 8)
{
hCPU->gpr[3 + data.gprCount] = value;
data.gprCount++;
}
else
{
uint32 stackOffset = 8 + data.stackCount * 4;
// PPCCore_executeCallbackInternal does -16*4 to save the current stack area
stackOffset -= 16 * 4;
memory_writeU32(hCPU->gpr[1] + stackOffset, value);
data.stackCount++;
}
}
// callback functions
inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
{
@ -36,21 +16,23 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data)
template <typename T, typename... TArgs>
uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args)
{
// TODO float arguments on stack
cemu_assert_debug(data.floatCount < 8);
cemu_assert_debug(data.gprCount <= 8);
cemu_assert_debug(data.floatCount <= 8);
PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance();
if constexpr (std::is_pointer_v<T>)
{
_PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR());
hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR();
data.gprCount++;
}
else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>)
{
_PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR());
hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR();
data.gprCount++;
}
else if constexpr (std::is_reference_v<T>)
{
_PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(&currentArg).GetMPTR());
hCPU->gpr[3 + data.gprCount] = MEMPTR(&currentArg).GetMPTR();
data.gprCount++;
}
else if constexpr(std::is_enum_v<T>)
{
@ -71,7 +53,8 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg,
}
else
{
_PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg);
hCPU->gpr[3 + data.gprCount] = (uint32)currentArg;
data.gprCount++;
}
return PPCCoreCallback(function, data, args...);

View file

@ -49,8 +49,8 @@ struct PPCInterpreter_t
uint32 fpscr;
uint8 cr[32]; // 0 -> bit not set, 1 -> bit set (upper 7 bits of each byte must always be zero) (cr0 starts at index 0, cr1 at index 4 ..)
uint8 xer_ca; // carry from xer
uint8 xer_so;
uint8 xer_ov;
uint8 LSQE;
uint8 PSE;
// thread remaining cycles
sint32 remainingCycles; // if this value goes below zero, the next thread is scheduled
sint32 skippedCycles; // number of skipped cycles
@ -67,8 +67,7 @@ struct PPCInterpreter_t
uint32 reservedMemValue;
// temporary storage for recompiler
FPR_t temporaryFPR[8];
uint32 temporaryGPR[4]; // deprecated, refactor backend dependency on this away
uint32 temporaryGPR_reg[4];
uint32 temporaryGPR[4];
// values below this are not used by Cafe OS usermode
struct
{
@ -93,8 +92,6 @@ struct PPCInterpreter_t
uint32 sr[16];
uint32 sdr1;
}sprExtended;
uint8 LSQE;
uint8 PSE;
// global CPU values
PPCInterpreterGlobal_t* global;
// interpreter control
@ -216,7 +213,7 @@ void PPCTimer_start();
// core info and control
extern uint32 ppcThreadQuantum;
uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset);
uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset);
uint8* PPCInterpreterGetStackPointer();
void PPCInterpreterModifyStackPointer(sint32 offset);
@ -230,9 +227,9 @@ static inline float flushDenormalToZero(float f)
// HLE interface
using HLECALL = void(*)(PPCInterpreter_t*);
using HLEIDX = sint32;
typedef void(*HLECALL)(PPCInterpreter_t* hCPU);
typedef sint32 HLEIDX;
HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName);
HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex);

View file

@ -1,4 +1,5 @@
#include "Cafe/HW/Espresso/Const.h"
#include "asm/x64util.h"
#include "config/ActiveSettings.h"
#include "util/helpers/fspinlock.h"
#include "util/highresolutiontimer/HighResolutionTimer.h"

View file

@ -1,18 +0,0 @@
#pragma once
#include "HW/Espresso/Recompiler/IML/IMLInstruction.h"
#include "../PPCRecompiler.h"
bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext);
void PPCRecompiler_cleanupAArch64Code(void* code, size_t size);
void PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions();
// architecture specific constants
namespace IMLArchAArch64
{
static constexpr int PHYSREG_GPR_BASE = 0;
static constexpr int PHYSREG_GPR_COUNT = 25;
static constexpr int PHYSREG_FPR_BASE = PHYSREG_GPR_COUNT;
static constexpr int PHYSREG_FPR_COUNT = 31;
}; // namespace IMLArchAArch64

File diff suppressed because it is too large Load diff

View file

@ -1,469 +0,0 @@
#include "../PPCRecompiler.h"
#include "../IML/IML.h"
#include "BackendX64.h"
#include "Common/cpu_features.h"
uint32 _regF64(IMLReg physReg);
uint32 _regI32(IMLReg r)
{
cemu_assert_debug(r.GetRegFormat() == IMLRegFormat::I32);
return (uint32)r.GetRegID();
}
static x86Assembler64::GPR32 _reg32(sint8 physRegId)
{
return (x86Assembler64::GPR32)physRegId;
}
static x86Assembler64::GPR8_REX _reg8(IMLReg r)
{
cemu_assert_debug(r.GetRegFormat() == IMLRegFormat::I32); // currently bool regs are implemented as 32bit registers
return (x86Assembler64::GPR8_REX)r.GetRegID();
}
static x86Assembler64::GPR32 _reg32_from_reg8(x86Assembler64::GPR8_REX regId)
{
return (x86Assembler64::GPR32)regId;
}
static x86Assembler64::GPR8_REX _reg8_from_reg32(x86Assembler64::GPR32 regId)
{
return (x86Assembler64::GPR8_REX)regId;
}
// load from memory
bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed)
{
sint32 realRegisterXMM = _regF64(imlInstruction->op_storeLoad.registerData);
sint32 realRegisterMem = _regI32(imlInstruction->op_storeLoad.registerMem);
sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER;
if( indexed )
realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2);
uint8 mode = imlInstruction->op_storeLoad.mode;
if( mode == PPCREC_FPR_LD_MODE_SINGLE )
{
// load byte swapped single into temporary FPR
if( indexed )
{
x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2);
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem);
if(g_CPUFeatures.x86.movbe)
x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32);
else
x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32);
}
else
{
if(g_CPUFeatures.x86.movbe)
x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32);
else
x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32);
}
if(g_CPUFeatures.x86.movbe == false )
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Gen_movd_xmmReg_reg64Low32(x64GenContext, realRegisterXMM, REG_RESV_TEMP);
if (imlInstruction->op_storeLoad.flags2.notExpanded)
{
// leave value as single
}
else
{
x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, realRegisterXMM);
}
}
else if( mode == PPCREC_FPR_LD_MODE_DOUBLE )
{
if( g_CPUFeatures.x86.avx )
{
if( indexed )
{
// calculate offset
x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem);
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2);
// load value
x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0);
x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP);
x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP);
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP);
}
else
{
x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0);
x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP);
x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP);
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP);
}
}
else
{
if( indexed )
{
// calculate offset
x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem);
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2);
// load double low part to temporaryFPR
x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP);
// calculate offset again
x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem);
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2);
// load double high part to temporaryFPR
x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP);
// load double from temporaryFPR
x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR));
}
else
{
// load double low part to temporaryFPR
x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP);
// load double high part to temporaryFPR
x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP);
// load double from temporaryFPR
x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR));
}
}
}
else
{
return false;
}
return true;
}
// store to memory
bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed)
{
sint32 realRegisterXMM = _regF64(imlInstruction->op_storeLoad.registerData);
sint32 realRegisterMem = _regI32(imlInstruction->op_storeLoad.registerMem);
sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER;
if( indexed )
realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2);
uint8 mode = imlInstruction->op_storeLoad.mode;
if( mode == PPCREC_FPR_ST_MODE_SINGLE )
{
if (imlInstruction->op_storeLoad.flags2.notExpanded)
{
// value is already in single format
x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM);
}
else
{
x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, realRegisterXMM);
x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP);
}
if(g_CPUFeatures.x86.movbe == false )
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
if( indexed )
{
if( realRegisterMem == realRegisterMem2 )
assert_dbg();
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2);
}
if(g_CPUFeatures.x86.movbe)
x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP);
else
x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP);
if( indexed )
{
x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2);
}
}
else if( mode == PPCREC_FPR_ST_MODE_DOUBLE )
{
if( indexed )
{
if( realRegisterMem == realRegisterMem2 )
assert_dbg();
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2);
}
x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR));
// store double low part
x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP);
// store double high part
x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP);
if( indexed )
{
x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2);
}
}
else if( mode == PPCREC_FPR_ST_MODE_UI32_FROM_PS0 )
{
x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM);
x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP);
if( indexed )
{
cemu_assert_debug(realRegisterMem == realRegisterMem2);
x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2);
x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP);
x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2);
}
else
{
x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP);
}
}
else
{
debug_printf("PPCRecompilerX64Gen_imlInstruction_fpr_store(): Unsupported mode %d\n", mode);
return false;
}
return true;
}
// FPR op FPR
void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction)
{
if( imlInstruction->operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT )
{
uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regR);
uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regA);
x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, regGpr, regFpr);
return;
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT )
{
uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regR);
uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regA);
x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext, regFpr, regGpr);
return;
}
else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT)
{
cemu_assert_debug(imlInstruction->op_fpr_r_r.regR.GetRegFormat() == IMLRegFormat::F64); // assuming target is always F64 for now
cemu_assert_debug(imlInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::I32); // supporting only 32bit floats as input for now
// exact operation depends on size of types. Floats are automatically promoted to double if the target is F64
uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regR);
if (imlInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::I32)
{
uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regA);
x64Gen_movq_xmmReg_reg64(x64GenContext, regFpr, regGpr); // using reg32 as reg64 param here is ok. We'll refactor later
// float to double
x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regFpr, regFpr);
}
else
{
cemu_assert_unimplemented();
}
return;
}
uint32 regR = _regF64(imlInstruction->op_fpr_r_r.regR);
uint32 regA = _regF64(imlInstruction->op_fpr_r_r.regA);
if( imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN )
{
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY )
{
x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE )
{
x64Gen_divsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD )
{
x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB )
{
x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_FCTIWZ )
{
x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext, REG_RESV_TEMP, regA);
x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP);
// move to FPR register
x64Gen_movq_xmmReg_reg64(x64GenContext, regR, REG_RESV_TEMP);
}
else
{
assert_dbg();
}
}
/*
* FPR = op (fprA, fprB)
*/
void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction)
{
uint32 regR = _regF64(imlInstruction->op_fpr_r_r_r.regR);
uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r.regA);
uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r.regB);
if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY)
{
if (regR == regA)
{
x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB);
}
else if (regR == regB)
{
x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else
{
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA);
x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB);
}
}
else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD)
{
// todo: Use AVX 3-operand VADDSD if available
if (regR == regA)
{
x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB);
}
else if (regR == regB)
{
x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA);
}
else
{
x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA);
x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB);
}
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB )
{
if( regR == regA )
{
x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regB);
}
else if( regR == regB )
{
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA);
x64Gen_subsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB);
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP);
}
else
{
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA);
x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regB);
}
}
else
assert_dbg();
}
/*
* FPR = op (fprA, fprB, fprC)
*/
void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction)
{
uint32 regR = _regF64(imlInstruction->op_fpr_r_r_r_r.regR);
uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r_r.regA);
uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r_r.regB);
uint32 regC = _regF64(imlInstruction->op_fpr_r_r_r_r.regC);
if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT )
{
x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, regA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0));
sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex();
x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0);
// select C
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regC);
sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex();
x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0);
// select B
PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex());
x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regB);
// end
PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex());
}
else
assert_dbg();
}
void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction)
{
uint32 regR = _regF64(imlInstruction->op_fpr_r.regR);
if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE )
{
x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom));
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_LOAD_ONE )
{
x64Gen_movsd_xmmReg_memReg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble1_1));
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS )
{
x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskBottom));
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS )
{
x64Gen_orps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom));
}
else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM )
{
// convert to 32bit single
x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, regR, regR);
// convert back to 64bit double
x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR);
}
else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64)
{
// convert bottom to 64bit double
x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR);
}
else
{
cemu_assert_unimplemented();
}
}
void PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction)
{
auto regR = _reg8(imlInstruction->op_fpr_compare.regR);
auto regA = _regF64(imlInstruction->op_fpr_compare.regA);
auto regB = _regF64(imlInstruction->op_fpr_compare.regB);
x64GenContext->emitter->XOR_dd(_reg32_from_reg8(regR), _reg32_from_reg8(regR));
x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, regA, regB);
if (imlInstruction->op_fpr_compare.cond == IMLCondition::UNORDERED_GT)
{
// GT case can be covered with a single SETnbe which checks CF==0 && ZF==0 (unordered sets both)
x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_NBE, regR);
return;
}
else if (imlInstruction->op_fpr_compare.cond == IMLCondition::UNORDERED_U)
{
// unordered case can be checked via PF
x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_PE, regR);
return;
}
// remember unordered state
auto regTmp = _reg32_from_reg8(_reg32(REG_RESV_TEMP));
x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_PO, regTmp); // by reversing the parity we can avoid having to XOR the value for masking the LT/EQ conditions
X86Cond x86Cond;
switch (imlInstruction->op_fpr_compare.cond)
{
case IMLCondition::UNORDERED_LT:
x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_B, regR);
break;
case IMLCondition::UNORDERED_EQ:
x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_Z, regR);
break;
default:
cemu_assert_unimplemented();
}
x64GenContext->emitter->AND_bb(_reg8_from_reg32(regR), _reg8_from_reg32(regTmp)); // if unordered (PF=1) then force LT/GT/EQ to zero
}

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
#pragma once
#include "IMLInstruction.h"
#include "IMLSegment.h"
// optimizer passes
void IMLOptimizer_OptimizeDirectFloatCopies(struct ppcImlGenContext_t* ppcImlGenContext);
void IMLOptimizer_OptimizeDirectIntegerCopies(struct ppcImlGenContext_t* ppcImlGenContext);
void PPCRecompiler_optimizePSQLoadAndStore(struct ppcImlGenContext_t* ppcImlGenContext);
void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext);
// debug
void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut);
void IMLDebug_DumpSegment(struct ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo = false);
void IMLDebug_Dump(struct ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo = false);

View file

@ -1,5 +0,0 @@
#include "IML.h"
//#include "PPCRecompilerIml.h"
#include "util/helpers/fixedSizeList.h"
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"

View file

@ -1,561 +0,0 @@
#include "IML.h"
#include "IMLInstruction.h"
#include "IMLSegment.h"
#include "IMLRegisterAllocatorRanges.h"
#include "util/helpers/StringBuf.h"
#include "../PPCRecompiler.h"
const char* IMLDebug_GetOpcodeName(const IMLInstruction* iml)
{
static char _tempOpcodename[32];
uint32 op = iml->operation;
if (op == PPCREC_IML_OP_ASSIGN)
return "MOV";
else if (op == PPCREC_IML_OP_ADD)
return "ADD";
else if (op == PPCREC_IML_OP_ADD_WITH_CARRY)
return "ADC";
else if (op == PPCREC_IML_OP_SUB)
return "SUB";
else if (op == PPCREC_IML_OP_OR)
return "OR";
else if (op == PPCREC_IML_OP_AND)
return "AND";
else if (op == PPCREC_IML_OP_XOR)
return "XOR";
else if (op == PPCREC_IML_OP_LEFT_SHIFT)
return "LSH";
else if (op == PPCREC_IML_OP_RIGHT_SHIFT_U)
return "RSH";
else if (op == PPCREC_IML_OP_RIGHT_SHIFT_S)
return "ARSH";
else if (op == PPCREC_IML_OP_LEFT_ROTATE)
return "LROT";
else if (op == PPCREC_IML_OP_MULTIPLY_SIGNED)
return "MULS";
else if (op == PPCREC_IML_OP_DIVIDE_SIGNED)
return "DIVS";
else if (op == PPCREC_IML_OP_FPR_ASSIGN)
return "FMOV";
else if (op == PPCREC_IML_OP_FPR_ADD)
return "FADD";
else if (op == PPCREC_IML_OP_FPR_SUB)
return "FSUB";
else if (op == PPCREC_IML_OP_FPR_MULTIPLY)
return "FMUL";
else if (op == PPCREC_IML_OP_FPR_DIVIDE)
return "FDIV";
else if (op == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64)
return "F32TOF64";
else if (op == PPCREC_IML_OP_FPR_ABS)
return "FABS";
else if (op == PPCREC_IML_OP_FPR_NEGATE)
return "FNEG";
else if (op == PPCREC_IML_OP_FPR_NEGATIVE_ABS)
return "FNABS";
else if (op == PPCREC_IML_OP_FPR_FLOAT_TO_INT)
return "F2I";
else if (op == PPCREC_IML_OP_FPR_INT_TO_FLOAT)
return "I2F";
else if (op == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT)
return "BITMOVE";
sprintf(_tempOpcodename, "OP0%02x_T%d", iml->operation, iml->type);
return _tempOpcodename;
}
std::string IMLDebug_GetRegName(IMLReg r)
{
std::string regName;
uint32 regId = r.GetRegID();
switch (r.GetRegFormat())
{
case IMLRegFormat::F32:
regName.append("f");
break;
case IMLRegFormat::F64:
regName.append("fd");
break;
case IMLRegFormat::I32:
regName.append("i");
break;
case IMLRegFormat::I64:
regName.append("r");
break;
default:
DEBUG_BREAK;
}
regName.append(fmt::format("{}", regId));
return regName;
}
void IMLDebug_AppendRegisterParam(StringBuf& strOutput, IMLReg virtualRegister, bool isLast = false)
{
strOutput.add(IMLDebug_GetRegName(virtualRegister));
if (!isLast)
strOutput.add(", ");
}
void IMLDebug_AppendS32Param(StringBuf& strOutput, sint32 val, bool isLast = false)
{
if (val < 0)
{
strOutput.add("-");
val = -val;
}
strOutput.addFmt("0x{:08x}", val);
if (!isLast)
strOutput.add(", ");
}
void IMLDebug_PrintLivenessRangeInfo(StringBuf& currentLineText, IMLSegment* imlSegment, sint32 offset)
{
// pad to 70 characters
sint32 index = currentLineText.getLen();
while (index < 70)
{
currentLineText.add(" ");
index++;
}
raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges;
while (subrangeItr)
{
if (subrangeItr->interval.start.GetInstructionIndexEx() == offset)
{
if(subrangeItr->interval.start.IsInstructionIndex() && !subrangeItr->interval.start.IsOnInputEdge())
currentLineText.add(".");
else
currentLineText.add("|");
currentLineText.addFmt("{:<4}", subrangeItr->GetVirtualRegister());
}
else if (subrangeItr->interval.end.GetInstructionIndexEx() == offset)
{
if(subrangeItr->interval.end.IsInstructionIndex() && !subrangeItr->interval.end.IsOnOutputEdge())
currentLineText.add("* ");
else
currentLineText.add("| ");
}
else if (subrangeItr->interval.ContainsInstructionIndexEx(offset))
{
currentLineText.add("| ");
}
else
{
currentLineText.add(" ");
}
index += 5;
// next
subrangeItr = subrangeItr->link_allSegmentRanges.next;
}
}
std::string IMLDebug_GetSegmentName(ppcImlGenContext_t* ctx, IMLSegment* seg)
{
if (!ctx)
{
return "<NoNameWithoutCtx>";
}
// find segment index
for (size_t i = 0; i < ctx->segmentList2.size(); i++)
{
if (ctx->segmentList2[i] == seg)
{
return fmt::format("Seg{:04x}", i);
}
}
return "<SegmentNotInCtx>";
}
std::string IMLDebug_GetConditionName(IMLCondition cond)
{
switch (cond)
{
case IMLCondition::EQ:
return "EQ";
case IMLCondition::NEQ:
return "NEQ";
case IMLCondition::UNSIGNED_GT:
return "UGT";
case IMLCondition::UNSIGNED_LT:
return "ULT";
case IMLCondition::SIGNED_GT:
return "SGT";
case IMLCondition::SIGNED_LT:
return "SLT";
default:
cemu_assert_unimplemented();
}
return "ukn";
}
void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut)
{
const sint32 lineOffsetParameters = 10;//18;
StringBuf strOutput(1024);
strOutput.reset();
if (inst.type == PPCREC_IML_TYPE_R_NAME || inst.type == PPCREC_IML_TYPE_NAME_R)
{
if (inst.type == PPCREC_IML_TYPE_R_NAME)
strOutput.add("R_NAME");
else
strOutput.add("NAME_R");
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
if(inst.type == PPCREC_IML_TYPE_R_NAME)
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR);
strOutput.add("name_");
if (inst.op_r_name.name >= PPCREC_NAME_R0 && inst.op_r_name.name < (PPCREC_NAME_R0 + 999))
{
strOutput.addFmt("r{}", inst.op_r_name.name - PPCREC_NAME_R0);
}
if (inst.op_r_name.name >= PPCREC_NAME_FPR_HALF && inst.op_r_name.name < (PPCREC_NAME_FPR_HALF + 32*2))
{
strOutput.addFmt("f{}", inst.op_r_name.name - ((PPCREC_NAME_FPR_HALF - inst.op_r_name.name)/2));
if ((inst.op_r_name.name-PPCREC_NAME_FPR_HALF)&1)
strOutput.add(".ps1");
else
strOutput.add(".ps0");
}
else if (inst.op_r_name.name >= PPCREC_NAME_SPR0 && inst.op_r_name.name < (PPCREC_NAME_SPR0 + 999))
{
strOutput.addFmt("spr{}", inst.op_r_name.name - PPCREC_NAME_SPR0);
}
else if (inst.op_r_name.name >= PPCREC_NAME_CR && inst.op_r_name.name <= PPCREC_NAME_CR_LAST)
strOutput.addFmt("cr{}", inst.op_r_name.name - PPCREC_NAME_CR);
else if (inst.op_r_name.name == PPCREC_NAME_XER_CA)
strOutput.add("xer.ca");
else if (inst.op_r_name.name == PPCREC_NAME_XER_SO)
strOutput.add("xer.so");
else if (inst.op_r_name.name == PPCREC_NAME_XER_OV)
strOutput.add("xer.ov");
else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_EA)
strOutput.add("cpuReservation.ea");
else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_VAL)
strOutput.add("cpuReservation.value");
else
{
strOutput.addFmt("name_ukn{}", inst.op_r_name.name);
}
if (inst.type != PPCREC_IML_TYPE_R_NAME)
{
strOutput.add(", ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR, true);
}
}
else if (inst.type == PPCREC_IML_TYPE_R_R)
{
strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst));
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regR);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regA, true);
}
else if (inst.type == PPCREC_IML_TYPE_R_R_R)
{
strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst));
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regR);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regA);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regB, true);
}
else if (inst.type == PPCREC_IML_TYPE_R_R_R_CARRY)
{
strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst));
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regR);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regA);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regB);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regCarry, true);
}
else if (inst.type == PPCREC_IML_TYPE_COMPARE)
{
strOutput.add("CMP ");
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regA);
IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regB);
strOutput.addFmt("{}", IMLDebug_GetConditionName(inst.op_compare.cond));
strOutput.add(" -> ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regR, true);
}
else if (inst.type == PPCREC_IML_TYPE_COMPARE_S32)
{
strOutput.add("CMP ");
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regA);
strOutput.addFmt("{}", inst.op_compare_s32.immS32);
strOutput.addFmt(", {}", IMLDebug_GetConditionName(inst.op_compare_s32.cond));
strOutput.add(" -> ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regR, true);
}
else if (inst.type == PPCREC_IML_TYPE_CONDITIONAL_JUMP)
{
strOutput.add("CJUMP ");
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_conditional_jump.registerBool, true);
if (!inst.op_conditional_jump.mustBeTrue)
strOutput.add("(inverted)");
}
else if (inst.type == PPCREC_IML_TYPE_JUMP)
{
strOutput.add("JUMP");
}
else if (inst.type == PPCREC_IML_TYPE_R_R_S32)
{
strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst));
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regR);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regA);
IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32.immS32, true);
}
else if (inst.type == PPCREC_IML_TYPE_R_R_S32_CARRY)
{
strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst));
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regR);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regA);
IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32_carry.immS32);
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regCarry, true);
}
else if (inst.type == PPCREC_IML_TYPE_R_S32)
{
strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst));
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_r_immS32.regR);
IMLDebug_AppendS32Param(strOutput, inst.op_r_immS32.immS32, true);
}
else if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_STORE ||
inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED)
{
if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_LOAD_INDEXED)
strOutput.add("LD_");
else
strOutput.add("ST_");
if (inst.op_storeLoad.flags2.signExtend)
strOutput.add("S");
else
strOutput.add("U");
strOutput.addFmt("{}", inst.op_storeLoad.copyWidth);
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_storeLoad.registerData);
if (inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED)
strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), IMLDebug_GetRegName(inst.op_storeLoad.registerMem2));
else
strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32);
}
else if (inst.type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE)
{
strOutput.add("ATOMIC_ST_U32");
while ((sint32)strOutput.getLen() < lineOffsetParameters)
strOutput.add(" ");
IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regEA);
IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regCompareValue);
IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regWriteValue);
IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regBoolOut, true);
}
else if (inst.type == PPCREC_IML_TYPE_NO_OP)
{
strOutput.add("NOP");
}
else if (inst.type == PPCREC_IML_TYPE_MACRO)
{
if (inst.operation == PPCREC_IML_MACRO_B_TO_REG)
{
strOutput.addFmt("MACRO B_TO_REG {}", IMLDebug_GetRegName(inst.op_macro.paramReg));
}
else if (inst.operation == PPCREC_IML_MACRO_BL)
{
strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16);
}
else if (inst.operation == PPCREC_IML_MACRO_B_FAR)
{
strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16);
}
else if (inst.operation == PPCREC_IML_MACRO_LEAVE)
{
strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", inst.op_macro.param);
}
else if (inst.operation == PPCREC_IML_MACRO_HLE)
{
strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", inst.op_macro.param, inst.op_macro.param2);
}
else if (inst.operation == PPCREC_IML_MACRO_COUNT_CYCLES)
{
strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", inst.op_macro.param);
}
else
{
strOutput.addFmt("MACRO ukn operation {}", inst.operation);
}
}
else if (inst.type == PPCREC_IML_TYPE_FPR_LOAD)
{
strOutput.addFmt("{} = ", IMLDebug_GetRegName(inst.op_storeLoad.registerData));
if (inst.op_storeLoad.flags2.signExtend)
strOutput.add("S");
else
strOutput.add("U");
strOutput.addFmt("{} [{}+{}] mode {}", inst.op_storeLoad.copyWidth / 8, IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32, inst.op_storeLoad.mode);
if (inst.op_storeLoad.flags2.notExpanded)
{
strOutput.addFmt(" <No expand>");
}
}
else if (inst.type == PPCREC_IML_TYPE_FPR_STORE)
{
if (inst.op_storeLoad.flags2.signExtend)
strOutput.add("S");
else
strOutput.add("U");
strOutput.addFmt("{} [t{}+{}]", inst.op_storeLoad.copyWidth / 8, inst.op_storeLoad.registerMem.GetRegID(), inst.op_storeLoad.immS32);
strOutput.addFmt(" = {} mode {}", IMLDebug_GetRegName(inst.op_storeLoad.registerData), inst.op_storeLoad.mode);
}
else if (inst.type == PPCREC_IML_TYPE_FPR_R)
{
strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst));
strOutput.addFmt("{}", IMLDebug_GetRegName(inst.op_fpr_r.regR));
}
else if (inst.type == PPCREC_IML_TYPE_FPR_R_R)
{
strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst));
strOutput.addFmt("{}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r.regA));
}
else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R_R)
{
strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst));
strOutput.addFmt("{}, {}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regB), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regC));
}
else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R)
{
strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst));
strOutput.addFmt("{}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regB));
}
else if (inst.type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK)
{
strOutput.addFmt("CYCLE_CHECK");
}
else if (inst.type == PPCREC_IML_TYPE_X86_EFLAGS_JCC)
{
strOutput.addFmt("X86_JCC {}", IMLDebug_GetConditionName(inst.op_x86_eflags_jcc.cond));
}
else
{
strOutput.addFmt("Unknown iml type {}", inst.type);
}
disassemblyLineOut.assign(strOutput.c_str());
}
void IMLDebug_DumpSegment(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo)
{
StringBuf strOutput(4096);
strOutput.addFmt("SEGMENT {} | PPC=0x{:08x} Loop-depth {}", IMLDebug_GetSegmentName(ctx, imlSegment), imlSegment->ppcAddress, imlSegment->loopDepth);
if (imlSegment->isEnterable)
{
strOutput.addFmt(" ENTERABLE (0x{:08x})", imlSegment->enterPPCAddress);
}
if (imlSegment->deadCodeEliminationHintSeg)
{
strOutput.addFmt(" InheritOverwrite: {}", IMLDebug_GetSegmentName(ctx, imlSegment->deadCodeEliminationHintSeg));
}
cemuLog_log(LogType::Force, "{}", strOutput.c_str());
if (printLivenessRangeInfo)
{
strOutput.reset();
IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, RA_INTER_RANGE_START);
cemuLog_log(LogType::Force, "{}", strOutput.c_str());
}
//debug_printf("\n");
strOutput.reset();
std::string disassemblyLine;
for (sint32 i = 0; i < imlSegment->imlList.size(); i++)
{
const IMLInstruction& inst = imlSegment->imlList[i];
// don't log NOP instructions
if (inst.type == PPCREC_IML_TYPE_NO_OP)
continue;
strOutput.reset();
strOutput.addFmt("{:02x} ", i);
//cemuLog_log(LogType::Force, "{:02x} ", i);
disassemblyLine.clear();
IMLDebug_DisassembleInstruction(inst, disassemblyLine);
strOutput.add(disassemblyLine);
if (printLivenessRangeInfo)
{
IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, i);
}
cemuLog_log(LogType::Force, "{}", strOutput.c_str());
}
// all ranges
if (printLivenessRangeInfo)
{
strOutput.reset();
strOutput.add("Ranges-VirtReg ");
raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges;
while (subrangeItr)
{
strOutput.addFmt("v{:<4}", (uint32)subrangeItr->GetVirtualRegister());
subrangeItr = subrangeItr->link_allSegmentRanges.next;
}
cemuLog_log(LogType::Force, "{}", strOutput.c_str());
strOutput.reset();
strOutput.add("Ranges-PhysReg ");
subrangeItr = imlSegment->raInfo.linkedList_allSubranges;
while (subrangeItr)
{
strOutput.addFmt("p{:<4}", subrangeItr->GetPhysicalRegister());
subrangeItr = subrangeItr->link_allSegmentRanges.next;
}
cemuLog_log(LogType::Force, "{}", strOutput.c_str());
}
// branch info
strOutput.reset();
strOutput.add("Links from: ");
for (sint32 i = 0; i < imlSegment->list_prevSegments.size(); i++)
{
if (i)
strOutput.add(", ");
strOutput.addFmt("{}", IMLDebug_GetSegmentName(ctx, imlSegment->list_prevSegments[i]).c_str());
}
cemuLog_log(LogType::Force, "{}", strOutput.c_str());
if (imlSegment->nextSegmentBranchNotTaken)
cemuLog_log(LogType::Force, "BranchNotTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchNotTaken).c_str());
if (imlSegment->nextSegmentBranchTaken)
cemuLog_log(LogType::Force, "BranchTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchTaken).c_str());
if (imlSegment->nextSegmentIsUncertain)
cemuLog_log(LogType::Force, "Dynamic target");
}
void IMLDebug_Dump(ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo)
{
for (size_t i = 0; i < ppcImlGenContext->segmentList2.size(); i++)
{
IMLDebug_DumpSegment(ppcImlGenContext, ppcImlGenContext->segmentList2[i], printLivenessRangeInfo);
cemuLog_log(LogType::Force, "");
}
}

View file

@ -1,536 +0,0 @@
#include "IMLInstruction.h"
#include "IML.h"
#include "../PPCRecompiler.h"
#include "../PPCRecompilerIml.h"
// return true if an instruction has side effects on top of just reading and writing registers
bool IMLInstruction::HasSideEffects() const
{
bool hasSideEffects = true;
if(type == PPCREC_IML_TYPE_R_R || type == PPCREC_IML_TYPE_R_R_S32 || type == PPCREC_IML_TYPE_COMPARE || type == PPCREC_IML_TYPE_COMPARE_S32)
hasSideEffects = false;
// todo - add more cases
return hasSideEffects;
}
void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const
{
registersUsed->readGPR1 = IMLREG_INVALID;
registersUsed->readGPR2 = IMLREG_INVALID;
registersUsed->readGPR3 = IMLREG_INVALID;
registersUsed->readGPR4 = IMLREG_INVALID;
registersUsed->writtenGPR1 = IMLREG_INVALID;
registersUsed->writtenGPR2 = IMLREG_INVALID;
if (type == PPCREC_IML_TYPE_R_NAME)
{
registersUsed->writtenGPR1 = op_r_name.regR;
}
else if (type == PPCREC_IML_TYPE_NAME_R)
{
registersUsed->readGPR1 = op_r_name.regR;
}
else if (type == PPCREC_IML_TYPE_R_R)
{
if (operation == PPCREC_IML_OP_X86_CMP)
{
// both operands are read only
registersUsed->readGPR1 = op_r_r.regR;
registersUsed->readGPR2 = op_r_r.regA;
}
else if (
operation == PPCREC_IML_OP_ASSIGN ||
operation == PPCREC_IML_OP_ENDIAN_SWAP ||
operation == PPCREC_IML_OP_CNTLZW ||
operation == PPCREC_IML_OP_NOT ||
operation == PPCREC_IML_OP_NEG ||
operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 ||
operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32)
{
// result is written, operand is read
registersUsed->writtenGPR1 = op_r_r.regR;
registersUsed->readGPR1 = op_r_r.regA;
}
else
cemu_assert_unimplemented();
}
else if (type == PPCREC_IML_TYPE_R_S32)
{
cemu_assert_debug(operation != PPCREC_IML_OP_ADD &&
operation != PPCREC_IML_OP_SUB &&
operation != PPCREC_IML_OP_AND &&
operation != PPCREC_IML_OP_OR &&
operation != PPCREC_IML_OP_XOR); // deprecated, use r_r_s32 for these
if (operation == PPCREC_IML_OP_LEFT_ROTATE)
{
// register operand is read and write
registersUsed->readGPR1 = op_r_immS32.regR;
registersUsed->writtenGPR1 = op_r_immS32.regR;
}
else if (operation == PPCREC_IML_OP_X86_CMP)
{
// register operand is read only
registersUsed->readGPR1 = op_r_immS32.regR;
}
else
{
// register operand is write only
// todo - use explicit lists, avoid default cases
registersUsed->writtenGPR1 = op_r_immS32.regR;
}
}
else if (type == PPCREC_IML_TYPE_R_R_S32)
{
registersUsed->writtenGPR1 = op_r_r_s32.regR;
registersUsed->readGPR1 = op_r_r_s32.regA;
}
else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY)
{
registersUsed->writtenGPR1 = op_r_r_s32_carry.regR;
registersUsed->readGPR1 = op_r_r_s32_carry.regA;
// some operations read carry
switch (operation)
{
case PPCREC_IML_OP_ADD_WITH_CARRY:
registersUsed->readGPR2 = op_r_r_s32_carry.regCarry;
break;
case PPCREC_IML_OP_ADD:
break;
default:
cemu_assert_unimplemented();
}
// carry is always written
registersUsed->writtenGPR2 = op_r_r_s32_carry.regCarry;
}
else if (type == PPCREC_IML_TYPE_R_R_R)
{
// in all cases result is written and other operands are read only
// with the exception of XOR, where if regA == regB then all bits are zeroed out. So we don't consider it a read
registersUsed->writtenGPR1 = op_r_r_r.regR;
if(!(operation == PPCREC_IML_OP_XOR && op_r_r_r.regA == op_r_r_r.regB))
{
registersUsed->readGPR1 = op_r_r_r.regA;
registersUsed->readGPR2 = op_r_r_r.regB;
}
}
else if (type == PPCREC_IML_TYPE_R_R_R_CARRY)
{
registersUsed->writtenGPR1 = op_r_r_r_carry.regR;
registersUsed->readGPR1 = op_r_r_r_carry.regA;
registersUsed->readGPR2 = op_r_r_r_carry.regB;
// some operations read carry
switch (operation)
{
case PPCREC_IML_OP_ADD_WITH_CARRY:
registersUsed->readGPR3 = op_r_r_r_carry.regCarry;
break;
case PPCREC_IML_OP_ADD:
break;
default:
cemu_assert_unimplemented();
}
// carry is always written
registersUsed->writtenGPR2 = op_r_r_r_carry.regCarry;
}
else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK)
{
// no effect on registers
}
else if (type == PPCREC_IML_TYPE_NO_OP)
{
// no effect on registers
}
else if (type == PPCREC_IML_TYPE_MACRO)
{
if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_COUNT_CYCLES || operation == PPCREC_IML_MACRO_HLE)
{
// no effect on registers
}
else if (operation == PPCREC_IML_MACRO_B_TO_REG)
{
cemu_assert_debug(op_macro.paramReg.IsValid());
registersUsed->readGPR1 = op_macro.paramReg;
}
else
cemu_assert_unimplemented();
}
else if (type == PPCREC_IML_TYPE_COMPARE)
{
registersUsed->readGPR1 = op_compare.regA;
registersUsed->readGPR2 = op_compare.regB;
registersUsed->writtenGPR1 = op_compare.regR;
}
else if (type == PPCREC_IML_TYPE_COMPARE_S32)
{
registersUsed->readGPR1 = op_compare_s32.regA;
registersUsed->writtenGPR1 = op_compare_s32.regR;
}
else if (type == PPCREC_IML_TYPE_CONDITIONAL_JUMP)
{
registersUsed->readGPR1 = op_conditional_jump.registerBool;
}
else if (type == PPCREC_IML_TYPE_JUMP)
{
// no registers affected
}
else if (type == PPCREC_IML_TYPE_LOAD)
{
registersUsed->writtenGPR1 = op_storeLoad.registerData;
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR1 = op_storeLoad.registerMem;
}
else if (type == PPCREC_IML_TYPE_LOAD_INDEXED)
{
registersUsed->writtenGPR1 = op_storeLoad.registerData;
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR1 = op_storeLoad.registerMem;
if (op_storeLoad.registerMem2.IsValid())
registersUsed->readGPR2 = op_storeLoad.registerMem2;
}
else if (type == PPCREC_IML_TYPE_STORE)
{
registersUsed->readGPR1 = op_storeLoad.registerData;
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR2 = op_storeLoad.registerMem;
}
else if (type == PPCREC_IML_TYPE_STORE_INDEXED)
{
registersUsed->readGPR1 = op_storeLoad.registerData;
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR2 = op_storeLoad.registerMem;
if (op_storeLoad.registerMem2.IsValid())
registersUsed->readGPR3 = op_storeLoad.registerMem2;
}
else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE)
{
registersUsed->readGPR1 = op_atomic_compare_store.regEA;
registersUsed->readGPR2 = op_atomic_compare_store.regCompareValue;
registersUsed->readGPR3 = op_atomic_compare_store.regWriteValue;
registersUsed->writtenGPR1 = op_atomic_compare_store.regBoolOut;
}
else if (type == PPCREC_IML_TYPE_CALL_IMM)
{
if (op_call_imm.regParam0.IsValid())
registersUsed->readGPR1 = op_call_imm.regParam0;
if (op_call_imm.regParam1.IsValid())
registersUsed->readGPR2 = op_call_imm.regParam1;
if (op_call_imm.regParam2.IsValid())
registersUsed->readGPR3 = op_call_imm.regParam2;
registersUsed->writtenGPR1 = op_call_imm.regReturn;
}
else if (type == PPCREC_IML_TYPE_FPR_LOAD)
{
// fpr load operation
registersUsed->writtenGPR1 = op_storeLoad.registerData;
// address is in gpr register
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR1 = op_storeLoad.registerMem;
}
else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED)
{
// fpr load operation
registersUsed->writtenGPR1 = op_storeLoad.registerData;
// address is in gpr registers
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR1 = op_storeLoad.registerMem;
if (op_storeLoad.registerMem2.IsValid())
registersUsed->readGPR2 = op_storeLoad.registerMem2;
}
else if (type == PPCREC_IML_TYPE_FPR_STORE)
{
// fpr store operation
registersUsed->readGPR1 = op_storeLoad.registerData;
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR2 = op_storeLoad.registerMem;
}
else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED)
{
// fpr store operation
registersUsed->readGPR1 = op_storeLoad.registerData;
// address is in gpr registers
if (op_storeLoad.registerMem.IsValid())
registersUsed->readGPR2 = op_storeLoad.registerMem;
if (op_storeLoad.registerMem2.IsValid())
registersUsed->readGPR3 = op_storeLoad.registerMem2;
}
else if (type == PPCREC_IML_TYPE_FPR_R_R)
{
// fpr operation
if (
operation == PPCREC_IML_OP_FPR_ASSIGN ||
operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64 ||
operation == PPCREC_IML_OP_FPR_FCTIWZ
)
{
registersUsed->readGPR1 = op_fpr_r_r.regA;
registersUsed->writtenGPR1 = op_fpr_r_r.regR;
}
else if (operation == PPCREC_IML_OP_FPR_MULTIPLY ||
operation == PPCREC_IML_OP_FPR_DIVIDE ||
operation == PPCREC_IML_OP_FPR_ADD ||
operation == PPCREC_IML_OP_FPR_SUB)
{
registersUsed->readGPR1 = op_fpr_r_r.regA;
registersUsed->readGPR2 = op_fpr_r_r.regR;
registersUsed->writtenGPR1 = op_fpr_r_r.regR;
}
else if (operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT ||
operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT ||
operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT)
{
registersUsed->writtenGPR1 = op_fpr_r_r.regR;
registersUsed->readGPR1 = op_fpr_r_r.regA;
}
else
cemu_assert_unimplemented();
}
else if (type == PPCREC_IML_TYPE_FPR_R_R_R)
{
// fpr operation
registersUsed->readGPR1 = op_fpr_r_r_r.regA;
registersUsed->readGPR2 = op_fpr_r_r_r.regB;
registersUsed->writtenGPR1 = op_fpr_r_r_r.regR;
}
else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R)
{
// fpr operation
registersUsed->readGPR1 = op_fpr_r_r_r_r.regA;
registersUsed->readGPR2 = op_fpr_r_r_r_r.regB;
registersUsed->readGPR3 = op_fpr_r_r_r_r.regC;
registersUsed->writtenGPR1 = op_fpr_r_r_r_r.regR;
}
else if (type == PPCREC_IML_TYPE_FPR_R)
{
// fpr operation
if (operation == PPCREC_IML_OP_FPR_NEGATE ||
operation == PPCREC_IML_OP_FPR_ABS ||
operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS ||
operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64 ||
operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM)
{
registersUsed->readGPR1 = op_fpr_r.regR;
registersUsed->writtenGPR1 = op_fpr_r.regR;
}
else if (operation == PPCREC_IML_OP_FPR_LOAD_ONE)
{
registersUsed->writtenGPR1 = op_fpr_r.regR;
}
else
cemu_assert_unimplemented();
}
else if (type == PPCREC_IML_TYPE_FPR_COMPARE)
{
registersUsed->writtenGPR1 = op_fpr_compare.regR;
registersUsed->readGPR1 = op_fpr_compare.regA;
registersUsed->readGPR2 = op_fpr_compare.regB;
}
else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC)
{
// no registers read or written (except for the implicit eflags)
}
else
{
cemu_assert_unimplemented();
}
}
IMLReg replaceRegisterIdMultiple(IMLReg reg, const std::unordered_map<IMLRegID, IMLRegID>& translationTable)
{
if (reg.IsInvalid())
return reg;
const auto& it = translationTable.find(reg.GetRegID());
cemu_assert_debug(it != translationTable.cend());
IMLReg alteredReg = reg;
alteredReg.SetRegID(it->second);
return alteredReg;
}
void IMLInstruction::RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& translationTable)
{
if (type == PPCREC_IML_TYPE_R_NAME)
{
op_r_name.regR = replaceRegisterIdMultiple(op_r_name.regR, translationTable);
}
else if (type == PPCREC_IML_TYPE_NAME_R)
{
op_r_name.regR = replaceRegisterIdMultiple(op_r_name.regR, translationTable);
}
else if (type == PPCREC_IML_TYPE_R_R)
{
op_r_r.regR = replaceRegisterIdMultiple(op_r_r.regR, translationTable);
op_r_r.regA = replaceRegisterIdMultiple(op_r_r.regA, translationTable);
}
else if (type == PPCREC_IML_TYPE_R_S32)
{
op_r_immS32.regR = replaceRegisterIdMultiple(op_r_immS32.regR, translationTable);
}
else if (type == PPCREC_IML_TYPE_R_R_S32)
{
op_r_r_s32.regR = replaceRegisterIdMultiple(op_r_r_s32.regR, translationTable);
op_r_r_s32.regA = replaceRegisterIdMultiple(op_r_r_s32.regA, translationTable);
}
else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY)
{
op_r_r_s32_carry.regR = replaceRegisterIdMultiple(op_r_r_s32_carry.regR, translationTable);
op_r_r_s32_carry.regA = replaceRegisterIdMultiple(op_r_r_s32_carry.regA, translationTable);
op_r_r_s32_carry.regCarry = replaceRegisterIdMultiple(op_r_r_s32_carry.regCarry, translationTable);
}
else if (type == PPCREC_IML_TYPE_R_R_R)
{
op_r_r_r.regR = replaceRegisterIdMultiple(op_r_r_r.regR, translationTable);
op_r_r_r.regA = replaceRegisterIdMultiple(op_r_r_r.regA, translationTable);
op_r_r_r.regB = replaceRegisterIdMultiple(op_r_r_r.regB, translationTable);
}
else if (type == PPCREC_IML_TYPE_R_R_R_CARRY)
{
op_r_r_r_carry.regR = replaceRegisterIdMultiple(op_r_r_r_carry.regR, translationTable);
op_r_r_r_carry.regA = replaceRegisterIdMultiple(op_r_r_r_carry.regA, translationTable);
op_r_r_r_carry.regB = replaceRegisterIdMultiple(op_r_r_r_carry.regB, translationTable);
op_r_r_r_carry.regCarry = replaceRegisterIdMultiple(op_r_r_r_carry.regCarry, translationTable);
}
else if (type == PPCREC_IML_TYPE_COMPARE)
{
op_compare.regR = replaceRegisterIdMultiple(op_compare.regR, translationTable);
op_compare.regA = replaceRegisterIdMultiple(op_compare.regA, translationTable);
op_compare.regB = replaceRegisterIdMultiple(op_compare.regB, translationTable);
}
else if (type == PPCREC_IML_TYPE_COMPARE_S32)
{
op_compare_s32.regR = replaceRegisterIdMultiple(op_compare_s32.regR, translationTable);
op_compare_s32.regA = replaceRegisterIdMultiple(op_compare_s32.regA, translationTable);
}
else if (type == PPCREC_IML_TYPE_CONDITIONAL_JUMP)
{
op_conditional_jump.registerBool = replaceRegisterIdMultiple(op_conditional_jump.registerBool, translationTable);
}
else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK || type == PPCREC_IML_TYPE_JUMP)
{
// no effect on registers
}
else if (type == PPCREC_IML_TYPE_NO_OP)
{
// no effect on registers
}
else if (type == PPCREC_IML_TYPE_MACRO)
{
if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_HLE || operation == PPCREC_IML_MACRO_COUNT_CYCLES)
{
// no effect on registers
}
else if (operation == PPCREC_IML_MACRO_B_TO_REG)
{
op_macro.paramReg = replaceRegisterIdMultiple(op_macro.paramReg, translationTable);
}
else
{
cemu_assert_unimplemented();
}
}
else if (type == PPCREC_IML_TYPE_LOAD)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
if (op_storeLoad.registerMem.IsValid())
{
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
}
}
else if (type == PPCREC_IML_TYPE_LOAD_INDEXED)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
if (op_storeLoad.registerMem.IsValid())
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
if (op_storeLoad.registerMem2.IsValid())
op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable);
}
else if (type == PPCREC_IML_TYPE_STORE)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
if (op_storeLoad.registerMem.IsValid())
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
}
else if (type == PPCREC_IML_TYPE_STORE_INDEXED)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
if (op_storeLoad.registerMem.IsValid())
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
if (op_storeLoad.registerMem2.IsValid())
op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable);
}
else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE)
{
op_atomic_compare_store.regEA = replaceRegisterIdMultiple(op_atomic_compare_store.regEA, translationTable);
op_atomic_compare_store.regCompareValue = replaceRegisterIdMultiple(op_atomic_compare_store.regCompareValue, translationTable);
op_atomic_compare_store.regWriteValue = replaceRegisterIdMultiple(op_atomic_compare_store.regWriteValue, translationTable);
op_atomic_compare_store.regBoolOut = replaceRegisterIdMultiple(op_atomic_compare_store.regBoolOut, translationTable);
}
else if (type == PPCREC_IML_TYPE_CALL_IMM)
{
op_call_imm.regReturn = replaceRegisterIdMultiple(op_call_imm.regReturn, translationTable);
if (op_call_imm.regParam0.IsValid())
op_call_imm.regParam0 = replaceRegisterIdMultiple(op_call_imm.regParam0, translationTable);
if (op_call_imm.regParam1.IsValid())
op_call_imm.regParam1 = replaceRegisterIdMultiple(op_call_imm.regParam1, translationTable);
if (op_call_imm.regParam2.IsValid())
op_call_imm.regParam2 = replaceRegisterIdMultiple(op_call_imm.regParam2, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_LOAD)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_STORE)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED)
{
op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable);
op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable);
op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_R)
{
op_fpr_r.regR = replaceRegisterIdMultiple(op_fpr_r.regR, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_R_R)
{
op_fpr_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r.regR, translationTable);
op_fpr_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r.regA, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_R_R_R)
{
op_fpr_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r.regR, translationTable);
op_fpr_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r.regA, translationTable);
op_fpr_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r.regB, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R)
{
op_fpr_r_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regR, translationTable);
op_fpr_r_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regA, translationTable);
op_fpr_r_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regB, translationTable);
op_fpr_r_r_r_r.regC = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regC, translationTable);
}
else if (type == PPCREC_IML_TYPE_FPR_COMPARE)
{
op_fpr_compare.regA = replaceRegisterIdMultiple(op_fpr_compare.regA, translationTable);
op_fpr_compare.regB = replaceRegisterIdMultiple(op_fpr_compare.regB, translationTable);
op_fpr_compare.regR = replaceRegisterIdMultiple(op_fpr_compare.regR, translationTable);
}
else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC)
{
// no registers read or written (except for the implicit eflags)
}
else
{
cemu_assert_unimplemented();
}
}

View file

@ -1,826 +0,0 @@
#pragma once
using IMLRegID = uint16; // 16 bit ID
using IMLPhysReg = sint32; // arbitrary value that is up to the architecture backend, usually this will be the register index. A value of -1 is reserved and means not assigned
// format of IMLReg:
// 0-15 (16 bit) IMLRegID
// 19-23 (5 bit) Offset In elements, for SIMD registers
// 24-27 (4 bit) IMLRegFormat RegFormat
// 28-31 (4 bit) IMLRegFormat BaseFormat
enum class IMLRegFormat : uint8
{
INVALID_FORMAT,
I64,
I32,
I16,
I8,
// I1 ?
F64,
F32,
TYPE_COUNT,
};
class IMLReg
{
public:
IMLReg()
{
m_raw = 0; // 0 is invalid
}
IMLReg(IMLRegFormat baseRegFormat, IMLRegFormat regFormat, uint8 viewOffset, IMLRegID regId)
{
m_raw = 0;
m_raw |= ((uint8)baseRegFormat << 28);
m_raw |= ((uint8)regFormat << 24);
m_raw |= (uint32)regId;
}
IMLReg(IMLReg&& baseReg, IMLRegFormat viewFormat, uint8 viewOffset, IMLRegID regId)
{
DEBUG_BREAK;
//m_raw = 0;
//m_raw |= ((uint8)baseRegFormat << 28);
//m_raw |= ((uint8)viewFormat << 24);
//m_raw |= (uint32)regId;
}
IMLReg(const IMLReg& other) : m_raw(other.m_raw) {}
IMLRegFormat GetBaseFormat() const
{
return (IMLRegFormat)((m_raw >> 28) & 0xF);
}
IMLRegFormat GetRegFormat() const
{
return (IMLRegFormat)((m_raw >> 24) & 0xF);
}
IMLRegID GetRegID() const
{
cemu_assert_debug(GetBaseFormat() != IMLRegFormat::INVALID_FORMAT);
cemu_assert_debug(GetRegFormat() != IMLRegFormat::INVALID_FORMAT);
return (IMLRegID)(m_raw & 0xFFFF);
}
void SetRegID(IMLRegID regId)
{
cemu_assert_debug(regId <= 0xFFFF);
m_raw &= ~0xFFFF;
m_raw |= (uint32)regId;
}
bool IsInvalid() const
{
return GetBaseFormat() == IMLRegFormat::INVALID_FORMAT;
}
bool IsValid() const
{
return GetBaseFormat() != IMLRegFormat::INVALID_FORMAT;
}
bool IsValidAndSameRegID(IMLRegID regId) const
{
return IsValid() && GetRegID() == regId;
}
// compare all fields
bool operator==(const IMLReg& other) const
{
return m_raw == other.m_raw;
}
private:
uint32 m_raw;
};
static const IMLReg IMLREG_INVALID(IMLRegFormat::INVALID_FORMAT, IMLRegFormat::INVALID_FORMAT, 0, 0);
static const IMLRegID IMLRegID_INVALID(0xFFFF);
using IMLName = uint32;
enum
{
PPCREC_IML_OP_ASSIGN, // '=' operator
PPCREC_IML_OP_ENDIAN_SWAP, // '=' operator with 32bit endian swap
PPCREC_IML_OP_MULTIPLY_SIGNED, // '*' operator (signed multiply)
PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, // unsigned 64bit multiply, store only high 32bit-word of result
PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, // signed 64bit multiply, store only high 32bit-word of result
PPCREC_IML_OP_DIVIDE_SIGNED, // '/' operator (signed divide)
PPCREC_IML_OP_DIVIDE_UNSIGNED, // '/' operator (unsigned divide)
// binary operation
PPCREC_IML_OP_OR, // '|' operator
PPCREC_IML_OP_AND, // '&' operator
PPCREC_IML_OP_XOR, // '^' operator
PPCREC_IML_OP_LEFT_ROTATE, // left rotate operator
PPCREC_IML_OP_LEFT_SHIFT, // shift left operator
PPCREC_IML_OP_RIGHT_SHIFT_U, // right shift operator (unsigned)
PPCREC_IML_OP_RIGHT_SHIFT_S, // right shift operator (signed)
// ppc
PPCREC_IML_OP_SLW, // SLW (shift based on register by up to 63 bits)
PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits)
PPCREC_IML_OP_CNTLZW,
// FPU
PPCREC_IML_OP_FPR_ASSIGN,
PPCREC_IML_OP_FPR_LOAD_ONE, // load constant 1.0 into register
PPCREC_IML_OP_FPR_ADD,
PPCREC_IML_OP_FPR_SUB,
PPCREC_IML_OP_FPR_MULTIPLY,
PPCREC_IML_OP_FPR_DIVIDE,
PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, // expand f32 to f64 in-place
PPCREC_IML_OP_FPR_NEGATE,
PPCREC_IML_OP_FPR_ABS, // abs(fpr)
PPCREC_IML_OP_FPR_NEGATIVE_ABS, // -abs(fpr)
PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, // round 64bit double to 64bit double with 32bit float precision (in bottom half of xmm register)
PPCREC_IML_OP_FPR_FCTIWZ,
PPCREC_IML_OP_FPR_SELECT, // selectively copy bottom value from operand B or C based on value in operand A
// Conversion (FPR_R_R)
PPCREC_IML_OP_FPR_INT_TO_FLOAT, // convert integer value in gpr to floating point value in fpr
PPCREC_IML_OP_FPR_FLOAT_TO_INT, // convert floating point value in fpr to integer value in gpr
// Bitcast (FPR_R_R)
PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT,
// R_R_R + R_R_S32
PPCREC_IML_OP_ADD, // also R_R_R_CARRY
PPCREC_IML_OP_SUB,
// R_R only
PPCREC_IML_OP_NOT,
PPCREC_IML_OP_NEG,
PPCREC_IML_OP_ASSIGN_S16_TO_S32,
PPCREC_IML_OP_ASSIGN_S8_TO_S32,
// R_R_R_carry
PPCREC_IML_OP_ADD_WITH_CARRY, // similar to ADD but also adds carry bit (0 or 1)
// X86 extension
PPCREC_IML_OP_X86_CMP, // R_R and R_S32
PPCREC_IML_OP_INVALID
};
#define PPCREC_IML_OP_FPR_COPY_PAIR (PPCREC_IML_OP_ASSIGN)
enum
{
PPCREC_IML_MACRO_B_TO_REG, // branch to PPC address in register (used for BCCTR, BCLR)
PPCREC_IML_MACRO_BL, // call to different function (can be within same function)
PPCREC_IML_MACRO_B_FAR, // branch to different function
PPCREC_IML_MACRO_COUNT_CYCLES, // decrease current remaining thread cycles by a certain amount
PPCREC_IML_MACRO_HLE, // HLE function call
PPCREC_IML_MACRO_LEAVE, // leaves recompiler and switches to interpeter
// debugging
PPCREC_IML_MACRO_DEBUGBREAK, // throws a debugbreak
};
enum class IMLCondition : uint8
{
EQ,
NEQ,
SIGNED_GT,
SIGNED_LT,
UNSIGNED_GT,
UNSIGNED_LT,
// floating point conditions
UNORDERED_GT, // a > b, false if either is NaN
UNORDERED_LT, // a < b, false if either is NaN
UNORDERED_EQ, // a == b, false if either is NaN
UNORDERED_U, // unordered (true if either operand is NaN)
ORDERED_GT,
ORDERED_LT,
ORDERED_EQ,
ORDERED_U
};
enum
{
PPCREC_IML_TYPE_NONE,
PPCREC_IML_TYPE_NO_OP, // no-op instruction
PPCREC_IML_TYPE_R_R, // r* = (op) *r (can also be r* (op) *r)
PPCREC_IML_TYPE_R_R_R, // r* = r* (op) r*
PPCREC_IML_TYPE_R_R_R_CARRY, // r* = r* (op) r* (reads and/or updates carry)
PPCREC_IML_TYPE_R_R_S32, // r* = r* (op) s32*
PPCREC_IML_TYPE_R_R_S32_CARRY, // r* = r* (op) s32* (reads and/or updates carry)
PPCREC_IML_TYPE_LOAD, // r* = [r*+s32*]
PPCREC_IML_TYPE_LOAD_INDEXED, // r* = [r*+r*]
PPCREC_IML_TYPE_STORE, // [r*+s32*] = r*
PPCREC_IML_TYPE_STORE_INDEXED, // [r*+r*] = r*
PPCREC_IML_TYPE_R_NAME, // r* = name
PPCREC_IML_TYPE_NAME_R, // name* = r*
PPCREC_IML_TYPE_R_S32, // r* (op) imm
PPCREC_IML_TYPE_MACRO,
PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK, // jumps only if remaining thread cycles < 0
// conditions and branches
PPCREC_IML_TYPE_COMPARE, // r* = r* CMP[cond] r*
PPCREC_IML_TYPE_COMPARE_S32, // r* = r* CMP[cond] imm
PPCREC_IML_TYPE_JUMP, // jump always
PPCREC_IML_TYPE_CONDITIONAL_JUMP, // jump conditionally based on boolean value in register
// atomic
PPCREC_IML_TYPE_ATOMIC_CMP_STORE,
// function call
PPCREC_IML_TYPE_CALL_IMM, // call to fixed immediate address
// FPR
PPCREC_IML_TYPE_FPR_LOAD, // r* = (bitdepth) [r*+s32*] (single or paired single mode)
PPCREC_IML_TYPE_FPR_LOAD_INDEXED, // r* = (bitdepth) [r*+r*] (single or paired single mode)
PPCREC_IML_TYPE_FPR_STORE, // (bitdepth) [r*+s32*] = r* (single or paired single mode)
PPCREC_IML_TYPE_FPR_STORE_INDEXED, // (bitdepth) [r*+r*] = r* (single or paired single mode)
PPCREC_IML_TYPE_FPR_R_R,
PPCREC_IML_TYPE_FPR_R_R_R,
PPCREC_IML_TYPE_FPR_R_R_R_R,
PPCREC_IML_TYPE_FPR_R,
PPCREC_IML_TYPE_FPR_COMPARE, // r* = r* CMP[cond] r*
// X86 specific
PPCREC_IML_TYPE_X86_EFLAGS_JCC,
};
enum // IMLName
{
PPCREC_NAME_NONE,
PPCREC_NAME_TEMPORARY = 1000,
PPCREC_NAME_R0 = 2000,
PPCREC_NAME_SPR0 = 3000,
PPCREC_NAME_FPR_HALF = 4800, // Counts PS0 and PS1 separately. E.g. fp3.ps1 is at offset 3 * 2 + 1
PPCREC_NAME_TEMPORARY_FPR0 = 5000, // 0 to 7
PPCREC_NAME_XER_CA = 6000, // carry bit from XER
PPCREC_NAME_XER_OV = 6001, // overflow bit from XER
PPCREC_NAME_XER_SO = 6002, // summary overflow bit from XER
PPCREC_NAME_CR = 7000, // CR register bits (31 to 0)
PPCREC_NAME_CR_LAST = PPCREC_NAME_CR+31,
PPCREC_NAME_CPU_MEMRES_EA = 8000,
PPCREC_NAME_CPU_MEMRES_VAL = 8001
};
#define PPC_REC_INVALID_REGISTER 0xFF // deprecated. Use IMLREG_INVALID instead
enum
{
// fpr load
PPCREC_FPR_LD_MODE_SINGLE,
PPCREC_FPR_LD_MODE_DOUBLE,
// fpr store
PPCREC_FPR_ST_MODE_SINGLE,
PPCREC_FPR_ST_MODE_DOUBLE,
PPCREC_FPR_ST_MODE_UI32_FROM_PS0, // store raw low-32bit of PS0
};
struct IMLUsedRegisters
{
IMLUsedRegisters() {};
bool IsWrittenByRegId(IMLRegID regId) const
{
if (writtenGPR1.IsValid() && writtenGPR1.GetRegID() == regId)
return true;
if (writtenGPR2.IsValid() && writtenGPR2.GetRegID() == regId)
return true;
return false;
}
bool IsBaseGPRWritten(IMLReg imlReg) const
{
cemu_assert_debug(imlReg.IsValid());
auto regId = imlReg.GetRegID();
return IsWrittenByRegId(regId);
}
template<typename Fn>
void ForEachWrittenGPR(Fn F) const
{
if (writtenGPR1.IsValid())
F(writtenGPR1);
if (writtenGPR2.IsValid())
F(writtenGPR2);
}
template<typename Fn>
void ForEachReadGPR(Fn F) const
{
if (readGPR1.IsValid())
F(readGPR1);
if (readGPR2.IsValid())
F(readGPR2);
if (readGPR3.IsValid())
F(readGPR3);
if (readGPR4.IsValid())
F(readGPR4);
}
template<typename Fn>
void ForEachAccessedGPR(Fn F) const
{
// GPRs
if (readGPR1.IsValid())
F(readGPR1, false);
if (readGPR2.IsValid())
F(readGPR2, false);
if (readGPR3.IsValid())
F(readGPR3, false);
if (readGPR4.IsValid())
F(readGPR4, false);
if (writtenGPR1.IsValid())
F(writtenGPR1, true);
if (writtenGPR2.IsValid())
F(writtenGPR2, true);
}
IMLReg readGPR1;
IMLReg readGPR2;
IMLReg readGPR3;
IMLReg readGPR4;
IMLReg writtenGPR1;
IMLReg writtenGPR2;
};
struct IMLInstruction
{
IMLInstruction() {}
IMLInstruction(const IMLInstruction& other)
{
memcpy(this, &other, sizeof(IMLInstruction));
}
uint8 type;
uint8 operation;
union
{
struct
{
uint8 _padding[7];
}padding;
struct
{
IMLReg regR;
IMLReg regA;
}op_r_r;
struct
{
IMLReg regR;
IMLReg regA;
IMLReg regB;
}op_r_r_r;
struct
{
IMLReg regR;
IMLReg regA;
IMLReg regB;
IMLReg regCarry;
}op_r_r_r_carry;
struct
{
IMLReg regR;
IMLReg regA;
sint32 immS32;
}op_r_r_s32;
struct
{
IMLReg regR;
IMLReg regA;
IMLReg regCarry;
sint32 immS32;
}op_r_r_s32_carry;
struct
{
IMLReg regR;
IMLName name;
}op_r_name; // alias op_name_r
struct
{
IMLReg regR;
sint32 immS32;
}op_r_immS32;
struct
{
uint32 param;
uint32 param2;
uint16 paramU16;
IMLReg paramReg;
}op_macro;
struct
{
IMLReg registerData;
IMLReg registerMem;
IMLReg registerMem2;
uint8 copyWidth;
struct
{
bool swapEndian : 1;
bool signExtend : 1;
bool notExpanded : 1; // for floats
}flags2;
uint8 mode; // transfer mode
sint32 immS32;
}op_storeLoad;
struct
{
uintptr_t callAddress;
IMLReg regParam0;
IMLReg regParam1;
IMLReg regParam2;
IMLReg regReturn;
}op_call_imm;
struct
{
IMLReg regR;
IMLReg regA;
}op_fpr_r_r;
struct
{
IMLReg regR;
IMLReg regA;
IMLReg regB;
}op_fpr_r_r_r;
struct
{
IMLReg regR;
IMLReg regA;
IMLReg regB;
IMLReg regC;
}op_fpr_r_r_r_r;
struct
{
IMLReg regR;
}op_fpr_r;
struct
{
IMLReg regR; // stores the boolean result of the comparison
IMLReg regA;
IMLReg regB;
IMLCondition cond;
}op_fpr_compare;
struct
{
IMLReg regR; // stores the boolean result of the comparison
IMLReg regA;
IMLReg regB;
IMLCondition cond;
}op_compare;
struct
{
IMLReg regR; // stores the boolean result of the comparison
IMLReg regA;
sint32 immS32;
IMLCondition cond;
}op_compare_s32;
struct
{
IMLReg registerBool;
bool mustBeTrue;
}op_conditional_jump;
struct
{
IMLReg regEA;
IMLReg regCompareValue;
IMLReg regWriteValue;
IMLReg regBoolOut;
}op_atomic_compare_store;
// conditional operations (emitted if supported by target platform)
struct
{
// r_s32
IMLReg regR;
sint32 immS32;
// condition
uint8 crRegisterIndex;
uint8 crBitIndex;
bool bitMustBeSet;
}op_conditional_r_s32;
// X86 specific
struct
{
IMLCondition cond;
bool invertedCondition;
}op_x86_eflags_jcc;
};
bool IsSuffixInstruction() const
{
if (type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_BL ||
type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_FAR ||
type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_TO_REG ||
type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_LEAVE ||
type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_HLE ||
type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ||
type == PPCREC_IML_TYPE_JUMP ||
type == PPCREC_IML_TYPE_CONDITIONAL_JUMP ||
type == PPCREC_IML_TYPE_X86_EFLAGS_JCC)
return true;
return false;
}
// instruction setters
void make_no_op()
{
type = PPCREC_IML_TYPE_NO_OP;
operation = 0;
}
void make_r_name(IMLReg regR, IMLName name)
{
cemu_assert_debug(regR.GetBaseFormat() == regR.GetRegFormat()); // for name load/store instructions the register must match the base format
type = PPCREC_IML_TYPE_R_NAME;
operation = PPCREC_IML_OP_ASSIGN;
op_r_name.regR = regR;
op_r_name.name = name;
}
void make_name_r(IMLName name, IMLReg regR)
{
cemu_assert_debug(regR.GetBaseFormat() == regR.GetRegFormat()); // for name load/store instructions the register must match the base format
type = PPCREC_IML_TYPE_NAME_R;
operation = PPCREC_IML_OP_ASSIGN;
op_r_name.regR = regR;
op_r_name.name = name;
}
void make_debugbreak(uint32 currentPPCAddress = 0)
{
make_macro(PPCREC_IML_MACRO_DEBUGBREAK, 0, currentPPCAddress, 0, IMLREG_INVALID);
}
void make_macro(uint32 macroId, uint32 param, uint32 param2, uint16 paramU16, IMLReg regParam)
{
this->type = PPCREC_IML_TYPE_MACRO;
this->operation = macroId;
this->op_macro.param = param;
this->op_macro.param2 = param2;
this->op_macro.paramU16 = paramU16;
this->op_macro.paramReg = regParam;
}
void make_cjump_cycle_check()
{
this->type = PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK;
this->operation = 0;
}
void make_r_r(uint32 operation, IMLReg regR, IMLReg regA)
{
this->type = PPCREC_IML_TYPE_R_R;
this->operation = operation;
this->op_r_r.regR = regR;
this->op_r_r.regA = regA;
}
void make_r_s32(uint32 operation, IMLReg regR, sint32 immS32)
{
this->type = PPCREC_IML_TYPE_R_S32;
this->operation = operation;
this->op_r_immS32.regR = regR;
this->op_r_immS32.immS32 = immS32;
}
void make_r_r_r(uint32 operation, IMLReg regR, IMLReg regA, IMLReg regB)
{
this->type = PPCREC_IML_TYPE_R_R_R;
this->operation = operation;
this->op_r_r_r.regR = regR;
this->op_r_r_r.regA = regA;
this->op_r_r_r.regB = regB;
}
void make_r_r_r_carry(uint32 operation, IMLReg regR, IMLReg regA, IMLReg regB, IMLReg regCarry)
{
this->type = PPCREC_IML_TYPE_R_R_R_CARRY;
this->operation = operation;
this->op_r_r_r_carry.regR = regR;
this->op_r_r_r_carry.regA = regA;
this->op_r_r_r_carry.regB = regB;
this->op_r_r_r_carry.regCarry = regCarry;
}
void make_r_r_s32(uint32 operation, IMLReg regR, IMLReg regA, sint32 immS32)
{
this->type = PPCREC_IML_TYPE_R_R_S32;
this->operation = operation;
this->op_r_r_s32.regR = regR;
this->op_r_r_s32.regA = regA;
this->op_r_r_s32.immS32 = immS32;
}
void make_r_r_s32_carry(uint32 operation, IMLReg regR, IMLReg regA, sint32 immS32, IMLReg regCarry)
{
this->type = PPCREC_IML_TYPE_R_R_S32_CARRY;
this->operation = operation;
this->op_r_r_s32_carry.regR = regR;
this->op_r_r_s32_carry.regA = regA;
this->op_r_r_s32_carry.immS32 = immS32;
this->op_r_r_s32_carry.regCarry = regCarry;
}
void make_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond)
{
this->type = PPCREC_IML_TYPE_COMPARE;
this->operation = PPCREC_IML_OP_INVALID;
this->op_compare.regR = regR;
this->op_compare.regA = regA;
this->op_compare.regB = regB;
this->op_compare.cond = cond;
}
void make_compare_s32(IMLReg regA, sint32 immS32, IMLReg regR, IMLCondition cond)
{
this->type = PPCREC_IML_TYPE_COMPARE_S32;
this->operation = PPCREC_IML_OP_INVALID;
this->op_compare_s32.regR = regR;
this->op_compare_s32.regA = regA;
this->op_compare_s32.immS32 = immS32;
this->op_compare_s32.cond = cond;
}
void make_conditional_jump(IMLReg regBool, bool mustBeTrue)
{
this->type = PPCREC_IML_TYPE_CONDITIONAL_JUMP;
this->operation = PPCREC_IML_OP_INVALID;
this->op_conditional_jump.registerBool = regBool;
this->op_conditional_jump.mustBeTrue = mustBeTrue;
}
void make_jump()
{
this->type = PPCREC_IML_TYPE_JUMP;
this->operation = PPCREC_IML_OP_INVALID;
}
// load from memory
void make_r_memory(IMLReg regD, IMLReg regMem, sint32 immS32, uint32 copyWidth, bool signExtend, bool switchEndian)
{
this->type = PPCREC_IML_TYPE_LOAD;
this->operation = 0;
this->op_storeLoad.registerData = regD;
this->op_storeLoad.registerMem = regMem;
this->op_storeLoad.immS32 = immS32;
this->op_storeLoad.copyWidth = copyWidth;
this->op_storeLoad.flags2.swapEndian = switchEndian;
this->op_storeLoad.flags2.signExtend = signExtend;
}
// store to memory
void make_memory_r(IMLReg regS, IMLReg regMem, sint32 immS32, uint32 copyWidth, bool switchEndian)
{
this->type = PPCREC_IML_TYPE_STORE;
this->operation = 0;
this->op_storeLoad.registerData = regS;
this->op_storeLoad.registerMem = regMem;
this->op_storeLoad.immS32 = immS32;
this->op_storeLoad.copyWidth = copyWidth;
this->op_storeLoad.flags2.swapEndian = switchEndian;
this->op_storeLoad.flags2.signExtend = false;
}
void make_atomic_cmp_store(IMLReg regEA, IMLReg regCompareValue, IMLReg regWriteValue, IMLReg regSuccessOutput)
{
this->type = PPCREC_IML_TYPE_ATOMIC_CMP_STORE;
this->operation = 0;
this->op_atomic_compare_store.regEA = regEA;
this->op_atomic_compare_store.regCompareValue = regCompareValue;
this->op_atomic_compare_store.regWriteValue = regWriteValue;
this->op_atomic_compare_store.regBoolOut = regSuccessOutput;
}
void make_call_imm(uintptr_t callAddress, IMLReg param0, IMLReg param1, IMLReg param2, IMLReg regReturn)
{
this->type = PPCREC_IML_TYPE_CALL_IMM;
this->operation = 0;
this->op_call_imm.callAddress = callAddress;
this->op_call_imm.regParam0 = param0;
this->op_call_imm.regParam1 = param1;
this->op_call_imm.regParam2 = param2;
this->op_call_imm.regReturn = regReturn;
}
// FPR
// load from memory
void make_fpr_r_memory(IMLReg registerDestination, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian)
{
this->type = PPCREC_IML_TYPE_FPR_LOAD;
this->operation = 0;
this->op_storeLoad.registerData = registerDestination;
this->op_storeLoad.registerMem = registerMemory;
this->op_storeLoad.immS32 = immS32;
this->op_storeLoad.mode = mode;
this->op_storeLoad.flags2.swapEndian = switchEndian;
}
void make_fpr_r_memory_indexed(IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 mode, bool switchEndian)
{
this->type = PPCREC_IML_TYPE_FPR_LOAD_INDEXED;
this->operation = 0;
this->op_storeLoad.registerData = registerDestination;
this->op_storeLoad.registerMem = registerMemory1;
this->op_storeLoad.registerMem2 = registerMemory2;
this->op_storeLoad.immS32 = 0;
this->op_storeLoad.mode = mode;
this->op_storeLoad.flags2.swapEndian = switchEndian;
}
// store to memory
void make_fpr_memory_r(IMLReg registerSource, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian)
{
this->type = PPCREC_IML_TYPE_FPR_STORE;
this->operation = 0;
this->op_storeLoad.registerData = registerSource;
this->op_storeLoad.registerMem = registerMemory;
this->op_storeLoad.immS32 = immS32;
this->op_storeLoad.mode = mode;
this->op_storeLoad.flags2.swapEndian = switchEndian;
}
void make_fpr_memory_r_indexed(IMLReg registerSource, IMLReg registerMemory1, IMLReg registerMemory2, sint32 immS32, uint32 mode, bool switchEndian)
{
this->type = PPCREC_IML_TYPE_FPR_STORE_INDEXED;
this->operation = 0;
this->op_storeLoad.registerData = registerSource;
this->op_storeLoad.registerMem = registerMemory1;
this->op_storeLoad.registerMem2 = registerMemory2;
this->op_storeLoad.immS32 = immS32;
this->op_storeLoad.mode = mode;
this->op_storeLoad.flags2.swapEndian = switchEndian;
}
void make_fpr_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond)
{
this->type = PPCREC_IML_TYPE_FPR_COMPARE;
this->operation = -999;
this->op_fpr_compare.regR = regR;
this->op_fpr_compare.regA = regA;
this->op_fpr_compare.regB = regB;
this->op_fpr_compare.cond = cond;
}
void make_fpr_r(sint32 operation, IMLReg registerResult)
{
// OP (fpr)
this->type = PPCREC_IML_TYPE_FPR_R;
this->operation = operation;
this->op_fpr_r.regR = registerResult;
}
void make_fpr_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER)
{
// fpr OP fpr
this->type = PPCREC_IML_TYPE_FPR_R_R;
this->operation = operation;
this->op_fpr_r_r.regR = registerResult;
this->op_fpr_r_r.regA = registerOperand;
}
void make_fpr_r_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperand1, IMLReg registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER)
{
// fpr = OP (fpr,fpr)
this->type = PPCREC_IML_TYPE_FPR_R_R_R;
this->operation = operation;
this->op_fpr_r_r_r.regR = registerResult;
this->op_fpr_r_r_r.regA = registerOperand1;
this->op_fpr_r_r_r.regB = registerOperand2;
}
void make_fpr_r_r_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperandA, IMLReg registerOperandB, IMLReg registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER)
{
// fpr = OP (fpr,fpr,fpr)
this->type = PPCREC_IML_TYPE_FPR_R_R_R_R;
this->operation = operation;
this->op_fpr_r_r_r_r.regR = registerResult;
this->op_fpr_r_r_r_r.regA = registerOperandA;
this->op_fpr_r_r_r_r.regB = registerOperandB;
this->op_fpr_r_r_r_r.regC = registerOperandC;
}
/* X86 specific */
void make_x86_eflags_jcc(IMLCondition cond, bool invertedCondition)
{
this->type = PPCREC_IML_TYPE_X86_EFLAGS_JCC;
this->operation = -999;
this->op_x86_eflags_jcc.cond = cond;
this->op_x86_eflags_jcc.invertedCondition = invertedCondition;
}
void CheckRegisterUsage(IMLUsedRegisters* registersUsed) const;
bool HasSideEffects() const; // returns true if the instruction has side effects beyond just reading and writing registers. Dead code elimination uses this to know if an instruction can be dropped when the regular register outputs are not used
void RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& translationTable);
};
// architecture specific constants
namespace IMLArchX86
{
static constexpr int PHYSREG_GPR_BASE = 0;
static constexpr int PHYSREG_FPR_BASE = 16;
};

View file

@ -1,719 +0,0 @@
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
#include "Cafe/HW/Espresso/Recompiler/IML/IML.h"
#include "Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h"
#include "../PPCRecompiler.h"
#include "../PPCRecompilerIml.h"
#include "../BackendX64/BackendX64.h"
#include "Common/FileStream.h"
#include <boost/container/static_vector.hpp>
#include <boost/container/small_vector.hpp>
IMLReg _FPRRegFromID(IMLRegID regId)
{
return IMLReg(IMLRegFormat::F64, IMLRegFormat::F64, 0, regId);
}
void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg fprReg)
{
IMLRegID fprIndex = fprReg.GetRegID();
IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad;
if (imlInstructionLoad->op_storeLoad.flags2.notExpanded)
return;
boost::container::static_vector<sint32, 4> trackedMoves; // only track up to 4 copies
IMLUsedRegisters registersUsed;
sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances)
bool foundMatch = false;
sint32 lastStore = -1;
for (sint32 i = imlIndexLoad + 1; i < scanRangeEnd; i++)
{
IMLInstruction* imlInstruction = imlSegment->imlList.data() + i;
if (imlInstruction->IsSuffixInstruction())
break;
// check if FPR is stored
if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE) ||
(imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE))
{
if (imlInstruction->op_storeLoad.registerData.GetRegID() == fprIndex)
{
if (foundMatch == false)
{
// flag the load-single instruction as "don't expand" (leave single value as-is)
imlInstructionLoad->op_storeLoad.flags2.notExpanded = true;
}
// also set the flag for the store instruction
IMLInstruction* imlInstructionStore = imlInstruction;
imlInstructionStore->op_storeLoad.flags2.notExpanded = true;
foundMatch = true;
lastStore = i + 1;
continue;
}
}
// if the FPR is copied then keep track of it. We can expand the copies instead of the original
if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R && imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN && imlInstruction->op_fpr_r_r.regA.GetRegID() == fprIndex)
{
if (imlInstruction->op_fpr_r_r.regR.GetRegID() == fprIndex)
{
// unexpected no-op
break;
}
if (trackedMoves.size() >= trackedMoves.capacity())
{
// we cant track any more moves, expand here
lastStore = i;
break;
}
trackedMoves.push_back(i);
continue;
}
// check if FPR is overwritten
imlInstruction->CheckRegisterUsage(&registersUsed);
if (registersUsed.writtenGPR1.IsValidAndSameRegID(fprIndex) || registersUsed.writtenGPR2.IsValidAndSameRegID(fprIndex))
break;
if (registersUsed.readGPR1.IsValidAndSameRegID(fprIndex))
break;
if (registersUsed.readGPR2.IsValidAndSameRegID(fprIndex))
break;
if (registersUsed.readGPR3.IsValidAndSameRegID(fprIndex))
break;
if (registersUsed.readGPR4.IsValidAndSameRegID(fprIndex))
break;
}
if (foundMatch)
{
// insert expand instructions for each target register of a move
sint32 positionBias = 0;
for (auto& trackedMove : trackedMoves)
{
sint32 realPosition = trackedMove + positionBias;
IMLInstruction* imlMoveInstruction = imlSegment->imlList.data() + realPosition;
if (realPosition >= lastStore)
break; // expand is inserted before this move
else
lastStore++;
cemu_assert_debug(imlMoveInstruction->type == PPCREC_IML_TYPE_FPR_R_R && imlMoveInstruction->op_fpr_r_r.regA.GetRegID() == fprIndex);
cemu_assert_debug(imlMoveInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::F64);
auto dstReg = imlMoveInstruction->op_fpr_r_r.regR;
IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, realPosition+1); // one after the move
newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, dstReg);
positionBias++;
}
// insert expand instruction after store
IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore);
newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, _FPRRegFromID(fprIndex));
}
}
/*
* Scans for patterns:
* <Load sp float into register f>
* <Random unrelated instructions>
* <Store sp float from register f>
* For these patterns the store and load is modified to work with un-extended values (float remains as float, no double conversion)
* The float->double extension is then executed later
* Advantages:
* Keeps denormals and other special float values intact
* Slightly improves performance
*/
void IMLOptimizer_OptimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext)
{
for (IMLSegment* segIt : ppcImlGenContext->segmentList2)
{
for (sint32 i = 0; i < segIt->imlList.size(); i++)
{
IMLInstruction* imlInstruction = segIt->imlList.data() + i;
if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE)
{
PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData);
}
else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE)
{
PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData);
}
}
}
}
void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg gprReg)
{
cemu_assert_debug(gprReg.GetBaseFormat() == IMLRegFormat::I64); // todo - proper handling required for non-standard sizes
cemu_assert_debug(gprReg.GetRegFormat() == IMLRegFormat::I32);
IMLRegID gprIndex = gprReg.GetRegID();
IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad;
if ( imlInstructionLoad->op_storeLoad.flags2.swapEndian == false )
return;
bool foundMatch = false;
IMLUsedRegisters registersUsed;
sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances)
sint32 i = imlIndexLoad + 1;
for (; i < scanRangeEnd; i++)
{
IMLInstruction* imlInstruction = imlSegment->imlList.data() + i;
if (imlInstruction->IsSuffixInstruction())
break;
// check if GPR is stored
if ((imlInstruction->type == PPCREC_IML_TYPE_STORE && imlInstruction->op_storeLoad.copyWidth == 32 ) )
{
if (imlInstruction->op_storeLoad.registerMem.GetRegID() == gprIndex)
break;
if (imlInstruction->op_storeLoad.registerData.GetRegID() == gprIndex)
{
IMLInstruction* imlInstructionStore = imlInstruction;
if (foundMatch == false)
{
// switch the endian swap flag for the load instruction
imlInstructionLoad->op_storeLoad.flags2.swapEndian = !imlInstructionLoad->op_storeLoad.flags2.swapEndian;
foundMatch = true;
}
// switch the endian swap flag for the store instruction
imlInstructionStore->op_storeLoad.flags2.swapEndian = !imlInstructionStore->op_storeLoad.flags2.swapEndian;
// keep scanning
continue;
}
}
// check if GPR is accessed
imlInstruction->CheckRegisterUsage(&registersUsed);
if (registersUsed.readGPR1.IsValidAndSameRegID(gprIndex) ||
registersUsed.readGPR2.IsValidAndSameRegID(gprIndex) ||
registersUsed.readGPR3.IsValidAndSameRegID(gprIndex))
{
break;
}
if (registersUsed.IsBaseGPRWritten(gprReg))
return; // GPR overwritten, we don't need to byte swap anymore
}
if (foundMatch)
{
PPCRecompiler_insertInstruction(imlSegment, i)->make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, gprReg, gprReg);
}
}
/*
* Scans for patterns:
* <Load sp integer into register r>
* <Random unrelated instructions>
* <Store sp integer from register r>
* For these patterns the store and load is modified to work with non-swapped values
* The big_endian->little_endian conversion is then executed later
* Advantages:
* Slightly improves performance
*/
void IMLOptimizer_OptimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext)
{
for (IMLSegment* segIt : ppcImlGenContext->segmentList2)
{
for (sint32 i = 0; i < segIt->imlList.size(); i++)
{
IMLInstruction* imlInstruction = segIt->imlList.data() + i;
if (imlInstruction->type == PPCREC_IML_TYPE_LOAD && imlInstruction->op_storeLoad.copyWidth == 32 && imlInstruction->op_storeLoad.flags2.swapEndian )
{
PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData);
}
}
}
}
IMLName PPCRecompilerImlGen_GetRegName(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg);
sint32 _getGQRIndexFromRegister(ppcImlGenContext_t* ppcImlGenContext, IMLReg gqrReg)
{
if (gqrReg.IsInvalid())
return -1;
sint32 namedReg = PPCRecompilerImlGen_GetRegName(ppcImlGenContext, gqrReg);
if (namedReg >= (PPCREC_NAME_SPR0 + SPR_UGQR0) && namedReg <= (PPCREC_NAME_SPR0 + SPR_UGQR7))
{
return namedReg - (PPCREC_NAME_SPR0 + SPR_UGQR0);
}
else
{
cemu_assert_suspicious();
}
return -1;
}
bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue)
{
// the default configuration is:
// UGQR0 = 0x00000000
// UGQR2 = 0x00040004
// UGQR3 = 0x00050005
// UGQR4 = 0x00060006
// UGQR5 = 0x00070007
// but games are free to modify UGQR2 to UGQR7 it seems.
// no game modifies UGQR0 so it's safe enough to optimize for the default value
// Ideally we would do some kind of runtime tracking and second recompilation to create fast paths for PSQ_L/PSQ_ST but thats todo
if (gqrIndex == 0)
gqrValue = 0x00000000;
else
return false;
return true;
}
// analyses register dependencies across the entire function
// per segment this will generate information about which registers need to be preserved and which ones don't (e.g. are overwritten)
class IMLOptimizerRegIOAnalysis
{
public:
// constructor with segment pointer list as span
IMLOptimizerRegIOAnalysis(std::span<IMLSegment*> segmentList, uint32 maxRegId) : m_segmentList(segmentList), m_maxRegId(maxRegId)
{
m_segRegisterInOutList.resize(segmentList.size());
}
struct IMLSegmentRegisterInOut
{
// todo - since our register ID range is usually pretty small (<64) we could use integer bitmasks to accelerate this? There is a helper class used in RA code already
std::unordered_set<IMLRegID> regWritten; // registers which are modified in this segment
std::unordered_set<IMLRegID> regImported; // registers which are read in this segment before they are written (importing value from previous segments)
std::unordered_set<IMLRegID> regForward; // registers which are not read or written in this segment, but are imported into a later segment (propagated info)
};
// calculate which registers are imported (read-before-written) and forwarded (read-before-written by a later segment) per segment
// then in a second step propagate the dependencies across linked segments
void ComputeDepedencies()
{
std::vector<IMLSegmentRegisterInOut>& segRegisterInOutList = m_segRegisterInOutList;
IMLSegmentRegisterInOut* segIO = segRegisterInOutList.data();
uint32 index = 0;
for(auto& seg : m_segmentList)
{
seg->momentaryIndex = index;
index++;
for(auto& instr : seg->imlList)
{
IMLUsedRegisters registerUsage;
instr.CheckRegisterUsage(&registerUsage);
// registers are considered imported if they are read before being written in this seg
registerUsage.ForEachReadGPR([&](IMLReg gprReg) {
IMLRegID gprId = gprReg.GetRegID();
if (!segIO->regWritten.contains(gprId))
{
segIO->regImported.insert(gprId);
}
});
registerUsage.ForEachWrittenGPR([&](IMLReg gprReg) {
IMLRegID gprId = gprReg.GetRegID();
segIO->regWritten.insert(gprId);
});
}
segIO++;
}
// for every exit segment, import all registers
for(auto& seg : m_segmentList)
{
if (!seg->nextSegmentIsUncertain)
continue;
if(seg->deadCodeEliminationHintSeg)
continue;
IMLSegmentRegisterInOut& segIO = segRegisterInOutList[seg->momentaryIndex];
for(uint32 i=0; i<=m_maxRegId; i++)
{
segIO.regImported.insert((IMLRegID)i);
}
}
// broadcast dependencies across segment chains
std::unordered_set<uint32> segIdsWhichNeedUpdate;
for (uint32 i = 0; i < m_segmentList.size(); i++)
{
segIdsWhichNeedUpdate.insert(i);
}
while(!segIdsWhichNeedUpdate.empty())
{
auto firstIt = segIdsWhichNeedUpdate.begin();
uint32 segId = *firstIt;
segIdsWhichNeedUpdate.erase(firstIt);
// forward regImported and regForward to earlier segments into their regForward, unless the register is written
auto& curSeg = m_segmentList[segId];
IMLSegmentRegisterInOut& curSegIO = segRegisterInOutList[segId];
for(auto& prevSeg : curSeg->list_prevSegments)
{
IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex];
bool prevSegChanged = false;
for(auto& regId : curSegIO.regImported)
{
if (!prevSegIO.regWritten.contains(regId))
prevSegChanged |= prevSegIO.regForward.insert(regId).second;
}
for(auto& regId : curSegIO.regForward)
{
if (!prevSegIO.regWritten.contains(regId))
prevSegChanged |= prevSegIO.regForward.insert(regId).second;
}
if(prevSegChanged)
segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex);
}
// same for hint links
for(auto& prevSeg : curSeg->list_deadCodeHintBy)
{
IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex];
bool prevSegChanged = false;
for(auto& regId : curSegIO.regImported)
{
if (!prevSegIO.regWritten.contains(regId))
prevSegChanged |= prevSegIO.regForward.insert(regId).second;
}
for(auto& regId : curSegIO.regForward)
{
if (!prevSegIO.regWritten.contains(regId))
prevSegChanged |= prevSegIO.regForward.insert(regId).second;
}
if(prevSegChanged)
segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex);
}
}
}
std::unordered_set<IMLRegID> GetRegistersNeededAtEndOfSegment(IMLSegment& seg)
{
std::unordered_set<IMLRegID> regsNeeded;
if(seg.nextSegmentIsUncertain)
{
if(seg.deadCodeEliminationHintSeg)
{
auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex];
regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end());
regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end());
}
else
{
// add all regs
for(uint32 i = 0; i <= m_maxRegId; i++)
regsNeeded.insert(i);
}
return regsNeeded;
}
if(seg.nextSegmentBranchTaken)
{
auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex];
regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end());
regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end());
}
if(seg.nextSegmentBranchNotTaken)
{
auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex];
regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end());
regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end());
}
return regsNeeded;
}
bool IsRegisterNeededAtEndOfSegment(IMLSegment& seg, IMLRegID regId)
{
if(seg.nextSegmentIsUncertain)
{
if(!seg.deadCodeEliminationHintSeg)
return true;
auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex];
if(nextSegIO.regImported.contains(regId))
return true;
if(nextSegIO.regForward.contains(regId))
return true;
return false;
}
if(seg.nextSegmentBranchTaken)
{
auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex];
if(nextSegIO.regImported.contains(regId))
return true;
if(nextSegIO.regForward.contains(regId))
return true;
}
if(seg.nextSegmentBranchNotTaken)
{
auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex];
if(nextSegIO.regImported.contains(regId))
return true;
if(nextSegIO.regForward.contains(regId))
return true;
}
return false;
}
private:
std::span<IMLSegment*> m_segmentList;
uint32 m_maxRegId;
std::vector<IMLSegmentRegisterInOut> m_segRegisterInOutList;
};
// scan backwards starting from index and return the index of the first found instruction which writes to the given register (by id)
sint32 IMLUtil_FindInstructionWhichWritesRegister(IMLSegment& seg, sint32 startIndex, IMLReg reg, sint32 maxScanDistance = -1)
{
sint32 endIndex = std::max<sint32>(startIndex - maxScanDistance, 0);
for (sint32 i = startIndex; i >= endIndex; i--)
{
IMLInstruction& imlInstruction = seg.imlList[i];
IMLUsedRegisters registersUsed;
imlInstruction.CheckRegisterUsage(&registersUsed);
if (registersUsed.IsBaseGPRWritten(reg))
return i;
}
return -1;
}
// returns true if the instruction can safely be moved while keeping ordering constraints and data dependencies intact
// initialIndex is inclusive, targetIndex is exclusive
bool IMLUtil_CanMoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex)
{
boost::container::static_vector<IMLRegID, 8> regsWritten;
boost::container::static_vector<IMLRegID, 8> regsRead;
// get list of read and written registers
IMLUsedRegisters registersUsed;
seg.imlList[initialIndex].CheckRegisterUsage(&registersUsed);
registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) {
if (isWritten)
regsWritten.push_back(reg.GetRegID());
else
regsRead.push_back(reg.GetRegID());
});
// check all the instructions inbetween
if(initialIndex < targetIndex)
{
sint32 scanStartIndex = initialIndex+1; // +1 to skip the moving instruction itself
sint32 scanEndIndex = targetIndex;
for (sint32 i = scanStartIndex; i < scanEndIndex; i++)
{
IMLUsedRegisters registersUsed;
seg.imlList[i].CheckRegisterUsage(&registersUsed);
// in order to be able to move an instruction past another instruction, any of the read registers must not be modified (written)
// and any of it's written registers must not be read
bool canMove = true;
registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) {
IMLRegID regId = reg.GetRegID();
if (!isWritten)
canMove = canMove && std::find(regsWritten.begin(), regsWritten.end(), regId) == regsWritten.end();
else
canMove = canMove && std::find(regsRead.begin(), regsRead.end(), regId) == regsRead.end();
});
if(!canMove)
return false;
}
}
else
{
cemu_assert_unimplemented(); // backwards scan is todo
return false;
}
return true;
}
sint32 IMLUtil_CountRegisterReadsInRange(IMLSegment& seg, sint32 scanStartIndex, sint32 scanEndIndex, IMLRegID regId)
{
cemu_assert_debug(scanStartIndex <= scanEndIndex);
cemu_assert_debug(scanEndIndex < seg.imlList.size());
sint32 count = 0;
for (sint32 i = scanStartIndex; i <= scanEndIndex; i++)
{
IMLUsedRegisters registersUsed;
seg.imlList[i].CheckRegisterUsage(&registersUsed);
registersUsed.ForEachReadGPR([&](IMLReg reg) {
if (reg.GetRegID() == regId)
count++;
});
}
return count;
}
// move instruction from one index to another
// instruction will be inserted before the instruction at targetIndex
// returns the new instruction index of the moved instruction
sint32 IMLUtil_MoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex)
{
cemu_assert_debug(initialIndex != targetIndex);
IMLInstruction temp = seg.imlList[initialIndex];
if (initialIndex < targetIndex)
{
cemu_assert_debug(targetIndex > 0);
targetIndex--;
for(size_t i=initialIndex; i<targetIndex; i++)
seg.imlList[i] = seg.imlList[i+1];
seg.imlList[targetIndex] = temp;
return targetIndex;
}
else
{
cemu_assert_unimplemented(); // testing needed
std::copy(seg.imlList.begin() + targetIndex, seg.imlList.begin() + initialIndex, seg.imlList.begin() + targetIndex + 1);
seg.imlList[targetIndex] = temp;
return targetIndex;
}
}
// x86 specific
bool IMLOptimizerX86_ModifiesEFlags(IMLInstruction& inst)
{
// this is a very conservative implementation. There are more cases but this is good enough for now
if(inst.type == PPCREC_IML_TYPE_NAME_R || inst.type == PPCREC_IML_TYPE_R_NAME)
return false;
if((inst.type == PPCREC_IML_TYPE_R_R || inst.type == PPCREC_IML_TYPE_R_S32) && inst.operation == PPCREC_IML_OP_ASSIGN)
return false;
return true; // if we dont know for sure, assume it does
}
void IMLOptimizer_DebugPrintSeg(ppcImlGenContext_t& ppcImlGenContext, IMLSegment& seg)
{
printf("----------------\n");
IMLDebug_DumpSegment(&ppcImlGenContext, &seg);
fflush(stdout);
}
void IMLOptimizer_RemoveDeadCodeFromSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg)
{
// algorithm works like this:
// Calculate which registers need to be preserved at the end of each segment
// Then for each segment:
// - Iterate instructions backwards
// - Maintain a list of registers which are read at a later point (initially this is the list from the first step)
// - If an instruction only modifies registers which are not in the read list and has no side effects, then it is dead code and can be replaced with a no-op
std::unordered_set<IMLRegID> regsNeeded = regIoAnalysis.GetRegistersNeededAtEndOfSegment(seg);
// start with suffix instruction
if(seg.HasSuffixInstruction())
{
IMLInstruction& imlInstruction = seg.imlList[seg.GetSuffixInstructionIndex()];
IMLUsedRegisters registersUsed;
imlInstruction.CheckRegisterUsage(&registersUsed);
registersUsed.ForEachWrittenGPR([&](IMLReg reg) {
regsNeeded.erase(reg.GetRegID());
});
registersUsed.ForEachReadGPR([&](IMLReg reg) {
regsNeeded.insert(reg.GetRegID());
});
}
// iterate instructions backwards
for (sint32 i = seg.imlList.size() - (seg.HasSuffixInstruction() ? 2:1); i >= 0; i--)
{
IMLInstruction& imlInstruction = seg.imlList[i];
IMLUsedRegisters registersUsed;
imlInstruction.CheckRegisterUsage(&registersUsed);
// register read -> remove from overwritten list
// register written -> add to overwritten list
// check if this instruction only writes registers which will never be read
bool onlyWritesRedundantRegisters = true;
registersUsed.ForEachWrittenGPR([&](IMLReg reg) {
if (regsNeeded.contains(reg.GetRegID()))
onlyWritesRedundantRegisters = false;
});
// check if any of the written registers are read after this point
registersUsed.ForEachWrittenGPR([&](IMLReg reg) {
regsNeeded.erase(reg.GetRegID());
});
registersUsed.ForEachReadGPR([&](IMLReg reg) {
regsNeeded.insert(reg.GetRegID());
});
if(!imlInstruction.HasSideEffects() && onlyWritesRedundantRegisters)
{
imlInstruction.make_no_op();
}
}
}
void IMLOptimizerX86_SubstituteCJumpForEflagsJump(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg)
{
// convert and optimize bool condition jumps to eflags condition jumps
// - Moves eflag setter (e.g. cmp) closer to eflags consumer (conditional jump) if necessary. If not possible but required then exit early
// - Since we only rely on eflags, the boolean register can be optimized out if DCE considers it unused
// - Further detect and optimize patterns like DEC + CMP + JCC into fused ops (todo)
// check if this segment ends with a conditional jump
if(!seg.HasSuffixInstruction())
return;
sint32 cjmpInstIndex = seg.GetSuffixInstructionIndex();
if(cjmpInstIndex < 0)
return;
IMLInstruction& cjumpInstr = seg.imlList[cjmpInstIndex];
if( cjumpInstr.type != PPCREC_IML_TYPE_CONDITIONAL_JUMP )
return;
IMLReg regCondBool = cjumpInstr.op_conditional_jump.registerBool;
bool invertedCondition = !cjumpInstr.op_conditional_jump.mustBeTrue;
// find the instruction which sets the bool
sint32 cmpInstrIndex = IMLUtil_FindInstructionWhichWritesRegister(seg, cjmpInstIndex-1, regCondBool, 20);
if(cmpInstrIndex < 0)
return;
// check if its an instruction combo which can be optimized (currently only cmp + cjump) and get the condition
IMLInstruction& condSetterInstr = seg.imlList[cmpInstrIndex];
IMLCondition cond;
if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE)
cond = condSetterInstr.op_compare.cond;
else if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE_S32)
cond = condSetterInstr.op_compare_s32.cond;
else
return;
// check if instructions inbetween modify eflags
sint32 indexEflagsSafeStart = -1; // index of the first instruction which does not modify eflags up to cjump
for(sint32 i = cjmpInstIndex-1; i > cmpInstrIndex; i--)
{
if(IMLOptimizerX86_ModifiesEFlags(seg.imlList[i]))
{
indexEflagsSafeStart = i+1;
break;
}
}
if(indexEflagsSafeStart >= 0)
{
cemu_assert(indexEflagsSafeStart > 0);
// there are eflags-modifying instructions inbetween the bool setter and cjump
// try to move the eflags setter close enough to the cjump (to indexEflagsSafeStart)
bool canMove = IMLUtil_CanMoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart);
if(!canMove)
{
return;
}
else
{
cmpInstrIndex = IMLUtil_MoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart);
}
}
// we can turn the jump into an eflags jump
cjumpInstr.make_x86_eflags_jcc(cond, invertedCondition);
if (IMLUtil_CountRegisterReadsInRange(seg, cmpInstrIndex, cjmpInstIndex, regCondBool.GetRegID()) > 1 || regIoAnalysis.IsRegisterNeededAtEndOfSegment(seg, regCondBool.GetRegID()))
return; // bool register is used beyond the CMP, we can't drop it
auto& cmpInstr = seg.imlList[cmpInstrIndex];
cemu_assert_debug(cmpInstr.type == PPCREC_IML_TYPE_COMPARE || cmpInstr.type == PPCREC_IML_TYPE_COMPARE_S32);
if(cmpInstr.type == PPCREC_IML_TYPE_COMPARE)
{
IMLReg regA = cmpInstr.op_compare.regA;
IMLReg regB = cmpInstr.op_compare.regB;
seg.imlList[cmpInstrIndex].make_r_r(PPCREC_IML_OP_X86_CMP, regA, regB);
}
else
{
IMLReg regA = cmpInstr.op_compare_s32.regA;
sint32 val = cmpInstr.op_compare_s32.immS32;
seg.imlList[cmpInstrIndex].make_r_s32(PPCREC_IML_OP_X86_CMP, regA, val);
}
}
void IMLOptimizer_StandardOptimizationPassForSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg)
{
IMLOptimizer_RemoveDeadCodeFromSegment(regIoAnalysis, seg);
#ifdef ARCH_X86_64
// x86 specific optimizations
IMLOptimizerX86_SubstituteCJumpForEflagsJump(regIoAnalysis, seg); // this pass should be applied late since it creates invisible eflags dependencies (which would break further register dependency analysis)
#endif
}
void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext)
{
IMLOptimizerRegIOAnalysis regIoAnalysis(ppcImlGenContext.segmentList2, ppcImlGenContext.GetMaxRegId());
regIoAnalysis.ComputeDepedencies();
for (IMLSegment* segIt : ppcImlGenContext.segmentList2)
{
IMLOptimizer_StandardOptimizationPassForSegment(regIoAnalysis, *segIt);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,125 +0,0 @@
#pragma once
// container for storing a set of register indices
// specifically optimized towards storing typical range of physical register indices (expected to be below 64)
class IMLPhysRegisterSet
{
public:
void SetAvailable(uint32 index)
{
cemu_assert_debug(index < 64);
m_regBitmask |= ((uint64)1 << index);
}
void SetReserved(uint32 index)
{
cemu_assert_debug(index < 64);
m_regBitmask &= ~((uint64)1 << index);
}
void SetAllAvailable()
{
m_regBitmask = ~0ull;
}
bool HasAllAvailable() const
{
return m_regBitmask == ~0ull;
}
bool IsAvailable(uint32 index) const
{
return (m_regBitmask & ((uint64)1 << index)) != 0;
}
IMLPhysRegisterSet& operator&=(const IMLPhysRegisterSet& other)
{
this->m_regBitmask &= other.m_regBitmask;
return *this;
}
IMLPhysRegisterSet& operator=(const IMLPhysRegisterSet& other)
{
this->m_regBitmask = other.m_regBitmask;
return *this;
}
void RemoveRegisters(const IMLPhysRegisterSet& other)
{
this->m_regBitmask &= ~other.m_regBitmask;
}
bool HasAnyAvailable() const
{
return m_regBitmask != 0;
}
bool HasExactlyOneAvailable() const
{
return m_regBitmask != 0 && (m_regBitmask & (m_regBitmask - 1)) == 0;
}
// returns index of first available register. Do not call when HasAnyAvailable() == false
IMLPhysReg GetFirstAvailableReg()
{
cemu_assert_debug(m_regBitmask != 0);
sint32 regIndex = 0;
auto tmp = m_regBitmask;
while ((tmp & 0xFF) == 0)
{
regIndex += 8;
tmp >>= 8;
}
while ((tmp & 0x1) == 0)
{
regIndex++;
tmp >>= 1;
}
return regIndex;
}
// returns index of next available register (search includes any register index >= startIndex)
// returns -1 if there is no more register
IMLPhysReg GetNextAvailableReg(sint32 startIndex) const
{
if (startIndex >= 64)
return -1;
uint32 regIndex = startIndex;
auto tmp = m_regBitmask;
tmp >>= regIndex;
if (!tmp)
return -1;
while ((tmp & 0xFF) == 0)
{
regIndex += 8;
tmp >>= 8;
}
while ((tmp & 0x1) == 0)
{
regIndex++;
tmp >>= 1;
}
return regIndex;
}
sint32 CountAvailableRegs() const
{
return std::popcount(m_regBitmask);
}
private:
uint64 m_regBitmask{ 0 };
};
struct IMLRegisterAllocatorParameters
{
inline IMLPhysRegisterSet& GetPhysRegPool(IMLRegFormat regFormat)
{
return perTypePhysPool[stdx::to_underlying(regFormat)];
}
IMLPhysRegisterSet perTypePhysPool[stdx::to_underlying(IMLRegFormat::TYPE_COUNT)];
std::unordered_map<IMLRegID, IMLName> regIdToName;
};
void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLRegisterAllocatorParameters& raParam);

View file

@ -1,635 +0,0 @@
#include "../PPCRecompiler.h"
#include "../PPCRecompilerIml.h"
#include "IMLRegisterAllocatorRanges.h"
#include "util/helpers/MemoryPool.h"
uint32 IMLRA_GetNextIterationIndex();
IMLRegID raLivenessRange::GetVirtualRegister() const
{
return virtualRegister;
}
sint32 raLivenessRange::GetPhysicalRegister() const
{
return physicalRegister;
}
IMLName raLivenessRange::GetName() const
{
return name;
}
void raLivenessRange::SetPhysicalRegister(IMLPhysReg physicalRegister)
{
this->physicalRegister = physicalRegister;
}
void raLivenessRange::SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister)
{
auto clusterRanges = GetAllSubrangesInCluster();
for(auto& range : clusterRanges)
range->physicalRegister = physicalRegister;
}
boost::container::small_vector<raLivenessRange*, 128> raLivenessRange::GetAllSubrangesInCluster()
{
uint32 iterationIndex = IMLRA_GetNextIterationIndex();
boost::container::small_vector<raLivenessRange*, 128> subranges;
subranges.push_back(this);
this->lastIterationIndex = iterationIndex;
size_t i = 0;
while(i<subranges.size())
{
raLivenessRange* cur = subranges[i];
i++;
// check successors
if(cur->subrangeBranchTaken && cur->subrangeBranchTaken->lastIterationIndex != iterationIndex)
{
cur->subrangeBranchTaken->lastIterationIndex = iterationIndex;
subranges.push_back(cur->subrangeBranchTaken);
}
if(cur->subrangeBranchNotTaken && cur->subrangeBranchNotTaken->lastIterationIndex != iterationIndex)
{
cur->subrangeBranchNotTaken->lastIterationIndex = iterationIndex;
subranges.push_back(cur->subrangeBranchNotTaken);
}
// check predecessors
for(auto& prev : cur->previousRanges)
{
if(prev->lastIterationIndex != iterationIndex)
{
prev->lastIterationIndex = iterationIndex;
subranges.push_back(prev);
}
}
}
return subranges;
}
void raLivenessRange::GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs)
{
range->lastIterationIndex = iterationIndex;
for (auto& it : range->list_fixedRegRequirements)
allowedRegs &= it.allowedReg;
// check successors
if (range->subrangeBranchTaken && range->subrangeBranchTaken->lastIterationIndex != iterationIndex)
GetAllowedRegistersExRecursive(range->subrangeBranchTaken, iterationIndex, allowedRegs);
if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->lastIterationIndex != iterationIndex)
GetAllowedRegistersExRecursive(range->subrangeBranchNotTaken, iterationIndex, allowedRegs);
// check predecessors
for (auto& prev : range->previousRanges)
{
if (prev->lastIterationIndex != iterationIndex)
GetAllowedRegistersExRecursive(prev, iterationIndex, allowedRegs);
}
};
bool raLivenessRange::GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters)
{
uint32 iterationIndex = IMLRA_GetNextIterationIndex();
allowedRegisters.SetAllAvailable();
GetAllowedRegistersExRecursive(this, iterationIndex, allowedRegisters);
return !allowedRegisters.HasAllAvailable();
}
IMLPhysRegisterSet raLivenessRange::GetAllowedRegisters(IMLPhysRegisterSet regPool)
{
IMLPhysRegisterSet fixedRegRequirements = regPool;
if(interval.ExtendsPreviousSegment() || interval.ExtendsIntoNextSegment())
{
auto clusterRanges = GetAllSubrangesInCluster();
for(auto& subrange : clusterRanges)
{
for(auto& fixedRegLoc : subrange->list_fixedRegRequirements)
fixedRegRequirements &= fixedRegLoc.allowedReg;
}
return fixedRegRequirements;
}
for(auto& fixedRegLoc : list_fixedRegRequirements)
fixedRegRequirements &= fixedRegLoc.allowedReg;
return fixedRegRequirements;
}
void PPCRecRARange_addLink_perVirtualGPR(std::unordered_map<IMLRegID, raLivenessRange*>& root, raLivenessRange* subrange)
{
IMLRegID regId = subrange->GetVirtualRegister();
auto it = root.find(regId);
if (it == root.end())
{
// new single element
root.try_emplace(regId, subrange);
subrange->link_sameVirtualRegister.prev = nullptr;
subrange->link_sameVirtualRegister.next = nullptr;
}
else
{
// insert in first position
raLivenessRange* priorFirst = it->second;
subrange->link_sameVirtualRegister.next = priorFirst;
it->second = subrange;
subrange->link_sameVirtualRegister.prev = nullptr;
priorFirst->link_sameVirtualRegister.prev = subrange;
}
}
void PPCRecRARange_addLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange)
{
subrange->link_allSegmentRanges.next = *root;
if (*root)
(*root)->link_allSegmentRanges.prev = subrange;
subrange->link_allSegmentRanges.prev = nullptr;
*root = subrange;
}
void PPCRecRARange_removeLink_perVirtualGPR(std::unordered_map<IMLRegID, raLivenessRange*>& root, raLivenessRange* subrange)
{
#ifdef CEMU_DEBUG_ASSERT
raLivenessRange* cur = root.find(subrange->GetVirtualRegister())->second;
bool hasRangeFound = false;
while(cur)
{
if(cur == subrange)
{
hasRangeFound = true;
break;
}
cur = cur->link_sameVirtualRegister.next;
}
cemu_assert_debug(hasRangeFound);
#endif
IMLRegID regId = subrange->GetVirtualRegister();
raLivenessRange* nextRange = subrange->link_sameVirtualRegister.next;
raLivenessRange* prevRange = subrange->link_sameVirtualRegister.prev;
raLivenessRange* newBase = prevRange ? prevRange : nextRange;
if (prevRange)
prevRange->link_sameVirtualRegister.next = subrange->link_sameVirtualRegister.next;
if (nextRange)
nextRange->link_sameVirtualRegister.prev = subrange->link_sameVirtualRegister.prev;
if (!prevRange)
{
if (nextRange)
{
root.find(regId)->second = nextRange;
}
else
{
cemu_assert_debug(root.find(regId)->second == subrange);
root.erase(regId);
}
}
#ifdef CEMU_DEBUG_ASSERT
subrange->link_sameVirtualRegister.prev = (raLivenessRange*)1;
subrange->link_sameVirtualRegister.next = (raLivenessRange*)1;
#endif
}
void PPCRecRARange_removeLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange)
{
raLivenessRange* tempPrev = subrange->link_allSegmentRanges.prev;
if (subrange->link_allSegmentRanges.prev)
subrange->link_allSegmentRanges.prev->link_allSegmentRanges.next = subrange->link_allSegmentRanges.next;
else
(*root) = subrange->link_allSegmentRanges.next;
if (subrange->link_allSegmentRanges.next)
subrange->link_allSegmentRanges.next->link_allSegmentRanges.prev = tempPrev;
#ifdef CEMU_DEBUG_ASSERT
subrange->link_allSegmentRanges.prev = (raLivenessRange*)1;
subrange->link_allSegmentRanges.next = (raLivenessRange*)1;
#endif
}
MemoryPoolPermanentObjects<raLivenessRange> memPool_livenessSubrange(4096);
// startPosition and endPosition are inclusive
raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition)
{
raLivenessRange* range = memPool_livenessSubrange.acquireObj();
range->previousRanges.clear();
range->list_accessLocations.clear();
range->list_fixedRegRequirements.clear();
range->imlSegment = imlSegment;
cemu_assert_debug(startPosition <= endPosition);
range->interval.start = startPosition;
range->interval.end = endPosition;
// register mapping
range->virtualRegister = virtualRegister;
range->name = name;
range->physicalRegister = -1;
// default values
range->hasStore = false;
range->hasStoreDelayed = false;
range->lastIterationIndex = 0;
range->subrangeBranchNotTaken = nullptr;
range->subrangeBranchTaken = nullptr;
cemu_assert_debug(range->previousRanges.empty());
range->_noLoad = false;
// add to segment linked lists
PPCRecRARange_addLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range);
PPCRecRARange_addLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range);
return range;
}
void _unlinkSubrange(raLivenessRange* range)
{
IMLSegment* imlSegment = range->imlSegment;
PPCRecRARange_removeLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range);
PPCRecRARange_removeLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range);
// unlink reverse references
if(range->subrangeBranchTaken)
range->subrangeBranchTaken->previousRanges.erase(std::find(range->subrangeBranchTaken->previousRanges.begin(), range->subrangeBranchTaken->previousRanges.end(), range));
if(range->subrangeBranchNotTaken)
range->subrangeBranchNotTaken->previousRanges.erase(std::find(range->subrangeBranchNotTaken->previousRanges.begin(), range->subrangeBranchNotTaken->previousRanges.end(), range));
range->subrangeBranchTaken = (raLivenessRange*)(uintptr_t)-1;
range->subrangeBranchNotTaken = (raLivenessRange*)(uintptr_t)-1;
// remove forward references
for(auto& prev : range->previousRanges)
{
if(prev->subrangeBranchTaken == range)
prev->subrangeBranchTaken = nullptr;
if(prev->subrangeBranchNotTaken == range)
prev->subrangeBranchNotTaken = nullptr;
}
range->previousRanges.clear();
}
void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range)
{
_unlinkSubrange(range);
range->list_accessLocations.clear();
range->list_fixedRegRequirements.clear();
memPool_livenessSubrange.releaseObj(range);
}
void IMLRA_DeleteRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range)
{
auto clusterRanges = range->GetAllSubrangesInCluster();
for (auto& subrange : clusterRanges)
IMLRA_DeleteRange(ppcImlGenContext, subrange);
}
void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext)
{
for(auto& seg : ppcImlGenContext->segmentList2)
{
raLivenessRange* cur;
while(cur = seg->raInfo.linkedList_allSubranges)
IMLRA_DeleteRange(ppcImlGenContext, cur);
seg->raInfo.linkedList_allSubranges = nullptr;
seg->raInfo.linkedList_perVirtualRegister.clear();
}
}
void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange)
{
#ifdef CEMU_DEBUG_ASSERT
PPCRecRA_debugValidateSubrange(subrange);
PPCRecRA_debugValidateSubrange(absorbedSubrange);
if (subrange->imlSegment != absorbedSubrange->imlSegment)
assert_dbg();
cemu_assert_debug(subrange->interval.end == absorbedSubrange->interval.start);
if (subrange->subrangeBranchTaken || subrange->subrangeBranchNotTaken)
assert_dbg();
if (subrange == absorbedSubrange)
assert_dbg();
#endif
// update references
subrange->subrangeBranchTaken = absorbedSubrange->subrangeBranchTaken;
subrange->subrangeBranchNotTaken = absorbedSubrange->subrangeBranchNotTaken;
absorbedSubrange->subrangeBranchTaken = nullptr;
absorbedSubrange->subrangeBranchNotTaken = nullptr;
if(subrange->subrangeBranchTaken)
*std::find(subrange->subrangeBranchTaken->previousRanges.begin(), subrange->subrangeBranchTaken->previousRanges.end(), absorbedSubrange) = subrange;
if(subrange->subrangeBranchNotTaken)
*std::find(subrange->subrangeBranchNotTaken->previousRanges.begin(), subrange->subrangeBranchNotTaken->previousRanges.end(), absorbedSubrange) = subrange;
// merge usage locations
for (auto& accessLoc : absorbedSubrange->list_accessLocations)
subrange->list_accessLocations.push_back(accessLoc);
absorbedSubrange->list_accessLocations.clear();
// merge fixed reg locations
#ifdef CEMU_DEBUG_ASSERT
if(!subrange->list_fixedRegRequirements.empty() && !absorbedSubrange->list_fixedRegRequirements.empty())
{
cemu_assert_debug(subrange->list_fixedRegRequirements.back().pos < absorbedSubrange->list_fixedRegRequirements.front().pos);
}
#endif
for (auto& fixedReg : absorbedSubrange->list_fixedRegRequirements)
subrange->list_fixedRegRequirements.push_back(fixedReg);
absorbedSubrange->list_fixedRegRequirements.clear();
subrange->interval.end = absorbedSubrange->interval.end;
PPCRecRA_debugValidateSubrange(subrange);
IMLRA_DeleteRange(ppcImlGenContext, absorbedSubrange);
}
// remove all inter-segment connections from the range cluster and split it into local ranges. Ranges are trimmed and if they have no access location they will be removed
void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange)
{
cemu_assert_debug(originRange->interval.ExtendsPreviousSegment() || originRange->interval.ExtendsIntoNextSegment()); // only call this on ranges that span multiple segments
auto clusterRanges = originRange->GetAllSubrangesInCluster();
for (auto& subrange : clusterRanges)
{
if (subrange->list_accessLocations.empty())
continue;
raInterval interval;
interval.SetInterval(subrange->list_accessLocations.front().pos, subrange->list_accessLocations.back().pos);
raLivenessRange* newSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), interval.start, interval.end);
// copy locations and fixed reg indices
newSubrange->list_accessLocations = subrange->list_accessLocations;
newSubrange->list_fixedRegRequirements = subrange->list_fixedRegRequirements;
if(originRange->HasPhysicalRegister())
{
cemu_assert_debug(subrange->list_fixedRegRequirements.empty()); // avoid unassigning a register from a range with a fixed register requirement
}
// validate
if(!newSubrange->list_accessLocations.empty())
{
cemu_assert_debug(newSubrange->list_accessLocations.front().pos >= newSubrange->interval.start);
cemu_assert_debug(newSubrange->list_accessLocations.back().pos <= newSubrange->interval.end);
}
if(!newSubrange->list_fixedRegRequirements.empty())
{
cemu_assert_debug(newSubrange->list_fixedRegRequirements.front().pos >= newSubrange->interval.start); // fixed register requirements outside of the actual access range probably means there is a mistake in GetInstructionFixedRegisters()
cemu_assert_debug(newSubrange->list_fixedRegRequirements.back().pos <= newSubrange->interval.end);
}
}
// delete the original range cluster
IMLRA_DeleteRangeCluster(ppcImlGenContext, originRange);
}
#ifdef CEMU_DEBUG_ASSERT
void PPCRecRA_debugValidateSubrange(raLivenessRange* range)
{
// validate subrange
if (range->subrangeBranchTaken && range->subrangeBranchTaken->imlSegment != range->imlSegment->nextSegmentBranchTaken)
assert_dbg();
if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->imlSegment != range->imlSegment->nextSegmentBranchNotTaken)
assert_dbg();
if(range->subrangeBranchTaken || range->subrangeBranchNotTaken)
{
cemu_assert_debug(range->interval.end.ConnectsToNextSegment());
}
if(!range->previousRanges.empty())
{
cemu_assert_debug(range->interval.start.ConnectsToPreviousSegment());
}
// validate locations
if (!range->list_accessLocations.empty())
{
cemu_assert_debug(range->list_accessLocations.front().pos >= range->interval.start);
cemu_assert_debug(range->list_accessLocations.back().pos <= range->interval.end);
}
// validate fixed reg requirements
if (!range->list_fixedRegRequirements.empty())
{
cemu_assert_debug(range->list_fixedRegRequirements.front().pos >= range->interval.start);
cemu_assert_debug(range->list_fixedRegRequirements.back().pos <= range->interval.end);
for(sint32 i = 0; i < (sint32)range->list_fixedRegRequirements.size()-1; i++)
cemu_assert_debug(range->list_fixedRegRequirements[i].pos < range->list_fixedRegRequirements[i+1].pos);
}
}
#else
void PPCRecRA_debugValidateSubrange(raLivenessRange* range) {}
#endif
// trim start and end of range to match first and last read/write locations
// does not trim start/endpoints which extend into the next/previous segment
void IMLRA_TrimRangeToUse(raLivenessRange* range)
{
if(range->list_accessLocations.empty())
{
// special case where we trim ranges extending from other segments to a single instruction edge
cemu_assert_debug(!range->interval.start.IsInstructionIndex() || !range->interval.end.IsInstructionIndex());
if(range->interval.start.IsInstructionIndex())
range->interval.start = range->interval.end;
if(range->interval.end.IsInstructionIndex())
range->interval.end = range->interval.start;
return;
}
// trim start and end
raInterval prevInterval = range->interval;
if(range->interval.start.IsInstructionIndex())
range->interval.start = range->list_accessLocations.front().pos;
if(range->interval.end.IsInstructionIndex())
range->interval.end = range->list_accessLocations.back().pos;
// extra checks
#ifdef CEMU_DEBUG_ASSERT
cemu_assert_debug(range->interval.start <= range->interval.end);
for(auto& loc : range->list_accessLocations)
{
cemu_assert_debug(range->interval.ContainsEdge(loc.pos));
}
cemu_assert_debug(prevInterval.ContainsWholeInterval(range->interval));
#endif
}
// split range at the given position
// After the split there will be two ranges:
// head -> subrange is shortened to end at splitIndex (exclusive)
// tail -> a new subrange that ranges from splitIndex (inclusive) to the end of the original subrange
// if head has a physical register assigned it will not carry over to tail
// The return value is the tail range
// If trimToUsage is true, the end of the head subrange and the start of the tail subrange will be shrunk to fit the read/write locations within. If there are no locations then the range will be deleted
raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage)
{
cemu_assert_debug(splitPosition.IsInstructionIndex());
cemu_assert_debug(!subrange->interval.IsNextSegmentOnly() && !subrange->interval.IsPreviousSegmentOnly());
cemu_assert_debug(subrange->interval.ContainsEdge(splitPosition));
// determine new intervals
raInterval headInterval, tailInterval;
headInterval.SetInterval(subrange->interval.start, splitPosition-1);
tailInterval.SetInterval(splitPosition, subrange->interval.end);
cemu_assert_debug(headInterval.start <= headInterval.end);
cemu_assert_debug(tailInterval.start <= tailInterval.end);
// create tail
raLivenessRange* tailSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), tailInterval.start, tailInterval.end);
tailSubrange->SetPhysicalRegister(subrange->GetPhysicalRegister());
// carry over branch targets and update reverse references
tailSubrange->subrangeBranchTaken = subrange->subrangeBranchTaken;
tailSubrange->subrangeBranchNotTaken = subrange->subrangeBranchNotTaken;
subrange->subrangeBranchTaken = nullptr;
subrange->subrangeBranchNotTaken = nullptr;
if(tailSubrange->subrangeBranchTaken)
*std::find(tailSubrange->subrangeBranchTaken->previousRanges.begin(), tailSubrange->subrangeBranchTaken->previousRanges.end(), subrange) = tailSubrange;
if(tailSubrange->subrangeBranchNotTaken)
*std::find(tailSubrange->subrangeBranchNotTaken->previousRanges.begin(), tailSubrange->subrangeBranchNotTaken->previousRanges.end(), subrange) = tailSubrange;
// we assume that list_locations is ordered by instruction index and contains no duplicate indices, so lets check that here just in case
#ifdef CEMU_DEBUG_ASSERT
if(subrange->list_accessLocations.size() > 1)
{
for(size_t i=0; i<subrange->list_accessLocations.size()-1; i++)
{
cemu_assert_debug(subrange->list_accessLocations[i].pos < subrange->list_accessLocations[i+1].pos);
}
}
#endif
// split locations
auto it = std::lower_bound(
subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), splitPosition,
[](const raAccessLocation& accessLoc, raInstructionEdge value) { return accessLoc.pos < value; }
);
size_t originalCount = subrange->list_accessLocations.size();
tailSubrange->list_accessLocations.insert(tailSubrange->list_accessLocations.end(), it, subrange->list_accessLocations.end());
subrange->list_accessLocations.erase(it, subrange->list_accessLocations.end());
cemu_assert_debug(subrange->list_accessLocations.empty() || subrange->list_accessLocations.back().pos < splitPosition);
cemu_assert_debug(tailSubrange->list_accessLocations.empty() || tailSubrange->list_accessLocations.front().pos >= splitPosition);
cemu_assert_debug(subrange->list_accessLocations.size() + tailSubrange->list_accessLocations.size() == originalCount);
// split fixed reg requirements
for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++)
{
raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i;
if (tailInterval.ContainsEdge(fixedReg->pos))
{
tailSubrange->list_fixedRegRequirements.push_back(*fixedReg);
}
}
// remove tail fixed reg requirements from head
for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++)
{
raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i;
if (!headInterval.ContainsEdge(fixedReg->pos))
{
subrange->list_fixedRegRequirements.resize(i);
break;
}
}
// adjust intervals
subrange->interval = headInterval;
tailSubrange->interval = tailInterval;
// trim to hole
if(trimToUsage)
{
if(subrange->list_accessLocations.empty() && (subrange->interval.start.IsInstructionIndex() && subrange->interval.end.IsInstructionIndex()))
{
IMLRA_DeleteRange(ppcImlGenContext, subrange);
subrange = nullptr;
}
else
{
IMLRA_TrimRangeToUse(subrange);
}
if(tailSubrange->list_accessLocations.empty() && (tailSubrange->interval.start.IsInstructionIndex() && tailSubrange->interval.end.IsInstructionIndex()))
{
IMLRA_DeleteRange(ppcImlGenContext, tailSubrange);
tailSubrange = nullptr;
}
else
{
IMLRA_TrimRangeToUse(tailSubrange);
}
}
// validation
cemu_assert_debug(!subrange || subrange->interval.start <= subrange->interval.end);
cemu_assert_debug(!tailSubrange || tailSubrange->interval.start <= tailSubrange->interval.end);
cemu_assert_debug(!tailSubrange || tailSubrange->interval.start >= splitPosition);
if (!trimToUsage)
cemu_assert_debug(!tailSubrange || tailSubrange->interval.start == splitPosition);
if(subrange)
PPCRecRA_debugValidateSubrange(subrange);
if(tailSubrange)
PPCRecRA_debugValidateSubrange(tailSubrange);
return tailSubrange;
}
sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment)
{
sint32 v = imlSegment->loopDepth + 1;
v *= 5;
return v*v; // 25, 100, 225, 400
}
// calculate additional cost of range that it would have after calling _ExplodeRange() on it
sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange)
{
auto ranges = subrange->GetAllSubrangesInCluster();
sint32 cost = 0;//-PPCRecRARange_estimateTotalCost(ranges);
for (auto& subrange : ranges)
{
if (subrange->list_accessLocations.empty())
continue; // this range would be deleted and thus has no cost
sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment);
bool hasAdditionalLoad = subrange->interval.ExtendsPreviousSegment();
bool hasAdditionalStore = subrange->interval.ExtendsIntoNextSegment();
if(hasAdditionalLoad && subrange->list_accessLocations.front().IsWrite()) // if written before read then a load isn't necessary
{
cemu_assert_debug(!subrange->list_accessLocations.front().IsRead());
cost += segmentLoadStoreCost;
}
if(hasAdditionalStore)
{
bool hasWrite = std::find_if(subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != subrange->list_accessLocations.end();
if(!hasWrite) // ranges which don't modify their value do not need to be stored
cost += segmentLoadStoreCost;
}
}
// todo - properly calculating all the data-flow dependency based costs is more complex so this currently is an approximation
return cost;
}
sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition)
{
// validation
#ifdef CEMU_DEBUG_ASSERT
if (subrange->interval.ExtendsIntoNextSegment())
assert_dbg();
#endif
cemu_assert_debug(splitPosition.IsInstructionIndex());
sint32 cost = 0;
// find split position in location list
if (subrange->list_accessLocations.empty())
return 0;
if (splitPosition <= subrange->list_accessLocations.front().pos)
return 0;
if (splitPosition > subrange->list_accessLocations.back().pos)
return 0;
size_t firstTailLocationIndex = 0;
for (size_t i = 0; i < subrange->list_accessLocations.size(); i++)
{
if (subrange->list_accessLocations[i].pos >= splitPosition)
{
firstTailLocationIndex = i;
break;
}
}
std::span<raAccessLocation> headLocations{subrange->list_accessLocations.data(), firstTailLocationIndex};
std::span<raAccessLocation> tailLocations{subrange->list_accessLocations.data() + firstTailLocationIndex, subrange->list_accessLocations.size() - firstTailLocationIndex};
cemu_assert_debug(headLocations.empty() || headLocations.back().pos < splitPosition);
cemu_assert_debug(tailLocations.empty() || tailLocations.front().pos >= splitPosition);
sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment);
auto CalculateCostFromLocationRange = [segmentLoadStoreCost](std::span<raAccessLocation> locations, bool trackLoadCost = true, bool trackStoreCost = true) -> sint32
{
if(locations.empty())
return 0;
sint32 cost = 0;
if(locations.front().IsRead() && trackLoadCost)
cost += segmentLoadStoreCost; // not overwritten, so there is a load cost
bool hasWrite = std::find_if(locations.begin(), locations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != locations.end();
if(hasWrite && trackStoreCost)
cost += segmentLoadStoreCost; // modified, so there is a store cost
return cost;
};
sint32 baseCost = CalculateCostFromLocationRange(subrange->list_accessLocations);
bool tailOverwritesValue = !tailLocations.empty() && !tailLocations.front().IsRead() && tailLocations.front().IsWrite();
sint32 newCost = CalculateCostFromLocationRange(headLocations) + CalculateCostFromLocationRange(tailLocations, !tailOverwritesValue, true);
cemu_assert_debug(newCost >= baseCost);
cost = newCost - baseCost;
return cost;
}

View file

@ -1,364 +0,0 @@
#pragma once
#include "IMLRegisterAllocator.h"
struct raLivenessSubrangeLink
{
struct raLivenessRange* prev;
struct raLivenessRange* next;
};
struct raInstructionEdge
{
friend struct raInterval;
public:
raInstructionEdge()
{
index = 0;
}
raInstructionEdge(sint32 instructionIndex, bool isInputEdge)
{
Set(instructionIndex, isInputEdge);
}
void Set(sint32 instructionIndex, bool isInputEdge)
{
if(instructionIndex == RA_INTER_RANGE_START || instructionIndex == RA_INTER_RANGE_END)
{
index = instructionIndex;
return;
}
index = instructionIndex * 2 + (isInputEdge ? 0 : 1);
cemu_assert_debug(index >= 0 && index < 0x100000*2); // make sure index value is sane
}
void SetRaw(sint32 index)
{
this->index = index;
cemu_assert_debug(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END || (index >= 0 && index < 0x100000*2)); // make sure index value is sane
}
// sint32 GetRaw()
// {
// this->index = index;
// }
std::string GetDebugString()
{
if(index == RA_INTER_RANGE_START)
return "RA_START";
else if(index == RA_INTER_RANGE_END)
return "RA_END";
std::string str = fmt::format("{}", GetInstructionIndex());
if(IsOnInputEdge())
str += "i";
else if(IsOnOutputEdge())
str += "o";
return str;
}
sint32 GetInstructionIndex() const
{
cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END);
return index >> 1;
}
// returns instruction index or RA_INTER_RANGE_START/RA_INTER_RANGE_END
sint32 GetInstructionIndexEx() const
{
if(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END)
return index;
return index >> 1;
}
sint32 GetRaw() const
{
return index;
}
bool IsOnInputEdge() const
{
cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END);
return (index&1) == 0;
}
bool IsOnOutputEdge() const
{
cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END);
return (index&1) != 0;
}
bool ConnectsToPreviousSegment() const
{
return index == RA_INTER_RANGE_START;
}
bool ConnectsToNextSegment() const
{
return index == RA_INTER_RANGE_END;
}
bool IsInstructionIndex() const
{
return index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END;
}
// comparison operators
bool operator>(const raInstructionEdge& other) const
{
return index > other.index;
}
bool operator<(const raInstructionEdge& other) const
{
return index < other.index;
}
bool operator<=(const raInstructionEdge& other) const
{
return index <= other.index;
}
bool operator>=(const raInstructionEdge& other) const
{
return index >= other.index;
}
bool operator==(const raInstructionEdge& other) const
{
return index == other.index;
}
raInstructionEdge operator+(sint32 offset) const
{
cemu_assert_debug(IsInstructionIndex());
cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END);
raInstructionEdge edge;
edge.index = index + offset;
return edge;
}
raInstructionEdge operator-(sint32 offset) const
{
cemu_assert_debug(IsInstructionIndex());
cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END);
raInstructionEdge edge;
edge.index = index - offset;
return edge;
}
raInstructionEdge& operator++()
{
cemu_assert_debug(IsInstructionIndex());
index++;
return *this;
}
private:
sint32 index; // can also be RA_INTER_RANGE_START or RA_INTER_RANGE_END, otherwise contains instruction index * 2
};
struct raAccessLocation
{
raAccessLocation(raInstructionEdge pos) : pos(pos) {}
bool IsRead() const
{
return pos.IsOnInputEdge();
}
bool IsWrite() const
{
return pos.IsOnOutputEdge();
}
raInstructionEdge pos;
};
struct raInterval
{
raInterval()
{
}
raInterval(raInstructionEdge start, raInstructionEdge end)
{
SetInterval(start, end);
}
// isStartOnInput = Input+Output edge on first instruction. If false then only output
// isEndOnOutput = Input+Output edge on last instruction. If false then only input
void SetInterval(sint32 start, bool isStartOnInput, sint32 end, bool isEndOnOutput)
{
this->start.Set(start, isStartOnInput);
this->end.Set(end, !isEndOnOutput);
}
void SetInterval(raInstructionEdge start, raInstructionEdge end)
{
cemu_assert_debug(start <= end);
this->start = start;
this->end = end;
}
void SetStart(const raInstructionEdge& edge)
{
start = edge;
}
void SetEnd(const raInstructionEdge& edge)
{
end = edge;
}
sint32 GetStartIndex() const
{
return start.GetInstructionIndex();
}
sint32 GetEndIndex() const
{
return end.GetInstructionIndex();
}
bool ExtendsPreviousSegment() const
{
return start.ConnectsToPreviousSegment();
}
bool ExtendsIntoNextSegment() const
{
return end.ConnectsToNextSegment();
}
bool IsNextSegmentOnly() const
{
return start.ConnectsToNextSegment() && end.ConnectsToNextSegment();
}
bool IsPreviousSegmentOnly() const
{
return start.ConnectsToPreviousSegment() && end.ConnectsToPreviousSegment();
}
// returns true if range is contained within a single segment
bool IsLocal() const
{
return start.GetRaw() > RA_INTER_RANGE_START && end.GetRaw() < RA_INTER_RANGE_END;
}
bool ContainsInstructionIndex(sint32 instructionIndex) const
{
cemu_assert_debug(instructionIndex != RA_INTER_RANGE_START && instructionIndex != RA_INTER_RANGE_END);
return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx();
}
// similar to ContainsInstructionIndex, but allows RA_INTER_RANGE_START/END as input
bool ContainsInstructionIndexEx(sint32 instructionIndex) const
{
if(instructionIndex == RA_INTER_RANGE_START)
return start.ConnectsToPreviousSegment();
if(instructionIndex == RA_INTER_RANGE_END)
return end.ConnectsToNextSegment();
return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx();
}
bool ContainsEdge(const raInstructionEdge& edge) const
{
return edge >= start && edge <= end;
}
bool ContainsWholeInterval(const raInterval& other) const
{
return other.start >= start && other.end <= end;
}
bool IsOverlapping(const raInterval& other) const
{
return start <= other.end && end >= other.start;
}
sint32 GetPreciseDistance()
{
cemu_assert_debug(!start.ConnectsToNextSegment()); // how to handle this?
if(start == end)
return 1;
cemu_assert_debug(!end.ConnectsToPreviousSegment() && !end.ConnectsToNextSegment());
if(start.ConnectsToPreviousSegment())
return end.GetRaw() + 1;
return end.GetRaw() - start.GetRaw() + 1; // +1 because end is inclusive
}
//private: not making these directly accessible only forces us to create loads of verbose getters and setters
raInstructionEdge start;
raInstructionEdge end;
};
struct raFixedRegRequirement
{
raInstructionEdge pos;
IMLPhysRegisterSet allowedReg;
};
struct raLivenessRange
{
IMLSegment* imlSegment;
raInterval interval;
// dirty state tracking
bool _noLoad;
bool hasStore;
bool hasStoreDelayed;
// next
raLivenessRange* subrangeBranchTaken;
raLivenessRange* subrangeBranchNotTaken;
// reverse counterpart of BranchTaken/BranchNotTaken
boost::container::small_vector<raLivenessRange*, 4> previousRanges;
// processing
uint32 lastIterationIndex;
// instruction read/write locations
std::vector<raAccessLocation> list_accessLocations;
// ordered list of all raInstructionEdge indices which require a fixed register
std::vector<raFixedRegRequirement> list_fixedRegRequirements;
// linked list (subranges with same GPR virtual register)
raLivenessSubrangeLink link_sameVirtualRegister;
// linked list (all subranges for this segment)
raLivenessSubrangeLink link_allSegmentRanges;
// register info
IMLRegID virtualRegister;
IMLName name;
// register allocator result
IMLPhysReg physicalRegister;
boost::container::small_vector<raLivenessRange*, 128> GetAllSubrangesInCluster();
bool GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters); // if the cluster has fixed register requirements in any instruction this returns the combined register mask. Otherwise returns false in which case allowedRegisters is left undefined
IMLPhysRegisterSet GetAllowedRegisters(IMLPhysRegisterSet regPool); // return regPool with fixed register requirements filtered out
IMLRegID GetVirtualRegister() const;
sint32 GetPhysicalRegister() const;
bool HasPhysicalRegister() const { return physicalRegister >= 0; }
IMLName GetName() const;
void SetPhysicalRegister(IMLPhysReg physicalRegister);
void SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister);
void UnsetPhysicalRegister() { physicalRegister = -1; }
private:
void GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs);
};
raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition);
void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange);
void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext);
void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange);
void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange);
raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage = false);
void PPCRecRA_debugValidateSubrange(raLivenessRange* subrange);
// cost estimation
sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment);
sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange);
//sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessRange* subrange, sint32 splitIndex);
sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition);

View file

@ -1,133 +0,0 @@
#include "IMLInstruction.h"
#include "IMLSegment.h"
void IMLSegment::SetEnterable(uint32 enterAddress)
{
cemu_assert_debug(!isEnterable || enterPPCAddress == enterAddress);
isEnterable = true;
enterPPCAddress = enterAddress;
}
bool IMLSegment::HasSuffixInstruction() const
{
if (imlList.empty())
return false;
const IMLInstruction& imlInstruction = imlList.back();
return imlInstruction.IsSuffixInstruction();
}
sint32 IMLSegment::GetSuffixInstructionIndex() const
{
cemu_assert_debug(HasSuffixInstruction());
return (sint32)(imlList.size() - 1);
}
IMLInstruction* IMLSegment::GetLastInstruction()
{
if (imlList.empty())
return nullptr;
return &imlList.back();
}
void IMLSegment::SetLinkBranchNotTaken(IMLSegment* imlSegmentDst)
{
if (nextSegmentBranchNotTaken)
nextSegmentBranchNotTaken->list_prevSegments.erase(std::find(nextSegmentBranchNotTaken->list_prevSegments.begin(), nextSegmentBranchNotTaken->list_prevSegments.end(), this));
nextSegmentBranchNotTaken = imlSegmentDst;
if(imlSegmentDst)
imlSegmentDst->list_prevSegments.push_back(this);
}
void IMLSegment::SetLinkBranchTaken(IMLSegment* imlSegmentDst)
{
if (nextSegmentBranchTaken)
nextSegmentBranchTaken->list_prevSegments.erase(std::find(nextSegmentBranchTaken->list_prevSegments.begin(), nextSegmentBranchTaken->list_prevSegments.end(), this));
nextSegmentBranchTaken = imlSegmentDst;
if (imlSegmentDst)
imlSegmentDst->list_prevSegments.push_back(this);
}
IMLInstruction* IMLSegment::AppendInstruction()
{
IMLInstruction& inst = imlList.emplace_back();
memset(&inst, 0, sizeof(IMLInstruction));
return &inst;
}
void IMLSegment_SetLinkBranchNotTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst)
{
// make sure segments aren't already linked
if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst)
return;
// add as next segment for source
if (imlSegmentSrc->nextSegmentBranchNotTaken != nullptr)
assert_dbg();
imlSegmentSrc->nextSegmentBranchNotTaken = imlSegmentDst;
// add as previous segment for destination
imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc);
}
void IMLSegment_SetLinkBranchTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst)
{
// make sure segments aren't already linked
if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst)
return;
// add as next segment for source
if (imlSegmentSrc->nextSegmentBranchTaken != nullptr)
assert_dbg();
imlSegmentSrc->nextSegmentBranchTaken = imlSegmentDst;
// add as previous segment for destination
imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc);
}
void IMLSegment_RemoveLink(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst)
{
if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst)
{
imlSegmentSrc->nextSegmentBranchNotTaken = nullptr;
}
else if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst)
{
imlSegmentSrc->nextSegmentBranchTaken = nullptr;
}
else
assert_dbg();
bool matchFound = false;
for (sint32 i = 0; i < imlSegmentDst->list_prevSegments.size(); i++)
{
if (imlSegmentDst->list_prevSegments[i] == imlSegmentSrc)
{
imlSegmentDst->list_prevSegments.erase(imlSegmentDst->list_prevSegments.begin() + i);
matchFound = true;
break;
}
}
if (matchFound == false)
assert_dbg();
}
/*
* Replaces all links to segment orig with linkts to segment new
*/
void IMLSegment_RelinkInputSegment(IMLSegment* imlSegmentOrig, IMLSegment* imlSegmentNew)
{
while (imlSegmentOrig->list_prevSegments.size() != 0)
{
IMLSegment* prevSegment = imlSegmentOrig->list_prevSegments[0];
if (prevSegment->nextSegmentBranchNotTaken == imlSegmentOrig)
{
IMLSegment_RemoveLink(prevSegment, imlSegmentOrig);
IMLSegment_SetLinkBranchNotTaken(prevSegment, imlSegmentNew);
}
else if (prevSegment->nextSegmentBranchTaken == imlSegmentOrig)
{
IMLSegment_RemoveLink(prevSegment, imlSegmentOrig);
IMLSegment_SetLinkBranchTaken(prevSegment, imlSegmentNew);
}
else
{
assert_dbg();
}
}
}

Some files were not shown because too many files have changed in this diff Show more