[llvm] [orc-rt] Refactor RTTI for cross-library support, add C API (PR #222946)

Lang Hames via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 06:28:18 PDT 2026


https://github.com/lhames created https://github.com/llvm/llvm-project/pull/222946

Identify types by name rather than by the address of a per-type ID object, so that a type's identity survives crossing a library boundary: an object may be constructed by one library and have its type queried by another, each with its own copy of orc-rt, where per-type addresses would not agree. Participating classes now declare an RTTIName, which must be unique across the process.

RTTIRoot records which library produced each value, so same-library queries still resolve by pointer comparison, and only cross-library ones fall back to strcmp.

Adds orc-rt-c/support/RTTI.h to expose this to C. ORC_RT_RTTI_PARTICIPANT and ORC_RT_C_RTTI_IMPL declare and define a per-type cast entry point, reached via ORC_RT_DYNCAST, alongside orc_rt_RTTIRoot_getTypeName for logging. This replaces the orc_rt_Error_TypeId API (orc_rt_Error_getTypeId, orc_rt_StringError_getTypeId), which exposed ID addresses and so could not work across libraries. orc_rt_ErrorRef moves from CoreTypes.h to Error.h.

RTTICrossDylibTest checks identity across a real boundary: its helper library links its own copy of orc-rt-bedrock, giving it a distinct library ID.

>From c51a8c06389efece33e9f09d7795c4fb4533999c Mon Sep 17 00:00:00 2001
From: Lang Hames <lhames at apple.com>
Date: Fri, 11 Sep 2026 21:21:48 +1000
Subject: [PATCH] [orc-rt] Refactor RTTI for cross-library support, add C API

Identify types by name rather than by the address of a per-type ID object, so
that a type's identity survives crossing a library boundary: an object may be
constructed by one library and have its type queried by another, each with its
own copy of orc-rt, where per-type addresses would not agree. Participating
classes now declare an RTTIName, which must be unique across the process.

RTTIRoot records which library produced each value, so same-library queries
still resolve by pointer comparison, and only cross-library ones fall back to
strcmp.

Adds orc-rt-c/support/RTTI.h to expose this to C. ORC_RT_RTTI_PARTICIPANT and
ORC_RT_C_RTTI_IMPL declare and define a per-type cast entry point, reached via
ORC_RT_DYNCAST, alongside orc_rt_RTTIRoot_getTypeName for logging. This
replaces the orc_rt_Error_TypeId API (orc_rt_Error_getTypeId,
orc_rt_StringError_getTypeId), which exposed ID addresses and so could not work
across libraries. orc_rt_ErrorRef moves from CoreTypes.h to Error.h.

RTTICrossDylibTest checks identity across a real boundary: its helper library
links its own copy of orc-rt-bedrock, giving it a distinct library ID.
---
 orc-rt/docs/CodingConventions.md              |   6 +-
 orc-rt/include/orc-rt-c/support/CoreTypes.h   |   5 -
 orc-rt/include/orc-rt-c/support/Error.h       |  26 ++--
 orc-rt/include/orc-rt-c/support/RTTI.h        |  56 ++++++++
 orc-rt/include/orc-rt/support/Error.h         |  41 ++++--
 orc-rt/include/orc-rt/support/RTTI.h          | 135 ++++++++++++++----
 orc-rt/lib/support/Error.cpp                  |   9 +-
 orc-rt/lib/support/RTTI.cpp                   |  15 +-
 orc-rt/test/unit/CMakeLists.txt               |  15 ++
 .../unit/Inputs/RTTICrossDylibTestError.h     |  42 ++++++
 .../unit/Inputs/RTTICrossDylibTestLib.cpp     |  33 +++++
 .../test/unit/Inputs/RTTICrossDylibTestLib.h  |  24 ++++
 orc-rt/test/unit/support/CAPICompileTest.c    |   7 +
 orc-rt/test/unit/support/ErrorCAPITest.cpp    | 100 ++++++-------
 .../support/ErrorExceptionInteropTest.cpp     |   2 +
 orc-rt/test/unit/support/ErrorTest.cpp        |   4 +
 .../test/unit/support/RTTICrossDylibTest.cpp  |  96 +++++++++++++
 orc-rt/test/unit/support/RTTITest.cpp         |  19 ++-
 18 files changed, 506 insertions(+), 129 deletions(-)
 create mode 100644 orc-rt/include/orc-rt-c/support/RTTI.h
 create mode 100644 orc-rt/test/unit/Inputs/RTTICrossDylibTestError.h
 create mode 100644 orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp
 create mode 100644 orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h
 create mode 100644 orc-rt/test/unit/support/RTTICrossDylibTest.cpp

diff --git a/orc-rt/docs/CodingConventions.md b/orc-rt/docs/CodingConventions.md
index 44228fb2da8b3..92f7db08cc258 100644
--- a/orc-rt/docs/CodingConventions.md
+++ b/orc-rt/docs/CodingConventions.md
@@ -42,15 +42,15 @@ flattened C name, and both are spelled with a trailing underscore.
 | Symbol                        | Kind     |
 |-------------------------------|----------|
 | `orc_rt_ErrorRef`             | type     |
-| `orc_rt_Error_getTypeId`      | function |
+| `orc_rt_Error_toString`       | function |
 | `orc_rt_StringError_create`   | function |
 | `orc_rt_log_Category`         | type     |
 | `orc_rt_log_Category_General` | value    |
 | `orc_rt_log_formatCheck`      | function |
 | `ORC_RT_LOG`                  | macro    |
 
-Reading these: `orc_rt_Error_getTypeId` is the `getTypeId` function scoped to
-the `Error` type; `orc_rt_log_formatCheck` is the `formatCheck` function in the
+Reading these: `orc_rt_Error_toString` is the `toString` function scoped to the
+`Error` type; `orc_rt_log_formatCheck` is the `formatCheck` function in the
 `log` namespace; and `orc_rt_log_Category_General` is the `General` value of the
 `Category` type in the `log` namespace.
 
diff --git a/orc-rt/include/orc-rt-c/support/CoreTypes.h b/orc-rt/include/orc-rt-c/support/CoreTypes.h
index 5c57d50672683..9251d4489778b 100644
--- a/orc-rt/include/orc-rt-c/support/CoreTypes.h
+++ b/orc-rt/include/orc-rt-c/support/CoreTypes.h
@@ -18,11 +18,6 @@
 
 ORC_RT_C_EXTERN_C_BEGIN
 
-/**
- * Opaque reference to an error instance. Null serves as the 'success' value.
- */
-typedef struct orc_rt_OpaqueError *orc_rt_ErrorRef;
-
 /**
  * A reference to an orc_rt::Session instance.
  */
diff --git a/orc-rt/include/orc-rt-c/support/Error.h b/orc-rt/include/orc-rt-c/support/Error.h
index 0c74ea752b767..1b925e0bf4ac3 100644
--- a/orc-rt/include/orc-rt-c/support/Error.h
+++ b/orc-rt/include/orc-rt-c/support/Error.h
@@ -18,22 +18,22 @@
 
 #include "orc-rt-c/support/Compiler.h"
 #include "orc-rt-c/support/CoreTypes.h"
+#include "orc-rt-c/support/RTTI.h"
 
 ORC_RT_C_EXTERN_C_BEGIN
 
-#define orc_rt_ErrorSuccess ((orc_rt_ErrorRef)0)
-
 /**
- * Error type identifier.
+ * Opaque reference to an error instance. Null serves as the 'success' value.
  */
-typedef const void *orc_rt_Error_TypeId;
+typedef struct orc_rt_OpaqueError *orc_rt_ErrorRef;
 
-/**
- * Returns the type id for the given error instance, which must be a failure
- * value (i.e. non-null).
- */
-ORC_RT_C_EXPORT orc_rt_Error_TypeId orc_rt_Error_getTypeId(orc_rt_ErrorRef Err)
-    ORC_RT_C_NOTHROW;
+#define orc_rt_ErrorSuccess ((orc_rt_ErrorRef)0)
+
+ORC_RT_RTTI_PARTICIPANT(Error)
+
+typedef struct orc_rt_OpaqueStringError *orc_rt_StringErrorRef;
+
+ORC_RT_RTTI_PARTICIPANT(StringError)
 
 /**
  * Dispose of the given error without handling it. This operation consumes the
@@ -68,12 +68,6 @@ orc_rt_Error_toString(orc_rt_ErrorRef Err) ORC_RT_C_NOTHROW;
 ORC_RT_C_EXPORT void
 orc_rt_Error_freeErrorMessage(char *ErrMsg) ORC_RT_C_NOTHROW;
 
-/**
- * Returns the type id for llvm StringError.
- */
-ORC_RT_C_EXPORT orc_rt_Error_TypeId orc_rt_StringError_getTypeId(void)
-    ORC_RT_C_NOTHROW;
-
 /**
  * Create a StringError.
  */
diff --git a/orc-rt/include/orc-rt-c/support/RTTI.h b/orc-rt/include/orc-rt-c/support/RTTI.h
new file mode 100644
index 0000000000000..0fc30dd1036c9
--- /dev/null
+++ b/orc-rt/include/orc-rt-c/support/RTTI.h
@@ -0,0 +1,56 @@
+/*===------------- RTTI.h - C API for ORC Runtime RTTI ------------*- C -*-===*\
+|*                                                                            *|
+|* Part of the LLVM Project, under the Apache License v2.0 with LLVM          *|
+|* Exceptions.                                                                *|
+|* See https://llvm.org/LICENSE.txt for license information.                  *|
+|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception                    *|
+|*                                                                            *|
+|*===----------------------------------------------------------------------===*|
+|*                                                                            *|
+|* This file defines the C interface to the ORC runtime's RTTI functions      *|
+|*                                                                            *|
+|* TODO: Explain ownership model.                                             *|
+|*                                                                            *|
+\*===----------------------------------------------------------------------===*/
+
+#ifndef ORC_RT_C_SUPPORT_RTTI_H
+#define ORC_RT_C_SUPPORT_RTTI_H
+
+#include "orc-rt-c/support/Compiler.h"
+#include "orc-rt-c/support/CoreTypes.h"
+
+ORC_RT_C_EXTERN_C_BEGIN
+
+/**
+ * Opaque reference to an RTTIRoot instance.
+ */
+typedef struct orc_rt_OpaqueRTTIRoot *orc_rt_RTTIRootRef;
+
+/**
+ * Mark a given type as participating in the ORC runtime's RTTI hierarchy.
+ *
+ * This enables the ORC_RT_DYNCAST operation to be used to safely cast between
+ * types.
+ */
+#define ORC_RT_RTTI_PARTICIPANT(Type)                                          \
+  ORC_RT_C_EXPORT orc_rt_##Type##Ref orc_rt_##Type##_fromRTTIRoot(             \
+      orc_rt_RTTIRootRef Obj) ORC_RT_C_NOTHROW;                                \
+  ORC_RT_MAYBE_UNUSED static inline orc_rt_RTTIRootRef                         \
+  orc_rt_##Type##_toRTTIRoot(orc_rt_##Type##Ref Obj) {                         \
+    return (orc_rt_RTTIRootRef)Obj;                                            \
+  }
+
+#define ORC_RT_DYNCAST(ToType, FromType, Value)                                \
+  orc_rt_##ToType##_fromRTTIRoot(orc_rt_##FromType##_toRTTIRoot(Value))
+
+/**
+ * Returns the dynamic type name of the given object.
+ *
+ * For logging purposes only. Use ORC_RT_DYNCAST to test/convert types.
+ */
+ORC_RT_C_EXPORT const char *
+orc_rt_RTTIRoot_getTypeName(orc_rt_RTTIRootRef Obj) ORC_RT_C_NOTHROW;
+
+ORC_RT_C_EXTERN_C_END
+
+#endif /* ORC_RT_C_SUPPORT_RTTI_H */
diff --git a/orc-rt/include/orc-rt/support/Error.h b/orc-rt/include/orc-rt/support/Error.h
index e370bad66dad1..02f876455905d 100644
--- a/orc-rt/include/orc-rt/support/Error.h
+++ b/orc-rt/include/orc-rt/support/Error.h
@@ -10,7 +10,7 @@
 #define ORC_RT_SUPPORT_ERROR_H
 
 #include "orc-rt-c/config.h"
