Forum: Compiler & IDEs rcall main() - Muss das sein?


von Uwe (Gast)


Lesenswert?

Hi.

Beim Debuggen ist mir aufgefallen, dass der AVR-GCC (4.3.2) offenbar 
immer nach der Initialisierung mit einem "rcall" nach main() springt. 
Das ist doch Käse - oder übersehe ich da etwas? Müsste das nicht eher 
"rjmp" sein?

Auch wenn ich __attribute__((noreturn)) oder __attribute__((OS_main)) 
verwende, besteht der GCC weiter auf dem "rcall". Das bedeutet im 
Endeffekt, dass schon am Anfan der main() zwei Bytes (die 
Rücksprungadresse zurück ins "init") auf dem Stack liegen, die da nie, 
nie, nie wieder rausgeholt werden und somit dauerhaft und unnütz RAM 
verbrauchen.

Frage deshalb: Kann ich dem GCC das irgendwie abgewöhnen, die zwei Bytes 
Stack so zu verschwenden? - Was zu funktionieren scheint, ist SP = 
RAMEND; am Anfang der main() einzufügen. Aber das kostet wieder 8 Byte 
Flash und kann doch so auch nicht sauber sein.

Für sachdienliche Hinweise bin ich dankbar. (Nicht, dass mich die zwei 
Bytes jetzt vor unlösbare Speicher-Probleme stellen, aber es stört mich 
trotzdem :) )

- Uwe

von (prx) A. K. (prx)


Lesenswert?

Drn Startup-Code der avr-libc anpassen. Die ist derzeit auf Standard-C 
getrimmt, was ein return aus main zulässt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Uwe wrote:
> Hi.
>
> Beim Debuggen ist mir aufgefallen, dass der AVR-GCC (4.3.2) offenbar
> immer nach der Initialisierung mit einem "rcall" nach main() springt.
> Das ist doch Käse - oder übersehe ich da etwas? Müsste das nicht eher
> "rjmp" sein?

Momentan ja, wie A.K. bereits erklärt hat.

Der springt zu main steht in .init9, du kannst diesem also zuvorkommen 
und sagen:
1
/* !!! Never call this Function !!! */
2
void _init8(void) __attribute__ ((naked, section (".init8")));
3
4
void _init8(void)
5
{
6
    __asm __volatile__ ("%~call main"::);
7
}

Zumindest dann, wenn .init8 ansonsten leer ist: Wenn .init8 schon 
gebraucht wird, geht das nicht, weil die Reihenfolge beim Linken der 
.init8-Komponenten nicht spezifiziert ist. Der Spring zu main muss aber 
als letztes stehen. Der (r)call main in .init9 wird aber trotz der 
Abkürzung noch stehen bleiben.

> Frage deshalb: Kann ich dem GCC das irgendwie abgewöhnen, die zwei Bytes
> Stack so zu verschwenden? - Was zu funktionieren scheint, ist SP =
> RAMEND; am Anfang der main() einzufügen. Aber das kostet wieder 8 Byte
> Flash und kann doch so auch nicht sauber sein.
>
> Für sachdienliche Hinweise bin ich dankbar. (Nicht, dass mich die zwei
> Bytes jetzt vor unlösbare Speicher-Probleme stellen, aber es stört mich
> trotzdem :) )


GCC unterstützt prinzipiell Sibling Calls (aka Tail Calls), d.h. bei 
einer Funktion wie
1
void foo(...)
2
{
3
   // mach was
4
   bar(...);
5
}
kann bar unter bestimmten Umständen per Sprung (jmp/rjmp) angesprungen 
werden, so daß foo nicht mit einer (r)call/ret-Sequenz enden muss. Für 
main würde das nix bringen, weil deren Aufruf nicht in C steht, sondern 
in asm. Für andere Funktionen genannten Aufbaus gäbe es jedoch 
kleineren, schnelleren Code und sparte Stack. Das ret zur Rückkehr aus 
foo steht dann in bar.

Das wird im avr-Backend von gcc aber (noch) nicht unterstützt. Also 
immer frisch ans Werk! :-)

Dazu wäre in gcc/config/avr/avr.c der Target-Hook 
TARGET_FUNCTION_OK_FOR_SIBCALL zu definieren, der darüber entscheidet, 
wann eine Funktion mit einem Sibcall anden darf (abhängig von Stack- und 
Register-Nutzung, Attributen wie "signal" und "interrupt", etc). 
Nachzulesen in
   http://gcc.gnu.org/onlinedocs/gccint/Tail-Calls.html#Tail-Calls

