Forum: PC-Programmierung Wert in AVR Register laden: optimalen Code generieren?


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ich hänge gerade an folgedem "Problem":

> Erzeuge den optimalen Code, um einen bekannten Wert in ein AVR
> Register zu laden, wobei 1..4 aufeinander folgende GPRs zu setzen sind.

Der Code-Generator erzeugt zwar Code für AVR, läuft selbst aber auf 
einem PC; daher die Frage in "PC-Programmierung".

Beispiel in C-Speek: u32 R10 = 0x1ff.

CLR  R13
CLR  R12
MOVW R10, R12
INC  R11
DEC  R10

Für den Fall, dass zum Beispiel u16 R24 bereits den Wert 0 enthält:

MOVW R12, R24
MOVW R10, R24
INC  R11
DEC  R10

Vor allem die mögliche Verwendung von Arithmetic wie im letzten Beispiel 
(INC, DEC, COM, NEG, ASR, LSR, LSL, SWAP, BLD) macht es komplizierter, 
ebenso wenn Teile des Ziel-Registers wiederverwendet werden können.

Falls ein 8-Bit Scratch-Reg verfügbar ist, lässt sich jede n-Byte 
Konstante in maximal 2n Instruktionen setzen: n * (LDI + MOV).  Falls 
kein Scratch-Register verfügbar ist, so braucht man 2 Instruktionen 
mehr, um temporär ein 8-Bit Scratch-Register in R16...R31 
freizuschaufeln.  Beispiel: u8 R10 = 42 und kein anderes GPR enthält 
bereits 42:

PUSH R31
LDI  R31, 42
MOV  R10, R31
POP  R31

Falls z.B. R31 = 2, dann geht es auch schneller und ohne PUSH/POP:

MOV R10, R31
SET
BLD R10, 3
BLD R10, 5

etc.

Sprache ist übrigens C++11.

: Bearbeitet durch User
von Sherlock 🕵🏽‍♂️ (Gast)


Lesenswert?

Das ist ein akademischer Hirnfurz, für nichts nützlich. Wie lautet deine 
Frage dazu?

Wer solche Aufgaben mag, dem empfehle ich die Programmiersprache 
Brainfuck.

von Björn W. (bwieck)


Lesenswert?

Welchen AVR meinst du denn?
In meiner kleinen Welt haben AVR Register 8-Bit

Dann wäre die Aufgabe so zu lösen:

LDI R16, $Wert
MOV R17, R16
MOV R18, R16
MOV R19, R16

von Wastl (hartundweichware)


Lesenswert?

Johann L. schrieb:
> daher die Frage in "PC-Programmierung".

Käse!

Es handelt sich um AVR Assembler Code, daher gehört der Thread
in den Bereich >Mikrocontroller und Digitale Elektronik< oder
in >Compiler & IDEs<.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Björn W. schrieb:
> Welchen AVR meinst du denn?

Egal, bzw. die Eigenschaften des AVR (hat MOVW oder nicht) sind als Pa
Parameter des Algorithmus' anzusehen.

> LDI R16, $Wert
> MOV R17, R16
> MOV R18, R16
> MOV R19, R16

Auch falls das unterste zu setzende Reg >= R16 ist, gibt es Abkürzungen.

Beispiel: Wert = 0 mod 65537 und MOVW verfügbar:

LDI  R16, lo8(Wert)
LDI  R17, hi8(Wert)
MOVW R18, R16

Oder: u32 R24 = -1 und falls u32 R24 bereits 0 enthält:

SBIW R24, 1
SBIW R26, 1

oder

SBIW R24, 1
MOVW R26, R24

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wastl schrieb:
> Johann L. schrieb:
>> daher die Frage in "PC-Programmierung".
> Es handelt sich um AVR Assembler Code,

Nein, er geht mir nicht um einen bestimmten Assembler Code, sondern für 
einen Generator dafür.  Und der Generator steht weder in Assembler, noch 
läuft er auf einem AVR.  Aber wenn es sich glücklich macht, dann schieb 
den Beitrag eben dort hin.

