Forum: Compiler & IDEs Erzeugung von reduntantem Code in AVR-GCC


von Tim  . (cpldcpu)


Lesenswert?

Ich habe mich etwas mit der Größenoptimierung von durch AVR-GCC 
erzeugten Code beschäftigt - Hintergrund ist die Reduktion der Codegröße 
von Micronucleus (bootloader).

https://github.com/Bluebie/micronucleus-t85/pull/30

Der von AVR-GCC 4.7.2 erzeugte code ist teilweise beeindruckend optimal. 
Ich bin über eine Merkwürdigkeit gestolpert:

Aus dieser Zeile:
1
uchar isLast = ((currentAddress % SPM_PAGESIZE) == 0);
2
if (isLast)...
currentAddress ist short, SPM_PAGESIZE=64 per define

wird dieser Code erzeugt:
1
1e02: 80 91 69 00  lds r24, 0x0069
2
1e06: 90 91 6a 00  lds r25, 0x006A  <-- redundant
3
1e0a: 8f 73        andi r24, 0x3F ; 63 <-- set Z flag if modulo==0
4
1e0c: 99 27        eor r25, r25    <-- redundant: clear R25 and Z flag
5
1e0e: 89 2b        or r24, r25     <--- redundant: set Z flag properly again
6
1e10: 39 f0        breq .+14      ; 0x1e20 <main+0x1ee>
7
1e12: 0d c0        rjmp .+26      ; 0x1e2e <main+0x1fc>

Wie man sieht, sind hier drei völlig redundante Befehle erzeugt worden.

Bug oder Feature?

Compileroptionen:
1
-g2 -ffunction-sections -fdata-sections -Wall -Os -fno-inline-small-functions -fno-move-loop-invariants -fno-tree-scev-cprop  -I. -Ilibs-device

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hast du ein i-File, erzeugt mit -save-temps?

von Tim  . (cpldcpu)


Lesenswert?

Sieht nicht so spannend aus:
1
   unsigned char isLast = ((currentAddress % 64) == 0);
2
3
    if (isLast) events |= (2);

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ja, es werden ein paar mehr Instruktionen erzeugt als erforderlich.  Das 
im GCC zu ändern ist nichttrivial und nicht auf den AVR-Spezifischen 
Teil beschränkt.

Falls die 3 Instruktionen mehr ein Killerkriterium sind dann geh über 
eine 8-Bit Zwischenvariable.

von Tim  . (cpldcpu)


Lesenswert?

Danke! Ich habe das Problem letztendlich durch einen cast auf ein char 
gelöst. Ein wenig unsauber...

von (prx) A. K. (prx)


Lesenswert?

Tim .  schrieb:
> Bug oder Feature?

Wenn ein Compiler nicht perfekten Code erzeugt, dann ist das weder Bug 
noch Feature.

GCC geht in weiten Teilen davon aus, dass der Zielprozessor 
Maschinenworte effizient verarbeiten kann. Die Sprache C geht davon aus, 
dass ein Maschinenwort mindestens 16 Bits breit ist. Folglich geht GCC 
eigentlich davon aus, dass der AVR-Prozessor 16-Bit Worte effizient 
verarbeiten kann.

Insofern ist es eher als nützliche Feature anzusehen, dass der Compiler 
nicht überall mit 16-Bit Codesequenzen arbeitet, sondern oft merkt, dass 
er mit 8-Bit Codesequenzen auch zum Ziel kommt. Ein Anspruch darauf, 
dies immer zu tun wo es möglich ist, entsteht dadurch aber nicht.

von Berti (Gast)


Lesenswert?

Auch wenn's nicht trivial zu ändern ist... gibt es denn eine 
Möglichkeit, in solchen Fällen (Promotion?) eine Warning anzuzeigen?

BTW, Johann, hattest du nicht mal 4.7er AVR-GCCs unter Windows 
kompilliert und irgendwo zur Verfügung gestellt? Gibt es so ein Paket 
auch mit der aktuellsten Version? 4.8.x?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Berti schrieb:
> Auch wenn's nicht trivial zu ändern ist... gibt es denn eine
> Möglichkeit, in solchen Fällen (Promotion?) eine Warning anzuzeigen?

