Forum: Compiler & IDEs Cortex M4 Assemblercode richtig in C nutzen


von Christopher S. (shalec)


Lesenswert?

Hallo allerseits,

ich möchte gerne die Bitfolge eines Integers invertieren, d.g. RBIT [1] 
nutzen. Ich dachte, dass ich sowas durch "__builtin_rbit(x)" nutzen 
kann, dies führte aber zu einem Fehler. Aber wie nutze ich das korrekt 
in C?

Mein Ziel: Eine Bitfolge umkehren.
D.h. angenommen ich habe "0b1010" dann "0b0101". Laut der Beschreibung 
soll das wohl RBIT leisten. Eine schnellere Lösung gibt es dann wohl 
nicht.
Die Verwendung: Ich übergebe einen uint32_t typen an die Funktion und 
bekomme die umgekehrte Bitfolge zurück.


[1] 
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABJJDDB.html

von Walter T. (nicolas)


Lesenswert?

Was passiert denn, wenn Du den Befehl einfach ausschreibst mit 
Shift-Operationen? Wie sieht das erzeuge Assembler-Listing aus?

von (prx) A. K. (prx)


Lesenswert?

Christopher S. schrieb:
> dies führte aber zu einem Fehler.

Zu welchem?

von Christopher S. (shalec)


Lesenswert?

Meinst du, wenn ich den Befehl in C per Hand programmiere? Ich bin 
relativ neu in Programmierwelt. Nutze C erst seit knapp 3 Monaten und 
das ist praktisch neues Terrain.

von Christopher S. (shalec)


Lesenswert?

A. K. schrieb:
> Christopher S. schrieb:
>> dies führte aber zu einem Fehler.
>
> Zu welchem?

