From a2fc6c86dfc9ca49dc809ba548afb15f893122ec Mon Sep 17 00:00:00 2001 From: Dalton Messmer Date: Wed, 15 Jul 2026 17:44:56 -0400 Subject: [PATCH] Add CMake options for targeting specific microarchitectures (#8464) Bumps the minimum x86_64 microarchitecture level for nightly builds from x86_64-v1 (which includes up to SSE2) to x86_64-v2 (which includes up to SSE4.2) and similarly bumps the arm64 microarchitecture target for nightly builds depending on the OS. This is done using new CMake options that control the target microarchitecture level. Also fixes the symbol visibility when compiling with GCC or Clang. Both of these changes have the potential for performance improvements, and will also let us write conditionally-enabled SIMD code targeting newer microarchitectures with better capabilities than the compiler baseline of SSE2/NEON. Changes: - Adds `TARGET_UARCH` and `TARGET_UARCH_FLAGS` CMake options for setting the target CPU microarchitecture - Allows LMMS builds to target newer CPUs, letting the compiler make use of instruction set extensions like SSE4.2 or POPCNT - The options for `TARGET_UARCH` are: "none", "official", "custom", and "native" - These options mean: - "none": No microarchitecture level is set. Uses the compiler default (i.e. x86_64-v1). - "official": Targets the baseline microarchitecture level of official LMMS releases (On x86_64, this is x86_64-v2, which was agreed upon as a new baseline on Discord. The arm64 baseline differs depending on the target OS). - "custom": Targets the microarchitecture specified by `TARGET_UARCH_FLAGS`. - "native": Targets the highest microarchitecture level supported by the build machine. - If no option is provided, it defaults to "native" so local builds are highly optimized by default - All automated builds explicitly use "official" - Choosing "none" has the same effect as prior to this PR - Added a CMake function called `determine_msvc_native_arch` which provides a workaround for the lack of a `/arch:native` flag in MSVC. This function has been tested on x86_64, but not on arm64 since we don't support MSVC on arm64 yet. - Changes symbol visibility when using GCC/Clang from default (where all symbols are exported from shared library or executable) to hidden (where only symbols marked with LMMS_EXPORT are exported) - Matches the behavior on Windows - According to GCC's documentation, doing this "very substantially improves load times of your DSO (Dynamic Shared Object)", "lets the optimiser produce better code", and "reduces the size of your DSO by 5-20%". In actuality, our AppImage size shrunk from 155 MB to 154.3 MB, so the size improvements are not very significant, though there may still be performance benefits. --- .github/workflows/build.yml | 6 ++ CMakeLists.txt | 86 ++++++++++++++- cmake/modules/CheckCPUFeatures.cmake | 152 +++++++++++++++++++++++++++ include/AutomationTrack.h | 2 +- 4 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 cmake/modules/CheckCPUFeatures.cmake diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d932ec9f..5a828ec82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ jobs: CMAKE_OPTS: >- -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DTARGET_UARCH=official -DUSE_COMPILE_CACHE=ON -DWANT_DEBUG_CPACK=ON CCACHE_MAXSIZE: 0 @@ -101,6 +102,7 @@ jobs: CMAKE_OPTS: >- -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DTARGET_UARCH=official -DUSE_COMPILE_CACHE=ON -DWANT_DEBUG_CPACK=ON CCACHE_MAXSIZE: 0 @@ -193,6 +195,7 @@ jobs: -Werror=dev -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DTARGET_UARCH=official -DUSE_COMPILE_CACHE=ON CCACHE_MAXSIZE: 0 CCACHE_NOCOMPRESS: 1 @@ -282,6 +285,7 @@ jobs: -Werror=dev -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DTARGET_UARCH=official -DUSE_COMPILE_CACHE=ON CCACHE_MAXSIZE: 0 CCACHE_NOCOMPRESS: 1 @@ -401,6 +405,7 @@ jobs: --toolchain C:/vcpkg/scripts/buildsystems/vcpkg.cmake ` -Werror=dev ` -DCMAKE_BUILD_TYPE=RelWithDebInfo ` + -DTARGET_UARCH=official ` -DWANT_QT6=ON ` -DUSE_COMPILE_CACHE=ON ` -DUSE_WERROR=ON ` @@ -441,6 +446,7 @@ jobs: env: CMAKE_OPTS: >- -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DTARGET_UARCH=official -DUSE_COMPILE_CACHE=ON -DCPACK_NSIS_EXECUTABLE=/clang64/bin/makensis.exe CCACHE_MAXSIZE: 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d16c6b76..f603ae69a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,11 @@ SET(LMMS_SOURCE_DIR ${CMAKE_SOURCE_DIR}) # Import of windows.h breaks min()/max() ADD_DEFINITIONS(-DNOMINMAX) +# Set symbol visibility +set(CMAKE_C_VISIBILITY_PRESET hidden) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) + # CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES is not set correctly for MinGW until # CMake 3.14.1, so avoid specifying system include directories on affected # versions. Normal include directories are safe, since GCC ignores them if they @@ -110,6 +115,83 @@ option(WANT_CPACK_TARBALL "Request CPack to create a tarball instead of an insta option(WANT_QT6 "Build with experimental Qt6 support" OFF) +# Set the target microarchitecture +if(TARGET_UARCH_FLAGS) + if(TARGET_UARCH AND NOT TARGET_UARCH STREQUAL "custom") + message(WARNING "Ignoring TARGET_UARCH since TARGET_UARCH_FLAGS was set") + endif() + + set(_default_target_uarch "custom") + message(STATUS "Setting target microarchitecture to 'custom'") + message(STATUS "Target microarchitecture flags: ${TARGET_UARCH_FLAGS}") +elseif(TARGET_UARCH) + message(STATUS "Setting target microarchitecture to '${TARGET_UARCH}'") +else() + if(CMAKE_CROSSCOMPILING) + set(_default_target_uarch "none") + else() + set(_default_target_uarch "native") + endif() + message(STATUS "Setting target microarchitecture to '${_default_target_uarch}' as none was specified") +endif() + +if(NOT TARGET_UARCH) + set(TARGET_UARCH ${_default_target_uarch} CACHE STRING "Choose the target microarchitecture" FORCE) + # Accepted options: + # - "none": No microarchitecture is set. Uses the compiler default. + # - "official": Targets the baseline microarchitecture of official LMMS releases. + # - "custom": Targets the microarchitecture specified by TARGET_UARCH_FLAGS. + # - "native": Targets the highest microarchitecture level supported by the build machine. + set_property(CACHE TARGET_UARCH PROPERTY STRINGS "none" "official" "custom" "native") +endif() + +if(TARGET_UARCH STREQUAL "custom" OR TARGET_UARCH_FLAGS) + add_compile_options(${TARGET_UARCH_FLAGS}) +elseif(TARGET_UARCH STREQUAL "none") + # Do nothing, use the compiler default +elseif(TARGET_UARCH STREQUAL "official") + if(LMMS_HOST_X86_64) + if(MSVC) + # MSVC doesn't have /arch:x86-64-v2, but this is pretty close + add_compile_options(/arch:SSE4.2) + else() + add_compile_options(-march=x86-64-v2) + endif() + elseif(LMMS_HOST_ARM64) + if(LMMS_BUILD_WIN64) + # Using Windows 11 minimum requirements + # https://learn.microsoft.com/en-us/windows-hardware/design/minimum/minimum-hardware-requirements-overview + if(MSVC) + add_compile_options(/arch:armv8.1) + else() + add_compile_options(-march=armv8.1-a) + endif() + elseif(LMMS_BUILD_APPLE) + # Chosen for Apple M1 support + add_compile_options(-march=armv8.4-a) + else() + # Chosen for Raspberry Pi 4 support + add_compile_options(-march=armv8-a) + endif() + else() + message(WARNING "There are no official releases for the target architecture, so there is no official baseline microarchitecture") + endif() +elseif(TARGET_UARCH STREQUAL "native") + if(CMAKE_CROSSCOMPILING) + message(FATAL_ERROR "Cannot target native microarchitecture while cross-compiling") + endif() + + if(MSVC) + include(CheckCPUFeatures) + determine_msvc_native_arch(_arch_option) + if(NOT "${_arch_option}" STREQUAL "") + add_compile_options(${_arch_option}) + endif() + else() + add_compile_options(-march=native -mtune=native) + endif() +endif() + IF(LMMS_BUILD_APPLE) # Fix linking on 10.14+. See issue #4762 on github LINK_DIRECTORIES("${APPLE_PREFIX}/lib") @@ -666,10 +748,10 @@ SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DLMMS_DEBUG") SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DLMMS_DEBUG") if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - add_compile_definitions(NDEBUG) + add_compile_definitions(NDEBUG) SET(STATUS_ASSERTIONS "Disabled") else() - remove_definitions(-DNDEBUG) + remove_definitions(-DNDEBUG) SET(STATUS_ASSERTIONS "Enabled") endif() diff --git a/cmake/modules/CheckCPUFeatures.cmake b/cmake/modules/CheckCPUFeatures.cmake new file mode 100644 index 000000000..5d3329794 --- /dev/null +++ b/cmake/modules/CheckCPUFeatures.cmake @@ -0,0 +1,152 @@ +# Copyright (c) 2026 Dalton Messmer +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Determines the newest microarchitecture supported by the build machine using MSVC. +# +# This exists because unlike GCC and Clang, MSVC does not have an equivalent +# to -march=native which targets the latest supported microarchitecture. +# +# Usage: +# determine_msvc_native_arch( +# # The cache variable in which to store the MSVC /arch:foo option +# ) +function(determine_msvc_native_arch _arch_option_out) + # Return if we already know the /arch option + if(NOT "${${_arch_option_out}}" STREQUAL "") + return() + endif() + + if(NOT MSVC) + message(FATAL_ERROR "Only MSVC is supported") + endif() + + if(CMAKE_CROSSCOMPILING) + message(FATAL_ERROR "Cross-compiling is not supported") + endif() + + if(NOT LMMS_HOST_X86_64 AND NOT LMMS_HOST_ARM64) + message(WARNING "CPU feature detection is only implemented for x86_64 and arm64 - using default /arch option.") + set("${_arch_option_out}" "" CACHE INTERNAL "Native /arch option") + return() + endif() + + set(_source [[ + #include + #if defined(_M_X64) + # include + # include + #elif defined(_M_ARM64) + # include + #else + # error Unsupported platform + #endif + + auto main() -> int + { + #if defined(_M_X64) + if (__check_isa_support(__IA_SUPPORT_AVX10_2, 0)) { + std::cout << "/arch:AVX10.2"; + } else if (__check_isa_support(__IA_SUPPORT_AVX10, 0)) { + std::cout << "/arch:AVX10.1"; + } else if (__check_isa_support(__IA_SUPPORT_VECTOR512, 0)) { + std::cout << "/arch:AVX512"; + } else if (__check_isa_support(__IA_SUPPORT_VECTOR256, 0)) { + std::cout << "/arch:AVX2"; + } else if (__check_isa_support(__IA_SUPPORT_SSE42, 0)) { + std::cout << "/arch:SSE4.2"; + } else if (__check_isa_support(__IA_SUPPORT_VECTOR128, 0)) { + std::cout << "/arch:SSE2"; + } else { + std::cout << ""; + } + #else + # if defined(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv9.4"; + } else + # endif + # if defined(PF_ARM_SME_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_SME_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv9.2"; + } else + # endif + # if defined(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv9.0"; + } else + # endif + # if defined(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE) && defined(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE) != FALSE + && IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv8.3"; + } else + # endif + # if defined(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) != FALSE) { + # if defined(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv8.2+rcpc"; + } else + # endif + { + std::cout << "/arch:armv8.2"; + } + } else + # endif + # if defined(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) != FALSE) { + # if defined(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) + if (IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv8.2+rcpc"; + } else + # endif + { + std::cout << "/arch:armv8.2"; + } + } else + # endif + if (IsProcessorFeaturePresent(PF_ARM_V8_INSTRUCTIONS_AVAILABLE) != FALSE) { + if (IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE) != FALSE) { + std::cout << "/arch:armv8.0+lse"; + } else { + std::cout << "/arch:armv8.0"; + } + } else { + std::cout << ""; + } + #endif + } + ]]) + + # Write the source code to a temporary file + string(SHA1 _source_hash "${_source}") + set(_source_file "${CMAKE_CURRENT_BINARY_DIR}/${_source_hash}.cpp") + file(WRITE "${_source_file}" "${_source}") + + # Build and run the temporary file to get the /arch option + # TODO CMake 3.25: Use the new signature for try_run which has a NO_CACHE + # option and doesn't require separate file management. + try_run( + _dmna_run_result _dmna_compile_result "${CMAKE_CURRENT_BINARY_DIR}" + SOURCES "${_source_file}" + CMAKE_FLAGS /EHsc /O2 + CXX_STANDARD 20 + RUN_OUTPUT_VARIABLE _run_output + COMPILE_OUTPUT_VARIABLE _compile_output + ) + + # Clean up the temporary file + file(REMOVE "${_source_file}") + + # Set the /arch option if the run was successful, using a cache variable since + # this check may be relatively expensive. Otherwise, log the error + # and inform the user. + if(_dmna_run_result EQUAL "0") + message(STATUS "Determined MSVC native /arch option: '${_run_output}'") + set("${_arch_option_out}" "${_run_output}" CACHE INTERNAL "Native /arch option") + else() + message(DEBUG "${_compile_output}") + endif() +endfunction() diff --git a/include/AutomationTrack.h b/include/AutomationTrack.h index 60a6af677..fa8f87a40 100644 --- a/include/AutomationTrack.h +++ b/include/AutomationTrack.h @@ -32,7 +32,7 @@ namespace lmms { -class AutomationTrack : public Track +class LMMS_EXPORT AutomationTrack : public Track { Q_OBJECT public: