2015-08-30 43 views
6

Se ho un programma C++ che dichiara un struct, dire:Accesso membri struct e array di struct da LLVM IR

struct S { 
    short s; 
    union U { 
     bool b; 
     void *v; 
    }; 
    U u; 
}; 

e generare qualche LLVM IR tramite la LLVM C++ API per rispecchiare dichiarazione ++ C:

vector<Type*> members; 
members.push_back(IntegerType::get(ctx, sizeof(short) * 8)); 
// since LLVM doesn't support unions, just use an ArrayType that's the same size 
members.push_back(ArrayType::get(IntegerType::get(ctx, 8), sizeof(S::U))); 

StructType *const llvm_S = StructType::create(ctx, "S"); 
llvm_S->setBody(members); 

come posso garantire che sizeof(S) nel codice C++ è la stessa dimensione della StructType nel codice LLVM IR? Lo stesso vale per gli offset dei singoli membri, ovvero .

È anche vero che ho un array di S allocato in C++:

S *s_array = new S[10]; 

e passo s_array di codice LLVM IR in cui accede singoli elementi della matrice. Affinché questo funzioni, sizeof(S) deve essere la stessa sia in C++ e LLVM IR in modo che questo:

%elt = getelementptr %S* %ptr_to_start, i64 1 

accederanno s_array[1] correttamente.

Quando compilare ed eseguire il seguente programma, emette:

sizeof(S) = 16 
allocSize(S) = 10 

Il problema è che manca LLVM 6 byte di riempimento tra S::s e S::u. Il compilatore C++ fa partire union su un limite allineato a 8 byte mentre LLVM no.

Stavo giocando con DataLayout. Per la mia macchina [Mac OS X 10.9.5, Apple ha g ++ LLVM versione 6.0 (clang-600.0.57) (sulla base di LLVM 3.5svn)], se stampare la stringa layout dei dati, ottengo:

e-m:o-i64:64-f80:128-n8:16:32:64-S128 

Se forzo-impostare il layout dei dati a:

e-m:o-i64:64-f80:128-n8:16:32:64-S128-a:64 

cui l'aggiunta è di a:64 che significa che un oggetto di tipo aggregato allinea su un limite di 64 bit, allora ottengo la stessa dimensione. Quindi, perché il layout dei dati predefinito non è corretto?


programma di lavoro completo sotto

// LLVM 
#include <llvm/ExecutionEngine/ExecutionEngine.h> 
#include <llvm/ExecutionEngine/MCJIT.h> 
#include <llvm/IR/DerivedTypes.h> 
#include <llvm/IR/LLVMContext.h> 
#include <llvm/IR/Module.h> 
#include <llvm/IR/Type.h> 
#include <llvm/Support/TargetSelect.h> 

// standard 
#include <iostream> 
#include <memory> 
#include <string> 

using namespace std; 
using namespace llvm; 

struct S { 
    short s; 
    union U { 
     bool b; 
     void *v; 
    }; 
    U u; 
}; 

ExecutionEngine* createEngine(Module *module) { 
    InitializeNativeTarget(); 
    InitializeNativeTargetAsmPrinter(); 

    unique_ptr<Module> u(module); 
    EngineBuilder eb(move(u)); 
    string errStr; 
    eb.setErrorStr(&errStr); 
    eb.setEngineKind(EngineKind::JIT); 
    ExecutionEngine *const exec = eb.create(); 
    if (!exec) { 
     cerr << "Could not create ExecutionEngine: " << errStr << endl; 
     exit(1); 
    } 
    return exec; 
} 

int main() { 
    LLVMContext ctx; 

    vector<Type*> members; 
    members.push_back(IntegerType::get(ctx, sizeof(short) * 8)); 
    members.push_back(ArrayType::get(IntegerType::get(ctx, 8), sizeof(S::U))); 

    StructType *const llvm_S = StructType::create(ctx, "S"); 
    llvm_S->setBody(members); 

    Module *const module = new Module("size_test", ctx); 
    ExecutionEngine *const exec = createEngine(module); 
    DataLayout const *const layout = exec->getDataLayout(); 
    module->setDataLayout(layout); 

    cout << "sizeof(S) = " << sizeof(S) << endl; 
    cout << "allocSize(S) = " << layout->getTypeAllocSize(llvm_S) << endl; 

    delete exec; 
    return 0; 
} 
+0

