Comment affecter la génération de code Delphi XEx pour les cibles Android / ARM?

266

Mise à jour 2017-05-17. Je ne travaille plus pour l'entreprise à l'origine de cette question et je n'ai pas accès à Delphi XEx. Pendant que j'étais là-bas, le problème a été résolu en migrant vers un FPC + GCC mixte (Pascal + C), avec des intrinsèques NEON pour certaines routines où cela faisait une différence. (FPC + GCC est fortement recommandé également car il permet d'utiliser des outils standard, en particulier Valgrind.) Si quelqu'un peut démontrer, avec des exemples crédibles, comment il est réellement capable de produire du code ARM optimisé à partir de Delphi XEx, je suis heureux d'accepter la réponse .


Les compilateurs Delphi d'Embarcadero utilisent un backend LLVM pour produire du code ARM natif pour les appareils Android. J'ai de grandes quantités de code Pascal que je dois compiler dans des applications Android et je voudrais savoir comment rendre Delphi générer du code plus efficace. Pour le moment, je ne parle même pas de fonctionnalités avancées telles que les optimisations automatiques SIMD, mais simplement de produire un code raisonnable. Il doit sûrement y avoir un moyen de transmettre des paramètres au côté LLVM, ou d'affecter le résultat d'une manière ou d'une autre? Habituellement, n'importe quel compilateur aura de nombreuses options pour affecter la compilation et l'optimisation du code, mais les cibles ARM de Delphi semblent être simplement "optimisation on / off" et c'est tout.

LLVM est censé être capable de produire du code raisonnablement serré et raisonnable, mais il semble que Delphi utilise ses installations de manière étrange. Delphi souhaite utiliser très fortement la pile et n'utilise généralement que les registres r0-r3 du processeur comme variables temporaires. Peut-être le plus fou de tous, il semble charger des entiers 32 bits normaux en quatre opérations de chargement de 1 octet. Comment faire pour que Delphi produise un meilleur code ARM, et sans les tracas octet par octet qu'il crée pour Android?

Au début, je pensais que le chargement octet par octet était destiné à permuter l'ordre des octets de big-endian, mais ce n'était pas le cas, il s'agit simplement de charger un nombre 32 bits avec 4 charges à un octet. * Il pourrait s'agir de charger les 32 bits complets sans effectuer une charge de mémoire de taille de mot non alignée. (si cela DEVRAIT éviter cela est une autre chose, ce qui laisserait penser que le tout est un bogue du compilateur) *

Regardons cette fonction simple:

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

Même avec les optimisations activées, Delphi XE7 avec le pack de mise à jour 1, ainsi que XE6, produisent le code d'assemblage ARM suivant pour cette fonction:

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}

Il suffit de compter le nombre d'instructions et d'accès à la mémoire dont Delphi a besoin pour cela. Et construire un entier 32 bits à partir de 4 charges sur un octet ... Si je change un peu la fonction et utilise un paramètre var au lieu d'un pointeur, c'est un peu moins alambiqué:

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}

Je n'inclurai pas le démontage ici, mais pour iOS, Delphi produit un code identique pour les versions de paramètre pointeur et var, et ils sont presque mais pas exactement les mêmes que la version de paramètre var Android. Edit: pour clarifier, le chargement octet par octet est uniquement sur Android. Et uniquement sur Android, les versions des paramètres pointeur et var diffèrent les unes des autres. Sur iOS, les deux versions génèrent exactement le même code.

A titre de comparaison, voici ce que FPC 2.7.1 (version SVN trunk de mars 2014) pense de la fonction avec le niveau d'optimisation -O2. Les versions des paramètres pointeur et var sont exactement les mêmes.

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

J'ai également testé une fonction C équivalente avec le compilateur C fourni avec le NDK Android.

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

Et cela compile essentiellement la même chose que FPC a faite:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Side S. Fresh
la source
14
Dans la discussion Google+ à ce sujet, Sam Shaw note que C ++ montre le code long dans les versions de débogage et le code optimisé dans la version. Là où Delphi le fait dans les deux. Donc, à partir de là, cela pourrait bien être un simple bogue dans les drapeaux qu'ils envoient LLVM, et si c'est le cas, un rapport de bogue mérite d'être classé, il pourrait être corrigé très bientôt.
David
9
Oh, ok, j'ai mal lu. Ensuite, comme l'a dit Notlikethat, il semble que la charge du pointeur ne soit pas alignée (ou ne puisse pas garantir l'alignement), et les anciennes plates-formes ARM ne peuvent pas nécessairement effectuer des charges non alignées. Assurez-vous de l'avoir construit à la armeabi-v7aplace de armeabi(ne savez pas s'il existe de telles options dans ce compilateur), car les charges non alignées doivent être prises en charge depuis ARMv6 (tout en armeabisupposant ARMv5). (Le désassemblage montré ne ressemble pas à une valeur bigendienne, il lit juste une petite valeur
endienne
6
J'ai trouvé RSP-9922 qui semble être ce même bug.
David
6
Quelqu'un avait demandé si l'optimisation était interrompue entre XE4 et XE5, dans le groupe de discussion embarcadero.public.delphi.platformspecific.ios, "Optimisation du compilateur ARM cassé?" devsuperpage.com/search/…
Side S. Fresh
6
@Johan: de quel exécutable s'agit-il? J'ai eu l'impression qu'il était en quelque sorte cuit dans l'exécutable du compilateur de Delphi. Essayez-le et faites-nous connaître les résultats.
Side S. Fresh

Réponses:

8

Nous enquêtons sur le problème. En bref, cela dépend du désalignement potentiel (à la limite 32) de l'entier référencé par un pointeur. Besoin d'un peu plus de temps pour avoir toutes les réponses ... et un plan pour y remédier.

Marco Cantù, modérateur des développeurs Delphi

Référence également Pourquoi les bibliothèques Delphi zlib et zip sont-elles si lentes sous 64 bits? car les bibliothèques Win64 sont livrées construites sans optimisation.


Dans le rapport QP: RSP-9922 Mauvais code ARM produit par le compilateur, la directive $ O est-elle ignorée? , Marco a ajouté l'explication suivante:

Il y a plusieurs problèmes ici:

  • Comme indiqué, les paramètres d'optimisation s'appliquent uniquement aux fichiers d'unité entiers et non aux fonctions individuelles. Autrement dit, l'activation et la désactivation de l'optimisation dans le même fichier n'auront aucun effet.
  • De plus, le simple fait d'activer les "informations de débogage" désactive l'optimisation. Ainsi, lorsque l'on débogue, l'activation explicite des optimisations n'aura aucun effet. Par conséquent, la vue CPU dans l'EDI ne pourra pas afficher une vue démontée du code optimisé.
  • Troisièmement, le chargement de données 64 bits non alignées n'est pas sûr et entraîne des erreurs, d'où les opérations distinctes de 4 octets qui sont nécessaires dans des scénarios donnés.
Kirk Strobeck
la source
Marco Cantù a publié cette note "Nous enquêtons sur le problème" en janvier 2015, et le rapport de bogue connexe RSP-9922 a été marqué résolu avec la résolution "Works As Expected" en janvier 2016, et il y a une mention "problème interne clos le 2 mars, 2015 ". Je ne comprends pas leurs explications.
Side S. Fresh
1
J'ai ajouté un commentaire dans la résolution du problème.
Marco Cantù