K (→Operanden und Constraints) |
(→Clobbers) |
||
Zeile 247: | Zeile 247: | ||
==Clobbers== | ==Clobbers== | ||
+ | |||
+ | In der Komma-getrennten Clobber-Liste kann man angeben, welche Register durch den Inline-Assembler ihren Wert ändern. Ändern z.B. <tt>r2</tt> und <tt>r3</tt> ihre Werte, dann ist die Clobber-Liste | ||
+ | "r2", "r3" | ||
+ | |||
+ | Wird schreibend auf das RAM zugegriffen, dann muss man das auch mitteilen, damit RAM-Inhalte, die sich evtl. in Registern befinden, nach dem Inline-Assembler neu gelesen werden. Der Clobber dafür ist: | ||
+ | "memory" | ||
+ | |||
+ | '''Beispiel:''' | ||
+ | |||
+ | Es soll ein Inline-Assembler geschrieben werden, das den Inhalt zweier aufeinanderfolgender Speicherstelen austauscht. Die Adresse soll in <tt>addr</tt> stehen. Sie ist Input-Operand und muss in Register X, Y oder Z stehen. Die passende Constraint ist also <tt>"e"</tt>. Nach der Sequenz liegt <tt>addr</tt> unverändert vor. | ||
+ | <pre> | ||
+ | asm volatile ( | ||
+ | "ld r2, %a0+" "\n\t" | ||
+ | "ld r3, %a0" "\n\t" | ||
+ | "st %a0, r2" "\n\t" | ||
+ | "st -%a0, r3" | ||
+ | : /* keine Output-Operanden */ | ||
+ | : "e" (addr) | ||
+ | : "r2", "r3", "memory" | ||
+ | ); | ||
+ | </pre> | ||
+ | avr-gcc entscheidet sich dazu, das Z-Register zu verwenden: | ||
+ | <pre> | ||
+ | ld r2, Z+ | ||
+ | ld r3, Z | ||
+ | st Z, r2 | ||
+ | st -Z, r3 | ||
+ | </pre> | ||
+ | |||
+ | Günstiger ist es jedoch, dem Compiler die Entscheidung zu überlassen, welche(s) Register als Hilfsregister verwendet werden sollen. Ein Register kann <tt>__tmp_reg__</tt> sein, für das andere legen wir eine lokale 8-Bit-Variable an: | ||
+ | <pre> | ||
+ | { | ||
+ | char hilf; | ||
+ | |||
+ | asm volatile ( | ||
+ | "ld __tmp_reg__, %a1+" "\n\t" | ||
+ | "ld %0, %a1" "\n\t" | ||
+ | "st %a1, __tmp_reg__" "\n\t" | ||
+ | "st -%a1, %0" | ||
+ | : "=r" (hilf) | ||
+ | : "e" (addr) | ||
+ | : "memory" | ||
+ | ); | ||
+ | } | ||
+ | </pre> | ||
+ | <tt>__tmp_reg__</tt> (also <tt>r0</tt>) brauch nicht in die Clobber-Liste aufgenommen zu werden. Um das zweite benötigte Register kümmert sich gcc und sichert es, falls nötig | ||
+ | <pre> | ||
+ | ld r0, Z+ | ||
+ | ld r24, Z | ||
+ | st Z, r0 | ||
+ | st -Z, r24 | ||
+ | </pre> | ||
==Vordefinierte Bezeichner und Makros== | ==Vordefinierte Bezeichner und Makros== |
Version vom 1. März 2006, 18:34 Uhr
An diesem Artikel arbeitet gerade Mitglied SprinterSB.
Am besten momentan noch keine gravierenden Ergänzungen / Änderungen vornehmen. Dieser Hinweis verschwindet wenn der Autor soweit ist. Sollte dieser Hinweis länger als drei Tage auf einer Seite sein, bitte beim Autor SprinterSB per PM / Mail oder Forum nachfragen ob er vergessen wurde. |
In C versteht man unter Inline Assembler die Möglichkeit, direkt Assembler-Befehle in den Code einzufügen bzw. die eingefügten Assembler-Befehle selbst.
Neben den einzufügenden Befehlen muss beschrieben werden, welche Nebeneffekte die Befehle auf die Maschine haben und wo/wie Parameter übergeben werden, bzw. wie die Zuordnung von Variablen zu den Registern ist.
Obgleich das dazu verwendete Schlüsselwort __asm zum ANSI-C-Standard gehört, ist dies in jedem C-Compiler anders implementiert. Das gilt insbesondere für die Schnittstellenbeschreibung Variablen/Register. Dieser Artikel bezieht sich auf Inline Assembler von avr-gcc.
Inhaltsverzeichnis
Begriffe
- Assembler-Template
- Das Template (Schablone) ist ein statischer, konstanter String im Sinne von C. Es enthält die Assembler-Befehle sowie Platzhalter, in deren Stelle später die Operanden treten
- Constraint
- Die Constraints (Nebenbedingungen) beschreiben Einschränkungen an die zu verwendeten Register. Dies ist notwendig, da nicht alle Maschinenbefehle auf alle Register anwendbar sind
- Clobber-List
- Das ist eine Liste von Registern, deren Inhalt durch den Inline-Assembler zerstört wird
Syntax und Semantik
Das Schlüsselwort, um eine Inline-Assembler Sequenz einzuleiten, ist __asm (ANSI). Oft ist auch asm oder __asm__ verwendbar. Um zu kennzeichnen, daß die Sequenz keinesfalls wegoptimiert werden darf – etwa dann, wenn der Assembler keine Wirkung auf C-Variablen hat – wird dem asm ein volatile bzw. __volatile nachgestellt. Danach folgen in runden Klammern die durch : getrennten Abschnitte des Inline-Assemblers:
asm volatile (asm-template : output-operand-list : input-operand-list : clobber-list);
Abschnitte, die leer sind, können auch weggelassen werden, wenn dahinter kein weiterer Abschnitt folgt:
asm volatile (asm-template);
Oder, wenn weder Input- noch Output-Operanden gebraucht werden, aber Register oder Speicher verändert werden:
asm volatile (asm-template ::: clobber-list);
Aus Compiler-Sicht werden die Assembler-Befehle im Template parallel, also gleichzeitig ausgeführt! Dies ist zu bedenken, wenn Register sowohl als Input als auch als Output verwendet werden.
Assembler-Template
Im Template stehen die durch Zeilenumbrüche getrennten Assembler-Befehle. Das Template kann zudem %-Ausdrücke als Platzhalter enthalten, welche durch die Operanden ersetzt werden. Dabei bezieht sich %0 auf den ersten Operanden, %1 auf den zweiten Operanden, etc. Die Operanden selbst werden im zweiten und dritten Abschnitt des Templates als Komma-getrennte Liste angegeben.
Ein Platzhalter kann zusätzlich einen einbuchstabigen Modifier enthalten, um ein Register in einem speziellen Format darzustellen. Wird z.B. ein 16-Bit-Wert in den Registern r31:r30 gehalten, dann wären folgende Ersetzungen denkbar (als erstes Argument):
%0 → r30
%A0 → r30
%B0 → r31
%a0 → y
Im einfachsten Falle enthält das Templater nur einen Befehl:
"nop"
oder sogar garkeinen Befehl und lediglich einen Kommentar:
"; ein Kommentar"
Platzhalter | wird ersetzt durch |
---|---|
%n | Wird ersezt durch Argument n mit n = 0...9 |
%An | das erste (untere) Register des Arguments n (Bits 0...7) |
%Bn | das zweite Register des Arguments n (Bits 8...15) |
%Cn | das dritte Register des Arguments n (Bits 16...23) |
%Dn | das vierte Register des Arguments n (Bits 24...31) |
%an | Ausgabe des Arguments als Adress-Register, also als x, y bzw. z. Erlaubt zusammen mit Constraint b, e, x, y, z |
%~ | wird auf AVR mit Flash bis max. 8kByte durch ein r ersetzt, ansonsten bleibt es leer. Zum Aufbau von Sprungbefehlen, etwa "%~call foo" |
%= | eine für dieses asm-Template und die Übersetzungseinheit eindeutige Zahl. Zum Aufbau lokaler Sprungmarken. |
Sequenz | wird ersetzt durch Sonderzeichen |
%% | das %-Zeichen selbst |
\n | ein Zeilenumbruch zum Trennen mehrerer asm-Befehle/Zeilen |
\t | ein TAB, zur Übersichtlichkeit im erzeugten asm |
\" | ein " wird eingefügt |
\\ | das \-Zeichen selbst |
Kommentar | Beschreibung |
; Text | einzeiliger Kommentar bis zum Ende des Templates bzw. nächsten Zeilenumbruch |
/* Text */ | mehrzeiliger Kommentar wie in C |
Operanden und Constraints
Ein Operand besteht aus der Angabe des Constraints (also der Registerklasse und Kennzeichnung, ob es sich um einen Output-Operanden handelt) und dahinter in runden Klammern der C-Ausdruck, der in Register der angegebenen Klasse geladen werden soll.
Mehrere Input- bzw. Output-Operanden werden durch Komma getrennt.
Constraint | Register | Wertebereich | Constraint | Konstante | Wertebereich | |
---|---|---|---|---|---|---|
a | einfache obere Register | r16...r23 | G | Floatingpoint-Konstante | 0.0 | |
b | Pointer-Register | y, z | i | Konstante | ||
d | obere Register | r16...r31 | I | positive 6-Bit-Konstante | 0...63 | |
e | Pointer-Register | x, y, z | J | negative 6-Bit Konstante | -63...0 | |
l | untere Register | r0...r15 | M | 8-Bit Konstante | 0...255 | |
q | Stack-Pointer | SPH:SPL | ||||
r | ein Register | r0...r31 | ||||
t | Scratch-Register | r0 | ||||
w | obere Register-Paare | r24, r26, r28, r30 | ||||
x | Pointer-Register X | x (r27:r26) | ||||
y | Pointer-Register Y | y (r29:r28) | ||||
z | Pointer-Register Z | z (r31:r30) | ||||
0...9 | Identisch mit dem angegebenen Operanden Wird verwendet, wenn ein Operand sowohl als Input als auch als Output dient, um sich auf diesen Operanden zu beziehen |
Modifier | Bedeutung |
---|---|
= | der Operand ist Output-Operand |
& | diesen Operanden nicht als Input-Operanden verwenden, sondern nur als Output-Operand |
Ein Input-Operand könnte also so aussehen, wobei foo eine C-Variable ist. Als Register dient ein (je nach Typ von foo auch mehrere) obere Register, irgendwo von r16 bis r31:
"d" (foo)
Soll foo ein Output-Operand sein, sieht es so aus:
"=d" (foo)
Ist foo sowohl Input als auch Output, sieht es so aus. Hier ein komplettes Beispiel, daß die Nibbles von foo tauscht:
unsigned char foo; ... asm volatile ("swap %0" : "=r" (foo) : "0" (foo));
Mnemonic | Constraint | Mnemonic | Constraint | Mnemonic | Constraint | Mnemonic | Constraint | |||
---|---|---|---|---|---|---|---|---|---|---|
adc | r,r | add | r,r | adiw | w,I | and | r,r | |||
andi | d,M | asr | r | bclr | I | bld | r,I | |||
brbc | I,label | brbs | I,label | bset | I | bst | r,I | |||
cbi | I,I | cbr | d,I | com | r | cp | r,r | |||
cpc | r,r | cpi | d,M | cpse | r,r | dec | r | |||
elpm | t,z | eor | r,r | in | r,I | inc | r | |||
ld | r,e | ldd | r,b | ldi | d,M | lds | r,label | |||
lpm | t,z | lsl | r | lsr | r | mov | r,r | |||
movw | r,r | mul | r,r | neg | r | or | r,r | |||
ori | d,M | out | I,r | pop | r | push | r | |||
rol | r | ror | r | sbc | r,r | sbci | d,M | |||
sbi | I,I | sbic | I,I | sbiw | w,I | sbr | d,M | |||
sbrc | r,I | sbrs | r,I | ser | d | st | e,r | |||
std | b,r | sts | label,r | sub | r,r | subi | d,M | |||
swap | r |
Clobbers
In der Komma-getrennten Clobber-Liste kann man angeben, welche Register durch den Inline-Assembler ihren Wert ändern. Ändern z.B. r2 und r3 ihre Werte, dann ist die Clobber-Liste
"r2", "r3"
Wird schreibend auf das RAM zugegriffen, dann muss man das auch mitteilen, damit RAM-Inhalte, die sich evtl. in Registern befinden, nach dem Inline-Assembler neu gelesen werden. Der Clobber dafür ist:
"memory"
Beispiel:
Es soll ein Inline-Assembler geschrieben werden, das den Inhalt zweier aufeinanderfolgender Speicherstelen austauscht. Die Adresse soll in addr stehen. Sie ist Input-Operand und muss in Register X, Y oder Z stehen. Die passende Constraint ist also "e". Nach der Sequenz liegt addr unverändert vor.
asm volatile ( "ld r2, %a0+" "\n\t" "ld r3, %a0" "\n\t" "st %a0, r2" "\n\t" "st -%a0, r3" : /* keine Output-Operanden */ : "e" (addr) : "r2", "r3", "memory" );
avr-gcc entscheidet sich dazu, das Z-Register zu verwenden:
ld r2, Z+ ld r3, Z st Z, r2 st -Z, r3
Günstiger ist es jedoch, dem Compiler die Entscheidung zu überlassen, welche(s) Register als Hilfsregister verwendet werden sollen. Ein Register kann __tmp_reg__ sein, für das andere legen wir eine lokale 8-Bit-Variable an:
{ char hilf; asm volatile ( "ld __tmp_reg__, %a1+" "\n\t" "ld %0, %a1" "\n\t" "st %a1, __tmp_reg__" "\n\t" "st -%a1, %0" : "=r" (hilf) : "e" (addr) : "memory" ); }
__tmp_reg__ (also r0) brauch nicht in die Clobber-Liste aufgenommen zu werden. Um das zweite benötigte Register kümmert sich gcc und sichert es, falls nötig
ld r0, Z+ ld r24, Z st Z, r0 st -Z, r24
Vordefinierte Bezeichner und Makros
Bezeichner | Bedeutung |
---|---|
__SP_L__ | unteres Byte des Stack-Pointers, für in bzw. out |
__SP_H__ | oberes Byte des Stack-Pointers, für in bzw. out |
__SREG__ | Status-Register, für in bzw. out |
__tmp_reg__ | ein Register zur temporären Verwendung (r0) |
__zero_reg__ | ein Register, das 0 enthält (r1) |
lo8(const) | die unteren 8 Bit der Konstanten const |
hi8(const) | Bits 8...15 der Konstanten const |
hlo8(const) | Bits 16...23 der Konstanten const |
hhi8(const) | Bits 24...31 der Konstanten const |
Beispiele
Quellen
- Doku zur avr-libc
- Doku zu avr-gcc
- Quellen von gcc