Wie kann die Delphi XEx-Codegenerierung für Android / ARM-Ziele beeinflusst werden?

266

Update 2017-05-17. Ich arbeite nicht mehr für das Unternehmen, aus dem diese Frage stammt, und habe keinen Zugriff auf Delphi XEx. Während ich dort war, wurde das Problem durch die Migration auf gemischte FPC + GCC (Pascal + C) mit NEON-Eigenheiten für einige Routinen gelöst, bei denen es einen Unterschied machte. (FPC + GCC wird dringend empfohlen, auch weil es die Verwendung von Standardtools, insbesondere von Valgrind, ermöglicht.) Wenn jemand anhand glaubwürdiger Beispiele demonstrieren kann, wie er tatsächlich optimierten ARM-Code aus Delphi XEx erstellen kann, bin ich froh, die Antwort zu akzeptieren .


Die Delphi-Compiler von Embarcadero verwenden ein LLVM-Backend, um nativen ARM-Code für Android-Geräte zu erstellen. Ich habe große Mengen an Pascal-Code, die ich zum Kompilieren in Android-Anwendungen benötigen, und ich möchte wissen, wie Delphi effizienteren Code generieren kann. Im Moment spreche ich nicht einmal über erweiterte Funktionen wie automatische SIMD-Optimierungen, sondern nur über die Erstellung von vernünftigem Code. Sicherlich muss es eine Möglichkeit geben, Parameter an die LLVM-Seite zu übergeben oder das Ergebnis irgendwie zu beeinflussen? Normalerweise hat jeder Compiler viele Optionen, um die Code-Kompilierung und -Optimierung zu beeinflussen, aber Delphis ARM-Ziele scheinen nur "Optimierung ein / aus" zu sein, und das war's.

LLVM soll in der Lage sein, einigermaßen engen und vernünftigen Code zu produzieren, aber es scheint, dass Delphi seine Einrichtungen auf seltsame Weise nutzt. Delphi möchte den Stack sehr stark nutzen und verwendet im Allgemeinen nur die Register r0-r3 des Prozessors als temporäre Variablen. Das vielleicht verrückteste von allen scheint das Laden normaler 32-Bit-Ganzzahlen als vier 1-Byte-Ladeoperationen zu sein. Wie kann man Delphi dazu bringen, besseren ARM-Code zu produzieren, und ohne den byteweisen Aufwand für Android?

Zuerst dachte ich, das Laden von Byte für Byte sei für das Austauschen der Bytereihenfolge von Big-Endian gedacht, aber das war nicht der Fall, es wird wirklich nur eine 32-Bit-Zahl mit 4 Einzelbyte-Ladevorgängen geladen. * Es könnte sein, dass geladen wird die vollen 32 Bits ohne eine nicht ausgerichtete Speicherlast in Wortgröße. (ob es das vermeiden SOLLTE, ist eine andere Sache, die darauf hindeuten würde, dass das Ganze ein Compiler-Fehler ist) *

Schauen wir uns diese einfache Funktion an:

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

Selbst bei aktivierten Optimierungen erzeugen Delphi XE7 mit Update Pack 1 sowie XE6 den folgenden ARM-Assemblycode für diese Funktion:

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}

Zählen Sie einfach die Anzahl der Anweisungen und Speicherzugriffe, die Delphi dafür benötigt. Und eine 32-Bit-Ganzzahl aus 4 Einzelbyte-Ladevorgängen erstellen ... Wenn ich die Funktion ein wenig ändere und anstelle eines Zeigers einen var-Parameter verwende, ist dies etwas weniger kompliziert:

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}

Ich werde die Demontage hier nicht einschließen, aber für iOS erzeugt Delphi identischen Code für die Zeiger- und Var-Parameterversionen, und sie sind fast, aber nicht genau identisch mit der Android-Var-Parameterversion. Bearbeiten: Zur Verdeutlichung ist das Laden von Byte für Byte nur unter Android möglich. Und nur unter Android unterscheiden sich die Zeiger- und Var-Parameterversionen voneinander. Unter iOS generieren beide Versionen genau den gleichen Code.

Zum Vergleich ist hier, was FPC 2.7.1 (SVN-Trunk-Version von März 2014) von der Funktion mit Optimierungsstufe -O2 hält. Die Zeiger- und var-Parameterversionen sind genau gleich.

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