: Bearbeitet durch User
von Wastl (hartundweichware)


Lesenswert?

Johann L. schrieb:
> Und der Generator steht weder in Assembler, noch
> läuft er auf einem AVR.

Ach so, kein AVR, und deswegen nennst du dein Vorhaben
"Wert in AVR Register laden: optimaler Code?"
Ja, schon klar ....

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hab den Titel angepasst.

von Oliver S. (oliverso)


Lesenswert?

Optimal bzgl. Größe, Geschwindigkeit, … ?

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Optimal bzgl. Größe, Geschwindigkeit, … ?

Größe.  Und falls es mehrere Möglichkeiten gibt, die Größe zu 
minimieren, dann die schnellste Lösung.  Wobei es hier immer so ist, 
dass kürzere Lösungen nie langsamer als lämgere Lösunge sind.  Eine 
kürzeste Lösung ist also auch dicht am Speed-Optimum.

Außerdem kann davon ausgegangen werden, dass mindestens ein Register < 
R16 beteiligt ist (weil wenn alle Teil-Regs >= R16 sind, dann ist die 
Lösung nicht allzu schwer).

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


Lesenswert?

...und natürlich erwarte ich hier nicht, dass mir jemand den Code 
schreibt; es geht einfach um Anregungen / Algorithmen.  Dabei ist dann 
die Laufzeit des Codes selbst auch nicht komplett egal; eine Brute-Force 
Suche ist zum Beispiel nicht praktikabel. Also

Generierter Code (auf AVR): Optimiert auf Größe.

Code (auf PC): Hier sind die Nebenbedingungen nicht so einfach 
darzustellen.  Eine Brute-Force Suche scheidet wie gesagt wegen der 
Performance aus.  Der aktuelle Ansatz ist, a priori die beste Sequenz zu 
erzeugen, was aber wegen der vielen Möglichkeiten inpraktikabel / 
schlecht wartbar / noch unvollständig ist.

von Björn W. (bwieck)


Lesenswert?

Spiel am besten mit dir selber.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Johann L. schrieb:

> Code (auf PC): Hier sind die Nebenbedingungen nicht so einfach
> darzustellen.  Eine Brute-Force Suche scheidet wie gesagt wegen der
> Performance aus.

Das glaube ich nicht. Der Suchraum kann ja bereits vorab ganz erheblich 
ausgedünnt werden. Was dann noch bleibt, sollte sich auf einem 
neuzeitlichen PC tatsächlich noch mittels BruteForce erschlagen lassen.

Und: Um wirklich garantiert die bestmögliche Lösung zu finden, ist es 
sogar der einzige Weg.

Alles andere kann die bestmögliche Lösung nur mit einer gewissen 
Wahrscheinlichkeit erreichen.

Allerdings: Hast du dir auch mal darüber Gedanken gemacht, dass es recht 
häufig vorkommt, dass SFIOR in einer bestimmten Reihenfolge beschrieben 
werden müssen? Oder in einem bestimmten zeitlichen Minimalabstand? Oder 
sogar auf irgendeine Antwort der Hardware gewartet werden muss, bevor 
der nächste Schreibvorgang getan werden kann?

Wenn du all diese Komplexität mit in deinem Generator unterbringen 
willst, dann wird es allerdings tatsächlich kompliziert und ist mittels 
BruteForce wohl auch auf einem PC nicht mehr sinnvoll lösbar.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Eine Brute-Force Suche scheidet wie gesagt wegen der
> Performance aus.

Vor Urzeiten gab es den GNU Super Optimizer, der genau das versuchte.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Lässt sich das nicht auf einen kürzeste-Wege-Algorithmus zurückführen? 
Jeder Knoten ist ein Zustand der Register, beim Startknoten sind alle 
Register "unbekannt", die Kanten sind Instruktionen. Kantengewichte sind 
Größe der Instruktion im Programmspeicher. Im Zielknoten haben die 
Register den gewünschten Inhalt.

Vielleicht A* Algorithmus, mit dem Abstand der Registerinhalt zum 
gewünschten Zustand als Distanzheuristik.

