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
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:
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
voidfoo(...)
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
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
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
intmain()
2
{
3
intvolatilex;
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
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