avr-gcc: mal lds, mal ld rd, Z

OP (Firma: Brainit GmbH) #3305636
Lesenswert?

Hallo zusammen,

da ich immer mal einen Blick auf den vom GCC erzeugten Assembler-Code 
werfe (man lernt ja nie aus), ist mir heute folgendes aufgefallen:

simpelste Instruktionen zur UART-Steuerung werden verschieden codiert:
1
UCSR0B &= ~_BV(UDRIE0);
wird zu
1
lds r24,193   ;  D.1563, MEM[(volatile uint8_t *)193B]
2
andi r24,lo8(-33)   ;  D.1563,
3
sts 193,r24   ;  MEM[(volatile uint8_t *)193B], D.1563

während
1
UCSR0B |= _BV(TXEN0);
so übersetzt wird:
1
ldi r30,lo8(-63)   ;  tmp47,
2
ldi r31,0   ; 
3
ld r24,Z   ;  D.1568, MEM[(volatile uint8_t *)193B]
4
ori r24,lo8(8)   ;  D.1568,
5
st Z,r24   ;  MEM[(volatile uint8_t *)193B], D.1568
(das Z-Register wird nachher nicht weiter verwendet, unmittelbar danach 
folgt ein ret)

ich würde gerne verstehen warum im zweiten Fall nicht auch mit lds/sts 
gearbeitet wird?

Danke, Michi
Persönliche Seite #3306148
Lesenswert?

Michael Reinelt schrieb:
> Johann L. schrieb:
>
>> Erste Hinweise geben dir die Dumps, welche mit -fdump-rtl-all
>> -fdump-tree-all erzeugt werden können.
>
> Ähhhh.... ich fürchte das sind eine Spur zu viele Hinweise :-) Der
> erzeugt mir jetzt ja gefühlte 2000 Dumps :-(

Schamlose Übertreibnug, da sind maximal 150-200 Dumps, mehr nicht.
>
> Hat jemand eine idee wo ungefähr ich da reinschauen sollte?

Schau dir an, wo die Ersetzung im erwarteten Fall (nicht) stattfindet 
und vergleiche das mit dem unliebsamen Fall.

Wenn der Pass gefunden ist, in dem die Ersetzung (nicht) stattfindet, 
dann

1) Ist das der Pass, in dem die Ersetzung wirklich stattfinden soll? 
IIRC wird teilweise in insn combine ersetzt, was aber mir aber nicht 
zielführend erscheint.

2) Wenn der Pass gefunden ist, gehst du in die Quellen und suchst dort 
den Übeltäter und erstellst einen Fix / ein Patch nebst Testfall :-)
OP (Firma: Brainit GmbH) #3306293
Lesenswert?

Johann L. schrieb:
> Michael Reinelt schrieb:
>> Johann L. schrieb:
>>
>>> Erste Hinweise geben dir die Dumps, welche mit -fdump-rtl-all
>>> -fdump-tree-all erzeugt werden können.
>>
>> Ähhhh.... ich fürchte das sind eine Spur zu viele Hinweise :-) Der
>> erzeugt mir jetzt ja gefühlte 2000 Dumps :-(
>
> Schamlose Übertreibnug, da sind maximal 150-200 Dumps, mehr nicht.

ich sagte ja "gefühlt" :-)

>> Hat jemand eine idee wo ungefähr ich da reinschauen sollte?
>
> Schau dir an, wo die Ersetzung im erwarteten Fall (nicht) stattfindet
> und vergleiche das mit dem unliebsamen Fall.
>
> Wenn der Pass gefunden ist, in dem die Ersetzung (nicht) stattfindet,
> dann
>
> 1) Ist das der Pass, in dem die Ersetzung wirklich stattfinden soll?
> IIRC wird teilweise in insn combine ersetzt, was aber mir aber nicht
> zielführend erscheint.
>
> 2) Wenn der Pass gefunden ist, gehst du in die Quellen und suchst dort
> den Übeltäter und erstellst einen Fix / ein Patch nebst Testfall :-)

na du machst mir Spaß :-)

Aber da ich an chronischer Neugierde im Endstadium leide, werd ich mir 
das antun... aber erst morgen.

Ich kann zwar RTL genau überhaupt nicht, aber ich vermute das steht für 
"Register tranfer language", was so ein Zwischenformat des GCC ist.

