From bdd2875642d2a6cf1b3e2bcaeb2e80ab191a9b30 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Sat, 11 Oct 2025 13:30:39 +1000 Subject: [PATCH] feat: Add Profile-Guided Optimization (PGO) build support Implements two-stage PGO build system with CMake integration and automated build scripts for Windows, Linux, and macOS. Supports MSVC, GCC, and Clang compilers. PGO enables runtime profiling-based optimizations for improved emulator performance. Includes helper scripts to streamline the build workflow and resolve platform-specific issues. Signed-off-by: Zephyron --- .gitignore | 9 ++ CMakeLists.txt | 21 ++++ CMakeModules/PGO.cmake | 274 +++++++++++++++++++++++++++++++++++++++++ fix-pgo-dll.ps1 | 174 ++++++++++++++++++++++++++ pgo-build.ps1 | 209 +++++++++++++++++++++++++++++++ pgo-build.sh | 203 ++++++++++++++++++++++++++++++ 6 files changed, 890 insertions(+) create mode 100644 CMakeModules/PGO.cmake create mode 100644 fix-pgo-dll.ps1 create mode 100644 pgo-build.ps1 create mode 100644 pgo-build.sh diff --git a/.gitignore b/.gitignore index fbadb208b..345a9796a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,12 @@ CMakeSettings.json # Windows global filetypes Thumbs.db +# PGO (Profile-Guided Optimization) files +pgo-profiles-backup/ +*.pgd +*.pgc +*.profraw +*.profdata +*.gcda +*.gcno + diff --git a/CMakeLists.txt b/CMakeLists.txt index 08e697bc2..c6f8a78a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul include(DownloadExternals) include(CMakeDependentOption) include(CTest) +include(PGO) # Disable Warnings as Errors for MSVC if (MSVC) @@ -78,6 +79,10 @@ option(CITRON_CHECK_SUBMODULES "Check if submodules are present" ON) option(CITRON_ENABLE_LTO "Enable link-time optimization" OFF) +option(CITRON_ENABLE_PGO_GENERATE "Build with PGO instrumentation to generate profile data (Stage 1)" OFF) +option(CITRON_ENABLE_PGO_USE "Build using PGO profile data for optimization (Stage 2)" OFF) +set(CITRON_PGO_PROFILE_DIR "${CMAKE_BINARY_DIR}/pgo-profiles" CACHE PATH "Directory to store PGO profile data") + option(CITRON_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF) option(CITRON_ENABLE_PORTABLE "Allow citron to enable portable mode if a user folder is found in the CWD" ON) @@ -792,6 +797,22 @@ if(DEFINED HAS_BOOST_PROCESS_DEFINITION) target_compile_definitions(core PRIVATE ${HAS_BOOST_PROCESS_DEFINITION}) endif() +# Apply PGO configuration to main targets +if(CITRON_ENABLE_PGO_GENERATE OR CITRON_ENABLE_PGO_USE) + if(TARGET citron) + citron_configure_pgo(citron) + endif() + if(TARGET citron-cmd) + citron_configure_pgo(citron-cmd) + endif() + if(TARGET citron-room) + citron_configure_pgo(citron-room) + endif() + + # Print PGO instructions + citron_print_pgo_instructions() +endif() + # Set citron project or citron-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not if(ENABLE_QT) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT citron) diff --git a/CMakeModules/PGO.cmake b/CMakeModules/PGO.cmake new file mode 100644 index 000000000..e736c4b64 --- /dev/null +++ b/CMakeModules/PGO.cmake @@ -0,0 +1,274 @@ +# SPDX-FileCopyrightText: 2025 citron Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# Profile-Guided Optimization (PGO) Support +# +# This module provides functions to enable Profile-Guided Optimization (PGO) for Citron. +# PGO is a two-stage compiler optimization technique: +# 1. GENERATE stage: Build with instrumentation to collect profiling data during runtime +# 2. USE stage: Rebuild using the collected profiling data to optimize hot paths +# +# Usage: +# set(CITRON_ENABLE_PGO_GENERATE ON) # First build: generate profiling data +# set(CITRON_ENABLE_PGO_USE ON) # Second build: use profiling data +# set(CITRON_PGO_PROFILE_DIR "${CMAKE_BINARY_DIR}/pgo-profiles") # Optional: custom profile directory + +# PGO profile directory - where .pgd/.profraw/.profdata files are stored +if(NOT DEFINED CITRON_PGO_PROFILE_DIR) + set(CITRON_PGO_PROFILE_DIR "${CMAKE_BINARY_DIR}/pgo-profiles" CACHE PATH "Directory to store PGO profile data") +endif() + +# Create the profile directory if it doesn't exist +file(MAKE_DIRECTORY "${CITRON_PGO_PROFILE_DIR}") + +# Function to copy MSVC PGO runtime DLLs +function(citron_copy_pgo_runtime_dlls target_name) + if(NOT MSVC) + return() + endif() + + # Find the Visual Studio installation directory + get_filename_component(MSVC_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + get_filename_component(MSVC_DIR "${MSVC_DIR}" DIRECTORY) + get_filename_component(MSVC_DIR "${MSVC_DIR}" DIRECTORY) + + # Common locations for PGO runtime DLLs + set(PGO_DLL_PATHS + "${MSVC_DIR}/VC/Redist/MSVC/*/x64/Microsoft.VC*.CRT/pgort140.dll" + "${MSVC_DIR}/VC/Redist/MSVC/*/x86/Microsoft.VC*.CRT/pgort140.dll" + "${MSVC_DIR}/VC/Tools/MSVC/*/bin/Hostx64/x64/pgort140.dll" + "${MSVC_DIR}/VC/Tools/MSVC/*/bin/Hostx64/x86/pgort140.dll" + "${MSVC_DIR}/VC/Tools/MSVC/*/bin/Hostx86/x64/pgort140.dll" + "${MSVC_DIR}/VC/Tools/MSVC/*/bin/Hostx86/x86/pgort140.dll" + ) + + # Find the PGO runtime DLL + set(PGO_DLL_FOUND FALSE) + foreach(dll_pattern ${PGO_DLL_PATHS}) + file(GLOB PGO_DLL_CANDIDATES ${dll_pattern}) + if(PGO_DLL_CANDIDATES) + list(GET PGO_DLL_CANDIDATES 0 PGO_DLL_PATH) + set(PGO_DLL_FOUND TRUE) + break() + endif() + endforeach() + + if(PGO_DLL_FOUND) + message(STATUS " [${target_name}] Found PGO runtime DLL: ${PGO_DLL_PATH}") + + # Get the target's output directory + get_target_property(TARGET_OUTPUT_DIR ${target_name} RUNTIME_OUTPUT_DIRECTORY) + if(NOT TARGET_OUTPUT_DIR) + get_target_property(TARGET_OUTPUT_DIR ${target_name} RUNTIME_OUTPUT_DIRECTORY_DEBUG) + endif() + if(NOT TARGET_OUTPUT_DIR) + set(TARGET_OUTPUT_DIR "${CMAKE_BINARY_DIR}/bin") + endif() + + # Copy the DLL to the output directory + add_custom_command(TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${PGO_DLL_PATH}" + "${TARGET_OUTPUT_DIR}/" + COMMENT "Copying PGO runtime DLL for ${target_name}" + ) + else() + message(WARNING "PGO runtime DLL (pgort140.dll) not found. The instrumented build may not run properly.") + message(STATUS " Please ensure Visual Studio is properly installed with PGO support.") + message(STATUS " You may need to install the 'MSVC v143 - VS 2022 C++ x64/x86 build tools' component.") + endif() +endfunction() + +# Function to configure PGO for a specific target +function(citron_configure_pgo target_name) + if(NOT TARGET ${target_name}) + message(WARNING "Target ${target_name} does not exist, skipping PGO configuration") + return() + endif() + + # Only configure PGO if either GENERATE or USE is enabled + if(NOT CITRON_ENABLE_PGO_GENERATE AND NOT CITRON_ENABLE_PGO_USE) + return() + endif() + + # Ensure both stages are not enabled at the same time + if(CITRON_ENABLE_PGO_GENERATE AND CITRON_ENABLE_PGO_USE) + message(FATAL_ERROR "Cannot enable both CITRON_ENABLE_PGO_GENERATE and CITRON_ENABLE_PGO_USE simultaneously. Please build twice: first with GENERATE, then with USE.") + endif() + + message(STATUS "Configuring PGO for target: ${target_name}") + + # MSVC-specific PGO + if(MSVC) + if(CITRON_ENABLE_PGO_GENERATE) + message(STATUS " [${target_name}] MSVC PGO: GENERATE stage") + # Use FASTGENPROFILE for faster profiling with similar accuracy + # GENPROFILE provides more detailed profiling but is slower + # You can change FASTGENPROFILE to GENPROFILE for more accuracy but slower profiling + target_compile_options(${target_name} PRIVATE /GL) + target_link_options(${target_name} PRIVATE + /LTCG + /FASTGENPROFILE + /PGD:"${CITRON_PGO_PROFILE_DIR}/${target_name}.pgd" + ) + + # Copy PGO runtime DLLs to output directory + citron_copy_pgo_runtime_dlls(${target_name}) + elseif(CITRON_ENABLE_PGO_USE) + message(STATUS " [${target_name}] MSVC PGO: USE stage") + # Check if profile data exists in pgo-profiles directory + set(PGD_FILE "${CITRON_PGO_PROFILE_DIR}/${target_name}.pgd") + + # Also check in the output directory (where MSVC creates them during GENERATE) + get_target_property(TARGET_OUTPUT_DIR ${target_name} RUNTIME_OUTPUT_DIRECTORY) + if(NOT TARGET_OUTPUT_DIR) + set(TARGET_OUTPUT_DIR "${CMAKE_BINARY_DIR}/bin") + endif() + set(PGD_FILE_OUTPUT "${TARGET_OUTPUT_DIR}/${target_name}.pgd") + + if(EXISTS "${PGD_FILE}") + target_compile_options(${target_name} PRIVATE /GL) + # Use the profile directory path + file(TO_NATIVE_PATH "${PGD_FILE}" PGD_FILE_NATIVE) + target_link_options(${target_name} PRIVATE + /LTCG + "/USEPROFILE:PGD=${PGD_FILE_NATIVE}" + ) + message(STATUS " [${target_name}] Using profile data: ${PGD_FILE}") + elseif(EXISTS "${PGD_FILE_OUTPUT}") + target_compile_options(${target_name} PRIVATE /GL) + # Use the output directory path + file(TO_NATIVE_PATH "${PGD_FILE_OUTPUT}" PGD_FILE_NATIVE) + target_link_options(${target_name} PRIVATE + /LTCG + "/USEPROFILE:PGD=${PGD_FILE_NATIVE}" + ) + message(STATUS " [${target_name}] Using profile data: ${PGD_FILE_OUTPUT}") + else() + message(WARNING "Profile data not found for ${target_name}. Checked:") + message(STATUS " - ${PGD_FILE}") + message(STATUS " - ${PGD_FILE_OUTPUT}") + message(WARNING "PGO USE stage will be skipped.") + endif() + endif() + + # GCC-specific PGO + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CITRON_ENABLE_PGO_GENERATE) + message(STATUS " [${target_name}] GCC PGO: GENERATE stage") + target_compile_options(${target_name} PRIVATE + -fprofile-generate="${CITRON_PGO_PROFILE_DIR}" + ) + target_link_options(${target_name} PRIVATE + -fprofile-generate="${CITRON_PGO_PROFILE_DIR}" + ) + elseif(CITRON_ENABLE_PGO_USE) + message(STATUS " [${target_name}] GCC PGO: USE stage") + # Check if profile data exists + file(GLOB profile_files "${CITRON_PGO_PROFILE_DIR}/*.gcda") + if(profile_files) + target_compile_options(${target_name} PRIVATE + -fprofile-use="${CITRON_PGO_PROFILE_DIR}" + -fprofile-correction # Handle inconsistencies in profile data + ) + target_link_options(${target_name} PRIVATE + -fprofile-use="${CITRON_PGO_PROFILE_DIR}" + ) + message(STATUS " [${target_name}] Using profile data from: ${CITRON_PGO_PROFILE_DIR}") + else() + message(WARNING "No profile data found for ${target_name} in ${CITRON_PGO_PROFILE_DIR}. PGO USE stage will be skipped.") + endif() + endif() + + # Clang-specific PGO + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(CITRON_ENABLE_PGO_GENERATE) + message(STATUS " [${target_name}] Clang PGO: GENERATE stage") + target_compile_options(${target_name} PRIVATE + -fprofile-generate="${CITRON_PGO_PROFILE_DIR}" + ) + target_link_options(${target_name} PRIVATE + -fprofile-generate="${CITRON_PGO_PROFILE_DIR}" + ) + elseif(CITRON_ENABLE_PGO_USE) + message(STATUS " [${target_name}] Clang PGO: USE stage") + + # For Clang, we need to merge .profraw files into .profdata first + set(PROFDATA_FILE "${CITRON_PGO_PROFILE_DIR}/default.profdata") + + # Check if merged profile data exists, if not try to create it + if(NOT EXISTS "${PROFDATA_FILE}") + file(GLOB profraw_files "${CITRON_PGO_PROFILE_DIR}/*.profraw") + if(profraw_files) + find_program(LLVM_PROFDATA llvm-profdata) + if(LLVM_PROFDATA) + message(STATUS " [${target_name}] Merging .profraw files into ${PROFDATA_FILE}") + execute_process( + COMMAND ${LLVM_PROFDATA} merge -output=${PROFDATA_FILE} ${profraw_files} + RESULT_VARIABLE merge_result + OUTPUT_QUIET + ERROR_QUIET + ) + if(NOT merge_result EQUAL 0) + message(WARNING "Failed to merge profile data for ${target_name}. PGO USE stage will be skipped.") + return() + endif() + else() + message(WARNING "llvm-profdata not found. Cannot merge profile data. PGO USE stage will be skipped.") + message(STATUS " Please run: llvm-profdata merge -output=${PROFDATA_FILE} ${CITRON_PGO_PROFILE_DIR}/*.profraw") + return() + endif() + else() + message(WARNING "No .profraw files found in ${CITRON_PGO_PROFILE_DIR}. PGO USE stage will be skipped.") + return() + endif() + endif() + + if(EXISTS "${PROFDATA_FILE}") + target_compile_options(${target_name} PRIVATE + -fprofile-use="${PROFDATA_FILE}" + ) + target_link_options(${target_name} PRIVATE + -fprofile-use="${PROFDATA_FILE}" + ) + message(STATUS " [${target_name}] Using profile data: ${PROFDATA_FILE}") + endif() + endif() + else() + message(WARNING "PGO is not supported for compiler: ${CMAKE_CXX_COMPILER_ID}") + endif() +endfunction() + +# Helper function to print PGO instructions +function(citron_print_pgo_instructions) + if(CITRON_ENABLE_PGO_GENERATE) + message(STATUS "") + message(STATUS "=================================================================") + message(STATUS "PGO GENERATE Stage") + message(STATUS "=================================================================") + message(STATUS "Citron has been built with profiling instrumentation.") + message(STATUS "") + message(STATUS "Next steps:") + message(STATUS " 1. Run the built citron executable") + message(STATUS " 2. Play games/perform typical operations to generate profile data") + message(STATUS " 3. Exit citron") + message(STATUS " 4. Profile data will be saved to: ${CITRON_PGO_PROFILE_DIR}") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(STATUS " 5. For Clang: Merge profile data with:") + message(STATUS " llvm-profdata merge -output=${CITRON_PGO_PROFILE_DIR}/default.profdata ${CITRON_PGO_PROFILE_DIR}/*.profraw") + endif() + message(STATUS " 6. Rebuild with: cmake -DCITRON_ENABLE_PGO_GENERATE=OFF -DCITRON_ENABLE_PGO_USE=ON") + message(STATUS "=================================================================") + message(STATUS "") + elseif(CITRON_ENABLE_PGO_USE) + message(STATUS "") + message(STATUS "=================================================================") + message(STATUS "PGO USE Stage") + message(STATUS "=================================================================") + message(STATUS "Citron is being optimized using profile data from: ${CITRON_PGO_PROFILE_DIR}") + message(STATUS "This build will be significantly faster than standard builds.") + message(STATUS "=================================================================") + message(STATUS "") + endif() +endfunction() + diff --git a/fix-pgo-dll.ps1 b/fix-pgo-dll.ps1 new file mode 100644 index 000000000..2b73f1c33 --- /dev/null +++ b/fix-pgo-dll.ps1 @@ -0,0 +1,174 @@ +# SPDX-FileCopyrightText: 2025 citron Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# Fix PGO DLL Script for Citron (Windows) +# This script helps locate and copy the required pgort140.dll for MSVC PGO builds + +param( + [Parameter()] + [string]$OutputDir = "build\bin\Release", + + [Parameter()] + [switch]$Help +) + +function Show-Usage { + Write-Host @" +Usage: .\fix-pgo-dll.ps1 [OPTIONS] + +This script helps fix the "pgort140.DLL was not found" error by locating +and copying the required PGO runtime DLL to your build output directory. + +Options: + -OutputDir PATH Target directory to copy DLL (default: build\bin\Release) + -Help Show this help message + +Example: + .\fix-pgo-dll.ps1 + .\fix-pgo-dll.ps1 -OutputDir "C:\MyBuild\bin" +"@ +} + +function Write-Header { + param([string]$Message) + Write-Host "`n=================================================================" -ForegroundColor Cyan + Write-Host $Message -ForegroundColor Cyan + Write-Host "=================================================================`n" -ForegroundColor Cyan +} + +function Write-Info { + param([string]$Message) + Write-Host "[INFO] $Message" -ForegroundColor Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "[WARNING] $Message" -ForegroundColor Yellow +} + +function Write-Error-Custom { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +if ($Help) { + Show-Usage + exit 0 +} + +Write-Header "PGO DLL Fixer for Citron" + +# Find Visual Studio installation +$VSInstallPath = $null +$VSWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + +if (Test-Path $VSWhere) { + Write-Info "Searching for Visual Studio installations..." + $VSInstallPath = & $VSWhere -latest -property installationPath + if ($VSInstallPath) { + Write-Info "Found Visual Studio at: $VSInstallPath" + } +} else { + Write-Warning "vswhere.exe not found. Trying common installation paths..." +} + +# Common Visual Studio installation paths +$CommonPaths = @( + "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community", + "${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional", + "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Community", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Professional", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Enterprise" +) + +if (-not $VSInstallPath) { + foreach ($path in $CommonPaths) { + if (Test-Path $path) { + $VSInstallPath = $path + Write-Info "Found Visual Studio at: $VSInstallPath" + break + } + } +} + +if (-not $VSInstallPath) { + Write-Error-Custom "Visual Studio installation not found!" + Write-Host "Please ensure Visual Studio 2019 or 2022 is installed with C++ support." + Write-Host "You can download it from: https://visualstudio.microsoft.com/downloads/" + exit 1 +} + +# Search for pgort140.dll +Write-Info "Searching for pgort140.dll..." + +$DllPaths = @( + "$VSInstallPath\VC\Redist\MSVC\*\x64\Microsoft.VC*.CRT\pgort140.dll", + "$VSInstallPath\VC\Redist\MSVC\*\x86\Microsoft.VC*.CRT\pgort140.dll", + "$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx64\x64\pgort140.dll", + "$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx64\x86\pgort140.dll", + "$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx86\x64\pgort140.dll", + "$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx86\x86\pgort140.dll" +) + +$FoundDll = $null +foreach ($pattern in $DllPaths) { + $matches = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue + if ($matches) { + $FoundDll = $matches[0].FullName + Write-Info "Found pgort140.dll at: $FoundDll" + break + } +} + +if (-not $FoundDll) { + Write-Error-Custom "pgort140.dll not found in Visual Studio installation!" + Write-Host "This usually means:" + Write-Host "1. Visual Studio was installed without PGO support" + Write-Host "2. You need to install the 'MSVC v143 - VS 2022 C++ x64/x86 build tools' component" + Write-Host "3. Try repairing your Visual Studio installation" + Write-Host "" + Write-Host "To fix this:" + Write-Host "1. Open Visual Studio Installer" + Write-Host "2. Click 'Modify' on your Visual Studio installation" + Write-Host "3. Go to 'Individual components' tab" + Write-Host "4. Search for 'PGO' and ensure it's checked" + Write-Host "5. Click 'Modify' to install the component" + exit 1 +} + +# Create output directory if it doesn't exist +if (-not (Test-Path $OutputDir)) { + Write-Info "Creating output directory: $OutputDir" + New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null +} + +# Copy the DLL +try { + Write-Info "Copying pgort140.dll to: $OutputDir" + Copy-Item -Path $FoundDll -Destination $OutputDir -Force + Write-Info "Successfully copied pgort140.dll!" + + # Verify the copy + $CopiedDll = Join-Path $OutputDir "pgort140.dll" + if (Test-Path $CopiedDll) { + Write-Info "Verification: pgort140.dll is now available in $OutputDir" + Write-Host "" + Write-Host "You can now run your PGO instrumented Citron build!" + } else { + Write-Error-Custom "Failed to copy pgort140.dll" + exit 1 + } +} catch { + Write-Error-Custom "Error copying pgort140.dll: $($_.Exception.Message)" + exit 1 +} + +Write-Header "PGO DLL Fix Complete!" +Write-Info "The pgort140.dll has been copied to your build output directory." +Write-Info "Your PGO instrumented Citron build should now run without the DLL error." +Write-Host "" +Write-Info "Next steps:" +Write-Host "1. Run your PGO instrumented build" +Write-Host "2. Play games to collect profile data" +Write-Host "3. Rebuild with PGO USE stage for optimization" diff --git a/pgo-build.ps1 b/pgo-build.ps1 new file mode 100644 index 000000000..156e62bbe --- /dev/null +++ b/pgo-build.ps1 @@ -0,0 +1,209 @@ +# SPDX-FileCopyrightText: 2025 citron Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# PGO Build Script for Citron (Windows/PowerShell) +# This script automates the Profile-Guided Optimization build process + +param( + [Parameter(Position=0, Mandatory=$true)] + [ValidateSet('generate', 'use', 'clean')] + [string]$Stage, + + [Parameter()] + [int]$Jobs = 0, + + [Parameter()] + [switch]$EnableLTO, + + [Parameter()] + [switch]$Help +) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BuildDir = Join-Path $ScriptDir "build" +$PgoProfilesDir = Join-Path $BuildDir "pgo-profiles" +$BackupProfilesDir = Join-Path $ScriptDir "pgo-profiles-backup" + +function Show-Usage { + Write-Host @" +Usage: .\pgo-build.ps1 [STAGE] [OPTIONS] + +STAGE can be: + generate - Build with PGO instrumentation (Stage 1) + use - Build using PGO profile data (Stage 2) + clean - Clean build directory but preserve profiles + +Example: + .\pgo-build.ps1 generate # Build instrumented version + # Run citron.exe to collect profiles + .\pgo-build.ps1 use # Build optimized version + +Options: + -Jobs N Number of parallel jobs (default: auto-detect) + -EnableLTO Enable Link-Time Optimization + -Help Show this help message +"@ +} + +function Write-Header { + param([string]$Message) + Write-Host "`n=================================================================" -ForegroundColor Cyan + Write-Host $Message -ForegroundColor Cyan + Write-Host "=================================================================`n" -ForegroundColor Cyan +} + +function Write-Info { + param([string]$Message) + Write-Host "[INFO] $Message" -ForegroundColor Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "[WARNING] $Message" -ForegroundColor Yellow +} + +function Write-Error-Custom { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +if ($Help) { + Show-Usage + exit 0 +} + +# Auto-detect number of processors +if ($Jobs -eq 0) { + $Jobs = $env:NUMBER_OF_PROCESSORS + if (-not $Jobs) { $Jobs = 4 } +} + +$LtoFlag = if ($EnableLTO) { "ON" } else { "OFF" } + +# Clean stage +if ($Stage -eq "clean") { + Write-Header "Cleaning Build Directory" + + if (Test-Path $PgoProfilesDir) { + Write-Info "Backing up PGO profiles..." + New-Item -ItemType Directory -Force -Path $BackupProfilesDir | Out-Null + Copy-Item -Path "$PgoProfilesDir\*" -Destination $BackupProfilesDir -Recurse -Force -ErrorAction SilentlyContinue + } + + if (Test-Path $BuildDir) { + Write-Info "Removing build directory..." + Remove-Item -Path $BuildDir -Recurse -Force + } + + if (Test-Path $BackupProfilesDir) { + Write-Info "Restoring PGO profiles..." + New-Item -ItemType Directory -Force -Path $PgoProfilesDir | Out-Null + Move-Item -Path "$BackupProfilesDir\*" -Destination $PgoProfilesDir -Force -ErrorAction SilentlyContinue + Remove-Item -Path $BackupProfilesDir -Recurse -Force + } + + Write-Info "Clean complete!" + exit 0 +} + +# Generate stage +if ($Stage -eq "generate") { + Write-Header "PGO Stage 1: Generate Profile Data" + + # Create build directory + New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null + Set-Location $BuildDir + + # Configure + Write-Info "Configuring CMake..." + cmake .. ` + -DCITRON_ENABLE_PGO_GENERATE=ON ` + -DCITRON_ENABLE_LTO=$LtoFlag ` + -DCMAKE_BUILD_TYPE=Release + + if ($LASTEXITCODE -ne 0) { + Write-Error-Custom "CMake configuration failed" + exit 1 + } + + # Build + Write-Info "Building instrumented Citron (this may take a while)..." + cmake --build . --config Release -j $Jobs + + if ($LASTEXITCODE -ne 0) { + Write-Error-Custom "Build failed" + exit 1 + } + + Write-Header "Build Complete!" + Write-Info "Next steps:" + Write-Host " 1. Run: .\bin\Release\citron.exe" + Write-Host " 2. Play games for 15-30 minutes to collect profile data" + Write-Host " 3. Exit citron" + Write-Host " 4. Run: .\pgo-build.ps1 use" + + Set-Location $ScriptDir +} + +# Use stage +if ($Stage -eq "use") { + Write-Header "PGO Stage 2: Build Optimized Binary" + + # Check if profile data exists + if (-not (Test-Path $PgoProfilesDir) -or -not (Get-ChildItem $PgoProfilesDir -ErrorAction SilentlyContinue)) { + Write-Error-Custom "No profile data found in $PgoProfilesDir" + Write-Info "Please run the generate stage first and collect profile data" + exit 1 + } + + # Backup profiles if build directory exists + if (Test-Path $BuildDir) { + Write-Info "Backing up PGO profiles..." + New-Item -ItemType Directory -Force -Path $BackupProfilesDir | Out-Null + Copy-Item -Path "$PgoProfilesDir\*" -Destination $BackupProfilesDir -Recurse -Force + Remove-Item -Path $BuildDir -Recurse -Force + } + + # Create build directory and restore profiles + New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null + if (Test-Path $BackupProfilesDir) { + New-Item -ItemType Directory -Force -Path $PgoProfilesDir | Out-Null + Move-Item -Path "$BackupProfilesDir\*" -Destination $PgoProfilesDir -Force + Remove-Item -Path $BackupProfilesDir -Recurse -Force + } + + Set-Location $BuildDir + + # Configure + Write-Info "Configuring CMake..." + cmake .. ` + -DCITRON_ENABLE_PGO_USE=ON ` + -DCITRON_ENABLE_LTO=$LtoFlag ` + -DCMAKE_BUILD_TYPE=Release + + if ($LASTEXITCODE -ne 0) { + Write-Error-Custom "CMake configuration failed" + Set-Location $ScriptDir + exit 1 + } + + # Build + Write-Info "Building optimized Citron (this may take a while)..." + cmake --build . --config Release -j $Jobs + + if ($LASTEXITCODE -ne 0) { + Write-Error-Custom "Build failed" + Set-Location $ScriptDir + exit 1 + } + + Write-Header "Build Complete!" + Write-Info "Your optimized Citron binary is ready!" + Write-Info "Location: $BuildDir\bin\Release\citron.exe" + Write-Host "" + Write-Info "This build is optimized for your specific usage patterns." + Write-Info "Enjoy improved performance! 🚀" + + Set-Location $ScriptDir +} + diff --git a/pgo-build.sh b/pgo-build.sh new file mode 100644 index 000000000..37c9af6d0 --- /dev/null +++ b/pgo-build.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2025 citron Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# PGO Build Script for Citron (Linux/macOS) +# This script automates the Profile-Guided Optimization build process + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUILD_DIR="${SCRIPT_DIR}/build" +PGO_PROFILES_DIR="${BUILD_DIR}/pgo-profiles" +BACKUP_PROFILES_DIR="${SCRIPT_DIR}/pgo-profiles-backup" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_header() { + echo -e "${BLUE}=================================================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}=================================================================${NC}" +} + +print_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +show_usage() { + echo "Usage: $0 [STAGE]" + echo "" + echo "STAGE can be:" + echo " generate - Build with PGO instrumentation (Stage 1)" + echo " use - Build using PGO profile data (Stage 2)" + echo " clean - Clean build directory but preserve profiles" + echo "" + echo "Example:" + echo " $0 generate # Build instrumented version" + echo " # Run citron to collect profiles" + echo " $0 use # Build optimized version" + echo "" + echo "Options:" + echo " -j N Number of parallel jobs (default: auto-detect)" + echo " -lto Enable Link-Time Optimization" + echo " -h Show this help message" +} + +# Parse arguments +STAGE="" +JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo "4") +ENABLE_LTO="OFF" + +while [[ $# -gt 0 ]]; do + case $1 in + generate|use|clean) + STAGE="$1" + shift + ;; + -j) + JOBS="$2" + shift 2 + ;; + -lto) + ENABLE_LTO="ON" + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +if [ -z "$STAGE" ]; then + print_error "No stage specified" + show_usage + exit 1 +fi + +# Clean stage +if [ "$STAGE" == "clean" ]; then + print_header "Cleaning Build Directory" + + if [ -d "$PGO_PROFILES_DIR" ]; then + print_info "Backing up PGO profiles..." + mkdir -p "$BACKUP_PROFILES_DIR" + cp -r "$PGO_PROFILES_DIR"/* "$BACKUP_PROFILES_DIR/" 2>/dev/null || true + fi + + if [ -d "$BUILD_DIR" ]; then + print_info "Removing build directory..." + rm -rf "$BUILD_DIR" + fi + + if [ -d "$BACKUP_PROFILES_DIR" ]; then + print_info "Restoring PGO profiles..." + mkdir -p "$PGO_PROFILES_DIR" + mv "$BACKUP_PROFILES_DIR"/* "$PGO_PROFILES_DIR/" 2>/dev/null || true + rm -rf "$BACKUP_PROFILES_DIR" + fi + + print_info "Clean complete!" + exit 0 +fi + +# Generate stage +if [ "$STAGE" == "generate" ]; then + print_header "PGO Stage 1: Generate Profile Data" + + # Create build directory + mkdir -p "$BUILD_DIR" + cd "$BUILD_DIR" + + # Configure + print_info "Configuring CMake..." + cmake .. \ + -DCITRON_ENABLE_PGO_GENERATE=ON \ + -DCITRON_ENABLE_LTO=$ENABLE_LTO \ + -DCMAKE_BUILD_TYPE=Release + + # Build + print_info "Building instrumented Citron (this may take a while)..." + cmake --build . -j"$JOBS" + + print_header "Build Complete!" + print_info "Next steps:" + echo " 1. Run: ./bin/citron" + echo " 2. Play games for 15-30 minutes to collect profile data" + echo " 3. Exit citron" + + # Clang-specific instructions + if [ "$(cmake --system-information | grep CMAKE_CXX_COMPILER_ID | grep -i clang)" ]; then + echo " 4. Merge profiles: llvm-profdata merge -output=$PGO_PROFILES_DIR/default.profdata $PGO_PROFILES_DIR/*.profraw" + echo " 5. Run: $0 use" + else + echo " 4. Run: $0 use" + fi +fi + +# Use stage +if [ "$STAGE" == "use" ]; then + print_header "PGO Stage 2: Build Optimized Binary" + + # Check if profile data exists + if [ ! -d "$PGO_PROFILES_DIR" ] || [ -z "$(ls -A $PGO_PROFILES_DIR 2>/dev/null)" ]; then + print_error "No profile data found in $PGO_PROFILES_DIR" + print_info "Please run the generate stage first and collect profile data" + exit 1 + fi + + # Backup profiles if build directory exists + if [ -d "$BUILD_DIR" ]; then + print_info "Backing up PGO profiles..." + mkdir -p "$BACKUP_PROFILES_DIR" + cp -r "$PGO_PROFILES_DIR"/* "$BACKUP_PROFILES_DIR/" + rm -rf "$BUILD_DIR" + fi + + # Create build directory and restore profiles + mkdir -p "$BUILD_DIR" + if [ -d "$BACKUP_PROFILES_DIR" ]; then + mkdir -p "$PGO_PROFILES_DIR" + mv "$BACKUP_PROFILES_DIR"/* "$PGO_PROFILES_DIR/" + rm -rf "$BACKUP_PROFILES_DIR" + fi + + cd "$BUILD_DIR" + + # Configure + print_info "Configuring CMake..." + cmake .. \ + -DCITRON_ENABLE_PGO_USE=ON \ + -DCITRON_ENABLE_LTO=$ENABLE_LTO \ + -DCMAKE_BUILD_TYPE=Release + + # Build + print_info "Building optimized Citron (this may take a while)..." + cmake --build . -j"$JOBS" + + print_header "Build Complete!" + print_info "Your optimized Citron binary is ready!" + print_info "Location: $BUILD_DIR/bin/citron" + echo "" + print_info "This build is optimized for your specific usage patterns." + print_info "Enjoy improved performance! 🚀" +fi +