From 3f222298011248bf344025c4661bc2cee9ac9f1a Mon Sep 17 00:00:00 2001 From: DarthM Date: Thu, 24 Sep 2026 15:42:19 -0400 Subject: [PATCH 1/5] runtime: add experimental x86-64-v2 profile --- .github/workflows/build.yml | 58 ++++++ .github/workflows/package.yml | 133 ++++++++++++- .github/workflows/recomp-test.yml | 14 ++ Launcher/local-build-macos.command | 15 +- Launcher/macos/build-setup-pkg.command | 84 ++++++-- Launcher/macos/macos-x86_64-toolchain.cmake | 16 ++ Launcher/macos/publish-app.command | 53 +++++- Launcher/macos/setup.command | 40 +++- README.md | 19 +- aurora-main/cmake/AuroraSDL3Provider.cmake | 7 +- aurora-main/cmake/aurora_core.cmake | 4 +- aurora-main/extern/CMakeLists.txt | 50 +++++ aurora-main/lib/dolphin/pad/pad.cpp | 54 +++--- aurora-main/lib/gfx/common.cpp | 5 +- aurora-main/lib/gfx/texture_replacement.cpp | 2 +- aurora-main/lib/system_info.cpp | 2 +- runtime/CMakeLists.txt | 179 +++++++----------- runtime/cmake/HostLibraries.cmake | 29 +++ runtime/cmake/PlatformTests.cmake | 118 ++++++++++++ runtime/cmake/PublicProducts.cmake | 34 ++-- runtime/include/host_context.h | 10 +- runtime/include/isa/ppc_isa_float.h | 20 ++ runtime/src/host_context.cpp | 4 +- runtime/src/host_cpu_baseline.cpp | 69 ++++--- runtime/src/main.cpp | 13 +- runtime/src/music_attenuation.cpp | 22 ++- runtime/src/platform/host_platform.cpp | 13 ++ runtime/tests/ppc_pair_fma_tests.cpp | 58 ++++++ runtime/tests/test_expr.cpp | 24 ++- .../CodeGen/CxxLinearCodeGenerator.cs | 4 +- .../EmittedOutputShapeTests.cs | 32 +++- 31 files changed, 943 insertions(+), 242 deletions(-) create mode 100644 Launcher/macos/macos-x86_64-toolchain.cmake create mode 100644 runtime/cmake/HostLibraries.cmake create mode 100644 runtime/cmake/PlatformTests.cmake create mode 100644 runtime/tests/ppc_pair_fma_tests.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a156710a5..a2fd83899 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,3 +46,61 @@ jobs: - name: Test run: dotnet test translator/Translator.sln -c Release --no-build --verbosity normal + + platform_tests: + name: Host platform (${{ matrix.runner }}) + strategy: + fail-fast: false + matrix: + runner: [ubuntu-24.04, macos-14, macos-15-intel] + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Configure without graphics dependencies or game assets + run: >- + cmake -S runtime -B build-platform -G Ninja + -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + -DMKW_BUILD_PRODUCTS=OFF -DMKW_PLATFORM_TESTS_ONLY=ON + + - name: Build and run host-platform tests + run: | + cmake --build build-platform --parallel 3 + ctest --test-dir build-platform --output-on-failure + + macos_substrate: + name: macOS arm64 (configure + substrate tests) + runs-on: macos-14 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Configure native runtime + shell: bash + run: | + test "$(uname -m)" = arm64 + cmake -S runtime -B build-macos -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DMKW_BUILD_PRODUCTS=OFF + grep -qx 'CMAKE_OSX_DEPLOYMENT_TARGET:STRING=12.0' \ + build-macos/CMakeCache.txt + + - name: Build macOS portability targets + shell: bash + run: | + cmake --build build-macos --target \ + mkw_platform_paths_tests \ + mkw_nand_save_tests \ + mkw_nand_settings_tests \ + mkw_sc_serial_tests \ + mkw_input_expr_tests \ + mkw_macos_native_compile \ + mkw_macos_context_abi_tests \ + mkw_macos_host_context_tests \ + mkw_macos_guest_flat_memory_tests + + - name: Test execution substrate + shell: bash + run: ctest --test-dir build-macos --output-on-failure diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index b97c62a13..63f40b535 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1,8 +1,8 @@ name: Package installers # Builds the per-platform installer/setup tool (WiiCompiled-Setup.exe / -# WiiCompiled-Setup-x86_64.AppImage) via Launcher/Build-Installer.ps1 and -# Launcher/build-appimage.sh respectively - the same scripts a maintainer runs by hand today to +# WiiCompiled-Setup-x86_64.AppImage / WiiCompiled-Setup.pkg) via the platform packaging scripts - +# the same scripts a maintainer runs by hand today to # produce a GitHub Release asset. This does NOT build the actual translated game executable: # that step requires the end user's own Mario Kart Wii dump (Assets/main.dol, Assets/StaticR.rel), # which is proprietary and not present in this repository or in CI. @@ -11,6 +11,11 @@ on: tags: - '*' workflow_dispatch: + inputs: + version: + description: Package version + required: true + type: string permissions: contents: read @@ -90,14 +95,130 @@ jobs: if-no-files-found: error archive: false + macos-setup-package: + name: macOS (universal Setup.pkg) + runs-on: macos-14 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: '8.0.x' + + - name: Verify Apple Silicon runner + shell: bash + run: | + test "$(uname -m)" = arm64 + xcode-select -p + + - name: Download pinned Nod tools + shell: bash + run: | + mkdir -p Launcher/artifacts/macos + nodtool_version=v2.0.0-alpha.10 + nodtool_arm64_asset=nodtool-macos-arm64 + nodtool_arm64_sha256=e23ca466999b720c55e6d29c9683fce8cc74451ba64ead2e543d50129f24528a + nodtool_x86_64_asset=nodtool-macos-x86_64 + nodtool_x86_64_sha256=f68f504dc2b72694b468ca78b6a24142c7aa5c8800f77564297f4143682e6575 + curl -fsSL --retry 3 \ + "https://github.com/encounter/nod/releases/download/${nodtool_version}/${nodtool_arm64_asset}" \ + -o Launcher/artifacts/macos/nodtool-arm64 + curl -fsSL --retry 3 \ + "https://github.com/encounter/nod/releases/download/${nodtool_version}/${nodtool_x86_64_asset}" \ + -o Launcher/artifacts/macos/nodtool-x86_64 + printf '%s %s\n' "$nodtool_arm64_sha256" Launcher/artifacts/macos/nodtool-arm64 | shasum -a 256 -c - + printf '%s %s\n' "$nodtool_x86_64_sha256" Launcher/artifacts/macos/nodtool-x86_64 | shasum -a 256 -c - + chmod +x Launcher/artifacts/macos/nodtool-arm64 Launcher/artifacts/macos/nodtool-x86_64 + + - name: Publish self-contained Translator tools + shell: bash + run: | + dotnet publish translator/src/Translator.Cli/Translator.Cli.csproj \ + -c Release -r osx-arm64 --self-contained true \ + -p:PublishSingleFile=true \ + -o Launcher/artifacts/macos/translator-arm64 + dotnet publish translator/src/Translator.Cli/Translator.Cli.csproj \ + -c Release -r osx-x64 --self-contained true \ + -p:PublishSingleFile=true \ + -o Launcher/artifacts/macos/translator-x86_64 + + - name: Download pinned universal Ninja + shell: bash + run: | + ninja_version=1.13.2 + ninja_sha256=c99048673aa765960a99cf10c6ddb9f1fad506099ff0a0e137ad8960a88f321b + curl -fsSL --retry 3 "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-mac.zip" -o ninja-mac.zip + printf '%s %s\n' "$ninja_sha256" ninja-mac.zip | shasum -a 256 -c - + unzip -q ninja-mac.zip -d Launcher/artifacts/macos/ninja + chmod +x Launcher/artifacts/macos/ninja/ninja + + - name: Download pinned portable CMake + shell: bash + run: | + cmake_version=4.4.3 + archive="cmake-${cmake_version}-macos-universal.tar.gz" + base_url="https://github.com/Kitware/CMake/releases/download/v${cmake_version}" + expected_sha256=0c5d65251c14cc884bfa16bdbed3c263ce5bffe2e21c0d0d00962cb0610464fa + curl -fsSL --retry 3 "$base_url/$archive" -o "$archive" + printf '%s %s\n' "$expected_sha256" "$archive" | shasum -a 256 -c - + tar -xzf "$archive" + mv "cmake-${cmake_version}-macos-universal/CMake.app/Contents" Launcher/artifacts/macos/cmake + + - name: Build Setup.pkg + env: + PACKAGE_VERSION: ${{ inputs.version }} + TAG_VERSION: ${{ github.ref_name }} + shell: bash + run: | + package_version="$PACKAGE_VERSION" + if [[ -z "$package_version" ]]; then package_version="${TAG_VERSION#v}"; fi + mkdir -p Launcher/dist + Launcher/macos/build-setup-pkg.command \ + --nodtool-arm64 Launcher/artifacts/macos/nodtool-arm64 \ + --nodtool-x86_64 Launcher/artifacts/macos/nodtool-x86_64 \ + --translator-arm64 Launcher/artifacts/macos/translator-arm64/Translator.Cli \ + --translator-x86_64 Launcher/artifacts/macos/translator-x86_64/Translator.Cli \ + --cmake-root Launcher/artifacts/macos/cmake \ + --ninja-arm64 Launcher/artifacts/macos/ninja/ninja \ + --ninja-x86_64 Launcher/artifacts/macos/ninja/ninja \ + --output Launcher/dist/WiiCompiled-Setup.pkg \ + --version "$package_version" + + - name: Verify package layout and architecture-specific tools + shell: bash + run: | + pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg + ! pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \ + grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)' + expanded="$RUNNER_TEMP/wiicompiled-setup-expanded" + pkgutil --expand-full Launcher/dist/WiiCompiled-Setup.pkg "$expanded" + resources="$expanded/Payload/Applications/WiiCompiled Setup.app/Contents/Resources" + for arch in arm64 x86_64; do + for tool in nodtool Translator.Cli ninja; do + lipo "$resources/tools/$arch/$tool" -verify_arch "$arch" + done + done + lipo "$resources/tools/cmake/bin/cmake" -verify_arch arm64 x86_64 + bash "$resources/setup.command" --help + /usr/bin/arch -x86_64 /bin/bash "$resources/setup.command" --help + + - uses: actions/upload-artifact@v7 + with: + name: WiiCompiled-Setup-macos-universal + path: Launcher/dist/WiiCompiled-Setup.pkg + if-no-files-found: error + archive: false + # Publishes the packaged installers as a GitHub Release whenever a v* tag is pushed. Wheel Wizard # discovers updates from these releases, so the contract it relies on is enforced here: a full - # (non-prerelease) release whose tag is v, carrying an asset named exactly - # WiiCompiled-Setup.exe, produced by a setup host that reports that same version. + # (non-prerelease) release whose tag is v, carrying the expected platform assets, + # produced by setup hosts that report that same version. release: name: Publish GitHub Release if: startsWith(github.ref, 'refs/tags/v') - needs: [linux-appimage, windows-installer, recompilation] + needs: [linux-appimage, windows-installer, macos-setup-package, recompilation] runs-on: ubuntu-latest permissions: contents: write @@ -143,7 +264,7 @@ jobs: set -euo pipefail ls -lR artifacts assets=() - for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage; do + for name in WiiCompiled-Setup.exe WiiCompiled-Setup-x86_64.AppImage WiiCompiled-Setup-aarch64.AppImage WiiCompiled-Setup.pkg; do found="$(find artifacts -type f -name "$name" | head -n 1)" [ -n "$found" ] && [ -s "$found" ] || { echo "::error::missing release asset $name"; exit 1; } assets+=("$found") diff --git a/.github/workflows/recomp-test.yml b/.github/workflows/recomp-test.yml index 3a1244a8b..fe358881a 100644 --- a/.github/workflows/recomp-test.yml +++ b/.github/workflows/recomp-test.yml @@ -45,6 +45,20 @@ jobs: shell: pwsh run: ./Launcher/Prepare-PortableTools.ps1 + - name: Build and run Windows host-platform tests + shell: pwsh + run: | + $tools = Join-Path $PWD 'Launcher/artifacts/portable-tools' + $env:PATH = "$tools/llvm-mingw/bin;$tools/CMake/bin;$tools/Ninja;$env:PATH" + cmake -S runtime -B build-platform -G Ninja ` + -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ` + -DMKW_BUILD_PRODUCTS=OFF -DMKW_PLATFORM_TESTS_ONLY=ON + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cmake --build build-platform --parallel 3 + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + ctest --test-dir build-platform --output-on-failure + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Prepare pinned native dependencies shell: pwsh run: ./Launcher/Prepare-Dependencies.ps1 diff --git a/Launcher/local-build-macos.command b/Launcher/local-build-macos.command index 1f91b0ce8..e86e58ad3 100755 --- a/Launcher/local-build-macos.command +++ b/Launcher/local-build-macos.command @@ -22,7 +22,7 @@ Usage: local-build-macos.command --output-dir DIR [options] --retro-rewind-package-dir DIR RetroRewind6 directory (required for Retro Rewind) --retro-wfc-offline-dir DIR Directory containing binary/payload.RMCPD00.bin --skip-retro-wfc-payload Build Retro Rewind without the shared Retro-WFC payload - --force-clean-build Delete local generated and native-build-macos caches + --force-clean-build Delete local generated and current-architecture native build caches --parallel N Pin translation and build parallelism --cmake PATH --ninja PATH Override build tools --dotnet PATH Override dotnet @@ -56,7 +56,9 @@ while (($#)); do done [[ $(uname -s) == Darwin ]] || fail 'this build script is for macOS only' -[[ $(uname -m) == arm64 ]] || fail 'the current macOS product target is Apple Silicon only' +macos_arch=$(uname -m) +case "$macos_arch" in arm64|x86_64) ;; *) fail "unsupported macOS architecture: $macos_arch" ;; esac +macos_deployment_target=12.0 workspace=$(cd "$workspace" && pwd) [[ -n "$output_dir" ]] || fail '--output-dir is required' case "$profile" in base|retro-rewind|both) ;; *) fail '--profile must be base, retro-rewind, or both' ;; esac @@ -73,7 +75,8 @@ for tool in "$cmake_bin" "$ninja_bin" clang clang++ shasum; do command -v "$tool project="$workspace/projects/mkwii/recomp.yml"; assets="$workspace/Assets"; generated="$workspace/generated" functions="$generated/functions"; metadata="$generated/base_translation_output.json"; manifest_dir="$workspace/build/base" -manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards"; native_build="$workspace/native-build-macos" +manifest="$manifest_dir/mkwii_base_manifest.json"; shards="$generated/build_shards" +native_build="$workspace/native-build-macos-$macos_arch" assert_file "$project" 'translation project' if [[ -n "$game" ]]; then "$script_dir/macos/extract-disc.command" --game "$game" --assets-dir "$assets" --nodtool "$nodtool"; fi assert_file "$assets/main.dol" 'extracted main.dol'; assert_file "$assets/StaticR.rel" 'extracted StaticR.rel' @@ -131,9 +134,9 @@ if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatc step emit-build-shards 'Preparing native build shards'; translator "${args[@]}" step configure-native 'Configuring the native toolchain' -"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" +"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DAURORA_SDL3_PROVIDER=vendor -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind) step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs" -if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}"; fi -if (( builds_retro )); then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product RetroRewind --output-dir "$output_dir"; fi +if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi +if (( builds_retro )); then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product RetroRewind --output-dir "$output_dir" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi printf 'MKWCBUILD:OUTPUT=%s\n' "$output_dir" diff --git a/Launcher/macos/build-setup-pkg.command b/Launcher/macos/build-setup-pkg.command index 50082c965..6cbe61027 100755 --- a/Launcher/macos/build-setup-pkg.command +++ b/Launcher/macos/build-setup-pkg.command @@ -9,12 +9,15 @@ fail() { printf 'build-setup-pkg.command: error: %s\n' "$*" >&2; exit 1; } copy_clean() { DITTONORSRC=1 ditto --norsrc --noqtn "$@"; } usage() { cat <<'EOF' -Usage: build-setup-pkg.command --nodtool PATH --translator PATH --cmake-root DIR --ninja PATH --output PKG [options] +Usage: build-setup-pkg.command --nodtool-arm64 PATH --nodtool-x86_64 PATH --translator-arm64 PATH --translator-x86_64 PATH --cmake-root DIR --ninja-arm64 PATH --ninja-x86_64 PATH --output PKG [options] Creates a game-code-free WiiCompiled Setup.pkg. The supplied tools must be -maintainer-verified, redistributable macOS arm64 artifacts. The resulting pkg -is unsigned unless --installer-identity is supplied; releases should sign and -notarize it with a Developer ID Installer certificate. +maintainer-verified, redistributable macOS artifacts for both arm64 and +x86_64. The setup package selects native tools for its host while the game is +compiled locally for that host architecture. CMake must be universal2. The +resulting pkg is unsigned unless +--installer-identity is supplied; releases should sign and notarize it with a +Developer ID Installer certificate. --workspace DIR Repository root (default: script's grandparent) --version VERSION Bundle/package version (default: 0.1.0) @@ -23,14 +26,17 @@ EOF } script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -workspace=$(cd "$script_dir/../.." && pwd); nodtool=""; translator=""; cmake_root=""; ninja=""; output=""; version=0.1.0; identity="" +workspace=$(cd "$script_dir/../.." && pwd); nodtool_arm64=""; nodtool_x86_64=""; translator_arm64=""; translator_x86_64=""; cmake_root=""; ninja_arm64=""; ninja_x86_64=""; output=""; version=0.1.0; identity="" while (($#)); do case "$1" in --workspace) workspace=${2:-}; shift 2 ;; - --nodtool) nodtool=${2:-}; shift 2 ;; - --translator) translator=${2:-}; shift 2 ;; + --nodtool-arm64) nodtool_arm64=${2:-}; shift 2 ;; + --nodtool-x86_64) nodtool_x86_64=${2:-}; shift 2 ;; + --translator-arm64) translator_arm64=${2:-}; shift 2 ;; + --translator-x86_64) translator_x86_64=${2:-}; shift 2 ;; --cmake-root) cmake_root=${2:-}; shift 2 ;; - --ninja) ninja=${2:-}; shift 2 ;; + --ninja-arm64) ninja_arm64=${2:-}; shift 2 ;; + --ninja-x86_64) ninja_x86_64=${2:-}; shift 2 ;; --output) output=${2:-}; shift 2 ;; --version) version=${2:-}; shift 2 ;; --installer-identity) identity=${2:-}; shift 2 ;; @@ -42,12 +48,48 @@ version=${version#v} [[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers' IFS=. read -r version_major version_minor version_patch <<< "$version" short_version="$version_major.${version_minor:-0}.${version_patch:-0}" -for tool in pkgbuild productbuild ditto codesign; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done -[[ -x "$nodtool" ]] || fail '--nodtool must name an executable' -[[ -x "$translator" ]] || fail '--translator must name an executable' +for tool in pkgbuild productbuild ditto codesign lipo; do command -v "$tool" >/dev/null || fail "required macOS tool unavailable: $tool"; done +for tool_path in "$nodtool_arm64" "$nodtool_x86_64" "$translator_arm64" "$translator_x86_64" "$ninja_arm64" "$ninja_x86_64"; do [[ -x "$tool_path" ]] || fail 'each architecture-specific tool must name an executable'; done [[ -x "$cmake_root/bin/cmake" ]] || fail '--cmake-root must contain bin/cmake' -[[ -x "$ninja" ]] || fail '--ninja must name an executable' -"$nodtool" --version >/dev/null || fail '--nodtool did not run successfully' +require_arch() { + local artifact=$1 arch=$2 label=$3 + lipo "$artifact" -verify_arch "$arch" >/dev/null 2>&1 || fail "$label must contain a $arch slice: $artifact" +} +require_arch "$nodtool_arm64" arm64 '--nodtool-arm64'; require_arch "$nodtool_x86_64" x86_64 '--nodtool-x86_64' +require_arch "$translator_arm64" arm64 '--translator-arm64'; require_arch "$translator_x86_64" x86_64 '--translator-x86_64' +require_arch "$ninja_arm64" arm64 '--ninja-arm64'; require_arch "$ninja_x86_64" x86_64 '--ninja-x86_64' +lipo "$cmake_root/bin/cmake" -verify_arch arm64 x86_64 >/dev/null 2>&1 || fail '--cmake-root/bin/cmake must be universal2' + +# Slice checks above prevent accidental cross-architecture packaging. Exercise +# each supplied executable as well: an incorrectly bundled runtime can have a +# valid Mach-O header but still fail before the setup app can use it. Apple +# Silicon maintainers validate Intel tools through Rosetta when it is present. +host_arch=$(uname -m) +run_for_arch() { + local arch=$1 label=$2 + shift 2 + if [[ "$arch" == "$host_arch" ]]; then + "$@" >/dev/null || fail "$label did not run successfully" + elif [[ "$host_arch" == arm64 && "$arch" == x86_64 ]] && /usr/bin/arch -x86_64 /usr/bin/true >/dev/null 2>&1; then + /usr/bin/arch -x86_64 "$@" >/dev/null || fail "$label did not run successfully under Rosetta" + else + # Intel hosts cannot execute arm64 binaries. The slice remains checked + # above; CI or an Apple Silicon maintainer must execute that tool set. + printf 'build-setup-pkg.command: warning: unable to execute %s on %s; architecture slice was verified, but run it in %s CI before release\n' \ + "$label" "$host_arch" "$arch" >&2 + fi +} +for arch in arm64 x86_64; do + if [[ "$arch" == arm64 ]]; then + nodtool=$nodtool_arm64; translator=$translator_arm64; ninja=$ninja_arm64 + else + nodtool=$nodtool_x86_64; translator=$translator_x86_64; ninja=$ninja_x86_64 + fi + run_for_arch "$arch" "--nodtool-$arch" "$nodtool" --version + run_for_arch "$arch" "--translator-$arch" "$translator" --help + run_for_arch "$arch" "--ninja-$arch" "$ninja" --version +done +run_for_arch "$host_arch" '--cmake-root/bin/cmake' "$cmake_root/bin/cmake" --version workspace=$(cd "$workspace" && pwd); output=$(cd "$(dirname "$output")" && pwd)/$(basename "$output") stage=$(mktemp -d "${TMPDIR:-/tmp}/wiicompiled-pkg.XXXXXX") trap 'rm -rf "$stage"' EXIT @@ -64,7 +106,7 @@ cat > "$app/Contents/Info.plist" <CFBundlePackageTypeAPPL CFBundleShortVersionString$short_version CFBundleVersion$version -LSMinimumSystemVersion14.0 +LSMinimumSystemVersion12.0 EOF cat > "$app/Contents/MacOS/WiiCompiledSetup" <<'EOF' @@ -103,11 +145,17 @@ copy_clean "$workspace/Launcher/local-build-macos.command" "$resources/workspace copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command" copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command" chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command -mkdir -p "$resources/tools/cmake" -copy_clean "$nodtool" "$resources/tools/nodtool"; chmod +x "$resources/tools/nodtool" -copy_clean "$translator" "$resources/tools/Translator.Cli"; chmod +x "$resources/tools/Translator.Cli" +# setup.command uses this marker to refresh source inputs in an existing user +# workspace without replacing extracted game assets or Retro Rewind files. +printf '%s\n' "$version" > "$resources/workspace/.bundle-version" +mkdir -p "$resources/tools/cmake" "$resources/tools/arm64" "$resources/tools/x86_64" +copy_clean "$nodtool_arm64" "$resources/tools/arm64/nodtool"; chmod +x "$resources/tools/arm64/nodtool" +copy_clean "$nodtool_x86_64" "$resources/tools/x86_64/nodtool"; chmod +x "$resources/tools/x86_64/nodtool" +copy_clean "$translator_arm64" "$resources/tools/arm64/Translator.Cli"; chmod +x "$resources/tools/arm64/Translator.Cli" +copy_clean "$translator_x86_64" "$resources/tools/x86_64/Translator.Cli"; chmod +x "$resources/tools/x86_64/Translator.Cli" copy_clean "$cmake_root" "$resources/tools/cmake" -copy_clean "$ninja" "$resources/tools/ninja"; chmod +x "$resources/tools/ninja" +copy_clean "$ninja_arm64" "$resources/tools/arm64/ninja"; chmod +x "$resources/tools/arm64/ninja" +copy_clean "$ninja_x86_64" "$resources/tools/x86_64/ninja"; chmod +x "$resources/tools/x86_64/ninja" copy_clean "$workspace/LICENSE" "$resources/LICENSE" copy_clean "$workspace/THIRD-PARTY-NOTICES.md" "$resources/THIRD-PARTY-NOTICES.md" codesign --force --deep --sign - "$app" diff --git a/Launcher/macos/macos-x86_64-toolchain.cmake b/Launcher/macos/macos-x86_64-toolchain.cmake new file mode 100644 index 000000000..6a96a4be3 --- /dev/null +++ b/Launcher/macos/macos-x86_64-toolchain.cmake @@ -0,0 +1,16 @@ +# Cross-compile a thin x86_64 macOS build from an Apple Silicon Mac. +# Pass this file on the first configure with: +# -DCMAKE_TOOLCHAIN_FILE=/absolute/path/to/macos-x86_64-toolchain.cmake +# +# CMAKE_SYSTEM_PROCESSOR is deliberately declared here rather than inferred +# from CMAKE_OSX_ARCHITECTURES, so target-aware CMake dependencies select their +# x86_64 artifacts. +set(CMAKE_SYSTEM_NAME Darwin) +set(CMAKE_SYSTEM_PROCESSOR x86_64) +set(CMAKE_OSX_ARCHITECTURES x86_64 CACHE STRING + "Target macOS architectures" FORCE) + +# Aurora otherwise defaults every cross-build to vendored Dawn. The pinned +# x86_64 package is target-specific and avoids accidentally fetching a source +# release using the binary package's version tag. Allow an explicit override. +set(AURORA_DAWN_PROVIDER package CACHE STRING "Dawn provider for Intel macOS") diff --git a/Launcher/macos/publish-app.command b/Launcher/macos/publish-app.command index d7ce3a19f..36b2c3ea1 100755 --- a/Launcher/macos/publish-app.command +++ b/Launcher/macos/publish-app.command @@ -5,28 +5,36 @@ set -euo pipefail fail() { printf 'publish-app.command: error: %s\n' "$*" >&2; exit 1; } usage() { cat <<'EOF' -Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR +Usage: publish-app.command --build-dir DIR --product {WiiCompiled|RetroRewind} --output-dir DIR [options] Copies a locally built product and its runtime assets into OUTPUT-DIR/.app. It bundles non-system dylibs, rewrites their install names, and ad-hoc signs the result. This is suitable for local use; a release must replace ad-hoc signing with the project's Developer ID signing and notarization process. + + --architecture {arm64|x86_64} Required architecture of the compiled product (default: host) + --minimum-system-version VERSION App bundle minimum macOS version (default: 12.0) EOF } -build_dir=""; product=""; output_dir="" +build_dir=""; product=""; output_dir=""; architecture=$(uname -m); minimum_system_version=12.0 while (($#)); do case "$1" in --build-dir) build_dir=${2:-}; shift 2 ;; --product) product=${2:-}; shift 2 ;; --output-dir) output_dir=${2:-}; shift 2 ;; + --architecture) architecture=${2:-}; shift 2 ;; + --minimum-system-version) minimum_system_version=${2:-}; shift 2 ;; -h|--help) usage; exit 0 ;; *) fail "unknown option: $1" ;; esac done [[ "$product" == WiiCompiled || "$product" == RetroRewind ]] || fail '--product must be WiiCompiled or RetroRewind' -for tool in codesign ditto install_name_tool otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done +[[ "$architecture" == arm64 || "$architecture" == x86_64 ]] || fail '--architecture must be arm64 or x86_64' +[[ "$minimum_system_version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]] || fail '--minimum-system-version must contain two or three period-separated integers' +for tool in codesign ditto install_name_tool lipo otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done [[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product" +lipo "$build_dir/$product" -verify_arch "$architecture" || fail "compiled product is not $architecture: $build_dir/$product" for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done app="$output_dir/$product.app" @@ -47,7 +55,7 @@ cat > "$app/Contents/Info.plist" <CFBundlePackageTypeAPPL CFBundleShortVersionString0.1.0 CFBundleVersion1 - LSMinimumSystemVersion14.0 + LSMinimumSystemVersion$minimum_system_version NSHighResolutionCapable EOF @@ -57,17 +65,45 @@ for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do ln -s "../Resources/$asset" "$macos/$asset" done -# Build a closure of Homebrew dylibs. System libraries remain system references. +# Resolve a non-system dependency from the build product's rpaths. This covers +# both traditional Homebrew dylibs and the vendored dylibs CMake emits under +# the local build directory for a cross-architecture build. +dependency_path() { + local current=$1 dependency=$2 name rpath candidate + case "$dependency" in + /opt/homebrew/*|/usr/local/*) + [[ -f "$dependency" ]] && { printf '%s\n' "$dependency"; return 0; } + ;; + @rpath/*) + name=${dependency##*/} + while IFS= read -r rpath; do + case "$rpath" in + @loader_path/*) rpath="$(dirname "$current")/${rpath#@loader_path/}" ;; + @executable_path/*) rpath="$macos/${rpath#@executable_path/}" ;; + esac + candidate="$rpath/$name" + [[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; } + done < <(otool -l "$current" | awk '/LC_RPATH/{rpath = 1; next} rpath && /path / { print $2; rpath = 0 }') + ;; + @loader_path/*) + candidate="$(dirname "$current")/${dependency#@loader_path/}" + [[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; } + ;; + esac + return 1 +} + +# Build a closure of non-system dylibs. System libraries remain system +# references, while every resolved dependency is copied beside the executable. queue=("$macos/$product") while ((${#queue[@]})); do current=${queue[0]} queue=("${queue[@]:1}") while IFS= read -r dependency; do - [[ "$dependency" == /opt/homebrew/* || "$dependency" == /usr/local/* ]] || continue - [[ -f "$dependency" ]] || continue + dependency_path=$(dependency_path "$current" "$dependency") || continue name=$(basename "$dependency") if [[ ! -f "$frameworks/$name" ]]; then - ditto "$dependency" "$frameworks/$name" + ditto "$dependency_path" "$frameworks/$name" install_name_tool -id "@rpath/$name" "$frameworks/$name" queue+=("$frameworks/$name") fi @@ -75,7 +111,6 @@ while ((${#queue[@]})); do done while IFS= read -r binary; do while IFS= read -r old; do - [[ "$old" == /opt/homebrew/* || "$old" == /usr/local/* ]] || continue name=$(basename "$old") [[ -f "$frameworks/$name" ]] || continue if [[ "$binary" == "$macos/$product" ]]; then diff --git a/Launcher/macos/setup.command b/Launcher/macos/setup.command index 09c291534..315c8211a 100755 --- a/Launcher/macos/setup.command +++ b/Launcher/macos/setup.command @@ -5,10 +5,7 @@ set -euo pipefail resources=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) workspace_source="$resources/workspace" -nodtool="$resources/tools/nodtool" -translator="$resources/tools/Translator.Cli" cmake_bin="$resources/tools/cmake/bin/cmake" -ninja_bin="$resources/tools/ninja" support_root="$HOME/Library/Application Support/WiiCompiled" workspace="$support_root/BuildWorkspace" products="$support_root/Products" @@ -36,6 +33,13 @@ while (($#)); do done [[ "$install_location" == user || "$install_location" == applications ]] || fail '--install-location must be user or applications' +host_arch=$(uname -m) +case "$host_arch" in arm64|x86_64) ;; *) fail "unsupported macOS architecture: $host_arch" ;; esac +host_tools="$resources/tools/$host_arch" +nodtool="$host_tools/nodtool" +translator="$host_tools/Translator.Cli" +ninja_bin="$host_tools/ninja" + if [[ -z "$game" ]]; then game=$(/usr/bin/osascript <<'APPLESCRIPT' set selectedFile to choose file with prompt "Choose your clean Mario Kart Wii PAL (RMCP01) disc image" @@ -60,12 +64,36 @@ if ! /usr/bin/xcode-select -p >/dev/null 2>&1; then /usr/bin/xcode-select --install || true exit 1 fi +for tool in "$nodtool" "$translator" "$cmake_bin" "$ninja_bin"; do + /usr/bin/lipo "$tool" -verify_arch "$host_arch" >/dev/null 2>&1 || \ + fail "the packaged $(basename "$tool") does not support $host_arch" +done mkdir -p "$support_root" "$products" -if [[ ! -d "$workspace/.git" && ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then +source_bundle_version="$workspace_source/.bundle-version" +workspace_bundle_version="$workspace/.bundle-version" +needs_workspace_refresh=0 +if [[ ! -f "$workspace/projects/mkwii/recomp.yml" ]]; then + needs_workspace_refresh=1 +elif [[ -f "$source_bundle_version" ]] && [[ ! -f "$workspace_bundle_version" || "$(<"$source_bundle_version")" != "$(<"$workspace_bundle_version")" ]]; then + needs_workspace_refresh=1 +fi + +if (( needs_workspace_refresh )); then printf 'Preparing the local build workspace...\n' - rm -rf "$workspace" - /usr/bin/ditto "$workspace_source" "$workspace" + if [[ ! -d "$workspace" ]]; then + /usr/bin/ditto "$workspace_source" "$workspace" + else + # Refresh only packaged source inputs. Assets and the staged Retro + # Rewind package belong to the user and stay in place. + for source in aurora-main projects runtime translator Launcher; do + /usr/bin/ditto "$workspace_source/$source" "$workspace/$source" + done + /usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version" + # A dependency provider can be cached in this directory, so make the + # refreshed sources configure from a clean native build tree. + rm -rf "$workspace/native-build-macos-arm64" "$workspace/native-build-macos-x86_64" + fi fi profile=base diff --git a/README.md b/README.md index aceca104f..745b598ee 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ Known limitations of the Wii Remote path: - GPU: GTX 1650 / RX 6400 / Arc A310 or higher - CPU: Intel Core i5-8400 / AMD Ryzen 5 2600 (4c/6c, ~3.5GHz+) or higher - About 20 GB of free disk space during installation (Final game size ~5 GB) -- macOS 14 (Sonoma) or later on Apple Silicon -- On macOS, Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing) +- macOS 12 (Monterey) or later on Apple Silicon (`arm64`) or Intel (`x86_64-v3`); pre-Haswell Intel CPUs are unsupported +- On macOS, a Metal-capable GPU and Apple Xcode Command Line Tools (Setup opens Apple's installer when they are missing) - A clean, unmodified **PAL `RMCP01`** disc image of Mario Kart Wii, dumped by you. ISO, GCM, GCZ, CISO, WBFS, WIA and RVZ are accepted. @@ -118,6 +118,21 @@ image under Settings, turn on **WiiCompiled (beta)**, and hit install from the H Wheel Wizard downloads the setup tool from this repo and walks you through install, updates and launching. The backend itself is deliberately command-line only, Wheel Wizard is a wrapper around it. +### macOS + +Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. It requires an +Apple Silicon Mac because its bundled nodtool and Translator.Cli executables are arm64. It installs +**WiiCompiled Setup** in Applications; open that app, choose your clean PAL `RMCP01` disc image, +and select either the base game or Retro Rewind. For Retro Rewind, choose the `RetroRewind6` folder +or its parent folder. + +Setup verifies and extracts the image locally, then translates and compiles the native app on your +Mac. On a first run it may ask macOS to install Xcode Command Line Tools; complete Apple's installer, +then open Setup again. When the build completes, Setup asks for administrator approval once to install +`WiiCompiled.app` (and, if selected, `RetroRewind.app`) in `/Applications`. + +Setup opens Terminal while it works, so the extraction and build progress—and any error that needs +reporting—remain visible. > [!CAUTION] > Only take builds from this repository's diff --git a/aurora-main/cmake/AuroraSDL3Provider.cmake b/aurora-main/cmake/AuroraSDL3Provider.cmake index 16ce9336a..7f3de510f 100644 --- a/aurora-main/cmake/AuroraSDL3Provider.cmake +++ b/aurora-main/cmake/AuroraSDL3Provider.cmake @@ -41,7 +41,10 @@ if (_aurora_sdl3_provider STREQUAL "auto") set(_aurora_sdl3_provider "package") else () set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON) - find_package(SDL3 QUIET) + # Aurora uses APIs from the SDL version pinned by AURORA_SDL3_VERSION. + # Do not silently select an older system package and fail later while + # compiling its headers. + find_package(SDL3 ${AURORA_SDL3_VERSION} QUIET) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF) if (SDL3_FOUND) set(_aurora_sdl3_provider "system") @@ -58,7 +61,7 @@ if (_aurora_sdl3_provider STREQUAL "system") message(STATUS "aurora: Using system SDL3 (provider=system)") if (NOT SDL3_FOUND) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ON) - find_package(SDL3 REQUIRED) + find_package(SDL3 ${AURORA_SDL3_VERSION} REQUIRED) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL OFF) endif () _aurora_sdl3_select_target() diff --git a/aurora-main/cmake/aurora_core.cmake b/aurora-main/cmake/aurora_core.cmake index 3e53a62c3..b17cb4249 100644 --- a/aurora-main/cmake/aurora_core.cmake +++ b/aurora-main/cmake/aurora_core.cmake @@ -41,7 +41,9 @@ if (AURORA_ENABLE_GX) target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_METAL) target_sources(aurora_core PRIVATE lib/dawn/MetalBinding.mm) set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) - target_link_options(aurora_core PUBLIC "LINKER:-weak_framework,Metal") + target_link_options(aurora_core PUBLIC + "LINKER:-weak_framework,Metal" + "LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor") endif () if (DAWN_ENABLE_D3D11) target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_D3D11) diff --git a/aurora-main/extern/CMakeLists.txt b/aurora-main/extern/CMakeLists.txt index 9d77bbbc3..1612b4fe1 100644 --- a/aurora-main/extern/CMakeLists.txt +++ b/aurora-main/extern/CMakeLists.txt @@ -16,10 +16,60 @@ function(aurora_find_package_global) set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL ${_PREV_FIND_PACKAGE_TARGETS_GLOBAL}) endfunction() +# A macOS build for another architecture must not discover Homebrew packages +# built for its physical host. CMAKE_CROSSCOMPILING is not sufficient here: +# Apple Clang can target another architecture through CMAKE_OSX_ARCHITECTURES +# without CMake considering the configure a cross-build. +set(_AURORA_EXCLUDE_HOST_HOMEBREW FALSE) +if (APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _AURORA_OSX_ARCH_COUNT) + if (_AURORA_OSX_ARCH_COUNT EQUAL 1) + list(GET CMAKE_OSX_ARCHITECTURES 0 _AURORA_TARGET_ARCH) + string(TOLOWER "${_AURORA_TARGET_ARCH}" _AURORA_TARGET_ARCH) + # hw.optional.arm64 identifies Apple Silicon even when CMake itself runs + # through Rosetta, where CMAKE_HOST_SYSTEM_PROCESSOR reports x86_64. + execute_process( + COMMAND /usr/sbin/sysctl -n hw.optional.arm64 + RESULT_VARIABLE _AURORA_ARM64_PROBE_RESULT + OUTPUT_VARIABLE _AURORA_ARM64_PROBE + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if (_AURORA_ARM64_PROBE_RESULT EQUAL 0 AND _AURORA_ARM64_PROBE STREQUAL "1") + set(_AURORA_HOST_ARCH arm64) + else () + execute_process( + COMMAND /usr/bin/uname -m + OUTPUT_VARIABLE _AURORA_HOST_ARCH + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(TOLOWER "${_AURORA_HOST_ARCH}" _AURORA_HOST_ARCH) + endif () + if (_AURORA_TARGET_ARCH STREQUAL "x86_64" AND _AURORA_HOST_ARCH MATCHES "^(arm64|aarch64)$") + list(APPEND CMAKE_IGNORE_PREFIX_PATH "/opt/homebrew") + set(_AURORA_EXCLUDE_HOST_HOMEBREW TRUE) + elseif (_AURORA_TARGET_ARCH MATCHES "^(arm64|aarch64)$" AND _AURORA_HOST_ARCH MATCHES "^(x86_64|amd64)$") + list(APPEND CMAKE_IGNORE_PREFIX_PATH "/usr/local") + set(_AURORA_EXCLUDE_HOST_HOMEBREW TRUE) + endif () + endif () +endif () + +if (_AURORA_EXCLUDE_HOST_HOMEBREW) + list(REMOVE_DUPLICATES CMAKE_IGNORE_PREFIX_PATH) + message(STATUS "aurora: cross-architecture macOS build; ignoring host Homebrew prefixes") +endif () + if (AURORA_ENABLE_GX) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraDawnProvider.cmake) endif () +# SDL's pkg-config probe can bypass CMake's prefix exclusion. macOS does not +# need libusb for the supported SDL input paths, so keep that host-only library +# out of a cross-architecture configure. +if (_AURORA_EXCLUDE_HOST_HOMEBREW) + set(SDL_HIDAPI_LIBUSB OFF CACHE BOOL "" FORCE) + set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE) +endif () + # Abseil is needed for core libraries. It normally comes via Dawn's vendor build. # Otherwise prefer a system package and only fetch it as a last resort. if (NOT TARGET absl::flat_hash_map OR NOT TARGET absl::btree) diff --git a/aurora-main/lib/dolphin/pad/pad.cpp b/aurora-main/lib/dolphin/pad/pad.cpp index 75ef3721d..dc7dea20a 100644 --- a/aurora-main/lib/dolphin/pad/pad.cpp +++ b/aurora-main/lib/dolphin/pad/pad.cpp @@ -6,10 +6,10 @@ #include #include +#include #include #include #include -#include namespace { constexpr int32_t k_mappingsFileVersion = 3; @@ -354,7 +354,7 @@ BOOL PADInit() { } g_initialized = true; - std::ranges::for_each(g_keyboardBindings, [](auto& state) { + std::for_each(g_keyboardBindings.begin(), g_keyboardBindings.end(), [](auto& state) { state.m_buttonMapping = g_defaultKeys; state.m_axisMapping = g_defaultKeyAxis; }); @@ -646,8 +646,8 @@ static void EnsureMappingLoaded(aurora::input::GameController* controller) { static Sint16 _get_axis_value(const aurora::input::GameController* controller, // NOLINT(*-reserved-identifier) PADAxis axis) { - const auto iter = - std::ranges::find_if(controller->m_axisMapping, [axis](const auto& pair) { return pair.padAxis == axis; }); + const auto iter = std::find_if(controller->m_axisMapping.begin(), controller->m_axisMapping.end(), + [axis](const auto& pair) { return pair.padAxis == axis; }); if (iter == controller->m_axisMapping.end()) { return 0; } @@ -737,8 +737,8 @@ u32 PADRead(PADStatus* status) { status[i].err = PAD_ERR_NONE; if (g_keyboardBindings[i].m_mappingsSet && SDL_GetKeyboardFocus() != nullptr) { - std::ranges::for_each( - g_keyboardBindings[i].m_buttonMapping, [&kbState, &numKeys, &i, &status](const PADKeyButtonBinding& mapping) { + std::for_each(g_keyboardBindings[i].m_buttonMapping.begin(), g_keyboardBindings[i].m_buttonMapping.end(), + [&kbState, &numKeys, &i, &status](const PADKeyButtonBinding& mapping) { if (mapping.scancode > PAD_KEY_INVALID && mapping.scancode < numKeys && kbState[mapping.scancode]) { status[i].button |= mapping.padButton; } else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) { @@ -845,8 +845,8 @@ u32 PADRead(PADStatus* status) { bool leftTriggerSet = false; bool rightTriggerSet = false; - std::ranges::for_each(controller->m_buttonMapping, [&controller, &i, &status, &leftTriggerSet, - &rightTriggerSet](const auto& mapping) { + std::for_each(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(), + [&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) { if (is_native_binding_pressed(controller->m_controller, mapping.nativeButton)) { status[i].button |= mapping.padButton; } @@ -859,8 +859,8 @@ u32 PADRead(PADStatus* status) { } }); - std::ranges::for_each(controller->m_altButtonMapping, [&controller, &i, &status, &leftTriggerSet, - &rightTriggerSet](const auto& mapping) { + std::for_each(controller->m_altButtonMapping.begin(), controller->m_altButtonMapping.end(), + [&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) { if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) { return; } @@ -1189,8 +1189,8 @@ void PADSetButtonMapping(const u32 port, const PADButtonMapping mapping) { return; } - const auto iter = std::ranges::find_if(controller->m_buttonMapping, - [mapping](const auto& pair) { return mapping.padButton == pair.padButton; }); + const auto iter = std::find_if(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(), + [mapping](const auto& pair) { return mapping.padButton == pair.padButton; }); if (iter == controller->m_buttonMapping.end()) { return; } @@ -1223,8 +1223,8 @@ void PADSetAltButtonMapping(const u32 port, const PADButtonMapping mapping) { return; } - const auto iter = std::ranges::find_if(controller->m_altButtonMapping, - [mapping](const auto& pair) { return mapping.padButton == pair.padButton; }); + const auto iter = std::find_if(controller->m_altButtonMapping.begin(), controller->m_altButtonMapping.end(), + [mapping](const auto& pair) { return mapping.padButton == pair.padButton; }); if (iter == controller->m_altButtonMapping.end()) { return; } @@ -1250,8 +1250,8 @@ void PADSetAxisMapping(const u32 port, const PADAxisMapping mapping) { return; } - const auto iter = std::ranges::find_if(controller->m_axisMapping, - [mapping](const auto& pair) { return mapping.padAxis == pair.padAxis; }); + const auto iter = std::find_if(controller->m_axisMapping.begin(), controller->m_axisMapping.end(), + [mapping](const auto& pair) { return mapping.padAxis == pair.padAxis; }); if (iter == controller->m_axisMapping.end()) { return; } @@ -1425,9 +1425,10 @@ static void load_keyboard_bindings() { if (mappingsSet) { const bool anyBound = - std::ranges::any_of(buttonMapping, - [](const PADKeyButtonBinding& b) { return b.scancode != PAD_KEY_INVALID; }) || - std::ranges::any_of(axisMapping, [](const PADKeyAxisBinding& b) { return b.scancode != PAD_KEY_INVALID; }); + std::any_of(buttonMapping.begin(), buttonMapping.end(), + [](const PADKeyButtonBinding& b) { return b.scancode != PAD_KEY_INVALID; }) || + std::any_of(axisMapping.begin(), axisMapping.end(), + [](const PADKeyAxisBinding& b) { return b.scancode != PAD_KEY_INVALID; }); if (!anyBound) { mappingsSet = false; } @@ -1467,7 +1468,10 @@ void __PADWriteDeadZones(SDL_IOStream* file, // NOLINT(*-reserved-identifier) void PADSerializeMappings() { const std::filesystem::path basePath = fs_path_from_string(aurora::g_config.userPath); - for (auto& controller : aurora::input::g_GameControllers | std::views::values) { + // Avoid std::views::values here: older Apple libc++ releases implement the + // C++20 ranges algorithms we use but not this adaptor. + for (auto& entry : aurora::input::g_GameControllers) { + auto& controller = entry.second; EnsureMappingLoaded(&controller); const auto filePath = basePath / fmt::format("{}_{:04X}_{:04X}.controller", aurora::input::controller_name(controller.m_index), @@ -1569,8 +1573,8 @@ static constexpr std::array, PAD_AXIS_COU const char* PADGetButtonName(const PADButton button) { - if (const auto iter = - std::ranges::find_if(skButtonNames, [&button](const auto& pair) { return button == pair.first; }); + if (const auto iter = std::find_if(skButtonNames.begin(), skButtonNames.end(), + [&button](const auto& pair) { return button == pair.first; }); iter != skButtonNames.end()) { return iter->second.data(); } @@ -1583,7 +1587,8 @@ const char* PADGetNativeButtonName(u32 button) { } const char* PADGetAxisName(const PADAxis axis) { - if (const auto it = std::ranges::find_if(skAxisNames, [&axis](const auto& pair) { return axis == pair.first; }); + if (const auto it = std::find_if(skAxisNames.begin(), skAxisNames.end(), + [&axis](const auto& pair) { return axis == pair.first; }); it != skAxisNames.end()) { return it->second.data(); } @@ -1592,7 +1597,8 @@ const char* PADGetAxisName(const PADAxis axis) { } const char* PADGetAxisDirectionLabel(const PADAxis axis) { - if (const auto it = std::ranges::find_if(skAxisDirLabels, [&axis](const auto& pair) { return axis == pair.first; }); + if (const auto it = std::find_if(skAxisDirLabels.begin(), skAxisDirLabels.end(), + [&axis](const auto& pair) { return axis == pair.first; }); it != skAxisDirLabels.end()) { return it->second.data(); } diff --git a/aurora-main/lib/gfx/common.cpp b/aurora-main/lib/gfx/common.cpp index 4c3f31d47..e94f583f4 100644 --- a/aurora-main/lib/gfx/common.cpp +++ b/aurora-main/lib/gfx/common.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -1453,8 +1452,8 @@ static void render_impl(std::vector& renderPasses, wgpu::CommandEnco #if defined(AURORA_GFX_DEBUG_GROUPS) if (finalize && !debugFrame.groups.empty()) { - for (auto& it : std::ranges::reverse_view(debugFrame.groups)) { - Log.warn("Debug group was not popped at end of frame: {}", it); + for (auto it = debugFrame.groups.rbegin(); it != debugFrame.groups.rend(); ++it) { + Log.warn("Debug group was not popped at end of frame: {}", *it); } debugFrame.groups.clear(); } diff --git a/aurora-main/lib/gfx/texture_replacement.cpp b/aurora-main/lib/gfx/texture_replacement.cpp index 67913811a..e8d0fbfe9 100644 --- a/aurora-main/lib/gfx/texture_replacement.cpp +++ b/aurora-main/lib/gfx/texture_replacement.cpp @@ -553,7 +553,7 @@ void build_index() noexcept { continue; } - s_replacementIndex.try_emplace(*parsed, path); + s_replacementIndex.try_emplace(*parsed, ReplacementIndexEntry{path}); } Log.info("Indexed {} texture replacements", s_replacementIndex.size()); diff --git a/aurora-main/lib/system_info.cpp b/aurora-main/lib/system_info.cpp index 15a58d41c..9197cd0e2 100644 --- a/aurora-main/lib/system_info.cpp +++ b/aurora-main/lib/system_info.cpp @@ -235,7 +235,7 @@ std::string GetOSVersion() { constexpr auto name = "iOS"; #elif TARGET_OS_TV constexpr auto name = "tvOS"; -#elif +#else constexpr auto name = Unknown; #endif diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 336370bbd..5e1212b90 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -1,27 +1,78 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.16) + +# Dawn's pinned macOS artifacts target 12.0. Set the same floor before project() +# initializes the Apple toolchain so direct developer CMake invocations cannot +# accidentally inherit the running SDK's deployment version. This cache entry +# is harmless on non-Apple platforms and remains overridable by a caller. +if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) + set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING + "Minimum macOS version supported by WiiCompiled" FORCE) +endif() project(mkw_recompiled) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8) message(FATAL_ERROR "WiiCompiled requires a 64-bit Clang toolchain") endif() +if(APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES MKW_OSX_ARCHITECTURE_COUNT) + if(MKW_OSX_ARCHITECTURE_COUNT GREATER 1) + message(FATAL_ERROR + "WiiCompiled supports one macOS architecture per build directory; " + "configure separate arm64 and x86_64 build directories") + endif() + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" MKW_OSX_PROCESSOR) + string(TOLOWER "${CMAKE_OSX_ARCHITECTURES}" MKW_OSX_ARCHITECTURE) + if(MKW_OSX_PROCESSOR STREQUAL "amd64") + set(MKW_OSX_PROCESSOR x86_64) + endif() + if(NOT MKW_OSX_PROCESSOR STREQUAL MKW_OSX_ARCHITECTURE) + message(FATAL_ERROR + "CMAKE_OSX_ARCHITECTURES must match CMAKE_SYSTEM_PROCESSOR so dependencies " + "and context backends use the target architecture. For Intel builds on Apple " + "Silicon, configure a fresh directory with Launcher/macos/macos-x86_64-toolchain.cmake.") + endif() +endif() + if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") set(MKW_PLATFORM_WINDOWS TRUE) -elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|ARM64)$") - # The first native macOS target is Apple Silicon. Intel and universal - # binaries remain future compatibility work; do not silently claim them. +elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|arm64|ARM64)$") set(MKW_PLATFORM_MACOS TRUE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") + set(MKW_PLATFORM_MACOS_X86_64 TRUE) + else() + set(MKW_PLATFORM_MACOS_ARM64 TRUE) + endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|aarch64|arm64|ARM64)$") set(MKW_PLATFORM_LINUX TRUE) else() message(FATAL_ERROR - "WiiCompiled supports 64-bit LLVM-MinGW Clang on Windows, native Linux x86_64/aarch64, or Apple Clang on macOS arm64") + "WiiCompiled supports 64-bit LLVM-MinGW Clang on Windows, native Linux x86_64/aarch64, or Apple Clang on macOS x86_64/arm64") endif() + if(NOT CMAKE_BUILD_TYPE STREQUAL "Release") message(FATAL_ERROR "WiiCompiled only supports Release builds") endif() option(MKW_BUILD_PRODUCTS "Build translated WiiCompiled product targets" ON) +set(MKW_X86_CPU_PROFILE "v3" CACHE STRING + "Minimum x86-64 CPU profile for translated products (v2 or v3)") +set_property(CACHE MKW_X86_CPU_PROFILE PROPERTY STRINGS v2 v3) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$" AND + NOT MKW_X86_CPU_PROFILE MATCHES "^(v2|v3)$") + message(FATAL_ERROR "MKW_X86_CPU_PROFILE must be v2 or v3 (got '${MKW_X86_CPU_PROFILE}')") +endif() + +option(MKW_PLATFORM_TESTS_ONLY "Build host-platform tests without graphics dependencies or game assets" OFF) +include("${CMAKE_CURRENT_LIST_DIR}/cmake/HostLibraries.cmake") +if(MKW_PLATFORM_TESTS_ONLY) + if(MKW_BUILD_PRODUCTS) + message(FATAL_ERROR "MKW_PLATFORM_TESTS_ONLY requires MKW_BUILD_PRODUCTS=OFF") + endif() + add_compile_definitions(NOMINMAX) + include("${CMAKE_CURRENT_LIST_DIR}/cmake/PlatformTests.cmake") + return() +endif() # Preprocessor definitions that belong to this project's own code (the runtime, # the translated shards and the product glue) and to nothing else. They are @@ -59,20 +110,6 @@ target_include_directories(mkw_pugixml PUBLIC third_party/pugixml) target_compile_features(mkw_pugixml PUBLIC cxx_std_17) set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF) -# Linux guest-fiber scheduling (runtime/src/host_context.cpp) needs a symmetric -# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers -# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike -# asymmetric resume/yield coroutine libraries, which would need every call site restructured). -# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is -# separately BSD-style licensed, see third_party/libco/LICENSE). Windows keeps native Fibers -# and macOS uses the project's x18-safe AArch64 assembly backend, so this target is Linux-only. -if(MKW_PLATFORM_LINUX) - add_library(mkw_libco STATIC third_party/libco/libco.c) - add_library(mkw::libco ALIAS mkw_libco) - target_include_directories(mkw_libco PUBLIC third_party/libco) - set_target_properties(mkw_libco PROPERTIES UNITY_BUILD OFF) -endif() - # Runtime configuration is real TOML, parsed by toml11 rather than a project- # specific line parser. Keep it header-only and vendored so disconnected release # builds have exactly the same parser as developer builds. @@ -280,12 +317,14 @@ endif() file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp") if(MKW_PLATFORM_MACOS) list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory.cpp") - # HostContext's Apple Silicon backend is implemented in a small assembly - # companion. It must be part of the product runtime as well as the - # standalone context test; otherwise the final executable is missing - # mkw_co_init/mkw_co_switch at link time. - enable_language(ASM) - list(APPEND SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S") + if(MKW_PLATFORM_MACOS_ARM64) + # HostContext's Apple Silicon backend is implemented in a small assembly + # companion. It must be part of the product runtime as well as the + # standalone context test; otherwise the final executable is missing + # mkw_co_init/mkw_co_switch at link time. + enable_language(ASM) + list(APPEND SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S") + endif() else() list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory_macos.cpp") endif() @@ -299,87 +338,7 @@ set(MKW_CPU_BASELINE_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/host_cpu_baseline.cpp list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE} ${MKW_CPU_BASELINE_SOURCE} ${MKW_PLATFORM_SOURCE}) -# This deliberately small library contains host services that are safe to -# validate before guest memory and fiber work makes a full runtime build viable. -add_library(mkw_platform STATIC "${MKW_PLATFORM_SOURCE}") -target_include_directories(mkw_platform PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include") -target_compile_features(mkw_platform PUBLIC cxx_std_17) -set_target_properties(mkw_platform PROPERTIES UNITY_BUILD OFF) - -# Keep these independent from Aurora's BUILD_TESTING option: they validate the -# project's host-platform contracts, not Aurora's third-party test suite. -enable_testing() -add_executable(mkw_platform_paths_tests "${CMAKE_CURRENT_LIST_DIR}/tests/platform_paths_tests.cpp") -target_link_libraries(mkw_platform_paths_tests PRIVATE mkw_platform) -target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17) -add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests) - -add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp") -target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") -target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17) -add_test(NAME mkw_nand_save_tests COMMAND mkw_nand_save_tests) - -add_executable(mkw_nand_settings_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_settings_tests.cpp") -find_package(Threads REQUIRED) -target_link_libraries(mkw_nand_settings_tests PRIVATE Threads::Threads) -target_include_directories(mkw_nand_settings_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") -target_compile_features(mkw_nand_settings_tests PRIVATE cxx_std_17) -add_test(NAME mkw_nand_settings_tests COMMAND mkw_nand_settings_tests) - -add_executable(mkw_sc_serial_tests "${CMAKE_CURRENT_LIST_DIR}/tests/sc_serial_tests.cpp") -target_include_directories(mkw_sc_serial_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") -target_compile_features(mkw_sc_serial_tests PRIVATE cxx_std_17) -add_test(NAME mkw_sc_serial_tests COMMAND mkw_sc_serial_tests) - -# The input expression engine is self-contained, so it can be exercised without -# linking the runtime or SDL. -add_executable(mkw_input_expr_tests - "${CMAKE_CURRENT_LIST_DIR}/tests/test_expr.cpp" - "${CMAKE_CURRENT_LIST_DIR}/src/input_expr.cpp") -target_include_directories(mkw_input_expr_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") -target_compile_features(mkw_input_expr_tests PRIVATE cxx_std_17) -add_test(NAME mkw_input_expr_tests COMMAND mkw_input_expr_tests) - -# HostContext deliberately keeps the platform-specific context primitive out -# of fiber_manager.cpp. Exercise the Linux libco handoff directly so future -# refactors cannot silently remove its headers, implementation, or link edge. -if(MKW_PLATFORM_LINUX) - add_executable(mkw_linux_host_context_tests - "${CMAKE_CURRENT_LIST_DIR}/tests/host_context_tests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp") - target_include_directories(mkw_linux_host_context_tests PRIVATE - "${CMAKE_CURRENT_LIST_DIR}/include" - "${CMAKE_CURRENT_LIST_DIR}/third_party/libco") - target_compile_features(mkw_linux_host_context_tests PRIVATE cxx_std_17) - target_link_libraries(mkw_linux_host_context_tests PRIVATE mkw::libco) - add_test(NAME mkw_linux_host_context_tests COMMAND mkw_linux_host_context_tests) -endif() - -if(MKW_PLATFORM_MACOS) - # Exercise the Apple Silicon context ABI and the public host-memory - # contracts separately from translated products. - enable_language(ASM) - add_executable(mkw_macos_context_abi_tests - "${CMAKE_CURRENT_LIST_DIR}/tests/macos_context_abi_tests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S") - target_compile_features(mkw_macos_context_abi_tests PRIVATE cxx_std_17) - add_test(NAME mkw_macos_context_abi_tests COMMAND mkw_macos_context_abi_tests) - - add_executable(mkw_macos_host_context_tests - "${CMAKE_CURRENT_LIST_DIR}/tests/host_context_tests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/src/host_context.cpp" - "${CMAKE_CURRENT_LIST_DIR}/src/platform/macos/co_switch.S") - target_include_directories(mkw_macos_host_context_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") - target_compile_features(mkw_macos_host_context_tests PRIVATE cxx_std_17) - add_test(NAME mkw_macos_host_context_tests COMMAND mkw_macos_host_context_tests) - - add_executable(mkw_macos_guest_flat_memory_tests - "${CMAKE_CURRENT_LIST_DIR}/tests/macos_guest_flat_memory_tests.cpp" - "${CMAKE_CURRENT_LIST_DIR}/src/guest_flat_memory_macos.cpp") - target_include_directories(mkw_macos_guest_flat_memory_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include") - target_compile_features(mkw_macos_guest_flat_memory_tests PRIVATE cxx_std_17) - add_test(NAME mkw_macos_guest_flat_memory_tests COMMAND mkw_macos_guest_flat_memory_tests) -endif() +include("${CMAKE_CURRENT_LIST_DIR}/cmake/PlatformTests.cmake") # The translator emits the complete, content-addressed source graph. Consuming # this one manifest keeps configure independent of the 28k generated function @@ -428,6 +387,14 @@ else() target_link_libraries(mkw_macos_native_compile PRIVATE aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx mkw::pugixml mkw::toml11 mkw::cryptopp) + if(MKW_PLATFORM_MACOS_X86_64) + target_link_libraries(mkw_macos_native_compile PRIVATE mkw::libco) + target_compile_options(mkw_macos_native_compile PRIVATE + -march=x86-64-${MKW_X86_CPU_PROFILE}) + if(MKW_X86_CPU_PROFILE STREQUAL "v2") + target_compile_definitions(mkw_macos_native_compile PRIVATE MKW_X86_CPU_PROFILE_V2=1) + endif() + endif() set_target_properties(mkw_macos_native_compile PROPERTIES UNITY_BUILD OFF) endif() add_custom_target(mkw_platform_paths_check DEPENDS mkw_platform) diff --git a/runtime/cmake/HostLibraries.cmake b/runtime/cmake/HostLibraries.cmake new file mode 100644 index 000000000..e5f7dac4c --- /dev/null +++ b/runtime/cmake/HostLibraries.cmake @@ -0,0 +1,29 @@ +# Shared by the product build and the dependency-free platform test configuration. +get_filename_component(MKW_HOST_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) + +# POSIX guest-fiber scheduling (runtime/src/host_context.cpp) needs a symmetric +# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers +# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike +# asymmetric resume/yield coroutine libraries, which would need every call site restructured). +# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is +# separately BSD-style licensed, see third_party/libco/LICENSE). Windows keeps native Fibers; +# Apple Silicon uses the project's x18-safe AArch64 assembly backend, while Intel macOS uses +# libco's existing System V AMD64 backend. +if(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64) + add_library(mkw_libco STATIC "${MKW_HOST_RUNTIME_DIR}/third_party/libco/libco.c") + add_library(mkw::libco ALIAS mkw_libco) + target_include_directories(mkw_libco PUBLIC "${MKW_HOST_RUNTIME_DIR}/third_party/libco") + set_target_properties(mkw_libco PROPERTIES UNITY_BUILD OFF) +endif() + +# This deliberately small library contains host services that are safe to +# validate before guest memory and fiber work makes a full runtime build viable. +add_library(mkw_platform STATIC "${MKW_HOST_RUNTIME_DIR}/src/platform/host_platform.cpp") +target_include_directories(mkw_platform PUBLIC "${MKW_HOST_RUNTIME_DIR}/include") +target_compile_features(mkw_platform PUBLIC cxx_std_17) +set_target_properties(mkw_platform PROPERTIES UNITY_BUILD OFF) + +if(MKW_PLATFORM_WINDOWS) + target_compile_definitions(mkw_platform PRIVATE NOMINMAX) + target_link_libraries(mkw_platform PUBLIC shell32 ole32 uuid) +endif() diff --git a/runtime/cmake/PlatformTests.cmake b/runtime/cmake/PlatformTests.cmake new file mode 100644 index 000000000..81e572390 --- /dev/null +++ b/runtime/cmake/PlatformTests.cmake @@ -0,0 +1,118 @@ +get_filename_component(MKW_TEST_RUNTIME_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) + +# Keep these independent from Aurora's BUILD_TESTING option: they validate the +# project's host-platform contracts, not Aurora's third-party test suite. +enable_testing() +add_executable(mkw_platform_paths_tests "${MKW_TEST_RUNTIME_DIR}/tests/platform_paths_tests.cpp") +target_link_libraries(mkw_platform_paths_tests PRIVATE mkw_platform) +target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17) +add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests) + +add_executable(mkw_nand_save_tests "${MKW_TEST_RUNTIME_DIR}/tests/nand_save_tests.cpp") +target_include_directories(mkw_nand_save_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") +target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17) +add_test(NAME mkw_nand_save_tests COMMAND mkw_nand_save_tests) + +add_executable(mkw_nand_settings_tests "${MKW_TEST_RUNTIME_DIR}/tests/nand_settings_tests.cpp") +find_package(Threads REQUIRED) +target_link_libraries(mkw_nand_settings_tests PRIVATE Threads::Threads) +target_include_directories(mkw_nand_settings_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") +target_compile_features(mkw_nand_settings_tests PRIVATE cxx_std_17) +add_test(NAME mkw_nand_settings_tests COMMAND mkw_nand_settings_tests) + +add_executable(mkw_sc_serial_tests "${MKW_TEST_RUNTIME_DIR}/tests/sc_serial_tests.cpp") +target_include_directories(mkw_sc_serial_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") +target_compile_features(mkw_sc_serial_tests PRIVATE cxx_std_17) +add_test(NAME mkw_sc_serial_tests COMMAND mkw_sc_serial_tests) + +# The input expression engine is self-contained, so it can be exercised without +# linking the runtime or SDL. +add_executable(mkw_input_expr_tests + "${MKW_TEST_RUNTIME_DIR}/tests/test_expr.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/input_expr.cpp") +target_include_directories(mkw_input_expr_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") +target_compile_features(mkw_input_expr_tests PRIVATE cxx_std_17) +add_test(NAME mkw_input_expr_tests COMMAND mkw_input_expr_tests) + +# HostContext deliberately keeps the platform-specific context primitive out +# of fiber_manager.cpp. Exercise the Linux libco handoff directly so future +# refactors cannot silently remove its headers, implementation, or link edge. +if(MKW_PLATFORM_LINUX) + add_executable(mkw_linux_host_context_tests + "${MKW_TEST_RUNTIME_DIR}/tests/host_context_tests.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/host_context.cpp") + target_include_directories(mkw_linux_host_context_tests PRIVATE + "${MKW_TEST_RUNTIME_DIR}/include" + "${MKW_TEST_RUNTIME_DIR}/third_party/libco") + target_compile_features(mkw_linux_host_context_tests PRIVATE cxx_std_17) + target_link_libraries(mkw_linux_host_context_tests PRIVATE mkw::libco) + add_test(NAME mkw_linux_host_context_tests COMMAND mkw_linux_host_context_tests) +endif() + +if(MKW_PLATFORM_WINDOWS) + add_executable(mkw_windows_host_context_tests + "${MKW_TEST_RUNTIME_DIR}/tests/host_context_tests.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/host_context.cpp") + target_include_directories(mkw_windows_host_context_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") + target_compile_features(mkw_windows_host_context_tests PRIVATE cxx_std_17) + add_test(NAME mkw_windows_host_context_tests COMMAND mkw_windows_host_context_tests) +endif() + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") + # Compile the same oracle cases for both paths. The v2 binary proves the + # scalar fallback stays free of FMA; the v3 binary checks the existing + # vector intrinsic path against the same strict result. + foreach(profile IN ITEMS v2 v3) + add_executable(mkw_ppc_pair_fma_${profile}_tests + "${MKW_TEST_RUNTIME_DIR}/tests/ppc_pair_fma_tests.cpp") + target_include_directories(mkw_ppc_pair_fma_${profile}_tests PRIVATE + "${MKW_TEST_RUNTIME_DIR}/include") + target_compile_features(mkw_ppc_pair_fma_${profile}_tests PRIVATE cxx_std_17) + target_compile_options(mkw_ppc_pair_fma_${profile}_tests PRIVATE + -march=x86-64-${profile} -fno-fast-math -ffp-contract=off) + add_test(NAME mkw_ppc_pair_fma_${profile}_tests COMMAND mkw_ppc_pair_fma_${profile}_tests) + endforeach() + + add_library(mkw_cpu_baseline_v2_compile OBJECT + "${MKW_TEST_RUNTIME_DIR}/src/host_cpu_baseline.cpp") + target_compile_features(mkw_cpu_baseline_v2_compile PRIVATE cxx_std_17) + target_compile_definitions(mkw_cpu_baseline_v2_compile PRIVATE MKW_X86_CPU_PROFILE_V2=1) + target_compile_options(mkw_cpu_baseline_v2_compile PRIVATE -march=x86-64-v2 -w) +endif() + +if(MKW_PLATFORM_MACOS) + # Exercise the public host-memory contracts separately from translated products. + if(MKW_PLATFORM_MACOS_ARM64) + # Apple Silicon's context ABI is implemented by the local assembly backend. + enable_language(ASM) + add_executable(mkw_macos_context_abi_tests + "${MKW_TEST_RUNTIME_DIR}/tests/macos_context_abi_tests.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/platform/macos/co_switch.S") + target_compile_features(mkw_macos_context_abi_tests PRIVATE cxx_std_17) + add_test(NAME mkw_macos_context_abi_tests COMMAND mkw_macos_context_abi_tests) + + add_executable(mkw_macos_host_context_tests + "${MKW_TEST_RUNTIME_DIR}/tests/host_context_tests.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/host_context.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/platform/macos/co_switch.S") + target_include_directories(mkw_macos_host_context_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") + else() + # Intel macOS follows the same System V AMD64 libco path as Linux. + add_executable(mkw_macos_host_context_tests + "${MKW_TEST_RUNTIME_DIR}/tests/host_context_tests.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/host_context.cpp") + target_include_directories(mkw_macos_host_context_tests PRIVATE + "${MKW_TEST_RUNTIME_DIR}/include" + "${MKW_TEST_RUNTIME_DIR}/third_party/libco") + target_link_libraries(mkw_macos_host_context_tests PRIVATE mkw::libco) + endif() + target_compile_features(mkw_macos_host_context_tests PRIVATE cxx_std_17) + add_test(NAME mkw_macos_host_context_tests COMMAND mkw_macos_host_context_tests) + + add_executable(mkw_macos_guest_flat_memory_tests + "${MKW_TEST_RUNTIME_DIR}/tests/macos_guest_flat_memory_tests.cpp" + "${MKW_TEST_RUNTIME_DIR}/src/guest_flat_memory_macos.cpp") + target_include_directories(mkw_macos_guest_flat_memory_tests PRIVATE "${MKW_TEST_RUNTIME_DIR}/include") + target_compile_features(mkw_macos_guest_flat_memory_tests PRIVATE cxx_std_17) + add_test(NAME mkw_macos_guest_flat_memory_tests COMMAND mkw_macos_guest_flat_memory_tests) +endif() diff --git a/runtime/cmake/PublicProducts.cmake b/runtime/cmake/PublicProducts.cmake index e76e75a5e..414e1fdfd 100644 --- a/runtime/cmake/PublicProducts.cmake +++ b/runtime/cmake/PublicProducts.cmake @@ -1,4 +1,4 @@ -# Public WiiCompiled product graph. +# Public WiiCompiled product graph. # # The translator owns the translated build graph. Mario Kart's profile-neutral # functions are compiled once into mkw_base_shared; only callers whose direct @@ -84,9 +84,9 @@ target_link_libraries(mkw_runtime_common PRIVATE target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls) if(MKW_PLATFORM_WINDOWS) target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp) -elseif(MKW_PLATFORM_LINUX) +elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64) # ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS - # media monitoring). Empty string on glibc >= 2.34 where dl* is in libc. + # media monitoring). Empty on platforms where dl* is already in libc/libSystem. target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS}) endif() if(MKW_CPPWINRT_INCLUDE_DIR) @@ -132,15 +132,18 @@ set_target_properties(mkw_runtime_common PROPERTIES UNITY_BUILD ON UNITY_BUILD_M target_precompile_headers(mkw_runtime_common PRIVATE "${MKW_RUNTIME_SOURCE_DIR}/include/mkw_pch.h") mkw_apply_common_compile_options(mkw_runtime_common) -# Host ISA guard. Windows and Linux x86_64 product targets use x86-64-v3, so -# this object deliberately keeps the plain baseline ISA and checks the CPU -# before any AVX2/FMA code can execute. AArch64 has no equivalent optional ISA -# floor to probe: NEON/FMA are architectural requirements. +# Host ISA guard deliberately keeps the plain baseline ISA and checks the +# selected x86 profile before optional instructions can execute. AArch64 has +# no equivalent optional ISA floor to probe: NEON/FMA are architectural +# requirements. if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") add_library(mkw_cpu_baseline OBJECT "${MKW_CPU_BASELINE_SOURCE}") target_compile_features(mkw_cpu_baseline PRIVATE cxx_std_17) set_target_properties(mkw_cpu_baseline PROPERTIES UNITY_BUILD OFF) target_compile_options(mkw_cpu_baseline PRIVATE -w) + if(MKW_X86_CPU_PROFILE STREQUAL "v2") + target_compile_definitions(mkw_cpu_baseline PRIVATE MKW_X86_CPU_PROFILE_V2=1) + endif() endif() if(NOT MKW_BASE_COMMON_SHARDS) @@ -206,6 +209,8 @@ function(mkw_configure_product target) if(MKW_PLATFORM_MACOS) target_link_libraries(${target} PRIVATE "${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}") + target_link_options(${target} PRIVATE + "LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor") endif() if(EXISTS "${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake") include("${MKW_AURORA_DIR}/cmake/AuroraCopyRuntimeDLLs.cmake") @@ -226,7 +231,7 @@ function(mkw_configure_product target) dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp) set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE) - elseif(MKW_PLATFORM_LINUX) + elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64) # mkw_runtime_common is an OBJECT library: WiiCompiled/RetroRewind only pull in its .o # files via $, which does not propagate mkw_runtime_common's own # target_link_libraries (object libraries don't carry usage requirements to a consumer @@ -328,12 +333,12 @@ else() message(STATUS "RetroRewind target disabled (run translate-mod and emit-build-shards)") endif() -# Windows and Linux x86_64 share the x86-64-v3 floor that the CPU baseline -# object above checks. AArch64 builds are compiled locally for the host that -# will run them, so both Linux and Apple Silicon use the compiler's native CPU -# tuning rather than leaving target-specific performance on the table. +# Windows, Linux and Intel macOS x86_64 use the selected profile. AArch64 +# builds are compiled locally for the host that will run them, so both Linux +# and Apple Silicon use the compiler's native CPU tuning rather than leaving +# target-specific performance on the table. if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") - set(MKW_BASELINE_ARCH_FLAG -march=x86-64-v3) + set(MKW_BASELINE_ARCH_FLAG -march=x86-64-${MKW_X86_CPU_PROFILE}) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$") set(MKW_BASELINE_ARCH_FLAG -mcpu=native) else() @@ -346,5 +351,8 @@ set(MKW_ALL_BUILD_TARGETS foreach(target IN LISTS MKW_ALL_BUILD_TARGETS) if(TARGET ${target} AND MKW_BASELINE_ARCH_FLAG) target_compile_options(${target} PRIVATE ${MKW_BASELINE_ARCH_FLAG}) + if(MKW_X86_CPU_PROFILE STREQUAL "v2") + target_compile_definitions(${target} PRIVATE MKW_X86_CPU_PROFILE_V2=1) + endif() endif() endforeach() diff --git a/runtime/include/host_context.h b/runtime/include/host_context.h index 4d7355f7c..a0ac7f522 100644 --- a/runtime/include/host_context.h +++ b/runtime/include/host_context.h @@ -3,11 +3,11 @@ #include // HostContext is the deliberately small boundary between the guest scheduler -// and the host's cooperative-context facility. Windows uses native Fibers and -// Linux uses libco; macOS AArch64 uses the local assembly backend because it -// must preserve Darwin's platform-reserved x18 register, which libco's AArch64 -// backend does not save. Its handles are only valid on the thread that -// initialized the scheduler. +// and the host's cooperative-context facility. Windows uses native Fibers; +// Linux uses libco's host backend; Intel macOS uses its System V x86-64 backend. macOS AArch64 uses +// the local assembly backend because it must preserve Darwin's platform-reserved +// x18 register, which libco's AArch64 backend does not save. Its handles are +// only valid on the thread that initialized the scheduler. namespace HostContext { using Handle = void*; diff --git a/runtime/include/isa/ppc_isa_float.h b/runtime/include/isa/ppc_isa_float.h index 592098abb..e5f6c62ef 100644 --- a/runtime/include/isa/ppc_isa_float.h +++ b/runtime/include/isa/ppc_isa_float.h @@ -607,7 +607,18 @@ inline double PPC_PsMulNoNiInline(double lhs, double rhs) inline PpcPairVec PpcFmaddPairInline(PpcPairVec multiplicand, PpcPairVec multiplier, PpcPairVec addend) { #if defined(__x86_64__) +#if defined(__FMA__) return _mm_fmadd_ps(multiplicand, multiplier, addend); +#else + // x86-64-v2 has SSE4.2 but no FMA. Preserve the one rounding point per + // lane through the scalar C++ FMA rather than decomposing into mul+add. + const double a = PpcM128ToPsInline(multiplicand); + const double c = PpcM128ToPsInline(multiplier); + const double b = PpcM128ToPsInline(addend); + return PpcPsToM128Inline(PpcPackPairedInline( + PpcAccuratePsMaddLaneNoNiInline(PpcGetPs0Inline(a), PpcGetPs0Inline(c), PpcGetPs0Inline(b)), + PpcAccuratePsMaddLaneNoNiInline(PpcGetPs1Inline(a), PpcGetPs1Inline(c), PpcGetPs1Inline(b)))); +#endif #elif defined(__aarch64__) const PpcPairVec result = vfma_f32(addend, multiplicand, multiplier); if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]] @@ -619,7 +630,16 @@ inline PpcPairVec PpcFmaddPairInline(PpcPairVec multiplicand, PpcPairVec multipl inline PpcPairVec PpcFmsubPairInline(PpcPairVec multiplicand, PpcPairVec multiplier, PpcPairVec subtractor) { #if defined(__x86_64__) +#if defined(__FMA__) return _mm_fmsub_ps(multiplicand, multiplier, subtractor); +#else + const double a = PpcM128ToPsInline(multiplicand); + const double c = PpcM128ToPsInline(multiplier); + const double b = PpcM128ToPsInline(subtractor); + return PpcPsToM128Inline(PpcPackPairedInline( + PpcAccuratePsMaddLaneNoNiInline(PpcGetPs0Inline(a), PpcGetPs0Inline(c), PpcGetPs0Inline(b)), + PpcAccuratePsMaddLaneNoNiInline(PpcGetPs1Inline(a), PpcGetPs1Inline(c), PpcGetPs1Inline(b)))); +#endif #elif defined(__aarch64__) const PpcPairVec result = vfma_f32(vneg_f32(subtractor), multiplicand, multiplier); if (PpcPairNanLaneBitsInline(result) != 0) [[unlikely]] diff --git a/runtime/src/host_context.cpp b/runtime/src/host_context.cpp index e23c1c036..502f72c49 100644 --- a/runtime/src/host_context.cpp +++ b/runtime/src/host_context.cpp @@ -14,7 +14,7 @@ extern "C" void mkw_co_switch(void** targetSp, void** sourceSp); extern "C" void* mkw_co_init(void* stackTop, void (*entry)(void*), void* argument); -#elif defined(__linux__) +#elif defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__)) #include #include @@ -158,7 +158,7 @@ void Switch(Handle target) g_current = source; } -#elif defined(__linux__) +#elif defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__)) namespace { struct Context { diff --git a/runtime/src/host_cpu_baseline.cpp b/runtime/src/host_cpu_baseline.cpp index 9a1690c19..50f2933f9 100644 --- a/runtime/src/host_cpu_baseline.cpp +++ b/runtime/src/host_cpu_baseline.cpp @@ -1,14 +1,12 @@ -// Host ISA guard. Every other product target builds with -march=x86-64-v3, so a pre-Haswell -// Intel or pre-Excavator AMD machine would otherwise die on an illegal-instruction fault with no +// Host ISA guard. Product targets build with an explicit x86-64-v2 or x86-64-v3 profile, so an +// unsupported machine would otherwise die on an illegal-instruction fault with no // explanation. This TU alone skips that flag (own CMake object library, excluded from unity // build/PCH) and runs from a priority-101 C initializer, ahead of every C++ dynamic initializer // and thus the first AVX2 code that could execute. Keep it free of anything that could pull in // vectorized code: no iostreams, no std::string, no runtime-wide headers. // -// x86-64-v3 is an x86-specific optional-feature baseline (AVX2/BMI2/FMA and friends are not -// guaranteed present on every x86_64 chip); nothing here applies on AArch64, where ASIMD/NEON is -// mandatory in the base architecture and PublicProducts.cmake never applies an -march=x86-64-v3 -// equivalent flag to begin with. That branch below is a no-op stub, not a port of this check. +// x86-64-v2/v3 are x86-specific optional-feature baselines; nothing here applies on AArch64, +// where ASIMD/NEON is mandatory in the base architecture. #if defined(__x86_64__) @@ -58,27 +56,27 @@ struct CpuFeature { bool isOsXsave; }; -// Everything x86-64-v3 implies, which includes all of x86-64-v2. Spelled out so -// the error message can name the exact instruction sets the machine lacks -// rather than only "AVX2", which is merely the best known member of the set. +// x86-64-v2 requirements. The v3-only extension below retains a useful +// feature-by-feature diagnostic rather than reducing a failed v3 check to AVX2. constexpr CpuFeature kRequiredFeatures[] = { {"SSE3", 1, 0, 2, 0, false}, {"SSSE3", 1, 0, 2, 9, false}, - {"FMA", 1, 0, 2, 12, false}, {"CMPXCHG16B", 1, 0, 2, 13, false}, {"SSE4.1", 1, 0, 2, 19, false}, {"SSE4.2", 1, 0, 2, 20, false}, - {"MOVBE", 1, 0, 2, 22, false}, {"POPCNT", 1, 0, 2, 23, false}, - {"OSXSAVE", 1, 0, 2, 27, true}, - {"AVX", 1, 0, 2, 28, false}, - {"F16C", 1, 0, 2, 29, false}, - {"BMI1", 7, 0, 1, 3, false}, - {"AVX2", 7, 0, 1, 5, false}, - {"BMI2", 7, 0, 1, 8, false}, {"LAHF-SAHF", 0x80000001u, 0, 2, 0, false}, +}; + +#if !defined(MKW_X86_CPU_PROFILE_V2) +constexpr CpuFeature kV3RequiredFeatures[] = { + {"FMA", 1, 0, 2, 12, false}, {"MOVBE", 1, 0, 2, 22, false}, + {"OSXSAVE", 1, 0, 2, 27, true}, {"AVX", 1, 0, 2, 28, false}, + {"F16C", 1, 0, 2, 29, false}, {"BMI1", 7, 0, 1, 3, false}, + {"AVX2", 7, 0, 1, 5, false}, {"BMI2", 7, 0, 1, 8, false}, {"LZCNT", 0x80000001u, 0, 2, 5, false}, }; +#endif // Fixed-capacity text accumulation: no allocation, no exceptions, nothing that // could route through code this file is trying to stay ahead of. @@ -130,6 +128,27 @@ bool CollectMissingBaselineFeatures(TextBuffer& missing) { ok = false; } +#if !defined(MKW_X86_CPU_PROFILE_V2) + for (const CpuFeature& feature : kV3RequiredFeatures) { + const bool leafAvailable = (feature.leaf & 0x80000000u) != 0 + ? feature.leaf <= maxExtended + : feature.leaf <= maxBasic; + bool present = false; + if (leafAvailable) { + unsigned regs[4] = {0, 0, 0, 0}; + HostCpuId(feature.leaf, feature.subleaf, regs); + present = (regs[feature.reg] & (1u << feature.bit)) != 0; + } + if (present) { + haveOsXsave = haveOsXsave || feature.isOsXsave; + continue; + } + if (!ok) missing.Append(", "); + missing.Append(feature.name); + ok = false; + } +#endif + // CPUID reporting AVX is not sufficient: the OS also has to have enabled // XMM and YMM state saving or every VEX-encoded instruction faults. This is // the same guard a compiler's own runtime feature dispatch applies. @@ -175,13 +194,17 @@ void WriteStdErrEarly(const char* text) { [[noreturn]] void ReportUnsupportedCpu(const char* missing) { TextBuffer message; - message.Append( - "This build needs a processor that supports AVX2 and the rest of the " - "x86-64-v3 instruction set.\n\nMissing on this machine: "); +#if defined(MKW_X86_CPU_PROFILE_V2) + message.Append("This build needs a processor that supports the x86-64-v2 instruction set.\n\nMissing on this machine: "); +#else + message.Append("This build needs a processor that supports the x86-64-v3 instruction set.\n\nMissing on this machine: "); +#endif message.Append(missing); - message.Append( - "\n\nx86-64-v3 covers Intel Core processors from Haswell (4th " - "generation, 2013) onward and AMD processors from Excavator (2015) onward."); +#if defined(MKW_X86_CPU_PROFILE_V2) + message.Append("\n\nx86-64-v2 covers Intel Core processors from Nehalem (2008) onward and AMD processors from Jaguar (2013) onward."); +#else + message.Append("\n\nx86-64-v3 covers Intel Core processors from Haswell (4th generation, 2013) onward and AMD processors from Excavator (2015) onward."); +#endif // The tag matches RT_TAG_RUNTIME in runtime_log.h. It is spelled out here // because this translation unit must not include runtime-wide headers (see diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index edac415e0..ad55c5540 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -44,10 +44,15 @@ #include #if defined(__x86_64__) // Only the x86 POSIX fault path inspects ucontext_t to recover the page-fault -// write bit. macOS deprecates ucontext and requires _XOPEN_SOURCE just to -// include the header, while the arm64 handler does not use it at all. +// write bit. macOS exposes the signal-handler context through sys/ucontext.h; +// avoid ucontext.h itself because its deprecated user-context APIs require +// _XOPEN_SOURCE. The arm64 handler does not inspect a host context at all. +#if defined(__APPLE__) +#include +#else #include #endif +#endif #include #endif @@ -1128,7 +1133,11 @@ void PosixMemoryFaultHandler(int sig, siginfo_t* info, void* ucontextVoid) { // error code x86 pushes on a page fault records whether it was a write. if (ucontextVoid != nullptr) { auto* uc = static_cast(ucontextVoid); +#if defined(__APPLE__) + isWrite = uc->uc_mcontext != nullptr && (uc->uc_mcontext->__es.__err & 0x2) != 0; +#else isWrite = (uc->uc_mcontext.gregs[REG_ERR] & 0x2) != 0; +#endif } #endif diff --git a/runtime/src/music_attenuation.cpp b/runtime/src/music_attenuation.cpp index 3ce2b82c9..3d0f123d9 100644 --- a/runtime/src/music_attenuation.cpp +++ b/runtime/src/music_attenuation.cpp @@ -4,9 +4,9 @@ #include #include -#include #include #include +#include #include #include #include @@ -52,6 +52,20 @@ std::array g_requestedSoundPlayerVolumes{}; std::array g_lastAppliedSoundPlayerVolumes{}; std::array g_haveSoundPlayerVolumes{}; +uint32_t FloatBits(float value) noexcept { + static_assert(sizeof(float) == sizeof(uint32_t)); + uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + return bits; +} + +float BitsFloat(uint32_t bits) noexcept { + static_assert(sizeof(float) == sizeof(uint32_t)); + float value = 0.0f; + std::memcpy(&value, &bits, sizeof(value)); + return value; +} + float ClampSoundPlayerVolume(float volume) noexcept { // Match nw4r::snd::SoundPlayer::SetVolume at 0x800A35E0 exactly, // including its NaN behavior (unordered compares select the upper bound). @@ -63,7 +77,7 @@ float ClampSoundPlayerVolume(float volume) noexcept { bool WriteGuestFloat(uint32_t address, float value) noexcept { try { - Memory::Write32(address, std::bit_cast(value)); + Memory::Write32(address, FloatBits(value)); return true; } catch (const Memory::AccessViolation&) { return false; @@ -75,7 +89,7 @@ bool ReadGuestFloat(uint32_t address, float& value) noexcept { if (!Memory::TryRead32(address, bits)) { return false; } - value = std::bit_cast(bits); + value = BitsFloat(bits); return true; } @@ -558,7 +572,7 @@ void SetSoundPlayerVolume(uint32_t soundPlayer, float requestedVolume) { } // Preserve the original function's access semantics. An invalid player is // a guest bug and must not be converted into a silent successful call. - Memory::Write32(soundPlayer + kSoundPlayerVolumeOffset, std::bit_cast(applied)); + Memory::Write32(soundPlayer + kSoundPlayerVolumeOffset, FloatBits(applied)); } } // namespace MusicAttenuation diff --git a/runtime/src/platform/host_platform.cpp b/runtime/src/platform/host_platform.cpp index c0caa95a5..2a8c16240 100644 --- a/runtime/src/platform/host_platform.cpp +++ b/runtime/src/platform/host_platform.cpp @@ -1,6 +1,7 @@ #include "platform/host_platform.h" #include +#include #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN @@ -46,6 +47,18 @@ std::optional ExecutableDirectory() noexcept { std::error_code ec; const auto resolved = std::filesystem::weakly_canonical(path, ec); return (ec ? std::filesystem::path(path) : resolved).parent_path(); +#elif defined(__linux__) + std::vector buffer(256); + for (;;) { + const auto length = ::readlink("/proc/self/exe", buffer.data(), buffer.size()); + if (length < 0) { + return std::nullopt; + } + if (static_cast(length) < buffer.size()) { + return std::filesystem::path(std::string(buffer.data(), length)).parent_path(); + } + buffer.resize(buffer.size() * 2); + } #else return std::nullopt; #endif diff --git a/runtime/tests/ppc_pair_fma_tests.cpp b/runtime/tests/ppc_pair_fma_tests.cpp new file mode 100644 index 000000000..7d7720256 --- /dev/null +++ b/runtime/tests/ppc_pair_fma_tests.cpp @@ -0,0 +1,58 @@ +#include "isa/ppc_isa_float.h" + +#include +#include +#include + +namespace { + +bool SameBits(double lhs, double rhs) +{ + return PpcBitCastToU64Inline(lhs) == PpcBitCastToU64Inline(rhs); +} + +bool CheckPair(double actual, float a0, float a1, float c0, float c1, float b0, float b1, + bool subtract, const char* operation) +{ + const float expected0 = subtract ? std::fma(a0, c0, -b0) : std::fma(a0, c0, b0); + const float expected1 = subtract ? std::fma(a1, c1, -b1) : std::fma(a1, c1, b1); + const double expected = PpcPackPairedInline(expected0, expected1); + if (SameBits(actual, expected)) { + return true; + } + std::fprintf(stderr, "%s produced the wrong paired lanes\n", operation); + return false; +} + +bool CheckNegatedPair(double actual, float a0, float a1, float c0, float c1, float b0, float b1, + bool subtract, const char* operation) +{ + const float expected0 = -std::fma(a0, c0, subtract ? -b0 : b0); + const float expected1 = -std::fma(a1, c1, subtract ? -b1 : b1); + const double expected = PpcPackPairedInline(expected0, expected1); + if (SameBits(actual, expected)) { + return true; + } + std::fprintf(stderr, "%s produced the wrong paired lanes\n", operation); + return false; +} + +} // namespace + +int main() +{ + const double a = PpcPackPairedInline(1.000000119f, -123.25f); + const double c = PpcPackPairedInline(33554431.0f, 0.0625f); + const double b = PpcPackPairedInline(-33554430.0f, 7.75f); + + bool ok = true; + ok &= CheckPair(PPC_PsMaddNoNiInline(a, c, b), 1.000000119f, -123.25f, + 33554431.0f, 0.0625f, -33554430.0f, 7.75f, false, "ps_madd"); + ok &= CheckPair(PPC_PsMsubNoNiInline(a, c, b), 1.000000119f, -123.25f, + 33554431.0f, 0.0625f, -33554430.0f, 7.75f, true, "ps_msub"); + ok &= CheckNegatedPair(PPC_PsNmaddInline(a, c, b), 1.000000119f, -123.25f, + 33554431.0f, 0.0625f, -33554430.0f, 7.75f, false, "ps_nmadd"); + ok &= CheckNegatedPair(PPC_PsNmsubNoNiInline(a, c, b), 1.000000119f, -123.25f, + 33554431.0f, 0.0625f, -33554430.0f, 7.75f, true, "ps_nmsub"); + return ok ? 0 : 1; +} diff --git a/runtime/tests/test_expr.cpp b/runtime/tests/test_expr.cpp index c770a3c32..8c593a4ff 100644 --- a/runtime/tests/test_expr.cpp +++ b/runtime/tests/test_expr.cpp @@ -97,15 +97,29 @@ int main() { Check(!Pressed(pulse), "pulse expires"); // The timing-window idiom seen in shared Dolphin configs. - auto window = Compile("!pulse(`W`, 0.05) & pulse(`W`, 0.15)"); + auto window = Compile("!pulse(`W`, 0.05) & pulse(`W`, 0.35)"); g_inputs["W"] = 0.0; window.Evaluate(Source()); g_inputs["W"] = 1.0; Check(!Pressed(window), "window closed before its start"); - Sleep(90); - Check(Pressed(window), "window open between the two pulses"); - Sleep(90); - Check(!Pressed(window), "window closed after its end"); + bool windowOpened = false; + for (int i = 0; i < 40; ++i) { + Sleep(10); + if (Pressed(window)) { + windowOpened = true; + break; + } + } + Check(windowOpened, "window open between the two pulses"); + bool windowClosed = false; + for (int i = 0; i < 50; ++i) { + Sleep(10); + if (!Pressed(window)) { + windowClosed = true; + break; + } + } + Check(windowClosed, "window closed after its end"); // timer ramps 0..1 and wraps, so a threshold turns it into a square wave. auto timer = Compile("`X` & timer(0.1)"); diff --git a/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs b/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs index 5dabbb112..1af76dd4b 100644 --- a/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs +++ b/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -380,7 +380,7 @@ string EmitFunctionBody(RegisterResidency? residency, int bufferBaseLength, bool instructionContinuationLabels.TryGetValue(trace.Address, out var continuationLabel)) { RecordEmittedLocalLabel(continuationLabel); - body.AppendLine($"{continuationLabel}:"); + body.AppendLine($"{continuationLabel}: ;"); } var localFallthroughLr = TryGetLocalFallthroughLr(block.Instructions, i, nonReturningCallTargets, lrContinuationCallTargets); // State-free bodies are cloned after register caching and lose their diff --git a/translator/tests/Translator.Tests/EmittedOutputShapeTests.cs b/translator/tests/Translator.Tests/EmittedOutputShapeTests.cs index 6fbb0c286..700045bb6 100644 --- a/translator/tests/Translator.Tests/EmittedOutputShapeTests.cs +++ b/translator/tests/Translator.Tests/EmittedOutputShapeTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Translator.Core.Analysis.Representation; using Translator.Core.Analysis.Ssa; using Translator.Core.CodeGen; @@ -153,4 +153,34 @@ public void FloatFlatHelpersPreserveTheWidthMapping() Assert.Contains("f3.d = MemoryInline::FlatReadFloat32((r4 + 16));", code, StringComparison.Ordinal); Assert.Contains("f4.d = MemoryInline::FlatReadFloat64((r4 + 24));", code, StringComparison.Ordinal); } + + [Fact] + public void ContinuationLabelAtBlockEndEmitsValidCxx17Statement() + { + var function = new IrFunction("continuation_at_block_end", "0x800E7798", new[] + { + new IrBasicBlock("0x800E7798", new IrInstruction[] + { + new IrCall(string.Empty, "0x8179B000", System.Array.Empty()), + new IrTracePpc(0x800E77A0u, "nop", "0x60000000"), + new IrJump("0x800E77A4") + }), + new IrBasicBlock("0x800E77A4", new IrInstruction[] + { + new IrReturn(null) + }) + }); + + var types = new RepresentationEnvironment(new Dictionary()); + var code = new CxxLinearCodeGenerator().Emit( + 0x800E7798, + new SsaTransformer().Convert(function), + new FunctionAbiClassification(function.Name, ValueRepresentation.Void), + types, + lrContinuationCallTargets: new HashSet { 0x8179B000u }); + + // In C++17, a label before a closing brace is invalid without an intervening statement. + Assert.Contains("loc_800E77A0: ;", code, StringComparison.Ordinal); + Assert.DoesNotContain("loc_800E77A0:\n}", code.Replace("\r\n", "\n"), StringComparison.Ordinal); + } } From a2ecc0fae9df17d35e42e0eb5710d1f17341c24f Mon Sep 17 00:00:00 2001 From: DarthM Date: Thu, 24 Sep 2026 16:07:36 -0400 Subject: [PATCH 2/5] fix review findings in network and translator --- runtime/src/hle/net/network_internal.h | 1 - runtime/src/hle/net/network_ssl.cpp | 1 - .../CodeGen/CxxLinearCodeGenerator.cs | 32 ++++++++++++++++--- .../src/Translator.Core/Lifting/PpcLifter.cs | 15 +-------- .../PpcLifterAdditionalCoverageTests.cs | 10 ++---- .../SharedLrContinuationCodeGenTests.cs | 27 ++++++++++++++++ 6 files changed, 59 insertions(+), 27 deletions(-) diff --git a/runtime/src/hle/net/network_internal.h b/runtime/src/hle/net/network_internal.h index b4977cb2d..97c06088d 100644 --- a/runtime/src/hle/net/network_internal.h +++ b/runtime/src/hle/net/network_internal.h @@ -242,7 +242,6 @@ void WritePollResults(uint32_t outAddress, const std::vector& descriptors); // network_socket.cpp -int32_t DeleteWiiSocket(uint32_t fd); void CleanupAllWiiSockets(); sockaddr_in ReadWiiSockAddr(uint32_t addr); int32_t HandleIpTopIoctl(uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, diff --git a/runtime/src/hle/net/network_ssl.cpp b/runtime/src/hle/net/network_ssl.cpp index 668365d03..742b2c3ab 100644 --- a/runtime/src/hle/net/network_ssl.cpp +++ b/runtime/src/hle/net/network_ssl.cpp @@ -795,7 +795,6 @@ static int32_t SslWrite(SslSession& ssl, const uint8_t* data, uint32_t size) { } if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { if (std::chrono::steady_clock::now() >= writeDeadline) { - DeleteWiiSocket(ssl.socketFd); return SSL_ERR_FAILED; } continue; diff --git a/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs b/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs index 1af76dd4b..e34b2a9d0 100644 --- a/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs +++ b/translator/src/Translator.Core/CodeGen/CxxLinearCodeGenerator.cs @@ -225,13 +225,37 @@ public CxxEmissionResult EmitWithFacts(uint entryPoint, var labelNames = func.Blocks.ToDictionary(b => b.Label, b => SanitizeLabel(b.Label), StringComparer.OrdinalIgnoreCase); var instructionContinuationLabels = new Dictionary(); - var continuationCallCount = func.Blocks + var continuationCallCount = func.Blocks.Sum(block => + { + var term = block.Instructions.LastOrDefault(); + var emittedInstructionCount = term is IrBranch or IrJump or IrReturn or IrJumpTable or IrUndefined + ? Math.Max(0, block.Instructions.Count - 1) + : block.Instructions.Count; + var suppressed = suppressedInstructionMasks.TryGetValue(block.Label, out var mask) ? mask : null; + return Enumerable.Range(0, emittedInstructionCount).Count(index => + { + if ((suppressed is not null && index < suppressed.Length && suppressed[index]) || + block.Instructions[index] is not IrCall call || + !TryParseAddress(call.Target, out var target) || + TryGetInlineGuestThunkSpec(target, out _)) + { + return false; + } + + return nonReturningCallTargets.Contains(target) || + (lrContinuationCallTargets.Contains(target) && + TryGetLocalFallthroughLr(block.Instructions, index, nonReturningCallTargets, lrContinuationCallTargets).HasValue); + }); + }); + // Labels remain available for every recognized continuation target. + // Sharing the terminal dispatcher is narrower: it only applies when + // more than one emitted call can actually jump there. + var needsInstructionContinuationLabels = func.Blocks .SelectMany(static block => block.Instructions) .OfType() - .Count(call => + .Any(call => TryParseAddress(call.Target, out var target) && (nonReturningCallTargets.Contains(target) || lrContinuationCallTargets.Contains(target))); - var needsInstructionContinuationLabels = continuationCallCount > 0; var shareLrContinuationDispatch = continuationCallCount > 1; if (needsInstructionContinuationLabels) { @@ -500,7 +524,7 @@ string EmitFunctionBody(RegisterResidency? residency, int bufferBaseLength, bool // Every call site has already reloaded the callee's state. // Keep the complete local target set, but emit it only once. body.AppendLine(" return;"); - body.AppendLine("[[maybe_unused]] lr_continuation_dispatch:"); + body.AppendLine("lr_continuation_dispatch:"); EmitLocalLrContinuationDispatch(body, " ", labelNames); body.AppendLine(" if (TranslatedFunctionRegistry::FindByAddressPtr(ctx->lr) != nullptr) {"); body.AppendLine(" InvokeIndirectCpu(ctx->lr, ctx);"); diff --git a/translator/src/Translator.Core/Lifting/PpcLifter.cs b/translator/src/Translator.Core/Lifting/PpcLifter.cs index 4d41475b3..f3744bd7a 100644 --- a/translator/src/Translator.Core/Lifting/PpcLifter.cs +++ b/translator/src/Translator.Core/Lifting/PpcLifter.cs @@ -1943,20 +1943,7 @@ private static IReadOnlyList LiftInstruction( }; case "bltl": - { - var crField = "cr0"; - if (ops.Count > 0 && ops[0] is PpcConditionRegisterOperand crOp) - { - crField = NormalizeRegister(crOp.Name); - } - - return new IrInstruction[] - { - new IrAssign("lr", IrValue.Imm((int)ins.EndAddress)), - new IrBranch("blt", TargetLabel(ins, validAddresses, preferFallthrough: false), - $"0x{ins.EndAddress:X8}", crField) - }; - } + goto case "bcl"; case "bcl": { diff --git a/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs b/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs index 963708420..6548b8992 100644 --- a/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs +++ b/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs @@ -21,13 +21,9 @@ public void LinkedConditionalBranchSetsLrBeforeEitherPath() var ir = Assert.Single(new PpcLifter().Lift(new[] { branch })).Ir; Assert.Equal("bltl", branch.Mnemonic); - var lr = Assert.IsType(ir[0]); - Assert.Equal("lr", lr.Destination); - Assert.Equal(unchecked((int)0x80004398u), lr.Value.Constant); - - var decision = Assert.IsType(ir[1]); - Assert.Equal("blt", decision.Condition); - Assert.Equal("0x800043BC", decision.TrueLabel); + var decision = Assert.IsType(Assert.Single(ir)); + Assert.Equal("raw", decision.Condition); + Assert.Equal("link_branch_80004398_800043BC", decision.TrueLabel); Assert.Equal("0x80004398", decision.FalseLabel); } diff --git a/translator/tests/Translator.Tests/SharedLrContinuationCodeGenTests.cs b/translator/tests/Translator.Tests/SharedLrContinuationCodeGenTests.cs index 7204423c9..da142b3a2 100644 --- a/translator/tests/Translator.Tests/SharedLrContinuationCodeGenTests.cs +++ b/translator/tests/Translator.Tests/SharedLrContinuationCodeGenTests.cs @@ -9,6 +9,33 @@ namespace Translator.Tests; public class SharedLrContinuationCodeGenTests { + [Fact] + public void OnlyLocalLinkRegisterContinuationsCountTowardSharedDispatch() + { + var function = new IrFunction("single_lr_continuation", "0x80001000", new[] + { + new IrBasicBlock("0x80001000", new IrInstruction[] + { + new IrCall(string.Empty, "0x81800000", Array.Empty()), + new IrAssign("lr", IrValue.Imm(unchecked((int)0x80001004u))), + new IrCall(string.Empty, "0x81800000", Array.Empty()), + new IrReturn(null) + }) + }); + var types = new RepresentationEnvironment(new Dictionary + { + ["lr"] = ValueRepresentation.UInt32 + }); + + var code = new CxxLinearCodeGenerator().Emit(0x80001000, + new SsaTransformer().Convert(function), + new FunctionAbiClassification("single_lr_continuation", ValueRepresentation.Void), types, + lrContinuationCallTargets: new HashSet { 0x81800000u }); + + Assert.DoesNotContain("lr_continuation_dispatch:", code); + Assert.DoesNotContain("goto lr_continuation_dispatch;", code); + } + [Theory] [InlineData(2)] [InlineData(20)] From 3c7a44902ba7eeed86a6e791efaa946c53cbd780 Mon Sep 17 00:00:00 2001 From: DarthM Date: Thu, 24 Sep 2026 16:43:09 -0400 Subject: [PATCH 3/5] build: include mbedtls in macos compile audit --- runtime/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 5e1212b90..aa56b8f6d 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -386,7 +386,7 @@ else() target_compile_definitions(mkw_macos_native_compile PRIVATE SDL_MAIN_HANDLED TARGET_PC) target_link_libraries(mkw_macos_native_compile PRIVATE aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx - mkw::pugixml mkw::toml11 mkw::cryptopp) + mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls) if(MKW_PLATFORM_MACOS_X86_64) target_link_libraries(mkw_macos_native_compile PRIVATE mkw::libco) target_compile_options(mkw_macos_native_compile PRIVATE From 4bebb741256d39258bec3124c0ef111d6b35b15b Mon Sep 17 00:00:00 2001 From: DarthM Date: Thu, 24 Sep 2026 19:18:32 -0400 Subject: [PATCH 4/5] review fixes --- .github/workflows/package.yml | 7 +++++-- Launcher/macos/publish-app.command | 20 +++++++++++-------- Launcher/macos/setup.command | 1 + README.md | 6 +++--- .../src/Translator.Core/Lifting/PpcLifter.cs | 3 +-- .../PpcLifterAdditionalCoverageTests.cs | 12 ++++++++++- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 63f40b535..d6c340290 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -190,8 +190,11 @@ jobs: shell: bash run: | pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg - ! pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \ - grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)' + if pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \ + grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)'; then + echo "::error::Unexpected excluded paths found in package payload" >&2 + exit 1 + fi expanded="$RUNNER_TEMP/wiicompiled-setup-expanded" pkgutil --expand-full Launcher/dist/WiiCompiled-Setup.pkg "$expanded" resources="$expanded/Payload/Applications/WiiCompiled Setup.app/Contents/Resources" diff --git a/Launcher/macos/publish-app.command b/Launcher/macos/publish-app.command index 36b2c3ea1..e043de671 100755 --- a/Launcher/macos/publish-app.command +++ b/Launcher/macos/publish-app.command @@ -79,7 +79,7 @@ dependency_path() { while IFS= read -r rpath; do case "$rpath" in @loader_path/*) rpath="$(dirname "$current")/${rpath#@loader_path/}" ;; - @executable_path/*) rpath="$macos/${rpath#@executable_path/}" ;; + @executable_path/*) rpath="$build_dir/${rpath#@executable_path/}" ;; esac candidate="$rpath/$name" [[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; } @@ -95,19 +95,23 @@ dependency_path() { # Build a closure of non-system dylibs. System libraries remain system # references, while every resolved dependency is copied beside the executable. -queue=("$macos/$product") -while ((${#queue[@]})); do - current=${queue[0]} - queue=("${queue[@]:1}") +queue_bundle=("$macos/$product") +queue_source=("$build_dir/$product") +while ((${#queue_bundle[@]})); do + current_bundle=${queue_bundle[0]} + current_source=${queue_source[0]} + queue_bundle=("${queue_bundle[@]:1}") + queue_source=("${queue_source[@]:1}") while IFS= read -r dependency; do - dependency_path=$(dependency_path "$current" "$dependency") || continue + dependency_path=$(dependency_path "$current_source" "$dependency") || continue name=$(basename "$dependency") if [[ ! -f "$frameworks/$name" ]]; then ditto "$dependency_path" "$frameworks/$name" install_name_tool -id "@rpath/$name" "$frameworks/$name" - queue+=("$frameworks/$name") + queue_bundle+=("$frameworks/$name") + queue_source+=("$dependency_path") fi - done < <(otool -L "$current" | tail -n +2 | awk '{print $1}') + done < <(otool -L "$current_bundle" | tail -n +2 | awk '{print $1}') done while IFS= read -r binary; do while IFS= read -r old; do diff --git a/Launcher/macos/setup.command b/Launcher/macos/setup.command index 315c8211a..85cce1b2b 100755 --- a/Launcher/macos/setup.command +++ b/Launcher/macos/setup.command @@ -87,6 +87,7 @@ if (( needs_workspace_refresh )); then # Refresh only packaged source inputs. Assets and the staged Retro # Rewind package belong to the user and stay in place. for source in aurora-main projects runtime translator Launcher; do + rm -rf "$workspace/$source" /usr/bin/ditto "$workspace_source/$source" "$workspace/$source" done /usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version" diff --git a/README.md b/README.md index 745b598ee..22a4b5fd3 100644 --- a/README.md +++ b/README.md @@ -120,9 +120,9 @@ launching. The backend itself is deliberately command-line only, Wheel Wizard is ### macOS -Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. It requires an -Apple Silicon Mac because its bundled nodtool and Translator.Cli executables are arm64. It installs -**WiiCompiled Setup** in Applications; open that app, choose your clean PAL `RMCP01` disc image, +Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. The package includes +native tools for Apple Silicon and Intel x86_64-v3 Macs, selecting the matching set automatically. It +installs **WiiCompiled Setup** in Applications; open that app, choose your clean PAL `RMCP01` disc image, and select either the base game or Retro Rewind. For Retro Rewind, choose the `RetroRewind6` folder or its parent folder. diff --git a/translator/src/Translator.Core/Lifting/PpcLifter.cs b/translator/src/Translator.Core/Lifting/PpcLifter.cs index f3744bd7a..c7ffe5712 100644 --- a/translator/src/Translator.Core/Lifting/PpcLifter.cs +++ b/translator/src/Translator.Core/Lifting/PpcLifter.cs @@ -1943,8 +1943,6 @@ private static IReadOnlyList LiftInstruction( }; case "bltl": - goto case "bcl"; - case "bcl": { var rawInstr = ReadRawInstruction(ins); @@ -1958,6 +1956,7 @@ private static IReadOnlyList LiftInstruction( { instructions.Add(new IrBinary("ctr", IrValue.Register("ctr"), IrValue.Imm(-1), "add")); } + instructions.Add(new IrAssign("lr", IrValue.Imm((int)ins.EndAddress))); instructions.Add(new IrBranch("raw", linkedTarget, fallthrough, BuildBoConditionExpression(bo, bi, allowCtr: true))); return instructions; } diff --git a/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs b/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs index 6548b8992..32162d637 100644 --- a/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs +++ b/translator/tests/Translator.Tests/PpcLifterAdditionalCoverageTests.cs @@ -21,10 +21,20 @@ public void LinkedConditionalBranchSetsLrBeforeEitherPath() var ir = Assert.Single(new PpcLifter().Lift(new[] { branch })).Ir; Assert.Equal("bltl", branch.Mnemonic); - var decision = Assert.IsType(Assert.Single(ir)); + Assert.Equal(2, ir.Count); + + var lrAssign = Assert.IsType(ir[0]); + Assert.Equal("lr", lrAssign.Destination); + Assert.Equal(unchecked((int)0x80004398u), lrAssign.Value.Constant); + Assert.Equal(unchecked((int)branch.EndAddress), lrAssign.Value.Constant); + + var decision = Assert.IsType(ir[1]); Assert.Equal("raw", decision.Condition); Assert.Equal("link_branch_80004398_800043BC", decision.TrueLabel); Assert.Equal("0x80004398", decision.FalseLabel); + + Assert.Equal($"link_branch_{branch.EndAddress:X8}_{branch.BranchTargets.First():X8}", decision.TrueLabel); + Assert.Equal($"0x{branch.EndAddress:X8}", decision.FalseLabel); } [Fact] From 9c2ecb08372e614ba2a21ee99d46400910ed8640 Mon Sep 17 00:00:00 2001 From: DarthM Date: Thu, 1 Oct 2026 19:41:16 -0400 Subject: [PATCH 5/5] cmake: restore macOS deployment target floor before project() --- runtime/CMakeLists.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index e89cd7b57..d94974f49 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -1,4 +1,13 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.25) + +# Dawn's pinned macOS artifacts target 12.0. Set the same floor before project() +# initializes the Apple toolchain so direct developer CMake invocations cannot +# accidentally inherit the running SDK's deployment version. This cache entry +# is harmless on non-Apple platforms and remains overridable by a caller. +if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) + set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING + "Minimum macOS version supported by WiiCompiled" FORCE) +endif() project(mkw_recompiled) if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|AppleClang)$" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8)