von (prx) A. K. (prx)


Lesenswert?

Eine bizarre Methode, ein Doppelregister zu Nullen, ist mir im GCC der 
16-Bit PICs begegnet: das Basisregister mit 0 multiplizieren. Da kommt 
man selten von alleine drauf, weil man das üblicherweise mit "langsam" 
verbindet, was da indes nicht zutrifft.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

(prx) A. K. schrieb:
> Da kommt
> man selten von alleine drauf, weil man das üblicherweise mit "langsam"
> verbindet, was da indes nicht zutrifft.

Dafür ist der Energieverbrauch bestimmt hoch 😉 Das wär auch ne 
Optimierungsmetrik!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Inzwischen habe ich das Problem gelöst.

Tiefensuche mit Tree Pruning.  Den Suchbaum zu stutzen und Bedingungen 
dafür anzugeben ist wesentlich einfacher, als kürzeste Sequenzen direkt 
zu erzeugen.  Die Lösung entspricht i.w. dem was Ob S. beschreibt.

Zum Beispiel reduzieren die meisten Instrukionen einer Lösung die 
Hamming-Distanz, es gibt aber auch Fälle mit MOVW wie bei den obigen 
Beispielen, welche die Hamming-Distanz nicht verändern.  Zum Glück gibt 
es keine Fälle, wo die Hamming-Distanz temporär zunimmt (zumindest sind 
mir keine eingefallen).

Ob S. schrieb:
> Der Suchraum kann ja bereits vorab ganz erheblich ausgedünnt
> werden. Was dann noch bleibt, sollte sich auf einem neuzeitlichen
> PC tatsächlich noch mittels BruteForce erschlagen lassen.

Im Kontext muss die Laufzeit effektiv O(1) sein, d.h. ab einer 
willkürlich festgelegten Grenze muss das Verfahren abrebrochen werden. 
In dem Fall gibt es evtl. nur eine suboptimale Lösung -- falls 
überhaupt.  Wo genau diese Grenze liegt, kann ich erst festlegen, wenn 
alles fertig ist und ich profilen kann.

> Hast du dir auch mal darüber Gedanken gemacht, dass es recht häufig
> vorkommt, dass SFIOR in einer bestimmten Reihenfolge beschrieben
> werden müssen?

Es geht um GPRs, nicht um SFRs.

> Wenn du all diese Komplexität mit in deinem Generator unterbringen
> willst, dann wird es allerdings tatsächlich kompliziert und ist mittels
> BruteForce wohl auch auf einem PC nicht mehr sinnvoll lösbar.

Inzwischen habe ich genügend Constraints zusammen, um den Suchbaum 
hinreichend klein zu halten, von wenigen Ausreißern abgesehen.

Was noch fehlt ist die eigentliche Code-Generierung (Erzeugung der 
AVR-Instruktionen), aber das ist nur Schreibarbeit.

Niklas G. schrieb:
> Lässt sich das nicht auf einen kürzeste-Wege-Algorithmus zurückführen?

Habe ich auch kurz drüber nachgedacht.  Aber das Problem ist ja nicht, 
eine kürzeste Sequenz in einer vorgegebenen Menge zu finden, sondern 
überhaupt erst mal Kandidaten zu erzeugen.  Zudem weiß man a priori 
nicht, wie lange eine kürzeste Sequenz sein wird.  Es gibt lediglich 
eine weiche Obergrenze, die im Laufe des Algorithmus' (hoffentlich) 
immer kleiner wird.

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


Lesenswert?

...inzwischen hab ich auch ein Beispiel, bei dem die Hamming-Distanz 
temporär zunimmt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Der Algorithmus ist fertig und läuft.  Hier ein paar einfache Beispiele 
mit generiertem Code vorher / nachher (ATmega8):
1
long long fn_zero (void)
2
{
3
    return 0;
4
}
1
;; vorher
2
fn_zero:
3
  ldi r18,0
4
  ldi r19,0
5
  ldi r20,0
6
  ldi r21,0
7
  ldi r22,0
8
  ldi r23,0
9
  ldi r24,0