-#include "orc-rt-c/support/CoreTypes.h"
+#include "orc-rt-c/support/Error.h"
 #include "orc-rt/support/CallableTraitsHelper.h"
 #include "orc-rt/support/Compiler.h"
 #include "orc-rt/support/RTTI.h"
@@ -20,6 +20,7 @@
 #include <cstdlib>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <type_traits>
 
 #if ORC_RT_ENABLE_EXCEPTIONS
@@ -33,6 +34,8 @@ class Error;
 /// Base class for all errors.
 class ErrorInfoBase : public RTTIExtends<ErrorInfoBase, RTTIRoot> {
 public:
+  static constexpr const char *RTTIName = "orc_rt::ErrorInfoBase";
+
   virtual std::string toString() const noexcept = 0;
 
 private:
@@ -53,18 +56,17 @@ class ErrorExtends : public ParentT {
   static_assert(std::is_base_of_v<ErrorInfoBase, ParentT>,
                 "ErrorExtends must extend ErrorInfoBase derivatives");
 
-  // Inherit constructors and isA methods from ParentT.
-  using ParentT::isA;
+  // Inherit constructors from ParentT.
   using ParentT::ParentT;
 
-  static char ID;
-
-  static const void *classID() noexcept { return &ThisT::ID; }
-
-  const void *dynamicClassID() const noexcept override { return &ThisT::ID; }
-
-  bool isA(const void *const ClassID) const noexcept override {
-    return ClassID == classID() || ParentT::isA(ClassID);
+  const char *dynamicRTTIName() const noexcept override {
+    static_assert(std::string_view(ThisT::RTTIName) !=
+                      std::string_view(ParentT::RTTIName),
+                  "ThisT must define its own RTTIName, distinct from "
+                  "ParentT::RTTIName (did you forget to shadow it, or copy "
+                  "the parent's string literal instead of writing a new "
+                  "one?)");
+    return ThisT::RTTIName;
   }
 
   static bool classof(const RTTIRoot *R) noexcept { return R->isA<ThisT>(); }
@@ -76,10 +78,17 @@ class ErrorExtends : public ParentT {
 
   Error restoreError() noexcept override;
 #endif // ORC_RT_ENABLE_EXCEPTIONS
-};
 
-template <typename ThisT, typename ParentT>
-char ErrorExtends<ThisT, ParentT>::ID = 0;
+protected:
+  bool sameDylibIsA(const char *const ClassName) const noexcept override {
+    return ClassName == ThisT::RTTIName || ParentT::sameDylibIsA(ClassName);
+  }
+
+  bool differentDylibIsA(const char *const ClassName) const noexcept override {
+    return strcmp(ClassName, ThisT::RTTIName) == 0 ||
+           ParentT::differentDylibIsA(ClassName);
+  }
+};
 
 /// Represents an environmental error.
 class [[nodiscard]] Error {
@@ -603,6 +612,8 @@ inline std::string toString(Error Err) noexcept {
 /// Simple string error type.
 class StringError : public ErrorExtends<StringError, ErrorInfoBase> {
 public:
+  static constexpr const char *RTTIName = "orc_rt::StringError";
+
   StringError(std::string ErrMsg) noexcept : ErrMsg(std::move(ErrMsg)) {}
   std::string toString() const noexcept override { return ErrMsg; }
 
@@ -615,6 +626,8 @@ class StringError : public ErrorExtends<StringError, ErrorInfoBase> {
 
 class ExceptionError : public ErrorExtends<ExceptionError, ErrorInfoBase> {
 public:
+  static constexpr const char *RTTIName = "orc_rt::ExceptionError";
+
   ExceptionError(std::exception_ptr E) noexcept : E(std::move(E)) {}
   std::string toString() const noexcept override;
   void throwAsException() override { std::rethrow_exception(E); }
diff --git a/orc-rt/include/orc-rt/support/RTTI.h b/orc-rt/include/orc-rt/support/RTTI.h
index 1614f6783c368..c1d8ec77139ff 100644
--- a/orc-rt/include/orc-rt/support/RTTI.h
+++ b/orc-rt/include/orc-rt/support/RTTI.h
@@ -12,25 +12,41 @@
 // the runtime is built with -frtti or not. This is predominantly used to
 // support error handling.
 //
-// The RTTIRoot class defines methods for comparing type ids. Implementations
-// of these methods can be injected into new classes using the RTTIExtends
-// class template.
+// Types identify themselves by name: each participating class declares a
+// RTTIName string, and RTTIRoot defines methods for comparing them.
+// Implementations of these methods can be injected into new classes using the
+// RTTIExtends class template, which also documents the requirements RTTIName
+// must satisfy.
+//
+// Names rather than addresses are used because a type's identity has to survive
+// crossing a library boundary. An object may be constructed by one library and
+// have its type queried by another, each with its own copy of orc-rt, so any
+// per-type address would differ between them. Comparing names is unaffected.
+// Within a single library the addresses do agree, and RTTIRoot records which
+// library produced each value so that case can be fast-pathed to a pointer
+// comparison.
 //
 // E.g.
 //
 //   @code{.cpp}
 //   class MyBaseClass : public RTTIExtends<MyBaseClass, RTTIRoot> {
 //   public:
+//     static constexpr const char *RTTIName = "mylib::MyBaseClass";
+//
 //     virtual void foo() = 0;
 //   };
 //
 //   class MyDerivedClass1 : public RTTIExtends<MyDerivedClass1, MyBaseClass> {
 //   public:
+//     static constexpr const char *RTTIName = "mylib::MyDerivedClass1";
+//
 //     void foo() override {}
 //   };
 //
 //   class MyDerivedClass2 : public RTTIExtends<MyDerivedClass2, MyBaseClass> {
 //   public:
+//     static constexpr const char *RTTIName = "mylib::MyDerivedClass2";
+//
 //     void foo() override {}
 //   };
 //
@@ -52,10 +68,40 @@
 #ifndef ORC_RT_SUPPORT_RTTI_H
 #define ORC_RT_SUPPORT_RTTI_H
 
+#include "orc-rt-c/support/RTTI.h"
+
+#include <cstring>
+#include <string_view>
 #include <type_traits>
 
 namespace orc_rt {
 
+class RTTIRoot;
+
+inline orc_rt_RTTIRootRef wrap(RTTIRoot *R) noexcept {
+  return reinterpret_cast<orc_rt_RTTIRootRef>(R);
+}
+
+inline RTTIRoot *unwrap(orc_rt_RTTIRootRef R) noexcept {
+  return reinterpret_cast<RTTIRoot *>(R);
+}
+
+/// Use this to implement C RTTI support on the given type.
+///
+/// Type must be named unqualified: it is pasted into both the C symbol name and
+/// an isA<> query, so a namespace-qualified name would produce a nonsense
+/// symbol. Use this macro from the defining scope, or bring the type into scope
+/// with a using-declaration instead.
+#define ORC_RT_C_RTTI_IMPL(Type)                                               \
+  extern "C" ORC_RT_C_EXPORT orc_rt_##Type##Ref orc_rt_##Type##_fromRTTIRoot(  \
+      orc_rt_RTTIRootRef Obj) noexcept {                                       \
+    if (!Obj)                                                                  \
+      return nullptr;                                                          \
+    if (unwrap(Obj)->isA<Type>())                                              \
+      return reinterpret_cast<orc_rt_##Type##Ref>(Obj);                        \
+    return nullptr;                                                            \
+  }
+
 class ErrorInfoBase;
 
 template <typename ThisT, typename ParentT> class RTTIExtends;
@@ -68,28 +114,42 @@ class RTTIRoot {
 public:
   virtual ~RTTIRoot() noexcept = default;
 
-  /// Returns the class ID for this type.
-  static const void *classID() noexcept { return &ID; }
+  /// The name identifying this type. See RTTIExtends for the requirements this
+  /// must satisfy.
+  static constexpr const char *RTTIName = "orc_rt::RTTIRoot";
 
-  /// Returns the class ID for the dynamic type of this RTTIRoot instance.
-  virtual const void *dynamicClassID() const noexcept = 0;
+  /// Return the library ID for this value.
+  ///
+  /// This identifies which dylib produced the value, allowing us to fast-path
+  /// type equality checks within the same library.
+  const void *libraryID() const noexcept { return LibraryID; }
 
-  /// Returns true if this class's ID matches the given class ID.
-  virtual bool isA(const void *const ClassID) const noexcept {
-    return ClassID == classID();
-  }
+  /// Returns the RTTIName of the dynamic type of this RTTIRoot instance.
+  virtual const char *dynamicRTTIName() const noexcept = 0;
 
   /// Check whether this instance is a subclass of QueryT.
   template <typename QueryT> bool isA() const noexcept {
-    return isA(QueryT::classID());
+    return libraryID() == &ThisLibraryID ? sameDylibIsA(QueryT::RTTIName)
+                                         : differentDylibIsA(QueryT::RTTIName);
   }
 
   static bool classof(const RTTIRoot *R) noexcept { return R->isA<RTTIRoot>(); }
 
+protected:
+  /// Fast-path isA for values produced by this dylib.
+  virtual bool sameDylibIsA(const char *const ClassName) const noexcept {
+    return ClassName == RTTIName;
+  }
+
+  /// Slow-path isA for values produced by different dylibs.
+  virtual bool differentDylibIsA(const char *const ClassName) const noexcept {
+    return strcmp(ClassName, RTTIName) == 0;
+  }
+
 private:
+  static char ThisLibraryID;
+  const char *const LibraryID = &ThisLibraryID;
   virtual void anchor() noexcept;
-
-  static char ID;
 };
 
 /// Inheritance utility for extensible RTTI.
@@ -101,40 +161,63 @@ class RTTIRoot {
 /// RTTIExtents uses CRTP so the first template argument to RTTIExtends is the
 /// newly introduced type, and the *second* argument is the parent class.
 ///
+/// Each participating type must declare its own RTTIName:
+///
 /// class MyType : public RTTIExtends<MyType, RTTIRoot> {
+/// public:
+///   static constexpr const char *RTTIName = "mylib::MyType";
 ///   ...
 /// };
 ///
 /// class MyDerivedType : public RTTIExtends<MyDerivedType, MyType> {
+/// public:
+///   static constexpr const char *RTTIName = "mylib::MyDerivedType";
 ///   ...
 /// };
 ///
+/// RTTINames must be unique across every library in the process, not just
+/// within one hierarchy. Types from different libraries are compared by name,
+/// so two unrelated types that share a name would satisfy each other's isa<>
+/// checks. Qualifying the name with its namespace, as above, is usually enough
+/// to keep it unique.
+///
+/// Forgetting to declare RTTIName leaves ParentT's visible by inheritance,
+/// which would make the type indistinguishable from its parent; RTTIExtends
+/// static_asserts against that. It cannot detect a collision with an unrelated
+/// type.
+///
 template <typename ThisT, typename ParentT> class RTTIExtends : public ParentT {
 public:
   static_assert(!std::is_base_of_v<ErrorInfoBase, ParentT>,
                 "RTTIExtends should not be used to define orc_rt custom error "
                 "types, use ErrorExtends instead");
 
-  // Inherit constructors and isA methods from ParentT.
-  using ParentT::isA;
+  // Inherit constructors from ParentT.
   using ParentT::ParentT;
 
-  static char ID;
-
-  static const void *classID() noexcept { return &ThisT::ID; }
+  const char *dynamicRTTIName() const noexcept override {
+    static_assert(std::string_view(ThisT::RTTIName) !=
+                      std::string_view(ParentT::RTTIName),
+                  "ThisT must define its own RTTIName, distinct from "
+                  "ParentT::RTTIName (did you forget to shadow it, or copy "
+                  "the parent's string literal instead of writing a new "
+                  "one?)");
+    return ThisT::RTTIName;
+  }
 
-  const void *dynamicClassID() const noexcept override { return &ThisT::ID; }
+  static bool classof(const RTTIRoot *R) noexcept { return R->isA<ThisT>(); }
 
-  bool isA(const void *const ClassID) const noexcept override {
-    return ClassID == classID() || ParentT::isA(ClassID);
+protected:
+  bool sameDylibIsA(const char *const ClassName) const noexcept override {
+    return ClassName == ThisT::RTTIName || ParentT::sameDylibIsA(ClassName);
   }
 
-  static bool classof(const RTTIRoot *R) noexcept { return R->isA<ThisT>(); }
+  bool differentDylibIsA(const char *const ClassName) const noexcept override {
+    return strcmp(ClassName, ThisT::RTTIName) == 0 ||
+           ParentT::differentDylibIsA(ClassName);
+  }
 };
 
-template <typename ThisT, typename ParentT>
-char RTTIExtends<ThisT, ParentT>::ID = 0;
-
 /// Returns true if the given value is an instance of the template type
 /// parameter.
 template <typename To, typename From> bool isa(const From &Value) noexcept {
diff --git a/orc-rt/lib/support/Error.cpp b/orc-rt/lib/support/Error.cpp
index 0c246d2fc5769..cc936c93ae4c6 100644
--- a/orc-rt/lib/support/Error.cpp
+++ b/orc-rt/lib/support/Error.cpp
@@ -52,10 +52,7 @@ std::string ExceptionError::toString() const noexcept {
 
 extern "C" {
 
-orc_rt_Error_TypeId orc_rt_Error_getTypeId(orc_rt_ErrorRef Err) noexcept {
-  assert(Err && "Err must not be null");
-  return reinterpret_cast<ErrorInfoBase *>(Err)->dynamicClassID();
-}
+ORC_RT_C_RTTI_IMPL(StringError)
 
 void orc_rt_Error_consume(orc_rt_ErrorRef Err) noexcept {
   consumeError(unwrap(Err));
@@ -71,10 +68,6 @@ char *orc_rt_Error_toString(orc_rt_ErrorRef Err) noexcept {
 
 void orc_rt_Error_freeErrorMessage(char *ErrMsg) noexcept { free(ErrMsg); }
 
-orc_rt_Error_TypeId orc_rt_StringError_getTypeId(void) noexcept {
-  return StringError::classID();
-}
-
 orc_rt_ErrorRef orc_rt_StringError_create(const char *ErrMsg) noexcept {
   return wrap(make_error<StringError>(ErrMsg));
 }
diff --git a/orc-rt/lib/support/RTTI.cpp b/orc-rt/lib/support/RTTI.cpp
index 09e93887403a0..0873ae0c20ebc 100644
--- a/orc-rt/lib/support/RTTI.cpp
+++ b/orc-rt/lib/support/RTTI.cpp
@@ -6,7 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// Contains the implementation of APIs in the orc-rt/support/RTTI.h header.
+// Contains the implementation of APIs in the orc-rt/support/RTTI.h and
+// orc-rt-c/support/RTTI.h headers.
 //
 //===----------------------------------------------------------------------===//
 
@@ -14,7 +15,17 @@
 
 namespace orc_rt {
 
-char RTTIRoot::ID = 0;
+char RTTIRoot::ThisLibraryID = 0;
 void RTTIRoot::anchor() noexcept {}
 
+// --- C API Implementation ---
+
+extern "C" {
+
+const char *orc_rt_RTTIRoot_getTypeName(orc_rt_RTTIRootRef Obj) noexcept {
+  return unwrap(Obj)->dynamicRTTIName();
+}
+
+} // extern "C"
+
 } // namespace orc_rt
diff --git a/orc-rt/test/unit/CMakeLists.txt b/orc-rt/test/unit/CMakeLists.txt
index 60671be1927c0..bfcca8df3a385 100644
--- a/orc-rt/test/unit/CMakeLists.txt
+++ b/orc-rt/test/unit/CMakeLists.txt
@@ -44,6 +44,7 @@ add_orc_rt_unittest(SupportTests
   support/ManglingTest.cpp
   support/MemoryFlagsTest.cpp
   support/ProxyTest.cpp
+  support/RTTICrossDylibTest.cpp
   support/RTTITest.cpp
   support/StringExtrasTest.cpp
   support/StringPoolTest.cpp
@@ -101,3 +102,17 @@ set_target_properties(NativeDylibManagerTestLib PROPERTIES
 target_compile_definitions(BedrockTests PRIVATE
   "NDM_TEST_LIB_PATH=\"$<TARGET_FILE:NativeDylibManagerTestLib>\"")
 add_dependencies(BedrockTests NativeDylibManagerTestLib)
+
+# Build a shared library for RTTICrossDylibTest. It links its own copy of
+# orc-rt-bedrock, giving it a LibraryID distinct from SupportTests's, so the
+# test exercises RTTI identity across a real library boundary rather than
+# simulating one within a single binary. SupportTests links it directly, the
+# same way any other consumer of orc-rt would.
+add_library(RTTICrossDylibTestLib SHARED
+  Inputs/RTTICrossDylibTestLib.cpp)
+set_target_properties(RTTICrossDylibTestLib PROPERTIES
+  PREFIX ""
+  CXX_VISIBILITY_PRESET hidden
+  VISIBILITY_INLINES_HIDDEN ON)
+target_link_libraries(RTTICrossDylibTestLib PRIVATE orc-rt-bedrock)
+target_link_libraries(SupportTests PRIVATE RTTICrossDylibTestLib)
diff --git a/orc-rt/test/unit/Inputs/RTTICrossDylibTestError.h b/orc-rt/test/unit/Inputs/RTTICrossDylibTestError.h
new file mode 100644
index 0000000000000..45816b19cadce
--- /dev/null
+++ b/orc-rt/test/unit/Inputs/RTTICrossDylibTestError.h
@@ -0,0 +1,42 @@
+//===- RTTICrossDylibTestError.h ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// An ErrorInfoBase subclass shared between CoreTests and
+// RTTICrossDylibTestLib, so that RTTICrossDylibTest.cpp can construct an
+// instance in one library, and check/cast it via RTTI in another -- a real
+// cross-library boundary, not a same-binary simulation of one.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_TEST_RTTICROSSDYLIBTESTERROR_H
+#define ORC_RT_TEST_RTTICROSSDYLIBTESTERROR_H
+
+#include "orc-rt/support/Error.h"
+
+namespace orc_rt_test {
+
+class CrossDylibTestError
+    : public orc_rt::ErrorExtends<CrossDylibTestError, orc_rt::ErrorInfoBase> {
+public:
+  static constexpr const char *RTTIName = "orc_rt_test::CrossDylibTestError";
+
+  explicit CrossDylibTestError(int Code) noexcept : Code(Code) {}
+
+  std::string toString() const noexcept override {
+    return "CrossDylibTestError(" + std::to_string(Code) + ")";
+  }
+
+  int getCode() const noexcept { return Code; }
+
+private:
+  int Code;
+};
+
+} // namespace orc_rt_test
+
+#endif // ORC_RT_TEST_RTTICROSSDYLIBTESTERROR_H
diff --git a/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp
new file mode 100644
index 0000000000000..a3612e75bb04e
--- /dev/null
+++ b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp
@@ -0,0 +1,33 @@
+// A minimal shared library for RTTICrossDylibTest: constructs a
+// CrossDylibTestError using its own linked copy of orc-rt's RTTI state, so
+// that the test binary can confirm that isA<>() and casting still work when
+// the object's RTTIRoot::LibraryID differs from the caller's.
+
+#include "RTTICrossDylibTestLib.h"
+#include "RTTICrossDylibTestError.h"
+
+#if defined(_WIN32)
+#define TEST_EXPORT __declspec(dllexport)
+#else
+#define TEST_EXPORT __attribute__((visibility("default")))
+#endif
+
+using namespace orc_rt;
+using orc_rt_test::CrossDylibTestError;
+
+extern "C" TEST_EXPORT ErrorInfoBase *rttiCrossDylibTest_makeError(int Code) {
+  return new CrossDylibTestError(Code);
+}
+
+extern "C" TEST_EXPORT void rttiCrossDylibTest_destroyError(ErrorInfoBase *E) {
+  delete E;
+}
+
+// Exposes this library's own RTTIRoot::LibraryID, so the test binary can
+// confirm the two libraries are genuinely using distinct identities (i.e.
+// that the test below exercises the cross-library strcmp path, not the
+// same-library pointer-equality fast path).
+extern "C" TEST_EXPORT const void *rttiCrossDylibTest_libraryID() {
+  CrossDylibTestError E(0);
+  return E.libraryID();
+}
diff --git a/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h
new file mode 100644
index 0000000000000..0ec2166bdfab3
--- /dev/null
+++ b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h
@@ -0,0 +1,24 @@
+//===- RTTICrossDylibTestLib.h --------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Declarations for the functions exported by RTTICrossDylibTestLib, shared
+// between the library's own definitions and RTTICrossDylibTest.cpp so the
+// two agree on signatures at compile time.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_TEST_RTTICROSSDYLIBTESTLIB_H
+#define ORC_RT_TEST_RTTICROSSDYLIBTESTLIB_H
+
+#include "orc-rt/support/Error.h"
+
+extern "C" orc_rt::ErrorInfoBase *rttiCrossDylibTest_makeError(int Code);
+extern "C" void rttiCrossDylibTest_destroyError(orc_rt::ErrorInfoBase *E);
+extern "C" const void *rttiCrossDylibTest_libraryID();
+
+#endif // ORC_RT_TEST_RTTICROSSDYLIBTESTLIB_H
diff --git a/orc-rt/test/unit/support/CAPICompileTest.c b/orc-rt/test/unit/support/CAPICompileTest.c
index bd9c81e64e261..88a85f5831682 100644
--- a/orc-rt/test/unit/support/CAPICompileTest.c
+++ b/orc-rt/test/unit/support/CAPICompileTest.c
@@ -20,8 +20,15 @@
 #include "orc-rt-c/support/CoreTypes.h"
 #include "orc-rt-c/support/Error.h"
 #include "orc-rt-c/support/Logging.h"
+#include "orc-rt-c/support/RTTI.h"
 #include "orc-rt-c/support/WrapperFunction.h"
 
+/* The _toRTTIRoot accessor defined by ORC_RT_RTTI_PARTICIPANT must be callable
+   from C. */
+orc_rt_RTTIRootRef orc_rt_test_errorToRTTIRoot(orc_rt_ErrorRef E) {
+  return orc_rt_Error_toRTTIRoot(E);
+}
+
 /* ORC_RT_HAS_BUILTIN must be usable in a preprocessor conditional in C, and
    must agree with the answer the C++ compiler gives for the same builtin. */
 #if ORC_RT_HAS_BUILTIN(__builtin_expect)
diff --git a/orc-rt/test/unit/support/ErrorCAPITest.cpp b/orc-rt/test/unit/support/ErrorCAPITest.cpp
index 2b7d5f0afd256..e9591c3ffdf3e 100644
--- a/orc-rt/test/unit/support/ErrorCAPITest.cpp
+++ b/orc-rt/test/unit/support/ErrorCAPITest.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "orc-rt-c/support/Error.h"
+#include "orc-rt-c/support/RTTI.h"
 #include "orc-rt/support/Error.h"
 #include "gtest/gtest.h"
 
@@ -19,6 +20,35 @@
 
 using namespace orc_rt;
 
+// Test wrapping a custom C++ error type and checking its type via C API.
+namespace orc_rt {
+
+class CustomCAPITestError
+    : public ErrorExtends<CustomCAPITestError, ErrorInfoBase> {
+public:
+  static constexpr const char *RTTIName = "::CustomCAPITestError";
+
+  CustomCAPITestError(int Code) : Code(Code) {}
+  std::string toString() const noexcept override {
+    return "CustomCAPITestError: " + std::to_string(Code);
+  }
+  int getCode() const { return Code; }
+
+private:
+  int Code;
+};
+
+extern "C" {
+
+typedef struct orc_rt_OpaqueCustomCAPITestError *orc_rt_CustomCAPITestErrorRef;
+
+ORC_RT_RTTI_PARTICIPANT(CustomCAPITestError)
+ORC_RT_C_RTTI_IMPL(CustomCAPITestError)
+
+} // extern "C"
+
+} // namespace orc_rt
+
 namespace {
 
 // Test that wrapping a success value produces null.
@@ -45,16 +75,6 @@ TEST(ErrorCAPITest, UnwrapSuccess) {
   EXPECT_FALSE(E) << "Unwrapping null should produce success";
 }
 
-// Test orc_rt_Error_getTypeId returns the correct type ID.
-TEST(ErrorCAPITest, GetTypeId) {
-  orc_rt_ErrorRef ErrRef = orc_rt_StringError_create("test");
-  orc_rt_Error_TypeId TypeId = orc_rt_Error_getTypeId(ErrRef);
-
-  EXPECT_EQ(TypeId, orc_rt_StringError_getTypeId());
-
-  orc_rt_Error_consume(ErrRef);
-}
-
 // Test orc_rt_Error_consume properly disposes of an error.
 TEST(ErrorCAPITest, Consume) {
   orc_rt_ErrorRef ErrRef = orc_rt_StringError_create("test");
@@ -95,7 +115,7 @@ TEST(ErrorCAPITest, StringErrorCreate) {
   EXPECT_NE(ErrRef, orc_rt_ErrorSuccess);
 
   // Verify it's a StringError.
-  EXPECT_EQ(orc_rt_Error_getTypeId(ErrRef), orc_rt_StringError_getTypeId());
+  EXPECT_TRUE(!!ORC_RT_DYNCAST(StringError, Error, ErrRef));
 
   // Verify the message.
   char *Msg = orc_rt_Error_toString(ErrRef);
@@ -103,23 +123,6 @@ TEST(ErrorCAPITest, StringErrorCreate) {
   orc_rt_Error_freeErrorMessage(Msg);
 }
 
-// Test orc_rt_StringError_getTypeId returns a consistent value.
-TEST(ErrorCAPITest, StringErrorTypeIdConsistent) {
-  orc_rt_Error_TypeId TypeId1 = orc_rt_StringError_getTypeId();
-  orc_rt_Error_TypeId TypeId2 = orc_rt_StringError_getTypeId();
-
-  EXPECT_EQ(TypeId1, TypeId2);
-  EXPECT_NE(TypeId1, nullptr);
-}
-
-// Test that C API type ID matches C++ StringError class ID.
-TEST(ErrorCAPITest, StringErrorTypeIdMatchesCpp) {
-  orc_rt_Error_TypeId CTypeId = orc_rt_StringError_getTypeId();
-  const void *CppTypeId = StringError::classID();
-
-  EXPECT_EQ(CTypeId, CppTypeId);
-}
-
 // Test creating and consuming multiple errors.
 TEST(ErrorCAPITest, MultipleErrors) {
   orc_rt_ErrorRef Err1 = orc_rt_StringError_create("error 1");
@@ -143,35 +146,32 @@ TEST(ErrorCAPITest, MultipleErrors) {
   orc_rt_Error_freeErrorMessage(Msg3);
 }
 
-// Test wrapping a custom C++ error type and checking its type via C API.
-class CustomCAPITestError
-    : public ErrorExtends<CustomCAPITestError, ErrorInfoBase> {
-public:
-  CustomCAPITestError(int Code) : Code(Code) {}
-  std::string toString() const noexcept override {
-    return "CustomCAPITestError: " + std::to_string(Code);
-  }
-  int getCode() const { return Code; }
-
-private:
-  int Code;
-};
-
-TEST(ErrorCAPITest, CustomErrorTypeId) {
+TEST(ErrorCAPITest, CustomErrorTypeChecks) {
   Error CppError = make_error<CustomCAPITestError>(42);
   orc_rt_ErrorRef ErrRef = wrap(std::move(CppError));
 
-  orc_rt_Error_TypeId TypeId = orc_rt_Error_getTypeId(ErrRef);
-
-  // Should not be a StringError.
-  EXPECT_NE(TypeId, orc_rt_StringError_getTypeId());
-
-  // Should match the C++ class ID.
-  EXPECT_EQ(TypeId, CustomCAPITestError::classID());
+  EXPECT_TRUE(!!ORC_RT_DYNCAST(CustomCAPITestError, Error, ErrRef));
+  EXPECT_FALSE(!!ORC_RT_DYNCAST(StringError, Error, ErrRef));
 
   char *Msg = orc_rt_Error_toString(ErrRef);
   EXPECT_STREQ(Msg, "CustomCAPITestError: 42");
   orc_rt_Error_freeErrorMessage(Msg);
 }
 
+// Test orc_rt_RTTIRoot_getTypeName reports the dynamic type's RTTIName.
+TEST(ErrorCAPITest, GetTypeName) {
+  // The static type of an orc_rt_ErrorRef is Error, so these also check that
+  // getTypeName reports the most-derived type rather than the one named by the
+  // reference it was handed.
+  orc_rt_ErrorRef StrErr = orc_rt_StringError_create("test error");
+  EXPECT_STREQ(orc_rt_RTTIRoot_getTypeName(orc_rt_Error_toRTTIRoot(StrErr)),
+               "orc_rt::StringError");
+  orc_rt_Error_consume(StrErr);
+
+  orc_rt_ErrorRef CustomErr = wrap(make_error<CustomCAPITestError>(42));
+  EXPECT_STREQ(orc_rt_RTTIRoot_getTypeName(orc_rt_Error_toRTTIRoot(CustomErr)),
+               "::CustomCAPITestError");
+  orc_rt_Error_consume(CustomErr);
+}
+
 } // namespace
diff --git a/orc-rt/test/unit/support/ErrorExceptionInteropTest.cpp b/orc-rt/test/unit/support/ErrorExceptionInteropTest.cpp
index d1d6e7a25f2e6..8205f30bea083 100644
--- a/orc-rt/test/unit/support/ErrorExceptionInteropTest.cpp
+++ b/orc-rt/test/unit/support/ErrorExceptionInteropTest.cpp
@@ -22,6 +22,8 @@ namespace {
 
 class CustomError : public ErrorExtends<CustomError, ErrorInfoBase> {
 public:
+  static constexpr const char *RTTIName = "::CustomError";
+
   std::string toString() const noexcept override { return "CustomError"; }
 };
 
diff --git a/orc-rt/test/unit/support/ErrorTest.cpp b/orc-rt/test/unit/support/ErrorTest.cpp
index 884beaf341eea..92b71dc67fd0f 100644
--- a/orc-rt/test/unit/support/ErrorTest.cpp
+++ b/orc-rt/test/unit/support/ErrorTest.cpp
@@ -23,6 +23,8 @@ namespace {
 
 class CustomError : public ErrorExtends<CustomError, ErrorInfoBase> {
 public:
+  static constexpr const char *RTTIName = "::CustomError";
+
   CustomError(int Info) : Info(Info) {}
   std::string toString() const noexcept override {
     return "CustomError (" + std::to_string(Info) + ")";
@@ -35,6 +37,8 @@ class CustomError : public ErrorExtends<CustomError, ErrorInfoBase> {
 
 class CustomSubError : public ErrorExtends<CustomSubError, CustomError> {
 public:
+  static constexpr const char *RTTIName = "::CustomSubError";
+
   CustomSubError(int Info, std::string ExtraInfo)
       : ErrorExtends<CustomSubError, CustomError>(Info),
         ExtraInfo(std::move(ExtraInfo)) {}
diff --git a/orc-rt/test/unit/support/RTTICrossDylibTest.cpp b/orc-rt/test/unit/support/RTTICrossDylibTest.cpp
new file mode 100644
index 0000000000000..8b92e161586f6
--- /dev/null
+++ b/orc-rt/test/unit/support/RTTICrossDylibTest.cpp
@@ -0,0 +1,96 @@
+//===- RTTICrossDylibTest.cpp ---------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Confirms that RTTI identity (isA<>, and casting on the strength of it)
+// survives a real cross-library boundary: RTTICrossDylibTestLib links its
+// own copy of orc-rt-bedrock, so the CrossDylibTestError it constructs has a
+// LibraryID distinct from this binary's. That forces isA<>() through the
+// strcmp fallback (see RTTIRoot::isA in orc-rt/support/RTTI.h) rather than
+// the same-library pointer-equality fast path.
+//
+// CoreTests links RTTICrossDylibTestLib directly (see CMakeLists.txt), so
+// the two get their own LibraryIDs the same way any two independently-linked
+// consumers of orc-rt would; nothing here needs to touch dlopen/dlsym.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../Inputs/RTTICrossDylibTestError.h"
+#include "../Inputs/RTTICrossDylibTestLib.h"
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+using orc_rt_test::CrossDylibTestError;
+
+namespace {
+
+class UnrelatedError : public ErrorExtends<UnrelatedError, ErrorInfoBase> {
+public:
+  static constexpr const char *RTTIName =
+      "orc_rt_test::RTTICrossDylibTest_UnrelatedError";
+  std::string toString() const noexcept override { return {}; }
+};
+
+} // namespace
+
+TEST(RTTICrossDylibTest, LibraryIDsAreDistinct) {
+  // Sanity-check the premise of every other test in this file: the two
+  // libraries must genuinely have different LibraryIDs, or the tests below
+  // would only ever exercise the same-library fast path.
+  CrossDylibTestError Local(0);
+  EXPECT_NE(Local.libraryID(), rttiCrossDylibTest_libraryID());
+}
+
+TEST(RTTICrossDylibTest, IsACrossesLibraryBoundary) {
+  ErrorInfoBase *E = rttiCrossDylibTest_makeError(42);
+  ASSERT_NE(E, nullptr);
+
+  EXPECT_TRUE(E->isA<CrossDylibTestError>());
+
+  rttiCrossDylibTest_destroyError(E);
+}
+
+TEST(RTTICrossDylibTest, IsABaseTypeCrossesLibraryBoundary) {
+  ErrorInfoBase *E = rttiCrossDylibTest_makeError(42);
+  ASSERT_NE(E, nullptr);
+
+  // Unlike the leaf-type query above, these succeed only by walking up the
+  // hierarchy: differentDylibIsA has to recurse into ParentT after its own
+  // strcmp fails. Every other cross-library check here either matches on the
+  // first comparison or fails at all of them, so this is the only one that
+  // sees the recursion return true across an image boundary.
+  EXPECT_TRUE(E->isA<ErrorInfoBase>());
+  EXPECT_TRUE(E->isA<RTTIRoot>());
+
+  rttiCrossDylibTest_destroyError(E);
+}
+
+TEST(RTTICrossDylibTest, CastAfterIsACrossesLibraryBoundary) {
+  ErrorInfoBase *E = rttiCrossDylibTest_makeError(42);
+  ASSERT_NE(E, nullptr);
+  ASSERT_TRUE(E->isA<CrossDylibTestError>());
+
+  // isA<>() returning true is a contract that E is genuinely a
+  // CrossDylibTestError -- the same C++ class, defined once in the shared
+  // header and compiled into both libraries -- so casting back and calling a
+  // real method on it must work, not just the boolean check.
+  auto *Cast = static_cast<CrossDylibTestError *>(E);
+  EXPECT_EQ(Cast->getCode(), 42);
+  EXPECT_EQ(Cast->toString(), "CrossDylibTestError(42)");
+
+  rttiCrossDylibTest_destroyError(E);
+}
+
+TEST(RTTICrossDylibTest, IsANotFooledByWrongType) {
+  ErrorInfoBase *E = rttiCrossDylibTest_makeError(7);
+  ASSERT_NE(E, nullptr);
+
+  EXPECT_FALSE(E->isA<UnrelatedError>());
+  EXPECT_TRUE(E->isA<CrossDylibTestError>());
+
+  rttiCrossDylibTest_destroyError(E);
+}
diff --git a/orc-rt/test/unit/support/RTTITest.cpp b/orc-rt/test/unit/support/RTTITest.cpp
index 0cd85a22f978a..3dae0df02690a 100644
--- a/orc-rt/test/unit/support/RTTITest.cpp
+++ b/orc-rt/test/unit/support/RTTITest.cpp
@@ -19,11 +19,20 @@ using namespace orc_rt;
 
 namespace {
 
-class MyBase : public RTTIExtends<MyBase, RTTIRoot> {};
-
-class MyDerivedA : public RTTIExtends<MyDerivedA, MyBase> {};
-
-class MyDerivedB : public RTTIExtends<MyDerivedB, MyBase> {};
+class MyBase : public RTTIExtends<MyBase, RTTIRoot> {
+public:
+  static constexpr const char *RTTIName = "::MyBase";
+};
+
+class MyDerivedA : public RTTIExtends<MyDerivedA, MyBase> {
+public:
+  static constexpr const char *RTTIName = "::MyDerivedA";
+};
+
+class MyDerivedB : public RTTIExtends<MyDerivedB, MyBase> {
+public:
+  static constexpr const char *RTTIName = "::MyDerivedB";
+};
 
 } // namespace
 



More information about the llvm-commits mailing list