Es gibt auch so einen Modus "-dP" oder so, wo er in den Assembler-Code 
die RTL-Statements als Kommentare reinzieht. Da hab ich heut mal kurz 
drübergeschaut (hab leider das Ergebnis grad nicht bei der Hand), aber 
die RTL-Statements sahen für mich zumindest ähnlich aus. Daher meine 
Frage: kann es sein dass der Unterschied gar nicht in der RTL 
aufscheint, sondern erst später, wo RTL in den tatsächlichen Code 
übersetzt wird? (RTL ist ja ein Stück weit "prozessor-neutral"?)

Was ich auch noch prüfen möchte: Der Code mit Z-Pointer sieht zwar 
komplexer aus, aber diese Operationen laufen ja schneller als ein "lds". 
Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie 
Hose, welcher Code erzeugt wird?

Vielleicht hängt es auch damit zusammen, dass eine Variante eine 
"normale" Funktion ist, während die andere aus einer ISR kommt?

Ich muss morgen auf jeden Fall einen echten, vereinfachten Testcase 
erstellen....

anyway: vielen Dank für deine Unterstützung!
Persönliche Seite #3306372
Lesenswert?

Michael Reinelt schrieb:
> Johann L. schrieb:
>> Michael Reinelt schrieb:
>>> Johann L. schrieb:
>>> Hat jemand eine idee wo ungefähr ich da reinschauen sollte?
>>
>> Schau dir an, wo die Ersetzung im erwarteten Fall (nicht) stattfindet
>> und vergleiche das mit dem unliebsamen Fall.
>>
>> Wenn der Pass gefunden ist, in dem die Ersetzung (nicht) stattfindet,
>> dann
>>
>> 1) Ist das der Pass, in dem die Ersetzung wirklich stattfinden soll?
>> IIRC wird teilweise in insn combine ersetzt, was aber mir aber nicht
>> zielführend erscheint.
>>
>> 2) Wenn der Pass gefunden ist, gehst du in die Quellen und suchst dort
>> den Übeltäter und erstellst einen Fix / ein Patch nebst Testfall :-)
>
> na du machst mir Spaß :-)

Naja, weg Fragt muß auch mit einer Antwort rechnen :-P

> Ich kann zwar RTL genau überhaupt nicht, aber ich vermute das steht für
> "Register tranfer language", was so ein Zwischenformat des GCC ist.

Jepp

> Es gibt auch so einen Modus "-dP" oder so, wo er in den Assembler-Code
> die RTL-Statements als Kommentare reinzieht. Da hab ich heut mal kurz
> drübergeschaut (hab leider das Ergebnis grad nicht bei der Hand), aber
> die RTL-Statements sahen für mich zumindest ähnlich aus.

Einmal müsste es eine indirekte Adressierung sein wie
1
(set (reg:QI Rn)
2
     (mem:QI (reg:HI r30)))
was ein "LD Rn, Z" bedeutet. Im Fall einer direkten Adressierung mit zur 
Compilezeit bekannter Adresse sowas wie
1
(set (reg:QI Rn)
2
     (mem:QI (const_int XYZ)))
Was ein "LDS Rn, XYZ" bedeutet.

Für die Maschinenmodi siehe z.B.

http://gcc.gnu.org/onlinedocs/gccint/Machine-Modes.html#Machine-Modes
http://gcc.gnu.org/wiki/avr-gcc#Exceptions_to_the_Calling_Convention

> Daher meine
> Frage: kann es sein dass der Unterschied gar nicht in der RTL
> aufscheint, sondern erst später, wo RTL in den tatsächlichen Code
> übersetzt wird? (RTL ist ja ein Stück weit "prozessor-neutral"?)

Nein. Idee und Ziel von RTL ist, eine arithmetische / algebraische 
Beschreibung einer Assembler-Instruktion darzustellen.  Im Laufe der 
RTL-Passes werden die insns immer weiter in Richtung "echter" 
Instruktionen umgewandelt und im lezten Pass lediglich in Text (s-File) 
umgewandelt. Das -dP Dump enthält die letzten insns als Kommentar und 
nachfolgend die entsprechende(n) Assembler-Instruktione(n).

Die Syntax und Semantik von RTL ist Target-übergreifend. Target-abhängig 
ist hingegen, welche RTL-Konstrukte erlaubt sind. Was in (mem:QI 
(const_int 0x1234)) bedeutet ist z.B. Target-unabhängig, allerdings ist 
so ein Speicherzugriff nur dann gültig, wenn das Target die 
entsprechende Adressierungsart auch unterstützt.

