Wie kann ich call sagen, dass Register nicht im Stack gespeichert werden sollen?

9

Das Ziel

Ich probiere gerade avr-llvm aus (ein llvm, der AVR als Ziel unterstützt). Mein Hauptziel ist es, den hoffentlich besseren Optimierer (im Vergleich zu dem von gcc) zu verwenden, um kleinere Binärdateien zu erreichen. Wenn Sie etwas über AVRs wissen, wissen Sie, dass Sie nur wenig Speicher haben.

Ich arbeite derzeit mit einem ATTiny45, 4KB Flash und 256 Bytes (nur Bytes nicht KB!) von SRAM.

Das Problem

Ich habe versucht, ein einfaches C-Programm zu kompilieren (siehe unten), um zu überprüfen, welcher Assemblercode erzeugt wird und wie sich die Maschinencodegröße entwickelt. Ich habe "clang -Oz-s test.c" verwendet, um die Ausgabe der Assembly zu erstellen und sie für minimale Größe zu optimieren. Mein Problem sind die unnötig gespeicherten Registerwerte, da ich weiß, dass diese Methode niemals zurückkehren wird.

Meine Fragen ...

Wie kann ich llvm sagen, dass es bei Bedarf jedes Register klauen kann, ohne es zu speichern / wiederherzustellen? Irgendwelche Ideen, wie man es noch mehr optimieren kann (z.B. effizienteres Einrichten des Stapels)?

Details / Beispiel

Hier ist mein Testprogramm. Wie oben erwähnt wurde mit "clang -Oz -S test.c" kompiliert.

%Vor%

Wie Sie sehen können, hat es nur eine "flüchtige" Variable vom Typ uint8_t (wenn ich es nicht auf volatile setze, wäre alles optimiert). Diese Variable ist auf 1 gesetzt. Und am Ende gibt es eine Endlosschleife. Sehen wir uns nun die Assembly-Ausgabe an:

%Vor%

Ja! Das ist viel Maschinencode für solch ein einfaches Programm. Ich habe gerade einige Varianten getestet und mir das Referenzhandbuch des AVR angeschaut ... damit ich erklären kann, was passiert. Werfen wir einen Blick auf jeden Teil.

Hier ist das "Rindfleisch", das gerade macht, worum es in unserem Programm geht. Es lädt r24 mit dem Wert "1", der bei Y + 1 im Speicher gespeichert wird (Stack Pointer + 1). Und es gibt natürlich unsere endlose Schleife:

%Vor%

Hinweis: Die Endlosschleife wird benötigt. Andernfalls wird __attribute__ ((noreturn)) ignoriert und die Stack-Zeiger + gespeicherten Register werden später wiederhergestellt.

Kurz davor ist der Zeiger in "Y" eingerichtet:

%Vor%

Was hier passiert ist:

  1. Y (Registerpaar r28: r29 ist äquivalent zu "Y") wird von den Ports 61 und 62 geladen, diese Ports bilden einige "Register" ab, nämlich SPL und SPH ("L" ow und "H" igh Byte des "S" tack "P" ointer)
  2. Der geladene Wert wird dekrementiert (sbiw r29: r28)
  3. Der geänderte Wert des Stapelzeigers wird in den Ports gespeichert. und ich denke, um Probleme zu vermeiden: Interrupts sind vorher deaktiviert; der Zustand von "cli / sti" [der in Register 63 (SREG) gespeichert ist] wird in r0 gespeichert und später wieder in Port 63 gespeichert.

Dieses Setup der Stack-Register scheint ineffizient zu sein. Um den Stapelzeiger zu erhöhen, müsste ich einfach "r0" zum Stapel schieben. Dann könnte ich einfach den Wert von SPH / SPL in r29: r28 laden. Dies würde jedoch wahrscheinlich einige Änderungen am llvm-Optimierer im Quellcode erfordern. Der obige Code macht nur Sinn, wenn mehr als 3 Bytes des Stacks für lokale Variablen reserviert werden müssen (selbst wenn -O3 optimiert wird, für -Oz ist dies für bis zu 6 Bytes sinnvoll). WIE JEDER ... Ich denke, wir müssen die Quelle von llvm dafür berühren; Das ist also nicht möglich.

