Re: llvm & RTTI over shared libraries

From: Mark Millard <marklmi_at_yahoo.com>
Date: Mon, 25 Apr 2022 22:39:48 UTC
	• <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.)

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