In the previous chapter, we saw that LLVM's build system is a behemoth: it contains hundreds of build files with thousands of interleaving build dependencies. Not to mention, it contains targets that require custom build instructions for heterogeneous source files. These complexities drove LLVM to adopt some advanced build system features and, more importantly, a more structural build system design. In this chapter, our goal will be to learn about some important directives for the sake of writing more concise and expressive build files when doing both in-tree and out-of-tree LLVM developments.
In this chapter, we will cover the following main topics:
Similar to Chapter 1, Saving Resources When Building LLVM, you might want to have a copy of LLVM built from its source. Optionally, since this chapter will touch on quite a lot of CMake build files, you may wish to prepare a syntax highlighting plugin for CMakeLists.txt (for example, VSCode's CMake Tools plugin). All major IDEs and editors should have it off-the-shelf. Also, familiarity with basic CMakeLists.txt syntax is preferable.
All the code examples in this chapter can be found in this book's GitHub repository: https://github.com/PacktPublishing/LLVM-Techniques-Tips-and-Best-Practices/tree/main/Chapter02.
LLVM has switched to CMake from GNU autoconf due to higher flexibility in terms of choosing underlying build systems. Ever since, LLVM has come up with many custom CMake functions, macros, and rules to optimize its own usage. This section will give you an overview of the most important and frequently used ones among them. We will learn how and when to use them.
Libraries are the building blocks of the LLVM framework. However, when writing CMakeLists.txt for a new library, you shouldn't use the normal add_library directive that appears in normal CMakeLists.txt files, as follows:
# In an in-tree CMakeLists.txt file…
add_library(MyLLVMPass SHARED
MyPass.cpp) # Do NOT do this to add a new LLVM library
There are several drawbacks of using the vanilla add_library here, as follows:
Therefore, you should always use the add_llvm_component_library CMake function shown here:
# In a CMakeLists.txt
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
Here, LLVMFancyOpt is the final library name and FancyOpt.cpp is the source file.
In regular CMake scripts, you can use target_link_libraries to designate a given target's library dependencies, and then use add_dependencies to assign dependencies among different build targets to create explicit build orderings. There is an easier way to do those tasks when you're using LLVM's custom CMake functions to create library targets.
By using the LINK_COMPONENTS argument in add_llvm_component_library (or add_llvm_library, which is the underlying implementation of the former one), you can designate the target's linked components:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp
LINK_COMPONENTS
Analysis ScalarOpts)
Alternatively, you can do the same thing with the LLVM_LINK_COMPONENTS variable, which is defined before the function call:
set(LLVM_LINK_COMPONENTS
Analysis ScalarOpts)
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
Component libraries are nothing but normal libraries with a special meaning when it comes to the LLVM building blocks you can use. They're also included in the gigantic libLLVM library if you choose to build it. The component names are slightly different from the real library names. If you need the mapping from component names to library names, you can use the following CMake function:
llvm_map_components_to_libnames(output_lib_names
<list of component names>)
If you want to directly link against a normal library (the non-LLVM component one), you can use the LINK_LIBS argument:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp
LINK_LIBS
${BOOST_LIBRARY})
To assign general build target dependencies to a library target (equivalent to add_dependencies), you can use the DEPENDS argument:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp
DEPENDS
intrinsics_gen)
intrinsics_gen is a common target representing the procedure of generating header files containing LLVM intrinsics definitions.
Many LLVM custom CMake functions have a pitfall that involves source file detection. Let's say you have a directory structure like this:
/FancyOpt
|___ FancyOpt.cpp
|___ AggressiveFancyOpt.cpp
|___ CMakeLists.txt
Here, you have two source files, FancyOpt.cpp and AggressiveFancyOpt.cpp. As their names suggest, FancyOpt.cpp is the basic version of this optimization, while AggressiveFancyOpt.cpp is an alternative, more aggressive version of the same functionality. Naturally, you will want to split them into separate libraries so that users can choose if they wish to include the more aggressive one in their normal workload. So, you might write a CMakeLists.txt file like this:
# In /FancyOpt/CMakeLists.txt
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
add_llvm_component_library(LLVMAggressiveFancyOpt
AggressiveFancyOpt.cpp)
Unfortunately, this would generate error messages telling you something to the effect of Found unknown source AggressiveFancyOpt.cpp … when processing the first add_llvm_component_library statement.
LLVM's build system enforces a stricter rule to make sure that all C/C++ source files in the same folder are added to the same library, executable, or plugin. To fix this, it is necessary to split either file into a separate folder, like so:
/FancyOpt
|___ FancyOpt.cpp
|___ CMakeLists.txt
|___ /AggressiveFancyOpt
|___ AggressiveFancyOpt.cpp
|___ CMakeLists.txt
In /FancyOpt/CMakeLists.txt, we have the following:
add_llvm_component_library(LLVMFancyOpt
FancyOpt.cpp)
add_subdirectory(AggressiveFancyOpt)
Finally, in /FancyOpt/AggressiveFancyOpt/CMakeLists.txt, we have the following:
add_llvm_component_library(LLVMAggressiveFancyOpt
AggressiveFancyOpt.cpp)
These are the essentials of adding build targets for (component) libraries using LLVM's custom CMake directives. In the next two sections, we will show you how to add executable and Pass plugin build targets using a different set of LLVM-specific CMake directives.
Similar to add_llvm_component_library, to add a new executable target, we can use add_llvm_executable or add_llvm_tool:
add_llvm_tool(myLittleTool
MyLittleTool.cpp)
These two functions have the same syntax. However, only targets created by add_llvm_tool will be included in the installations. There is also a global CMake variable, LLVM_BUILD_TOOLS, that enables/disables those LLVM tool targets.
Both functions can also use the DEPENDS argument to assign dependencies, similar to add_llvm_library, which we introduced earlier. However, you can only use the LLVM_LINK_COMPONENTS variable to designate components to link.
While we will cover Pass plugin development later in this book, adding a build target for a Pass plugin couldn't be any easier than now (compared to earlier LLVM versions, which were still using add_llvm_library with some special arguments). We can simply use the following command:
add_llvm_pass_plugin(MyPass
HelloWorldPass.cpp)
The LINK_COMPONENTS, LINK_LIBS, and DEPENDS arguments are also available here, with the same usages and functionalities as in add_llvm_component_library.
These are some of the most common and important LLVM-specific CMake directives. Using these directives can not only make your CMake code more concise but also help synchronize it with LLVM's own build system, in case you want to do some in-tree development. In the next section, we will show you how to integrate LLVM into an out-of-tree CMake project, and leverage the knowledge we learned in this chapter.
In-tree versus out-of-tree development
In this book, in-tree development means contributing code directly to the LLVM project, such as fixing LLVM bugs or adding new features to the existing LLVM libraries. Out-of-tree development, on the other hand, either represents creating extensions for LLVM (writing an LLVM pass, for example) or using LLVM libraries in some other projects (using LLVM's code generation libraries to implement your own programming language, for example).
Implementing your features in an in-tree project is good for prototyping, since most of the infrastructure is already there. However, there are many scenarios where pulling the entire LLVM source tree into your code base is not the best idea, compared to creating an out-of-tree project and linking it against the LLVM libraries. For example, you only want to create a small code refactoring tool using LLVM's features and open source it on GitHub, so telling developers on GitHub to download a multi-gigabyte LLVM source tree along with your little tool might not be a pleasant experience.
There are at least two ways to configure out-of-tree projects to link against LLVM:
Both approaches help you sort out all the details, including header files and library paths. However, the latter creates more concise and readable CMake scripts, which is preferable for projects that are already using CMake. This section will show the essential steps of using LLVM's CMake modules to integrate it into an out-of-tree CMake project.
First, we need to prepare an out-of-tree (C/C++) CMake project. The core CMake functions/macros we discussed in the previous section will help us work our way through this. Let's look at our steps:
project(MagicCLITool)
set(SOURCE_FILES
main.cpp)
add_executable(magic-cli
${SOURCE_FILES})
Regardless of whether you're trying to create a project generating executable, just like the one we saw in the preceding code block, or other artifacts such as libraries or even LLVM Pass plugins, the biggest question now is how to get include path, as well as library path.
project(MagicCLITool)
find_package(LLVM REQUIRED CONFIG)
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})
…
To make the find_package trick work, you need to supply the LLVM_DIR CMake variable while invoking the CMake command for this project:
$ cmake -DLLVM_DIR=<LLVM install path>/lib/cmake/llvm …
Make sure it's pointing to the lib/cmake/llvm subdirectory under LLVM install path.
The following snippet imports LLVM's CMake module (more specifically, the AddLLVM CMake module), which contains those LLVM-specific functions/macros that we introduced in the previous section:
find_package(LLVM REQUIRED CONFIG)
…
list(APPEND CMAKE_MODULE_PATH ${LLVM_CMAKE_DIR})
include(AddLLVM)
find_package(LLVM REQUIRED CONFIG)
…
include(AddLLVM)
set(LLVM_LINK_COMPONENTS
Support
Analysis)
add_llvm_executable(magic-cli
main.cpp)
find_package(LLVM REQUIRED CONFIG)
…
include(AddLLVM)
add_llvm_library(MyMagicLibrary
lib.cpp
LINK_COMPONENTS
Support Analysis)
find_package(LLVM REQUIRED CONFIG)
…
include(AddLLVM)
add_llvm_pass_plugin(MyMagicPass
ThePass.cpp)
find_package(LLVM REQUIRED CONFIG)
…
add_definitions(${LLVM_DEFINITIONS})
if(NOT ${LLVM_ENABLE_RTTI})
# For non-MSVC compilers
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
endif()
add_llvm_xxx(source.cpp)
This is especially true for the RTTI part because, by default, LLVM is not built with RTTI support, but normal C++ applications are. A compilation error will be thrown if there is an RTTI mismatch between your code and LLVM's libraries.
Despite the convenience of developing inside LLVM's source tree, sometimes, enclosing the entire LLVM source in your project might not be feasible. So, instead, we must create an out-of-tree project and integrate LLVM as a library. This section showed you how to integrate LLVM into your CMake-based out-of-tree projects and make good use of the LLVM-specific CMake directives we learned about in the Exploring a glossary of LLVM's important CMake directives section.
This chapter dug deeper into LLVM's CMake build system. We saw how to use LLVM's own CMake directives to write concise and effective build scripts, for both in-tree and out-of-tree development. Learning these CMake skills can make your LLVM development more efficient and provide you with more options to engage LLVM features with other existing code bases or custom logic.
In the next chapter, we will introduce another important infrastructure in the LLVM project known as the LLVM LIT, which is an easy-to-use yet general framework for running various kinds of tests.
3.16.81.94