Forum: Compiler & IDEs C-Funktion von Assembler aus Aufrufen: Prologue?


von asmprogger (Gast)


Lesenswert?

Hi,

ich habe ein Programm, mit einer ISR, welche sehr schnell sein soll. 
Daher ist diese in Assembler implementiert.
In der ISR entscheidet sich, ob Funktion A oder Funktion B aufgerufen 
werden muss. Funktion A ist in Assembler geschrieben, Funktion B in "C".

Prinzipiell funktioniert das auch. Die ISR hat das Attribut "naked", um 
sich das (langsame) Sichern der Register zu sparen. Benötigt werden nur 
2 Stück, das ist per Hand implementiert. In 99% der Fälle wird auch nur 
Funktion A aufgerufen (kommt mit 2 Registern aus), in 1% der Fälle 
Funktion B, welche eben diese Register alle benötigt.
Wenn ich jetzt aber einen CALL auf die Funktion B mache, dann geht diese 
davon aus, dass die Register bereits gesichert wurden und überschreibt 
sie einfach. Das ist natürlich nicht gut.
Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass 
die Register, welche dort verwendet werden, nochmal gesichert werden 
müssen?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Das hängt von der Architektur ab.

von asmprogger (Gast)


Lesenswert?

hatte vergessen zu erwähnen: ich verwende den avr-gcc aus dem aktuellen 
atmel studio

von Ralf G. (ralg)


Lesenswert?

asmprogger schrieb:
> Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass
> die Register, welche dort verwendet werden, nochmal gesichert werden
> müssen?

Den Compiler interessiert das nicht, dem hast du ja vorher extra 
verboten, sich da reinzuhängen. Das musst du schon selbermachen. Evtl. 
per Inline-Assembler?

von Stefan R. (srand)


Lesenswert?

asmprogger schrieb:
> ich habe ein Programm, mit einer ISR, welche sehr schnell sein soll.
> Daher ist diese in Assembler implementiert.

Non sequitur.

von Ralf G. (ralg)


Lesenswert?

Stefan Rand schrieb:
> Non sequitur.

Um etwas genauer zu werden: Funktionsaufrufe in einer ISR :-/

von Stefan E. (sternst)


Lesenswert?

asmprogger schrieb:
> Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass
> die Register, welche dort verwendet werden, nochmal gesichert werden
> müssen?

Du könntest der Funktion das Attribut "signal" verpassen, dann wird es 
vom Compiler als "ISR-Funktion" betrachtet, was das Sichern aller 
verwendeten Register einschließt. Zwei Dinge gibt es aber zu beachten:
1) Es entsteht etwas zusätzlicher (unnötiger) Overhead.
2) Die Funktion schließt mit einem RETI ab.

Gegen 1 kann man nichts machen (IMO). Die Auswirkungen von 2 
(vorzeitiges Enablen der Interrupts) kann man eliminieren, indem man dem 
Call der Funktion ein sofortiges cli folgen lässt.

von Stefan R. (srand)


Lesenswert?

Ralf G. schrieb:
> Stefan Rand schrieb:
>> Non sequitur.
>
> Um etwas genauer zu werden: Funktionsaufrufe in einer ISR :-/

Das ist nicht genauer.

Mir gehts schlicht darum, daß Assembler nicht aus "muß schnell sein" 
folgt.

von Ralf G. (ralg)


Lesenswert?

Stefan Rand schrieb:
> Mir gehts schlicht darum, daß Assembler nicht aus "muß schnell sein"
> folgt.

Das stimmt.
Der häufiger vorkommende Fehler ist ist allerdings, alles in die ISR zu 
packen um sich dann zu wundern, dass die Zeit nicht reicht. Und 
Funktionsaufrufe verschlimmbessern die Situation nur.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> asmprogger schrieb:
>> Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass
>> die Register, welche dort verwendet werden, nochmal gesichert werden
>> müssen?
>
> Du könntest der Funktion das Attribut "signal" verpassen, dann wird es
> vom Compiler als "ISR-Funktion" betrachtet, was das Sichern aller
> verwendeten Register einschließt. Zwei Dinge gibt es aber zu beachten:
> 1) Es entsteht etwas zusätzlicher (unnötiger) Overhead.
> 2) Die Funktion schließt mit einem RETI ab.
>
> Gegen 1 kann man nichts machen (IMO). Die Auswirkungen von 2
> (vorzeitiges Enablen der Interrupts) kann man eliminieren, indem man dem
> Call der Funktion ein sofortiges cli folgen lässt.

Auf ATxmega werden ISRs direkt nach dem RETI aktiviert, es bleibt keine 
Zeit für CLI wie bei den "alten" Cores.  Lösen kann man das, indem man 
folgenden Hack anwendet:
1
__asm (".macro reti\n"
2
       "ret\n"
3
       ".endm");