> Was ich auch noch prüfen möchte: Der Code mit Z-Pointer sieht zwar
> komplexer aus, aber diese Operationen laufen ja schneller als ein "lds".
> Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie
> Hose, welcher Code erzeugt wird?

Siehe unten.

> Vielleicht hängt es auch damit zusammen, dass eine Variante eine
> "normale" Funktion ist, während die andere aus einer ISR kommt?

Nö, das spielt keine Rolle. Eher von bedeutung ist volatile.


A. K. schrieb:
> Michael Reinelt schrieb:
>> Vielleicht ist es sowohl von code-size als auch von cycles Jacke wie
>> Hose, welcher Code erzeugt wird?
>
> Und genau so ist es.

Ich habe aber auch schon Fälle gesehen, wo es ungünstiger ist, und von 
der Registerlast her ist es auf jeden Fall schlecht.  Dass es hier nicht 
mieser ist als direkte Adressierung ist eher Zufall aber kein Design.
OP (Firma: Brainit GmbH) #3306693
Lesenswert?

So, ich hab mal einen testcase vorbereitet:
1
#include <stdint.h>
2
#include <avr/io.h>
3

4
volatile uint8_t flag;
5

6
void test_a(void)
7
{
8
    UCSR0B |= _BV(TXEN0);
9
}
10

11

12
void test_b(void)
13
{
14
    if (flag) {
15
  UCSR0B |= _BV(TXEN0);
16
    }
17
}

das "if(flag)" ist notwendig, sonst kompiliert er beide Funktionen 
gleich)

Ergebnis:
1
  .text
2
.global  test_a
3
  .type  test_a, @function
4
test_a:
5
/* prologue: function */
6
/* frame size = 0 */
7
/* stack size = 0 */
8
.L__stack_usage = 0
9
  ldi r30,lo8(-63)   ;  tmp44,
10
  ldi r31,0   ; 
11
  ld r24,Z   ;  D.1400, MEM[(volatile uint8_t *)193B]
12
  ori r24,lo8(8)   ;  D.1400,
13
  st Z,r24   ;  MEM[(volatile uint8_t *)193B], D.1400
14
  ret
15
  .size  test_a, .-test_a
16
.global  test_b
17
  .type  test_b, @function
18
test_b:
19
/* prologue: function */
20
/* frame size = 0 */
21
/* stack size = 0 */
22
.L__stack_usage = 0
23
  lds r24,flag   ;  flag.0, flag
24
  tst r24   ;  flag.0
25
  breq .L2   ; ,
26
  lds r24,193   ;  D.1397, MEM[(volatile uint8_t *)193B]
27
  ori r24,lo8(8)   ;  D.1397,
28
  sts 193,r24   ;  MEM[(volatile uint8_t *)193B], D.1397
29
.L2:
30
  ret
31
  .size  test_b, .-test_b
32
  .comm  flag,1,1
33
  .ident  "GCC: (GNU) 4.7.2"
34
.global __do_clear_bss

und jetzt zum RTL: die relevanten Stellen habe ich aus -dP rauskopiert:
1
 ; (insn 6 5 7 (set (reg:QI 24 r24 [orig:43 D.1400 ] [43])
2
 ;         (mem/v:QI (reg/f:HI 30 r30 [44]) [0 MEM[(volatile uint8_t *)193B]+0 S1 A8])) uart.c:18 28 {movqi_insn}
3
 ;      (nil))
4
  ld r24,Z   ;  D.1400, MEM[(volatile uint8_t *)193B]   ;  6  movqi_insn/4  [length = 1]
und
1
 ; (insn 10 8 11 (set (reg:QI 24 r24 [orig:44 D.1397 ] [44])
2
 ;         (mem/v:QI (const_int 193 [0xc1]) [0 MEM[(volatile uint8_t *)193B]+0 S1 A8])) uart.c:25 28 {movqi_insn}
3
 ;      (nil))
4
  lds r24,193   ;  D.1397, MEM[(volatile uint8_t *)193B]   ;  10  movqi_insn/4  [length = 2]

