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
ucharisLast=((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:
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.
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.
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?
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.
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. :)
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.
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.
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?
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.
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.
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.
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.
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.