Auserdem muss natürlich die Maschinenbeschreibung gcc/config/avr/avr.md 
"sibcall"/"sibcall_value"-Patterns analog zu "call" und "call_value" 
erzeugen und matchen können, evtl. auch "sibcall_epilogue":
   http://gcc.gnu.org/onlinedocs/gccint/Standard-Names.html#index-g_t_0040code_007bsibcall_005fepilogue_007d-instruction-pattern-3489

Johann

von Uwe (Gast)


Lesenswert?

Danke, Johann, für die ausführliche Erklärung. - Zur Zeit liegt es mir 
allerdings fern, Hand an den GCC zu legen :) Ich hatte eher an einen 
Switch oder ein Attribut gedacht, das ich bisher nur übersehen habe, als 
ich nach einer Lösung fragte.

Die Lösung, die .init9 zu überspringen, finde ich letztlich aber auch 
nicht besser, als SP=RAMEND; am Anfang der main(). - Ich hatte neulich 
sogar eine main(), die zu Beginn automatisch ein paar "call saved" 
register weg-pusht und so noch einige Bytes "klaut". Solche Situationen 
"behebt" die Re-Initialisierung vom SP gleich mit.

Solange der GCC hier RAM "klaut", wäre das also ein Punkt, den man in 
ein Optimierugs-How-To aufnehmen könnte...

- Uwe

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Uwe wrote:
> Danke, Johann, für die ausführliche Erklärung. - Zur Zeit liegt es mir
> allerdings fern, Hand an den GCC zu legen :) Ich hatte eher an einen
> Switch oder ein Attribut gedacht, das ich bisher nur übersehen habe, als
> ich nach einer Lösung fragte.
>
> Die Lösung, die .init9 zu überspringen, finde ich letztlich aber auch
> nicht besser, als SP=RAMEND; am Anfang der main(). - Ich hatte neulich
> sogar eine main(), die zu Beginn automatisch ein paar "call saved"
> register weg-pusht und so noch einige Bytes "klaut". Solche Situationen
> "behebt" die Re-Initialisierung vom SP gleich mit.

hmmm... wenn main einen Frame hat, dann gehst du damit aber gründlich 
baden!

Erzwing zB mal einen Frame (bzw. Frame-Pointer) in main per
1
int main()
2
{
3
    int volatile x;
4
    x = 0;
5
    // ...
6
}
und überleg dir die Konsequenz von SP=...! Auch nicht-pathologischer 
C/C++-Code ist natürlich in der Lage, nen Frame erforderlich zu machen.

Dummerweise gibt's kein __builtin_frame_size, mit dem die Frame-Größe 
bestimmt werden könnte. (Naja, es gibt ja die Quellen und du kannst auch 
das in avr-gcc reinbasteln :-)) Allerdings dürfte das kaum mit 
__builtin_choose_expr zusammenarbeiten können, weil die Frame-Größe 
nicht vor der globalen Register-Allokierung bekannt ist, 
__builtin_choose_expr aber logischerweise nur Bedingungen auswerten 
kann, die zur Compilezeit bekannt sind -- ansonsten wär's einfach ein 
if.

Also keine Chance, das wasserdicht zu machen, es sei denn, du 
übersetzt einmal main, kontrolliert, daß es keinen Frame braucht, und 
änderst nie wieder das Object-File, ohne es danach immer abermals zu 
sichten (also keine anderen Schalter, keine neuem gcc-Versionen, keine 
Änderungen im Modul, etc. ohne Sichtung).

Ich mach das in meinen Anwendungen brigens so, daß main nix macht ausser 
eine noreturn-Funktion zu rufen. Ist zwar ein paar Byte schlechter als 
Assembler, but who cares...

Einige Bytes sind immer einem nicht-perfekten Compiler oder einem 
Standard geschuldet.

Johann

von Uwe (Gast)


Lesenswert?

Das mit dem main()-Frame stimmt natürlich auch wieder - obwohl man das 
ja leicht vermeiden kann, wenn man will. Also doch lieber die 
.init8-Lösung.

Andererseits könnte man das vielleicht generell im GCC verankern: Wenn 
eine Funktion aufgerufen werden soll, die ((noreturn)) ist, dann nimm 
ein "rjmp".

Nunja, vielleicht kommt das ja irgendwann - oder ich finde die Zeit und 
mach's schnell selbst :-) Bis dahin schenke ich ihm einfach erstmal die 
zwei Bytes ;-)

- Uwe

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.