vor der ISR-Funktion (ich würd ISR einem Attribute signal vorziehen) und 
übersetzen mit -fno-toplevel-reorder.  Zudem darf nach dem asm keine 
Funktion ausser der ISR-Funktion folgen.

Alternativ kann man um den Aufruf der Funktion auf asm-Ebene alle 
call-clobbered Register sichern und wieder herstellen. Dies sind: SREG, 
R0-R1, R18-R27, R30-R31.  Zudem muß R1 aka. __zero_reg__ mit 0 
initialisiert werden.

von asmprogger (Gast)


Lesenswert?

Ok, danke soweit für die Antworten. :-) Da scheints wohl keinen sehr 
schönen/angenehmen Weg dafür zu geben. Ich werde dann die Register "per 
Hand" sichern.


Sehr interessant finde ichs aber, wie manche Leute sich anmaßen den Code 
zu kommentieren, wo sie ihn weder gesehen haben, noch überhaupt wissen, 
was er tun soll. Darüber, dass sich er GCC unsinnigerweiße erstmal 
etliche Register sichert meckert keiner, aber WEHE man ruft eine 
Funktion auf, das macht die Sache ja immer gleich unmöglich langsam. So 
ein Schmarrn...

von Ralf G. (ralg)


Lesenswert?

asmprogger schrieb:
> Darüber, dass sich er GCC unsinnigerweiße erstmal
> etliche Register sichert meckert keiner,

Das ist nicht unsinnig!!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

asmprogger schrieb:
> Darüber, dass sich er GCC unsinnigerweiße erstmal
> etliche Register sichert meckert keiner,

Aber hallo, darüber wird natürlich auch oft und gerne gemeckert!

Bislang war das "Problem" allerdings noch keinem wichtig genug, eine 
Lösung dafür zu erarbeiten...

von Axel S. (a-za-z0-9)


Lesenswert?

Ralf G. schrieb:
> asmprogger schrieb:
>> Darüber, dass sich er GCC unsinnigerweiße erstmal
>> etliche Register sichert meckert keiner,
>
> Das ist nicht unsinnig!!

Doch, ist es. Auf jeden Fall sollte das Sichern der Register hinter das 
if() verschoben werden und nicht im Prolog der ISR stattfinden. Insofern 
ist die konsequente Lösung, die ISR als naked zu deklarieren und die 
Register vor dem Anspringen der C-Funktion händisch zu sichern.

Was mich persönlich viel mehr nervt (weil es IMHO sehr einfach zu 
optimieren wäre) ist daß r1 aka _zero_reg_ immer erst auf den Stack 
gesichert und dann mit 0 initialisiert wird, auch dann wenn es nirgendwo 
in der ISR gebraucht wird.

@asmprogger: 
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage


XL

von Ralf G. (ralg)


Lesenswert?

Axel Schwenke schrieb:
> Doch, ist es. Auf jeden Fall sollte das Sichern der Register hinter das
> if() verschoben werden und nicht im Prolog der ISR stattfinden. Insofern
> ist die konsequente Lösung, die ISR als naked zu deklarieren und die
> Register vor dem Anspringen der C-Funktion händisch zu sichern.

Und bei mehreren 'IFs'? Jedesmal? Nur für eine ISR, die mehrere 
Funktionen aufrufen will, die aus irgendwelchen Dateien zusammengesucht 
werden? (Gerne auch in Assembler!) Wer an dem Punkt ankommt, hat was 
anderes falsch gemacht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Axel Schwenke schrieb:

> Was mich persönlich viel mehr nervt (weil es IMHO sehr einfach zu
> optimieren wäre) ist daß r1 aka _zero_reg_ immer erst auf den Stack
> gesichert und dann mit 0 initialisiert wird, auch dann wenn es nirgendwo
> in der ISR gebraucht wird.

Im Gegenteil, das ist ganz und garnicht einfach zu optimieren.  Daß klar 
ist, was das Resultat der Optimierung sein soll, haißt noch lange nicht, 
daß es einfach zu implementieren ist.

I.W. bedeutet es, fast das ganze avr-Backend des GCC neu zu schreiben.

Ich selbst bin nie an diese Optimierung (PR20296) rangegangen: Praktisch 
das ganze avr-Backend umkrempeln, um im Endeffekt maximal 3 
Instruktionen zu sparen.

Wenn es sooo zeitkritisch ist, dann führt an Assembler eh kein Weg 
vorbei.  Ohnehin wird das 0-Register bei größeren Funktionen fast immer 
gebraucht, d.h. die Optimierung würde nur bei Mini-Funktionen was 
bringen.

