Jak wpłynąć na generowanie kodu Delphi XEx dla celów Android / ARM?

266

Aktualizacja 2017-05-17. Nie pracuję już w firmie, z której pochodzi to pytanie, i nie mam dostępu do Delphi XEx. Podczas gdy tam byłem, problem został rozwiązany przez migrację do mieszanego FPC + GCC (Pascal + C), z wewnętrznymi cechami NEON dla niektórych procedur, w których miało to znaczenie. (FPC + GCC jest wysoce zalecane również dlatego, że umożliwia korzystanie ze standardowych narzędzi, zwłaszcza Valgrind.) Jeśli ktoś może wykazać, na wiarygodnych przykładach, w jaki sposób jest w stanie wygenerować zoptymalizowany kod ARM z Delphi XEx, chętnie przyjmuję odpowiedź .


Kompilatory Delphi Embarcadero wykorzystują backend LLVM do tworzenia natywnego kodu ARM dla urządzeń z Androidem. Mam duże ilości kodu Pascal, który muszę skompilować do aplikacji na Androida i chciałbym wiedzieć, jak sprawić, aby Delphi generował bardziej wydajny kod. W tej chwili nie mówię nawet o zaawansowanych funkcjach, takich jak automatyczne optymalizacje SIMD, tylko o tworzeniu rozsądnego kodu. Z pewnością musi istnieć sposób na przekazanie parametrów po stronie LLVM, czy w jakiś sposób wpłynąć na wynik? Zwykle każdy kompilator będzie miał wiele opcji wpływających na kompilację i optymalizację kodu, ale cele ARM Delphi wydają się być po prostu „optymalizacją włączoną / wyłączoną” i to wszystko.

LLVM ma być w stanie produkować dość ciasny i rozsądny kod, ale wydaje się, że Delphi używa swoich możliwości w dziwny sposób. Delphi chce bardzo intensywnie wykorzystywać stos i generalnie wykorzystuje rejestry procesora r0-r3 jako zmienne tymczasowe. Być może najbardziej szalony ze wszystkich, wydaje się, że ładuje normalne 32-bitowe liczby całkowite jako cztery operacje ładowania 1-bajtowego. Jak zmusić Delphi do tworzenia lepszego kodu ARM i bez kłopotów bajt po bajcie robi to dla Androida?

Na początku myślałem, że ładowanie bajt po bajcie służyło do zamiany kolejności bajtów z big-endian, ale tak nie było, tak naprawdę to po prostu ładowanie liczby 32-bitowej z 4 ładowaniami jednobajtowymi. * Może to być ładowanie pełne 32 bity bez wykonywania niewyrównanego ładowania pamięci o wielkości słowa. (czy NALEŻY tego uniknąć, to kolejna rzecz, która sugerowałaby, że całość jest błędem kompilatora) *

Spójrzmy na tę prostą funkcję:

function ReadInteger(APInteger : PInteger) : Integer;
begin
  Result := APInteger^;
end;

Nawet przy włączonych optymalizacjach Delphi XE7 z pakietem aktualizacji 1, a także XE6, generują następujący kod zestawu ARM dla tej funkcji:

Disassembly of section .text._ZN16Uarmcodetestform11ReadIntegerEPi:

