| Step 6: In-Depth System Introspection |
| ===================================== |
| |
| In order to discover information about the system environment and the toolchain, |
| CMake will often compile small test programs to verify the availability of |
| compiler flags, headers, and builtins or other language constructs. |
| |
| In this step, we will take advantage of the same test program mechanisms that |
| CMake uses in our own project code. |
| |
| Background |
| ^^^^^^^^^^ |
| |
| An old trick going back to the oldest days of configuration and build systems |
| is to verify the availability of some feature by compiling a small program |
| which uses that feature. |
| |
| CMake makes this unnecessary for many contexts. As we will address in later |
| steps, if CMake can find a library dependency, we can rely on it having all |
| the facilities (headers, code generators, test utilities, etc) we expect it to |
| have. Conversely, if CMake can't find a dependency, attempting to use the |
| dependency anyway will almost certainly fail. |
| |
| However, there are other kinds of information about the toolchain which CMake |
| doesn't communicate readily. For these advanced cases, we can write our own |
| test programs and compile commands to check for availability. |
| |
| CMake provides modules to simplify these checks. These are documented at |
| :manual:`cmake-modules(7)`. Any module that begins with ``Check`` is a system |
| introspection module we can use to interrogate the toolchain and system |
| environment. Some notable ones include: |
| |
| ``CheckIncludeFiles`` |
| Check one or more C/C++ header files. |
| |
| ``CheckCompilerFlag`` |
| Check whether the compiler supports a given flag. |
| |
| ``CheckSourceCompiles`` |
| Checks whether source code can be built for a given language. |
| |
| ``CheckIPOSupported`` |
| Check whether the compiler supports interprocedural optimization (IPO/LTO). |
| |
| |
| Exercise 1 - Check Include File |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| A fast and easy check to perform is if a given header file is available on |
| a certain platform, for which CMake provides :module:`CheckIncludeFiles`. This |
| is most appropriate for system and intrinsic headers, which may not be provided |
| by a specific package by are expected to be available in many build environments. |
| |
| .. code-block:: cmake |
| |
| include(CheckIncludeFiles) |
| check_include_files(sys/socket.h HAVE_SYS_SOCKET_H LANGUAGE CXX) |
| |
| .. note:: |
| These functions are not immediately available in CMake, they must be added via |
| :command:`include`'ing their associated module (aka, a CMakeLang file). Many |
| modules live inside CMake's own ``Modules`` folder. This built-in ``Modules`` |
| folder is one of the places CMake searches when evaluating an :command:`include` |
| command. You can think of these modules like standard library headers, they're |
| expected to be available. |
| |
| Once a header file is known to exist, we can communicate that to our code using |
| the same mechanisms of conditionals and target commands already covered. |
| |
| Goal |
| ---- |
| |
| Check if the x86 SSE2 intrinsic header is available, and if so use it to |
| improve ``mathfunctions::sqrt``. |
| |
| Helpful Resources |
| ----------------- |
| |
| * :module:`CheckIncludeFiles` |
| * :command:`target_compile_definitions` |
| |
| Files to Edit |
| ------------- |
| |
| * ``MathFunctions/CMakeLists.txt`` |
| * ``MathFunctions/MathFunctions.cxx`` |
| |
| Getting Started |
| --------------- |
| |
| The ``Help/guide/tutorial/Step6`` directory contains the complete, recommended |
| solution to ``Step5`` and relevant ``TODOs`` for this step. It also contains |
| specialized implementations of the ``sqrt`` function for various conditions, |
| which you will find in ``MathFunctions/MathFunctions.cxx``. |
| |
| Complete ``TODO 1`` through ``TODO 3``. Note that some ``#ifdef`` directives |
| have already been added to the library, which will change its operation as we |
| work through the step. |
| |
| Build and Run |
| ------------- |
| |
| We can use our usual commands to configure. |
| |
| .. code-block:: console |
| |
| cmake --preset tutorial |
| cmake --build build |
| |
| In the output of the configuration step we should observe CMake checking for |
| the ``emmintrin.h`` header. |
| |
| .. code-block:: console |
| |
| -- Looking for include file emmintrin.h |
| -- Looking for include file emmintrin.h - found |
| |
| If the header is available on your system, verify the ``Tutorial`` output |
| contains the message about using SSE2. Conversely, if the header is not |
| available you should see the usual behavior from ``Tutorial``. |
| |
| Solution |
| -------- |
| |
| First we include and use the ``CheckIncludeFiles`` module, verifying the |
| ``emmintrin.h`` header is available. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 1: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/MathFunctions/CMakeLists.txt |
| :caption: TODO 1: MathFunctions/CMakeLists.txt |
| :name: MathFunctions/CMakeLists.txt-check-include-files |
| :language: cmake |
| :start-at: include(CheckIncludeFiles |
| :end-at: check_include_files( |
| |
| .. raw:: html |
| |
| </details> |
| |
| Then we use the result of the check to conditionally set a compile definition |
| on ``MathFunctions``. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 2: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/MathFunctions/CMakeLists.txt |
| :caption: TODO 2: MathFunctions/CMakeLists.txt |
| :name: MathFunctions/CMakeLists.txt-define-use-sse2 |
| :language: cmake |
| :start-at: if(HAS_EMMINTRIN) |
| :end-at: endif() |
| |
| .. raw:: html |
| |
| </details> |
| |
| Finally we can conditionally include the header in the ``MathFunctions`` library. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 3: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/MathFunctions/MathFunctions.cxx |
| :caption: TODO 3: MathFunctions/MathFunctions.cxx |
| :name: MathFunctions/MathFunctions.cxx-include-sse2 |
| :language: c++ |
| :start-at: #ifdef TUTORIAL_USE_SSE2 |
| :end-at: #endif |
| |
| .. raw:: html |
| |
| </details> |
| |
| |
| Exercise 2 - Check Source Compiles |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| Sometimes it is insufficient to merely check for a header. This is especially |
| true when no header is available to check, such is the case with |
| compiler-builtins. For these scenarios we have :module:`CheckSourceCompiles`. |
| |
| .. code-block:: cmake |
| |
| include(CheckSourceCompiles) |
| check_source_compiles(CXX |
| " |
| int main() { |
| int a, b, c; |
| __builtin_add_overflow(a, b, &c); |
| } |
| " |
| HAS_CHECKED_ADDITION |
| ) |
| |
| .. note:: |
| By default :module:`CheckSourceCompiles` builds and links an executable. The |
| code to be check must provide a valid ``int main()`` in order to succeed. |
| |
| After performing the check, this system introspection can be applied identically |
| to how we discussed with header files. |
| |
| Goal |
| ---- |
| |
| Check if the GNU SSE2 builtins are available, and if so use them to improve |
| ``mathfunctions::sqrt``. |
| |
| Helpful Resources |
| ----------------- |
| |
| * :module:`CheckSourceCompiles` |
| * :command:`target_compile_definitions` |
| |
| Files to Edit |
| ------------- |
| |
| * ``MathFunctions/CMakeLists.txt`` |
| |
| Getting Started |
| --------------- |
| |
| Complete ``TODO 4`` and ``TODO 5``. No code changes to the ``MathFunctions`` |
| implementation are necessary, as these have already been provided. |
| |
| Build and Run |
| ------------- |
| |
| We need only rebuild the tutorial. |
| |
| .. code-block:: console |
| |
| cmake --build build |
| |
| .. note:: |
| If a check fails and you think it should succeed, you will need to clear the |
| CMake Cache by deleting the ``CMakeCache.txt`` file. CMake will not rerun |
| compile checks on subsequent runs if it has a cached result. |
| |
| In the output of the configuration step we should observe CMake checking if the |
| provided source code compiles, which will be reported under the variable name |
| we provided to ``check_source_compiles()``. |
| |
| .. code-block:: console |
| |
| -- Performing Test HAS_GNU_BUILTIN |
| -- Performing Test HAS_GNU_BUILTIN - Success |
| |
| If the builtins are available on your compiler, verify the ``Tutorial`` output |
| contains the message about using GNU-builting. Conversely, if the builtins are |
| not available you should see the previous behavior from ``Tutorial``. |
| |
| Solution |
| -------- |
| |
| First we include and use the ``CheckSourceCompiles`` module, verifying the |
| provided source code can be built. |
| |
| .. |
| pygments doesn't like the [=[ <string> ]=] literals in the following |
| literalinclude, so use :language: none |
| |
| .. raw:: html |
| |
| <details><summary>TODO 4: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/MathFunctions/CMakeLists.txt |
| :caption: TODO 4: MathFunctions/CMakeLists.txt |
| :name: MathFunctions/CMakeLists.txt-check-source-compiles |
| :language: none |
| :start-at: include(CheckSourceCompiles |
| :end-at: HAS_GNU_BUILTIN |
| :append: ) |
| |
| .. raw:: html |
| |
| </details> |
| |
| Then we use the result of the check to conditionally set a compile definition |
| on ``MathFunctions``. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 5: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/MathFunctions/CMakeLists.txt |
| :caption: TODO 5: MathFunctions/CMakeLists.txt |
| :name: MathFunctions/CMakeLists.txt-define-use-gnu-builtin |
| :language: cmake |
| :start-at: if(HAS_GNU_BUILTIN) |
| :end-at: endif() |
| |
| .. raw:: html |
| |
| </details> |
| |
| Exercise 3 - Check Interprocedural Optimization |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| |
| Interprocedural and link time optimizations can provide significant performance |
| improvements to some software. CMake has the capacity to check for the |
| availability of IPO flags via :module:`CheckIPOSupported`. |
| |
| .. code-block:: cmake |
| |
| include(CheckIPOSupported) |
| check_ipo_supported() # fatal error if IPO is not supported |
| set_target_properties(MyApp |
| PROPERTIES |
| INTERPROCEDURAL_OPTIMIZATION TRUE |
| ) |
| |
| .. note:: |
| There a couple important caveats with regard to in-project IPO configuration: |
| |
| * CMake does not know about every IPO/LTO flag on every compiler, better |
| results can often be achieved with individual tuning for a known toolchain. |
| * Setting the :prop_tgt:`INTERPROCEDURAL_OPTIMIZATION` property on a target |
| does not alter any of the targets it links to, or dependencies from other |
| projects. IPO can only "see" into other targets which are also compiled |
| appropriately. |
| |
| For these reasons, serious consideration should be given to manually setting |
| up IPO/LTO flags across all projects in the dependency tree via external |
| mechanisms (presets, :option:`-D <cmake -D>` flags, |
| :manual:`toolchain files <cmake-toolchains(7)>`, etc) instead of in-project |
| control. |
| |
| However, especially for extremely large projects, it can be useful to have |
| an in-project mechanism to use IPO whenever it is available. |
| |
| Goal |
| ---- |
| |
| Enable IPO for the entire tutorial project when it is available from the |
| toolchain. |
| |
| Helpful Resources |
| ----------------- |
| |
| * :module:`CheckIPOSupported` |
| * :variable:`CMAKE_INTERPROCEDURAL_OPTIMIZATION` |
| |
| Files to Edit |
| ------------- |
| |
| * ``CMakeLists.txt`` |
| |
| Getting Started |
| --------------- |
| |
| Continue editing the files in ``Step6``. Complete ``TODO 6`` and ``TODO 7``. |
| |
| Build and Run |
| ------------- |
| |
| We need only rebuild the tutorial. |
| |
| .. code-block:: console |
| |
| cmake --build build |
| |
| If IPO is unavailable, we will see an error message during configuration. |
| Otherwise nothing will change. |
| |
| .. note:: |
| Regardless of the result of the IPO check, we shouldn't expect any change |
| in behavior from ``Tutorial`` or ``MathFunctions``. |
| |
| Solution |
| -------- |
| |
| The first ``TODO`` is easy, we add another option to our project. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 6: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/CMakeLists.txt |
| :caption: TODO 6: CMakeLists.txt |
| :name: CMakeLists.txt-enable-ipo |
| :language: cmake |
| :start-at: option(TUTORIAL_ENABLE_IPO |
| :end-at: option(TUTORIAL_ENABLE_IPO |
| |
| .. raw:: html |
| |
| </details> |
| |
| The next step is involved, however the documentation for :module:`CheckIPOSupported` |
| has an almost complete example of what we need to do. The only difference is |
| we are going to enable IPO project-wide instead of for a single target. |
| |
| .. raw:: html |
| |
| <details><summary>TODO 7: Click to show/hide answer</summary> |
| |
| .. literalinclude:: Step7/CMakeLists.txt |
| :caption: TODO 7: CMakeLists.txt |
| :name: CMakeLists.txt-check-ipo |
| :language: cmake |
| :start-at: if(TUTORIAL_ENABLE_IPO) |
| :end-at: endif() |
| :append: endif() |
| |
| .. raw:: html |
| |
| </details> |
| |
| .. note:: |
| Normally we have discouraged setting ``CMAKE_`` variables inside the project. |
| Here, we are controlling that behavior with an :command:`option()`. This |
| allows packagers to opt-out of our override. This is an imperfect, but |
| acceptable solution to situations where we want to provide options to control |
| project-wide behavior controlled by ``CMAKE_`` variables. |