2017年4月28日 星期五

[Quick Tutorial] How to USE LLVM with CMake

Perhaps I hang around in the LLVM developers world for too long, that I almost forget that LLVM is a framework consist of libraries - libraries for application developers to USE.

Recently I found that there are some developers who use LLVM (and related) tools like clang and llc, have some experience surfing in the LLVM codebase, and even played around with some small hacks - But they're not sure how to USE LLVM as normal libraries during application developments.

Of course, you can just use the similar semantic below to build your application with LLVM.
g++ -I<LLVM Include Path> -L<LLVM Library Path> foo.cc -o foo -lLLVMSomeLib
But well, It's 21st, we got CMake to make C++ building process more neat and easier. And since LLVM had migrated to CMake as its default and only build system since version 3.7, it provides some great CMake modules for application developers to integrate LLVM into their projects faster and error free. Here are two examples for small and large projects.

Small Projects

I prefer to say "small executable" rather than small project, actually. Because just like what I'd described, this example is suitable for scenario that you just need to embed LLVM in single executable. Maybe a scratch program to illustrate how certain components work or you're using the utilities libraries within LLVM, command line library for example, in your executable. 

First, in your CMakeLists.txt, define LLVM_DIR variable with path to the cmake module folder within LLVM install dir(Yeah, I know the variable name is pretty confused). Usually the path is <LLVM Install Folder>/lib/cmake/llvm. After that, find the LLVM package:
find_package(LLVM REQUIRED CONFIG) 
Then there are several variables been defined for us to use. Including the include folder path:
include_directories(${LLVM_INCLUDE_DIRS}) 
And some compiler definitions:
add_definitions(${LLVM_DEFINITION})
What about libraries? Here comes the magic, a convenient cmake function is provided to resolve the burden of library names and path:
llvm_map_components_to_libnames(_LLVM_LIBS support core irreader) 
_LLVM_LIBS variable is used as output variable, so it would store all the path to the given component libraries. Support, Core and IRReader components in this case, we just need to give the component names, in lowercase, then llvm_map_components_to_libnames would translate it into LLVMCore, LLVMSupport etc.

Finally, we link those libraries to our target:
add_executable(fooProgram foo.cpp bar.cpp) target_link_libraries(fooProgram ${_LLVM_LIBS})
Here is the full code list:
cmake_minimum_required(VERSION 3.6)
project(LLVMExperiments CXX)

set(CMAKE_CXX_STANDARD 11)

set(LLVM_DIR /usr/local/opt/llvm-clang/4.0/lib/cmake/llvm)
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Use LLVM ${LLVM_PACKAGE_VERSION}")
include_directories(${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITION})

# LLVM libraries are static linking by default
# So pick only the component libraries you need
# in case your final executable becomes large in size
llvm_map_components_to_libnames(_LLVM_LIBS support core analysis irreader)

set(SOURCE_FILES
        main.cpp)
add_executable(LLVMExperiments ${SOURCE_FILES})
target_link_libraries(LLVMExperiments ${_LLVM_LIBS})

Large Projects / Loadable Components

As the section title indicates, this method is the only way I figure out how to build LLVM loadable components, especially LLVM Pass, with CMake. Also, this method is strongly recommended if you want to develop tools or components that may merge into upstream LLVM codebase in the future.

The first thing you may need to change is the project's folder structure. We're going to create a subdirectory to accommodate main code for your tools, and create another CMakeLists.txt within it. So there would be two cmake build scripts: <Project Folder>/CMakeLists.txt and <Project Folder>/<Tool Folder>/CMakeLists.txt.

Let's look at the first build script, the one in upper directory. In the previous section, "Small Project", some of you may bump into a problem that the LLVM library you use isn't built with RTTI, so you need to add "-fno-rtti" flags to compiler option in your cmake build script. But you want to make the cmake build script more general, so you add the following code:
if (NOT ${LLVM_ENABLE_RTTI})
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
endif()

Well, that's the RTTI case...what about other similar flags? It's pretty cumbersome to add bunch of if-else statements for them. Here comes the solution:
list(APPEND CMAKE_MODULE_PATH ${LLVM_CMAKE_DIR})

set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)
set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib)

include(HandleLLVMOptions)

Add the lines above after the LLVM package-finding statement. LLVM_RUNTIME_OUTPUT_INTDIR and LLVM_LIBRARY_OUTPUT_INTDIR variables are necessary for HandleLLVMOptions module, but feel free to set them with whatever path you want to output the result binaries and libraries.

Finally in our upper directory build script, add two additional statements:
include(AddLLVM)
add_subdirectory(MyToolSubDirectory)

MyToolSubDirectory is the subdirectory name for your tool, library or loadable component.
Here is the full code list for the build script in upper directory:

cmake_minimum_required(VERSION 3.7)
project(LLVMExperiments2 CXX)

set(CMAKE_CXX_STANDARD 11)

set(LLVM_DIR /usr/local/opt/llvm-clang/current/lib/cmake/llvm)
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Using LLVM version ${LLVM_PACKAGE_VERSION}")

list(APPEND CMAKE_MODULE_PATH ${LLVM_CMAKE_DIR})

set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)
set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib)

include(HandleLLVMOptions)
include(AddLLVM)

add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})

add_subdirectory(MyToolSubDirectory)

Let's go forward to the subdirectory. This subdirectory is where your code resides. The cmake build script here, however, is pretty simple. If you're building executable, here are the only few statements you need to add:

set(LLVM_LINK_COMPONENTS
        Support
        Core
        IRReader
        )

add_llvm_executable(MyExecutable
        main.cpp
        foo.cpp
        )

MyExecutable is the name for your executable. Defining LLVM_LINK_COMPONENTS variable is obviously adding component libraries to link against your executable.

For loadable components, the build script is even simpler:

add_llvm_loadable_module(MyLLVMPass
        pass.cpp
        foo.cpp
        )

That's it!

Here is a simple LLVM pass example I wrote for your reference:
https://github.com/mshockwave/LLVM-Sample-Pass