Zitat:
3 ordinale Paramter maximal für Proceduren
2 ordinale Paramter maximal für Methoden (Self ist der 3. unsichtbare Parameter)
register Aufrufkonvention wenn möglich
Bei der Aufrufkonvention "register" gelten folgende Regeln
1.) register ist die Standardaufrufkonvetion, wenn nicht explizit anderes deklariert wurde
2.) register über gibt die ersten 3 Parameter immer in den Registern EAX,EDX,ECX wenn der Parametertyp ordinal oder per Referenz auf einen Ordinalen Typ ist
3.) Methoden sind standardmäßig register deklariert
4.) Methoden nutzen als ersten odinalen Parameter den versteckten Parameter Self, somit haben wir nur noch die zwei Register EDX,ECX zur Verfügung
5.) bei Proceduren mit mehr als 3 oder aber Methoden mit mehr als 2 Parametern werden die restlichen Parameter auf dem Stack übergeben
6.) Fließkomma Paramater sind IMMER eine Ausnahme, sie werden nie in direkt in Registern übergeben, sondern immer by Reference
7.) Komplexe Rückgabe Paramater bei Funktionen werden ebenfalls als Referenz in den Stack Speicher als letzter Paramater übergeben. Darunter fallen zB. Records oder Interfaces oder ShortStrings, aber nicht LongStrings
Zitat:
Result immer nur am Ende der Funktion belegen, oder vor einem Exit;
Der Optimierer versucht als erste wichtige Maßnahme die häufigst benutzten lokalen ordinalen Variablen in Register zu optimieren. Sollten nicht genügend Register vorhanden sein um alle Lokale Variablen plus der ordinalen Resultat Variablen in Registern zu speichern, so gerät der Optimierer und Druck, eg. Zugzwang. Als erstes wird er das Resultat als temporäre lokale Variable anlegen. Beim Exit aus der Funktion wird dann dieser Inhalt in Register EAX kopiert. Ausnahme sind Fließkomma oder komplexe Datentypen.
Angenommen die Funktion nutzt nur sehr wenige lokale Variablen, es stehen also genügend freie Register zur Verfügung. Wird nun denoch Result sofort in der Funktion benutzt, statt eben erst ganz ganz zum Schluß so wird der Optimier ein Register benutzt das NICHT EAX ist. Erst am Schluß wird er das benutzte Register, meistens ESI,EDI,EBX in das EAX Register kopieren. Dies bedeutet das wenn man das Resultat in eine selbstdefinierte lokale Variable berechnet und erst ganz zum Schluß Result := LokaleVariable setzt der Optimier nun Freiheiten gewinnt. Er kann also selbständig entscheiden wie und wo er diese Lokale Vaiable in welches Register optimiert. So oder so wird er am Exit der Funktion ein MOV EAX,reg durchführen.
Hintergund bei dieser Logik ist der Fakt das nachdem Resultat am Anfeng gesetzt wurde nun Aufrufe zu Unterproceduren erfolgen können. Er darf also NICHT EAX sofort als Resultat Register bentzen, da ja durch den Unteraufruf EAX,EDX,ECX jederzeit zerstört werden können. Desweiteren ist die Standardaufrufkonvention ja register, also EAX,EDX,ECX könnten bei Unterfunktionen als Paramater dienen. Nun, statt wie in C/C++ aufwendige Analysen durchzuführen haben die Borlandianer anders gedacht. Sie benutzen die Regel das EAX,EDX,ECX jederzeit zerstörbar sind und ESi,EDI,EBX immer innerhalb von Unterfunktionen durch diese gesichert werden müssen. Somit können die Register ESI,EDI,EBX ohne weitere Source-Analyse als Register-Lokale-Varibalen benutzt werden. Statt also wie in C/C++ auf alle Eventualitäten durch komplette Live-Analyse des gesammten Source zu optimieren, wie beim Borland Compiler von vornherein mit einem Regelwerk gearbeitet. Der Optimierer optimiert so als wäre es im Scheiß egal welche Unterfunktionen noch im Source stehen.
Nun, wird Result schon am Anfang der Procedure gesetzt so MUSS der Optimierer diesen Wert als unsichtbare temporäre lokale Variable auf dem Stack verwalten.
Zitat:
möglichst wenige lokale unkomplizierte Variablen
das Aufsplitten komplexer Funktionen auf mehrere weniger komplexe Funktion macht den Code meistens schneller, da der Optimierer besser arbeiten kann (gilt auch für nested Proceduren)
Auch bei diesen Tipps geht es darum dem Optimierer den Registerdruck zu nehmen, bzw. eben die Entscheidungs/Optimierungsfreiheiten zu erhöhen. Der Delphi Optimierer sieht das Innere einer Funktion als eigenständige und kleinste Optimierungseinheit an, bzw. BlackBox. Er weiß das EAX,EDX,ECX frei benutzt werden dürfen, das EDI,ESI nach deren Sicherung auf dem Stack frei benutzt werden können, und das EBX abhänig ob man Kylix Code erzeugt oder nicht ebenfalls nach der Sicherung auf dem Stack benutzt werden kann. Außnahme bei EBX sind nested Funktions. Bei solchen Funktionen wird der Compiler EBX als indirekten Stack-Pointer-Index benutzen um auf den übergeordneten Stack zuzugreifen. Allerdings NUR wenn in der nested Funktion auch wirklich Code steht der auf übergeordnete Variablen zugreift.
Nun, als erstes wird der Optimierer abhänig von der Komplexität entscheiden ob er die Übergabeparameter der Funktion zwischenspeichern muß. Wenn NICHT das entsteht die optimialste Form einer Funktion. Input über EAX,EDX,ECX, Berechnung nur mit EAX,EDX,ECX und Rückgabe über EAX. Falls aber Lokale Variablen existieren und Registerdruck ermittelt wird wird der Optimierer an´fangen ESI,EDI und eventuell EBX auf den Stack zu sichern. Danach werden die Paramater in EAX,EDX,ECX ind ESI,EDI kopiert. Dies trifft zB. fast immer bei Methoden zu, d.h. Self in EAX landet am Anfang der Methode in ESI. Das wird gemacht damit eventuelle Unterproceduren und methoden die wiederum Self benötigen OHNE Stackzugriffe über ESI Self zur Verfügung haben. Vor dem Aufruf einer solchen Unter-Methode wird der Compiler also ESI wieder nach EAX kopieren. Bei zB. 5 solcher Aufrufe würde er als 1. ESI pushen, 2. ESI=EAX setzen, 3. dann 5 mal EAX=ESI setzen und die Untermethoden aufrufen. Dies kommt mit 2 Stackzugriffen aus zum Pushen und Poppen von ESI. Würde man Self im Stack speichern so müsste der Compiler vor jedem Aufruf der 5 Untermethoden einen Stackzugriff durchführen um EAX korrekt zu laden.
Steigt die Komplexität der Funktion/Methode weiter an, so wird der Compiler die Taktik ändern. Er betrachten Schleifen als eigenständige Blacboxes und versucht nun inerhalb der Schleifen die Register gut zu nutzen. Z.b. preferiert er als Zählvaribale ein Register plus ein Register als Zeiger auf die Daten plus ein Register als Datenkontainer. Innerhalb der Schleife wird er nun per indirekter Addressierung mit Hilfe der Zählvaribalen im Register die daten in das Datenregister laden.
Gruß Hagen