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 <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-10-11 13:30:39 +10:00
parent 2a7e6c74bd
commit bdd2875642
6 changed files with 890 additions and 0 deletions

9
.gitignore vendored
View File

@@ -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

View File

@@ -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)

274
CMakeModules/PGO.cmake Normal file
View File

@@ -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()

174
fix-pgo-dll.ps1 Normal file
View File

@@ -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"

209
pgo-build.ps1 Normal file
View File

@@ -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
}

203
pgo-build.sh Normal file
View File

@@ -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