2011-05-17 9 views
17

De http://llvm.org/docs/CodingStandards.html#ci_rtti_exceptions¿Cómo se implementa LLVM isa <>?

LLVM sí hace uso extensivo de una forma enrollado a mano de RTTI que utilizan plantillas como ISA <>, echados <>, y dyn_cast <>. Esta forma de RTTI es opt-in y se puede agregar a cualquier clase. También es sustancialmente más eficiente que dynamic_cast <>.

¿Cómo se implementa isa y los demás?

+0

¿Es esta una pregunta de C# o C++? –

+0

@Will: Buena captura. Tenía las etiquetas completamente equivocadas –

+0

aplausos.¿Has echado un vistazo a la fuente de las plantillas? –

Respuesta

5

Debo mencionar que http://llvm.org/docs/ProgrammersManual.html#isa - este documento tiene alguna descripción adicional.

El código fuente de isa, cast y dyn_cast se encuentra en un solo archivo, y se comenta mucho.

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 } 
6

Simplemente añadiendo cosas a la respuesta de osgx: básicamente, cada clase debe implementar el método classof() que hace todo el material necesario. Por ejemplo, classof del Valor() rutina se ve así:

// Methods for support type inquiry through isa, cast, and dyn_cast: 
    static inline bool classof(const Value *) { 
    return true; // Values are always values. 
    } 

Para comprobar si tenemos una clase del tipo apropiado, cada clase tiene su ValueID único. Puede consultar la lista completa de ValueID dentro del archivo include/llvm/Value.h. Este ValueID se utiliza de la siguiente manera (extracto de 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; 
    } 

Así, en pocas palabras: cada clase debe implementar el método classof() que realiza la decisión necesaria. La implementación en cuestión consiste en el conjunto de ValueID únicos. Por lo tanto, para implementar classof() uno debería simplemente comparar el ValueID del argumento con el propio ValueID.

Si mal no recuerdo, la primera implementación de isa <> y amigos fueron adoptados de boost ~ 10 años atrás. En este momento, las implementaciones divergen significativamente :)

+2

Una nota: este sistema funciona muy bien para LLVM porque es autónomo. No es extensible al código arbitrario porque uno tendría problemas para coordinar las atribuciones de 'ValueIDs' entre las DLL. –

+0

@ Matthieu: sí, esto es correcto. Aunque no veo ningún problema si uno mantiene de alguna manera la lista de ValueID consistente en todas las DLL. Además, uno puede usar otras cosas como ID de valor, p. la dirección de alguna función de memeber de clase estática, esto será único en diferentes DLL –

+0

@Anton: De hecho, la dirección de un miembro de la clase probablemente sería una excelente forma de obtener este comportamiento :) (por DLL me refería a las DLL proporcionadas por varias fuentes externas)) –

22

En primer lugar, el sistema LLVM es extremadamente específico y en absoluto un reemplazo directo para el sistema RTTI.

Local

  • Para la mayoría de las clases, no es necesario para generar información de RTTI
  • Cuando se requiera, la información sólo tiene sentido dentro de una jerarquía dada
  • Nos impide múltiples herencia de este sistema

Identificación de una clase de objeto

Tome una jerarquía simple, por ejemplo:

struct Base {}; /* abstract */ 
struct DerivedLeft: Base {}; /* abstract */ 
struct DerivedRight:Base {}; 
struct MostDerivedL1: DerivedLeft {}; 
struct MostDerivedL2: DerivedLeft {}; 
struct MostDerivedR: DerivedRight {}; 

Vamos a crear una enumeración específica a esta jerarquía, con un miembro de la enumeración de cada uno de los miembro de la jerarquía que se pueden crear instancias (las otras tendrían inútil).

enum BaseId { 
    DerivedRightId, 
    MostDerivedL1Id, 
    MostDerivedL2Id, 
    MostDerivedRId 
}; 

Entonces, la clase Base será reforzado con un método que devuelva esta enumeración.

struct Base { 
    static inline bool classof(Base const*) { return true; } 

    Base(BaseId id): Id(id) {} 

    BaseId getValueID() const { return Id; } 

    BaseId Id; 
}; 

Y cada clase concreta es aumentado también, de esta manera:

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) {} 
}; 

Ahora, es posible, simplemente, para consultar el tipo exacto, para la fundición.

ocultación de detalles de implementación

Tener los usuarios murking con getValueID sería problemático, sin embargo, por lo que en este LLVM está escondida con el uso de métodos classof.

Una clase determinada debe implementar dos métodos classof: uno para su base más profunda (con una prueba de los valores adecuados de BaseId) y uno para sí mismo (optimización pura). Por ejemplo:

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) {} 
}; 

De esta manera, se puede comprobar si un yeso es posible o no a través de las plantillas:

template <typename To, typename From> 
bool isa(From const& f) { 
    return To::classof(&f); 
} 

Imagínese por un momento que To es MostDerivedL1:

  • si From es MostDerivedL1, luego invocamos la primera sobrecarga de classof, y funciona
  • si From es cualquier otra cosa, luego invocamos la segunda sobrecarga de classof, y la verificación usa la enumeración para determinar si el tipo concreto coincide.

Espero que sea más claro.

+0

ah ha. Mucho más claro. Esto no suena automático en absoluto. Suena como un estilo recomendado de implementación de sus propios datos al desconectar RTTI. Al menos esto es muy, muy eficiente. +1 y esperaré a aceptarlo con la esperanza de que alguien que trabaje en el proyecto pueda proporcionar información adicional –

+1

@ acidzombie24: Anton está trabajando en Clang y LLVM (y con mucha más experiencia que yo), mientras que trabajo principalmente en Clang . Tal vez @Chris Lattner notará esta publicación y proporcionará su respuesta;) No es automático en absoluto, que es el punto en realidad, ya que no pagas mientras no lo uses. Sin embargo, no recomendaría generalizarlo. LLVM y Clang son proyectos muy específicos con necesidades muy específicas, para la mayoría de los proyectos el RTTI simple es realmente suficiente. –

+0

¿De verdad tiene que proporcionar una 'bool classof (DerivedLeft const *)' cuando también proporciona una 'bool classof (Base const *)?' También me pregunto por qué el caso trivial de tipos idénticos (o convertibles) no se maneja dentro de la maquinaria 'isa' y por qué' isa_impl :: doit' toma una referencia constante como argumento pero 'classof' toma un puntero const. –

Cuestiones relacionadas