mirror of
https://github.com/izzy2lost/xenia-edge.git
synced 2026-07-06 00:20:26 -07:00
Merge commit '4fcb8e4498aca8bfb13a8150c2e6375a58fdf767' into canary-rebase
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
name: Commit Message Check
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
commit-message:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate commit messages
|
||||
run: |
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
HEAD="${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
COMMITS=$(git log --format="%H %s" "${BASE}..${HEAD}" --)
|
||||
|
||||
if [ -z "${COMMITS}" ]; then
|
||||
echo "No commits found in this PR."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FAILED=0
|
||||
while IFS= read -r LINE; do
|
||||
SHA="${LINE%% *}"
|
||||
MSG="${LINE#* }"
|
||||
SHORT="${SHA::7}"
|
||||
|
||||
if echo "${MSG}" | grep -qP '^\[.+?\]'; then
|
||||
echo "✅ ${SHORT}: ${MSG}"
|
||||
else
|
||||
echo "❌ ${SHORT}: ${MSG}"
|
||||
echo "::error::Commit ${SHORT} is missing a [Tag] prefix. Expected format: [Tag] Description (e.g. [CPU] Fix overflow in JIT)"
|
||||
FAILED=1
|
||||
fi
|
||||
done <<< "${COMMITS}"
|
||||
|
||||
echo ""
|
||||
|
||||
if [ "${FAILED}" -eq 1 ]; then
|
||||
echo "::error::One or more commits are missing a [Tag] prefix."
|
||||
echo ""
|
||||
echo "Expected format: [Tag] Description"
|
||||
echo "Examples: [CPU] <commit message>"
|
||||
echo " [GPU] <commit message>"
|
||||
echo " [UI] <commit message>"
|
||||
echo " [CI] <commit message>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All commit messages have a valid [Tag] prefix."
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
#LLVM_VERSION: ${{ steps.setup.outputs.LLVM_VERSION }}
|
||||
UBUNTU_BASE: ${{ steps.setup.outputs.UBUNTU_BASE }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup
|
||||
id: setup
|
||||
env:
|
||||
LLVM_VERSION: 20
|
||||
run: |
|
||||
UBUNTU_BASE=$(lsb_release -cs)
|
||||
#echo "LLVM_VERSION=$LLVM_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "UBUNTU_BASE=$UBUNTU_BASE" >> "$GITHUB_OUTPUT"
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||
sudo apt-add-repository "deb http://apt.llvm.org/${UBUNTU_BASE}/ llvm-toolchain-${UBUNTU_BASE}-$LLVM_VERSION main"
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install clang-format-$LLVM_VERSION
|
||||
- name: Lint
|
||||
run: ./xenia-build.py lint --all
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
name: Linux Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
config:
|
||||
description: 'Build configuration (Release)'
|
||||
required: false
|
||||
default: 'release'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
LLVM_VERSION: 20
|
||||
UBUNTU_BASE: noble # We're running on ubuntu-24.04. Remember to change it after changing deploy env.
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Vulkan SDK
|
||||
id: cache-vulkan-sdk-linux
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/vulkan-sdk
|
||||
key: ${{ runner.os }}-vulkan-sdk-latest
|
||||
|
||||
- name: Cache linuxdeploy tools
|
||||
id: cache-linuxdeploy
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/linuxdeploy
|
||||
key: ${{ runner.os }}-linuxdeploy-tools
|
||||
|
||||
- name: Setup build environment
|
||||
run: |
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||
sudo apt-add-repository "deb http://apt.llvm.org/${{ env.UBUNTU_BASE }}/ llvm-toolchain-${{ env.UBUNTU_BASE }}-${{ env.LLVM_VERSION }} main"
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install mesa-vulkan-drivers valgrind libc++-dev libc++abi-dev libgtk-3-dev libsdl2-dev libvulkan-dev libx11-xcb-dev clang-${{ env.LLVM_VERSION }} lld-${{ env.LLVM_VERSION }} ninja-build cmake spirv-tools libfuse2
|
||||
|
||||
# Pin LLVM tools to the correct version so system ar/ranlib/lld
|
||||
# don't use the older LLVM 17 gold plugin with clang-20 LTO bitcode
|
||||
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${{ env.LLVM_VERSION }} 200
|
||||
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${{ env.LLVM_VERSION }} 200
|
||||
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-${{ env.LLVM_VERSION }} 200
|
||||
sudo update-alternatives --install /usr/bin/ld.lld ld.lld /usr/bin/ld.lld-${{ env.LLVM_VERSION }} 200
|
||||
sudo update-alternatives --install /usr/bin/llvm-ar llvm-ar /usr/bin/llvm-ar-${{ env.LLVM_VERSION }} 200
|
||||
sudo update-alternatives --install /usr/bin/llvm-ranlib llvm-ranlib /usr/bin/llvm-ranlib-${{ env.LLVM_VERSION }} 200
|
||||
sudo update-alternatives --install /usr/bin/llvm-nm llvm-nm /usr/bin/llvm-nm-${{ env.LLVM_VERSION }} 200
|
||||
|
||||
# Download linuxdeploy tools if not cached
|
||||
if [ '${{ steps.cache-linuxdeploy.outputs.cache-hit }}' != 'true' ]; then
|
||||
mkdir -p ~/linuxdeploy
|
||||
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -O ~/linuxdeploy/linuxdeploy
|
||||
chmod +x ~/linuxdeploy/linuxdeploy
|
||||
fi
|
||||
echo "$HOME/linuxdeploy" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Vulkan SDK
|
||||
run: |
|
||||
# Install Vulkan SDK
|
||||
if [ '${{ steps.cache-vulkan-sdk-linux.outputs.cache-hit }}' != 'true' ]; then
|
||||
wget -qO vulkan-sdk.tar.xz https://sdk.lunarg.com/sdk/download/latest/linux/vulkan-sdk.tar.xz
|
||||
mkdir -p ~/vulkan-sdk
|
||||
tar -xf vulkan-sdk.tar.xz -C ~/vulkan-sdk
|
||||
fi
|
||||
|
||||
VULKAN_SDK_VERSION=$(ls ~/vulkan-sdk)
|
||||
echo "VULKAN_SDK=$HOME/vulkan-sdk/$VULKAN_SDK_VERSION/x86_64" >> $GITHUB_ENV
|
||||
echo "$HOME/vulkan-sdk/$VULKAN_SDK_VERSION/x86_64/bin" >> $GITHUB_PATH
|
||||
|
||||
# Verify shader tools are available
|
||||
for tool in glslangValidator spirv-opt spirv-dis; do
|
||||
which "$tool" && "$tool" --version 2>/dev/null || echo "Warning: $tool not found"
|
||||
done
|
||||
|
||||
- name: Download submodules
|
||||
run: |
|
||||
# Exclude not needed 3pp modules
|
||||
EXCLUDE="DirectXShaderCompiler"
|
||||
SUBMODULES=$(grep -oP '(?<=path = ).+' .gitmodules | grep -vE "$EXCLUDE")
|
||||
git submodule update --init --depth=1 -j$(nproc) $SUBMODULES
|
||||
|
||||
- name: Build Xenia
|
||||
env:
|
||||
CC: clang-${{ env.LLVM_VERSION }}
|
||||
CXX: clang++-${{ env.LLVM_VERSION }}
|
||||
run: ./xenia-build.py build --config=Release
|
||||
|
||||
- name: Prepare AppImage
|
||||
id: prepare_artifacts
|
||||
run: |
|
||||
binary=build/bin/Linux/Release/xenia_canary
|
||||
if [ $(stat -c%s $binary) -le 100000 ]; then
|
||||
echo "::error::Binary is too small."
|
||||
exit 1
|
||||
fi
|
||||
chmod +x $binary
|
||||
|
||||
# Set up AppDir structure
|
||||
APPDIR=artifacts/xenia_canary
|
||||
mkdir -p $APPDIR/usr/bin
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/16x16/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/32x32/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/48x48/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/64x64/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/128x128/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/256x256/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/512x512/apps
|
||||
mkdir -p $APPDIR/usr/share/icons/hicolor/1024x1024/apps
|
||||
|
||||
# Install icons
|
||||
cp assets/icon/16.png $APPDIR/usr/share/icons/hicolor/16x16/apps/xenia_canary.png
|
||||
cp assets/icon/32.png $APPDIR/usr/share/icons/hicolor/32x32/apps/xenia_canary.png
|
||||
cp assets/icon/48.png $APPDIR/usr/share/icons/hicolor/48x48/apps/xenia_canary.png
|
||||
cp assets/icon/64.png $APPDIR/usr/share/icons/hicolor/64x64/apps/xenia_canary.png
|
||||
cp assets/icon/128.png $APPDIR/usr/share/icons/hicolor/128x128/apps/xenia_canary.png
|
||||
cp assets/icon/256.png $APPDIR/usr/share/icons/hicolor/256x256/apps/xenia_canary.png
|
||||
cp assets/icon/512.png $APPDIR/usr/share/icons/hicolor/512x512/apps/xenia_canary.png
|
||||
cp assets/icon/1024.png $APPDIR/usr/share/icons/hicolor/1024x1024/apps/xenia_canary.png
|
||||
|
||||
# Copy any extra runtime data directories alongside the binary
|
||||
find build/bin/Linux/Release -maxdepth 1 -type d ! -name Release -exec cp -r {} $APPDIR/usr/bin/ \;
|
||||
|
||||
# Build AppImage with linuxdeploy (requires absolute paths)
|
||||
linuxdeploy --appdir $APPDIR \
|
||||
--executable $binary \
|
||||
--desktop-file $GITHUB_WORKSPACE/assets/xenia_canary.desktop \
|
||||
--icon-file $GITHUB_WORKSPACE/assets/icon/256.png \
|
||||
--output appimage
|
||||
|
||||
# Find the generated AppImage and move to release directory
|
||||
appimage_file=$(ls -1 *.AppImage | head -n1)
|
||||
if [ -z "$appimage_file" ]; then
|
||||
echo "::error::AppImage file not found after linuxdeploy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p artifacts/release
|
||||
mv "$appimage_file" artifacts/release/xenia_canary_linux.AppImage
|
||||
chmod +x artifacts/release/xenia_canary_linux.AppImage
|
||||
|
||||
- name: Upload Xenia Canary artifact
|
||||
if: steps.prepare_artifacts.outcome == 'success'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xenia_canary_linux
|
||||
path: artifacts/release
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -1,88 +0,0 @@
|
||||
name: Orchestrator
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'docs/**'
|
||||
- 'LICENSE'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'docs/**'
|
||||
- 'LICENSE'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ===========================================================================
|
||||
# Pre-requirements
|
||||
# ===========================================================================
|
||||
commit-message:
|
||||
name: Commit Message Validation
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/workflows/Check_commit_message.yml
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
uses: ./.github/workflows/Lint.yml
|
||||
|
||||
# Add optional steps here
|
||||
|
||||
# ===========================================================================
|
||||
# Stage 2: Platform builds
|
||||
# ===========================================================================
|
||||
build-windows:
|
||||
name: Windows (x86-64)
|
||||
needs: [lint, commit-message]
|
||||
if: ${{ !failure() && !cancelled() }}
|
||||
uses: ./.github/workflows/Windows_x86.yml
|
||||
|
||||
build-linux:
|
||||
name: Linux (x86-64)
|
||||
needs: [lint, commit-message]
|
||||
if: ${{ !failure() && !cancelled() }}
|
||||
uses: ./.github/workflows/Linux_x86.yml
|
||||
|
||||
# Uncomment when platform support is ready:
|
||||
# build-windows-arm64:
|
||||
# name: Windows-ARM64
|
||||
# needs: [lint]
|
||||
# uses: ./.github/workflows/build-win_arm64.yml
|
||||
|
||||
# build-linux-arm64:
|
||||
# name: Linux-ARM64
|
||||
# needs: [lint]
|
||||
# uses: ./.github/workflows/build-linux_arm64.yml
|
||||
|
||||
# build-macos:
|
||||
# name: macOS
|
||||
# needs: [lint]
|
||||
# uses: ./.github/workflows/build-macos.yml
|
||||
|
||||
# build-android:
|
||||
# name: Android
|
||||
# needs: [lint]
|
||||
# uses: ./.github/workflows/build-android.yml
|
||||
|
||||
# ===========================================================================
|
||||
# Stage 3: Release
|
||||
# ===========================================================================
|
||||
release:
|
||||
name: Create Release
|
||||
needs: [build-windows, build-linux]
|
||||
if: |
|
||||
always() &&
|
||||
github.repository == 'xenia-canary/xenia-canary' &&
|
||||
github.event_name == 'push' &&
|
||||
github.ref == 'refs/heads/canary_experimental' &&
|
||||
(needs.build-windows.result == 'success' && needs.build-linux.result == 'success')
|
||||
uses: ./.github/workflows/Create_release.yml
|
||||
permissions:
|
||||
contents: write
|
||||
secrets: inherit
|
||||
with:
|
||||
tag: ${{ github.sha }}
|
||||
branch: ${{ github.ref_name }}
|
||||
@@ -1,92 +0,0 @@
|
||||
name: Windows (x86-64)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
config:
|
||||
description: 'Build configuration (Release)'
|
||||
required: false
|
||||
default: 'release'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2025
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Vulkan SDK
|
||||
id: cache-vulkan-sdk
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: C:\VulkanSDK
|
||||
key: ${{ runner.os }}-vulkan-sdk-${{ hashFiles('**/vulkan-sdk.exe') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-vulkan-sdk-
|
||||
|
||||
- name: Install Vulkan SDK
|
||||
run: |
|
||||
# Install Vulkan SDK with spirv-tools
|
||||
if (Test-Path -Path "C:\VulkanSDK") {
|
||||
echo "Vulkan SDK found in cache."
|
||||
} else {
|
||||
Invoke-WebRequest -Uri "https://sdk.lunarg.com/sdk/download/latest/windows/vulkan-sdk.exe" -OutFile "vulkan-sdk.exe"
|
||||
Start-Process -FilePath "vulkan-sdk.exe" -ArgumentList "--accept-licenses", "--default-answer", "--confirm-command", "install" -Wait
|
||||
}
|
||||
$env:VULKAN_SDK = "C:\VulkanSDK\$(Get-ChildItem -Path 'C:\VulkanSDK' -Directory | Select-Object -First 1 -ExpandProperty Name)"
|
||||
$env:PATH = "$env:VULKAN_SDK\Bin;$env:PATH"
|
||||
echo "VULKAN_SDK=$env:VULKAN_SDK" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "$env:VULKAN_SDK\Bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
# Verify shader tools are available
|
||||
foreach ($tool in @("glslangValidator", "spirv-opt", "spirv-dis")) {
|
||||
$toolPath = "$env:VULKAN_SDK\Bin\$tool.exe"
|
||||
if (Test-Path $toolPath) {
|
||||
echo "$tool found at: $toolPath"
|
||||
& $toolPath --version 2>$null
|
||||
} else {
|
||||
echo "Warning: $tool.exe not found at expected location"
|
||||
}
|
||||
}
|
||||
|
||||
# Verify FXC is available (from Windows SDK)
|
||||
$fxcPaths = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\fxc.exe" -ErrorAction SilentlyContinue | Sort-Object FullName
|
||||
if ($fxcPaths) {
|
||||
$fxcPath = $fxcPaths[-1].FullName
|
||||
echo "FXC found at: $fxcPath"
|
||||
} else {
|
||||
echo "Warning: fxc.exe not found in Windows SDK"
|
||||
}
|
||||
|
||||
- name: Download submodules
|
||||
run: git submodule update --init --depth=1 -j $env:NUMBER_OF_PROCESSORS
|
||||
|
||||
- name: Build Xenia
|
||||
run: python xenia-build.py build --config=Release --target=xenia-app
|
||||
|
||||
- name: Prepare artifacts
|
||||
id: prepare_artifacts
|
||||
run: |
|
||||
if ((Get-Item 'build\bin\Windows\Release\xenia_canary.exe').Length -le 100000) {
|
||||
echo "::error:: Executable is too small."
|
||||
exit 1
|
||||
}
|
||||
robocopy . build\bin\Windows\Release LICENSE /r:0 /w:0
|
||||
robocopy build\bin\Windows\Release artifacts\xenia_canary xenia_canary.exe LICENSE /r:0 /w:0
|
||||
If ($LastExitCode -le 7) { echo "LastExitCode = $LastExitCode";$LastExitCode = 0 }
|
||||
|
||||
- name: Upload Xenia Canary artifact
|
||||
if: steps.prepare_artifacts.outcome == 'success'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: xenia_canary_windows
|
||||
path: artifacts\xenia_canary
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -604,7 +604,7 @@ class IConfigVarUpdate {
|
||||
// If you're reviewing a pull request with a change here, check if 1) has been
|
||||
// done by the submitter before merging.
|
||||
static constexpr uint32_t kLastCommittedUpdateDate =
|
||||
MakeConfigVarUpdateDate(2026, 2, 16, 12);
|
||||
MakeConfigVarUpdateDate(2026, 4, 9, 12);
|
||||
|
||||
virtual ~IConfigVarUpdate() = default;
|
||||
|
||||
|
||||
@@ -85,14 +85,15 @@ TEST_CASE("heap_alloc_basic", "[heap]") {
|
||||
TEST_CASE("heap_alloc_top_down", "[heap]") {
|
||||
TestHeap h(0x80000000, 0x100000, 0x1000);
|
||||
|
||||
// Top-down treats high_page_number as exclusive, so the top page is
|
||||
// never handed out.
|
||||
uint32_t addr = 0;
|
||||
REQUIRE(h.Alloc(0x1000, 0x1000, true, &addr));
|
||||
// Top-down: should be at the highest aligned address.
|
||||
REQUIRE(addr == 0x800FF000);
|
||||
REQUIRE(addr == 0x800FE000);
|
||||
REQUIRE(h.unreserved_page_count() == 255);
|
||||
|
||||
REQUIRE(h.Alloc(0x2000, 0x1000, true, &addr));
|
||||
REQUIRE(addr == 0x800FD000);
|
||||
REQUIRE(addr == 0x800FC000);
|
||||
REQUIRE(h.unreserved_page_count() == 253);
|
||||
}
|
||||
|
||||
@@ -237,16 +238,18 @@ TEST_CASE("heap_alloc_alignment_top_down", "[heap]") {
|
||||
// 1MB heap, 4KB pages
|
||||
TestHeap h(0x80000000, 0x100000, 0x1000);
|
||||
|
||||
// Allocate 1 page at the top.
|
||||
// Top-down skips the top page (0x800FF000), so a 1-page allocation
|
||||
// lands on page 0xFE.
|
||||
uint32_t first = 0;
|
||||
REQUIRE(h.Alloc(0x1000, 0x1000, true, &first));
|
||||
REQUIRE(first == 0x800FF000);
|
||||
REQUIRE(first == 0x800FE000);
|
||||
|
||||
// Allocate with 64KB alignment top-down — should align down.
|
||||
// 64KB-aligned top-down: stride 16, exclusive high at page 0xFF, so
|
||||
// the highest aligned base is page 0xE0.
|
||||
uint32_t aligned = 0;
|
||||
REQUIRE(h.Alloc(0x1000, 0x10000, true, &aligned));
|
||||
REQUIRE((aligned % 0x10000) == 0);
|
||||
REQUIRE(aligned == 0x800F0000);
|
||||
REQUIRE(aligned == 0x800E0000);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/memory.h"
|
||||
|
||||
#include "third_party/catch/include/catch.hpp"
|
||||
|
||||
#include "xenia/base/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace test {
|
||||
|
||||
// All tests use kMemoryAllocationReserve which only touches the page table,
|
||||
// not host memory. This lets us pass nullptr for membase and Memory*.
|
||||
|
||||
TEST_CASE("PhysicalHeap::GetPhysicalAddress", "[memory]") {
|
||||
VirtualHeap parent;
|
||||
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
|
||||
0x20000000, 4096);
|
||||
|
||||
SECTION("heap with no offset returns heap-relative address") {
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
|
||||
0x20000000, 64 * 1024, &parent);
|
||||
|
||||
REQUIRE(heap.host_address_offset() == 0);
|
||||
REQUIRE(heap.GetPhysicalAddress(0xA0000000) == 0);
|
||||
REQUIRE(heap.GetPhysicalAddress(0xA0010000) == 0x10000);
|
||||
REQUIRE(heap.GetPhysicalAddress(0xA1000000) == 0x1000000);
|
||||
}
|
||||
|
||||
SECTION("0xE0000000 heap always has 0x1000 physical offset") {
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xE0000000,
|
||||
0x1FD00000, 4096, &parent);
|
||||
|
||||
// The 0x1000 physical offset is baked into the view mapping
|
||||
// (map_info target_address), not derived from host_address_offset.
|
||||
REQUIRE(heap.GetPhysicalAddress(0xE0000000) == 0x1000);
|
||||
REQUIRE(heap.GetPhysicalAddress(0xE0001000) == 0x2000);
|
||||
REQUIRE(heap.GetPhysicalAddress(0xE0010000) == 0x11000);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("PhysicalHeap::Alloc alignment", "[memory]") {
|
||||
VirtualHeap parent;
|
||||
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
|
||||
0x20000000, 4096);
|
||||
|
||||
SECTION("returned address is page-aligned") {
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
|
||||
0x20000000, 64 * 1024, &parent);
|
||||
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.Alloc(0x10000, 0x10000, kMemoryAllocationReserve,
|
||||
kMemoryProtectRead, false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr != 0);
|
||||
REQUIRE(addr % 0x10000 == 0);
|
||||
REQUIRE(addr >= 0xA0000000);
|
||||
REQUIRE(addr < 0xC0000000);
|
||||
}
|
||||
|
||||
SECTION("multiple allocations with different alignments") {
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
|
||||
0x20000000, 64 * 1024, &parent);
|
||||
|
||||
for (uint32_t alignment : {0x10000u, 0x20000u, 0x40000u, 0x100000u}) {
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.Alloc(alignment, alignment, kMemoryAllocationReserve,
|
||||
kMemoryProtectRead, false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr % alignment == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("PhysicalHeap::AllocRange alignment", "[memory]") {
|
||||
VirtualHeap parent;
|
||||
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
|
||||
0x20000000, 4096);
|
||||
|
||||
SECTION("returned address respects alignment within range") {
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
|
||||
0x20000000, 64 * 1024, &parent);
|
||||
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.AllocRange(0xA0000000, 0xBFFFFFFF, 0x10000, 0x10000,
|
||||
kMemoryAllocationReserve, kMemoryProtectRead,
|
||||
false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr % 0x10000 == 0);
|
||||
REQUIRE(addr >= 0xA0000000);
|
||||
REQUIRE(addr <= 0xBFFFFFFF);
|
||||
}
|
||||
|
||||
SECTION("large alignment preserved through translation") {
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xC0000000,
|
||||
0x20000000, 16 * 1024 * 1024, &parent);
|
||||
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.AllocRange(0xC0000000, 0xDFFFFFFF, 0x1000000, 0x1000000,
|
||||
kMemoryAllocationReserve, kMemoryProtectRead,
|
||||
false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr % 0x1000000 == 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("PhysicalHeap::AllocFixed alignment", "[memory]") {
|
||||
VirtualHeap parent;
|
||||
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
|
||||
0x20000000, 4096);
|
||||
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
|
||||
0x20000000, 64 * 1024, &parent);
|
||||
|
||||
// AllocFixed at a specific aligned address must succeed
|
||||
bool ok = heap.AllocFixed(0xA0100000, 0x10000, 0x10000,
|
||||
kMemoryAllocationReserve, kMemoryProtectRead);
|
||||
REQUIRE(ok);
|
||||
}
|
||||
|
||||
TEST_CASE("PhysicalHeap vE0000000 alignment", "[memory]") {
|
||||
VirtualHeap parent;
|
||||
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
|
||||
0x20000000, 4096);
|
||||
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xE0000000,
|
||||
0x1FD00000, 4096, &parent);
|
||||
|
||||
// The 0xE0000000 heap always has a 0x1000 physical offset, so the
|
||||
// translation offset is 0xE0000000 - 0x1000 = 0xDFFFF000, which is
|
||||
// 4KB-aligned but not 64KB-aligned. This is true on all platforms.
|
||||
uint32_t physical_base = heap.GetPhysicalAddress(heap.heap_base());
|
||||
REQUIRE(physical_base == 0x1000);
|
||||
|
||||
SECTION("page-size allocation preserves alignment") {
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.Alloc(0x1000, 0x1000, kMemoryAllocationReserve,
|
||||
kMemoryProtectRead, false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr % 0x1000 == 0);
|
||||
REQUIRE(addr >= 0xE0000000);
|
||||
}
|
||||
|
||||
SECTION("translation offset is 4KB-aligned") {
|
||||
uint32_t translation_offset = heap.heap_base() - physical_base;
|
||||
REQUIRE(translation_offset % heap.page_size() == 0);
|
||||
}
|
||||
|
||||
SECTION("alloc with alignment larger than page_size is rejected") {
|
||||
// vE0000000 has a 0x1000 physical translation offset, so a 64KB
|
||||
// alignment request can't produce a 64KB-aligned guest address.
|
||||
// PhysicalHeap::Alloc forces top-down, which here lands one stride
|
||||
// past the end of the child heap and BaseHeap::AllocFixed rejects
|
||||
// it as out of range.
|
||||
uint32_t alignment = 0x10000; // 64KB
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.Alloc(0x10000, alignment, kMemoryAllocationReserve,
|
||||
kMemoryProtectRead, false, &addr);
|
||||
REQUIRE_FALSE(ok);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("PhysicalHeap vE0000000 AllocRange alignment", "[memory]") {
|
||||
VirtualHeap parent;
|
||||
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
|
||||
0x20000000, 4096);
|
||||
|
||||
PhysicalHeap heap;
|
||||
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xE0000000,
|
||||
0x1FD00000, 4096, &parent);
|
||||
|
||||
SECTION("page-aligned AllocRange succeeds") {
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.AllocRange(0xE0000000, 0xFFFCFFFF, 0x1000, 0x1000,
|
||||
kMemoryAllocationReserve, kMemoryProtectRead,
|
||||
false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr % 0x1000 == 0);
|
||||
}
|
||||
|
||||
SECTION("AllocRange with large alignment succeeds via bottom-up") {
|
||||
// Bottom-up search picks a low parent address that translates to a
|
||||
// guest address inside the child heap, so BaseHeap::AllocFixed accepts
|
||||
// it. The PhysicalHeap alignment check is host-based
|
||||
// ((addr + host_address_offset_) % alignment), so the misalignment of
|
||||
// the guest address itself is not rejected here.
|
||||
uint32_t alignment = 0x10000;
|
||||
uint32_t addr = 0;
|
||||
bool ok = heap.AllocRange(0xE0000000, 0xFFFCFFFF, 0x10000, alignment,
|
||||
kMemoryAllocationReserve, kMemoryProtectRead,
|
||||
false, &addr);
|
||||
REQUIRE(ok);
|
||||
REQUIRE(addr >= 0xE0000000);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace xe
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <sched.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#include <array>
|
||||
@@ -611,6 +612,7 @@ class PosixCondition<Thread> final : public PosixConditionBase {
|
||||
/// Thread::GetCurrentThread() on the main thread
|
||||
explicit PosixCondition(pthread_t thread)
|
||||
: thread_(thread),
|
||||
tid_(static_cast<pid_t>(syscall(SYS_gettid))),
|
||||
signaled_(false),
|
||||
exit_code_(0),
|
||||
state_(State::kRunning),
|
||||
@@ -742,31 +744,48 @@ class PosixCondition<Thread> final : public PosixConditionBase {
|
||||
|
||||
int priority() const {
|
||||
WaitStarted();
|
||||
int policy;
|
||||
sched_param param{};
|
||||
int ret = pthread_getschedparam(thread_, &policy, ¶m);
|
||||
if (ret != 0) {
|
||||
return -1;
|
||||
if (!fifo_failed_) {
|
||||
int policy;
|
||||
sched_param param{};
|
||||
int ret = pthread_getschedparam(thread_, &policy, ¶m);
|
||||
if (ret != 0) {
|
||||
return -1;
|
||||
}
|
||||
return param.sched_priority;
|
||||
}
|
||||
|
||||
return param.sched_priority;
|
||||
// When using nice values, map back to the SCHED_FIFO range (1-32)
|
||||
// so callers see a consistent priority space.
|
||||
int nice_val = getpriority(PRIO_PROCESS, tid_);
|
||||
// nice -19..19 → fifo 32..1
|
||||
return 16 - nice_val;
|
||||
}
|
||||
|
||||
void set_priority(int new_priority) const {
|
||||
WaitStarted();
|
||||
sched_param param{};
|
||||
param.sched_priority = new_priority;
|
||||
int res = pthread_setschedparam(thread_, SCHED_FIFO, ¶m);
|
||||
if (res != 0) {
|
||||
switch (res) {
|
||||
case EPERM:
|
||||
XELOGW("Permission denied while setting priority");
|
||||
break;
|
||||
case EINVAL:
|
||||
assert_always();
|
||||
default:
|
||||
XELOGW("Unknown error while setting priority");
|
||||
if (!fifo_failed_) {
|
||||
// Try real-time SCHED_FIFO for best priority control.
|
||||
sched_param param{};
|
||||
param.sched_priority = new_priority;
|
||||
int res = pthread_setschedparam(thread_, SCHED_FIFO, ¶m);
|
||||
if (res == 0) {
|
||||
return;
|
||||
}
|
||||
if (res == EPERM) {
|
||||
fifo_failed_ = true;
|
||||
} else {
|
||||
XELOGW("Unexpected error {} while setting SCHED_FIFO priority", res);
|
||||
fifo_failed_ = true;
|
||||
}
|
||||
}
|
||||
// Fall back to nice values under SCHED_OTHER.
|
||||
// Map SCHED_FIFO range (1-32) to nice range (19 to -19).
|
||||
// Center: fifo 16 → nice 0.
|
||||
int nice_val = 16 - new_priority;
|
||||
// Clamp to valid nice range.
|
||||
if (nice_val < -20) nice_val = -20;
|
||||
if (nice_val > 19) nice_val = 19;
|
||||
if (tid_ > 0) {
|
||||
setpriority(PRIO_PROCESS, tid_, nice_val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,6 +949,8 @@ class PosixCondition<Thread> final : public PosixConditionBase {
|
||||
sem_destroy(&suspend_sem_);
|
||||
}
|
||||
pthread_t thread_;
|
||||
pid_t tid_ = 0; // Kernel TID for setpriority() fallback
|
||||
mutable bool fifo_failed_ = false; // True after SCHED_FIFO was rejected
|
||||
bool signaled_;
|
||||
int exit_code_;
|
||||
State state_; // Protected by state_mutex_
|
||||
@@ -1243,6 +1264,7 @@ void* PosixCondition<Thread>::ThreadStartRoutine(void* parameter) {
|
||||
delete start_data;
|
||||
|
||||
current_thread_ = thread;
|
||||
thread->handle_.tid_ = static_cast<pid_t>(syscall(SYS_gettid));
|
||||
{
|
||||
std::unique_lock lock(thread->handle_.state_mutex_);
|
||||
thread->handle_.state_ =
|
||||
|
||||
@@ -867,101 +867,6 @@ struct MEMSET_I64
|
||||
EMITTER_OPCODE_TABLE(OPCODE_MEMSET, MEMSET_I64);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_ATOMIC_EXCHANGE
|
||||
// ============================================================================
|
||||
// Note: src1 is a HOST address (not guest), matching the x64 backend.
|
||||
struct ATOMIC_EXCHANGE_I8
|
||||
: Sequence<ATOMIC_EXCHANGE_I8,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I8Op, I64Op, I8Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
// src1 is already a host address.
|
||||
if (i.src1.is_constant) {
|
||||
e.mov(e.x4, i.src1.constant());
|
||||
} else {
|
||||
e.mov(e.x4, i.src1);
|
||||
}
|
||||
if (i.src2.is_constant) {
|
||||
e.mov(e.w0, static_cast<uint64_t>(
|
||||
static_cast<uint32_t>(i.src2.constant()) & 0xFF));
|
||||
} else {
|
||||
e.and_(e.w0, i.src2, 0xFF);
|
||||
}
|
||||
|
||||
if (e.IsFeatureEnabled(kA64EmitLSE)) {
|
||||
e.swpalb(e.w0, i.dest, ptr(e.x4));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& retry = e.NewCachedLabel();
|
||||
e.L(retry);
|
||||
e.ldaxrb(e.w1, ptr(e.x4));
|
||||
e.stlxrb(e.w2, e.w0, ptr(e.x4));
|
||||
e.cbnz(e.w2, retry);
|
||||
e.mov(i.dest, e.w1);
|
||||
}
|
||||
};
|
||||
struct ATOMIC_EXCHANGE_I16
|
||||
: Sequence<ATOMIC_EXCHANGE_I16,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I16Op, I64Op, I16Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
if (i.src1.is_constant) {
|
||||
e.mov(e.x4, i.src1.constant());
|
||||
} else {
|
||||
e.mov(e.x4, i.src1);
|
||||
}
|
||||
if (i.src2.is_constant) {
|
||||
e.mov(e.w0, static_cast<uint64_t>(
|
||||
static_cast<uint32_t>(i.src2.constant()) & 0xFFFF));
|
||||
} else {
|
||||
e.and_(e.w0, i.src2, 0xFFFF);
|
||||
}
|
||||
|
||||
if (e.IsFeatureEnabled(kA64EmitLSE)) {
|
||||
e.swpalh(e.w0, i.dest, ptr(e.x4));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& retry = e.NewCachedLabel();
|
||||
e.L(retry);
|
||||
e.ldaxrh(e.w1, ptr(e.x4));
|
||||
e.stlxrh(e.w2, e.w0, ptr(e.x4));
|
||||
e.cbnz(e.w2, retry);
|
||||
e.mov(i.dest, e.w1);
|
||||
}
|
||||
};
|
||||
struct ATOMIC_EXCHANGE_I32
|
||||
: Sequence<ATOMIC_EXCHANGE_I32,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I32Op, I64Op, I32Op>> {
|
||||
static void Emit(A64Emitter& e, const EmitArgType& i) {
|
||||
// src1 is a host address (not guest).
|
||||
if (i.src1.is_constant) {
|
||||
e.mov(e.x4, i.src1.constant());
|
||||
} else {
|
||||
e.mov(e.x4, i.src1);
|
||||
}
|
||||
if (i.src2.is_constant) {
|
||||
e.mov(e.w0,
|
||||
static_cast<uint64_t>(static_cast<uint32_t>(i.src2.constant())));
|
||||
} else {
|
||||
e.mov(e.w0, i.src2);
|
||||
}
|
||||
|
||||
if (e.IsFeatureEnabled(kA64EmitLSE)) {
|
||||
e.swpal(e.w0, i.dest, ptr(e.x4));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& retry = e.NewCachedLabel();
|
||||
e.L(retry);
|
||||
e.ldaxr(e.w1, ptr(e.x4));
|
||||
e.stlxr(e.w2, e.w0, ptr(e.x4));
|
||||
e.cbnz(e.w2, retry);
|
||||
e.mov(i.dest, e.w1);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_ATOMIC_EXCHANGE, ATOMIC_EXCHANGE_I8,
|
||||
ATOMIC_EXCHANGE_I16, ATOMIC_EXCHANGE_I32);
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_ATOMIC_COMPARE_EXCHANGE
|
||||
// ============================================================================
|
||||
|
||||
@@ -295,68 +295,6 @@ RegExp ComputeMemoryAddressOffset(X64Emitter& e, const T& guest,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OPCODE_ATOMIC_EXCHANGE
|
||||
// ============================================================================
|
||||
// Note that the address we use here is a real, host address!
|
||||
// This is weird, and should be fixed.
|
||||
template <typename SEQ, typename REG, typename ARGS>
|
||||
void EmitAtomicExchangeXX(X64Emitter& e, const ARGS& i) {
|
||||
if (i.dest == i.src1) {
|
||||
e.mov(e.rax, i.src1);
|
||||
if (i.dest != i.src2) {
|
||||
if (i.src2.is_constant) {
|
||||
e.mov(i.dest, i.src2.constant());
|
||||
} else {
|
||||
e.mov(i.dest, i.src2);
|
||||
}
|
||||
}
|
||||
e.lock();
|
||||
e.xchg(e.dword[e.rax], i.dest);
|
||||
} else {
|
||||
if (i.dest != i.src2) {
|
||||
if (i.src2.is_constant) {
|
||||
e.mov(i.dest, i.src2.constant());
|
||||
} else {
|
||||
e.mov(i.dest, i.src2);
|
||||
}
|
||||
}
|
||||
e.lock();
|
||||
e.xchg(e.dword[i.src1.reg()], i.dest);
|
||||
}
|
||||
}
|
||||
struct ATOMIC_EXCHANGE_I8
|
||||
: Sequence<ATOMIC_EXCHANGE_I8,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I8Op, I64Op, I8Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAtomicExchangeXX<ATOMIC_EXCHANGE_I8, Reg8>(e, i);
|
||||
}
|
||||
};
|
||||
struct ATOMIC_EXCHANGE_I16
|
||||
: Sequence<ATOMIC_EXCHANGE_I16,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I16Op, I64Op, I16Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAtomicExchangeXX<ATOMIC_EXCHANGE_I16, Reg16>(e, i);
|
||||
}
|
||||
};
|
||||
struct ATOMIC_EXCHANGE_I32
|
||||
: Sequence<ATOMIC_EXCHANGE_I32,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I32Op, I64Op, I32Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAtomicExchangeXX<ATOMIC_EXCHANGE_I32, Reg32>(e, i);
|
||||
}
|
||||
};
|
||||
struct ATOMIC_EXCHANGE_I64
|
||||
: Sequence<ATOMIC_EXCHANGE_I64,
|
||||
I<OPCODE_ATOMIC_EXCHANGE, I64Op, I64Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
EmitAtomicExchangeXX<ATOMIC_EXCHANGE_I64, Reg64>(e, i);
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_ATOMIC_EXCHANGE, ATOMIC_EXCHANGE_I8,
|
||||
ATOMIC_EXCHANGE_I16, ATOMIC_EXCHANGE_I32,
|
||||
ATOMIC_EXCHANGE_I64);
|
||||
|
||||
struct LVL_V128 : Sequence<LVL_V128, I<OPCODE_LVL, V128Op, I64Op>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.mov(e.edx, 0xf);
|
||||
|
||||
@@ -568,6 +568,10 @@ bool SimplificationPass::TryHandleANDROLORSHLSeq(hir::Instr* i,
|
||||
bool SimplificationPass::CheckAnd(hir::Instr* i, hir::HIRBuilder* builder) {
|
||||
retry_and_simplification:
|
||||
|
||||
if (SimplifyAndNot(i, builder)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto [constant_value, variable_value] = i->BinaryValueArrangeAsConstAndVar();
|
||||
if (!constant_value) {
|
||||
// added this for srawi
|
||||
@@ -1247,6 +1251,38 @@ bool SimplificationPass::SimplifyAddArith(hir::Instr* i,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimplificationPass::SimplifyAndNot(hir::Instr* i,
|
||||
hir::HIRBuilder* builder) {
|
||||
// check if either of the 2 AND operands has just used NOT and fold into
|
||||
// an AND_NOT opcode
|
||||
Value* src1 = i->src1.value;
|
||||
Value* src2 = i->src2.value;
|
||||
|
||||
Instr* def1 = src1->def;
|
||||
Instr* def2 = src2->def;
|
||||
if (!def1 || !def2) return false;
|
||||
|
||||
// Bypass the NOT from an incoming operand and combine it into AND_NOT.
|
||||
// If the original NOT does not have any further uses, then the
|
||||
// dead-code-elimination pass will delete it. Otherwise, if it still has uses,
|
||||
// then there will still be a NOT operation.
|
||||
if (def2->opcode == &OPCODE_NOT_info) {
|
||||
// Fold src2's NOT into AND_NOT
|
||||
i->Replace(&OPCODE_AND_NOT_info, 0);
|
||||
i->set_src1(src1);
|
||||
i->set_src2(def2->src1.value);
|
||||
return true;
|
||||
} else if (def1->opcode == &OPCODE_NOT_info) {
|
||||
// Swap operands and fold src1's NOT into AND_NOT
|
||||
i->Replace(&OPCODE_AND_NOT_info, 0);
|
||||
i->set_src1(src2);
|
||||
i->set_src2(def1->src1.value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SimplificationPass::SimplifySubArith(hir::Instr* i,
|
||||
hir::HIRBuilder* builder) {
|
||||
/*
|
||||
|
||||
@@ -42,6 +42,7 @@ class SimplificationPass : public ConditionalGroupSubpass {
|
||||
bool SimplifyAddWithSHL(hir::Instr* i, hir::HIRBuilder* builder);
|
||||
bool SimplifyAddToSelf(hir::Instr* i, hir::HIRBuilder* builder);
|
||||
bool SimplifyAddArith(hir::Instr* i, hir::HIRBuilder* builder);
|
||||
bool SimplifyAndNot(hir::Instr* i, hir::HIRBuilder* builder);
|
||||
bool SimplifySubArith(hir::Instr* i, hir::HIRBuilder* builder);
|
||||
bool SimplifySHLArith(hir::Instr* i, hir::HIRBuilder* builder);
|
||||
// handle either or or xor with 0
|
||||
|
||||
@@ -2225,17 +2225,6 @@ Value* HIRBuilder::Unpack(Value* value, uint32_t pack_flags) {
|
||||
return i->dest;
|
||||
}
|
||||
|
||||
Value* HIRBuilder::AtomicExchange(Value* address, Value* new_value) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
ASSERT_INTEGER_TYPE(new_value);
|
||||
Instr* i =
|
||||
AppendInstr(OPCODE_ATOMIC_EXCHANGE_info, 0, AllocValue(new_value->type));
|
||||
i->set_src1(address);
|
||||
i->set_src2(new_value);
|
||||
i->src3.value = NULL;
|
||||
return i->dest;
|
||||
}
|
||||
|
||||
Value* HIRBuilder::AtomicCompareExchange(Value* address, Value* old_value,
|
||||
Value* new_value) {
|
||||
ASSERT_ADDRESS_TYPE(address);
|
||||
|
||||
@@ -297,7 +297,6 @@ class HIRBuilder {
|
||||
Value* Pack(Value* value1, Value* value2, uint32_t pack_flags = 0);
|
||||
Value* Unpack(Value* value, uint32_t pack_flags = 0);
|
||||
|
||||
Value* AtomicExchange(Value* address, Value* new_value);
|
||||
Value* AtomicCompareExchange(Value* address, Value* old_value,
|
||||
Value* new_value);
|
||||
Value* AtomicAdd(Value* address, Value* value);
|
||||
|
||||
@@ -282,7 +282,6 @@ enum Opcode {
|
||||
OPCODE_PACK, // break up into smaller operations and add a float16 convert
|
||||
// opcode
|
||||
OPCODE_UNPACK,
|
||||
OPCODE_ATOMIC_EXCHANGE,
|
||||
OPCODE_ATOMIC_COMPARE_EXCHANGE,
|
||||
OPCODE_SET_ROUNDING_MODE,
|
||||
OPCODE_VECTOR_DENORMFLUSH, // converts denormals to signed zeros in a vector
|
||||
|
||||
@@ -650,12 +650,6 @@ DEFINE_OPCODE(
|
||||
OPCODE_SIG_V_V,
|
||||
0)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_ATOMIC_EXCHANGE,
|
||||
"atomic_exchange",
|
||||
OPCODE_SIG_V_V_V,
|
||||
OPCODE_FLAG_VOLATILE)
|
||||
|
||||
DEFINE_OPCODE(
|
||||
OPCODE_ATOMIC_COMPARE_EXCHANGE,
|
||||
"atomic_compare_exchange",
|
||||
|
||||
@@ -646,45 +646,6 @@ TEST_CASE("SET_NJM_OFF", "[backend]") {
|
||||
#endif
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Atomic Exchange I32
|
||||
// =============================================================================
|
||||
// Tests that AtomicExchange correctly swaps a value in memory and returns
|
||||
// the old value.
|
||||
// NOTE: OPCODE_ATOMIC_EXCHANGE uses a HOST address (not guest), per the
|
||||
// x64 backend comment: "the address we use here is a real, host address!"
|
||||
TEST_CASE("ATOMIC_EXCHANGE_I32", "[backend]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
// r[4] holds the host address directly.
|
||||
auto addr = LoadGPR(b, 4);
|
||||
auto new_val = b.Truncate(LoadGPR(b, 5), hir::INT32_TYPE);
|
||||
auto old_val = b.AtomicExchange(addr, new_val);
|
||||
StoreGPR(b, 3, b.ZeroExtend(old_val, hir::INT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
|
||||
// Allocate guest memory and compute the host pointer.
|
||||
uint32_t guest_addr = test.memory->SystemHeapAlloc(4);
|
||||
REQUIRE(guest_addr != 0);
|
||||
auto* host_ptr = test.memory->TranslateVirtual(guest_addr);
|
||||
|
||||
test.Run(
|
||||
[&](PPCContext* ctx) {
|
||||
*reinterpret_cast<uint32_t*>(host_ptr) = 0xAABBCCDD;
|
||||
// Pass the HOST address in r[4].
|
||||
ctx->r[4] = reinterpret_cast<uint64_t>(host_ptr);
|
||||
ctx->r[5] = 0x11223344;
|
||||
},
|
||||
[&](PPCContext* ctx) {
|
||||
// r[3] should have the old value.
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0xAABBCCDD);
|
||||
// Memory should now have the new value.
|
||||
REQUIRE(*reinterpret_cast<uint32_t*>(host_ptr) == 0x11223344);
|
||||
});
|
||||
|
||||
test.memory->SystemHeapFree(guest_addr);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DOT_PRODUCT_3 — inline NEON dot product of first 3 vector elements
|
||||
// =============================================================================
|
||||
|
||||
@@ -491,8 +491,100 @@ TEST_CASE("ATOMIC_COMPARE_EXCHANGE_I32", "[atomic]") {
|
||||
// ============================================================================
|
||||
// AND_NOT — bitwise AND with complement of second operand
|
||||
// ============================================================================
|
||||
TEST_CASE("AND_NOT_I8", "[bitwise]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 2,
|
||||
b.ZeroExtend(b.And(b.Truncate(LoadGPR(b, 4), INT8_TYPE),
|
||||
b.Not(b.Truncate(LoadGPR(b, 5), INT8_TYPE))),
|
||||
INT64_TYPE));
|
||||
StoreGPR(b, 3,
|
||||
b.ZeroExtend(b.AndNot(b.Truncate(LoadGPR(b, 4), INT8_TYPE),
|
||||
b.Truncate(LoadGPR(b, 5), INT8_TYPE)),
|
||||
INT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
// result = src1 & ~src2
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFF;
|
||||
ctx->r[5] = 0x0F;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0xF0);
|
||||
});
|
||||
// All bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xAA;
|
||||
ctx->r[5] = 0xFF;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x00);
|
||||
});
|
||||
// No bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0x12;
|
||||
ctx->r[5] = 0x00;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x12);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("AND_NOT_I16", "[bitwise]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 2,
|
||||
b.ZeroExtend(b.And(b.Truncate(LoadGPR(b, 4), INT16_TYPE),
|
||||
b.Not(b.Truncate(LoadGPR(b, 5), INT16_TYPE))),
|
||||
INT64_TYPE));
|
||||
StoreGPR(b, 3,
|
||||
b.ZeroExtend(b.AndNot(b.Truncate(LoadGPR(b, 4), INT16_TYPE),
|
||||
b.Truncate(LoadGPR(b, 5), INT16_TYPE)),
|
||||
INT64_TYPE));
|
||||
b.Return();
|
||||
});
|
||||
// result = src1 & ~src2
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFF00;
|
||||
ctx->r[5] = 0x0F0F;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0xF000);
|
||||
});
|
||||
// All bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xAAAA;
|
||||
ctx->r[5] = 0xFFFF;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x0000);
|
||||
});
|
||||
// No bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0x1234;
|
||||
ctx->r[5] = 0x0000;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x1234);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("AND_NOT_I32", "[bitwise]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 2,
|
||||
b.ZeroExtend(b.And(b.Truncate(LoadGPR(b, 4), INT32_TYPE),
|
||||
b.Not(b.Truncate(LoadGPR(b, 5), INT32_TYPE))),
|
||||
INT64_TYPE));
|
||||
StoreGPR(b, 3,
|
||||
b.ZeroExtend(b.AndNot(b.Truncate(LoadGPR(b, 4), INT32_TYPE),
|
||||
b.Truncate(LoadGPR(b, 5), INT32_TYPE)),
|
||||
@@ -506,6 +598,7 @@ TEST_CASE("AND_NOT_I32", "[bitwise]") {
|
||||
ctx->r[5] = 0x0F0F0F0F;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0xF000F000);
|
||||
});
|
||||
// All bits masked out.
|
||||
@@ -515,6 +608,7 @@ TEST_CASE("AND_NOT_I32", "[bitwise]") {
|
||||
ctx->r[5] = 0xFFFFFFFF;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x00000000);
|
||||
});
|
||||
// No bits masked out.
|
||||
@@ -524,10 +618,87 @@ TEST_CASE("AND_NOT_I32", "[bitwise]") {
|
||||
ctx->r[5] = 0x00000000;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(static_cast<uint32_t>(ctx->r[3]) == 0x12345678);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("AND_NOT_I64", "[bitwise]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreGPR(b, 2, b.And(LoadGPR(b, 4), b.Not(LoadGPR(b, 5))));
|
||||
StoreGPR(b, 3, b.AndNot(LoadGPR(b, 4), LoadGPR(b, 5)));
|
||||
b.Return();
|
||||
});
|
||||
// result = src1 & ~src2
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xFF00FF00FF00FF00;
|
||||
ctx->r[5] = 0x0F0F0F0F0F0F0F0F;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(ctx->r[3] == 0xF000F000F000F000);
|
||||
});
|
||||
// All bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0xAAAAAAAAAAAAAAAA;
|
||||
ctx->r[5] = 0xFFFFFFFFFFFFFFFF;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(ctx->r[3] == 0x0000000000000000);
|
||||
});
|
||||
// No bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->r[4] = 0x1234567812345678;
|
||||
ctx->r[5] = 0x0000000000000000;
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->r[2] == ctx->r[3]);
|
||||
REQUIRE(ctx->r[3] == 0x1234567812345678);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("AND_NOT_V128", "[bitwise]") {
|
||||
TestFunction test([](HIRBuilder& b) {
|
||||
StoreVR(b, 2, b.And(LoadVR(b, 4), b.Not(LoadVR(b, 5))));
|
||||
StoreVR(b, 3, b.AndNot(LoadVR(b, 4), LoadVR(b, 5)));
|
||||
b.Return();
|
||||
});
|
||||
// result = src1 & ~src2
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128s(0xFF00);
|
||||
ctx->v[5] = vec128s(0x0F0F);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[2] == ctx->v[3]);
|
||||
REQUIRE(ctx->v[3] == vec128s(0xF000));
|
||||
});
|
||||
// All bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128b(0xAA);
|
||||
ctx->v[5] = vec128b(0xFF);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[2] == ctx->v[3]);
|
||||
REQUIRE(ctx->v[3] == vec128b(0x00));
|
||||
});
|
||||
// No bits masked out.
|
||||
test.Run(
|
||||
[](PPCContext* ctx) {
|
||||
ctx->v[4] = vec128i(0x12345678);
|
||||
ctx->v[5] = vec128i(0x00000000);
|
||||
},
|
||||
[](PPCContext* ctx) {
|
||||
REQUIRE(ctx->v[2] == ctx->v[3]);
|
||||
REQUIRE(ctx->v[3] == vec128i(0x12345678));
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TRUNCATE — integer narrowing
|
||||
// ============================================================================
|
||||
|
||||
@@ -41,6 +41,125 @@ namespace xe {
|
||||
namespace hid {
|
||||
namespace winkey {
|
||||
|
||||
static uint8_t VirtualKeyToHIDUsage(UINT vk) {
|
||||
// Letters: contiguous in both VK and HID space
|
||||
if (vk >= 'A' && vk <= 'Z') {
|
||||
return vk - 'A' + 0x04;
|
||||
}
|
||||
|
||||
// Digits 1-9 (0 is irregular: 0x27)
|
||||
if (vk >= '1' && vk <= '9') {
|
||||
return vk - '1' + 0x1E;
|
||||
}
|
||||
|
||||
// F1-F12
|
||||
if (vk >= VK_F1 && vk <= VK_F12) {
|
||||
return vk - VK_F1 + 0x3A;
|
||||
}
|
||||
|
||||
// F13-F24
|
||||
if (vk >= VK_F13 && vk <= VK_F24) {
|
||||
return vk - VK_F13 + 0x68;
|
||||
}
|
||||
|
||||
// Numpad 1-9 (0 is irregular: 0x62)
|
||||
if (vk >= VK_NUMPAD1 && vk <= VK_NUMPAD9) {
|
||||
return vk - VK_NUMPAD1 + 0x59;
|
||||
}
|
||||
|
||||
// Modifiers (Left side starts at 0xE0, Right at 0xE4)
|
||||
if (vk >= VK_LCONTROL && vk <= VK_LWIN) {
|
||||
return vk - VK_LCONTROL + 0xE0;
|
||||
}
|
||||
if (vk >= VK_RCONTROL && vk <= VK_RWIN) {
|
||||
return vk - VK_RCONTROL + 0xE4;
|
||||
}
|
||||
|
||||
switch (vk) {
|
||||
case '0':
|
||||
return 0x27;
|
||||
case VK_RETURN:
|
||||
return 0x28;
|
||||
case VK_ESCAPE:
|
||||
return 0x29;
|
||||
case VK_BACK:
|
||||
return 0x2A;
|
||||
case VK_TAB:
|
||||
return 0x2B;
|
||||
case VK_SPACE:
|
||||
return 0x2C;
|
||||
case VK_OEM_MINUS:
|
||||
return 0x2D;
|
||||
case VK_OEM_PLUS:
|
||||
return 0x2E;
|
||||
case VK_OEM_4:
|
||||
return 0x2F;
|
||||
case VK_OEM_6:
|
||||
return 0x30;
|
||||
case VK_OEM_5:
|
||||
return 0x31;
|
||||
case VK_OEM_1:
|
||||
return 0x33;
|
||||
case VK_OEM_7:
|
||||
return 0x34;
|
||||
case VK_OEM_3:
|
||||
return 0x35;
|
||||
case VK_OEM_COMMA:
|
||||
return 0x36;
|
||||
case VK_OEM_PERIOD:
|
||||
return 0x37;
|
||||
case VK_OEM_2:
|
||||
return 0x38;
|
||||
case VK_CAPITAL:
|
||||
return 0x39;
|
||||
case VK_SNAPSHOT:
|
||||
return 0x46;
|
||||
case VK_SCROLL:
|
||||
return 0x47;
|
||||
case VK_PAUSE:
|
||||
return 0x48;
|
||||
case VK_INSERT:
|
||||
return 0x49;
|
||||
case VK_HOME:
|
||||
return 0x4A;
|
||||
case VK_PRIOR:
|
||||
return 0x4B;
|
||||
case VK_DELETE:
|
||||
return 0x4C;
|
||||
case VK_END:
|
||||
return 0x4D;
|
||||
case VK_NEXT:
|
||||
return 0x4E;
|
||||
case VK_RIGHT:
|
||||
return 0x4F;
|
||||
case VK_LEFT:
|
||||
return 0x50;
|
||||
case VK_DOWN:
|
||||
return 0x51;
|
||||
case VK_UP:
|
||||
return 0x52;
|
||||
case VK_NUMLOCK:
|
||||
return 0x53;
|
||||
case VK_DIVIDE:
|
||||
return 0x54;
|
||||
case VK_MULTIPLY:
|
||||
return 0x55;
|
||||
case VK_SUBTRACT:
|
||||
return 0x56;
|
||||
case VK_ADD:
|
||||
return 0x57;
|
||||
case VK_NUMPAD0:
|
||||
return 0x62;
|
||||
case VK_DECIMAL:
|
||||
return 0x63;
|
||||
case VK_APPS:
|
||||
return 0x65;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
bool static IsPassthroughEnabled() {
|
||||
return static_cast<KeyboardMode>(cvars::keyboard_mode) ==
|
||||
KeyboardMode::Passthrough;
|
||||
@@ -344,10 +463,12 @@ X_RESULT WinKeyInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
||||
}
|
||||
|
||||
if (IsPassthroughEnabled()) {
|
||||
const UINT vk = static_cast<UINT>(xinput_virtual_key);
|
||||
hid_code = VirtualKeyToHIDUsage(vk);
|
||||
if (GetKeyboardState(key_map_)) {
|
||||
const UINT sc = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
|
||||
WCHAR buf;
|
||||
if (ToUnicode(uint8_t(xinput_virtual_key), 0, key_map_, &buf, 1, 0) ==
|
||||
1) {
|
||||
if (ToUnicode(vk, sc, key_map_, &buf, 1, 0) == 1) {
|
||||
keystroke_flags |= 0x1000; // XINPUT_KEYSTROKE_VALIDUNICODE
|
||||
unicode = buf;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user