diff --git a/llvm/docs/HowToSetUpLLVMStyleRTTI.rst b/llvm/docs/HowToSetUpLLVMStyleRTTI.rst --- a/llvm/docs/HowToSetUpLLVMStyleRTTI.rst +++ b/llvm/docs/HowToSetUpLLVMStyleRTTI.rst @@ -412,3 +412,58 @@ #. For each class in the hierarchy that has children, implement a ``classof`` that checks a range of the first child's ``Kind`` and the last child's ``Kind``. + +RTTI for Open Class Hierarchies +=============================== + +Sometimes it is not possible to know all types in a hierarchy ahead of time. +For example, in the shapes hierarchy described above the authors may have +wanted their code to work for user defined shapes too. To support use cases +that require open hierarchies LLVM provides the ``RTTIRoot`` and +``RTTIExtends`` utilities. + +The ``RTTIRoot`` class describes an interface for performing RTTI checks. The +``RTTIExtends`` class template provides an implementation of this interface +for classes derived from ``RTTIRoot``. ``RTTIExtends`` uses the "`Curiously +Recurring Template Idiom`_", taking the class being defined as its first +template argument and the parent class as the second argument. Any class that +uses ``RTTIExtends`` must define a ``static char ID`` member, the address of +which will be used to identify the type. + +This open-hierarchy RTTI support should only be used if your use case requries +it. Otherwise the standard LLVM RTTI system should be preferred. + +.. _`Curiously Recurring Template Idiom`: +https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern + +E.g. + +.. code-block:: c++ + + class Shape : public RTTIExtends { + public: + static char ID; + virtual double computeArea() = 0; + }; + + class Square : public RTTIExtends { + double SideLength; + public: + static char ID; + + Square(double S) : SideLength(S) {} + double computeArea() override; + }; + + class Circle : public RTTIExtends { + double Radius; + public: + static char ID; + + Circle(double R) : Radius(R) {} + double computeArea() override; + }; + + char Shape::ID = 0; + char Square::ID = 0; + char Circle::ID = 0; diff --git a/llvm/include/llvm/Support/ExtensibleRTTI.h b/llvm/include/llvm/Support/ExtensibleRTTI.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Support/ExtensibleRTTI.h @@ -0,0 +1,135 @@ +//===-- llvm/Support/ExtensibleRTTI.h - ExtensibleRTTI support --*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// \file +// +// Defines an extensible RTTI mechanism designed to work with Casting.h. +// +// Extensible RTTI differs from LLVM's primary RTTI mechanism (see +// llvm.org/docs/HowToSetUpLLVMStyleRTTI.html) by supporting open type +// hierarchies, where new types can be added from outside libraries without +// needing to change existing code. LLVM's primary RTTI mechanism should be +// preferred where possible, but where open hierarchies are needed this system +// can be used. +// +// The RTTIRoot class defines methods for comparing type ids. Implementations +// of these methods can be injected into new classes using the RTTIExtends +// class template. +// +// E.g. +// +// @code{.cpp} +// class MyBaseClass : public RTTIExtends { +// public: +// static char ID; +// virtual void foo() = 0; +// }; +// +// class MyDerivedClass1 : public RTTIExtends { +// public: +// static char ID; +// void foo() override {} +// }; +// +// class MyDerivedClass2 : public RTTIExtends { +// public: +// static char ID; +// void foo() override {} +// }; +// +// char MyBaseClass::ID = 0; +// char MyDerivedClass1::ID = 0; +// char MyDerivedClass2:: ID = 0; +// +// void fn() { +// std::unique_ptr B = llvm::make_unique(); +// llvm::outs() << isa(B) << "\n"; // Outputs "1". +// llvm::outs() << isa(B) << "\n"; // Outputs "1". +// llvm::outs() << isa(B) << "\n"; // Outputs "0'. +// } +// +// @endcode +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_EXTENSIBLERTTI_H +#define LLVM_SUPPORT_EXTENSIBLERTTI_H + +namespace llvm { + +template class RTTIExtends; + +/// Base class for the extensible RTTI hierarchy. +/// +/// This class defines virtual methods, dynamicClassID and isA, that enable +/// type comparisons. +class RTTIRoot { +public: + virtual ~RTTIRoot() = default; + + /// Returns the class ID for this type. + static const void *classID() { return &ID; } + + /// Returns the class ID for the dynamic type of this RTTIRoot instance. + virtual const void *dynamicClassID() const = 0; + + /// Returns true if this class's ID matches the given class ID. + virtual bool isA(const void *const ClassID) const { + return ClassID == classID(); + } + + /// Check whether this instance is a subclass of QueryT. + template + bool isA() const { return isA(QueryT::classID()); } + +private: + virtual void anchor(); + + static char ID; +}; + +/// Inheritance utility for extensible RTTI. +/// +/// Supports single inheritance only: A class can only have one +/// ExtensibleRTTI-parent (i.e. a parent for which the isa<> test will work), +/// though it can have many non-ExtensibleRTTI parents. +/// +/// RTTIExtents uses CRTP so the first template argument to RTTIExtends is the +/// newly introduced type, and the *second* argument is the parent class. +/// +/// class MyType : public RTTIExtends { +/// public: +/// static char ID; +/// }; +/// +/// class MyDerivedType : public RTTIExtends { +/// public: +/// static char ID; +/// }; +/// +template +class RTTIExtends : public ParentT { +public: + // Inherit constructors from ParentT. + using ParentT::ParentT; + + static const void *classID() { return &ThisT::ID; } + + const void *dynamicClassID() const override { return &ThisT::ID; } + + bool isA(const void *const ClassID) const override { + return ClassID == classID() || ParentT::isA(ClassID); + } + + static bool classof(const RTTIRoot *R) { return R->isA(); } +}; + +} // end namespace llvm + +#endif // LLVM_SUPPORT_EXTENSIBLERTTI_H diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -91,6 +91,7 @@ ELFAttributes.cpp Error.cpp ErrorHandling.cpp + ExtensibleRTTI.cpp FileCheck.cpp FileCollector.cpp FileUtilities.cpp diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -32,6 +32,7 @@ ErrnoTest.cpp ErrorOrTest.cpp ErrorTest.cpp + ExtensibleRTTITest.cpp FileCheckTest.cpp FileCollectorTest.cpp FileOutputBufferTest.cpp diff --git a/llvm/unittests/Support/ExtensibleRTTITest.cpp b/llvm/unittests/Support/ExtensibleRTTITest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/ExtensibleRTTITest.cpp @@ -0,0 +1,86 @@ +//===------ unittests/ExtensibleRTTITest.cpp - Extensible RTTI Tests ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/ExtensibleRTTI.h" +#include "llvm/Support/Casting.h" + +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +class MyBaseType : public RTTIExtends { +public: + static char ID; +}; + +class MyDerivedType : public RTTIExtends { +public: + static char ID; +}; + +class MyOtherDerivedType : public RTTIExtends { +public: + static char ID; +}; + +class MyDeeperDerivedType + : public RTTIExtends { +public: + static char ID; +}; + +char MyBaseType::ID = 0; +char MyDerivedType::ID = 0; +char MyOtherDerivedType::ID = 0; +char MyDeeperDerivedType::ID = 0; + +TEST(ExtensibleRTTI, isa) { + MyBaseType B; + MyDerivedType D; + MyDeeperDerivedType DD; + + EXPECT_TRUE(isa(B)); + EXPECT_FALSE(isa(B)); + EXPECT_FALSE(isa(B)); + EXPECT_FALSE(isa(B)); + + EXPECT_TRUE(isa(D)); + EXPECT_TRUE(isa(D)); + EXPECT_FALSE(isa(D)); + EXPECT_FALSE(isa(D)); + + EXPECT_TRUE(isa(DD)); + EXPECT_TRUE(isa(DD)); + EXPECT_FALSE(isa(DD)); + EXPECT_TRUE(isa(DD)); +} + +TEST(ExtensibleRTTI, cast) { + MyDerivedType D; + MyBaseType &BD = D; + + cast(D); + cast(BD); + cast(BD); +} + +TEST(ExtensibleRTTI, dyn_cast) { + MyBaseType B; + MyDerivedType D; + MyBaseType &BD = D; + + EXPECT_EQ(dyn_cast(&B), nullptr); + EXPECT_EQ(dyn_cast(&D), &D); + EXPECT_EQ(dyn_cast(&BD), &BD); + EXPECT_EQ(dyn_cast(&BD), &D); +} + +} // namespace