2015-08-30 15 views
6

Jeśli mam program C++, który deklaruje struct, powiedzieć:Dostęp do członków struct i tablice kodowanym z LLVM IR

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

i wygenerować jakiś LLVM IR za pomocą LLVM C++ API do lustrzanych deklarację 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); 

Skąd mogę mieć pewność, że sizeof(S) w kodzie C++ jest taki sam rozmiar jak StructType w kodzie LLVM IR? To samo dotyczy przesunięć poszczególnych elementów, tj. u.b.

Jest to również przypadek, że ma szereg S przydzielonych C++

S *s_array = new S[10]; 

i przechodzą s_array kodu LLVM IR, w którym dostęp do poszczególnych elementów tablicy. Aby to działało, sizeof(S) musi być taka sama zarówno w C++ i LLVM IR więc to:

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

uzyska dostęp s_array[1] prawidłowo.

Kiedy skompilować i uruchomić program poniżej, Wyjścia:

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

Problemem jest to, że brakuje LLVM 6 bajtów wypełnienia między S::s i S::u. Kompilator C++ sprawia, że ​​union zaczyna się od granicy wyrównanej do 8 bajtów, podczas gdy LLVM nie.

Bawiłem się z DataLayout. Na moim komputerze [Mac OS X 10.9.5, g ++ Jabłko LLVM w wersji 6.0 (dzyń-600.0.57) (oparty na LLVM 3.5svn)], jeśli mogę wydrukować ciąg układ danych, uzyskać:

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

Jeśli wymusić ustawiony układ danych do:

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

gdzie dodatek jest a:64 co oznacza, że ​​obiekt typu kruszywa wyrównany na granicy 64-bitowej, a następnie pojawia się samą rozmiar. Dlaczego więc domyślny układ danych nie jest poprawny?


Kompletny program roboczy poniżej

// 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

Co z [getTypeAllocSize()] (http://llvm.org/docs/doxygen/html/classllvm_1_1DataLayout.html#a1d6fcc02e91ba24510aba42660c90e29)? –

+0

OK, to mówi mi, jak duży jest. W takim przypadku rozmiary _nie_ pasują. Jak mogę je dopasować? –

Odpowiedz

4

Ponieważ oryginalna odpowiedź jest poprawną odpowiedzią na pytanie "przededycyjna", piszę zupełnie nową odpowiedź na nowe pytanie (i moje przypuszczenie, że te struktury nie są w rzeczywistości takie same, było całkiem niezłe).

Problem polega nie na tym, że DataLayout [ale będziesz potrzebować DataLayout, aby rozwiązać problem, więc musisz zaktualizować kod, aby utworzyć moduł przed rozpoczęciem tworzenia LLVM-IR], ale fakt, że łączą się union który ma ograniczeń dopasowywania w bazie struct z mniejszymi ograniczeniami wyrównanie:

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

teraz w LLVM code-gen:

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

drugi element w swojej struktury jest 0.123., który ma wymóg wyrównania wynoszący 1. Więc, oczywiście, LLVM wyrówna struct inaczej niż kompilator C++, który ma ostrzejsze kryteria wyrównania.

W tym konkretnym przypadku, przy użyciu i8 * (aka void *) zamiast tablicy i8 by rade [oczywiście z odpowiednim bitcast przełożyć do innych typów jako niezbędnych przy dostępie wartość b]

Aby rozwiązać ten problem, w całkowicie ogólny sposób należy wyprodukować element struct składający się z elementu o największym wymogu wyrównania w union, a następnie wstawić go z wystarczającą liczbą elementów char, aby nadrobić największy rozmiar.

Będę miał teraz coś do jedzenia, ale wrócę z pewnym kodem, który rozwiązuje go poprawnie, ale jest nieco bardziej skomplikowany niż początkowo sądziłem.

Oto main pisał powyżej zmodyfikowana użyć wskaźnika zamiast char tablicy:

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

Istnieją również pewne drobne zmiany, aby ukryć fakt, że setDataLayout uległa zmianie między wersją LLVM i jeden ja "Używam.

I wreszcie ogólna wersja, która umożliwia dowolnego typu do stosowania:

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

zauważyć, że w obu przypadkach trzeba operację bitcast przekonwertować adres b - i w drugim przypadku, można również potrzebuję bitcasta, żeby przekonwertować struct na void *, ale zakładając, że faktycznie potrzebujesz ogólnej obsługi union, to tak i tak musiałbyś to zrobić.

Kompletny kawałek kodu do wygenerowania typ union można znaleźć tutaj, co jest dla mojego Pascal kompilator variant [który jest sposób Pascala dokonania union]:

https://github.com/Leporacanthicus/lacsap/blob/master/types.cpp#L525 i generowania kodu w tym bitcast: https://github.com/Leporacanthicus/lacsap/blob/master/expr.cpp#L520

+0

Tak, to wydaje się działać. :) Ale dlaczego istnieją rozbieżności w obu wersjach kodu? Na przykład ta wersja sprawdza 'mas