10
  ldi r25,0
11
  ret
1
;; nachher
2
fn_zero:
3
  ldi r18,0
4
  ldi r19,0
5
  movw r20,r18
6
  movw r22,r18
7
  movw r24,r18
8
  ret
1
unsigned fn_crc (unsigned y, unsigned x)
2
{
3
    for (char i = 16; i--; x >>= 1)
4
        y ^= (y ^ x) & 0x80 ? 79 : 0;
5
    return y;
6
}
1
;; vorher
2
fn_crc:
3
  movw r18,r22
4
  ldi r22,lo8(16)
5
.L4:
6
  movw r30,r24
7
  eor r30,r18
8
  eor r31,r19
9
  mov r20,r30
10
  andi r20,1<<7
11
  clr r21
12
  sbrs r30,7
13
  rjmp .L3
14
  ldi r20,lo8(79)
15
  ldi r21,0
16
.L3:
17
  eor r24,r20
18
  eor r25,r21
19
  lsr r19
20
  ror r18
21
  subi r22,lo8(1)
22
  brne .L4
23
  ret
1
;; nachher
2
fn_crc:
3
  movw r18,r22
4
  ldi r22,lo8(16)
5
.L4:
6
  movw r30,r24
7
  eor r30,r18
8
  eor r31,r19
9
  mov r20,r30
10
  andi r20,1<<7
11
  sbrc r30,7
12
  ldi r20,lo8(79)
13
  eor r24,r20
14
  lsr r19
15
  ror r18
16
  subi r22,lo8(1)
17
  brne .L4
18
  ret
1
int fn_eq0 (char c)
2
{
3
    return c == 0;
4
}
1
;; vorher
2
fn_eq0:
3
  mov r18,r24
4
  ldi r24,lo8(1)
5
  ldi r25,0
6
  cp r18, __zero_reg__
7
  breq .L10
8
  ldi r24,0
9
  ldi r25,0
10
.L10:
11
  ret
1
;; nachher
2
fn_eq0:
3
  mov r18,r24
4
  ldi r24,lo8(1)
5
  ldi r25,0
6
  cpse r18,__zero_reg__
7
  ldi r24,0
8
  ret

von Sherlock 🕵🏽‍♂️ (Gast)


Lesenswert?

Johann L. schrieb:
> Der Algorithmus ist fertig und läuft.

War das einfach nur eine sportliche Herausforderung, oder hast du damit 
etwas konkret vor?

Die hier aktiven Assembler-Programmierer erwecken den Eindruck, dass sie 
nur ihrem eigenen hand-gedengelten Code trauen.

von Falk B. (falk)


Lesenswert?

Johann L. schrieb:
> Der Algorithmus ist fertig und läuft.  Hier ein paar einfache Beispiele
> mit generiertem Code vorher / nachher (ATmega8):

Welche praktischen Nutzen hat die Geschichte? Dein Tests sind alle 
andere als praxisrelevant. Und long long auf nem 8 Bit AVR . . . . .

von Wastl (hartundweichware)


Lesenswert?

Falk B. schrieb:
> Und long long auf nem 8 Bit AVR . . . . .

Brauch ich täglich! Meine 18GHz-Projekte auf dem Tiny ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sherlock 🕵🏽‍♂️ schrieb:
> Die hier aktiven Assembler-Programmierer erwecken den Eindruck, dass sie
> nur ihrem eigenen hand-gedengelten Code trauen.

Mag sein. Es gibt aber auch genügend Programmierer, die die Vorteile 
eines (optimierenden) Compilers zu schätzen wissen.

Falk B. schrieb:
> Dein Tests sind alle andere als praxisrelevant.

Es sind einfache Tests.  Und welche, wo es überhaupt was bringt.

> Und long long auf nem 8 Bit AVR ...

Gehört zu C99/C++.

: Bearbeitet durch User
Beitrag #7779578 wurde von einem Moderator gelöscht.
von Falk B. (falk)


Lesenswert?