Und hier gibts tatsächlich einen unterschied:
einmal ist es
set (reg:QI 24 r24) (mem/v:QI (reg/f:HI 30 r30 [44]))
und das zweitmal
(set (reg:QI 24 r24) (mem/v:QI (const_int 193 [0xc1]))

jetzt weiss ich aber nicht mehr weiter :-(

Johann, magst du mir nochmal helfen bitte?
OP (Firma: Brainit GmbH) #3307226
Lesenswert?

A. K. schrieb:
> LDS/STS 2 Takte 2 Worte, der Rest 1 Takt 1 Wort. Zähl selbst.

Ich hab nachgezählt:

LDS 2W 2Cycle
ORI 1W 1C
STS 2W 2C
---------
    5W 5C

LDI r30 1W 1C
LDI r31 1W 1C
LD,Z    1W 1C
ORI     1W 1C
ST,Z    1W 2C
-------------
        5W 6C

der "komplizierte" Code ist zwar gleich lang (5 Wörter) braucht aber 
einen takt länger. oder hab ich mich verzählt?
OP (Firma: Brainit GmbH) #3307246
Lesenswert?

ich hab mir tatsächlich das RTL angetan :-) und zumindest die Stelle 
gefunden wo der Unterschied auftritt:

nach pass 161 (forward propagation) sieht der Code noch gleich aus, 
interessanterweise beide male noch mit "indirekter" Adressierung:
1
(insn 5 2 6 2 (set (reg/f:HI 44)
2
        (const_int 193 [0xc1])) test1.c:18 32 {*movhi}
3
     (nil))
4

5
(insn 6 5 7 2 (set (reg:QI 42 [ D.1390 ])
6
        (mem/v:QI (reg/f:HI 44) [0 MEM[(volatile uint8_t *)193B]+0 S1 A8])) test1.c:18 28 {movqi_insn}
7
     (nil))
8

9
(insn 7 6 9 2 (set (reg:QI 43 [ D.1391 ])
10
        (ior:QI (reg:QI 42 [ D.1390 ])
11
            (const_int 8 [0x8]))) test1.c:18 178 {iorqi3}
12
     (expr_list:REG_DEAD (reg:QI 42 [ D.1390 ])
13
        (nil)))
14

15
(insn 9 7 0 2 (set (mem/v:QI (reg/f:HI 44) [0 MEM[(volatile uint8_t *)193B]+0 S1 A8])
16
        (reg:QI 43 [ D.1391 ])) test1.c:18 28 {movqi_insn}
17
     (expr_list:REG_DEAD (reg/f:HI 44)
18
        (expr_list:REG_DEAD (reg:QI 43 [ D.1391 ])
19
            (nil))))


nach pass 162 (constant propagation) wird die Variante mit dem "if" 
plötzlich zu
1
(insn 10 8 11 4 (set (reg:QI 43 [ D.1393 ])
2
        (mem/v:QI (const_int 193 [0xc1]) [0 MEM[(volatile uint8_t *)193B]+0 S1 A8])) test2.c:19 28 {movqi_insn}
3
     (nil))
4

5
(insn 11 10 13 4 (set (reg:QI 44 [ D.1394 ])
6
        (ior:QI (reg:QI 43 [ D.1393 ])
7
            (const_int 8 [0x8]))) test2.c:19 178 {iorqi3}
8
     (expr_list:REG_DEAD (reg:QI 43 [ D.1393 ])
9
        (nil)))
10

11
(insn 13 11 16 4 (set (mem/v:QI (const_int 193 [0xc1]) [0 MEM[(volatile uint8_t *)193B]+0 S1 A8])
12
        (reg:QI 44 [ D.1394 ])) test2.c:19 28 {movqi_insn}
13
     (expr_list:REG_DEAD (reg/f:HI 45)
14
        (expr_list:REG_DEAD (reg:QI 44 [ D.1394 ])
15
            (nil))))
was ja eigentlich "besser" und "hübscher" ist.

ich verstehe zwar nicht was das mit "constant propagation" zu tun hat, 
aber noch viel weniger verstehe ich warum diese Änderung/Verbesserung im 
"einfacheren" Fall ohne if() nicht durchgeführt wird...
OP (Firma: Brainit GmbH) #3307318
Lesenswert?

Yalu X. schrieb:
> Michael Reinelt schrieb:
>> Wenn ich das Datenblatt richtig lese, braucht die "einfache" Variante
>> ohne Inkrement/Dekrement des Z-Pointers nur einen Takt.
>
> Von welchem COntroller reden wir?
>
> Beim ATmega48 sind es 2 Zyklen, bei den neuen, halbierten AVRs
> (ATtiny4/5/9/10) kann es evtl. anders aussehen.

ATmega328P

Wo schaust du nach? ich hier: http://www.atmel.com/Images/doc0856.pdf 
Seite 93

Wobei beim XMEGA dabeisteht dass es bei Zugriff auf internes SRAM ein 
zyklus mehr ist, aber der 328 ist doch kein XMEGA, oder?

Und dann gibts da noch so Fußnoten...

Aber ich lese das so, dass das 1 zyklus braucht.
Moderator Persönliche Seite #3307373
Lesenswert?

m.n. schrieb:
> Schon lustig. In dem Datenblatt von 9/2001 stehen da konstant 2 Zyklen.

Steht auch in älteren Versionen der instruction set summary.  Das
mit dem einen Zyklus trifft wirklich nur auf die tiny-Architektur
(ATtiny10 & Co.) zu bzw. auf die IO-Register vom Xmega.  Die Fußnoten
klingen auch verdammt nach „tiny“.  (Nur dort ist der Flash in den
normalen Adressbereich gemapt.)

Da hat man wohl den Abschnitt zuerst für den Xmega und dann für den
TinyTiny aufgebohrt.
Persönliche Seite #3307563
Lesenswert?

Michael Reinelt schrieb:
> nach pass 161 (forward propagation) sieht der Code noch gleich aus,
> interessanterweise beide male noch mit "indirekter" Adressierung:
> [...]
>
> nach pass 162 (constant propagation) wird die Variante mit dem "if"
> plötzlich zu
>
1
> (insn 10 8 11 4 (set (reg:QI 43 [ D.1393 ])
2
>         (mem/v:QI (const_int 193 [0xc1]) [0 MEM[(volatile uint8_t 
3
> *)193B]+0 S1 A8])) test2.c:19 28 {movqi_insn}
4
>      (nil))
5
> ...
6
>
> was ja eigentlich "besser" und "hübscher" ist.
>
> ich verstehe zwar nicht was das mit "constant propagation" zu tun hat,

Propagiert wird die (compiletime) konstante Adresse 193, die nach 
Pseudo-Register r44 geladen wird. Pseudo r44 wird später ins Z-Register 
r30 allokiert. (Je nach Testfall unterscheidet sich die Anzahl der 
benötigten Pseudos und damit auch deren Numerierung.)

> aber noch viel weniger verstehe ich warum diese Änderung/Verbesserung im
> "einfacheren" Fall ohne if() nicht durchgeführt wird...

Hier ist dann Debuggen des Compilers angesagt und nachvollziehen der 
Quelle.  Im einfachsten Falle sind es fehlende / fehlerhafte 
Kostenbeschreibungen im avr-Backend.

IIRC werden Speicherzugriffe in neueren GCC-Versionen zunächst indirekt 
umgesetzt und erst in einem späteren Pass wieder in direkte Zugriffe 
gewandelt falls dies Vorteilhaft erscheint. Dies ermöglicht z.B. bessere 
Optimierung von Adressen die in Schleifen verwendet werden.

Pass "cprop" ist in gcc/cprop.c implementiert: 
http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/cprop.c?content-type=text%2Fplain&view=co

Vielleicht liefert -fdump-rtl-cprop-details o.ä. noch detailiertere 
Infos. Kosten können im avr-gcc mit -mlog=rtx_costs ausgegeben werden.
Gast #3307708
Lesenswert?

Michael Reinelt schrieb:
> Was den komplizierteren Code also nochmal
> langsamer macht...

Freu Dich, dass Du den "komplizierten Code" nicht komplett in Assembler 
schreiben mußt.
Um einzelne Taktzyklen solltest Du Dich erst dann kümmern, wenn dieser 
Code >100000 Mal/s aufgerufen wird und Dein Controller schon mit 20MHz 
läuft.
Alles andere ist vergeudete Zeit.
OP (Firma: Brainit GmbH) #3307714
Lesenswert?

m.n. schrieb:
> Michael Reinelt schrieb:
>> Was den komplizierteren Code also nochmal
>> langsamer macht...
>
> Freu Dich, dass Du den "komplizierten Code" nicht komplett in Assembler
> schreiben mußt.
> Um einzelne Taktzyklen solltest Du Dich erst dann kümmern, wenn dieser
> Code >100000 Mal/s aufgerufen wird und Dein Controller schon mit 20MHz
> läuft.
> Alles andere ist vergeudete Zeit.

Gut dass nicht alle so denken. Open Source wie GCC lebt nun mal von 
engagierten Mitgliedern der Community, die das nicht als "vergeudete 
Zeit" ansehen, solche Eigenartigkeiten untersuchen und als Bug Report 
eintüten.
Gast #3307783
Lesenswert?

Michael Reinelt schrieb:
> und als Bug Report eintüten.

Gartuliere, da hast Du ja einen richtig schweren Fehler gefunden.

Ich habe mit gerade einen Codeschnipsel in meinem Compilat angesehen. In 
einer ISR erfolgt:

PUSH R1
EOR R1,R1
.
.
POP R1

Zwischendurch wird R1 garnicht gebraucht. Ich könnte jetzt die 
verschwendeten Taktzyklen zählen, um zu dokumentieren welch schweren Bug 
ich gefunden habe.
Und, mache ich das? NEIN.
#3307883
Lesenswert?

Ein Bug Report wäre da auch sinnlos. Der Compiler setzt ohne Kontrolle 
oder Ressourcenmanagement voraus, dass in R1 stets 0 drinsteht. Also 
muss der Anfang einer ISR diesen Zustand herstellen.

Und ja, eine verkehrt laufende Optimierung gehört wirklich nach 
Bugzilla. Dazu ist dieses Werkzeug ebenfalls da, auch wenns kein 
dramatischer Fall ist. Ob sich wer drum kümmern wird ist eine andere 
Sache, hohe Priorität hat das nicht.

Der Fall hat evtl. etwas bessere Chancen auf Beachtung, wenn man eine 
Vorversion findet, die es besser machte, und darauf explizit hinweist 
(regression).
Gast #3308186
Lesenswert?

A. K. schrieb:
> hohe Priorität hat das nicht.

Was man meiner Meinung nach mit hoher Priorität angehen könnte, wären 
reservierte Register, die man in eigenen Ass.-Routinen sehr effizient 
verwenden kann.
IAR bietet das für R4-R15, was ISRs sehr schnell macht, weil im besten 
Fall kein PUSH/POP notwendig ist.

Ich sagte "könnte", denn ich kann das nicht und fordere auch nicht, dass 
ein Anderer dies für mich macht.
Moderator Persönliche Seite #3308264
Lesenswert?

m.n. schrieb:
> Was man meiner Meinung nach mit hoher Priorität angehen könnte, wären
> reservierte Register, die man in eigenen Ass.-Routinen sehr effizient
> verwenden kann.

Gibt's doch:
1
uint8_t i __asm__("r2");

Musst du halt konsistent in deinem gesamten Projekt durchziehen, und
du musst dich bei den benutzten Bibliotheksroutinen vergewissern, dass
diese derartige Register nicht benutzen.

Es gibt aber keinen Grund, nun allen Nutzern diese Register zu
verbieten, nur weil hin und wieder mal einzelne sowas brauchen.
Daher halt auch die Forderung an denjenigen, der sowas macht, dass
er sich selbst um die Bibliothek kümmern möge, denn die
default-Bibliothek stellt sich das nicht zur Aufgabe.  (Man kann sich
aber natürlich auch eine eigene avr-libc compilieren, in der man
konsistent obiges Statement mit einbringt und auf diese Weise das
entsprechende Register im Compiler „reserviert“.
Gast #3322404
Lesenswert?

A. K. schrieb:
> Nur hat IAR ziemlich sicher keine 64-Bit Integers.

Doch, signed und unsigned. Und selbst, wenn man sich alle freien 
Register reserviert, geht noch 64-bit double.
Die Variablen werden wohl auf einem separaten CSTACK (Y) abgelegt, 
wodurch die Routinen auch noch unterbrechbar werden. Die LDD und STD 
Befehle sind dafür gut geeignet. Die Rücksprungadressen liegen auf dem 
RSTACK, den der AVR von Hause aus für CALL und RET bietet.
Ohne Frage hat das auch alles seinen Preis, weshalb ich Kritik am GCC 
wegen eines Taktzyklus etwas übertrieben finde.

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