Mein Fehler; mit Promotion hat das hier nix to tun, die Inputs sind ja 
bereits 16-Bit Werte.  Eher Back-Propagation, falls es den Begriff gibt, 
d.h. anhand der späteren Verwendung müsste erkannt werden, daß die 
oberen 8 Bits des geladenen Wertes nicht benötigt werden und nur der 
verwendete Teil geladen wird, vorausgesetzt das ist erlaubt und 
günstiger.

> BTW, Johann, hattest du nicht mal 4.7er AVR-GCCs unter Windows
> kompilliert und irgendwo zur Verfügung gestellt? Gibt es so ein Paket
> auch mit der aktuellsten Version? 4.8.x?

Ein Link ins Verzeichnis und Beschreibung ist in

http://lists.gnu.org/archive/html/avr-gcc-list/2012-09/msg00024.html

Im Verzeichnis ist ein avr-gcc 4.8 für MinGW32 von 2013-03-06.

Die Version ist ein 4.8.1 (prerelease) der einige Tage vor der 
offiziellen 4.8.1 datiert.

Neuere Versinen gibt's keine, da ich kaum noch zu avr-gcc beitrage.

von Berti (Gast)


Lesenswert?

Johann L. schrieb:
> Neuere Versinen gibt's keine, da ich kaum noch zu avr-gcc beitrage.

Das ist aber schade. ;) Also, dass du weniger zum Code beiträgst, ist ja 
nicht schlimm, aber du postest hier sehr viel zum GCC und stehst mit 
vielen Hintergrundinfos bereit, bist also noch irgendwie involviert. Da 
wäre es doch super, wenn's zumindest neue Stable-Versionen gäbe. :) 
Viele haben die Pakete lieb gewonnen, weil die neueren Versionen hier 
wirklich besser sind, als die alten. :)

von Tim  . (cpldcpu)


Lesenswert?

Hier noch einmal genau die gleiche Mist wie oben. In diesem Fall lässt 
sich AVR-GCC aber nicht einmal durch den Cast beeinflussen.


1
       if (((uchar)idlePolls)&0x90)  DDRB  |= _BV(NANITE_CTRLPIN);
Das erzeugt AVR-GCC:
1
    1eb6:  80 91 66 00   lds  r24, 0x0066
2
    1eba:  90 91 67 00   lds  r25, 0x0067
3
    1ebe:  9c 01         movw  r18, r24
4
    1ec0:  20 79         andi  r18, 0x90  ; 144
5
    1ec2:  33 27         eor  r19, r19
6
    1ec4:  23 2b         or  r18, r19
7
    1ec6:  09 f0         breq  .+2        ; 0x1eca <main+0x324>
8
    1ec8:  ba 9a         sbi  0x17, 2  ; 23

Das will ich haben:
1
    1eb6:  80 91 66 00   lds  r18, 0x0066
2
    1ec0:  20 79         andi  r18, 0x90  ; 144
3
    1ec6:  09 f0         breq  .+2        ; 0x1eca <main+0x324>
4
    1ec8:  ba 9a         sbi  0x17, 2  ; 23

von Tim  . (cpldcpu)


Lesenswert?

So klappt es:
1
if (*((unsigned char*)&idlePolls)&0x90)  DDRB  |= _BV(NANITE_CTRLPIN);
1
    1eb6:  80 91 66 00   lds  r24, 0x0066
2
    1eba:  80 79         andi  r24, 0x90  ; 144
3
    1ebc:  09 f0         breq  .+2        ; 0x1ec0 <main+0x31a>
4
    1ebe:  ba 9a         sbi  0x17, 2  ; 23

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Tim    schrieb:
> Hier noch einmal genau die gleiche Mist wie oben. In diesem Fall lässt
> sich AVR-GCC aber nicht einmal durch den Cast beeinflussen.

Hier ist idlelPolls ziemlich sicher ein 16-Bit Wert.

von Tim  . (cpldcpu)


Lesenswert?

Johann L. schrieb:
> Hier ist idlelPolls ziemlich sicher ein 16-Bit Wert.

