Forum: Mikrocontroller und Digitale Elektronik ARM GCC Inline Assembler - temporäre Variablen


von Vincent H. (vinci)


Lesenswert?

Grüß euch

Ich darf mich seit längerem mal wieder mit Assembler (Cortex M4) 
befassen und kämpfe etwas mit der Syntax und dem ein oder anderen 
Stolperstein.

Eins vorweg, folgenden Guide nutze ich bereits:
http://www.ethernut.de/en/documents/arm-inline-asm.html


Soweit so gut. Mein Problem ist folgendes. Ich habe einige Zeilen Code 
die DSP Instruktionen nutzen und nach ein wenig herumrechnen 4x 32bit 
Werte (p0-p4) ausspucken.
1
asm volatile("ssub8   %[src], %[src], %[int8_min]"    "\n\t"
2
             "sxtb16  r0, %[src], ror #0"             "\n\t"
3
             "sxtb16  r2, %[src], ror #8"             "\n\t"
4
             "smulbb  r1, r0, %[gain]"                "\n\t"
5
             "smultb  r5, r0, %[gain]"                "\n\t"
6
             "smulbb  r3, r2, %[gain]"                "\n\t"
7
             "smultb  r7, r2, %[gain]"                "\n\t"
8
             "pkhbt   r0, r1, %[last], lsl #16"       "\n\t"
9
             "pkhbt   r1, r3, r1, lsl #16"            "\n\t"
10
             "pkhbt   r2, r5, r3, lsl #16"            "\n\t"
11
             "pkhbt   r3, r7, r5, lsl #16"            "\n\t"
12
             "shadd16 r4, r0, r1"                     "\n\t"
13
             "shadd16 r5, r2, r3"                     "\n\t"
14
             "pkhtb   %[p0], r1, r4, asr #16"         "\n\t"
15
             "pkhbt   %[p1], r4, r2, lsl #0"          "\n\t"
16
             "pkhtb   %[p2], r3, r5, asr #16"         "\n\t"
17
             "pkhbt   %[p3], r5, r3, lsl #16"         "\n\t"
18
             : [src] "+r" (*src),
19
               [p0] "=r" (p0),
20
               [p1] "=r" (p1),
21
               [p2] "=r" (p2),
22
               [p3] "=r" (p3)
23
             : [int8_min] "r" (0x80808080),
24
               [gain] "r" (0x00000100),
25
               [last] "r" (process.last_)
26
             : "cc", "r0", "r1", "r2", "r3", "r4", "r5", "r7");

Das funktioniert soweit wunderbar und sieht im Disassembly quasi 1:1 so 
aus
1
08002790:   ldr.w   r9, [pc, #252]
2
08002794:   ldr.w   r8, [r9]
3
08002798:   mov.w   lr, #256        
4
0800279c:   mov.w   r10, #2155905152        
5
080027a0:   ssub8   r8, r8, r10
6
080027a4:   sxtb16  r0, r8
7
080027a8:   sxtb16  r2, r8, ror #8
8
080027ac:   smulbb  r1, r0, lr
9
080027b0:   smultb  r5, r0, lr
10
080027b4:   smulbb  r3, r2, lr
11
080027b8:   smultb  r7, r2, lr
12
080027bc:   pkhbt   r0, r1, r12, lsl #16
13
080027c0:   pkhbt   r1, r3, r1, lsl #16
14
080027c4:   pkhbt   r2, r5, r3, lsl #16
15
080027c8:   pkhbt   r3, r7, r5, lsl #16
16
080027cc:   shadd16 r4, r0, r1
17
080027d0:   shadd16 r5, r2, r3
18
080027d4:   pkhtb   r10, r1, r4, asr #16
19
080027d8:   pkhbt   r11, r4, r2
20
080027dc:   pkhtb   r12, r3, r5, asr #16
21
080027e0:   pkhbt   lr, r5, r3, lsl #16
22
080027e4:   mov     r1, r11
23
080027e6:   str.w   r8, [r9]


Das Problem ist jetzt, dass man den C-Code aktuell nicht mehr erweitern 
kann, da die Register auf denen gerechnet wird hard-coded sind. Sobald 
man versucht weitere Assembler Befehle einzufügen spuckt der Compiler 
eine Fehlermeldung aus, dass sich der Spaß von den Register-Constraints 
her nicht ausgeht... sprich es is kein Platz mehr da zum Herumrechnen. 
:)