Johann L. schrieb:
>> Die hier aktiven Assembler-Programmierer erwecken den Eindruck, dass sie
>> nur ihrem eigenen hand-gedengelten Code trauen.
>
> Mag sein. Es gibt aber auch genügend Programmierer, die die Vorteile
> eines (optimierenden) Compilers zu schätzen wissen.

Das bestreitet keiner.

> Falk B. schrieb:
>> Dein Tests sind alle andere als praxisrelevant.
>
> Es sind einfache Tests.  Und welche, wo es überhaupt was bringt.

Also bei komplexeren Tests bringt es nichts? Aha . . .

>> Und long long auf nem 8 Bit AVR ...
>
> Gehört zu C99/C++.

Ändert nix an der Aussage.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Falk B. schrieb:
> Also bei komplexeren Tests bringt es nichts? Aha . . .

Ach komm.  So schwer von Begriff bist du doch garnicht.

Falk B. schrieb:
> Dein Tests sind alle andere als praxisrelevant.

Die beiden letzten Tests zeigen, dass die Optimierung auch über die 
Grenzen von Basic Blocks hinweg funktioniert.  Programme, die mehr als 
einen Basic Block haben, als nicht praxisrelevant zu bezeichnen, zeugt 
nicht gerade von Erfahrung.

: Bearbeitet durch User
Beitrag #7779685 wurde von einem Moderator gelöscht.
von Dergute W. (derguteweka)


Lesenswert?

Moin,

Wie wenn man Schweine in ein Uhrwerk schauen laesst: Da kommt halt nur 
Grunzen, aber kein Verstaendnis.

fremdschaemenden Gruss
WK

von Falk B. (falk)


Lesenswert?

Dergute W. schrieb:
> Wie wenn man Schweine in ein Uhrwerk schauen laesst: Da kommt halt nur
> Grunzen, aber kein Verstaendnis.

OINK OINK ;-)

Ich hab zwar NULL Ahnung von C-Compilern, kann aber wohl deren Ergebnis 
im Fall eines AVR bewerten, denn AVR ASM kriegt ich gerade noch so auf 
die Reihe. Und da sehe ich relativ wenig Verbesserung. Die entscheidende 
Frage ist eher. Welches Optimierungspotential steckt überhaupt noch im 
avr gcc? Das Ding hat nun schon reichlich 20 Jahre Entwicklung hinter 
sich und ist, soweit ich das mit meinem begrenzten Horizont beurteilen 
kann, schon recht gut. Ich sehe da wenig Optimierungspotential, daß in 
vielen Fällen substantiell was verbessert. Diese Geschichte hier ist 
eher eine Fingerübung eines Compilerexperten. Man könnte ja noch 0,5% an 
Stellen rausholen, die 1% der Anwender nutzen . . . Die 3. Wachsschicht 
auf dem 5er BMW.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Falk B. schrieb:
> Die entscheidende
> Frage ist eher. Welches Optimierungspotential steckt überhaupt noch im
> avr gcc?

Jede Menge. Denn auch wenns den seit 20 Jahren gibt, hat wohl außer 
Johann niemand ernsthaft dran gearbeitet, und das Target war ja schonmal 
fast aus gcc rausgeflogen. Seit gcc 8 gehts ja zudem nur noch rückwärts 
mit der Optimierung bzgl. Codegröße. Alles, was am gcc verbessert wurde, 
ging in die X86/ARM-Architekturen, und bringt dem von-Neumann-Exoten AVR 
gar nichts.

Oliver

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


Lesenswert?

Falk B. schrieb:
> Also bei komplexeren Tests bringt es nichts?

Die Optimierung funktioniert unabhängig von der Komplexität des Codes. 
Was gemacht wird:

* Jeder Block wird von oben nach unten durchgegangen, und es wird 
ausgenutzt, wenn es bekannte Werte in Registern gibt.

* Es besteht die Möglichkeit, dass ein Registerwert bereits am Anfang 
eines Blocks bekannt ist.  Dazu werden die Blöcke in reverse Post Order 
topologisch sortiert, um die Chance zu erhöhen, dass ein Register via 
aller eingehenden Kanten bekannt ist.  (Die Werte von allen eingehenden 
Kanten müssen natürlich übereinstimmen, falls überhaupt bekannt).

