Compilation Databases#

Before it can analyze a code base, CBI needs to know how each source file is compiled. Just like a compiler, CBI requires a full list of include paths, macro definitions and other options in order to identify which code is used by each platform. Rather than require all of this information to be specified manually, CBI reads it from a compilation database.

Generating a Compilation Database#

Since our sample code base is already set up with a CMakeLists.txt file, we can ask CMake to generate the compilation database for us with the CMAKE_EXPORT_COMPILE_COMMANDS option:

cmake_minimum_required(VERSION 3.5)
project(tutorial)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(SOURCES main.cpp third-party/library.cpp)

option(GPU_OFFLOAD "Enable GPU offload." OFF)
if (GPU_OFFLOAD)
    add_definitions("-D GPU_OFFLOAD=1")
    list(APPEND SOURCES gpu/foo.cpp)
else()
    list(APPEND SOURCES cpu/foo.cpp)
endif()

add_executable(tutorial ${SOURCES})

Important

For projects that don’t use CMake, we can use Bear to intercept the commands generated by other build systems (such as GNU makefiles). Other build systems and tools that produce compilation databases should also be compatible.

CPU Compilation Commands#

Let’s start by running CMake without the GPU_OFFLOAD option enabled, to obtain a compilation database for the CPU:

$ mkdir build-cpu
$ cmake ../
$ ls

CMakeCache.txt  CMakeFiles  Makefile  cmake_install.cmake  compile_commands.json

This compile_commands.json file includes all the commands required to build the code, corresponding to the commands that would be executed if we were to actually run make.

Attention

CMake generates compilation databases when the cmake command is executed, allowing us to generate compilation databases without also building the application. Other tools (like Bear) may require a build.

In this case, it contains:

[
{
  "directory": "/home/username/src/build-cpu",
  "command": "/usr/bin/c++ -o CMakeFiles/tutorial.dir/main.cpp.o -c /home/username/src/main.cpp",
  "file": "/home/username/src/main.cpp"
},
{
  "directory": "/home/username/src/build-cpu",
  "command": "/usr/bin/c++ -o CMakeFiles/tutorial.dir/third-party/library.cpp.o -c /home/username/src/third-party/library.cpp",
  "file": "/home/username/src/third-party/library.cpp"
},
{
  "directory": "/home/username/src/build-cpu",
  "command": "/usr/bin/c++ -o CMakeFiles/tutorial.dir/cpu/foo.cpp.o -c /home/username/src/cpu/foo.cpp",
  "file": "/home/username/src/cpu/foo.cpp"
}
]

GPU Compilation Commands#

Repeating the exercise with GPU_OFFLOAD enabled gives us a different compilation database for the GPU.

Warning

The GPU_OFFLOAD option is specific to this CMakeLists.txt file, and isn’t something provided by CMake. Understanding how to build an application for a specific target platform is beyond the scope of this tutorial.

As expected, we can see that the compilation database refers to gpu.cpp instead of cpu.cpp, and that the GPU_OFFLOAD macro is defined as part of each compilation command:

[
{
  "directory": "/home/username/src/build-gpu",
  "command": "/usr/bin/c++ -D GPU_OFFLOAD=1 -o CMakeFiles/tutorial.dir/main.cpp.o -c /home/username/src/main.cpp",
  "file": "/home/username/src/main.cpp"
},
{
  "directory": "/home/username/src/build-gpu",
  "command": "/usr/bin/c++ -D GPU_OFFLOAD=1 -o CMakeFiles/tutorial.dir/third-party/library.cpp.o -c /home/username/src/third-party/library.cpp",
  "file": "/home/username/src/third-party/library.cpp"
},
{
  "directory": "/home/username/src/build-gpu",
  "command": "/usr/bin/c++ -D GPU_OFFLOAD=1 -o CMakeFiles/tutorial.dir/gpu/foo.cpp.o -c /home/username/src/gpu/foo.cpp",
  "file": "/home/username/src/gpu/foo.cpp"
}
]

These differences are the result of code divergence. We’ll explore how to use codebasin to measure the amount of code divergence in a later tutorial.