undefined reference to `__builtin_rbit'

und
gf_arithmetics.c:12:20: warning: implicit declaration of function 
'__builtin_rbit' [-Wimplicit-function-declaration]
 #define REVERT(x) (__builtin_rbit(x))
                    ^
gf_arithmetics.c:205:14: note: in expansion of macro 'REVERT'
  uint8_t a = REVERT(A[10]);

Implementiert als:
#define REVERT(x) (__builtin_rbit(x))

das hier funktioniert übrigens:
#define CLZ(x) (__builtin_clz(x))

: Bearbeitet durch User
von 2⁵ (Gast)


Lesenswert?

Was passt an
1
#include <stdint.h>
2
3
uint32_t rbit(uint32_t c)
4
{
5
    //return __builtin_rbit(c); 
6
    asm("rbit %1,%0" : "=r" (c) : "r" (c));
7
    return c;
8
}

nicht?
1
$ arm-none-eabi-gcc -mcpu=cortex-m4 -S -O2 rbit.c 
2
$ cat rbit.s
3
[...]        
4
        .thumb
5
        .thumb_func
6
        .fpu softvfp
7
        .type   rbit, %function
8
rbit:
9
        @ args = 0, pretend = 0, frame = 0
10
        @ frame_needed = 0, uses_anonymous_args = 0
11
        @ link register save eliminated.
12
        .syntax unified
13
@ 6 "rbit.c" 1
14
        rbit r0,r0
15
@ 0 "" 2
16
[...]

von (prx) A. K. (prx)


Lesenswert?

static inline __attribute__((always_inline))
uint32_t rbit(uint32_t x)
{
  asm("rbit %0,%1" : "=r" (x) : "r" (x));
  return x;
}

: Bearbeitet durch User
von Christopher S. (shalec)


Lesenswert?

2⁵ schrieb:
> Was passt an
>
1
> #include <stdint.h>
2
> 
3
> uint32_t rbit(uint32_t c)
4
> {
5
>     //return __builtin_rbit(c);
6
>     asm("rbit %1,%0" : "=r" (c) : "r" (c));
7
>     return c;
8
> }
9
>
>
> nicht?
>

Ich wusste noch nicht, dass man das so schreiben kann. Daher frage ich 
ja :)

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:
>   asm("rbit %0,%1" : "=r" (x) : "r" (x));

... oder andersrum, %1,%0. Weiss grad nicht, wie arm asm das hält.

: Bearbeitet durch User
von Christopher S. (shalec)


Lesenswert?

Vielen Dank an alle Codeschnipsel, könntet ihr mir nochmal erklären 
worin der Unterschied in der "static inline" Variante und der anderen 
liegt?

von (prx) A. K. (prx)


Lesenswert?

Christopher S. schrieb:
> Vielen Dank an alle Codeschnipsel, könntet ihr mir nochmal erklären
> worin der Unterschied in der "static inline" Variante und der anderen
> liegt?

Ohne ist das eine externe Funktionsdefinition, die es exakt einmal geben 
darf. Mit "static inline" darf sie in jedem Quellfile auftauchen, wird 
aber meist inlined. Der Kram dahinter ändert das "meist" zu "immer".

von eagle user (Gast)


Lesenswert?

darf ich bitte auch so eine Frage fragen?
Warum habe ich in CTZ() das Ergebnis doppelt drin stehen? Es 
funktioniert, aber hier kann man doch bestimmt etwas verbessern?
1
// asm.h
2
#pragma once
3
#include <stdint.h>
4
5
static inline int  // count leading zeroes
6
CLZ (uint32_t input)
7
{
8
uint32_t  result;
9
10
   __asm__ (" clz   %0, %1" : "=r" (result) : "r" (input));
11
   return result;
12
}
13
14
static inline int  // count trailing zeroes
15
CTZ (uint32_t input)
16
{
17
uint32_t  result, tupni;
18
19
   __asm__ (" rbit  %0, %2\n"
20
            " clz   %1, %0" : "=r" (tupni), "=r" (result) : "r" (input));
21
   return result;
22
}

von Markus F. (mfro)


Lesenswert?

eagle user schrieb:
> aber hier kann man doch bestimmt etwas verbessern?

Jein.

Inline-Assembler ist bezüglich des möglichen 
sich-selbst-ins-Knie-Schiessen-Potentials die Steigerung (um nicht zu 
sagen, Potenzierung) von C.

Schauen wir uns mal an, was der Inline-Assembler daraus macht:
1
static inline int  // count trailing zeroes
2
CTZ (uint32_t input)
3
{
4
uint32_t  result, tupni;
5
6
    __asm__ ("  rbit  %[tupni], %[input]\n"
7
            "   clz   %[result], %[tupni]"
8
            /* output */ : [tupni]"=r" (tupni), [result]"=r" (result)
9
            /* input */ : [input]"r" (input));
10
    return result;
11
}
12
13
int main(int argc, char *argv[])
14
{
15
    xprintf("ctz 0x0e: 0x%02x\r\n", CTZ(0x0e));
16
    while (1);
17
}
(ich habe hier die %[]-Schreibweise benutzt (so auch erlaubt), die m.E. 
wesentlich besser zu lesen ist.
Dabei kommt das dabei raus:
1
    2074:   230e        movs    r3, #14
2
    2076:   fa93 f2a3   rbit    r2, r3
3
    207a:   fab2 f182   clz r1, r2

Wir konstatieren: funktioniert, aber belegt - wie Du ja bereits bemerkt 
hast - ein paar Register/Variablen mehr als notwendig. Man könnte jetzt
auf die Idee kommen, das zu optimieren:
1
static inline int  // count trailing zeroes
2
CTZ2 (uint32_t input)
3
{
4
    uint32_t  result, tupni;
5
6
    __asm__ ("  rbit  %[result], %[input]\n"
7
            "   clz   %[input], %[result]"
8
            /* output */ : [result]"=r" (result)
9
            /* input */ : [input]"r" (input));
10
    return result;
11
}
... und richtig:
1
    2070:   210e        movs    r1, #14
2
    2072:   b508        push    {r3, lr}
3
    2074:   fa91 f1a1   rbit    r1, r1
4
    2078:   fab1 f181   clz r1, r1

Prima, statt drei Registern brauchen wir jetzt nur noch eins. 
Funktioniert.


Allerdings nur genauso lange, bis wir auf die Idee kommen, das ganze 
mehrfach aufzurufen:
1
int  // count trailing zeroes
2
CTZ2 (uint32_t input)
3
{
4
    uint32_t  result, tupni;
5
6
    __asm__ ("  rbit  %[result], %[input]\n"
7
            "   clz   %[input], %[result]"
8
            /* output */ : [result]"=r" (result)
9
            /* input */ : [input]"r" (input));
10
    return result;
11
}
12
13
int main(int argc, char *argv[])
14
{
15
    xprintf("ctz2 0x0e: 0x%02x, ctz2 0x0e: 0x%02x\r\n", CTZ(0x0e), CTZ(0x0e));
16
17
    while (1);
18
}

Dabei kommt das raus:
1
    2074:   230e        movs    r3, #14
2
    2076:   fa93 f1a3   rbit    r1, r3
3
    207a:   fab1 f381   clz r3, r1
4
    207e:   fa93 f3a3   rbit    r3, r3
5
    2082:   fab3 f283   clz r2, r3

Der zweite Aufruf macht nicht das, was er soll. Statt "0xe" als Eingabe 
wird das Ergebnis des letzten Aufrufs genommen, was definitiv nicht das 
ist, was wir haben wollten.

Leider kann man über input-constraints nicht angeben, ob input-Parameter 
im Inline-Code verändert werden oder nicht, der Compiler geht aber davon 
aus, dass das nicht der Fall ist. Früher konnte man mal Parameter in der 
Clobber-Liste angeben, das geht aber nicht mehr. Also muss man selbst 
darauf achten.

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Was passiert denn, wenn Du den Befehl einfach ausschreibst mit
> Shift-Operationen?

Weil ich neugierig war, habe ich es einfach mal probiert:

Aus diesem naiv zusammengeschusterten Stück:
1
/* Test: Bits vertauschen */
2
uint32_t reverseBitOrder32(uint32_t in)
3
{
4
    uint32_t out = 0, maskl = 0x00010000, maskr = 0x00008000;
5
    uint_fast8_t i = 1;
6
    while( maskl ) {
7
        out |= (in & maskl) >> i;
8
        out |= (in & maskr) << i;
9
        maskl<<=1;
10
        maskr>>=1;
11
        i+=2;
12
    }
13
14
    return out;
15
}
 erzeugt der Compiler (GCC 5.4.1) für einen STM32F446 bei 
Optimierungsstufe -O2 noch:
1
08000506  bne.n  0x80004e8 <main+400>
2
08000508  str.w  lr, [sp, #20]
3
0800050C  ldr.w  r12, [sp, #20]
4
08000510  mov.w  r7, #32768  ; 0x8000
5
08000514  mov.w  lr, #0
6
08000518  movs  r2, #1
7
0800051A  mov.w  r0, #65536  ; 0x10000
8
0800051E  and.w  r1, r12, r0
9
08000522  and.w  r3, r12, r7
10
08000526  lsrs  r1, r2
11
08000528  lsls  r3, r2
12
0800052A  adds  r2, #2
13
0800052C  orrs  r3, r1
14
0800052E  cmp  r2, #33  ; 0x21
15
08000530  mov.w  r0, r0, lsl #1
16
08000534  orr.w  lr, lr, r3
17
08000538  mov.w  r7, r7, lsr #1
18
 ...
d.h. erkennt wohl nicht die konstante Vertauschung der Bits, für die ein 
Hardware-Befehl existiert.

von Walter T. (nicolas)


Lesenswert?

Aus der Variante:
1
/* Test: Bits vertauschen */
2
uint32_t reverseBitOrder32(uint32_t in)
3
{
4
    uint32_t out = 0, temp = in;
5
    for( uint_fast8_t i = 0; i<32; i++ ) {
6
        out <<= 1;
7
        out |= in & 1;
8
        in >>=1;
9
    }
10
    return out;
11
}

Macht er das hier:
1
080004EC  mov  r2, r4
2
080004EE  movs  r3, #32
3
080004F0  movs  r1, #0
4
080004F2  and.w  r0, r2, #1
5
080004F6  subs  r3, #1
6
080004F8  orr.w  r1, r0, r1, lsl #1
7
080004FC  mov.w  r2, r2, lsr #1
8
08000500  bne.n  0x80004f2 <main+410>

Er rafft es also immer noch nicht...


OK, also ist hier inline-Assembler wohl angebracht. Einen Versuch war es 
trotzdem wert.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Dazu gibt's einen enhancement request:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50481

Es ist also nicht ausgeschlossen, dass sich ein weiterer Versuch mit 
einer der nächsten Compilerversionen lohnt.

CTZ gibt's schon länger als __builtin_ctz().

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Markus F. schrieb:
> Inline-Assembler ist bezüglich des möglichen
> sich-selbst-ins-Knie-Schiessen-Potentials die Steigerung (um nicht zu
> sagen, Potenzierung) von C.

Stimmt. ;-)

Vorschlag:
1
static inline int
2
CTZ (unsigned input)
3
{
4
    unsigned result;
5
    __asm__ ("  rbit  %[result], %[input]\n"
6
            "   clz   %[result], %[result]"
7
            : [result]"=r" (result)
8
            : [input]"r" (input));
9
    return result;
10
}

> Leider kann man über input-constraints nicht angeben, ob input-Parameter
> im Inline-Code verändert werden oder nicht

Aber man kann beim Output angeben, dass er auch ein Input ist:
1
static inline int
2
CTZ (unsigned input)
3
{
4
    __asm__ ("  rbit  %[input], %[input]\n"
5
            "   clz   %[input], %[input]"
6
            : [input]"+r" (input)
7
            : );
8
    return input;
9
}
Anders würden sich 1-Operand Befehle wie x86 NEG nicht sinnvoll abbilden 
lassen. Die Variante mit separatem Resultat erlaubt dem Compiler aber, 
einen möglicherweise sonst erforderlichen Move einzusparen.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Man gibt dem Compiler zusätzliche Freiheit, wenn man die Befehle 
auftrennt:
1
static inline unsigned
2
RBIT (unsigned input)
3
{
4
    unsigned result;
5
    __asm__ ("  rbit  %[result], %[input]\n"
6
            : [result]"=r" (result)
7
            : [input]"r" (input));
8
    return result;
9
}
10
11
static inline unsigned
12
CLZ (unsigned input)
13
{
14
    unsigned result;
15
    __asm__ ("  clz  %[result], %[input]\n"
16
            : [result]"=r" (result)
17
            : [input]"r" (input));
18
    return result;
19
}
20
21
static inline unsigned
22
CTZ (unsigned input)
23
{
24
    return CLZ(RBIT(input));
25
}

Für eine Maschine mit einfacher superskalarer Befehlsausführung (wie 
Cortex M7) kann der Compiler dann die Befehle passend umordnen. Die 
beiden Befehle lassen sich ja nur nacheinander ausführen. In dieser Form 
hingegen hingegen lassen sich die Befehle von 2 CTZ Aufrufen so 
ineinander verzahnen, so dass pro Takt auch 2 Befehle ausgeführt werden.

: Bearbeitet durch User
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.