2011-05-17 3 views
17

Da http://llvm.org/docs/CodingStandards.html#ci_rtti_exceptionsCome viene implementato LLVM <>?

LLVM fa fare ampio uso di una forma arrotolato a mano di RTTI che utilizzano modelli come isa <>, Cast <>, e dyn_cast <>. Questa forma di RTTI è opt-in e può essere aggiunta a qualsiasi classe. È anche molto più efficiente rispetto a dynamic_cast <>.

Come vengono implementati Isa e gli altri?

+0

È una domanda C# o C++? –

+0

@Will: buona cattura. Ho avuto il tag completamente sbagliato –

+0

evviva.Hai dato un'occhiata alla fonte per i modelli? –

risposta

5

Vorrei menzionare che http://llvm.org/docs/ProgrammersManual.html#isa - questo documento ha qualche descrizione aggiuntiva.

Il codice sorgente di isa, cast e dyn_cast si trova in un singolo file e ha commentato molto.

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

Basta aggiungere roba alla risposta di osgx: in pratica ogni classe dovrebbe implementare il metodo classof(), che fa tutte le cose necessarie. Ad esempio, classof del Valore() di routine assomiglia a questo:

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

Per verificare se abbiamo una classe del tipo appropriato, ogni classe ha la sua ValueID unico. È possibile controllare l'elenco completo di ValueID all'interno del file include/llvm/Value.h. Questo ValueID è utilizzato come segue (estratto da 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; 
    } 

Così, in breve: ogni classe deve implementare il metodo classof(), che esegue la decisione necessaria. L'implementazione in questione è costituita dall'insieme di ValueID univoci. Quindi per implementare classof() si dovrebbe semplicemente confrontare il ValueID dell'argomento con il proprio ValueID.

Se ricordo male, la prima implementazione di isa <> e gli amici sono stati adottati da boost ~ 10 anni fa. In questo momento le implementazioni divergono in modo significativo :)

+2

Una nota: questo sistema funziona alla grande per LLVM perché è autonomo. Non è estensibile al codice arbitrario perché si avrebbero problemi con il coordinamento delle attribuzioni di 'ValueIDs' tra le DLL. –

+0

@Matthieu: sì, questo è corretto. Anche se non vedo alcun problema se si sarebbe in qualche modo mantenere l'elenco di ValueIDs coerente tra tutte le DLL. Inoltre, si può usare altro materiale come ID valore, ad es. l'indirizzo di qualche funzione memeber di classe statica, questo sarà univoco tra diverse DLL –

+0

@Anton: Infatti l'indirizzo di un membro di classe sarebbe probabilmente un ottimo modo per ottenere questo comportamento :) (per DLL intendo DLL fornite da varie fonti esterne). –

22

Prima di tutto, il sistema LLVM è estremamente specifico e non è affatto una sostituzione drop-in per il sistema RTTI.

Locali

  • Per la maggior parte delle classi, non è necessario per generare le informazioni RTTI
  • Quando è necessario, le informazioni che ha senso solo all'interno di una determinata gerarchia
  • Abbiamo esclude multi-eredità questo sistema

Identificare una classe oggetto

Prendere una gerarchia semplice, ad esempio:

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

Creeremo un enum specifico per questa gerarchia, con un membro enum per ciascuno degli Stati gerarchia che può essere un'istanza (gli altri sarebbe inutili).

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

Poi, la classe Base sarà aumentata con un metodo che restituirà questo enum.

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

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

    BaseId getValueID() const { return Id; } 

    BaseId Id; 
}; 

E ogni classe concreta è aumentata troppo, in questo modo:

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

Ora, è possibile, semplicemente, per interrogare il tipo esatto, per la fusione.

Nascondere i dettagli di implementazione

Avendo gli utenti murking con getValueID sarebbe problematico, però, così in questo LLVM è nascosto con l'uso di metodi di classof.

Una determinata classe deve implementare due metodi classof: uno per la sua base più profonda (con un test dei valori appropriati di BaseId) e uno per sé (ottimizzazione pura). Per esempio:

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

In questo modo, siamo in grado di verificare se un cast è possibile o non attraverso i modelli:

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

Immaginate per un momento che To è MostDerivedL1:

  • se From è MostDerivedL1, quindi invochiamo il primo sovraccarico di classof e funziona
  • se From è altro, quindi invochiamo il secondo sovraccarico di classof e il controllo utilizza l'enum per determinare se il tipo di calcestruzzo corrisponde.

Spero sia più chiaro.

+0

ah ah. Molto più chiaro. Questo non suona affatto automatico. Sembra uno stile consigliato per implementare i propri dati quando si dispone di RTTI. Almeno questo è molto, molto efficiente. +1 e aspetterò di accettarlo nella speranza che qualcuno che lavora al progetto possa fornire ulteriori informazioni –

+1

@ acidzombie24: Anton sta lavorando sia su Clang che su LLVM (e con molta più esperienza di me), mentre io lavoro principalmente su Clang . Forse @Chris Lattner noterà questo post e fornirà la sua risposta;) Non è affatto automatico, che è il punto in realtà, dal momento che non si paga per tutto il tempo che non lo si usa. Tuttavia non consiglierei di generalizzarlo. LLVM e Clang sono progetti molto specifici con esigenze molto specifiche, per la maggior parte dei progetti è sufficiente RTTI. –

+0

Realmente devi fornire una 'bool classof (DerivedLeft const *)' quando fornisci anche una 'bool classof (Base const *)?' Mi chiedo anche perché il caso banale di tipi identici (o convertibili) non sia gestito all'interno di il meccanismo 'isa' e perché' isa_impl :: doit' prende un riferimento const come argomento ma 'classof' prende un puntatore const. –