X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=docs%2FHowToSetUpLLVMStyleRTTI.rst;h=38929948590916ecc1198980e012abe21631295e;hb=deae476f2cdeec046993c8ceb11b6385de524dd0;hp=4c96c95a76f8123b2b314cbb9ea9a07820de57e8;hpb=8a6538cd6117c5a7b1efb5c2e9cf172a5556c23e;p=oota-llvm.git diff --git a/docs/HowToSetUpLLVMStyleRTTI.rst b/docs/HowToSetUpLLVMStyleRTTI.rst index 4c96c95a76f..38929948590 100644 --- a/docs/HowToSetUpLLVMStyleRTTI.rst +++ b/docs/HowToSetUpLLVMStyleRTTI.rst @@ -1,11 +1,7 @@ -.. _how-to-set-up-llvm-style-rtti: - ====================================================== How to set up LLVM-style RTTI for your class hierarchy ====================================================== -.. sectionauthor:: Sean Silva - .. contents:: Background @@ -44,14 +40,14 @@ RTTI for this class hierarchy: double SideLength; public: Square(double S) : SideLength(S) {} - double computeArea() /* override */; + double computeArea() override; }; class Circle : public Shape { double Radius; public: Circle(double R) : Radius(R) {} - double computeArea() /* override */; + double computeArea() override; }; The most basic working setup for LLVM-style RTTI requires the following @@ -65,10 +61,9 @@ steps: #include "llvm/Support/Casting.h" - #. In the base class, introduce an enum which discriminates all of the - different classes in the hierarchy, and stash the enum value somewhere in - the base class. + different concrete classes in the hierarchy, and stash the enum value + somewhere in the base class. Here is the code after introducing this change: @@ -78,8 +73,8 @@ steps: public: + /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) + enum ShapeKind { - + SquareKind, - + CircleKind + + SK_Square, + + SK_Circle + }; +private: + const ShapeKind Kind; @@ -103,7 +98,7 @@ steps: You might wonder why the ``Kind`` enum doesn't have an entry for ``Shape``. The reason for this is that since ``Shape`` is abstract (``computeArea() = 0;``), you will never actually have non-derived - instances of exactly that class (only subclasses). See `Concrete Bases + instances of exactly that class (only subclasses). See `Concrete Bases and Deeper Hierarchies`_ for information on how to deal with non-abstract bases. It's worth mentioning here that unlike ``dynamic_cast<>``, LLVM-style RTTI can be used (and is often used) for @@ -122,8 +117,8 @@ steps: public: /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) enum ShapeKind { - SquareKind, - CircleKind + SK_Square, + SK_Circle }; private: const ShapeKind Kind; @@ -139,16 +134,16 @@ steps: double SideLength; public: - Square(double S) : SideLength(S) {} - + Square(double S) : Shape(SquareKind), SideLength(S) {} - double computeArea() /* override */; + + Square(double S) : Shape(SK_Square), SideLength(S) {} + double computeArea() override; }; class Circle : public Shape { double Radius; public: - Circle(double R) : Radius(R) {} - + Circle(double R) : Shape(CircleKind), Radius(R) {} - double computeArea() /* override */; + + Circle(double R) : Shape(SK_Circle), Radius(R) {} + double computeArea() override; }; #. Finally, you need to inform LLVM's RTTI templates how to dynamically @@ -164,8 +159,8 @@ steps: public: /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.) enum ShapeKind { - SquareKind, - CircleKind + SK_Square, + SK_Circle }; private: const ShapeKind Kind; @@ -179,45 +174,31 @@ steps: class Square : public Shape { double SideLength; public: - Square(double S) : Shape(SquareKind), SideLength(S) {} - double computeArea() /* override */; + Square(double S) : Shape(SK_Square), SideLength(S) {} + double computeArea() override; + + static bool classof(const Shape *S) { - + return S->getKind() == SquareKind; + + return S->getKind() == SK_Square; + } }; class Circle : public Shape { double Radius; public: - Circle(double R) : Shape(CircleKind), Radius(R) {} - double computeArea() /* override */; + Circle(double R) : Shape(SK_Circle), Radius(R) {} + double computeArea() override; + + static bool classof(const Shape *S) { - + return S->getKind() == CircleKind; + + return S->getKind() == SK_Circle; + } }; The job of ``classof`` is to dynamically determine whether an object of - a base class is in fact of a particular derived class. The argument to - ``classof`` should always be an *ancestor* class because the - implementation has logic to allow and optimize away - upcasts/up-``isa<>``'s automatically. It is as though every class - ``Foo`` automatically has a ``classof`` like: + a base class is in fact of a particular derived class. In order to + downcast a type ``Base`` to a type ``Derived``, there needs to be a + ``classof`` in ``Derived`` which will accept an object of type ``Base``. - .. code-block:: c++ - - class Foo { - [...] - static bool classof(const Foo *) { return true; } - [...] - }; - - In order to downcast a type ``Base`` to a type ``Derived``, there needs - to be a ``classof`` in ``Derived`` which will accept an object of type - ``Base``. - - To be concrete, in the following code: + To be concrete, consider the following code: .. code-block:: c++ @@ -226,11 +207,35 @@ steps: /* do something ... */ } - The code of ``isa<>`` will eventually boil down---after template - instantiation and some other machinery---to a check roughly like - ``Circle::classof(S)``. For more information, see + The code of the ``isa<>`` test in this code will eventually boil + down---after template instantiation and some other machinery---to a + check roughly like ``Circle::classof(S)``. For more information, see :ref:`classof-contract`. + The argument to ``classof`` should always be an *ancestor* class because + the implementation has logic to allow and optimize away + upcasts/up-``isa<>``'s automatically. It is as though every class + ``Foo`` automatically has a ``classof`` like: + + .. code-block:: c++ + + class Foo { + [...] + template + static bool classof(const T *, + ::std::enable_if< + ::std::is_base_of::value + >::type* = 0) { return true; } + [...] + }; + + Note that this is the reason that we did not need to introduce a + ``classof`` into ``Shape``: all relevant classes derive from ``Shape``, + and ``Shape`` itself is abstract (has no entry in the ``Kind`` enum), + so this notional inferred ``classof`` is all we need. See `Concrete + Bases and Deeper Hierarchies`_ for more information about how to extend + this example to more general hierarchies. + Although for this small example setting up LLVM-style RTTI seems like a lot of "boilerplate", if your classes are doing anything interesting then this will end up being a tiny fraction of the code. @@ -240,16 +245,25 @@ Concrete Bases and Deeper Hierarchies For concrete bases (i.e. non-abstract interior nodes of the inheritance tree), the ``Kind`` check inside ``classof`` needs to be a bit more -complicated. Say that ``SpecialSquare`` and ``OtherSpecialSquare`` derive +complicated. The situation differs from the example above in that + +* Since the class is concrete, it must itself have an entry in the ``Kind`` + enum because it is possible to have objects with this class as a dynamic + type. + +* Since the class has children, the check inside ``classof`` must take them + into account. + +Say that ``SpecialSquare`` and ``OtherSpecialSquare`` derive from ``Square``, and so ``ShapeKind`` becomes: .. code-block:: c++ enum ShapeKind { - SquareKind, - + SpecialSquareKind, - + OtherSpecialSquareKind, - CircleKind + SK_Square, + + SK_SpecialSquare, + + SK_OtherSpecialSquare, + SK_Circle } Then in ``Square``, we would need to modify the ``classof`` like so: @@ -257,11 +271,11 @@ Then in ``Square``, we would need to modify the ``classof`` like so: .. code-block:: c++ - static bool classof(const Shape *S) { - - return S->getKind() == SquareKind; + - return S->getKind() == SK_Square; - } + static bool classof(const Shape *S) { - + return S->getKind() >= SquareKind && - + S->getKind() <= OtherSpecialSquareKind; + + return S->getKind() >= SK_Square && + + S->getKind() <= SK_OtherSpecialSquare; + } The reason that we need to test a range like this instead of just equality @@ -281,6 +295,78 @@ ordering right:: | OtherSpecialSquare | Circle +A Bug to be Aware Of +-------------------- + +The example just given opens the door to bugs where the ``classof``\s are +not updated to match the ``Kind`` enum when adding (or removing) classes to +(from) the hierarchy. + +Continuing the example above, suppose we add a ``SomewhatSpecialSquare`` as +a subclass of ``Square``, and update the ``ShapeKind`` enum like so: + +.. code-block:: c++ + + enum ShapeKind { + SK_Square, + SK_SpecialSquare, + SK_OtherSpecialSquare, + + SK_SomewhatSpecialSquare, + SK_Circle + } + +Now, suppose that we forget to update ``Square::classof()``, so it still +looks like: + +.. code-block:: c++ + + static bool classof(const Shape *S) { + // BUG: Returns false when S->getKind() == SK_SomewhatSpecialSquare, + // even though SomewhatSpecialSquare "is a" Square. + return S->getKind() >= SK_Square && + S->getKind() <= SK_OtherSpecialSquare; + } + +As the comment indicates, this code contains a bug. A straightforward and +non-clever way to avoid this is to introduce an explicit ``SK_LastSquare`` +entry in the enum when adding the first subclass(es). For example, we could +rewrite the example at the beginning of `Concrete Bases and Deeper +Hierarchies`_ as: + +.. code-block:: c++ + + enum ShapeKind { + SK_Square, + + SK_SpecialSquare, + + SK_OtherSpecialSquare, + + SK_LastSquare, + SK_Circle + } + ... + // Square::classof() + - static bool classof(const Shape *S) { + - return S->getKind() == SK_Square; + - } + + static bool classof(const Shape *S) { + + return S->getKind() >= SK_Square && + + S->getKind() <= SK_LastSquare; + + } + +Then, adding new subclasses is easy: + +.. code-block:: c++ + + enum ShapeKind { + SK_Square, + SK_SpecialSquare, + SK_OtherSpecialSquare, + + SK_SomewhatSpecialSquare, + SK_LastSquare, + SK_Circle + } + +Notice that ``Square::classof`` does not need to be changed. + .. _classof-contract: The Contract of ``classof`` @@ -291,9 +377,38 @@ contract for ``classof`` is "return ``true`` if the dynamic type of the argument is-a ``C``". As long as your implementation fulfills this contract, you can tweak and optimize it as much as you want. +For example, LLVM-style RTTI can work fine in the presence of +multiple-inheritance by defining an appropriate ``classof``. +An example of this in practice is +`Decl `_ vs. +`DeclContext `_ +inside Clang. +The ``Decl`` hierarchy is done very similarly to the example setup +demonstrated in this tutorial. +The key part is how to then incorporate ``DeclContext``: all that is needed +is in ``bool DeclContext::classof(const Decl *)``, which asks the question +"Given a ``Decl``, how can I determine if it is-a ``DeclContext``?". +It answers this with a simple switch over the set of ``Decl`` "kinds", and +returning true for ones that are known to be ``DeclContext``'s. + .. TODO:: Touch on some of the more advanced features, like ``isa_impl`` and ``simplify_type``. However, those two need reference documentation in the form of doxygen comments as well. We need the doxygen so that we can say "for full details, see http://llvm.org/doxygen/..." + +Rules of Thumb +============== + +#. The ``Kind`` enum should have one entry per concrete class, ordered + according to a preorder traversal of the inheritance tree. +#. The argument to ``classof`` should be a ``const Base *``, where ``Base`` + is some ancestor in the inheritance hierarchy. The argument should + *never* be a derived class or the class itself: the template machinery + for ``isa<>`` already handles this case and optimizes it. +#. For each class in the hierarchy that has no children, implement a + ``classof`` that checks only against its ``Kind``. +#. 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``.