+0

Ponieważ wynik końcowy jest taki sam - jeśli największy wyrównany element ma wartość> = maxSize, wtedy nie potrzebujemy dopełnienia. Jeśli element z maksymalnym wyrównaniem jest taki sam jak element maksymalnego rozmiaru, nie trzeba dopełniania. [Myślę, że mój kod kompilatora może być uproszczony, aby być sprawiedliwym, ale jestem w trakcie naprawiania innych rzeczy, i nie chcę łamać rzeczy tylko po to, aby usunąć trzy wiersze kodu, który nie jest bezwzględnie potrzebny, ale działa] –

+0

Na marginesie, jeśli chciałem, aby moja struktura 'struct' zawierała, powiedzmy,' std :: string', wolałbym _not_, aby musiała dokładnie opisać jej układ (który i tak byłby zależny od implementacji). Myślę, że byłoby możliwe zrobienie czegoś podobnego do "union" poprzez posiadanie szablonu szablonu zawierającego typ T (ponownie, powiedzmy, 'std :: string') i wykrycie jego offsetu, a następnie skonstruowanie' StructType' odpowiednio. Myśli? –

1

Głównym celem DataLayout jest wiedzieć wyrównanie elementów. Jeśli nie musisz znać rozmiaru, wyrównania lub przesunięć elementów w kodzie [i LLVM nie ma naprawdę użytecznych sposobów poza instrukcją GEP, aby znaleźć przesunięcie, więc możesz zignorować część odsuniętą], nie będzie potrzebował datalayout, dopóki nie wykonasz (lub nie wygenerujesz pliku obiektowego) z IR.

(Mam kilka bardzo interesujących błędów, próbując skompilować 32-bitowy kod z 64-bitowym "natywnym" datalayout, gdy zaimplementowałem przełącznik -m32 dla mojego kompilatora - nie jest dobrym pomysłem przełączanie DataLayout w środku kompilacji, co zrobiłem, ponieważ użyłem "domyślnego", a następnie ustawiłem inny podczas tworzenia pliku rzeczywistego obiektu).

+0

Moje pytanie mówi, że muszę mieć poprawne rozmiary i wyrównania. –

+0

Mimo to, DataLayout ma znaczenie tylko w momencie generowania kodu lub uzyskania rozmiaru, więc myślę, że musisz pokazać nieco więcej kodu, lub wyjaśnić nieco dokładniej to, co NIE działa. Jak to jest źle? Co się dzieje i czym różni się od tego, czego oczekujesz? Czym jest "sizeof (S)" w C++ i czym jest "DataLayout :: getTypeAllocSize (t)"? Czym jest 'DataLayout :: getPrefTypeAlignment (x);', gdzie 'x' jest elementem' t'? –

+0

Moja rzeczywista struktura jest nieco bardziej zaangażowana niż pokazany tutaj przykład zabawki. Wystarczy powiedzieć, że 'sizeof (S)' <'DataLayout :: getTypeAllocSize (s)' gdzie 's' jest instancją LLVM równoważną' StructType'. Potrzebuję tego drugiego, aby był tego samego rozmiaru co poprzedni, więc obiekty C++ utworzone przez kompilator C++ mogą być używane przez kod LLVM IR JIT. I chodzi o to, aby wygenerować kod JIT'd LLVM IR, który może współdziałać z wygenerowanym kodem kompilatora C++. –