Ich habe auch eine äquivalente C-Funktion mit dem C-Compiler getestet, der mit dem Android NDK geliefert wird.

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

Und dies ergibt im Wesentlichen dasselbe, was FPC gemacht hat:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
Seite S. Frisch
quelle
14
Übrigens stellt Sam Shaw in der Google+ Diskussion darüber fest, dass C ++ den Langformcode in Debugbuilds und den optimierten Code in der Version anzeigt. Wo Delphi es in beiden macht. Daher könnte es sich durchaus um einen einfachen Fehler in den Flags handeln, die LLVM senden, und wenn sich ein Fehlerbericht sehr lohnt, wird er möglicherweise bald behoben.
David
9
Oh, ok, ich habe falsch verstanden. Dann, wie Notlikethat sagte, klingt es so, als würde davon ausgegangen, dass die Zeigerlast nicht ausgerichtet ist (oder keine Ausrichtung garantieren kann), und ältere ARM-Plattformen können nicht unbedingt nicht ausgerichtete Lasten ausführen. Stellen Sie sicher, dass Sie das Targeting armeabi-v7aanstelle von erstellen armeabi(nicht sicher, ob es solche Optionen in diesem Compiler gibt), da nicht ausgerichtete Lasten seit ARMv6 unterstützt werden sollten (während armeabiARMv5 vorausgesetzt wird). (Die gezeigte Demontage sieht nicht so aus, als würde sie einen Bigendian-Wert lesen, sondern nur einen kleinen Endian-Wert byteweise.)
mstorsjo
6
Ich habe RSP-9922 gefunden, bei dem es sich anscheinend um denselben Fehler handelt.
David
6
Jemand hatte in der Newsgroup embarcadero.public.delphi.platformspecific.ios gefragt, ob die Optimierung zwischen XE4 und XE5 unterbrochen werden soll: "ARM Compiler-Optimierung unterbrochen?" devsuperpage.com/search/…
Seite S. Fresh
6
@Johan: Welche ausführbare Datei ist das? Ich hatte den Eindruck, dass es irgendwie in Delphis ausführbarer Compiler-Datei gebacken war. Probieren Sie es aus und teilen Sie uns die Ergebnisse mit.
Seite S. Fresh

Antworten:

8

Wir untersuchen das Problem. Kurz gesagt, es hängt von der möglichen Fehlausrichtung (bis zur Grenze 32) der Ganzzahl ab, auf die ein Zeiger verweist. Benötigen Sie etwas mehr Zeit, um alle Antworten zu erhalten ... und einen Plan, um dies zu beheben.

Marco Cantù, Moderator bei Delphi Developers

Siehe auch Warum sind die Delphi zlib- und zip-Bibliotheken unter 64 Bit so langsam? Da Win64-Bibliotheken ohne Optimierungen ausgeliefert werden.


Im QP-Bericht: RSP-9922 Vom Compiler erzeugter fehlerhafter ARM-Code, $ O-Direktive ignoriert? Marco fügte folgende Erklärung hinzu:

Hier gibt es mehrere Probleme:

  • Wie angegeben, gelten die Optimierungseinstellungen nur für ganze Gerätedateien und nicht für einzelne Funktionen. Einfach ausgedrückt hat das Ein- und Ausschalten der Optimierung in derselben Datei keine Auswirkungen.
  • Wenn Sie lediglich "Debug-Informationen" aktivieren, wird die Optimierung deaktiviert. Wenn Sie also debuggen, hat das explizite Aktivieren von Optimierungen keine Auswirkung. Folglich kann die CPU-Ansicht in der IDE keine zerlegte Ansicht des optimierten Codes anzeigen.
  • Drittens ist das Laden nicht ausgerichteter 64-Bit-Daten nicht sicher und führt zu Fehlern, daher die separaten 4-Ein-Byte-Operationen, die in bestimmten Szenarien erforderlich sind.
Kirk Strobeck
quelle
Marco Cantù veröffentlichte diesen Hinweis "Wir untersuchen das Problem" im Januar 2015, und der zugehörige Fehlerbericht RSP-9922 wurde im Januar 2016 mit der Lösung "Works As Expected" als behoben markiert. Es wird erwähnt, dass das interne Problem am 2. März geschlossen wurde. 2015 ". Ich verstehe ihre Erklärungen nicht.
Seite S. Fresh
1
Ich habe einen Kommentar in die Problemlösung eingefügt.
Marco Cantù