Falk B. schrieb:
> Welches Optimierungspotential steckt überhaupt noch im avr gcc?
> Das Ding hat nun schon reichlich 20 Jahre Entwicklung hinter sich
> und ist, soweit ich das mit meinem begrenzten Horizont beurteilen
> kann, schon recht gut.

Ich beschäftige mich mit dem Ding, und von daher ist meine Sicht darauf 
garantiert voreingenommen, weil ja Sachen im Fokus stehen, die nicht 
oder nur unzureichend funktionieren.  Da hab ich bestimmt nen negativen 
Bias.

Und nicht alles is besser geworden.  In wesentlichen Aspekten 
(Register-Allokation und Verwendung von Post-Increment etc) war die v3.4 
von 2006 zum Beispiel besser als alle neuere Versionen, die SSA (Static 
Single Assignment) verwenden.

> Ich sehe da wenig Optimierungspotential,

Das aktuelle Paradigma von Multi-Pass Compilern hat natürlich Grenzen. 
In GCC sind das aktuell über 300 Passes.

Wenn überhaupt sind signifikante Verbesserungen m.E. nur mit komplett 
anderen Paradigmen zu erreichen, zum Beispiel mit Machine Learning.  Die 
hat aber das fundamentale Problem, dass der erzeugte Code nicht inhärent 
korrekt wäre -- und es auch keinen praktikablen Weg gibt, die 
Korrektheit a posteriori sicherzustellen (das ist grundlegend anders als 
zB bei AlphaGo etc, wo unvalide Züge einfach aussortiert werden können).

von Falk B. (falk)


Lesenswert?

Johann L. schrieb:

> Falk B. schrieb:
>> Welches Optimierungspotential steckt überhaupt noch im avr gcc?

> Und nicht alles is besser geworden.  In wesentlichen Aspekten
> (Register-Allokation und Verwendung von Post-Increment etc) war die v3.4
> von 2006 zum Beispiel besser als alle neuere Versionen, die SSA (Static
> Single Assignment) verwenden.

Ja, aber zu DER alten Version will ja auch keiner zurück. Ich nutze noch 
die letzte 2010er Version regelmäßig für meinen Kram und bin damit 
zufrieden.

>> Ich sehe da wenig Optimierungspotential,
>
> Das aktuelle Paradigma von Multi-Pass Compilern hat natürlich Grenzen.
> In GCC sind das aktuell über 300 Passes.
>
> Wenn überhaupt sind signifikante Verbesserungen m.E. nur mit komplett
> anderen Paradigmen zu erreichen, zum Beispiel mit Machine Learning.

Das heißt aber im Umkehrschluß, die Kiste ist real ausgereizt und es 
gibt kein nennenswertes Optimierungspotential. Gut für die Anwender, sie 
haben einen guten, kostenlosen Compiler. Schlecht für die Entwickler, 
die müssen sich neue Herausforderungen suchen. Ces't la vie!

> Die
> hat aber das fundamentale Problem, dass der erzeugte Code nicht inhärent
> korrekt wäre -- und es auch keinen praktikablen Weg gibt, die
> Korrektheit a posteriori sicherzustellen (das ist grundlegend anders als
> zB bei AlphaGo etc, wo unvalide Züge einfach aussortiert werden können).

Also ein unbrauchbarer Ansatz.

von Falk B. (falk)


Lesenswert?

Oliver S. schrieb:
>> Die entscheidende
>> Frage ist eher. Welches Optimierungspotential steckt überhaupt noch im
>> avr gcc?
>
> Jede Menge.

Wirklich? Gefühlt oder real? Werd mal konkret.

> Denn auch wenns den seit 20 Jahren gibt, hat wohl außer
> Johann niemand ernsthaft dran gearbeitet, und das Target war ja schonmal
> fast aus gcc rausgeflogen.

Nicht schön, aber am Ende verständlich, wenn sich keiner drum kümmert. 
Der 4004 wird vom gcc auch nicht unterstützt ;-)

