Wednesday, October 19, 2011

Nerd Food: C++11 with clang

So, C++11 is finally out! However, we still have to wait for the compilers to catch up. Some compilers are faster than others, however, and clang is certainly one of the fastest of the lot. I've recently set-up a simple trunk environment for clang and found it to be pretty stable - at least for learning purposes - so I decided to share my notes.

I started my wonderings with this post: Compiling llvm, clang and libc++ on Linux.However, I must say I found their approach to be a bit too... hacky... so I adjusted it somewhat. You won't fail to notice the similarities though. For the remainder of the article, I assume you have git, svn, cmake, and all other usual development packages installed; if not, apt-get them before proceeding. I also decided to compile clang using clang stable, for giggles mainly, so I got my hands on the latest clang (2.9) via the usual apt-get means. Without further ado, lets get on with it.

The first thing to do is to checkout the source code. I use git as an svn client, but feel free to use svn directly. Unfortunately, I couldn't get git svn to use modules, so I ended up having to do an svn checkout of clang. Oh well.

Checkout steps:
$ mkdir llvm
$ cd llvm
$ git svn clone -r HEAD http://llvm.org/svn/llvm-project/llvm/trunk llvm_src
$ cd llvm_src/tools
$ svn checkout http://llvm.org/svn/llvm-project/cfe/trunk clang
$ cd ../../
$ git svn clone -r HEAD http://llvm.org/svn/llvm-project/libcxx/trunk libcxx_src
We now have llvm, clang and the standard library. However, before we can do a build we must do the include headers hack:
$ cd llvm_src/tools/clang/lib/Frontend
$ emacs InitHeaderSearch.cpp
"Apply" the following patch, replacing marco with your username:
// FIXME: temporary hack: hard-coded paths.
- AddPath("/usr/local/include", System, true, false, false);
+ AddPath("/home/marco/local/include/c++/v1", System, true, false, false);
Exit emacs. The source is now ready to build. As we are civilised folk, we do out of source builds:
$ cd - # e.g. back at the llvm top level directory
$ mkdir llvm_build
$ cd llvm_build
$ CC=clang CXX=clang++ cmake -DCMAKE_INSTALL_PREFIX=/home/marco/local ../llvm_src -j4
A couple of notes:
  • If you want to do a default gcc build you can omit the CC and CXX variables. If you have multiple gcc versions, just set them accordingly (e.g. CC=gcc-4.5 CXX=g++-4.5, etc.).
  • Ideally you want the install directory to be a throw away directory that you can wipe out whenever, so don't put anything else on it.
  • Feel free to bump the -j4 to a sensible number depending on the cores you have available. If you are not using the machine, a -j6 or even -j8 on a quad-core tends to speed things up a lot.
You can now install clang:
$ make install
This will copy all the libs and binaries over to the install directory (~/local on our case). Now we can build and install the standard library:
$ cd ../libcxx_build
$ CC=clang CXX=clang++ cmake -DCMAKE_INSTALL_PREFIX=/home/marco/local ../libcxx_src -j4
$ make install
At this point you now have a complete, if somewhat bare, clang trunk environment to play with. So lets create a simple program to test it:
$ cd ..
$ mkdir simple_src
$ mkdir simple_build
$ cd simple_src
$ emacs simple.cpp
Type a simple hello world:
#include "iostream" // can't get the angle-brackets to work..
#include "fstream" // can't get the angle-brackets to work..
using namespace std;

int main(int, char**) {
std::cout << "Hello c++11 world!" << std::endl;
return 0;
}
On the same directory, create a trivial CMakeLists.txt file (I'm making it a bit more involved as I have a template...):
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

project(simple)

#
# compiler and linker flags
#

# enable as many warnings as possible
set(warning_flags "-Wall -Wextra")

# issue all the warnings demanded by strict iso c and iso c++
set(warning_flags "${warning_flags} -pedantic")

# treat warnings as errors
set(warning_flags "${warning_flags} -Werror")

# definition shadows another
set(warning_flags "${warning_flags} -Wshadow")

# do not issue warnings for system headers
set(warning_flags "${warning_flags} -Wno-system-headers")

# overloaded virtual function has a different signature
set(warning_flags "${warning_flags} -Woverloaded-virtual")

# make string constants const char*
set(warning_flags "${warning_flags} -Wwrite-strings")

# enable RTTI
set(other_flags "-frtti")

# set the flags
set(CMAKE_CC_COMPILER /home/marco/local/bin/clang)
set(CMAKE_CXX_COMPILER /home/marco/local/bin/clang++)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/home/marco/local/lib")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${optimisation_flags}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${warning_flags}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${profiling_flags}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${other_flags}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")

set(app "simple")

set(files "")
file(GLOB_RECURSE files RELATIVE
"${CMAKE_CURRENT_SOURCE_DIR}/"
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")

add_executable(${app} ${files})
We can now compile our test program! Exit emacs and type:
$ cd ..
$ cd simple_build
$ cmake ../simple_src
$ make
To run the binary we will need to ensure the C++ library is on the program loader's path:
$ LD_LIBRARY_PATH=/home/marco/local/lib ./simple
Hello c++11 world!
One interesting thing to notice is that we still have two C++ libraries:
$ LD_LIBRARY_PATH=/home/marco/local/lib ldd simple | grep c++
libc++.so.1 => /home/marco/local/lib/libc++.so.1 (0x00007fa5b5188000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa5b463f000)
Something for a further article though.

With this set-up you can now follow clang trunk development; just git svn rebase, and svn update all the checkout directories, rebuild and re-install. Every so often you may need to delete the whole of ~/local and start again, but other than that you should be good to go.