2011-07-01 8 views
5

Mam źródłowy kod w języku C++, który analizuję za pomocą języka clang, tworząc kod bajtowy llvm. Od tego momentu chcę samemu przetworzyć ten plik ... Zakodowałem jednak problem. Rozważmy następujący scenariusz: - Tworzę klasę z nietrywialnym destruktorem lub konstruktorem kopiowania. - Definiuje funkcję, w której obiekt tej klasy jest przekazywany jako parametr, według wartości (bez odniesienia lub wskaźnika).Emitowanie kodu bajtowego llvm z klangu: atrybut "byval" dla przekazywania obiektów z nietrywialnym destruktorem do funkcji

W wygenerowanym kodzie bajtowym otrzymuję zamiast niego wskaźnik. W przypadku klas bez destruktora parametr jest opisywany jako "byval", ale tak nie jest w tym przypadku. W rezultacie nie mogę rozróżnić, czy parametr jest przekazywany przez wartość, czy naprawdę przez wskaźnik.

Rozważmy następujący przykład: Plik

input - cpass.cpp:

class C { 
    public: 
    int x; 
    ~C() {} 
}; 

void set(C val, int x) {val.x=x;}; 

void set(C *ptr, int x) {ptr->x=x;} 

wiersz polecenia kompilacji:

clang++ -c cpass.cpp -emit-llvm -o cpass.bc; llvm-dis cpass.bc 

Produkowane plik wyjściowy (cpass.ll):

; ModuleID = 'cpass.bc' 
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" 
target triple = "x86_64-unknown-linux-gnu" 

%class.C = type { i32 } 

define void @_Z3set1Ci(%class.C* %val, i32 %x) nounwind { 
    %1 = alloca i32, align 4 
    store i32 %x, i32* %1, align 4 
    %2 = load i32* %1, align 4 
    %3 = getelementptr inbounds %class.C* %val, i32 0, i32 0 
    store i32 %2, i32* %3, align 4 
    ret void 
} 

define void @_Z3setP1Ci(%class.C* %ptr, i32 %x) nounwind { 
    %1 = alloca %class.C*, align 8 
    %2 = alloca i32, align 4 
    store %class.C* %ptr, %class.C** %1, align 8 
    store i32 %x, i32* %2, align 4 
    %3 = load i32* %2, align 4 
    %4 = load %class.C** %1, align 8 
    %5 = getelementptr inbounds %class.C* %4, i32 0, i32 0 
    store i32 %3, i32* %5, align 4 
    ret void 
} 

Jak widać, parametry obu funkcji wyglądają dokładnie tak samo. Więc jak mogę powiedzieć, że pierwsza funkcja miała przyjąć parametr według wartości, zamiast wskaźnika?

Jednym rozwiązaniem może być jakoś przeanalizowanie nazwy zniekształconej funkcji, ale może nie być zawsze wykonalne. A co, jeśli ktoś przed uruchomieniem funkcji wstawi extern "C"?

Czy istnieje sposób, aby powiedzieć clang, aby zachować adnotację byval lub utworzyć dodatkową adnotację dla każdego parametru funkcji przekazanego przez wartość?

Anton Korobeynikov sugeruje, że powinienem zagłębić się w emisję LLVM IR klanu. Niestety nie wiem prawie nic o wewnętrznych dzwonkach, dokumentacja jest dość skąpa. The Internals Manual clang nie mówi o emisji IR. Tak naprawdę nie wiem, jak zacząć, gdzie iść, aby rozwiązać problem, miejmy nadzieję, bez konieczności przechodzenia przez wszystkich kodu źródłowego klang. Jakieś wskazówki? Poradnik? Dalsze czytanie?


W odpowiedzi Anton Korobeynikov:

wiem, bardziej lub mniej, jak C++ ABI wygląda względem parametru podjęcia. Znaleziono dobre czytanie tutaj: http://agner.org./optimize/calling_conventions.pdf. Ale to zależy od platformy! Takie podejście może nie być możliwe w przypadku różnych architektur lub w szczególnych okolicznościach.

W moim przypadku, na przykład, funkcja będzie uruchamiana na innym urządzeniu niż to, z którego jest wywoływana. Te dwa urządzenia nie współużytkują pamięci, więc nie dzielą się nią nawet. Jeśli użytkownik nie podaje wskaźnika (w takim przypadku zakładamy, że wie, co robi), obiekt powinien zawsze zostać przekazany w komunikacie funkcji-parametrów. Jeśli ma nietrywialny konstruktor kopiowania, powinien zostać wykonany przez wywołującego, ale obiekt powinien zostać utworzony również w obszarze parametrów.

Chciałem więc w jakiś sposób zastąpić ABI w klangu, bez zbytniego wtrącania się w ich kod źródłowy. Lub może dodać dodatkową adnotację, która byłaby ignorowana w normalnym potoku kompilacji, ale mogłem wykryć podczas analizowania pliku .bc/.ll. Lub w jakiś sposób inaczej zrekonstruuj sygnaturę funkcji.


+0

Cóż, wydaje się, że dla twoich potrzeb naprawdę zmienisz emisję LLVM IRL klanu. Pamiętaj również, że IR nie jest neutralny dla celu, w twoim przypadku generujesz IR na podstawie specyfikacji x86. –

+0

W takim razie: Jakieś wskazówki dotyczące zmiany emisji IR bez większych problemów? Może jakieś wskazówki? Uważam, że dokumentacja klang (http://clang.llvm.org/docs/InternalsManual.html) jest raczej skromna i niewystarczająca do tego celu. – CygnusX1

Odpowiedz

5

Niestety, "byval" to nie tylko "adnotacja", to atrybut parametru, który oznacza dużo dla optymalizatorów i backendów. Zasadniczo, zasady przekazywania małych struktur/klas zi bez nietrywialnych funkcji są rządowe według platformy C++ ABI, więc nie można po prostu zawsze używać tutaj bajtu.

W rzeczywistości, tu bywalek jest tylko wynikiem niewielkiej optymalizacji na poziomie frontendu. Kiedy przekazujesz rzeczy według wartości, to tymczasowy obiekt powinien być zbudowany na stosie (za pomocą domyślnego ctor). Kiedy masz klasę, która jest podobna do POD, wtedy klang może wywnioskować, że ctor będzie trudny i zoptymalizuje parę ctor/dtor, przekazując tylko "zawartość".

Dla klas niebanalnych (jak w twoim przypadku) klang nie może wykonać takiej optymalizacji i ma, aby wywołać zarówno ctor, jak i dtor. W ten sposób widzisz wskaźnik do obiektu tymczasowego.

Spróbuj wywołać funkcje set(), a zobaczysz, co się tam dzieje.

+0

Dziękuję za odpowiedź i za poświęcony czas! Przeczytaj moje rozszerzenie w pytaniu. Nie ma tu wystarczająco miejsca, by odpowiedzieć. – CygnusX1

Powiązane problemy