cmake_minimum_required(VERSION 3.20) project(PSPRecomp VERSION 0.1.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) option(PSPRECOMP_BUILD_TESTS "Build reusable PSPRecomp tests" ON) option(PSPRECOMP_BUILD_PROFILE_TESTS "Build tests supplied by the selected profile" ON) set(PSPRECOMP_PROFILE "" CACHE STRING "Profile to build from profiles/; empty builds only the framework") set(PSPRECOMP_GENERATED_OPT_LEVEL "2" CACHE STRING "Optimization level for generated AOT units (0, 1, 2 or 3)") set_property(CACHE PSPRECOMP_GENERATED_OPT_LEVEL PROPERTY STRINGS 0 1 2 3) if(NOT PSPRECOMP_GENERATED_OPT_LEVEL MATCHES "^[0-3]$") message(FATAL_ERROR "PSPRECOMP_GENERATED_OPT_LEVEL must be 0, 1, 2 or 3") endif() option(PSPRECOMP_LTO "Enable link-time/whole-program optimization" OFF) option(PSPRECOMP_NATIVE_AVX2 "Use AVX2 code generation for optimized MSVC builds" OFF) option(PSPRECOMP_HOST_AVX "Use AVX for host-side scalar floor/ceil hot paths on MSVC" ON) option(PSPRECOMP_AOT_ASSUME_NO_WRITE_WATCH "Remove per-store write-watch branch from production AOT fast memory" OFF) option(PSPRECOMP_AOT_PRODUCTION_FASTPATHS "Compile out AOT chain diagnostic/counter branches for production builds" OFF) set(PSPRECOMP_MSVC_CGTHREADS "0" CACHE STRING "MSVC LTCG code-generation threads (0 = compiler default)") if(NOT PSPRECOMP_MSVC_CGTHREADS MATCHES "^[0-9]+$") message(FATAL_ERROR "PSPRECOMP_MSVC_CGTHREADS must be a non-negative integer") endif() # Bare /MP lets cl.exe spawn one front-end per logical core, and MSBuild /m # multiplies that by the number of projects it builds in parallel. On the VCS # profile each generated AOT unit costs well over a gigabyte at /Ox /Ob3, so the # unbounded product exhausts physical memory. Cap the per-cl.exe degree here and # let the build scripts pick a memory-aware value. set(PSPRECOMP_MSVC_MP_JOBS "0" CACHE STRING "Max cl.exe front-ends (0 = logical-core default, 1 = disable /MP)") if(NOT PSPRECOMP_MSVC_MP_JOBS MATCHES "^[0-9]+$") message(FATAL_ERROR "PSPRECOMP_MSVC_MP_JOBS must be a non-negative integer") endif() if(PSPRECOMP_MSVC_MP_JOBS STREQUAL "0") set(PSPRECOMP_MSVC_MP_FLAG "/MP") elseif(PSPRECOMP_MSVC_MP_JOBS STREQUAL "1") # Omitting /MP is materially different from /MP1: the latter still creates # and communicates with a child compiler process and can fail with D8040. set(PSPRECOMP_MSVC_MP_FLAG "") else() set(PSPRECOMP_MSVC_MP_FLAG "/MP${PSPRECOMP_MSVC_MP_JOBS}") endif() if(PSPRECOMP_AOT_ASSUME_NO_WRITE_WATCH) add_compile_definitions(PSPRECOMP_AOT_ASSUME_NO_WRITE_WATCH=1) endif() if(PSPRECOMP_AOT_PRODUCTION_FASTPATHS) add_compile_definitions(PSPRECOMP_AOT_PRODUCTION_FASTPATHS=1) endif() if(MSVC AND PSPRECOMP_NATIVE_AVX2) add_compile_options($<$:/arch:AVX2> $<$:/arch:AVX2>) endif() if(PSPRECOMP_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT PSPRECOMP_LTO_SUPPORTED OUTPUT PSPRECOMP_LTO_REASON) if(PSPRECOMP_LTO_SUPPORTED) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL ON) else() message(WARNING "Whole-program optimization unavailable: ${PSPRECOMP_LTO_REASON}") endif() endif() add_library(psprecomp_core STATIC src/decoder.cpp src/deflate.cpp src/elf32.cpp src/guest_memory.cpp src/nid_registry.cpp src/program_analysis.cpp src/runtime.cpp src/sha256.cpp ) target_include_directories(psprecomp_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_compile_definitions(psprecomp_core PRIVATE $<$:_CRT_SECURE_NO_WARNINGS>) if(MSVC) target_compile_options(psprecomp_core PRIVATE /W4 /permissive- /EHsc ${PSPRECOMP_MSVC_MP_FLAG} $<$:/Ob3> $<$:/Ob3>) else() target_compile_options(psprecomp_core PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Wshadow) endif() add_executable(psp_analyze tools/analyzer_main.cpp) target_link_libraries(psp_analyze PRIVATE psprecomp_core) add_executable(psp_recomp tools/codegen_main.cpp) target_link_libraries(psp_recomp PRIVATE psprecomp_core) add_executable(dump_function tools/dump_function.cpp) target_link_libraries(dump_function PRIVATE psprecomp_core) function(psprecomp_enable_host_avx target) # /arch:AVX appears after the directory-wide /arch:AVX2 option in MSVC's # command line and therefore silently downgrades the whole target. AVX2 # already includes the AVX scalar instructions this compatibility switch # was introduced for, so do not emit the weaker override in an AVX2 build. if(PSPRECOMP_HOST_AVX AND MSVC AND NOT PSPRECOMP_NATIVE_AVX2) target_compile_options(${target} PRIVATE /arch:AVX) endif() endfunction() if(PSPRECOMP_BUILD_TESTS) enable_testing() add_executable(psprecomp_tests tests/test_main.cpp) target_link_libraries(psprecomp_tests PRIVATE psprecomp_core) add_dependencies(psprecomp_tests psp_recomp) target_compile_definitions(psprecomp_tests PRIVATE PSPRECOMP_CODEGEN_PATH="$") add_test(NAME psprecomp_tests COMMAND psprecomp_tests) endif() if(NOT PSPRECOMP_PROFILE STREQUAL "") set(PSPRECOMP_PROFILE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/profiles/${PSPRECOMP_PROFILE}") if(NOT EXISTS "${PSPRECOMP_PROFILE_DIR}/CMakeLists.txt") message(FATAL_ERROR "Unknown PSPRecomp profile: ${PSPRECOMP_PROFILE}") endif() add_subdirectory("${PSPRECOMP_PROFILE_DIR}" "profiles/${PSPRECOMP_PROFILE}") endif()