[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