c++ Inline namespaces as symbol versioning in shared libraries

142 views Asked by At

What use are inline namespaces for shared library ABI compatibility when mangled names aren't even deterministic between compilers/versions? What I would need is simply a named prefix to the linker symbol, but one can only get that together with name mangling?

Example

Header:

#include <string>

namespace abi_v1
{
  void post_to_stack_overflow(std::string s);
}

inline namespace abi_v2
{
  void post_to_stack_overflow(std::string s);
}

Implementation:

#include "fake_lib.h"
#include <iostream>


namespace abi_v1
{
  void post_to_stack_overflow(std::string s)
  {
    std::cout << "Fake: " << s << std::endl;
  }
}

inline namespace abi_v2
{
  void post_to_stack_overflow(std::string s)
  {
    std::cout << "Fake2: " << s << std::endl;
  }
}

Compile:

g++ -fPIC -shared fake_lib.cpp -o fake_lib.so

Produces to symbols:

# nm fake_lib.so |grep post_to
0000000000001199 T _ZN6abi_v122post_to_stack_overflowENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
00000000000011e9 T _ZN6abi_v222post_to_stack_overflowENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

Those names aren't always going to be the same.

And this is even ignoring the fact that you'd have to start out with a namespace from the start; you can't introduce the inline namespace around functions later, because the link name will change.

Additionally, using extern "C" (for broader binary portability) in combination with inline namespaces seems to undo the C-style linkage and reintroduce the name mangling:

header:

extern "C"
{

  inline namespace abi_v2
  {
    void post_to_stack_overflow(std::string s);
  }
}

Implementation:

#include "fake_lib.h"
#include <iostream>


void post_to_stack_overflow(std::string s)
{
  std::cout << "Fake: " << s << std::endl;
}

inline namespace abi_v2
{
  void post_to_stack_overflow(std::string s)
  {
    std::cout << "Fake2: " << s << std::endl;
  }
}

Produces:

# nm fake_lib.so |grep post_to
00000000000011e9 T post_to_stack_overflow
0000000000001199 T _Z22post_to_stack_overflowNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE

What would be useful, is to get something like __abi_v2_post_to_stack_overflow as symbol; deterministically.

1

There are 1 answers

0
Jeff Garrett On

What use are inline namespaces for shared library ABI compatibility when mangled names aren't even deterministic between compilers/versions?

The premise is false. Mangled names are deterministic. They are part of the platform ABI. Clang and GCC will agree on the mangled names on Linux. Clang and MSVC will agree on the mangled names on Windows.

ABI stability requires:

  1. Having multiple versions of the entity in the binary artifact.
  2. If you previously linked against a version, even with a new copy of the library, you will continue to get that version.

Let's a simple example of adding a second version for a simple function.

We start with:

inline namespace v1 { void f(); }

On Linux, there will be a single function in the library named _ZN2v11fEv.

We add a second version:

namespace v1 { void f(); }
inline namespace v2 { void f(); }

On Linux, there are now two functions in the library named _ZN2v11fEv and _ZN2v21fEv. If a previously built executable linked against the first version _ZN2v11fEv, it will continue to get that version.

In the executable using the library, calling f() will pick the latest version at build time, and will continue to use that version even as later versions are added.

This is what is meant when inline namespaces are mentioned in the context of ABI stability.

What I would need is simply a named prefix to the linker symbol, but one can only get that together with name mangling?

Yes. But this doesn't really matter. The mangled name isn't needed at the source level. It is simply how C++ ABIs encode multiple entities with similar source names (namespaces, overload sets, etc.). In this case, it does include a prefix with the version by way of the namespace.

Those names aren't always going to be the same.

Incorrect as mentioned.

And this is even ignoring the fact that you'd have to start out with a namespace from the start.

You would need to put it in a versioned sub-namespace, rather. C++ is very difficult to make ABI-compatible, so yes, you are going to need to design your library with that in mind. Adopting a regime, whatever it is, will be an ABI break.

Additionally, using extern "C" (for broader binary portability) in combination with inline namespaces seems to undo the C-style linkage and reintroduce the name mangling.

Yes, this is what extern "C" does. If you want ABI compatibility and C linkage, that's essentially a question about C, and C doesn't have namespaces. There, all you have is implementation or ABI-specific options. Those exist. You can use ELF symbol versioning on Linux, for example.

There are many real problems with ABI stability that inline namespaces do not solve however, so at best they are an incomplete solution:

The standard library is implementation-specific and not ABI-specific. You cannot use std::string in your ABI-stable interface and expect it to work with both libstdc++ and libc++. You can require libstdc++ and then using std::string is fine... regardless of using GCC or Clang.

While inline namespaces solve ABI stability for free functions, types greatly complicate the picture. You can have a type (and it's definition and the definition of all member functions) for each namespace, and effectively have a copy of the library for each namespace... but that's a lot of work. If you don't duplicate types, then you have to be very careful what changes are allowed to the types that can work with all versions of the free functions.

So... generally you'd favor opaque types, much like you would do with C.