Che dire di [getTypeAllocSize()] (http://llvm.org/docs/doxygen/html/classllvm_1_1DataLayout.html#a1d6fcc02e91ba24510aba42660c90e29)? –

+0

OK, questo mi dice quanto è grande. In questo caso, le dimensioni non corrispondono a _non_. Quindi, come faccio a farli corrispondere? –

risposta

4

Poiché la risposta originale è la risposta corretta alla domanda "pre-modifica", sto scrivendo una risposta completamente nuova alla nuova domanda (e la mia ipotesi che le strutture non siano effettivamente le stesse era piuttosto buona).

Il problema non riguarda DataLayout in quanto tale [ma è necessario il DataLayout per risolvere il problema, quindi è necessario aggiornare il codice per creare il modulo prima di iniziare a creare LLVM-IR], ma il fatto che si sono combinando un union che ha restrizioni di allineamento in un struct con restrizioni di allineamento minori:

struct S { 
    short s;  // Alignment = 2 
    union U {  
     bool b;  // Alignment = 1 
     void *v; // Alignment = 4 or 8 
    }; 
    U u;   // = Alignment = 4 or 8 
}; 

Ora nel tuo LLVM code-gen:

members.push_back(IntegerType::get(ctx, sizeof(short) * 8)); 
members.push_back(ArrayType::get(IntegerType::get(ctx, 8), sizeof(S::U))); 

Il secondo elemento nella tua struct è un 0.123., che ha un requisito di allineamento di 1. Quindi, ovviamente, LLVM allineerà lo struct in modo diverso rispetto al compilatore C++ che ha un criterio di allineamento più severo.

In questo caso particolare, utilizzando un i8 * (alias void *) al posto della matrice di i8 farebbe il trucco [ovviamente con il relativo bitcast tradurre in altri tipi come necessario quando si accede al valore di b]

Per risolvere questo problema, in un modo completamente generico, è necessario produrre un struct costituito dall'elemento con il requisito di allineamento più grande nello union, quindi riempirlo con elementi sufficienti da char per ottenere le dimensioni massime.

Ora ho qualcosa da mangiare, ma tornerò con un codice che lo risolve correttamente, ma è un po 'più complesso di quanto pensassi in origine.

Ecco la main postato sopra modificati per utilizzare un puntatore invece di char array:

int main() { 
    LLVMContext ctx; 

    vector<Type*> members; 
    members.push_back(IntegerType::get(ctx, sizeof(short) * 8)); 
    members.push_back(PointerType::getUnqual(IntegerType::get(ctx, 8))); 

    StructType *const llvm_S = StructType::create(ctx, "S"); 
    llvm_S->setBody(members); 

    Module *const module = new Module("size_test", ctx); 
    ExecutionEngine *const exec = createEngine(module); 
    DataLayout const *const layout = exec->getDataLayout(); 
    module->setDataLayout(*layout); 

    cout << "sizeof(S) = " << sizeof(S) << endl; 
    cout << "allocSize(S) = " << layout->getTypeAllocSize(llvm_S) << endl; 

    delete exec; 
    return 0; 
} 

Ci sono anche alcuni piccoli cambiamenti per coprire il fatto che setDataLayout è cambiato tra la versione di LLVM e quello che ho sto usando.

E infine una versione generica che permette a qualsiasi tipo da utilizzare:

Type* MakeUnionType(Module* module, LLVMContext& ctx, vector<Type*> um) 
{ 
    const DataLayout dl(module); 
    size_t maxSize = 0; 
    size_t maxAlign = 0; 
    Type* maxAlignTy = 0; 

    for(auto m : um) 
    { 
     size_t sz = dl.getTypeAllocSize(m); 
     size_t al = dl.getPrefTypeAlignment(m); 
     if(sz > maxSize) 
      maxSize = sz; 
     if(al > maxAlign) 
     { 
      maxAlign = al; 
      maxAlignTy = m; 
     } 
    } 
    vector<Type*> sv = { maxAlignTy }; 
    size_t mas = dl.getTypeAllocSize(maxAlignTy); 
    if(mas < maxSize) 
    { 
     size_t n = maxSize - mas; 
     sv.push_back(ArrayType::get(IntegerType::get(ctx, 8), n)); 
    } 
    StructType* u = StructType::create(ctx, "U"); 
    u->setBody(sv); 
    return u; 
} 

int main() { 
    LLVMContext ctx; 

    Module *const module = new Module("size_test", ctx); 
    ExecutionEngine *const exec = createEngine(module); 
    DataLayout const *const layout = exec->getDataLayout(); 
    module->setDataLayout(*layout); 

    vector<Type*> members; 
    members.push_back(IntegerType::get(ctx, sizeof(short) * 8)); 
    vector<Type*> unionMembers = { PointerType::getUnqual(IntegerType::get(ctx, 8)), 
        IntegerType::get(ctx, 1) }; 
    members.push_back(MakeUnionType(module, ctx, unionMembers)); 

    StructType *const llvm_S = StructType::create(ctx, "S"); 
    llvm_S->setBody(members); 

    cout << "sizeof(S) = " << sizeof(S) << endl; 
    cout << "allocSize(S) = " << layout->getTypeAllocSize(llvm_S) << endl; 

    delete exec; 
    return 0; 
} 

Si noti che in entrambi i casi, è necessario un intervento bitcast per convertire l'indirizzo di b - e nel secondo caso, è anche è necessario un bitcast per convertire struct in void *, ma supponendo che si desideri effettivamente il supporto generico union, sarebbe come si dovrebbe farlo comunque.

un pezzo completo di codice per generare un tipo di union può essere trovato qui, che è per il mio Pascal compilatore variant [che è il modo di Pascal per fare un union]:

https://github.com/Leporacanthicus/lacsap/blob/master/types.cpp#L525 e la generazione di codice tra cui bitcast: https://github.com/Leporacanthicus/lacsap/blob/master/expr.cpp#L520

+0

Sì, sembra funzionare. :) Ma perché ci sono discrepanze nelle due versioni del codice? Ad esempio, questa versione controlla 'mas

