The New C++11 Stack

FreeBSD 10 ships with a new C++ stack, replacing the old GNU one.

Rationale

Why do we need a new C++ stack? There are two main reasons: functionality and licensing. FreeBSD 9.0 includes the last GPLv2 (+ linking exemption) release of GNU libstdc++. GPLv2 isn't ideal for FreeBSD and GPLv3 (the license of the newer versions) is considered unacceptable, so a replacement is desirable.

In 2011, a new version of the C++ standard was introduced. C++11 is not supported at all by the 2007 version of libstdc++ that we currently ship, yet is going to be important moving forward.

Overview

A C++ implementation requires several components. The major ones are:

The STL implementation sits on top of the ABI library, which provides things like guards for initialising static variables, exceptions, and so on. The new stack uses the following components:

These components have all been imported into the base system in -CURRENT. They were merged into 9-STABLE prior to 9.1 and are in 9.1 and later, but libc++ is not built by default in 9.x. They are all built by default in 10.0, replacing their GNU equivalents, on any platforms where clang is the default compiler. Our old GCC is not capable of building libc++ (which uses C++11 internally), so this stack is not built by default on other architectures.

Building the New Stack on 9.x

Because libc++ uses a number of C++11 features, it needs to be built with a compiler that supports C++11. In 9.x, the base system defaults to building with gcc. You need to follow the instructions for building with clang to switch to a more modern compiler. You also need to add this line to /etc/make.conf:

WITH_LIBCPLUSPLUS=yes

Using the New Stack on 9.x

To use the new stack, you will need to use clang++ as your C++ compiler and linker-driver. Add -stdlib=libc++ to your compile and link flags and, in theory, it will Just Work™. In practice, please report any problems that you encounter to theraven@.

Mixing Libraries using Libc++ and Libstdc++

Although both libc++ and libstdc++ provide a largely overlapping set of source-language symbols, they are mangled differently for the linker. This means that (as long as both are using libcxxrt) you can link against libraries that use either. The only proviso is that STL symbols should not cross the language barrier.

For example, here is a trivial shared library:

$ cat test.cc
#include <iostream>

void print1(std::string &str)
{
        std::cout << str;
}

void print1(const char *str)
{
        std::cout << str;
}

And here is a program that uses it:

$ cat testmain.cc
#include <string>
int print1(std::string&);
int print1(const char*);

int main()
{
        std::string msg = "Hello world\n";
#ifdef WORK
        print1(msg.c_str());
#else
        print1(msg);
#endif
}

To show how the interoperability works, we'll first compile the library against the old C++ stack:

$ g++ -fPIC -shared test.cc -o libtest.so

Now, if we compile the test program with the old stack:

$ g++ testmain.cc -L. -ltest
$ setenv LD_LIBRARY_PATH .
$ ./a.out
Hello World

As expected, that works fine. The same will happen if you use clang++ instead of g++. Now, let's try using the new C++ stack:

$ clang++ -stdlib=libc++  testmain.cc -L. -ltest
/tmp/testmain-9COPu7.o: In function `main':
testmain.cc:(.text+0x83): undefined reference to `print1(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

Note that this is referring to the std::__1 namespace, not just std::. This is the inline namespace that libc++ uses for symbol versioning. The version compiled against libstdc++ does not use this namespace, so the symbols are different. This includes the mangling of functions that use these types, so the print1() function will have different mangling - and therefore be a different symbol - depending on whether you use libc++ or libstdc++ headers.

The simplest fix is to just use the version of the function that takes a C string:

$ clang++ -stdlib=libc++ -DWORK testmain.cc -L. -ltest
$ ldd ./a.out
./a.out:
        libtest.so => ./libtest.so (0x80081a000)
        libc++.so.1 => /usr/lib/libc++.so.1 (0x800a1b000)
        libm.so.5 => /lib/libm.so.5 (0x800cc9000)
        libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x800ee9000)
        libc.so.7 => /lib/libc.so.7 (0x8010f6000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x801455000)
        libcxxrt.so.1 => /lib/libcxxrt.so.1 (0x801765000)
$ ./a.out 
Hello world

This now links against both libc++ and libstdc++, but there are no conflicts. There are a few caveats to make this work:

Still To Do

This new stack contains all of the parts of the system that are specific to C++. The ABI library implements the C++ parts of the unwinder, but not the generic parts. These are currently provided by libgcc_s. This can be replaced by libunwind, but that is not done. It probably requires some ARM-specific functions to be implemented on top of libunwind too.

Currently, libstdc++ statically links against libsupc++, which implements the same specification as libcxxrt. This means that code that links the two is going to end up with two definitions of these symbols and stuff is likely to break. Unfortunately, the symbols in libsupc++ are not versioned. To be able to mix code between the two, we need to make libstdc++ link against libcxxrt instead of libsupc++. In theory, this is trivial, but there may be unforeseen complications.

Finally, the libstdc++ shipped with FreeBSD 9 and earlier, modified to use libcxxrt, needs moving into a port so that it can be installed for ABI compatibility.

NewC++Stack (last edited 2013-09-19 11:45:13 by theraven)