00000000 <_ZN16Uarmcodetestform11ReadIntegerEPi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   78c1        ldrb    r1, [r0, #3]
   a:   7882        ldrb    r2, [r0, #2]
   c:   ea42 2101   orr.w   r1, r2, r1, lsl #8
  10:   7842        ldrb    r2, [r0, #1]
  12:   7803        ldrb    r3, [r0, #0]
  14:   ea43 2202   orr.w   r2, r3, r2, lsl #8
  18:   ea42 4101   orr.w   r1, r2, r1, lsl #16
  1c:   9101        str r1, [sp, #4]
  1e:   9000        str r0, [sp, #0]
  20:   4608        mov r0, r1
  22:   b003        add sp, #12
  24:   bd80        pop {r7, pc}

Wystarczy policzyć liczbę instrukcji i dostęp do pamięci potrzebnych do tego Delphi. I konstruowanie 32-bitowej liczby całkowitej z 4 ładowań jednobajtowych ... Jeśli zmienię nieco funkcję i użyję parametru var zamiast wskaźnika, będzie to nieco mniej skomplikowane:

Disassembly of section .text._ZN16Uarmcodetestform14ReadIntegerVarERi:

00000000 <_ZN16Uarmcodetestform14ReadIntegerVarERi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   6801        ldr r1, [r0, #0]
   a:   9101        str r1, [sp, #4]
   c:   9000        str r0, [sp, #0]
   e:   4608        mov r0, r1
  10:   b003        add sp, #12
  12:   bd80        pop {r7, pc}

Nie dołączę tutaj dezasemblacji, ale dla iOS Delphi produkuje identyczny kod dla wersji wskaźnika i wersji parametru i są one prawie, ale nie dokładnie takie same, jak wersja parametru dla Androida. Edycja: aby wyjaśnić, ładowanie bajt po bajcie odbywa się tylko na Androidzie. I tylko w Androidzie wersje wskaźnika i parametru var różnią się od siebie. W systemie iOS obie wersje generują dokładnie ten sam kod.

Dla porównania oto, co myśli FPC 2.7.1 (wersja magistrali SVN z marca 2014 r.) O funkcji z poziomem optymalizacji -O2. Wersje wskaźnika i parametru var są dokładnie takie same.

Disassembly of section .text.n_p$armcodetest_$$_readinteger$pinteger$$longint:

00000000 <P$ARMCODETEST_$$_READINTEGER$PINTEGER$$LONGINT>:

   0:   6800        ldr r0, [r0, #0]
   2:   46f7        mov pc, lr

Testowałem również równoważną funkcję C z kompilatorem C dostarczanym z Androidem NDK.

int ReadInteger(int *APInteger)
{
    return *APInteger;
}

I to składa się zasadniczo na to samo, co FPC:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Side S. Fresh
źródło
14
Sam w dyskusji na ten temat w Google+ Sam Shaw zauważa, że ​​C ++ pokazuje długi kod w kompilacjach debugowania i zoptymalizowany kod w wydaniu. Gdzie Delphi robi to w obu przypadkach. Z tego powodu może to być prosty błąd w flagach, które wysyłają LLVM, a jeśli tak, raport o błędzie jest warty zgłoszenia, może zostać naprawiony wkrótce.
David
9
Och, ok, źle odczytałem. Następnie, jak powiedział Notlikethat, brzmi to tak, jakby zakładało, że obciążenie wskaźnika nie będzie wyrównane (lub nie będzie w stanie zagwarantować wyrównania), a starsze platformy ARM niekoniecznie będą wykonywać niewyrównane obciążenia. Upewnij się, że masz kompilację docelową armeabi-v7azamiast armeabi(nie jestem pewien, czy istnieją takie opcje w tym kompilatorze), ponieważ niezrównane obciążenia powinny być obsługiwane od ARMv6 (przy armeabizałożeniu , że ARMv5). (Pokazany demontaż nie wygląda tak, jakby odczytywał wartość bigendian, po prostu odczytuje małą wartość endian jeden bajt na raz.)
mstorsjo
6
Znalazłem RSP-9922, który wydaje się być tym samym błędem.
David
6
Ktoś zapytał o awarię optymalizacji między XE4 a XE5 w grupie dyskusyjnej embarcadero.public.delphi.platformspecific.ios „Zepsuła się optymalizacja kompilatora ARM?” devsuperpage.com/search/…
Side S. Fresh
6
@Johan: co to jest plik wykonywalny? Miałem wrażenie, że jakoś wypiekło go w pliku wykonywalnym kompilatora Delphi. Spróbuj i daj nam znać wyniki.
Side S. Fresh

Odpowiedzi:

8

Badamy ten problem. Krótko mówiąc, zależy to od potencjalnego niedopasowania (do 32 granicy) liczby całkowitej, do której odnosi się wskaźnik. Potrzebujesz trochę więcej czasu, aby uzyskać wszystkie odpowiedzi ... i plan, aby rozwiązać ten problem.

Marco Cantù, moderator Delphi Developers

Zobacz także Dlaczego biblioteki zlib i zip Delphi są tak wolne w wersji 64-bitowej? ponieważ biblioteki Win64 są dostarczane bez kompilacji.


W raporcie QP: RSP-9922 Zły kod ARM wygenerowany przez kompilator, czy zignorowano dyrektywę $ O? Marco dodał następujące wyjaśnienie:

Tutaj jest wiele problemów:

  • Jak wskazano, ustawienia optymalizacji dotyczą tylko całych plików jednostek, a nie poszczególnych funkcji. Mówiąc najprościej, włączanie i wyłączanie optymalizacji w tym samym pliku nie przyniesie żadnego efektu.
  • Ponadto samo włączenie opcji „Informacje debugowania” wyłącza optymalizację. Dlatego podczas debugowania jawne włączenie optymalizacji nie przyniesie żadnego efektu. W związku z tym widok procesora w IDE nie będzie w stanie wyświetlić rozłożonego widoku zoptymalizowanego kodu.
  • Po trzecie, ładowanie niezrównanych danych 64-bitowych nie jest bezpieczne i powoduje błędy, stąd osobne 4-bajtowe operacje, które są potrzebne w danych scenariuszach.
Kirk Strobeck
źródło
Marco Cantù opublikował notatkę „Badamy problem” w styczniu 2015 r., A powiązany raport o błędzie RSP-9922 został oznaczony jako rozwiązany z rozdzielczością „Działa zgodnie z oczekiwaniami” w styczniu 2016 r., A 2 marca wspomniano o „problemie wewnętrznym”, 2015 ”. Nie rozumiem ich wyjaśnień.
Side S. Fresh
1
Dodałem komentarz do rozwiązania problemu.
Marco Cantù,