> Seit gcc 8 gehts ja zudem nur noch rückwärts
> mit der Optimierung bzgl. Codegröße.

Man kann durchaus auch alte Versionen nutzen. Der Fetisch des dauernden 
Updates ist manchmal kontraproduktiv.

> Alles, was am gcc verbessert wurde,
> ging in die X86/ARM-Architekturen, und bringt dem von-Neumann-Exoten AVR
> gar nichts.

Ist halt so, was will man daran ändern? Oder vielmehr, MUSS man daran 
was ändern? AVR ist auch kein Neuling mehr sondern seit über 25 Jahren 
sehr erfolgreich und massenhaft im Markt etabliert. Da gibt es 
bestenfalls Anpassungen für die neueren AVR-Kerne.

von Peter D. (peda)


Lesenswert?

Falk B. schrieb:
> Ich sehe da wenig Optimierungspotential

Ich sehe da schon einiges. Man könnte endlich mal den alten Zopf mit R0, 
R1 Doppelnutzung als Zero und SREG Sicherung abschneiden. Das ist immer 
ein elendiges Gefummel, weil R0 eben nicht sicher 0 ist und R1 durch MUL 
zerstört wird.
Z.B. auf R2, R3 umschwenken, würde alles wesentlich einfacher machen.
Man könnte auch noch ein Attribut für Interrupts als nicht unterbrechbar 
definieren, dann entfällt Push/Pop für die SREG Sicherung.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Man könnte endlich mal den alten Zopf mit R0, R1 Doppelnutzung
> als Zero und SREG Sicherung abschneiden.

Weder R0 noch R1 ist in irgendeiner Weise mit SREG assoziiert.

> Das ist immer ein elendiges Gefummel, weil R0 eben nicht sicher 0
> ist und R1 durch MUL zerstört wird.

R1 enthält sicher 0 sowohl in Inline Assembler als auch am Beginn von 
(Assembler) Funktionen.  Die einzigen Stellen, wo R1 (möglicherwise) 
nicht 0 enthält, sind zu Beginn einer ISR, oder wenn man Code verwendet, 
der nicht ABI-konform ist.

Und R0 wird nicht nur durch MUL zerstört, sondern auch an ganz vielen 
anderen Stellen (Load, Store, Shift, Sign-Extend, ...).  Evtl. ist das, 
was du suchst, ein globales Register.

> Man könnte auch noch ein Attribut für Interrupts als nicht unterbrechbar
> definieren, dann entfällt Push/Pop für die SREG Sicherung.

Das würde nichts bringen, weil eine ISR den Inhalt von SREG nicht 
clobbern darf.  Dafür spielt es keine Rolle, ob die ISR selbst 
unterbrochen wird oder nicht.

Das würde das "CLR ZERO_REG" nach (fast) jedem MUL sparen.  Wozu ein 
zweites TMP-Register gut sein soll sehe ich jetzt nicht.  Und wenn eine 
ISR ZERO_REG nicht braucht, dann wird es auch nicht gesichert, dito für 
TMP_REG:
1
#include <avr/interrupt.h>
2
3
uint8_t volatile vx;
4
5
ISR (PCINT1_vect)
6
{
7
    ++vx;
8
}

wird zu:
1
00000056 <__vector_4>:
2
  56:  8f 93         push  r24
3
  58:  8f b7         in  r24, 0x3f  ; SREG
4
  5a:  8f 93         push  r24
5
  5c:  80 91 00 01   lds  r24, 0x0100  ; 0x800100 <vx>
6
  60:  8f 5f         subi  r24, 0xFF
7
  62:  80 93 00 01   sts  0x0100, r24  ; 0x800100 <vx>
8
  66:  8f 91         pop  r24
9
  68:  8f bf         out  0x3f, r24  ; SREG
10
  6a:  8f 91         pop  r24
11
  6c:  18 95         reti

