There are several reasons why LLVM launches its own RTTI system. This system is simple and powerful and is described in the LLVM Programmer's Guide . As another poster noted, coding standards poses two main problems with C ++ RTTI: 1) the cost of space and 2) the low performance of using it.
The bulk cost of RTTI is quite high: each class with vtable (at least one virtual method) receives RTTI information, which includes the name of the class and information about its base classes. This information is used to implement the typeid operator as well as dynamic_cast . Since this cost is paid for each class using vtable (and no, PGO and connection time optimization do not help, because vtable indicates RTTI information) LLVM builds with -fno-rtti. Empirically, this saves about 5-10% of the executable size, which is quite significant. LLVM does not need the equivalent of typeid, so storing names (among other things in type_info) for each class is just a waste of space.
Poor performance is pretty simple if you benchmark or look at the code generated for simple operations. The LLVM is & <> operator is usually compiled to a single load and compared to a constant (although classes control this based on how they implement their class method). Here is a trivial example:
#include "llvm/Constants.h" using namespace llvm; bool isConstantInt(Value *V) { return isa<ConstantInt>(V); }
Compiled for:
$ clang t.cc -S -o - -O3 -I $ HOME / llvm / include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -mkernel -fomit-frame-pointer
...
__Z13isConstantIntPN4llvm5ValueE:
cmpb $ 9, 8 (% rdi)
sete% al
movzbl% al,% eax
ret
which (if you are not reading the assembly) is a load and is compared with a constant. In contrast, the equivalent with dynamic_cast:
#include "llvm/Constants.h" using namespace llvm; bool isConstantInt(Value *V) { return dynamic_cast<ConstantInt*>(V) != 0; }
which compiles to:
clang t.cc -S -o - -O3 -I $ HOME / llvm / include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -mkernel -fomit-frame-pointer
...
__Z13isConstantIntPN4llvm5ValueE:
pushq% rax
xorb% al,% al
testq% rdi,% rdi
je LBB0_2
xorl% esi,% esi
movq $ -1,% rcx
xorl% edx,% edx
callq ___dynamic_cast
testq% rax,% rax
setne% al
LBB0_2:
movzbl% al,% eax
popq% rdx
ret
This is a lot more code, but the killer is a __dynamic_cast call, which then needs to understand the RTTI data structures and make a very general, dynamically calculated pass through this material. This is several orders of magnitude slower than load and comparison.
Alright, alright, so it's slower, why does it matter? This matters because LLVM performs many type checks. Many parts of optimizers are built around a template that matches specific constructs in the code and performs permutations on them. For example, here is some code for matching a simple pattern (which already knows that Op0 / Op1 are the left and right sides of an integer subtraction operation):
// (X*2) - X -> X if (match(Op0, m_Mul(m_Specific(Op1), m_ConstantInt<2>()))) return Op1;
The match operator and m_ * are pattern templates that come down to a series of isa / dyn_cast calls, each of which must perform a type check. Using dynamic_cast for this kind of fine-grained pattern matching would be fierce and exponentially slow.
Finally, there is another point, which is one of expressiveness. the different rtti operators used by LLVM are used to express different things: type checking, dynamic_cast, forced (statement), null reference, etc. C ++ dynamic_cast does not offer (initially) any of these functions.
After all, there are two ways to look at this situation. On the other hand, C ++ RTTI is too narrowly defined for what many people want (full reflection), and too slow to be useful even for simple things like LLVM. On the plus side, the C ++ language is powerful enough so that we can define abstractions like this, like library code, and refuse to use the language function. One of my favorite things about C ++ is how powerful and elegant libraries are. RTTI is not even very high among my least favorite C ++ features :)!
-Chris