+0

Poiché il risultato finale è identico, se l'elemento allineato più grande è> = maxSize, non è necessario il riempimento. Se l'elemento con l'allineamento massimo è uguale all'elemento dimensione massima, non è necessario il riempimento. [Penso che il mio codice compilatore possa essere semplificato, per essere onesti, ma sono nel bel mezzo di sistemare altre cose, e non voglio rompere le cose solo per rimuovere tre righe di codice che non sono strettamente necessarie ma funziona] –

+0

Per inciso, se volessi che il mio 'struct' contenga, per esempio,' std :: string', preferirei _non_ dover descrivere in modo elaborato il suo layout (che sarebbe comunque dipendente dall'implementazione). Penso che sarebbe possibile fare qualcosa di simile a 'union' avendo una struct template che contiene un tipo T (di nuovo, per esempio' std :: string') e calcoliamo il suo offset e quindi costruendo un 'StructType' di conseguenza. Pensieri? –

1

Lo scopo principale di DataLayout è quello di conoscere l'allineamento degli elementi. Se non hai bisogno di conoscere la dimensione, l'allineamento o gli offset degli elementi nel tuo codice [e LLVM non ha davvero un modo utile oltre l'istruzione GEP per trovare l'offset, quindi puoi quasi ignorare la parte offset], tu non avrà bisogno di un datalayout fino a quando non arriverete ad eseguire (o generare file oggetto) dall'IR.

(Ho avuto alcuni bug molto interessanti dal tentativo di compilare il codice a 32 bit con un datalayout "nativo" a 64 bit quando ho implementato l'opzione -m32 per il mio compilatore - non è una buona idea cambiare DataLayout nel mezzo di compilazione, cosa che ho fatto perché ho usato quello "predefinito" e ne ho impostato uno diverso quando si trattava di creare il vero file oggetto).

+0

La mia domanda diceva che ho bisogno di ottenere le dimensioni e gli allineamenti corretti. –

+0

Ma ancora, il DataLayout conta solo al punto di generare codice o ottenere le dimensioni, quindi penso che sia necessario mostrare un po 'più di codice, o spiegare un po' più esattamente ciò che NON funziona. Come è sbagliato? Cosa succede e in che cosa è diverso da ciò che ti aspetti? Qual è il 'sizeof (S)' in C++, e qual è 'DataLayout :: getTypeAllocSize (t)'? Cos'è 'DataLayout :: getPrefTypeAlignment (x);', dove 'x' è gli elementi di' t'? –

+0

La mia vera struttura è un po 'più complicata rispetto all'esempio di giocattolo mostrato qui. Basti dire che 'sizeof (S)' <'DataLayout :: getTypeAllocSize (s)' dove 's' è un'istanza LLVM di un equivalente' StructType'. Ho bisogno che quest'ultimo abbia le stesse dimensioni del primo, quindi gli oggetti C++ creati dal compilatore C++ possono essere utilizzati dal codice JIT di LLVM IR. E il punto fondamentale è generare codice IR JIT'LVM che possa interagire con il codice generato dal compilatore C++. –