Aber es steht jedem frei, das anders zu sehen und in medias res zu 
gehen, d.h. in die GCC-Quellen und ein Patch zu erarbeiten :-)

Pfiffige Entwickler gibt's hier doch genug; hier sitzt man an der Quelle 
-- sollte man eigentlich meinen...

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

Ralf G. schrieb:
> Axel Schwenke schrieb:
>> Doch, ist es. Auf jeden Fall sollte das Sichern der Register hinter das
>> if() verschoben werden und nicht im Prolog der ISR stattfinden. Insofern
>> ist die konsequente Lösung, die ISR als naked zu deklarieren und die
>> Register vor dem Anspringen der C-Funktion händisch zu sichern.
>
> Und bei mehreren 'IFs'? Jedesmal? Nur für eine ISR, die mehrere
> Funktionen aufrufen will, die aus irgendwelchen Dateien zusammengesucht
> werden?

Wo soll das Problem sein? Für eine ISR ohne Funktionsaufrufe kann gcc ja 
jetzt schon feststellen, welche Register er in der ISR benutzt und 
welche nicht. Jetzt müßte er bloß noch den PUSH-opcode möglichst nahe an 
die tatsächliche Verwendung des Registers schieben.

Und wenn er beim Aufruf einer Funktion mit C linkage davon ausgeht, daß 
die alle call-saved Register clobbered, dann kann er die ja meinetwegen 
unmittelbar vor dem Funktionsaufruf auf den Stack schieben. Nur eben 
nicht im Prolog der ISR.

Tatsächlich würde ich mir ein Attribut ähnlich signal wünschen, das den 
Compiler veranlaßt, einer Funktion Prolog und Epilog zum Sichern aller 
benutzten Register hinzuzufügen (aber mit RET statt RETI 
zurückzukehren). Dann könnte man solche Funktionen "blind" aufrufen.


XL

von Axel S. (a-za-z0-9)


Lesenswert?

Johann L. schrieb:
> Im Gegenteil, das ist ganz und garnicht einfach zu optimieren.  Daß klar
> ist, was das Resultat der Optimierung sein soll, haißt noch lange nicht,
> daß es einfach zu implementieren ist.
>
> I.W. bedeutet es, fast das ganze avr-Backend des GCC neu zu schreiben.

OK, dann war meine Opinion wohl doch zu Humble ;)

In der Tat spreche ich von winz-ISR (nur dort fällt die "überflüssige" 
Sicherung von R1 ins Gewicht). So etwas wie
1
volatile uint8_t t0u;
2
3
ISR (TIMER0_OVF_vect)
4
{
5
   ++t0u;
6
}

das ist ein sehr typisches Konstrukt in meinen Programmen. Noch öfter 
als Zähler, der bis 0 runterzählt und dann da bleibt:
1
volatile uint8_t countdown;
2
...
3
if ((uint8_t r=countdown)) 
4
  countdown= r-1;

Dann bleibt wohl nur die Variante, das in (Inline) ASM zu schreiben. 
Nicht portabel, aber irgendwas ist ja immer.


XL

von Rolf Magnus (Gast)


Lesenswert?

asmprogger schrieb:
> Darüber, dass sich er GCC unsinnigerweiße erstmal etliche Register
> sichert meckert keiner, aber WEHE man ruft eine Funktion auf, das macht
> die Sache ja immer gleich unmöglich langsam.

Da beteht ein direkter Zusammenhang: GCC merkt sich die ganzen Register 
gerade weil eine Funktion aufgerufen wird, denn er weiß nicht, welche 
Register die Funktion braucht.

Axel Schwenke schrieb:
> Wo soll das Problem sein? Für eine ISR ohne Funktionsaufrufe kann gcc ja
> jetzt schon feststellen, welche Register er in der ISR benutzt und
> welche nicht. Jetzt müßte er bloß noch den PUSH-opcode möglichst nahe an
> die tatsächliche Verwendung des Registers schieben.

Bei einer ISR, die mit mehreren ifs arbeitet und in manchen davon einen 
Funktionsaufruf hat, würde das zu einer Codeexplosion führen, weil dann 
die PUSH-Orgie mehrmals eingefügt werden müßte. Und das, um manchmal 
weniger Taktzyklen in der ISR zu brauchen? In der Regel zählt bei ISRs 
eh nur der Worst Case. Entweder eine ISR ist immer schnell genug, oder 
sie ist es nicht. Eine, die manachmal, aber eben nicht immer besonders 
schnell ist, bringt keinen besonderen Vorteil.