SREG wird durch SUBI geclobbert und muss daher wie gesagt gesichert 
werden unabhängig davon, ob die ISR unterbrochen wird oder nicht.  Ein 
spezielles (neues) fixes Register nur zur SREG-Sicherung zu verwenden 
bringt m.E. nicht viel.  Wenn es auf jedes einzelne Tick ankommt, dann 
ist (Inline) Assembler dein Freund.

> Z.B. auf R2, R3 umschwenken, würde alles wesentlich einfacher machen.

Der Blocker ist, dass das ein neues ABI wäre.  Teile der LibC werden 
ungültig, dito für andere Libs wie für Arduino, oder generell Code, der 
Inline Asm enthält.

Dann die Frage, wie du das mit dem Startup-Code machen willst.  R1 
nullen oder R2?  Ein Configure-Test funktioniert nicht, weil crt*.o per 
Device-Packs distribuiert werden kann.  Binutils PR21683 setzt auch das 
aktuelle ABI voraus.  crt*.o per Symbol zu bespaßen à la
1
.byte __0reg17, 0x24  ; CLR __0reg17 / 17

setzt voraus, dass man weiß, welches ABI aktiv ist, d.h. compiler 
definiert ein Symbol, das denn vom Default-Linkerskript definiert wird:
1
__0reg = DEFINED(__0reg) ? __0reg : 1;
2
__0reg17 = DEFINED(__0reg17) ? __0reg17 : 17 * __0reg;

Ich wüsste jetzt niemand, der das Fass aufmachen möchte.

: Bearbeitet durch User
von Michael B. (mb_)


Lesenswert?

Peter D. schrieb:
> Man könnte auch noch ein Attribut für Interrupts als nicht unterbrechbar
> definieren, dann entfällt Push/Pop für die SREG Sicherung.

Die würde nicht entfallen.
SREG wird nicht durch Unterbrechung überschrieben, sondern vom normalen 
Progammablauf durch Instruktionen, die Flags verändern.

von Peter D. (peda)


Lesenswert?

Michael B. schrieb:
> SREG wird nicht durch Unterbrechung überschrieben, sondern vom normalen
> Progammablauf durch Instruktionen, die Flags verändern.

Wird ein Interrupt nicht durch andere unterbrochen, reicht die Sicherung 
per IN/OUT in ein reserviertes Register (2 Zyklen) für alle Interrupts 
aus.
Erste bei Unterbrechbarkeit ist zusätzliches PUSH/POP (+ 4 Zyklen) 
notwendig.
Hier mal der Overhead, inklusive der Zero-Reg Arie:
1
ISR(INT0_vect)
2
{
3
  80:  1f 92         push  r1
4
  82:  0f 92         push  r0
5
  84:  0f b6         in  r0, 0x3f  ; 63
6
  86:  0f 92         push  r0
7
  88:  11 24         eor  r1, r1
8
  PORTB |= 1<<1;
9
  8a:  29 9a         sbi  0x05, 1  ; 5
10
}
11
  8c:  0f 90         pop  r0
12
  8e:  0f be         out  0x3f, r0  ; 63
13
  90:  0f 90         pop  r0
14
  92:  1f 90         pop  r1
15
  94:  18 95         reti
Man könnte also 13 Zyklen sparen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Hier mal der Overhead, inklusive der Zero-Reg Arie: [...]

Du verwendest veraltete Tools!
1
#include <avr/interrupt.h>
2
3
ISR (INT0_vect)
4
{
5
  PORTB |= 1 << 1;
6
}
7
8
int main (void)
9
{
10
    return 0;
11
}
1
$ avr-gcc -Os demo.c -o demo.elf -mmcu=atmega8 && avr-objdump -d demo.elf
2
...
3
00000038 <__vector_1>:
4
  38:  c1 9a         sbi  0x18, 1  ; 24
5
  3a:  18 95         reti
6
...

https://gcc.gnu.org/gcc-8/changes.html#avr

Release der v8 war in 2018...

von Falk B. (falk)


Lesenswert?

Peter D. schrieb:
> Man könnte also 13 Zyklen sparen.

Für 98% aller ISRs ist das egal. Die restlichen 2%, die das WIRKLICH 
brauchen, nehmen eine ISR in reinem ASM und gut.

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.