ARM GCC Inline Assembler Kochbuch
Über dieses Dokument
Der GNU C-Compiler für ARM RISC Prozessoren erlaubt, Assemblerbefehlscode in C Programmen einzubetten. Diese coole Funktion kann für manuelle Optimierung zeitkritischer Programmabschnitte benutzt werden oder um spezifische Prozessoranweisung zu verwenden, die in C nicht zur Verfügung stehen.
Es wird angenommen, dass Sie mit Schreiben von ARM Assembler-Programmen vertraut sind, denn dies ist keine Einführung in Assembler-Programmierung. Ebensowenig ist es als Einführung in die C Programmierung geeignet.
Das Dokument basiert auf GCC Version 4, sollte aber auch mit früheren Versionen verwendbar sein.
GCC asm Befehl
Lassen Sie uns mit einem einfachen Beispiel beginnen. Der folgende Befehl kann in Ihrem C Programm wie jeder andere Befehl verwendet werden.
/* Beispiel NOP */ asm("mov r0,r0");
Er kopiert den Inhalt von Register r0 in das Register r0. Das heißt, der Befehl tut nicht viel mehr als gar nichts. Er ist auch als NOP (No OPeration) Befehl bekannt und wird gewöhnlich für sehr kurze Delays verwendet.
Halt! Bevor Sie unmittelbar daran gehen, dieses Beispiel in Ihrem Programm zu verwenden, fahren Sie mit dem Lesen fort um zu erfahren, warum er möglicherweise nicht so wie erwartet funktionieren wird.
Mit Inline-Assembler können Sie die gleiche Assemblerbefehlgedächtnislehre verwenden, die Sie für Schreiben reinen ARMversammlungscode verwenden würden. Und Sie können mehr als eine Assemblerbefehl in eine einzelne Inline-asm Statement schreiben. Um sie lesbarer zu bilden, können Sie jede Anweisung auf eine unterschiedliche Linie setzen.
asm( "mov r0, r0\n\t" "mov r0, r0\n\t" "mov r0, r0\n\t" "mov r0, r0" );
Die spezielle Reihenfolge des Zeilenvorschubes und der Tabulatorzeichen hält die Versammlungsteilnehmerauflistung, nett zu schauen. Es kann eine Spitze zum ersten Mal scheinen, die ungerade ist, aber die ist die Weise, die, der Compiler seinen eigenen Versammlungsteilnehmercode bei der Zusammenstellung von c-Befehln verursacht.
Bis jetzt sind die Assemblerbefehlen die vielen selben, die sie in den reinen Assemblersprachenprogrammen erscheinen würden. Jedoch werden Register und Konstanten in einer unterschiedlichen Art spezifiziert, wenn sie auf c-Ausdrücke sich beziehen. Die allgemeine Form einer Inline-Assemblersteilnehmeraussage ist
asm (Code: Ausgangsrechengrößenliste: Eingangsrechengrößenliste: Clobberliste);
Der Anschluss zwischen Assemblersprache und c-Rechengrößen wird von einem wahlweise freigestellten zweiten und dritten Teil der asm-Befehl, der Liste des Ausganges und der Eingangsrechengrößen zur Verfügung gestellt. Wir erklären das dritte wahlweise freigestellte Teil, die Liste von Clobbers, später.
Das folgende Beispiel des Drehens der Spitzen führt c-Variablen zur Assemblersprache. Es nimmt den Wert von einer Ganzzahlvariable, dreht Recht die Spitzen durch eine und speichert die Ganzzahlvariable des Resultats sofort.
/* Beispiel rotierende Bits */ asm("mov %[result], %[value], ror #1" : [result] "=r" (y) : [value] "r" (x));
Jede asm-Befehl wird durch Doppelpunkte in bis vier Teile unterteilt:
-
Die Assemblerbefehlen, definiert in einem einzelnen Schnurdruckfehler:
"Bewegungen % [Resultat], % [Wert], ror #1"
-
Eine wahlweise freigestellte Liste der Ausgangsrechengrößen, getrennt durch
Kommas. Jede Eintragung besteht aus einem symbolischen Namen, der in den
eckigen Klammern eingeschlossen wird, gefolgt von einer Begrenzungsschnur,
gefolgt vom Wechselstrom-Ausdruck, der in Klammern eingeschlossen wird. Unser
Beispiel benutzt gerade einen Eintrag:
[Resultat] "=r" (Y)
-
Ein Komma trennte Liste der Eingangsrechengrößen, die die gleiche Syntax wie
die Liste der Ausgangsrechengrößen verwendet. Wieder ist dieses wahlweise
freigestellter und unser Beispielgebrauch eine Rechengröße nur:
[Wert] "r" (X)
- Wahlweise freigestellte Liste der verprügelten Register, ausgelassen in unserem Beispiel.
Wie in dem Anfangs-NOP Beispiel gezeigt können schleppende Teile der asm Statement ausgelassen werden, wenn unbenutzt. Die Inline-asm Statementn, die nur Assemblerbefehl enthalten, sind alias grundlegende Inline-Assembler, während die Befehln, die wahlweise freigestellte Teile enthalten, ausgedehnte Inline-Assembler genannt werden. Wenn ein unbenutztes Teil von einem gefolgt wird, das verwendet wird, muss es leer gelassen werden. Das folgende Beispiel stellt das Programmstatusregister der ARM-CPU ein. Es verwendet einen Eingang, aber keine Ausgangsrechengröße.
asm ("msr cpsr, % [ps]": : [ps] "r" (Status));
Sogar kann das Codeteil leer gelassen werden, obwohl eine leere Schnur angefordert wird. Die folgende Befehl verursacht einen speziellen Clobber, um den Compiler zu erklären, Inhalt dieses Gedächtnisses kann geändert haben. Wieder wird die Clobberliste später erklärt, wenn wir einen Blick zur Codeoptimierung nehmen.
asm (""::: "Gedächtnis");
Sie können Räume, Neuzeilen und sogar c-Anmerkungen zur Zunahmelesbarkeit einsetzen.
asm("mov %[result], %[value], ror #1" : [result]"=r" (y) /* Resultat. */ : [value]"r" (x) /* Wert. */ : /* Keine clobbers */ );
Im Codeabschnitt werden Rechengrößen durch ein Prozentzeichen bezogen, das vom in Verbindung stehenden symbolischen Namen gefolgt wird, der in den eckigen Klammern eingeschlossen wird. Er bezieht sich die auf Eintragung in einer der Rechengrößenlisten, die den gleichen symbolischen Namen enthält. Vom drehenden Spitzenbeispiel:
% [Resultat] bezieht sich auf Ausgangsrechengröße, das C variables y
und
% [Wert] bezieht sich die auf Eingangsrechengröße, das C variables X.
Namen der symbolischen Rechengröße benutzen einen unterschiedlichen Namensplatz. Die Mittel, das dort ist keine Relation zu jeder möglicher anderen Symboltabelle. Zu sie setzen einfach: Sie können einen Namen wählen, ohne mach's gut, ob der gleiche Name bereits in Ihrem c-Code existiert. Jedoch müssen einzigartige Symbole innerhalb jeder asm Statement verwendet werden.
Wenn Sie bereits zu einigen Funktionsinline-Versammlungsteilnehmeraussagen schauten, die von anderen Autoren geschrieben wurden, können Sie einen bedeutenden Unterschied beachtet haben. Tatsächlich stützt der GCC-Compiler symbolische Namen seit Version 3.1. Für frühere Freilassungen muss das drehende Spitzenbeispiel wie geschrieben werden
asm ("Bewegung %0, %1, ror #1": "=r" (Resultat): "r" (Wert));
Rechengrößen werden durch ein Prozentzeichen bezogen, das von einer einzelnen Stelle gefolgt wird, in der %0 auf die ersten %1 zur zweiten Rechengröße sich und so weiter bezieht. Dieses Format wird noch durch die spätesten GCC-Freigaben gestützt, aber ziemlich fehleranfällig und schwierig beizubehalten. Nach der Einfügung einer neuen Ausgangsrechengröße, stellen Sie sich, das vor, das Sie viele Assemblerbefehlen geschrieben haben, in denen Rechengrößen manuell geumnumeriert werden müssen.
Wenn dieses ganzes Material noch ungerades wenig schaut, sorgen Sie sich nicht. Neben der geheimnisvollen Clobberliste haben Sie das starke Gefühl, dass noch etwas fehlt, recht? In der Tat sprachen wir nicht über die Begrenzungsschnüre in den Rechengrößenlisten. Ich möchte um Ihre Geduld bitten. Es gibt etwas, das zum Höhepunkt im Folgenden Kapitel wichtiger ist.
C-Codeoptimierung
Es gibt zwei mögliche Gründe, warum Sie Assemblersprache verwenden möchten. Zuerst, ist, dieses C ist begrenzt, wenn wir näeher an der Hardware erhalten. Z.B. gibt es keine c-Befehl für das Prozessorstatusregister direkt ändern. Der zweite Grund ist, - optimierten Code in hohem Grade zu verursachen. Kein Zweifel, der Codeoptimierer GNU-C erledigt eine gute Arbeit, aber die Resultate sind von handcrafted Versammlungsteilnehmercode weit entfernt.
Das Thema des Kapitels wird häufig übersehen: Wenn man Assemblersprachencode, indem man Inline-Assemblersteilnehmeraussagen verwendet, dieser Code wird auch verarbeitet durch den Codeoptimierer des c-Compilers hinzufügt. Lassen Sie uns das Teil einer Compilerliste überprüfen, die von unserem drehenden Spitzenbeispiel erzeugt worden sein kann:
00309DE5 ldr r3, [sp, #0] @ x, x E330A0E1 mov r3, r3, ror #1 @ tmp, x 04308DE5 str r3, [sp, #4] @ tmp, y
Das Compiler vorgewählte Register r3 für Spitzenumdrehung. Sie könnte irgendein anderes Register oder zwei Register, eins vorgewählt haben für jede c-Variable. Sie kann den Wert möglicherweise nicht ausdrücklich laden oder das Resultat speichern. Ist hier eine andere Auflistung, erzeugt durch einen anderen Compiler, den Version mit unterschiedlichem Wahlen kompilieren:
E420A0E1 mov r2, r4, ror #1 @ y, x
Der Compiler wählte ein einzigartiges Register für jede Rechengröße, unter Verwendung des Wertes vor, der bereits in r4 und im Führen des Resultats zum folgenden Code in r2 cachiert wurde. Erhielten Sie die Abbildung?
Häufig wird es schlechter. Der Compiler kann sogar sich entscheiden, Ihren Versammlungsteilnehmercode nicht überhaupt einzuschließen. Diese Entscheidungen sind ein Teil der Optimierungsstrategie des Compilers und hängen vom Zusammenhang ab, in dem Ihre Assemblerbefehlen verwendet werden. Z.B. wenn Sie nie irgendwelche der Ausgangsrechengrößen im restlichen Teil des c-Programms verwenden, entfernt der Optimierer höchstwahrscheinlich Ihre Inline-Assemblersteilnehmeraussage. Das NOP Beispiel, das wir kann uns darstellten solch ein Anwärter zuerst, außerdem sein, weil zum Compiler dieses unbrauchbares obenliegendes ist und Ablauf des Programms verlangsamen.
Die Lösung ist, das löschbare Attribut der asm-Befehl hinzuzufügen, um den Kompilator anzuweisen, Ihren Versammlungsteilnehmercode von der Codeoptimierung auszuschließen. Erinnern Sie sich, das, das Sie gewarnt worden sind, das Anfangsbeispiel zu verwenden. Ist hier die korrigierte Version:
/* Revidiertes Beispiel NOP */ asm volatile("mov r0, r0");
Aber es gibt mehr Mühe, die uns wartet. Ein hoch entwickelter Optimierer ordnet den Code neu. Das folgende c-Stückchen war nach einigen letzten Änderungen zurückgelassen worden:
i++; if (j == 1) x += 3; i++;
Der Optimierer erkennt, das, welches die zwei Stufensprünge keine Auswirkung auf die bedingte Befehl haben. Außerdem weiß er, das, das einen Wert durch 2 erhöht, kostet nur eine ARM-Anweisung. So ordnet er den Code zu neu
if (j == 1) x += 3; i += 2;
und außer einer ARM-Anweisung. Infolgedessen: Es gibt keine Garantie, die, die der kompilierte Code die Reihenfolge der Befehln behält, die im Quellencode gegeben werden.
Dieses kann eine große Auswirkung auf Ihren Code haben, da wir jetzt demonstrieren. Der folgende Code beabsichtigt, c mit b, von zu multiplizieren, welches oder beide kann durch ein Unterbrechungsprogramm geändert werden. Sperrenunterbrechungen, bevor sie auf die Variablen zurückgreifen und re-enable sie aussieht danach wie eine gute Idee.
asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" ::: "r12", "cc"); c *= b; /* Unsichere Funktion. */ asm volatile("mrs r12, cpsr\n" "bic r12, r12, #0xC0\n" "msr cpsr_c, r12" ::: "r12", "cc");
Leider kann der Optimierer sich entscheiden, die Vermehrung zuerst zu tun und beide Inline-Assemblerbefehlen dann durchzuführen oder umgekehrt. Dieses bildet unseren Versammlungscode unbrauchbar.
Wir können dieses mithilfe der Clobberliste lösen, die jetzt erklärt wird. Die Clobberliste vom Beispiel oben
"r12", "cm"
informiert den Compiler, dass der Versammlungscode Register r12 ändert und die Bedingungsschlüsselmarkierungsfahnen aktualisiert. BTW. unter Verwendung eines fest verdrahteten Registers verhindert gewöhnlich beste Optimierungsresultate. Im Allgemeinen sollten Sie eine Variable führen und lassen den Kompilator das ausreichende Register wählen. Neben Registerbezeichnungen und cm für das Bedingungregister, ist Gedächtnis ein gültiges Schlüsselwort auch. Es erklärt dem Compiler, dass die Assemblerbefehl Speicherstellen ändern kann. Nach der Durchführung der Assemblerbefehlen dieses zwingt den Kompilator, alle cachierten Werte vorher zu speichern und sie neu zu laden. Nach der Durchführung einer asm Statement mit einem Gedächtnis Clobber und es muss die Reihenfolge behalten, weil der Inhalt aller Variablen unvorhersehbar ist.
asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" :: : "r12", "cc", "memory"); c *= b; /* Sichere Funktion. */ asm volatile("mrs r12, cpsr\n" "bic r12, r12, #0xC0\n" "msr cpsr_c, r12" ::: "r12", "cc", "memory");
Alle cachierten Werte ungültig zu erklären kann suboptimal sein. Wechselweise können Sie eine blinde Rechengröße addieren, um eine künstliche Abhängigkeit zu verursachen:
asm volatile("mrs r12, cpsr\n\t" "orr r12, r12, #0xC0\n\t" "msr cpsr_c, r12\n\t" : "=X" (b) :: "r12", "cc"); c *= b; /* Sichere Funktion. */ asm volatile("mrs r12, cpsr\n" "bic r12, r12, #0xC0\n" "msr cpsr_c, r12" :: "X" (c) : "r12", "cc");
Dieser Code täuscht, variables b in der ersten asm Statement zu ändern vor und den Inhalt variables c in der zweiten zu benutzen. Dieses konserviert die Reihenfolge unserer drei Befehln, ohne andere cachierte Variablen ungültig zu erklären.
Es ist wesentlich, zu verstehen, wie der Optimierer Inline-Assemblersteilnehmeraussagen beeinflußt. Wenn etwas neblig bleibt, besser lesen Sie dieses Teil neu, vor das folgende Thema weitergehen.
Eingangs- und Ausgangsrechengrößen
Wir erlernten, dieser jeder Eingang und Ausgangsrechengröße werden beschrieben durch einen symbolischen Namen, der in der eckigen Klammer eingeschlossen wurde, gefolgt von einer Begrenzungsschnur, die der Reihe nach vom Wechselstrom-Ausdruck in Klammern gefolgt wird.
Was sind diese Begrenzungen und warum wir benötigen sie? Sie wissen vermutlich, dass jeder Assemblerbefehl nur spezifische Rechengrößenarten annimmt. Z.B. erwartet der Verzweigungsbefehl eine Zieladresse, um an zu springen. Jedoch ist nicht jede Speicheradresse gültig, weil das abschließende opcode ein 24 Bit annimmt, das nur versetzt wird. Im Gegenteil erwartet die Niederlassungs- und Austauschanweisung ein Register, das eine 32-Bitzieladresse enthält. In beiden Fällen überschritt die Rechengröße von C zum Inline-Assemblersteilnehmer kann der gleiche c-Funktionszeiger sein. So wenn er Konstanten, Zeiger oder Variablen zu den Inline-Assemblersaussagen führt, muss der Inline-Assemblersteilnehmer wissen, wie sie im Versammlungscode dargestellt werden sollten.
Für ARM-Prozessoren liefert GCC 4 die folgenden Begrenzungen.
Begrenzung | Verbrauch im ARM-Zustand | Verbrauch im Daumenzustand |
f | Gleitkomma registriert f0. f7 | Nicht vorhanden |
h | Nicht vorhanden | Registriert r8. .r15 |
G | Sofortig Gleitkomma-Konstante | Nicht vorhanden |
H | Selben ein G, aber verneint | Nicht vorhanden |
I |
Sofortig Wert in datenverarbeitenden Anweisungen z.B. ORR R0, R0, #operand |
Konstante in der Strecke 0. 255 z.B. SWI Rechengröße |
J |
Bewegungskonstanten -4095. 4095 z.B. LDR R1, [PC, #operand] |
Konstante in der Strecke -255. -1 z.B. VORR0, R0, #operand |
K | Selben wie I, aber umgewandelt | Selben wie I, aber verschoben |
L | Selben wie I, aber verneint |
Konstante in der Strecke -7. 7 z.B. VORR0, R1, #operand |
L | Selben wie r |
Registriert r0. .r7 z.B. DRÜCKEN Sie Rechengröße |
M |
Konstante in der Strecke 0. 32 oder eine Energie von 2 z.B. BEWEGUNGEN R2, R1, ROR #operand |
Konstante, die eine Mehrfachverbindungsstelle von 4 in der Strecke 0. ist. 1020 z.B. ADDIEREN Sie R0, SP, #operand |
m | Irgendeine gültige Speicheradresse | |
N | Nicht vorhanden |
Konstante in der Strecke 0. 31 z.B. LSL R0, R1, #operand |
O | Nicht vorhanden |
Konstante, die eine Mehrfachverbindungsstelle von 4 in der Strecke -508. ist.
508 z.B. ADDIEREN Sie SP, #operand |
r |
Mehrzweckregister r0. r15 z.B. VORoperand1, operand2, operand3 |
Nicht vorhanden |
w | Vektorgleitkomma registriert s0. s31 | Nicht vorhanden |
X | Irgendeine Rechengröße |
Begrenzungsbuchstaben können durch einen einzelnen Begrenzungsmodifizierfaktor vorangestellt werden. Begrenzungen ohne einen Modifizierfaktor spezifizieren Read-only-Rechengrößen. Modifizierfaktoren sind:
Modifizierfaktor | Spezifiziert |
= | Write-only Rechengröße, normalerweise verwendet für alle Ausgangsrechengrößen |
+ | Lese-Schreibrechengröße, muss als Ausgangsrechengröße verzeichnet werden |
u. | Ein Register, das für nur Ausgang benutzt werden sollte |
Ausgangsrechengrößen müssen write-only sein und das c-Ausdruckresultat muss ein lvalue sein, also bedeutet es, dass die Rechengrößen auf der linken Seite von Anweisungen gültig sein müssen. Der c-Compiler ist in der Lage, dieses zu überprüfen.
Eingangsrechengrößen sind, Sie schätzten ihn, schreibgeschützt. Anmerkung, die der c-Compiler ist nicht in der Lage zu überprüfen, ob die Rechengrößen von der angemessenen Art für die Art des Betriebes verwendet in den Assemblerbefehlen sind. Die meisten Probleme werden während des späten Versammlungsstadiums ermittelt, das für seine sonderbaren Fehlermeldungen weithin bekannt ist. Selbst wenn es behauptet, ein internes Compilerproblem gefunden zu haben, das die Autoren sofort berichtet werden sollte, verbessern Sie Überprüfung Ihr Inline-Assemblersteilnehmercode zuerst.
Eine strenge Richtlinie ist: Schreiben Sie nie überhaupt zu einer Eingangsrechengröße. Aber was, wenn Sie die gleiche Rechengröße für Eingang und Ausgang benötigen? Der Begrenzungsmodifizierfaktor + tut den Trick wie in dem folgenden Beispiel gezeigt:
asm ("Bewegung % [Wert], % [Wert], ror #1": [Wert] "+r" (Y));
Dieses ist unserem drehenden Spitzenbeispiel ähnlich, das oben dargestellt wird. Es dreht den Inhalt des variablen Wertes rechts durch ein Bit. Gegenüber im vorhergehenden Beispiel wird das Resultat nicht in einer anderen Variable gespeichert. Stattdessen wird der ursprüngliche Inhalt der Eingangsvariable geändert.
Der Modifizierfaktor + kann möglicherweise nicht durch frühere Freilassungen des Compilers gestützt werden. Glücklicherweise bieten sie eine andere Lösung an, die noch mit der spätesten Compilerversion arbeitet. Für Eingangsoperatoren ist es möglich, eine einzelne Stelle in der Begrenzungsschnur zu benutzen. Unter Verwendung der Stelle erklärt n dem Kompilator, das gleiche Register wie für die n-th Rechengröße, beginnend mit null zu benutzen. Ist hier ein Beispiel:
asm ("Bewegung %0, %0, ror #1": "=r" (Wert): "0" (Wert));
Begrenzung "0" erklärt dem Kompilator, um das gleiche Eingangsregister zu benutzen, das für die erste Ausgangsrechengröße benutzt wird.
Merken Sie jedoch, dieses dieses nicht automatisch andeutet den Rückfall. Der Compiler kann die gleichen Register für Eingang und Ausgang wählen, selbst wenn nicht erklärt, so zu tun. Sie können an die erste Umwandlungsliste des drehenden Spitzenbeispiels mit zwei Variablen dich erinnern, in denen der Compiler das gleiche Register r3 für beide Variablen benutzte. Die asm Statement
asm("mov %[result],%[value],ror #1":[result] "=r" (y):[value] "r" (x));
erzeugte diesen Code:
00309DE5 ldr r3, [sp, #0] @ x, x E330A0E1 mov r3, r3, ror #1 @ tmp, x 04308DE5 str r3, [sp, #4] @ tmp, y
Dieses ist nicht ein Problem in den meisten Fällen, aber kann tödlich sein, wenn der Ausgangsoperator durch den Versammlungsteilnehmercode geändert wird, bevor der Eingangsoperator verwendet wird. In den Situationen, in denen Ihr Code von den verschiedenen Registern abhängt, die für Eingangs- und Ausgangsrechengrößen benutzt werden, müssen Sie u. Begrenzungsmodifizierfaktor Ihrer Ausgangsrechengröße hinzufügen. Der folgende Code zeigt dieses Problem.
asm volatile("ldr %0, [%1]" "\n\t" "str %2, [%1, #4]" "\n\t" : "=&r" (rdv) : "r" (&table), "r" (wdv) : "memory");
Ein Wert wird von einer Tabelle gelesen und dann wird ein anderer Wert zu einer anderen Position in diese Tabelle geschrieben. Wenn der Compiler das gleiche Register für Eingang und Ausgang gewählt haben würde, dann würde der Ausgangswert auf der ersten Assemblerbefehl zerstört worden sein. Glücklicherweise u. weist Modifizierfaktor den Kompilator, an kein Register für den Ausgangswert vorzuwählen, der für irgendwelche der Eingangsrechengrößen verwendet wird.
Weitere Rezepte
Inline-Assemblersteilnehmer als Präprozessormakro
Um Ihre Assemblerspracheteile wiederzuverwenden, ist es nützlich sie als Makro zu definieren und sie in sich zu setzen Include-Dateien. Unter Verwendung solchen Include-Dateien kann Compilerwarnungen produzieren, wenn sie in den Modulen verwendet werden, die im strengen ANSI-Modus kompiliert werden. Um das zu vermeiden, können Sie __asm anstelle von asm und __volatile schreiben anstelle vom flüchtigen Stoff. Diese sind gleichwertiger angenommener Name. Ist hier ein Makro, das einen langen Wert von wenig umwandelt, das zu großem endian oder umgekehrt endian ist:
#define BYTESWAP(val) \ __asm__ __volatile__ ( \ "eor r3, %1, %1, ror #16\n\t" \ "bic r3, r3, #0x00FF0000\n\t" \ "mov %0, %1, ror #8\n\t" \ "eor %0, %0, r3, lsr #8" \ : "=r" (val) \ : "0"(val) \ : "r3", "cc" \ );
C-Stummelfunktionen
Makrodefinitionen umfassen den gleichen Versammlungsteilnehmercode, wann immer sie bezogen werden. Dieses kann möglicherweise nicht für größere Programme annehmbar sein. In diesem Fall können Sie Wechselstrom-Stummelfunktion definieren. Ist hier das Bytetauschenverfahren wieder, dieses mal, das als Wechselstrom-Funktion eingeführt wird.
unsigned long ByteSwap(unsigned long val) { asm volatile ( "eor r3, %1, %1, ror #16\n\t" "bic r3, r3, #0x00FF0000\n\t" "mov %0, %1, ror #8\n\t" "eor %0, %0, r3, lsr #8" : "=r" (val) : "0"(val) : "r3" ); return val; }
Symbolische Namen von C Variablen ersetzen
Durch Rückstellung verwendet GCC die gleichen symbolischen Namen von Funktionen oder von Variablen in C und im Versammlungsteilnehmercode. Sie können einen anderen Namen für den Versammlungsteilnehmercode spezifizieren, indem Sie eine spezielle Form der asm-Befehl verwenden:
nicht unterzeichneter langer Wert asm ("Taktgeber") = 3686400;
Diese Befehl weist den Kompilator an, den Taktgeber des symbolischen Namens eher als Wert zu benutzen. Dieses ist sinnvoll nur für globale Variablen. Lokale Variablen (aka Selbstvariablen) haben nicht symbolische Namen im Versammlungsteilnehmercode.
Symbolische Namen von C Funktionen ersetzen
Um den Namen einer Funktion zu ändern, benötigen Sie eine Prototyperklärung, weil der Compiler nicht das asm-Schlüsselwort in der Funktionsdefinition annimmt:
extern langer Calc (leerer) asm ("BERECHNEN Sie");
Das Benennen der Funktion Calc () verursacht Assemblerbefehlen, die Funktion zu benennen BERECHNEN.
Zwingen des Verbrauches der spezifischen Register
Eine lokale Variable kann in einem Register gehalten werden. Sie können den Inline-Assemblersteilnehmer anweisen, ein spezifisches Register für es zu benutzen.
void Count(void) { register unsigned char counter asm("r3"); ... some code... asm volatile("eor r3, r3, r3"); ... more code... }
Die Assemblerbefehl, "eor r3, r3, r3", löscht den variablen Kostenzähler. Seien gewarnt Sie, diese diese Probe ist schlecht in den meisten Situationen, weil sie den Optimierer des Compilers behindert. Außerdem hebt GCC nicht vollständig das spezifizierte Register auf. Wenn der Optimierer erkennt, dass die Variable nicht irgendwie länger bezogen wird, kann das Register wiederverwendet werden. Aber der Compiler ist nicht in der Lage, zu überprüfen, ob dieser Registerverbrauch mit irgendeinem vorbestimmten Register widerspricht. Wenn Sie zu viele Register auf diese Art aufheben, kann der Compiler aus Registern heraus während des Codeerzeugung sogar laufen.
Unter Verwendung der Register vorübergehend
Wenn Sie Register benutzen, die nicht als Rechengrößen geführt worden waren, müssen Sie den Compiler über dieses informieren. Der folgende Code justiert einen Wert auf eine Mehrfachverbindungsstelle von vier. Er verwendet r3 wie ein Kratzerregister und informiert den Kompilator über dieses, indem er r3 in der Clobberliste spezifiziert. Außerdem werden die CPU-Statusmarkierungsfahnen durch die ands Anweisung geändert und folglich war cm den Clobbers hinzugefügt worden.
asm volatile( "ands r3, %1, #3" "\n\t" "eor %0, %0, r3" "\n\t" "addne %0, #4" : "=r" (len) : "0" (len) : "cc", "r3" );
Wieder ist harter Kodierungregisterverbrauch immer schlechte Kodierungart. Bessere Werkzeugwechselstrom-Stummelfunktion und verwenden eine lokale Variable für temporäre Werte.
Register-Verbrauch
Es ist immer eine gute Idee, den Umwandlungslisteausgang des c-Compilers zu analysieren und den erzeugten Code zu studieren. Die folgende Tabelle des typischen Registerverbrauches des Compilers ist vermutlich nützlich, den Code zu verstehen.
Register | Alt. Name | Verbrauch |
r0 | a1 |
Erstes Funktionsargument Zahlfunktionsresultat Kratzerregister |
r1 | a2 |
Zweites Funktionsargument Kratzerregister |
r2 | a3 |
Drittes Funktionsargument Kratzerregister |
r3 | a4 |
Viertes Funktionsargument Kratzerregister |
r4 | v1 | Registervariable |
r5 | v2 | Registervariable |
r6 | v3 | Registervariable |
r7 | v4 | Registervariable |
r8 | v5 | Registervariable |
r9 |
v6 rfp |
Registervariable Realer Rahmenzeiger |
r10 | SL | Stapelbegrenzung |
r11 | fp | Argumentzeiger |
r12 | IP | Temporärer Arbeitsplatz |
r13 | SP | Stapelzeiger |
r14 | LR |
Verbindungsregister Arbeitsplatz |
r15 | PC | Befehlszähler |
Allgemeine Gefahren
Anweisungsfolge
Entwickler erwarten häufig, das eine Befehlsfolge Überreste im abschließenden Code, wie im Quellencode spezifiziert. Diese Annahme ist falsch und führt häufig stark ein, um Wanzen zu finden. Wirklich werden asm Statementn durch den Optimierer genauso wie andere c-Befehln verarbeitet. Sie können neu geordnet werden, wenn Abhängigkeiten dieses erlauben.
Das Kapitel "c-Codeoptimierung" bespricht die Details und bietet Lösungen an.
Durchführung im Daumenstatus
Seien Sie, das bewusst, abhängig von gegeben kompilieren Wahlen, der Compiler kann zum Daumenzustand schalten. Unter Verwendung des Inline-Assemblersteilnehmers mit Anweisungen, die nicht im Daumenzustand vorhanden sind, ergibt mysteriöses kompilieren Störungen.
Versammlungscodegröße
In den meisten Fällen stellt der Compiler richtig die Größe der Assemblerbefehl fest, aber er kann durch Versammlungsteilnehmermakro konfus werden. Besser vermeiden Sie sie.
Falls Sie konfus sind: Dieses ist über Assemblersprachenmakro, nicht c-Präprozessormakro. Es ist fein, die letzteren zu benutzen.
Aufkleber
Innerhalb der Assemblerbefehl können Sie Aufkleber als Sprungziele benutzen. Jedoch dürfen Sie nicht von einer Assemblerbefehl in andere springen. Der Optimierer weiß nichts über jene Niederlassungen und kann schlechten Code erzeugen.
Externe Links
Für eine vollständigere Diskussion über Inline-Assemblersverbrauch, sehen Sie
das GCC-Benutzerhandbuch. Die späteste Version des GCC-Handbuches ist immer
hier vorhanden:
http://gcc.gnu.org/onlinedocs/
Copyright
Wie Sie sicher wissen (oder auch nicht) ist jede originäre Arbeit durch ein Copyright geschützt, auch wenn dies nicht ausdrücklich erwähnt ist. Die Vorgängerversion dieses Dokuments wurde häufig kopiert und veröffentlicht, was im Interesse des Autors ist. bei einigen Kopien entsteht allerdings der Eindruck, der Herausgeber (nicht ich) sei der Autor (grrr...). Daher habe ich mich entschlossen, dieses Dokument unter den Schutz der GNU FDL zu stellen:
Copyright (c) 2007-2009 Harald Kipp. Kopieren, Verbreiten und/oder Verändern ist unter den Bedingungen der GNU Free Documentation License, Version 1.3 oder einer späteren Version, veröffentlicht von der Free Software Foundation, erlaubt.