GCC-Interna: Optimierung von Bitmasken

Gast #1997485
Lesenswert?

Hallo,

wieder so eine Sache, die sich mir nicht erschließt:
MODULATE_A ist hier ein Bitfeld von einem Bit Breite, das auf ein SFR 
außerhalb des I/O-Bereiches zeigt (hier TIMSK, Details im Anhang).

Dann nehme man folgende Schnippsel her:
1
/* Variante A */
2
if (phase_a > 0)
3
  MODULATE_A = 1;
4
else
5
  MODULATE_A = 0;
und
1
/* Variante B */
2
MODULATE_A = (phase_a > 0);

Da fällt auf (avr-gcc 4.5.1, -Os): Bei Variante A werden schlicht und 
ergreifend zwei Masken erzeugt und mit ori und andi verarbeitet:
1
 520:  49 b7         in  r20, 0x39  ; 57
2
 522:  88 23         and  r24, r24
3
 524:  11 f0         breq  .+4        ; 0x52a <__stack+0xcb>
4
 526:  48 60         ori  r20, 0x08  ; 8
5
 528:  01 c0         rjmp  .+2        ; 0x52c <__stack+0xcd>
6
 52a:  47 7f         andi  r20, 0xF7  ; 247
7
 52c:  49 bf         out  0x39, r20  ; 57

Wohingegen der GCC sich bei zweiter Variante mit obskuren Operationen 
abquält:
1
 4fe:  41 e0         ldi  r20, 0x01  ; 1
2
 500:  88 23         and  r24, r24
3
 502:  09 f4         brne  .+2        ; 0x506 <__stack+0xa7>
4
 504:  40 e0         ldi  r20, 0x00  ; 0
5
 506:  42 95         swap  r20
6
 508:  40 7f         andi  r20, 0xF0  ; 240
7
 50a:  89 b7         in  r24, 0x39  ; 57
8
 50c:  8f 7e         andi  r24, 0xEF  ; 239
9
 50e:  84 2b         or  r24, r20
10
 510:  89 bf         out  0x39, r24  ; 57

Woher kommt das denn? Je nachdem, welches Bit gemeint ist (hier OCIE1A) 
schiebt und rotiert und addiert er sich einen zurecht und macht 
eigentlich alles, nur nicht das schicke, schlanke ori/andi von zuvor.

Danke und Gruß,
Sven
Persönliche Seite #1999810
Lesenswert?

Wie wär's mit einem compilierbaren Beispiel?
Welche Optionen sind gesetzt?
Was ist phase_a?
Wieso IN und OUT wenn das auf ein "SFR außerhalb des I/O-Bereiches 
zeigt"?
Welcher µC?

Übersetze mit -fdump-tree-all -da (erzeugt ca. 150 temporäre Dateien je 
nach Verschalterung) und du siehst zumindest den Pass wo es passert 
(bzw. nicht passiert).

AFAIK implementiert das avr-Backend keine Pattern für Vergleiche, also 
keine Pattern "scond" wie in den Internals [1] beschrieben

[1] 
http://gcc.gnu.org/onlinedocs/gccint/Standard-Names.html#Standard-Names
Gast #2000101
Lesenswert?

Johann L. schrieb:
> Wie wär's mit einem compilierbaren Beispiel?
Auha, sorry. Ist wieder bei der Vorschau abhanden gekommen :-(

> Welche Optionen sind gesetzt?
Übersetzung:
1
$ avr-gcc -Wall -Wextra -mmcu=atmega8 -Os -g -DF_CPU=16000000UL --std=c99 -ffunction-sections -fdata-sections -o avrtest.o -c avrtest.c

Bindung:
1
$ avr-gcc -mmcu=atmega8 -g -Wl,--gc-sections -o object avrtest.o

Listing:
1
$ avr-objdump -h -S -d object > list

Version:
1
$ avr-gcc --version
2
avr-gcc (GCC) 4.5.1




> Wieso IN und OUT wenn das auf ein "SFR außerhalb des I/O-Bereiches
> zeigt"?
Korrigiere, außerhalb des bit-adressierbaren I/O-Bereiches. Das sollte 
zum Ausdruck bringen, dass sbi/cbi nicht mehr funktionieren.

> Übersetze mit -fdump-tree-all -da (erzeugt ca. 150 temporäre Dateien je
> nach Verschalterung) und du siehst zumindest den Pass wo es passert
> (bzw. nicht passiert).
>
> AFAIK implementiert das avr-Backend keine Pattern für Vergleiche, also
> keine Pattern "scond" wie in den Internals [1] beschrieben
Tatsächlich, interessant, mal hineinzuschauen.
Bereits im dritten Schritt (gimple) zerlegt er die erste Variante in 
einen bedingten Sprung mit ori/andi, wohingegen sich die zweite Variante 
bis zum Schluss als Ausdruck hält und dann wohl mehr oder weniger 1:1 
übersetzt wird.

Danke!
Angehängte Dateien:
Persönliche Seite #2001836
Lesenswert?

Sven P. schrieb:
> Übersetzung:
> ...

Bei solchen Beispielen reicht schon compilierbar im engeren Sinne, also
1
$ avr-gcc -c foo.c -save-temps ...

Mit save-temps werden s- und i-File nicht gelöscht. Linkfähig (main 
etc.) braucht es nicht zu sein.

avr-gcc 4.5 übersetzt die Beispiele
1
/* Variante A */
2
void foo_a (uint8_t phase_a)
3
{
4
    if (phase_a > 0)
5
        MODULATE_A = 1;
6
    else
7
        MODULATE_A = 0;
8
}
9

10
/* Variante B */
11
void foo_b (uint8_t phase_a)
12
{
13
    MODULATE_A = (phase_a > 0);
14
}

so wie hingeschrieben. In B berechnet er zunächst phase_a != 0 (phase_a 
ist unsigned), das ist 0 oder 1, und schiebt das dann an die benötigte 
Bitposition 4 (swap+andi).

Das Problem bei der Optimierung ist, daß man bei der Erzeugung des Bits 
schon was über dessen Verwendung wissen müsste (bzw. umgekehrt), um das 
zur Optimierung zu verwenden. So wäre es in Fall B günstiger, das Bit 
nicht in Position 0 zu erzeugen, sondern in Position 4. Die optimale 
Sequenz
1
in   r20, 0x39
2
cbr  r20, 4
3
cpse r24, __zero_reg__  ;; phase_a
4
sbr  r20, 4
5
out  0x39, r20
mit gcc zu erzeugen wäre recht viel Arbeit und würe auch Codeänderungen 
an Stellen nach sich ziehen, die scheinbar nichts mit der genannten 
Sequenz zu tun haben.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren