Das Vorbelegen von Result ist überflüssig. Zu dem verhindert es die Codeoptimierung und macht die Registernutzung
unmöglich.
Delphi-Quellcode:
function BadResultUsage: Integer;
var
I: Integer;
begin
Result := 1;
for I := 0 to 1024 do
Result := Result + Result;
end;
function GoodUsage: Integer;
var
I,J: Integer;
begin
J := 1;
for I := 0 to 1024 do
J := J + J;
Result := J;
end;
Dazu Hagen Reddmann:
"In komplexeren Sources wird nämlich in BadResultUsage das Resultat im Stack zwischen gespeichert. In
GoodUsage geben wir aber dem Optimierer in J die Berechnungsvariable vor, die der Optimierer innerhalb
der Loop in ein Register optimieren kann. Erst am Ende der Funktion wird das Resultat belegt, was meistens
durch den Optimierer einfach bedeutet das er das optimierte Register J ind Register EAX kopiert. In
BadResultUsage wäre dies aber im Stack gespeichert.
Wohlgemerkt obige Beispiele sind zu einfach, das heißt der Optimierer kann so wie sie sind beide optimal
umsetzen. Würde man aber viel mehr Code drinnen stehen haben tritt diese Regel in Kraft."
Und weiter:
"Der Optimierer versucht als erste wichtige Maßnahme die am häufigsten 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, bzw. unter
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 dennoch Result sofort in der Funktion benutzt, statt eben erst ganz ganz zum
Schluss so wird der Optimierer ein Register benutzt das NICHT EAX ist. Erst am Schluss wird er das benutzte
Register, meistens ESI,EDI,EBX in das EAX Register kopieren. Dies bedeutet das wenn man das Resultat in
eine selbst definierte lokale Variable berechnet und erst ganz zum Schluss Result := LokaleVariable setzt der
Optimierer nun Freiheiten gewinnt. Er kann also selbständig entscheiden wie und wo er diese Lokale Variable
in welches Register optimiert. So oder so wird er am Exit der Funktion ein MOV EAX,reg durchführen.
Hintergrund bei dieser Logik ist der Fakt das nachdem Resultat am Anfang gesetzt wurde nun Aufrufe zu Unterprozeduren
erfolgen können. Er darf also NICHT EAX sofort als Resultat Register benutzen, da ja durch
den Unteraufruf EAX,EDX,ECX jederzeit zerstört werden können. Des weiteren ist die Standardaufrufkonvention
ja register, also EAX,EDX,ECX könnten bei Unterfunktionen als Parameter 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-Variablen benutzt werden. Statt also wie in C/C++ auf alle Eventualitäten durch komplette
Live-Analyse des gesamten 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 Prozedur gesetzt so muss der Optimierer diesen Wert als unsichtbare
temporäre lokale Variable auf dem Stack verwalten. "