RTTI draft proposal
Daveed Vandevoorde
daveed at edg.com
Thu Aug 19 15:43:53 UTC 1999
Hi everyone,
Please find hereunder a draft of an RTTI proposal.
(Note: NTBS == Null-terminated byte string.)
Daveed
Run-time type information
=========================
The C++ programming language definition implies that information about types
be available at run time for three distinct purposes:
(a) to support the typeid operator,
(b) to match an exception handler with a thrown object, and
(c) to implement the dynamic_cast operator.
(c) only requires type information about polymorphic class types, but (a) and
(b) may apply to other types as well; for example, when a pointer to an int is
thrown, it can be caught by a handler that catches "int const*".
Open questions
--------------
. What is the type of base class offsets?
. What is the limit on the number of classes?
. Should the common dynamic_cast case be optimized by adding an entry to the
vtable (to indicate a polymorphic base subobject appears on more than one
derivation path to the complete object type)?
Place of emission
-----------------
It is probably desirable to minimize the number of places where a particular
bit of RTTI is emitted. For polymorphic types, a similar problem occurs for
virtual function tables, and hence the information can be appended at the end
of the primary vtable for that type. For other types, they must presumably be
emitted at the location where their use is implied: the object file containing
the typeid, throw or catch. Basic type information (such as for "int",
"bool", etc.) could be kept in the run-time support library but the benefits
of that may be limited.
The typeid operator
-------------------
The typeid operator produces a reference to a std::type_info structure with
the following public interface:
struct std::type_info {
virtual ~type_info();
bool operator==(type_info const&) const;
bool operator!=(type_info const&) const;
bool before(type_info const&) const;
char const* name() const;
};
Assuming that after linking and loading only one type_info structure is active
for any particular type symbol, the equality and inequality operators can be
written as address comparisons: to type_info structures describe the same type
if and only if they are the same structure (at the same address). In a flat
address space (such as that of the IA-64 architecture), the before() member is
also easily written in terms of an address comparison. The only additional
piece of information that is required is the NTBS that encodes the name. The
type_info structure itself can hold a pointer into a read-only segment that
contains the text bytes.
Matching throw expressions with handlers
----------------------------------------
When an object is thrown a copy is made of it and the type of that copy is TT.
A handler that catches type HT will match that throw if:
. HT is equal to TT except that HT may be a reference and that HT may have
top-level cv qualifiers (i.e., HT can be "TT cv", "TT&" or "TT cv&"); or
. HT is a reference to a public and unambiguous base type of TT; or
. HT has a pointer type to which TT can be converted by a standard pointer
conversion (though only public, unambiguous derived-to-base conversions
are permitted) and/or a qualification conversion.
This implies that the type information must keep a description of the public,
unambiguous inheritance relationship of a type, as well as the const and
volatile qualifications applied to types.
The dynamic_cast operator
-------------------------
Although dynamic_cast can work on pointers and references, from the point of
view of representation we need only to worry about polymorphic class types.
Also, some kinds of dynamic_cast operations are handled at compile time and do
not need any RTTI. There are then three kinds of truly dynamic cast
operations:
. dynamic_cast<void cv*>, which returns a pointer to the complete lvalue,
. dynamic_cast operation from a base class to a derived class, and
. dynamic_cast across the hierarchy which can be seen as a cast to the
complete lvalue and back to a sibling base.
RTTI layout
-----------
0. The RTTI layout for a given type depends on whether a 32-bit or 64-bit
mode is in effect.
1. Every vtable shall contain one entry describing the offset from a vptr
for that vtable to the origin of the object containing that vptr (or
equivalently: to the vptr for the primary vtable). This entry is directly
useful to implement dynamic_cast<void cv*>, but is also needed for the other
truly dynamic casts.
2. Every vtable shall contain one entry pointing to an object derived from
std::extended_type_info; the possible types are:
. std::__fundamental_type_info
. std::__pointer_type_info
. std::__reference_type_info
. std::__array_type_info
. std::__function_type_info
. std::__enum_type_info
. std::__class_type_info
. std::__qualifier_type_info
std::extended_type_info derives from std::type_info but adds no fields to the
latter; it is introduced solely to follow the suggestion of the C++ standard.
3. std::type_info contains just two pointers:
. its vptr
. a pointer to a NTBS representing the name of the type
4. std::__pointer_type_info adds two fields:
. a word describing the cv-qualification of what is pointed to
(e.g., "int volatile*" should have the "volatile" bit set in that word)
. a pointer to the std::type_info derivation for the unqualified type
being pointed to
Note that the first bits should not be folded into the pointer because we may
eventually need more qualifier bits (e.g. for "restrict").
5. std::__reference_type_info is similar to std::__pointer_type_info but
describes references.
6. std::__qualifier_type_info is similar to std::__pointer_type_info but
describes top level qualifiers as in "int const" and "char *const".
7. std::__class_type_info introduces a variable length structure.
The variable part that follows consists of a sequence of base class
descriptions having the following structure:
struct std::__base_class_info {
std::type_info *type; /* Null if unused. */
std::ptrdiff_t offset;
std::__base_class_index next; /* Hash table link. */
int is_direct: 1;
int is_floating: 1; /* I.e., virtual or base of virtual subobject. */
int is_virtual: 1; /* Implies is_floating. */
int is_shared: 1; /* Implies is_floating and the virtual subobject
appears on multiple derivation paths. */
int is_accessible: 1;
int is_ambiguous: 1;
};
The fixed length introduction adds the following fields to std::type_info:
. a word with flags describing details about the class such as whether
it is a class/struct/union and whether it is polymorphic.
. a hashvalue that can be used for quick lookups in a variable length
structure describing base classes.
. the number of base class descriptions that follow it (a power of two).
std::type_info::name()
----------------------
The NTBS returned by this routine is the mangled name of the type.
The dynamic_cast algorithm
--------------------------
Dynamic casts to "void cv*" are inserted inline at compile time. So are
dynamic casts of null pointers and dynamic casts that are really static.
This leaves the following test to be implemented in the run-time library for
truly dynamic casts of the form "dynamic_cast<T>(v)":
(see [expr.dynamic_cast] 5.2.7/8)
. If, in the most derived object pointed (referred) to by v, v points
(refers) to a public base class sub-object of a T object [note: this can
be checked at compile time], and if only one object of type T is derived
from the sub-object pointed (referred) to by v, the result is a pointer
(an lvalue referring) to that T object.
. Otherwise, if v points (refers) to a public base class sub-object of the
most derived object, and the type of the most derived object has an
unambiguous public base class of type T, the result is a pointer (an
lvalue referring) to the T sub-object of the most derived object.
. Otherwise, the run-time check fails.
The first check corresponds to a "base-to-derived cast" and the second to a
"cross cast". These tests are implemented by std::__dynamic_cast.
void* std::__dynamic_cast(void *sub, std::__class_type_info *src,
std::__class_type_info *dst,
std::ptrdiff_t src2dst_offset) {
// Pick up vtable pointer from given object:
void *vptr = *(void**)sub;
if (src2dst_offset>=0 && NO_VBASE(sub, vptr)) {
// If the type of "v" was not an accessible nonvirtual base type of
// "T", src2dst_offset should have been set to -1 if it was an
// accessible but floating base of "T" and to -2 if it was not at all
// an accessible base of "T".
// In addition, the vtable should contain an entry to indicate that
// the complete object has no virtual bases (e.g., a count of the
// vbase locator entries).
return (char*)sub+src2dst_offset;
} else {
// Slower case
void *result = 0;
if (src2dst_offset==-1) {
// Possibly a "floating" base-to-derived cast:
result = floating_base2derived(sub, src, dst);
}
if (result==0) {
// The base-to-derived case did not succeed, so we should attempt
// the cross-cast (which is really a derived-to-base cast from the
// complete object):
result = derived2base(complete, dst);
}
}
return result;
}
The exception handler matching algorithm
----------------------------------------
bool __eh_match(std::type_info *thrown_type,
std::type_info *handled_type) {
if (thrown_type == handled_type) {
return true;
} else if (IS_REFTYPE(handled_type)) {
std::type_info *caught_type = REMOVE_REFTYPE(handled_type);
if (IS_CVQUAL(caught_type)) {
caught_type = REMOVE_CVQUAL(caught_type);
}
return thrown_type==caught_type;
} else if (IS_PTRTYPE(handled_type)) {
cvqual_match(&thrown_type, &handled_type);
if (thrown_type==handled_type) {
return true;
} else if (IS_CLASSTYPE(thrown_type) && IS_CLASSTYPE(handled_type)) {
return derived2base_check(thrown_type, handled_type);
} else {
return false;
}
} else {
return false;
}
}
More information about the cxx-abi-dev
mailing list