Jetzt wollt ich die ganzen Register Angaben schlichtweg durch temporäre 
Variablen ersetzen, der Compiler soll sich das ganze doch bitte selbst 
optimieren.
1
asm volatile("ssub8   %[src], %[src], %[int8_min]"        "\n\t"
2
             "sxtb16  %[aux0], %[src], ror #0"            "\n\t"
3
             "sxtb16  %[aux2], %[src], ror #8"            "\n\t"
4
             "smulbb  %[aux1], %[aux0], %[gain]"          "\n\t"
5
             "smultb  %[aux5], %[aux0], %[gain]"          "\n\t"
6
             "smulbb  %[aux3], %[aux2], %[gain]"          "\n\t"
7
             "smultb  %[aux6], %[aux2], %[gain]"          "\n\t"
8
             "pkhbt   %[aux0], %[aux1], %[last], lsl #16" "\n\t"
9
             "pkhbt   %[aux1], %[aux3], %[aux1], lsl #16" "\n\t"
10
             "pkhbt   %[aux2], %[aux5], %[aux3], lsl #16" "\n\t"
11
             "pkhbt   %[aux3], %[aux6], %[aux5], lsl #16" "\n\t"
12
             "shadd16 %[aux4], %[aux0], %[aux1]"          "\n\t"
13
             "shadd16 %[aux5], %[aux2], %[aux3]"          "\n\t"
14
             "pkhtb   %[p0], %[aux1], %[aux4], asr #16"   "\n\t"
15
             "pkhbt   %[p1], %[aux4], %[aux2], lsl #0"    "\n\t"
16
             "pkhtb   %[p2], %[aux3], %[aux5], asr #16"   "\n\t"
17
             "pkhbt   %[p3], %[aux5], %[aux3], lsl #16"   "\n\t"
18
             : [src] "+r" (*src),
19
               [p0] "=r" (p0),
20
               [p1] "=r" (p1),
21
               [p2] "=r" (p2),
22
               [p3] "=r" (p3),
23
               [aux0] "r" (aux0),
24
               [aux1] "r" (aux1),
25
               [aux2] "r" (aux2),
26
               [aux3] "r" (aux3),
27
               [aux4] "r" (aux4),
28
               [aux5] "r" (aux5),
29
               [aux6] "r" (aux6)
30
             : [int8_min] "r" (0x80808080),
31
               [gain] "r" (0x00000100),
32
               [last] "r" (process.last_));