> Tatsächlich würde ich mir ein Attribut ähnlich signal wünschen, das den
> Compiler veranlaßt, einer Funktion Prolog und Epilog zum Sichern aller
> benutzten Register hinzuzufügen (aber mit RET statt RETI
> zurückzukehren). Dann könnte man solche Funktionen "blind" aufrufen.

Wenn eine Funktion aber ausschließlich aus einer ISR heraus aufgerufen 
wird, ist es in der Regel nicht unbedingt nötig, den Code in eine 
separate Funktion zu packen. Das produziert dann nur noch zusätzlichen 
Funktionsaufrufs-Overhead.
Und wenn eine Funktion, wie du sie beschreibst, auch außerhalb von ISRs 
aufgerufen wird, hätte man dort immer den Overhead der 
Registersicherungen.

von Oliver (Gast)


Lesenswert?

asmprogger schrieb:
> Da scheints wohl keinen sehr
> schönen/angenehmen Weg dafür zu geben. Ich werde dann die Register "per
> Hand" sichern.

Was ist daran unschön? Setzt halt vor und hinter den den Aufruf der 
C-Funktion noch ein paar Zeilen Assembler, die die caller-saved Register 
pushen und poppen, das wars. So schlimm ist das nun auch wieder nicht.

Unschön wird es erst, wenn du auch da um jeden Zyklus feilschen willst, 
und nur genau die Register retten willst, die auch tatsächlich benutzt 
werden. Dann darfst du das nach jeder Änderung am C-Code anpassen, mit 
durchaus realem Risiko, es zu vergessen.

Oliver

von Peter D. (peda)


Lesenswert?

asmprogger schrieb:
> noch überhaupt wissen,
> was er tun soll.

Genau das ist das Hauptproblem, es gibt nämlich für nichts eine 
universelle Lösung.
Je mehr man über den Kontext wüßte, umso besser könnte man helfen.

In der Regel nützt ein kurzer Interrupt nämlich garnichts, sondern der 
Worst-Case ist entscheidend.
Z.B. die CPU ist gerade in einem UART-, I2C- oder sonstigem Interrupt 
beschäftigt, dann muß der kurze Interrupt erstmal schön lange warten, 
bis er ran darf.

Daher wäre schonmal wichtig, ob es keine weiteren Interrupts zu 
berücksichtigen gibt.
Denn wenn es eh keine weiteren Interrupts gibt, dann könnte auch ein 
Polling in der Mainloop die Lösung sein. Damit spart man schonmal 10 
Zyklen für das Anspringen und Verlassen des Interrupts.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver schrieb:
> asmprogger schrieb:
>> Da scheints wohl keinen sehr schönen/angenehmen Weg dafür zu
>> geben. Ich werde dann die Register "per Hand" sichern.
>
> [...] ein paar Zeilen Assembler, die die caller-saved Register
> pushen und poppen, das wars.

Nein, su sichern sind die call-clobbered Register.  Um die anderen,
nämlich die callee-saved Register, kümmert sich bereits die gerufene
Funktion.  Zusätzlich müssen R0, R1 und SREG verarztet werden.


Axel Schwenke schrieb:
> Johann L. schrieb:
>> Im Gegenteil, das ist ganz und garnicht einfach zu optimieren.
>>  Daß klar ist, was das Resultat der Optimierung sein soll, heißt
>> noch lange nicht, daß es einfach zu implementieren ist.
>>
>> I.W. bedeutet es, fast das ganze avr-Backend des GCC neu zu schreiben.
>
> OK, dann war meine Opinion wohl doch zu Humble ;)
>
> In der Tat spreche ich von winz-ISR (nur dort fällt die "überflüssige"
> Sicherung von R1 ins Gewicht). So etwas wie
>
1
volatile uint8_t t0u;
2
3
ISR (TIMER0_OVF_vect)
4
{
5
   ++t0u;
6
}

Der Aufwand der Implementierung ist aber unabhängig von der Größe des 
übersetzten Codes.  Im GCC müssten alle pseudo-Instruktionen, aka. 
insns, erweitert werden.  Das Backend kennt rund 1400 insns.


> das ist ein sehr typisches Konstrukt in meinen Programmen. Noch öfter
> als Zähler, der bis 0 runterzählt und dann da bleibt:
>
1
volatile uint8_t countdown;
2
...
3
if ((uint8_t r=countdown))
4
  countdown= r-1;
>
> Dann bleibt wohl nur die Variante, das in (Inline) ASM zu schreiben.
> Nicht portabel, aber irgendwas ist ja immer.

Solche Countdown-Zähler benutze ich auch, und die tun prima in C; selbst 
bei zeitkritischen Anwendungen musste ich da nicht auf Assembler 
umstellen.

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.