Interessanter ist dieser Teil:

%Vor%

Da main () nicht zurückgegeben werden soll, macht das keinen Sinn. Dies verschwendet nur RAM und Flash-Speicher für dumme Anweisungen (denken Sie daran: Wir haben nur 64, 128 oder 256 Bytes SRAM in einigen Geräten verfügbar).

Ich habe das ein bisschen weiter untersucht: Wenn wir Hauptrückgabe (zB keine Endlosschleife) den Stapelzeiger zurückgeben lassen, haben wir einen "ret" Befehl am Ende UND die Register r28 und r29 werden vom Stapel über "pop" wiederhergestellt r29, pop 28 ". Aber der Compiler sollte wissen, dass, wenn der Gültigkeitsbereich der Funktion "main" nie verlassen wird, alle Register geplottert werden können, ohne sie im Stack zu speichern.

Dieses Problem scheint nur ein bisschen "albern" zu sein, da wir über 2 Bytes RAM sprechen. Aber denken Sie darüber nach, was passiert, wenn das Programm beginnt, die restlichen Register zu verwenden.

All dies hat meine Sichtweise bei aktuellen "Compilern" wirklich verändert. Ich dachte heute, es gäbe nicht viel Platz für die Optimierung mit Assembler. Aber es scheint, dass es ...

gibt

Also, die Frage ist immer noch ...

Haben Sie eine Idee, wie Sie diese Situation verbessern können (abgesehen von der Einreichung eines Fehlerberichtes / Feature-Requests)?

Ich meine: Gibt es nur einige Compiler-Schalter, die ich vielleicht übersehen habe ...?

Zusätzliche Informationen

Die Verwendung von __attribute__ ((OS_main)) funktioniert für avr-gcc.

Die Ausgabe ist wie folgt:

%Vor%

Dies ist (meiner Meinung nach) optimal in der Größe (6 Befehle oder 12 Bytes) und auch in der Geschwindigkeit für dieses Beispielprogramm. Gibt es ein gleichwertiges Attribut für llvm? (clang version '3.2 (trunk 160228) (basierend auf LLVM 3.2svn)' weiß weder von OS_task noch weiß er etwas über OS_main).

    
SDwarfs 11.10.2012, 23:31
quelle

1 Antwort

3

Die Antwort auf die gestellte Frage wird von Anton in seinem Kommentar etwas aufgegriffen: Das Problem liegt nicht in LLVM, sondern in Ihrem AVR-Ziel. Zum Beispiel ist hier ein äquivalentes Programm, das durch Clang und LLVM für andere Ziele läuft:

%Vor%

Wie Sie für alle drei dieser Ziele sehen können, besteht der einzige generierte Code darin, Stack-Speicherplatz zu reservieren (wenn es nicht auf x86-64 steht) und den Wert auf dem Stack zu setzen. Ich denke, das ist minimal.

Wenn Probleme mit dem LLVM-Optimierer auftauchen, sollten Sie am besten E-Mails an die Entwicklungs-Mailingliste senden oder Fehler melden, wenn Sie eine bestimmte IR-Eingangssequenz haben, die eine minimale IR-Ausgabe erzeugen soll.

Schließlich, um die Fragen zu beantworten, die in Kommentaren zu Ihrer Frage gestellt werden: Es gibt Bereiche, in denen der Optimierer von LLVM wesentlich leistungsfähiger ist als der GCC. Es gibt jedoch auch Bereiche, in denen es deutlich weniger stark ist. =] Benchmark der Code, den Sie interessieren.

    
Chandler Carruth 07.07.2013, 07:39
quelle