Leider funktioniert der Code dann nur noch wenn die temporären Variablen 
volatile deklariert werden, was den unschönen Nebeneffekt hat dass 
erstmal zig loads durchgeführt werden bevor irgendwas interessantes 
passiert...
1
08002790:   ldrh    r1, [r4, #30]    // :(
2
08002792:   ldr     r0, [sp, #28]    // :(
3
08002794:   ldr     r2, [sp, #24]    // :(
4
08002796:   ldr     r3, [sp, #20]    // :(
5
08002798:   ldr     r7, [sp, #16]    // :(
6
0800279a:   ldr.w   lr, [sp, #12]    // :(
7
0800279e:   ldr.w   r12, [sp, #8]    // :(
8
080027a2:   ldr.w   r8, [sp, #4]     // :(
9
080027a6:   ldr     r6, [pc, #232]   // :(
10
080027a8:   ldr     r5, [r6, #0]     // :(
11
080027aa:   mov.w   r9, #2155905152
12
080027ae:   mov.w   r10, #256      
13
080027b2:   ssub8   r5, r5, r9
14
080027b6:   sxtb16  r0, r5
15
080027ba:   sxtb16  r3, r5, ror #8
16
080027be:   smulbb  r2, r0, r10
17
080027c2:   smultb  r12, r0, r10
18
080027c6:   smulbb  r7, r3, r10
19
080027ca:   smultb  r8, r3, r10
20
080027ce:   pkhbt   r0, r2, r1, lsl #16
21
080027d2:   pkhbt   r2, r7, r2, lsl #16
22
080027d6:   pkhbt   r3, r12, r7, lsl #16
23
080027da:   pkhbt   r7, r8, r12, lsl #16
24
080027de:   shadd16 lr, r0, r2
25
080027e2:   shadd16 r12, r3, r7
26
080027e6:   pkhtb   r2, r2, lr, asr #16
27
080027ea:   pkhbt   r3, lr, r3
28
080027ee:   pkhtb   r12, r7, r12, asr #16
29
080027f2:   pkhbt   r8, r12, r7, lsl #16
30
080027f6:   str     r5, [r6, #0]


Meine Recherche hat ergeben, dass man an den sogenannten Constraints 
(z.b. "r" vor der Variablen) wohl noch jede Menge drehen kann. Leider 
scheinen die DSP Instruktionen aber alle ausschließlich direkt mit 
Registern bzw. als "Register" deklarierte Variablen arbeiten zu 
wollen...
Was tut man in so einem Fall? Diese ganzen Loads sind komplett 
umsonst...


/edit
aux Variablen sind in & output, falsch kopiert...

von Clemens L. (c_l)


Lesenswert?

"volatile" ist oft ein Zeichen, dass du die passenden Modifier nicht 
gesetzt hast: https://gcc.gnu.org/onlinedocs/gcc/Modifiers.html

Wenn dir die Register ausgehen, solltest du nur die unwichtigsten Werte 
in temporäre Variablen auslagern. (Und dann kannst du auch "m" benutzen 
und das "ld" explizit hinschreiben.)

von Simon (Gast)


Lesenswert?

Ist dein Anwendungsfall wirklich nicht durch CMSIS abgedeckt? Dann 
könntest du dir den Umgang mit ASM sparen ;-)

von Vincent H. (vinci)


Lesenswert?

Den passenden Modifier scheint es in meinem Fall nicht zu geben.

Deklariere ich die "aux" Variablen lediglich als Inputs, so passiert 
folgendes...
1
080027c8:   sxtb16  r3, r3
2
080027cc:   sxtb16  r3, r3, ror #8
3
080027d0:   smulbb  r3, r3, r1
4
080027d4:   smultb  r3, r3, r1
5
080027d8:   smulbb  r3, r3, r1
6
080027dc:   smultb  r3, r3, r1
7
080027e0:   pkhbt   r3, r3, r0, lsl #16
8
080027e4:   pkhbt   r3, r3, r3, lsl #16
9
080027e8:   pkhbt   r3, r3, r3, lsl #16
10
080027ec:   pkhbt   r3, r3, r3, lsl #16
11
080027f0:   shadd16 r3, r3, r3
12
080027f4:   shadd16 r3, r3, r3
13
080027f8:   pkhtb   r5, r3, r3, asr #16
14
080027fc:   pkhbt   r0, r3, r3
15
08002800:   pkhtb   r1, r3, r3, asr #16
16
08002804:   pkhbt   r3, r3, r3, lsl #16

Dass da nichts Vernünftiges rauskommt muss ich wohl nicht erwähnen. :D 
"volatile" oder auch der "+r" Modifier verhindern das, sorgen aber 
wieder für lästige Loads weil der Compiler offensichtlich nicht klug 
genug ist um zu erkennen, dass sämtliche "aux" vorher eigentlich nicht 
geladen werden müssen...

"m" ist für DSP Instruktionen nicht zulässig und der Compiler schreit 
sofort nach Registern.


Deklariere ich sämtliche "aux" als "Output" only (=r), dann kommt auch 
nur Blödsinn raus... :/



/edit
Ok...
volatile + (=r) sorgt dafür, dass die Loads am Anfang verschwinden. 
Dafür hab ich nun unnötige Stores der ganzen "aux" Variablen. :)

von Clemens L. (c_l)


Lesenswert?

Wenn die DSP-Befehle Register haben wollen, musst du ihnen Register 
geben.

Wenn du temporäre Variablen brauchst, solltest du ihre Adresse 
übergeben, und st/ld von Hand machen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Vincent H. schrieb:
> Dass da nichts Vernünftiges rauskommt muss ich wohl nicht erwähnen.

Ist üblicherweise ein Symptom vergessener earyl-clobbers "&".

Wenn das asm eh gast alle Register braucht, kann man auch dem Allokator 
"helfen" und Register explizit vergeben.

Solche mega-asm sind aber oft einfach nur aua und ein Indiz, so langsam 
Assembler einzusetzen — spätestens wenns mehr Register (gleichzeitig) 
braucht als die Maschine überhaupt hat...

von J. V. (janvi)


Lesenswert?

> Was tut man in so einem Fall?

Du musst alle benutzten Register retten (z. Bsp. auf den Stack), evlt. 
dafür sorgen dass kein Interrupt reinspukt, dann die Berechnung mit den 
ASM Teil ausführen und abschliessend die Register wieder herstellen. 
Versteht sich von selbst, daß ein solcher Asm Teil nicht rekursiv sein 
muss. C Compiler
sind i.d.R. aber darauf angewiesen rekursiven Code zu erzeugen weshalb 
sie
praktisch nie was großes in Registern rechnen sondern immer auf dem 
Stack arbeiten obgleich das viel langsamer sein kann.

>Diese ganzen Loads sind komplett umsonst...

es wäre auch falsch, wenn ein Compiler den in ASM codierten Code 
optimieren möchte. Viel mehr als das Einsetzen der passenden 
Sprungweiten bzw. das aneinanderreihen der Literale sollte man den 
Compiler bzw. Linker nicht machen lassen und im ASM Code hat ein 
Compiler gar nix zu suchen.

Warum schreibst du Inline und machst nicht ein vernünftiges Asm File mit 
definierter Schnittstelle und Funktionsaufrufen in deinem Projekt ? 
Inline ist eher für einzelne ASM Anweisung wie das Abschalten von 
Interrupts oder so gedacht wo aber die CMSIS schon was über "Intrinsics" 
Funktionen bereit stellt. Es gibt wohl noch viel was die CMSIS nicht 
abdeckt und auch die ST-Libs geben nur typische aber keine speziellen 
Einstellungen an so daß man durchaus sinnvolles erreichen kann indem man 
selbst an den Registern rumfummelt oder gar einen Teil in ASM schreibt.

von Vincent H. (vinci)


Lesenswert?

Johann L. schrieb:
> Vincent H. schrieb:
>> Dass da nichts Vernünftiges rauskommt muss ich wohl nicht erwähnen.
>
> Ist üblicherweise ein Symptom vergessener earyl-clobbers "&".
>
> Wenn das asm eh gast alle Register braucht, kann man auch dem Allokator
> "helfen" und Register explizit vergeben.
>
> Solche mega-asm sind aber oft einfach nur aua und ein Indiz, so langsam
> Assembler einzusetzen — spätestens wenns mehr Register (gleichzeitig)
> braucht als die Maschine überhaupt hat...


Danke!
Die "extended asm" Seite hat zu den "early-clobbers" hierzu auch ein 
gutes Beispiel:
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

Exakt was in dem von mir geposteten Fall passiert ist. Ich hab die "&" 
nun drin und kein einziger unnötiger str/load ist mehr vorgekommen.


@"gleich in asm"
Ich hab bereits überlegt das ganze direkt als asm-file abzulegen, jedoch 
finde ich sind 16-asm Befehle jetzt nicht unbedingt mega viel. Es 
war/ist irgendwie grenzwertig... Da das ganze aber innerhalb einer 
Funktion werkeln soll, die blockweise einen Buffer abarbeitet, bietet 
sich inline assembler für einen einzelnen Block dann doch irgendwie an. 
Das komplett in Assembler zu schreiben wollt ich mir dann doch nicht 
unbedingt antun...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.