In-Depth System Introspection.rst 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. Step 6: In-Depth System Introspection
  2. =====================================
  3. In order to discover information about the system environment and the toolchain,
  4. CMake will often compile small test programs to verify the availability of
  5. compiler flags, headers, and builtins or other language constructs.
  6. In this step, we will take advantage of the same test program mechanisms that
  7. CMake uses in our own project code.
  8. Background
  9. ^^^^^^^^^^
  10. An old trick going back to the oldest days of configuration and build systems
  11. is to verify the availability of some feature by compiling a small program
  12. which uses that feature.
  13. CMake makes this unnecessary for many contexts. As we will address in later
  14. steps, if CMake can find a library dependency, we can rely on it having all
  15. the facilities (headers, code generators, test utilities, etc) we expect it to
  16. have. Conversely, if CMake can't find a dependency, attempting to use the
  17. dependency anyway will almost certainly fail.
  18. However, there are other kinds of information about the toolchain which CMake
  19. doesn't communicate readily. For these advanced cases, we can write our own
  20. test programs and compile commands to check for availability.
  21. CMake provides modules to simplify these checks. These are documented at
  22. :manual:`cmake-modules(7)`. Any module that begins with ``Check`` is a system
  23. introspection module we can use to interrogate the toolchain and system
  24. environment. Some notable ones include:
  25. ``CheckIncludeFiles``
  26. Check one or more C/C++ header files.
  27. ``CheckCompilerFlag``
  28. Check whether the compiler supports a given flag.
  29. ``CheckSourceCompiles``
  30. Checks whether source code can be built for a given language.
  31. ``CheckIPOSupported``
  32. Check whether the compiler supports interprocedural optimization (IPO/LTO).
  33. Exercise 1 - Check Include File
  34. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  35. A fast and easy check to perform is if a given header file is available on
  36. a certain platform, for which CMake provides :module:`CheckIncludeFiles`. This
  37. is most appropriate for system and intrinsic headers, which may not be provided
  38. by a specific package by are expected to be available in many build environments.
  39. .. code-block:: cmake
  40. include(CheckIncludeFiles)
  41. check_include_files(sys/socket.h HAVE_SYS_SOCKET_H LANGUAGE CXX)
  42. .. note::
  43. These functions are not immediately available in CMake, they must be added via
  44. :command:`include`'ing their associated module (aka, a CMakeLang file). Many
  45. modules live inside CMake's own ``Modules`` folder. This built-in ``Modules``
  46. folder is one of the places CMake searches when evaluating an :command:`include`
  47. command. You can think of these modules like standard library headers, they're
  48. expected to be available.
  49. Once a header file is known to exist, we can communicate that to our code using
  50. the same mechanisms of conditionals and target commands already covered.
  51. Goal
  52. ----
  53. Check if the x86 SSE2 intrinsic header is available, and if so use it to
  54. improve ``mathfunctions::sqrt``.
  55. Helpful Resources
  56. -----------------
  57. * :module:`CheckIncludeFiles`
  58. * :command:`target_compile_definitions`
  59. Files to Edit
  60. -------------
  61. * ``MathFunctions/CMakeLists.txt``
  62. * ``MathFunctions/MathFunctions.cxx``
  63. Getting Started
  64. ---------------
  65. The ``Help/guide/tutorial/Step6`` directory contains the complete, recommended
  66. solution to ``Step5`` and relevant ``TODOs`` for this step. It also contains
  67. specialized implementations of the ``sqrt`` function for various conditions,
  68. which you will find in ``MathFunctions/MathFunctions.cxx``.
  69. Complete ``TODO 1`` through ``TODO 3``. Note that some ``#ifdef`` directives
  70. have already been added to the library, which will change its operation as we
  71. work through the step.
  72. Build and Run
  73. -------------
  74. We can use our usual commands to configure.
  75. .. code-block:: console
  76. cmake --preset tutorial
  77. cmake --build build
  78. In the output of the configuration step we should observe CMake checking for
  79. the ``emmintrin.h`` header.
  80. .. code-block:: console
  81. -- Looking for include file emmintrin.h
  82. -- Looking for include file emmintrin.h - found
  83. If the header is available on your system, verify the ``Tutorial`` output
  84. contains the message about using SSE2. Conversely, if the header is not
  85. available you should see the usual behavior from ``Tutorial``.
  86. Solution
  87. --------
  88. First we include and use the ``CheckIncludeFiles`` module, verifying the
  89. ``emmintrin.h`` header is available.
  90. .. raw:: html
  91. <details><summary>TODO 1: Click to show/hide answer</summary>
  92. .. literalinclude:: Step7/MathFunctions/CMakeLists.txt
  93. :caption: TODO 1: MathFunctions/CMakeLists.txt
  94. :name: MathFunctions/CMakeLists.txt-check-include-files
  95. :language: cmake
  96. :start-at: include(CheckIncludeFiles
  97. :end-at: check_include_files(
  98. .. raw:: html
  99. </details>
  100. Then we use the result of the check to conditionally set a compile definition
  101. on ``MathFunctions``.
  102. .. raw:: html
  103. <details><summary>TODO 2: Click to show/hide answer</summary>
  104. .. literalinclude:: Step7/MathFunctions/CMakeLists.txt
  105. :caption: TODO 2: MathFunctions/CMakeLists.txt
  106. :name: MathFunctions/CMakeLists.txt-define-use-sse2
  107. :language: cmake
  108. :start-at: if(HAS_EMMINTRIN)
  109. :end-at: endif()
  110. .. raw:: html
  111. </details>
  112. Finally we can conditionally include the header in the ``MathFunctions`` library.
  113. .. raw:: html
  114. <details><summary>TODO 3: Click to show/hide answer</summary>
  115. .. literalinclude:: Step7/MathFunctions/MathFunctions.cxx
  116. :caption: TODO 3: MathFunctions/MathFunctions.cxx
  117. :name: MathFunctions/MathFunctions.cxx-include-sse2
  118. :language: c++
  119. :start-at: #ifdef TUTORIAL_USE_SSE2
  120. :end-at: #endif
  121. .. raw:: html
  122. </details>
  123. Exercise 2 - Check Source Compiles
  124. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  125. Sometimes it is insufficient to merely check for a header. This is especially
  126. true when no header is available to check, such is the case with
  127. compiler-builtins. For these scenarios we have :module:`CheckSourceCompiles`.
  128. .. code-block:: cmake
  129. include(CheckSourceCompiles)
  130. check_source_compiles(CXX
  131. "
  132. int main() {
  133. int a, b, c;
  134. __builtin_add_overflow(a, b, &c);
  135. }
  136. "
  137. HAS_CHECKED_ADDITION
  138. )
  139. .. note::
  140. By default :module:`CheckSourceCompiles` builds and links an executable. The
  141. code to be check must provide a valid ``int main()`` in order to succeed.
  142. After performing the check, this system introspection can be applied identically
  143. to how we discussed with header files.
  144. Goal
  145. ----
  146. Check if the GNU SSE2 builtins are available, and if so use them to improve
  147. ``mathfunctions::sqrt``.
  148. Helpful Resources
  149. -----------------
  150. * :module:`CheckSourceCompiles`
  151. * :command:`target_compile_definitions`
  152. Files to Edit
  153. -------------
  154. * ``MathFunctions/CMakeLists.txt``
  155. Getting Started
  156. ---------------
  157. Complete ``TODO 4`` and ``TODO 5``. No code changes to the ``MathFunctions``
  158. implementation are necessary, as these have already been provided.
  159. Build and Run
  160. -------------
  161. We need only rebuild the tutorial.
  162. .. code-block:: console
  163. cmake --build build
  164. .. note::
  165. If a check fails and you think it should succeed, you will need to clear the
  166. CMake Cache by deleting the ``CMakeCache.txt`` file. CMake will not rerun
  167. compile checks on subsequent runs if it has a cached result.
  168. In the output of the configuration step we should observe CMake checking if the
  169. provided source code compiles, which will be reported under the variable name
  170. we provided to ``check_source_compiles()``.
  171. .. code-block:: console
  172. -- Performing Test HAS_GNU_BUILTIN
  173. -- Performing Test HAS_GNU_BUILTIN - Success
  174. If the builtins are available on your compiler, verify the ``Tutorial`` output
  175. contains the message about using GNU-builting. Conversely, if the builtins are
  176. not available you should see the previous behavior from ``Tutorial``.
  177. Solution
  178. --------
  179. First we include and use the ``CheckSourceCompiles`` module, verifying the
  180. provided source code can be built.
  181. ..
  182. pygments doesn't like the [=[ <string> ]=] literals in the following
  183. literalinclude, so use :language: none
  184. .. raw:: html
  185. <details><summary>TODO 4: Click to show/hide answer</summary>
  186. .. literalinclude:: Step7/MathFunctions/CMakeLists.txt
  187. :caption: TODO 4: MathFunctions/CMakeLists.txt
  188. :name: MathFunctions/CMakeLists.txt-check-source-compiles
  189. :language: none
  190. :start-at: include(CheckSourceCompiles
  191. :end-at: HAS_GNU_BUILTIN
  192. :append: )
  193. .. raw:: html
  194. </details>
  195. Then we use the result of the check to conditionally set a compile definition
  196. on ``MathFunctions``.
  197. .. raw:: html
  198. <details><summary>TODO 5: Click to show/hide answer</summary>
  199. .. literalinclude:: Step7/MathFunctions/CMakeLists.txt
  200. :caption: TODO 5: MathFunctions/CMakeLists.txt
  201. :name: MathFunctions/CMakeLists.txt-define-use-gnu-builtin
  202. :language: cmake
  203. :start-at: if(HAS_GNU_BUILTIN)
  204. :end-at: endif()
  205. .. raw:: html
  206. </details>
  207. Exercise 3 - Check Interprocedural Optimization
  208. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  209. Interprocedural and link time optimizations can provide significant performance
  210. improvements to some software. CMake has the capacity to check for the
  211. availability of IPO flags via :module:`CheckIPOSupported`.
  212. .. code-block:: cmake
  213. include(CheckIPOSupported)
  214. check_ipo_supported() # fatal error if IPO is not supported
  215. set_target_properties(MyApp
  216. PROPERTIES
  217. INTERPROCEDURAL_OPTIMIZATION TRUE
  218. )
  219. .. note::
  220. There a couple important caveats with regard to in-project IPO configuration:
  221. * CMake does not know about every IPO/LTO flag on every compiler, better
  222. results can often be achieved with individual tuning for a known toolchain.
  223. * Setting the :prop_tgt:`INTERPROCEDURAL_OPTIMIZATION` property on a target
  224. does not alter any of the targets it links to, or dependencies from other
  225. projects. IPO can only "see" into other targets which are also compiled
  226. appropriately.
  227. For these reasons, serious consideration should be given to manually setting
  228. up IPO/LTO flags across all projects in the dependency tree via external
  229. mechanisms (presets, :option:`-D <cmake -D>` flags,
  230. :manual:`toolchain files <cmake-toolchains(7)>`, etc) instead of in-project
  231. control.
  232. However, especially for extremely large projects, it can be useful to have
  233. an in-project mechanism to use IPO whenever it is available.
  234. Goal
  235. ----
  236. Enable IPO for the entire tutorial project when it is available from the
  237. toolchain.
  238. Helpful Resources
  239. -----------------
  240. * :module:`CheckIPOSupported`
  241. * :variable:`CMAKE_INTERPROCEDURAL_OPTIMIZATION`
  242. Files to Edit
  243. -------------
  244. * ``CMakeLists.txt``
  245. Getting Started
  246. ---------------
  247. Continue editing the files in ``Step6``. Complete ``TODO 6`` and ``TODO 7``.
  248. Build and Run
  249. -------------
  250. We need only rebuild the tutorial.
  251. .. code-block:: console
  252. cmake --build build
  253. If IPO is unavailable, we will see an error message during configuration.
  254. Otherwise nothing will change.
  255. .. note::
  256. Regardless of the result of the IPO check, we shouldn't expect any change
  257. in behavior from ``Tutorial`` or ``MathFunctions``.
  258. Solution
  259. --------
  260. The first ``TODO`` is easy, we add another option to our project.
  261. .. raw:: html
  262. <details><summary>TODO 6: Click to show/hide answer</summary>
  263. .. literalinclude:: Step7/CMakeLists.txt
  264. :caption: TODO 6: MathFunctions/CMakeLists.txt
  265. :name: CMakeLists.txt-enable-ipo
  266. :language: cmake
  267. :start-at: option(TUTORIAL_ENABLE_IPO
  268. :end-at: option(TUTORIAL_ENABLE_IPO
  269. .. raw:: html
  270. </details>
  271. The next step is involved, however the documentation for :module:`CheckIPOSupported`
  272. has an almost complete example of what we need to do. The only difference is
  273. we are going to enable IPO project-wide instead of for a single target.
  274. .. raw:: html
  275. <details><summary>TODO 7: Click to show/hide answer</summary>
  276. .. literalinclude:: Step7/CMakeLists.txt
  277. :caption: TODO 7: CMakeLists.txt
  278. :name: CMakeLists.txt-check-ipo
  279. :language: cmake
  280. :start-at: if(TUTORIAL_ENABLE_IPO)
  281. :end-at: endif()
  282. :append: endif()
  283. .. raw:: html
  284. </details>
  285. .. note::
  286. Normally we have discouraged setting ``CMAKE_`` variables inside the project.
  287. Here, we are controlling that behavior with an :command:`option()`. This
  288. allows packagers to opt-out of our override. This is an imperfect, but
  289. acceptable solution to situations where we want to provide options to control
  290. project-wide behavior controlled by ``CMAKE_`` variables.