sep schrieb:
> Das war auch mein Ansatz, das ich ihn anschließend in ein uint8_t caste.
> Auch leider ohne Erfolg..
> Ich nehme immer die Seite zu Rate:
> https://rn-wissen.de/wiki/index.php/Inline-Assembler_in_avr-gcc
>
> Hm. Da ist halt ldi mit M. Da steht zwar was von 0..255 aber ich dachte
> nun nicht dass ein - ein so großen Unterschied macht. Vor allem da
> int8_t ja die gleiche Breite hat wie uint8_t und der Rest
> Interpretationsasche ist...
Bei Constraints geht es um Werte, nicht um Bitbreiten, hier zum Beispiel
die Definition von Constraint "M":
http://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/config/avr/constraints.md;h=d7a8ae67afaee5c1acc40a0a4f685d0872741f6d#l75
1 | (define_constraint "M"
|
2 | "Integer constant in the range 0 @dots{} 0xff."
|
3 | (and (match_code "const_int")
|
4 | (match_test "ival >= 0 && ival <= 0xff")))
|
"Mein" avr-as akzeptiert Werte von -255 bis 255 im LDI. Eine passende
Constraint gibt's nicht dafür, aber man kann sich eine zusammenstöpseln
aus "M" und "Cn8":
1 | __asm ("ldi R16, %0" :: "nMCn8" (-255));
|
Allerdings ist der AVR-Instruktionssatz so gestrickt, dass es nicht
sinnvoll ist, Constaints zu verwenden, die echte Teilmengen von "n"
sind, also von "zur Compilezeit bekannter Integer". Zweckmäßig wäre das
z.B. wenn es eine ADD instruktion gäbe, die zwei Register addieren kann
oder auch Register und Immediate im Bereich 0...255. Das könnte man
etwa folgenden Code schreiben:
1 | char add (char akku, char val)
|
2 | {
|
3 | __asm ("ADD %0, %1" : "+r" (akku) : "rM" (val)); // #1
|
4 | val = -1;
|
5 | __asm ("ADD %0, %1" : "+r" (akku) : "rM" (val)); // #2
|
6 | val = 100;
|
7 | __asm ("ADD %0, %1" : "+r" (akku) : "rM" (val)); // #3
|
8 | __asm ("ADD %0, %1" : "+r" (akku) : "rM" (-42)); // #4
|
9 | return akku;
|
10 | }
|
1 | add:
|
2 | ADD r24, r22
|
3 |
|
4 | ldi r25,lo8(-1)
|
5 | ADD r24, r25
|
6 |
|
7 | ADD r24, 100
|
8 |
|
9 | ldi r18,lo8(-42)
|
10 | ldi r19,lo8(-1)
|
11 | ADD r24, r18
|
12 |
|
13 | ret
|
In Fall #1 ist %1 eine Variable, die in einem Register lebt, und diese
kann direkt eingesetzt werden.
In Fall #2 wird val = -1 verwendet, und ADD kann diese Konstante nicht
verdauen, daher muss sie in ein Register (hier R25) geladen werden.
In Fall #3 wird 100 addiert, und unsere ADD-Instruktion kann diese
Konstante addieren, daher darf der Compiler "ADD *,100" erzeugen.
In Fall #4 ist die Konstante wieder außerhalb des erlaubten Bereichs,
und zwar ein int. Daher wird -42 in ein 16-Bit Register (hier R19:R18)
geladen.
So hingeschrieben ist dass alles etwas seltsam, aber wenn man das ADD
als Makro verfügbar macht, sieht's plausibler aus:
1 | #define ADD(a, b) __asm ("ADD %0, %1" : "+r" (a) : "rM" ((int8_t) (b)))
|
2 |
|
3 | int8_t add (int8_t akku, int8_t val)
|
4 | {
|
5 | ADD (akku, val);
|
6 | ADD (akku, -1);
|
7 | ADD (akku, 100);
|
8 | ADD (akku, -42);
|
9 | return akku;
|
10 | }
|
Unter der Annahme ADD sei eine 8-Bit Addition gibt es ein Cast auf
int8_t, daher lädt Fall #4 nur noch ein 8-Bit Register mit -42.
Der AVR-Instruktionssatz sieht aber wie gesagt anders aus, da sieht man
schon am Mnemonic, welche Typen die Operanden haben, z.B. ADD vs. SUBI.
Für AVR würde ein entsprechendes ADD-Makro also deutlich kompliziertes
aussehen:
1 | #define ADD(a, b) \
|
2 | do { \
|
3 | if (__builtin_constant_p (b) && b == 0) \
|
4 | (void) 0; \
|
5 | else if (__builtin_constant_p (b) && b + 255 <= 510) \
|
6 | __asm ("SUBI %0, %n1" : "+r" (a) : "MCn8" (b)); \
|
7 | else \
|
8 | __asm ("ADD %0, %1" : "+r" (a) : "r" (b)); \
|
9 | } while (0)
|
Die Vereinigung von "M" und "Cn8" zu nutzen bring hier aber kein Vortail
gegenüber "n".