Как реализуется LLVM isa <>?
От http://llvm.org/docs/CodingStandards.html#ci_rtti_exceptions
LLVM широко использует ручная форма RTTI, которые используют шаблоны, такие как isa < > , cast < > и dyn_cast < > . Эта форма RTTI opt-in и может быть добавлен в любой класс. Это также значительно больше эффективнее, чем dynamic_cast < > .
Как реализовано isa и другие?
Ответы
Ответ 1
Прежде всего, система LLVM чрезвычайно специфична и вовсе не является заменой для системы RTTI.
Служебные
- Для большинства классов нет необходимости генерировать информацию RTTI.
- Когда это требуется, информация имеет смысл только в пределах определенной иерархии
- Мы исключаем многоуровневое использование этой системы
Идентификация класса объекта
Возьмите простую иерархию, например:
struct Base {}; /* abstract */
struct DerivedLeft: Base {}; /* abstract */
struct DerivedRight:Base {};
struct MostDerivedL1: DerivedLeft {};
struct MostDerivedL2: DerivedLeft {};
struct MostDerivedR: DerivedRight {};
Мы создадим перечисление, специфичное для этой иерархии, с элементом перечисления для каждого члена иерархии, который может быть создан (остальные будут бесполезны).
enum BaseId {
DerivedRightId,
MostDerivedL1Id,
MostDerivedL2Id,
MostDerivedRId
};
Затем класс Base
будет дополнен методом, который вернет это перечисление.
struct Base {
static inline bool classof(Base const*) { return true; }
Base(BaseId id): Id(id) {}
BaseId getValueID() const { return Id; }
BaseId Id;
};
И каждый конкретный класс также дополняется таким образом:
struct DerivedRight: Base {
static inline bool classof(DerivedRight const*) { return true; }
static inline bool classof(Base const* B) {
switch(B->getValueID()) {
case DerivedRightId: case MostDerivedRId: return true;
default: return false;
}
}
DerivedRight(BaseId id = DerivedRightId): Base(id) {}
};
Теперь можно просто запросить точный тип для кастинга.
Скрытие деталей реализации
Наличие пользователей, несущихся с помощью getValueID
, было бы проблематичным, так как в LLVM это скрыто с использованием методов classof
.
Данный класс должен реализовывать два метода classof
: один для его самой глубокой базы (с проверкой подходящих значений BaseId
) и один для себя (чистая оптимизация). Например:
struct MostDerivedL1: DerivedLeft {
static inline bool classof(MostDerivedL1 const*) { return true; }
static inline bool classof(Base const* B) {
return B->getValueID() == MostDerivedL1Id;
}
MostDerivedL1(): DerivedLeft(MostDerivedL1Id) {}
};
Таким образом, мы можем проверить, возможно ли трансляция через шаблоны:
template <typename To, typename From>
bool isa(From const& f) {
return To::classof(&f);
}
Представьте себе, что To
MostDerivedL1
:
- if
From
is MostDerivedL1
, тогда мы вызываем первую перегрузку classof
, и она работает
- if
From
- это что-то другое, затем мы вызываем вторую перегрузку classof
, а проверка использует перечисление для определения соответствия конкретного типа.
Надеюсь, что это станет понятнее.
Ответ 2
Просто добавьте материал в ответ osgx: в основном каждый класс должен реализовать метод classof(), который делает все необходимое. Например, процедура класса valueof() выглядит так:
// Methods for support type inquiry through isa, cast, and dyn_cast:
static inline bool classof(const Value *) {
return true; // Values are always values.
}
Чтобы проверить, есть ли у нас класс соответствующего типа, каждый класс имеет уникальный идентификатор ValueID. Вы можете проверить полный список ValueID внутри файла include/llvm/Value.h. Этот ValueID используется следующим образом (выдержка из Function.h):
/// Methods for support type inquiry through isa, cast, and dyn_cast:
static inline bool classof(const Function *) { return true; }
static inline bool classof(const Value *V) {
return V->getValueID() == Value::FunctionVal;
}
Итак, одним словом: каждый класс должен реализовать метод classof(), который выполняет необходимое решение. Реализация, о которой идет речь, состоит из набора уникальных идентификаторов ValueID. Таким образом, для реализации classof() нужно просто сравнить ValueID аргумента с собственным ValueID.
Если я правильно помню, первая реализация isa < > и друзей была принята из boost ~ 10 лет назад. В настоящее время реализации значительно отличаются:)
Ответ 3
Я должен упомянуть, что http://llvm.org/docs/ProgrammersManual.html#isa - этот документ содержит некоторое дополнительное описание.
Исходный код isa, cast и dyn_cast находится в одном файле и много прокомментировал.
http://llvm.org/doxygen/Casting_8h_source.html
00047 // isa<X> - Return true if the parameter to the template is an instance of the
00048 // template type argument. Used like this:
00049 //
00050 // if (isa<Type*>(myVal)) { ... }
00051 //
00052 template <typename To, typename From>
00053 struct isa_impl {
00054 static inline bool doit(const From &Val) {
00055 return To::classof(&Val);
00056 }
00057 };
00193 // cast<X> - Return the argument parameter cast to the specified type. This
00194 // casting operator asserts that the type is correct, so it does not return null
00195 // on failure. It does not allow a null argument (use cast_or_null for that).
00196 // It is typically used like this:
00197 //
00198 // cast<Instruction>(myVal)->getParent()
00199 //
00200 template <class X, class Y>
00201 inline typename cast_retty<X, Y>::ret_type cast(const Y &Val) {
00202 assert(isa<X>(Val) && "cast<Ty>() argument of incompatible type!");
00203 return cast_convert_val<X, Y,
00204 typename simplify_type<Y>::SimpleType>::doit(Val);
00205 }
00218 // dyn_cast<X> - Return the argument parameter cast to the specified type. This
00219 // casting operator returns null if the argument is of the wrong type, so it can
00220 // be used to test for a type as well as cast if successful. This should be
00221 // used in the context of an if statement like this:
00222 //
00223 // if (const Instruction *I = dyn_cast<Instruction>(myVal)) { ... }
00224 //
00225
00226 template <class X, class Y>
00227 inline typename cast_retty<X, Y>::ret_type dyn_cast(const Y &Val) {
00228 return isa<X>(Val) ? cast<X, Y>(Val) : 0;
00229 }