Ist es. Mich wundert nur, dass trotz des casts nach uchar noch alle 16 
bit bearbeitet werden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Dann ziehen wir mal noch nen Wurm aus deiner Nase:  Vermultich ist die 
Variable auch volatile.

von Sven P. (Gast)


Lesenswert?

Johann L. schrieb:
> Dann ziehen wir mal noch nen Wurm aus deiner Nase:  Vermultich ist die
> Variable auch volatile.

Und im Umkehrschluss bedeutet das, dass bei flüchtigen (volatile) 
Variablen stets vollständige Lesezugriffe erzeugt werden?

von Tim  . (cpldcpu)


Lesenswert?

Nein, aber die Variable ist global.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sven P. schrieb:

> Und im Umkehrschluss bedeutet das, dass bei flüchtigen (volatile)
> Variablen stets vollständige Lesezugriffe erzeugt werden?

Ja, weil die Nebeneffekte nicht bekannt sind, so kann Lesen eines SFRs 
ein anderes SFR wie ein Flag-Register ändern.

von Sven P. (Gast)


Lesenswert?

Johann L. schrieb:
> Sven P. schrieb:
>
>> Und im Umkehrschluss bedeutet das, dass bei flüchtigen (volatile)
>> Variablen stets vollständige Lesezugriffe erzeugt werden?
>
> Ja, weil die Nebeneffekte nicht bekannt sind, so kann Lesen eines SFRs
> ein anderes SFR wie ein Flag-Register ändern.
Ok.

Da drängt sich mir dann schon länglich eine Frage auf: Wie sind denn 
16-Bit-Zugriffe im avr-Target implementiert? Da gibt es ja bei den AVRs 
ein paar Sonderlinge. Etwa die 16-Bit-Timer, die mit dem temporären 
Register für die obere Hälfte arbeiten. Oder das Ergebnisregister vom 
ADC.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Das hängt vom Controller ab. Auf Xmega wird Low zuerst geschrieben, auf 
anderen AVRs High. Beim Lesen wird immer Low zuerst geladen.

IIRC gibt das für manch AVRs Probleme, die die Reihenfolge bei manchen 
SFRs anders brauchen.  Die Information ist aber im Compiler nicht 
vorhanden, z.b. bei indirektem Zugriff.

von Tim  . (cpldcpu)


Lesenswert?

Ich habe in V-USB noch ein interessantes Beispiel gefunden. In osccal.c 
gibt es eine Funktion die den RC-Osccilator kallibriert:


1
uchar       step = 128;
2
uchar       trialValue = 0, optimumValue;
3
int         x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);
4
5
    /* do a binary search: */
6
    do{
7
        OSCCAL = trialValue + step;
8
        x = usbMeasureFrameLength();    /* proportional to current real frequency */
9
        if(x < targetValue)             /* frequency still too low */
10
            trialValue += step;
11
        step >>= 1;
12
    }while(step > 0);

Hier der erzeugte Code:
1
    1f88:  c8 e0         ldi  r28, 0x08  ; 8
2
    1f8a:  d0 e0         ldi  r29, 0x00  ; 0
3
uchar       step = 128;
4
uchar       trialValue = 0, optimumValue;
5
    1f8c:  10 e0         ldi  r17, 0x00  ; 0
6
 * a USB RESET. We first do a binary search for the OSCCAL value and then
7
 * optimize this value with a neighboorhod search.
8
 */
9
void    calibrateOscillator(void)
10
{
11
uchar       step = 128;
12
    1f8e:  00 e8         ldi  r16, 0x80  ; 128
13
uchar       trialValue = 0, optimumValue;
14
int         x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);
15
16
    /* do a binary search: */
