Skip to content

Commit

Permalink
Easy setup of LTO
Browse files Browse the repository at this point in the history
  • Loading branch information
bsamseth committed May 23, 2019
1 parent cce7bf1 commit c27f099
Show file tree
Hide file tree
Showing 2 changed files with 252 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Expand Up @@ -6,7 +6,10 @@ project(VMC VERSION 0.1.0.0 LANGUAGES C CXX)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
include(ConfigSafeGuards)
include(Colors)
include(LTO)

find_lto(C)
find_lto(CXX)


# --------------------------------------------------------------------------------
Expand Down
249 changes: 249 additions & 0 deletions cmake/LTO.cmake
@@ -0,0 +1,249 @@
# Usage :
#
# Variable : ENABLE_LTO | Enable or disable LTO support for this build
#
# find_lto(lang)
# - lang is C or CXX (the language to test LTO for)
# - call it after project() so that the compiler is already detected
#
# This will check for LTO support and create a target_enable_lto(target [debug,optimized,general]) macro.
# The 2nd parameter has the same meaning as in target_link_libraries, and is used to enable LTO only for those build configurations
# 'debug' is by default the Debug configuration, and 'optimized' all the other configurations
#
# if ENABLE_LTO is set to false, an empty macro will be generated
#
# Then to enable LTO for your target use
#
# target_enable_lto(mytarget general)
#
# It is however recommended to use it only for non debug builds the following way :
#
# target_enable_lto(mytarget optimized)
#
# Note : For CMake versions < 3.9, target_link_library is used in it's non plain version.
# You will need to specify PUBLIC/PRIVATE/INTERFACE to all your other target_link_library calls for the target
#
# WARNING for cmake versions older than 3.9 :
# This module will override CMAKE_AR CMAKE_RANLIB and CMAKE_NM by the gcc versions if found when building with gcc


# License:
#
# Copyright (C) 2016 Lectem <lectem@gmail.com>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the 'Software') deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


macro(find_lto lang)
if(ENABLE_LTO AND NOT LTO_${lang}_CHECKED)

#LTO support was added for clang/gcc in 3.9
if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.9)
cmake_policy(SET CMP0054 NEW)
message(STATUS "Checking for LTO Compatibility")
# Since GCC 4.9 we need to use gcc-ar / gcc-ranlib / gcc-nm
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_GCC_AR OR NOT CMAKE_GCC_RANLIB OR NOT CMAKE_GCC_NM)
find_program(CMAKE_GCC_AR NAMES
"${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar"
"${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar-${_version}"
DOC "gcc provided wrapper for ar which adds the --plugin option"
)
find_program(CMAKE_GCC_RANLIB NAMES
"${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib"
"${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib-${_version}"
DOC "gcc provided wrapper for ranlib which adds the --plugin option"
)
# Not needed, but at least stay coherent
find_program(CMAKE_GCC_NM NAMES
"${_CMAKE_TOOLCHAIN_PREFIX}gcc-nm"
"${_CMAKE_TOOLCHAIN_PREFIX}gcc-nm-${_version}"
DOC "gcc provided wrapper for nm which adds the --plugin option"
)
mark_as_advanced(CMAKE_GCC_AR CMAKE_GCC_RANLIB CMAKE_GCC_NM)
set(CMAKE_LTO_AR ${CMAKE_GCC_AR})
set(CMAKE_LTO_RANLIB ${CMAKE_GCC_RANLIB})
set(CMAKE_LTO_NM ${CMAKE_GCC_NM})
endif()
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_LTO_AR ${CMAKE_AR})
set(CMAKE_LTO_RANLIB ${CMAKE_RANLIB})
set(CMAKE_LTO_NM ${CMAKE_NM})
endif()

if(CMAKE_LTO_AR AND CMAKE_LTO_RANLIB)
set(__lto_flags -flto)

if(NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 4.7)
list(APPEND __lto_flags -fno-fat-lto-objects)
endif()

if(NOT DEFINED CMAKE_${lang}_PASSED_LTO_TEST)
set(__output_dir "${CMAKE_PLATFORM_INFO_DIR}/LtoTest1${lang}")
file(MAKE_DIRECTORY "${__output_dir}")
set(__output_base "${__output_dir}/lto-test-${lang}")

execute_process(
COMMAND ${CMAKE_COMMAND} -E echo "void foo() {}"
COMMAND ${CMAKE_${lang}_COMPILER} ${__lto_flags} -c -xc -
-o "${__output_base}.o"
RESULT_VARIABLE __result
ERROR_QUIET
OUTPUT_QUIET
)

if("${__result}" STREQUAL "0")
execute_process(
COMMAND ${CMAKE_LTO_AR} cr "${__output_base}.a" "${__output_base}.o"
RESULT_VARIABLE __result
ERROR_QUIET
OUTPUT_QUIET
)
endif()

if("${__result}" STREQUAL "0")
execute_process(
COMMAND ${CMAKE_LTO_RANLIB} "${__output_base}.a"
RESULT_VARIABLE __result
ERROR_QUIET
OUTPUT_QUIET
)
endif()

if("${__result}" STREQUAL "0")
execute_process(
COMMAND ${CMAKE_COMMAND} -E echo "void foo(); int main() {foo();}"
COMMAND ${CMAKE_${lang}_COMPILER} ${__lto_flags} -xc -
-x none "${__output_base}.a" -o "${__output_base}"
RESULT_VARIABLE __result
ERROR_QUIET
OUTPUT_QUIET
)
endif()

if("${__result}" STREQUAL "0")
set(__lto_found TRUE)
endif()

set(CMAKE_${lang}_PASSED_LTO_TEST
${__lto_found} CACHE INTERNAL
"If the compiler passed a simple LTO test compile")
endif()
if(CMAKE_${lang}_PASSED_LTO_TEST)
message(STATUS "Checking for LTO Compatibility - works")
set(LTO_${lang}_SUPPORT TRUE CACHE BOOL "Do we have LTO support ?")
set(LTO_COMPILE_FLAGS -flto CACHE STRING "Link Time Optimization compile flags")
set(LTO_LINK_FLAGS -flto CACHE STRING "Link Time Optimization link flags")
else()
message(STATUS "Checking for LTO Compatibility - not working")
endif()

endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
message(STATUS "Checking for LTO Compatibility - works (assumed for clang)")
set(LTO_${lang}_SUPPORT TRUE CACHE BOOL "Do we have LTO support ?")
set(LTO_COMPILE_FLAGS -flto CACHE STRING "Link Time Optimization compile flags")
set(LTO_LINK_FLAGS -flto CACHE STRING "Link Time Optimization link flags")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
message(STATUS "Checking for LTO Compatibility - works")
set(LTO_${lang}_SUPPORT TRUE CACHE BOOL "Do we have LTO support ?")
set(LTO_COMPILE_FLAGS /GL CACHE STRING "Link Time Optimization compile flags")
set(LTO_LINK_FLAGS -LTCG:INCREMENTAL CACHE STRING "Link Time Optimization link flags")
else()
message(STATUS "Checking for LTO Compatibility - compiler not handled by module")
endif()
mark_as_advanced(LTO_${lang}_SUPPORT LTO_COMPILE_FLAGS LTO_LINK_FLAGS)


set(LTO_${lang}_CHECKED TRUE CACHE INTERNAL "" )

if(CMAKE_GCC_AR AND CMAKE_GCC_RANLIB AND CMAKE_GCC_NM)
# THIS IS HACKY BUT THERE IS NO OTHER SOLUTION ATM
set(CMAKE_AR ${CMAKE_GCC_AR} CACHE FILEPATH "Forcing gcc-ar instead of ar" FORCE)
set(CMAKE_NM ${CMAKE_GCC_NM} CACHE FILEPATH "Forcing gcc-nm instead of nm" FORCE)
set(CMAKE_RANLIB ${CMAKE_GCC_RANLIB} CACHE FILEPATH "Forcing gcc-ranlib instead of ranlib" FORCE)
endif()
endif(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.9)
endif(ENABLE_LTO AND NOT LTO_${lang}_CHECKED)


if(ENABLE_LTO)
#Special case for cmake older than 3.9, using a library for gcc/clang, but could setup the flags directly.
#Taking advantage of the [debug,optimized] parameter of target_link_libraries
if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.9)
if(LTO_${lang}_SUPPORT)
if(NOT TARGET __enable_lto_tgt)
add_library(__enable_lto_tgt INTERFACE)
endif()
target_compile_options(__enable_lto_tgt INTERFACE ${LTO_COMPILE_FLAGS})
#this might not work for all platforms... in which case we'll have to set the link flags on the target directly
target_link_libraries(__enable_lto_tgt INTERFACE ${LTO_LINK_FLAGS} )
macro(target_enable_lto _target _build_configuration)
if(${_build_configuration} STREQUAL "optimized" OR ${_build_configuration} STREQUAL "debug" )
target_link_libraries(${_target} PRIVATE ${_build_configuration} __enable_lto_tgt)
else()
target_link_libraries(${_target} PRIVATE __enable_lto_tgt)
endif()
endmacro()
else()
#In old cmake versions, we can set INTERPROCEDURAL_OPTIMIZATION even if not supported by the compiler
#So if we didn't detect it, let cmake give it a try
set(__IPO_SUPPORTED TRUE)
endif()
else()
cmake_policy(SET CMP0069 NEW)
include(CheckIPOSupported)
# Optional IPO. Do not use IPO if it's not supported by compiler.
check_ipo_supported(RESULT __IPO_SUPPORTED OUTPUT output)
if(NOT __IPO_SUPPORTED)
message(STATUS "IPO is not supported or broken.")
else()
message(STATUS "IPO is supported")
endif()
endif()
if(__IPO_SUPPORTED)
macro(target_enable_lto _target _build_configuration)
if(NOT ${_build_configuration} STREQUAL "debug" )
#enable for all configurations
set_target_properties(${_target} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
if(${_build_configuration} STREQUAL "optimized" )
#blacklist debug configurations
set(__enable_debug_lto FALSE)
else()
#enable only for debug configurations
set(__enable_debug_lto TRUE)
endif()
get_property(DEBUG_CONFIGURATIONS GLOBAL PROPERTY DEBUG_CONFIGURATIONS)
if(NOT DEBUG_CONFIGURATIONS)
set(DEBUG_CONFIGURATIONS DEBUG) # This is what is done by CMAKE internally... since DEBUG_CONFIGURATIONS is empty by default
endif()
foreach(config IN LISTS DEBUG_CONFIGURATIONS)
set_target_properties(${_target} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_${config} ${__enable_debug_lto})
endforeach()
endmacro()
endif()
endif()
if(NOT COMMAND target_enable_lto)
macro(target_enable_lto _target _build_configuration)
endmacro()
endif()
endmacro()

0 comments on commit c27f099

Please sign in to comment.