Re: llvm & RTTI over shared libraries
- In reply to: Mark Millard : "Re: llvm & RTTI over shared libraries"
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 25 Apr 2022 23:22:12 UTC
On 2022-Apr-25, at 15:39, Mark Millard <marklmi@yahoo.com> wrote:
> • <jbo_at_insane.engineer> wrote on
> • Date: Mon, 25 Apr 2022 13:01:48 UTC :
>
>> I've created a small minimal test case which reproduces the problem (attached).
>> The key points here are:
>> - CMake based project consisting of:
>> - The header-only interface for the plugin and the types (test-interface).
>> - The main executable that loads the plugin (test-core).
>> - A plugin implementation (plugin-one).
>> - Compiles out-of-the-box on FreeBSD 13/stable with both lang/gcc11 and devel/llvm14.
>> - It uses the exact mechanism I use to load the plugins in my actual application.
>>
>> stdout output when compiling with lang/gcc11:
>>
>> t is type int
>> t is type string
>> done.
>>
>>
>> stdout output when compiling with lang/llvm14:
>>
>> could not cast t
>> could not cast t
>> done.
>>
>>
>> Unfortunately, I could not yet figure out which compiler/linker flags llvm requires to implement the same behavior as GCC does. I understand that eventually I'd be better of rewriting the necessary parts to eliminate that problem but this is not a quick job.
>>
>> Could somebody lend me a hand in figuring out which compiler/linker flags are necessary to get this to work with llvm?
>
> The GCC default behavior is technically wrong. GCC allows being configured to
> do the correct thing --at the cost of ABI mismatches vs. what they originally
> did. (At least that is how I understand what I read in the code.)
>
> To my knowledge LLVM does not allow clang++ being configured to do the wrong
> thing: it never had the ABI messed up and so did not face the self-compatibility
> question. (Bug-for-bug clang++ vs. g++ compatibility has not been the major
> goal.)
Looks like I may have got that wrong, although, like gcc11,
it is is more of a when-building-the-C++-library time frame
operation than a when-copmiling/linking to use the C++
library time frame thing. Also, nothing says the same
strings would be used by clang++ vs. g++, possibly the
comparisons might not agree across toolchains when string
comparisons are used.
. . ./contrib/llvm-project/libcxx/include/typeinfo has the
following material for !defined(_LIBCPP_ABI_MICROSOFT):
// ========================================================================== //
// Implementations
// ========================================================================== //
// ------------------------------------------------------------------------- //
// Unique
// (_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION = 1)
// ------------------------------------------------------------------------- //
// This implementation of type_info assumes a unique copy of the RTTI for a
// given type inside a program. This is a valid assumption when abiding to the
// Itanium ABI (http://itanium-cxx-abi.github.io/cxx-abi/abi.html#vtable-components).
// Under this assumption, we can always compare the addresses of the type names
// to implement equality-comparison of type_infos instead of having to perform
// a deep string comparison.
// -------------------------------------------------------------------------- //
// NonUnique
// (_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION = 2)
// -------------------------------------------------------------------------- //
// This implementation of type_info does not assume there is always a unique
// copy of the RTTI for a given type inside a program. For various reasons
// the linker may have failed to merge every copy of a types RTTI
// (For example: -Bsymbolic or llvm.org/PR37398). Under this assumption, two
// type_infos are equal if their addresses are equal or if a deep string
// comparison is equal.
// -------------------------------------------------------------------------- //
// NonUniqueARMRTTIBit
// (_LIBCPP_TYPEINFO_COMPARISON_IMPLEMENTATION = 3)
// -------------------------------------------------------------------------- //
// This implementation is specific to ARM64 on Apple platforms.
//
// This implementation of type_info does not assume always a unique copy of
// the RTTI for a given type inside a program. When constructing the type_info,
// the compiler packs the pointer to the type name into a uintptr_t and reserves
// the high bit of that pointer, which is assumed to be free for use under that
// ABI. If that high bit is set, that specific copy of the RTTI can't be assumed
// to be unique within the program. If the high bit is unset, then the RTTI can
// be assumed to be unique within the program.
//
// When comparing type_infos, if both RTTIs can be assumed to be unique, it
// suffices to compare their addresses. If both the RTTIs can't be assumed to
// be unique, we must perform a deep string comparison of the type names.
// However, if one of the RTTIs is guaranteed unique and the other one isn't,
// then both RTTIs are necessarily not to be considered equal.
//
// The intent of this design is to remove the need for weak symbols. Specifically,
// if a type would normally have a default-visibility RTTI emitted as a weak
// symbol, it is given hidden visibility instead and the non-unique bit is set.
// Otherwise, types declared with hidden visibility are always considered to have
// a unique RTTI: the RTTI is emitted with linkonce_odr linkage and is assumed
// to be deduplicated by the linker within the linked image. Across linked image
// boundaries, such types are thus considered different types.
// This value can be overriden in the __config_site. When it's not overriden,
// we pick a default implementation based on the platform here.
> I have a nearly-minimalist change to your example that makes it result in:
>
> # ./test-core
> t is type_int
> t is type_string
> done.
>
> under clang. I pasted a diff -ruN in the message later below but that may
> lead to white space not being fully preserved. (I could send it to you in
> another form if it proved needed.)
>
> Basically I avoid inline definitions of:
>
> virtual ~type_base();
> virtual ~type_int();
> virtual ~type_string();
>
> Also, these are deliberately(!) the first non-inline virtual
> member functions in the 3 types. Where the implementations
> are placed controls were the type_info is put for the 3 types.
> (Not a language definition issue but a fairly common
> implementation technique.)
>
> I also make the place with the implementation be a tiny .so
> that both test-core and libplugin-one.so are bound to. This
> makes them use the same type_info definitions instead of
> having multiple competing ones around, sort of a form of
> single-definition-rule (unique addresses in the process).
> With the single definition rule followed, RTTI works just
> fine.
>
> I do warn that this is the first direct adjustment of cmake
> material that I've ever done. So if anything looks odd for
> how I did the cmake aspects, do not be surprised. I'm not
> cmake literate.
>
> For reference:
>
> # find clang_test_dist_m_m/ -print
> clang_test_dist_m_m/
> clang_test_dist_m_m/plugins
> clang_test_dist_m_m/plugins/CMakeLists.txt
> clang_test_dist_m_m/plugins/plugin_one
> clang_test_dist_m_m/plugins/plugin_one/CMakeLists.txt
> clang_test_dist_m_m/plugins/plugin_one/plugin.cpp
> clang_test_dist_m_m/shared_types_impl
> clang_test_dist_m_m/shared_types_impl/CMakeLists.txt
> clang_test_dist_m_m/shared_types_impl/types_impl.cpp
> clang_test_dist_m_m/core
> clang_test_dist_m_m/core/dlclass.hpp
> clang_test_dist_m_m/core/CMakeLists.txt
> clang_test_dist_m_m/core/main.cpp
> clang_test_dist_m_m/CMakeLists.txt
> clang_test_dist_m_m/interface
> clang_test_dist_m_m/interface/plugin.hpp
> clang_test_dist_m_m/interface/types.hpp
> clang_test_dist_m_m/interface/CMakeLists.txt
>
> where the diff -ruN is . . .
>
> diff -ruN clang_test_dist/ clang_test_dist_m_m/ | more
> diff -ruN clang_test_dist/CMakeLists.txt clang_test_dist_m_m/CMakeLists.txt
> --- clang_test_dist/CMakeLists.txt 2022-04-19 13:38:59.000000000 -0700
> +++ clang_test_dist_m_m/CMakeLists.txt 2022-04-25 12:51:03.448582000 -0700
> @@ -5,4 +5,5 @@
>
> add_subdirectory(core)
> add_subdirectory(interface)
> +add_subdirectory(shared_types_impl)
> add_subdirectory(plugins)
> diff -ruN clang_test_dist/core/CMakeLists.txt clang_test_dist_m_m/core/CMakeLists.txt
> --- clang_test_dist/core/CMakeLists.txt 2022-04-19 13:38:59.000000000 -0700
> +++ clang_test_dist_m_m/core/CMakeLists.txt 2022-04-25 13:18:52.539921000 -0700
> @@ -19,9 +19,12 @@
> PRIVATE
> test-interface
> dl
> + PUBLIC
> + shared-types-impl
> )
>
> add_dependencies(
> ${TARGET}
> + shared-types-impl
> plugin-one
> )
> diff -ruN clang_test_dist/interface/types.hpp clang_test_dist_m_m/interface/types.hpp
> --- clang_test_dist/interface/types.hpp 2022-04-19 13:38:59.000000000 -0700
> +++ clang_test_dist_m_m/interface/types.hpp 2022-04-25 14:48:52.534159000 -0700
> @@ -7,18 +7,20 @@
>
> struct type_base
> {
> - virtual ~type_base() = default;
> + virtual ~type_base();
> };
>
> struct type_int :
> type_base
> {
> + virtual ~type_int();
> int data;
> };
>
> struct type_string :
> type_base
> {
> + virtual ~type_string();
> std::string data;
> };
>
> diff -ruN clang_test_dist/plugins/plugin_one/CMakeLists.txt clang_test_dist_m_m/plugins/plugin_one/CMakeLists.txt
> --- clang_test_dist/plugins/plugin_one/CMakeLists.txt 2022-04-19 13:38:59.000000000 -0700
> +++ clang_test_dist_m_m/plugins/plugin_one/CMakeLists.txt 2022-04-25 13:19:20.188778000 -0700
> @@ -12,3 +12,14 @@
> PRIVATE
> plugin.cpp
> )
> +
> +target_link_libraries(
> + ${TARGET}
> + PUBLIC
> + shared-types-impl
> +)
> +
> +add_dependencies(
> + ${TARGET}
> + shared-types-impl
> +)
> diff -ruN clang_test_dist/shared_types_impl/CMakeLists.txt clang_test_dist_m_m/shared_types_impl/CMakeLists.txt
> --- clang_test_dist/shared_types_impl/CMakeLists.txt 1969-12-31 16:00:00.000000000 -0800
> +++ clang_test_dist_m_m/shared_types_impl/CMakeLists.txt 2022-04-25 12:55:29.760985000 -0700
> @@ -0,0 +1,15 @@
> +set(TARGET shared-types-impl)
> +add_library(${TARGET} SHARED)
> +
> +target_compile_features(
> + ${TARGET}
> + PRIVATE
> + cxx_std_20
> +)
> +
> +target_sources(
> + ${TARGET}
> + PRIVATE
> + types_impl.cpp
> +)
> +
> diff -ruN clang_test_dist/shared_types_impl/types_impl.cpp clang_test_dist_m_m/shared_types_impl/types_impl.cpp
> --- clang_test_dist/shared_types_impl/types_impl.cpp 1969-12-31 16:00:00.000000000 -0800
> +++ clang_test_dist_m_m/shared_types_impl/types_impl.cpp 2022-04-25 14:49:23.599440000 -0700
> @@ -0,0 +1,5 @@
> +#include "../interface/types.hpp"
> +
> +interface::type_base::~type_base() {}
> +interface::type_int::~type_int() {}
> +interface::type_string::~type_string() {}
>
>
> That is all there is to the changes.
>
===
Mark Millard
marklmi at yahoo.com