17
    do{
18
        OSCCAL = trialValue + step;
19
    1f90:  f1 2e         mov  r15, r17
20
    1f92:  f0 0e         add  r15, r16
21
    1f94:  f1 be         out  0x31, r15  ; 49
22
        x = usbMeasureFrameLength();    /* proportional to current real frequency */
23
    1f96:  90 dc         rcall  .-1760     ; 0x18b8 <usbMeasureFrameLength>
24
        if(x < targetValue)             /* frequency still too low */
25
    1f98:  84 33         cpi  r24, 0x34  ; 52
26
    1f9a:  29 e0         ldi  r18, 0x09  ; 9
27
    1f9c:  92 07         cpc  r25, r18
28
    1f9e:  0c f4         brge  .+2        ; 0x1fa2 <calibrateOscillator+0x24>
29
            trialValue += step;
30
    1fa0:  1f 2d         mov  r17, r15
31
        step >>= 1;
32
    1fa2:  06 95         lsr  r16
33
    1fa4:  21 97         sbiw  r28, 0x01  ; 1
34
    }while(step > 0);
35
    1fa6:  a1 f7         brne  .-24       ; 0x1f90 <calibrateOscillator+0x12>

Interessanterweise erzeugt AVR-GCC einen eigenen Schleifenzähler in 
r28/r29. Warum das? Zusätzlich ist er unnötigerweise 16bit breit, was 
wohl an der angenommenen nativen Wortbreite des Prozessors liegt.

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


Lesenswert?

Hier gibts jede Menge Optionen, die du mit -fno-xxx abschalten kannst, 
um festzustellen, was genau dafür zuständig war:
http://gcc.gnu.org/onlinedocs/gcc-4.7.3/gcc/Optimize-Options.html#Optimize-Options

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Tim    schrieb:

[Märchencode], der so nicht compilierbar ist, denn er bringt zig Fehler. 
Von daher sind keine Aussagen dazu möglich.

von Tim  . (cpldcpu)


Lesenswert?

>[Märchencode]

Der Originalcode ist hier:

https://github.com/obdev/v-usb/blob/master/libs-device/osccal.c

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Tim    schrieb:
>>[Märchencode]
>
> Der Originalcode ist hier:
>
> https://github.com/obdev/v-usb/blob/master/libs-device/osccal.c
1
osccal.c: In function 'calibrateOscillator':
2
osccal.c:20:60: error: 'F_CPU' undeclared (first use in this function)
3
 int         x, optimumDev, targetValue = (unsigned)(1499 * (double)F_CPU / 10.5e6 + 0.5);
4
                                                            ^
5
osccal.c:20:60: note: each undeclared identifier is reported only once for each function it appears in
6
osccal.c:25:9: warning: implicit declaration of function 'usbMeasureFrameLength' [-Wimplicit-function-declaration]
7
         x = usbMeasureFrameLength();    /* proportional to current real frequency */

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> osccal.c:20:60: error: 'F_CPU' undeclared (first use in this function)

Naja, VUSB lässt sich für einige verschiedene CPU-Frequenzen
compilieren.  Nimm einfach mal ein -DF_CPU=12000000ul mit in die
Kommandozeile, dann compiliert es zumindest.  (Die Warnung über die
nicht deklarierte Funktion stört ja nicht so sehr, es wird halt ein
RCALL eingebaut, aber das wäre ohnehin der Fall gewesen, auch mit
einer passenden Deklaration.)

Ich bekomme (GCC 4.7.1, -mmcu=atmega8, -Os) das hier für die erste
Schleife:
1
        ldi r24,lo8(8)
2
        mov r14,r24
3
        mov r15,__zero_reg__
4
        ldi r28,0
5
        ldi r29,lo8(-128)
6
.L3:
7
        mov r13,r28
8
        add r13,r29
9
        out 0x31,r13
10
        rcall usbMeasureFrameLength
11
        cpi r24,-79
12
        ldi r18,6
13
        cpc r25,r18
14
        brge .L2
15
        mov r28,r13
16
.L2:
17
        lsr r29
18
        ldi r18,1
19
        sub r14,r18
20
        sbc r15,__zero_reg__
21
        brne .L3
22
...

Ist vermutlich für ein anderes F_CPU als der Code da oben (einmal wird
mit 6 verglichen, einmal mit 9).  Der Schleifenzähler befindet sich
wohl hier in r14/r15, und der sieht in der Tat etwas merkwürdig aus.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Heißer Kandidat in solchen Fällen if -fno-tree-loop-optimize, was auch 
hier zu etwas besserem Code führt.

AVR-GCC-Codeoptimierung: Feinabstimmung der Optimizer

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.