\chapter{Der TooL-Optimierer}
\label{TheOptimizer}


\begin{figure}
\begin{center}
\leavevmode
\epsfxsize=15cm
\epsffile{strategy.eps}
\end{center}
\caption{Die Phasen des TooL-Optimierers}
\label{OptimizerStrategy}
\end{figure}

Die Darstellungen der letzten beiden Kapitel bilden eine Grundlage
f"ur die Entwurfsentscheidungen des TooL-Optimierers.  Die Eckpunkte des
Entwurfs lassen sich wie folgt charakterisieren:

\begin{itemize}
\item
Ein Profiler soll Empf"anger- und Argumentklassen, Selektoren und
Aufrufh"aufigkeiten zur Laufzeit von Programmen festhalten.
Diese Information l"a\3t sich aufgrund
der persistenten Natur des TooL-Systems "uber mehrere Sitzungen hinweg 
gewinnen.
\item 
Der Aufbau eines Methodencaches mit empf"anger- und argumentspezifisch optimierten
Methoden wird  durch  die Profilinformation gesteuert und nutzt auch 
die Information "uber Argumentklassen aus. Dieser Vorgang verl"auft
wegen der hohen Laufzeit des in TL geschriebenen TooL-Optimierers
nicht dynamisch, sondern mu\3 explizit ausgel"ost werden. Auch der
Methodencache bleibt persistent erhalten.
\item 
Weder selektorbasierte Klassenabsch"atzung noch Inline-Caching werden eingesetzt,
denn die dadurch erzwungene Mitbehandlung der Sonderf"alle, in denen Klassentests 
fehlschlagen, f"uhrt zu gro\3en Codezuw"achsen und erfordert einen
Codeverschiebungslauf
({\em splitting}).
Statt dessen wird das Typsystem von TooL "ahnlich wie in
\cite{Atkinson86} und  \cite{JohGraZur88} als Informationsquelle angesehen
und das Problem der Mitf"uhrung der Sonderf"alle vermieden.
\item 
Eine Klassenanalyse soll Klasseninformation bei einem Durchlauf durch den TML-Graphen
verbreiten und dabei dynamische Methodenaufrufe durch statisch
gebundene Funktionsaufrufe ersetzen.
\item
Der in \cite{Kira94} beschriebene CPS-Optimierer f"ur TML dient als Inlining-Mechanismus
und wird erweitert, um allgemein Endrekursionen in Spr"unge umwandeln
zu k"onnen.
Damit sollen den in TooL auftretenden Nachteilen der
reinen Objekt\-orientierung auf m"oglichst saubere Weise begegnet werden,
denn so brauchen auch die Kontrollstrukturen {\sf while}, {\sf for} usw.
keine festverdrahtete Sonderbehandlung.

\end{itemize}

Die Phasen des TooL-Optimierers sind in Abbildung
\ref{OptimizerStrategy} angedeutet.

\section{Profiling}

Eine der Kernaussagen der Arbeit von H"olzle \cite{Hoelzle94} lautet:
Klasseninformation, die in objektorientierten Sprachen zur "Ubersetzungszeit
nur durch m"uhsame und 
wenig effektive Analysen  gewonnen werden kann, liegt
w"ahrend der Laufzeit zu Gen"uge vor. Ein  wirkungs\-voller 
Methodencache mit empf"angerspezifisch "ubersetzten Methoden kann nur durch 
Laufzeitinformation "uber tats"achlich aufgetretene Empf"angerklassen
aufgebaut werden. Daher bietet sich  der Einsatz eines Profilers
an, der Information "uber das Laufzeitverhalten von Programmen sammelt.
Die Phasen des Profilings und der Optimierung sind zeitlich getrennt, da
der Programmierer oder die Programmiererin das Profiling und die
Optimierungsl"aufe explizit ausl"osen m"ussen.
Wie in Abschnitt \ref{profileValidity} diskutiert, lassen die
Messungen von \cite{GroDeaGarCha95}, die eine hohe Korrelation
zwischen Laufzeitprofilen verschiedener Programm\-versionen "uber gr"o\3ere
Zeitr"aume hinweg nachweisen,  den Modus der getrennten Profiler- und
Optimierer\-l"aufe
in TooL als gerechtfertigt erscheinen.

In TooL werden 
zur Laufzeit anfallende Klasseninformationen in einer Profiltabelle festgehalten.
Hierzu wird die virtuelle Maschine des Tycoon-Systems folgenderma\3en 
erweitert: bei der  Ausf"uhrung eines {\sf send}-Bytecodes liegen 
der Selektor der Methode, der Empf"anger und die sonstigen 
Argumente im Laufzeitkeller.  Die Klassen des Empf"angers und der
Argumente werden mit einem Aufrufz"ahler in eine Hashtabelle
eingetragen, deren Eintr"age die in Abbildung \ref{ProfileTable} angedeutete
Struktur besitzen.


\begin{figure}[tbh]
\begin{center}
\leavevmode
\epsfxsize=13cm
\epsffile{hashentry.eps}
\end{center}
\caption{Struktur eines Profileintrags}
\label{ProfileTable}
\end{figure}


Gest"utzt von der empirischen Feststellung, da\3
in Smalltalk mehr als 95\% aller Botschaften mit drei oder weniger 
Argumenten aufgerufen werden (der Empf"anger wird auch als Argument
angesehen) \cite{Kra83}, wird die Anzahl der zus"atzlichen Argumente pro 
Eintrag in der Profiltabelle auf zwei begrenzt; somit werden
die Klassen von bis zu drei Argumenten festgehalten.
F"ur jedes Argument werden nur bis zu zwei m"ogliche verschiedene Klassen 
aufgezeichnet, denn mehr Klassen deuten einen zu hohen Grad an Polymorphismus
an, so da\3 die gesammelten Klasseninformationen  f"ur dieses Argument keine 
geeigneten Hinweise an den 
Optimierer darstellen.

Um den Aufwand des Profilings m"oglichst klein zu halten, wird eine einfache
Hashing-Strategie mit folgender Hashfunktion
eingesetzt:
\begin{eqnarray*}
h(selector,class) = ((class\; *\; const)\; +\; selector)\; mod\; hashsize,
\end{eqnarray*}

Die Tabelle wird durch offene Adressierung gef"ullt; bei Kollisionen
wird linear sondiert, bis ein freies Feld vorliegt. Die L"ange der 
Sondierung ist begrenzt  und im Falle einer gef"ullten Sondierungssequenz
wird der Eintrag mit der kleinsten Zahl von Aufrufen "uberschrieben.
Da TooL-Programme von einem Bytecodeinterpreter
bearbeitet werden, und ein dynamischer Methodenaufruf mehrere
C-Funktionsaufrufe und Hashoperationen umfa\3t, f"allt der Zusatzaufwand
des Profilings nicht unverh"altnism"a\3ig hoch ins Gewicht. Messungen
an nicht optimierten TooL-Programmen mit einer hohen Botschaftsdichte
zeigen, da\3 die f"ur das Profiling ben"otigte Zeit nicht mehr als \( 10 \% \) der
Gesamtlaufzeit von Programmen ausmacht.

\section{Aufbau eines empf"anger- und argumentspezifisch optimierten Methoden\-caches}

Die Aufrufz"ahler in den Eintr"agen der Profiltabelle dienen als Hinweise,
bei welchen Methoden eine Optimierung sich lohnen k"onnte, denn durch sie
sind besonders oft aufgerufene Methoden auffindbar. Bedingt durch die 
Tatsache, da\3 die Profilinformation keine aufrufortspezifischen H"aufigkeiten
beinhaltet\footnote{Aufrufortspezifische
Laufzeitprofile sind  nach  \cite{GroDeaGarCha95} die geeigneteste
Informationsquelle
f"ur Optimierungen,  aber gemittelte Profildaten stellen 
einen guten Kompromi\3 zwischen Granularit"at der Information und Einfachheit
des Profilings dar.
}, wird in der jetzigen Version des TooL-Optimierers  diese 
Information nur auf  einfache
Weise ausgenutzt: Methoden mit einer Aufrufh"aufigkeit (normiert um die
maximale Zahl der Aufrufe) gr"o\3er als eine Grenze $ \theta $ werden als 
Optimierungskandidaten angesehen; andere werden nicht optimiert.
Messungen haben ergeben, da\3 bei dem Wert von $0.001$ f"ur
$ \theta $ circa die H"alfte der Profileintr"age wegf"allt, und da\3 die Effizienzsteigerung
durch den Cache von optimierten Methoden  90\% bis 95\% der maximal m"oglichen
Effizienzsteigerung bei $ \theta = 0 $ ist.

F"ur jeden Optimierungskandidaten l"a\3t sich aus dem Selektor und der
Empf"angerklasse die Repr"asentation einer  Methoden auffinden. In dieser sind neben dem
ausf"uhrbaren Code zus"atzlich der TML-Code\footnote{Aufgrund der einfachen Abbildung von TooL-AST nach TML lie\3e sich an dieser Stelle
statt des relativ volumin"osen TML-Graphen auch eine kompakte Bytecodierung des TooL-AST 
eintragen, die bei Optimierungsbedarf in TML konvertiert werden kann.}
 und die Signatur der Methode abgelegt.
In der Signatur stehen die Typen der Argumente und des
Ergebnisses der Methode. Diese Information wird nur f"ur einen
begrenzten Satz von Basistypen wie {\sf Int}, {\sf Fun}, {\sf Bool},
{\sf String}, und {\sf Char} ausgenutzt, um die Klassen der
Argumente zu bestimmen. Die Information aus der Signatur, bereichert
um die Profildaten "uber die Klassen des Empf"angers und der Argumente,
stellt den Ausgangspunkt eines Klassenanalyseverfahrens, das auf den
TML-Code der Methode angewendet wird.

Die Typangaben von Signaturen sind dabei eine wichtige Informationsquelle,
deren Aussch"opfung allerdings die Subtypisierung f"ur die Basisklassen einschr"ankt,
wie in dem n"achsten Abschnitt erl"autert wird.

\section{Markierte Typen} \label{BrandedTypes}

Wenn in  TS
({\em Typed Smalltalk}) \cite{JohGraZur88}
 Variablen als Exemplare eines Typs \mbox{deklariert} werden,
kann diese Information durch einen Optimierer ausgenutzt werden, um eventuell
auftretende dynamische Methodenaufrufe durch statische Funktionsaufrufe 
zu ersetzen, denn Klassen sind in TS Typen. 
In TooL dagegen bieten die in den Signaturen  aufgef"uhrten
Typen aufgrund
der strukturellen Typisierung und dem
Subtyppolymorphismus keine 
verl"a\3liche Aussage "uber tats"achlich zur Laufzeit auftretende
Klassen.
Dies bedeutet, da\3  Typangaben in den Signaturen keine Grundlage
f"ur das statische Binden von Methoden bilden, denn Exemplare von "ubergebenen Subtypen k"onnen
eigene Methodenimplementierungen besitzen, die nicht mit der statisch
gebundenen Implementierung  "ubereinstimmen.
Demzufolge k"onnen die in den Signaturen vorhandenen Typhinweise
nicht zu Optimierungszwecken heran\-gezogen werden, und aus der Sicht des Optimierers
erscheint TooL wie eine g"anzlich untypisierte Sprache.

In Modula-3 \cite{Cardelli89} ist  das Konzept der \glqq markierten Typen\backq
({\em branded types}) vorgesehen, das  f"ur einige Typen die Subtypbeziehung zu anderen
Typen verhindert, um auszuschlie\3en, da\3 durch "Ubergabe eines
Subtypen verdeckte Felder eines abstrakten Datentypen zug"anglich
gemacht werden. Im TooL-System werden einige Basistypen wie
{\sf Int}, {\sf Fun}, {\sf Bool}, {\sf String}, und {\sf Char} als
markierte Typen angesehen, d.h. sie k"onnen mit ihren jeweiligen Klassen
gleichgesetzt werden.
Von diesen markierten Typen k"onnen keine Subtypen gebildet
werden, und sie sind durch  ihre Bezeichner
identifizierbar,  so da\3 diese Bezeichner in den Signaturen der
Methoden als Hinweise  an den Optimierer  dienen k"onnen.
Subklassen dieser Basisklassen lassen sich weiterhin definieren.

Die Einschr"ankung der 
Subtypisierung stellt keinen gro\3en Verlust
an Modellierungs\-m"achtigkeit
dar, da es fraglich ist, ob sinnvolle
Subtypen  dieser Basistypen existieren. F"ur die Klassen {\sf Int} und
{\sf Char} w"urde zum Beispiel die naheliegende,
objektorientierte Programmiertechnik der Hinzunahme von neuen Methoden
in Subklassen  ohnehin Subklassen erzeugen, die keine Subtypen
sind, denn {\sf Int} und {\sf Char} besitzen bin"are Methoden.

Ferner ist der Gedanke einer festgelegten Semantik f"ur Basistypen
durchaus begr"undbar: 
F"ur die rein objektorientierte Sprache Emerald \cite{Hutchinson87} argumentiert 
Hutchinson, da\3 eine eindeutige
Implementierung f"ur Basistypen zwingend notwendig ist, denn das restliche System
wird  mit einer
impliziten Annahme "uber die Semantik der Basistypen entwickelt, der
 die Korrektheit des Systems zugrundeliegt. Mehrdeutigkeiten
sollten bez"uglich der Semantik von Grundkonstrukten der Programmierung ausgeschlossen
sein. 


Die  Vorteile des Ansatzes der markierten Typen sind zweifach:
\begin{itemize}
\item
Keine Klassenabsch"atzungen oder speziellen Tricks sind notwendig, um die
Folgen der reinen Objektorientierung zu eliminieren; auch die 
\glqq harten N"usse\backq der Kontroll\-strukturen lassen sich damit ohne Sonderbehandlung
knacken. Ferner entfallen  die Klassen\-tests der Klassen\-absch"atzung
und die Angabe der F"alle f"ur fehl\-geschlagene Klassen\-tests. Die Duplikation
von Code f"ur gr"o\3ere monomorphe Pfade ({\em splitting}) wird unn"otig.
\item
Da  
die Information "uber Basisklassen in den Signaturen neben den Profildaten 
lediglich  eine weitere Informationsquelle darstellt, passen
die markierten Typen gut in das Gef"uge der Klassenanalyse, in der
auch noch andere M"oglichkeiten zur Verbreitung von Klasseninformation
ausgenutzt werden.

\end{itemize}

\section{Klassenanalyse}


Die Aufgabe der Klassenanalyse besteht darin, Klasseninformation durch
einen TML-Graphen zu verbreiten und eventuell neue Klasseninformation
durch einfache Inferenzen zu gewinnen. Da dabei
dynamisch gebundene Referenzen
an Methoden durch statisch gebundene Funktions\-aufrufe ersetzt und neue
Kontrollflu\3knoten eingef"uhrt werden, ist das Verfahren strenggenommen
ein Analyse- und Transformationsschritt.
Eine analysierte Methode wird an\-schlie\3end 
mit Hilfe der Beta-Reduktion weitergehend optimiert, wobei statisch
gebundene Referenzen expandiert werden k"onnen ({\em inlining}).

\subsection{Der Analyse-Algorithmus}

Der Analyse-Algorithmus soll jeweils eine  Zielmethode,
die sich aus einem Profileintrag auffinden l"a\3t, bearbeiten
und 
Methodenaufrufe durch Funktionsaufrufe ersetzen. Dabei werden
Hilfsfunktionen aufgedeckt, in denen ebenfalls Methodenaufrufe
ersetzt werden. Das Ergebnis ist eine modifizierte
Zielmethode mit allen transitiv referenzierten Hilfsfunktionen. 

In dem Analyseverfahren wird Information "uber
\glqq bekannte Empf"anger\backq durch den TML-Graphen einer Methode
gereicht und aktualisiert.
Ein {\em bekannter Empf"anger} ist ein Tupel $<$$v$,$T$$>$, wobei 
$v$ eine Variable  und $T$ eine Menge von m"oglichen Klassen ist.
Zu Beginn der Analyse einer Methode ist  die aufgezeichnete
Information
"uber die Parameter einer \mbox{Methode} ({\sf self} inklusive)
 aus den
Profildaten und der Signatur bekannt. Diese Information
stellt die Menge der {\em initialen} bekannten Empf"anger einer
Methode
dar, die um abgeleitete bekannte Empf"anger w"ahrend einer
TML-Traversierung erweitert wird.
Der Klassenanalyse-Algorithmus l"a\3t sich wie folgt beschreiben:

\begin{enumerate}
\item
Zwei Mengen von Methoden werden ben"otigt: eine {\em Agendamenge} der noch zu bearbeitenden
Methoden  und eine {\em Ergebnismenge} der analysierten Methoden. Anfangs befindet sich nur
die aufgefundene Zielmethode eines Profileintrags in der Agendamenge; die Ergebnismenge
ist leer. Der aktuelle Optimierungskandidat wird der Agendamenge entnommen.
\item
Der TML-Code des aktuellen Optimierungskandidaten wird unter Mitf"uhrung
der bekannten Empf"anger traversiert. 
Botschaften an eindeutig bekannte
Empf"anger werden durch Funktionsaufrufe ersetzt, und eventuell k"onnen 
neue bekannte Empf"anger aufgedeckt werden. Wenn eine Variable mehrere m"ogliche
Empf"angerklassen besitzt, wird ein Verzweigungspunkt in den TML-Graphen 
an der Stelle der Botschaft an die Variable eingef"ugt,
so da\3 monomorphe Zweige mit duplizierten Methodenaufrufen entstehen.
An Vereinigungspunkten im Graphen werden die Mengen der m"oglichen Klassen
f"ur die bekannten Empf"anger vereinigt. 
\item
Der TML-Code einer ersetzten Botschaft wird mit seinen initialen bekannten 
Empf"angern in die Agendamenge eingef"ugt. Diese Menge an initial
bekannten Empf"angern beinhaltet {\sf self} und m"oglicherweise weitere
Parameter, die sich  aus der Signatur der ersetzten Botschaft ergeben
k"onnen.
\item
Eine traversierte Methode kommt in die Ergebnismenge der bearbeiteten Methoden. Der n"achste
Optimierungskandidat wird  der Agendamenge entnommen, und obiges
\mbox{Verfahren}  wiederholt sich ab Punkt 2, bis die Agendamenge leer ist.

\end{enumerate}

\subsection{Ein Beispiel}

Das Traversierungsverfahren l"a\3t sich am Beispiel der Methode {\sf while}
veranschaulichen. Der TML-Graph der Methode ist wie folgt:

\begin{xple}
\begin{tabular}{l}
\k{proc}(self cond statement c)\\
  (send ``[$\:$]'' cond \k{cont}(t\_1)\\
  (send ``?'' t\_1\\
     \k{proc}(c\_3)\\
      (send ``[$\:$]'' statement \k{cont}(t\_4)\\
      (send ``while'' self cond statement \k{cont}(t\_5)\\
      (c\_3 t\_5)))\\
     \k{cont}(t\_6)\\
       (c t\_6)))\\
\end{tabular}   
\end{xple}


Beim Eintritt in den TML-Graphen sind nur die Parameter
der Methode (die initialen bekannten Empf"anger) bekannt:

\begin{xple}
\begin{tabular}{l}
  \k{proc}(self cond statement c) \\
$\rhd$ (send ``[$\:$]'' cond \k{cont}(t\_1) $\ldots$ \{$<$self,Nil$>$ $<$cond,Fun:Bool$>$  $<$statement,Fun$>$\}\\
\end{tabular}
\end{xple}

Die Klasse der Variable {\sf cond} ist der Signatur der Methode {\sf
while} entnommen 
und f"uhrt dazu, da\3 der Methodenaufruf ersetzt wird. Im
Fall der Klasse
{\sf Fun} wird diese Ersetzung implizit Beta-reduziert, weil der
markierte
Typ {\sf Fun} aufgrund
der Information "uber den Werte\-bereich einer Funktion eine Sonderstellung in der 
tats"achlichen Implementierung besitzt. Die Beta-Reduktion w"are an diesem
TML-Knoten ohnehin beim Lauf des CPS-Optimierers erfolgt. Daher werden
Methodenaufrufe  {\sf (send  ``[$\:$]'' function arguments
$\ldots$)} an die Klasse {\sf Fun} direkt in  Funktionsaufrufe der Form
{\sf (function arguments $\ldots$)} "uberf"uhrt.
Bei Ersetzungen von \mbox{Botschaften} durch Funktionen kann der Wertebereich der
Funktion, der in der Signatur aufgef"uhrt ist, Aufschlu\3 "uber die m"oglichen
Klassen der Fortsetzungsvariable der Funktion geben, sofern der Wertebereich
ein markierter Typ ist. 
Der Wertebereich der Funktion {\sf cond} ist die abstrakte Klasse {\sf Bool}; 
in dem  Klassenanalyse-Algorithmus werden die konkreten Sub\-klassen {\sf True} und
{\sf False} als Menge der m"oglichen Klassen der Variable {\sf t\_1}  eingetragen:

\begin{xple}
  \k{proc}(self cond statement c)
   (cond \k{cont}(t\_1)                
$\rhd$ (send ``?'' t\_1 \k{proc}(c\_3) $\ldots$    \{ $\ldots$ $<$t\_1,(True,False)$>$ $\ldots$ \}
\end{xple}


Die Variable {\sf t\_1} besitzt an der Stelle des Aufrufs der Botschaft
{\sf \sfrontq ?\sbackq} zwei m"ogliche Empf"angerklassen, was zu einer Einf"ugung eines
Verzweigungspunktes in monomorphe Pfade f"ur {\sf t\_1} f"uhrt:

\begin{xple}
\begin{tabular}{l}
  \k{proc}(self cond statement c)\\
   (cond \k{cont}(t\_1)\\
   (\k{lambda}(join)\\
    .  (\k{lambda}(argument)\\
    .    $|$  (classTest t\_1 ``True'' ``False''\\
    .    $|$       \k{cont}()\\
    .    $|$    $\rhd$   (send ``?'' t\_1 argument join)  \{ $\ldots$ $<$t\_1,True$>$  $\ldots$\}\\
    .    $|$       \k{cont}()\\
    .    $|$          (send ``?'' t\_1 argument join)) \{ $\ldots$ $<$t\_1,False$>$  $\ldots$\}\\
    .    $|$  \k{proc}(c\_3) $\ldots$\\
    . \k{cont}() $\ldots$\\
\end{tabular}    
\end{xple}

Da jetzt f"ur jeden der Pfade die Klasse von {\sf t\_1} eindeutig bekannt ist,
lassen sich die Methoden\-aufrufe an {\sf \sfrontq ?\sbackq} durch Funktionsaufrufe ersetzen,
und die Implementierungen der Methode {\sf \sfrontq ?\sbackq} werden in die Agendamenge aufgenommen.
Ab dem Vereinigungspunkt {\sf join} wird die Analyse mit 
dem bekannten Empf"anger {\sf $<$t\_1,(True,False)$>$} fortgesetzt, 
f"ur den die Klasseninformation
der beiden konvergierenden Pfade vereinigt wurde:


\begin{xple}
\begin{tabular}{l}
  \k{proc}(self cond statement c)\\
   (cond \k{cont}(t\_1)\\
   (\k{lambda}(join)\\
    .  (\k{lambda}(argument)\\
    .    $|$  (classtest t\_1 ``True'' ``False''\\
    .    $|$       \k{cont}()\\
    .    $|$          (True:? t\_1 argument join)\\ 
    .    $|$       \k{cont}()\\
    .    $|$          (False:? t\_1 argument join))\\
    .    $|$  \k{proc}(c\_3) $\ldots$\\
    . $\rhd$ \k{cont}() $\ldots$     \{ $\ldots$ $<$t\_1,(True,False)$>$  $\ldots$\}\\
\end{tabular}
\end{xple}



\subsection{Verbreitung von Klasseninformation}

In dem vorherigen Beispiel der Methode {\sf while} wurde an der 
Ersetzung der {\sf \sfrontq [$\:$]\sbackq}-Botschaft exemplarisch demonstriert, wie
der Wertebereich einer ersetzten Methode zu neuer Klassen\-information f"uhren kann
(in dem Beispiel f"ur die Variable {\sf t\_1}).
In der Implementierung des TooL-Optimierers werden folgende M"oglichkeiten zur 
Gewinnung und Verbreitung von Klassen\-information ausgesch"opft:

\begin{itemize}
\item
Wertebereiche von ersetzten Methoden oder
primitiven Operationen k"onnen die Klassen der durch die Fortsetzung definierten 
Variablen festlegen. Der folgende Methodenaufruf k"onnte z.B.
durch einen Funktionaufruf an eine Methode mit der Signatur
{\sf foo(:Self):Int} ersetzt werden:

\begin{xple}
\begin{tabbing}
(send ``foo'' self \k{cont}(t) $\ldots$ 
\end{tabbing}	 
\end{xple}
Hieraus l"a\3t sich inferieren, da\3 {\sf t} den markierten Typ {\sf Int}
besitzt:
\begin{xple}
\begin{tabbing}
(Object:foo self \k{cont}(t) $\ldots$  \{$\ldots$ $<$t,Int$>$ $\ldots$\}
\end{tabbing}
\end{xple}
\item
Durch Lambda-Applikationen definierte Variablen k"onnen die Klassen 
ihrer Argumente "ubernehmen, anhand der Applikation
\begin{xple}
\begin{tabbing}
(\k{lambda}(x) ( $\ldots$ ) y)     \{ $\ldots$ $<$y,OrderedCollection$>$ $\ldots$\} 
\end{tabbing}
\end{xple}
wird gefolgert, da\3 {\sf x} die Klasse {\sf OrderedCollection} besitzt.
\item
Die Variablen gebundener Fortsetzungen erhalten ihre Klassen als Vereinigung
der \mbox{Klassen} der Argumente von Aufrufen der  Fortsetzung. F"ur die 
Variable {\sf t} in dem folgenden Beispiel l"a\3t sich der bekannte Empf"anger
{\sf $<$t,(Int,OrderedCollection)$>$} bilden:
\begin{xple}
\begin{tabbing}
(\k{lambda}(join)\\
%   $\ldots$\\
    $\ldots$ (join x)   \{$\ldots$ $<$x,Int$>$ $\ldots$\}\\
    $\ldots$ (join y)   \{$\ldots$ $<$y,OrderedCollection$>$ $\ldots$\}\\
%   $\ldots$)\\
\k{cont}(t)       \{$\ldots$ $<$t,(Int,OrderedCollection)$>$ $\ldots$\}\\
    $\ldots$
\end{tabbing}	      
\end{xple}

\item
Vor der Aufnahme einer aufgedeckten Methodenimplementierung in die
Agendamenge kann neben der Signatur der Methode der Aufrufkontext
Hinweise "uber die Klassen der Formalparameter liefern. Der folgende
Methodenaufruf an {\sf self} deckt nicht nur eine weitere
Methodenimplementierung auf, sondern liefert Information
"uber die Klassen der Parameter:
\begin{xple}
\begin{tabbing}
(send ``averageBy'' self 100 \k{cont}(t\_1) $\ldots$ \{$\ldots$ $<$self,Statistics$>$ $\ldots$\}\\
\end{tabbing}	      
\end{xple}
So kann zum Beispiel  die Information aus der Signatur von
{\sf averageBy(factor :Number):Void}  um den Hinweis erweitert werden,
da\3 f"ur diesen Aufrufkontext immer ein Literal der Sorte {\sf Int}
({\sf 100}) f"ur den  Parameter {\sf factor} vorliegt:
\begin{xple}
\begin{tabbing}
\k{proc}(self factor) $\ldots$ \{$<$self,Statistics$>$$<$factor,Int$>$\}\\
\end{tabbing}	      
\end{xple}




\end{itemize}
 
\subsection{Optimierung durch Beta-Reduktion}
\label{Y-Format}

Das Ergebnis der Klassenanalyse ist eine Menge von
allen transitiv "uber {\sf self}-Botschaften referenzierten Methoden und 
sonstigen bekannten Referenzen, etwa aus Botschaften an Para\-meter der Methode.
In den R"umpfen dieser Methoden sind
die dynamischen Referenzen an eindeutig bekannte Empf"anger durch
statische Funktionsaufrufe ersetzt worden. Da implizite, m"oglicher\-weise
wechselseitige Rekursionen dadurch in explizite rekursive Referenzen 
umgewandelt worden sind, werden alle aufgedeckten Methoden in einem 
Y-Primitiv zusammen\-gefa\3t.
Das Y-Konstrukt der Methode {\sf while}, in dessen Rumpf die Methoden {\sf True:?} und
{\sf False:?} ersetzt wurden, ist:

\begin{xple}
\begin{tabbing}
 \k{proc}(c\_0)\\
  (Y \k{proc}(c\_1 False:? True:? Nil:while)\\
  (c\_1\\
    \k{cont}()\\
      (c\_0 Nil:while)\\
    \k{proc}(self ifTrue c\_2)            ; {\rm Rumpf von} False:?\\
      (c\_2 ok)\\
    \k{proc}(self ifTrue c\_3)            ; {\rm Rumpf von} True:?\\
      (ifTrue \k{cont}(t)\\
      (c\_3 t))\\
    \k{proc}(self cond statement c\_4)    ; {\rm Rumpf von} Nil:while\\
      (cond \k{cont}(t)\\
         $\ldots$\\
             $\ldots$  )))))))\\
\end{tabbing}	      
\end{xple}


Die Beta-Reduktion erfolgt auf diesem Y-Primitiv und expandiert kurze
Methoden
an  ihren Aufruforten. Das Ergebnis dieser
Optimierungsphase ist eine Methode, die auf bestimmte
Empf"anger- und Argumentklassen spezialisiert ist.
Die spezialisierte Methode wird an\-schlie\3end "ubersetzt und ergibt damit
einen Funktionsabschlu\3 mit allen referenzierten 
Hilfsfunktionen, der in den Methodencache eingetragen wird.
Ein ungel"ostes Problem ist hierbei, da\3 bei der Optimierung
mehrerer Methoden die gleichen
Hilfsfunktionen wiederholt "ubersetzt und  angelegt werden.
Eleganter w"are
der Einsatz eines Caches von Hilfsfunktionen.

\subsection{Aufruf einer spezialisierten Methode}

Eine spezialisierte Methode kann m"oglicherweise besondere Argumentklassen
erwarten, denn der Optimierer kann  Botschaften an
Argumente durch statische Funktionsaufrufe ersetzt haben.
In einem solchen Fall ist das Ergebnis der Optimierungsphase nicht
nur auf die Empf"angerklasse, sondern auch auf eine oder mehrere 
der Argumentklassen zugeschnitten.

Wenn Ersetzungen aufgrund von Profildaten und nicht von
Signaturangaben erfolgten, k"onnen durchaus  unerwartete Argumentklassen
auftreten. Da der Methodenaufruf ausschlie\3lich von der
Empf"angerklasse und dem Selektor bestimmt wird, mu\3 zur Laufzeit
gepr"uft werden, ob die Argumentklassen mit denen "ubereinstimmen,
f"ur die eine Methode spezialisiert wurde. Sind alle Argumente wie erwartet,
kann die optimierte Version der Methode auf\-gerufen werden.
Im anderen Fall mu\3 die urspr"ungliche (nicht-spezialisierte) Version ablaufen.
An dem folgenden Beispiel ist dieser Mechanismus erkennbar:

\begin{xple} 
\k{proc}(self aStream c\_1)  \nopagebreak
\begin{tabular}{ll} 
 (\k{lambda}(specializedLabel)               &  \\
   .  (classTest aStream ``File''        &  \\
   .      \k{cont}()                             &  \\
   .        (specializedLabel)               &  \\
   .      \k{cont}()                             &  \\
   .        ($<$oid 0x2C79FC$>$ self aStream c\_1) & \\
   . \k{cont}()                                  &  \\
        $\ldots$                              &   \\
	       (File:put aStream ch cont(t) & \\
	         $\ldots$))) &\\
\end{tabular}	 
\end{xple} 

Der Code entstammt der 
Methode {\sf printOn}, die auf die Empf"angerklasse {\sf Set} und
die Klasse {\sf File} f"ur das erste Argument {\sf aStream}
zugeschnitten ist. Der Aufruf der urspr"unglichen Methode erfolgt "uber
eine explizite Objektreferenz ($<${\sf oid 0x2C79FC}$>$) an den Bytecode.


\section{Elimination von Endrekursion}

Der im vorherigen Abschnitt beschriebene Algorithmus der Klassenanalyse und der 
Beta-Reduktionsmechanismus erweisen sich vor allem durch die Einf"uhrung
der markierten Typen als wirkungsvolle Optimierungsstrategie.
Eine wesentliche Optimierungstechnik, um die der in \cite{Kira94}
beschriebene TML-Optimierer erweitert wurde, ist die "Uberf"uhrung von
Endrekursionen in Iterationen. Diese ist n"otig, um die Leistung von explizit codierten
Kontrollstrukturen zu erreichen, denn endrekursive Schleifen besitzen zwei
Nachteile:

\begin{enumerate}
\item
Funktionsaufrufe sind erheblich langsamer als einfache Spr"unge.
Dies gilt sowohl f"ur virtuelle Maschinen wie die TVM, in der ein
{\tt call}-Opcode interpretiert wird, als auch f"ur reale Prozessoren.
Gerade Schleifen z"ahlen zu den h"aufig ausgef"uhrten Teilen eines Programms
und sollten nicht durch teure Kontrollflu\3mechanismen zus"atzlich
verlangsamt werden.
\item
Die Speicheranforderungen
einer rekursiven {\sf while}- oder {\sf for}-Schleife mit einer gro\3en
Iterationstiefe sind
unn"otig hoch, da alle Aktivierungssegmente bis zum Rekursionsende im 
 Keller gespeichert werden m"ussen. Endlose Ereignisschleifen
 sind mit endrekursiven Kontrollstrukturen
unm"oglich\footnote{TL besitzt Schleifenkonstrukte, die explizit im AST in
Spr"unge "ubersetzt werden. Daher stellt der Einsatz von TL-Programmen
als Threads oder Ereignisschleifen kein Problem dar.}.
\end{enumerate}


Aus diesen Gr"unden sehen andere rein objektorientierte Sprachen
fest verdrahtete Schleifen oder eingebaute Schleifenprimitive vor.
In SELF  wird
 neben dem dynamischen
Methoden\-aufruf und der Vererbung ein Schleifenprimitiv als Grundbaustein
der reinen Objekt\-orientierung angesehen \cite{Chambers92}. Da TooL keine derartige
Konstrukte vorsieht, ist es umso entscheidender, da\3 Endrekursion
eliminiert wird.

Der im folgenden dargestellte Ansatz unterscheidet sich von dem in
\cite{Steele78}  beschriebenen Verfahren. Steele analysiert
Funktionen daraufhin, ob sie ihre Definitionsumgebung "uber\-leben
k"onnen (etwa als R"uckgabewert einer anderen Funktion).
Sollte dies nicht der Fall sein, werden alle Aufrufe an diese
Funktion einfach in Spr"unge "ubersetzt, sofern keine der Aufrufe in
anderen Aufrufen geschachtelt ist.
 Wechselseitige Rekursionen
brauchen auf \mbox{diese} Weise  keine besondere Behandlung und k"onnen 
ohne weiteres  auf Iterationen abgebildet werden. Die in dieser Arbeit
verfolgte Methode kommt ohne obige Analyse aus, und 
 auch endrekursive Funktionen, die ihre Definitionsumgebung
"uberleben, k"onnen optimiert werden. Allerdings entf"allt die M"oglichkeit,
wechselseitige Rekursionen auf einfache Art zu optimieren.


Die wesentlichen Aspekte des Ansatzes dieser Arbeit zur  "Uberf"uhrung von
Endrekursionen in Iterationen
sind: 
\begin{itemize}
\item
Erkennung von Endrekursion:
Endrekursionen sind nach einem Reduktionslauf daran erkennbar,
da\3 alle Fortsetzungen der rekursiven Aufrufe  direkt die
R"ucksprungfortsetzung anspringen. 

\item
Erweiterung von TML um Fortsetzungen
mit mehr als einem Argument:
Die Zwischensprache TML wird derart erweitert, da\3
Fortsetzungen  zwei oder mehr Argumente "ubernehmen k"onnen,
wenn der Aufruf der Fortsetzung explizit erfolgt. F"ur implizit auf\-gerufene
anonyme Fortsetzungen (Lambda-Fortsetzungen) ergeben mehr Argu\-mente keinen Sinn,
da diese Fortsetzungen nur ein Ergebnis der letzten Berechnung binden.
In der virtuellen Maschine wird beim Sprung an eine Fortsetzung der aktuelle
Keller\-rahmen von "uberfl"ussigen Zwischenergebnissen durch eine Anpassungsoperation
bereinigt. Im Fall eines Sprungs an eine Fortsetzung mit mehreren Argumenten
m"ussen in der TVM einige der oberen Keller-Eintr"age "uber diese Anpassungsoperation hinaus
als Argumente an die Fortsetzung erhalten bleiben.
Dieses ist in Abbildung \ref{adjp} dargestellt.



\item
Entfernen von unver"anderlichen Argumenten:
Wenn einige Parameter einer End\-rekursion unver"andert an alle endrekursiven
Aufrufe "ubergeben werden,  k"onnen \mbox{diese} Para\-meter aus dem Kopf und den Aufrufen der
endrekursiven Funktion entfernt werden. Dadurch werden Schleifen
vereinfacht und beschleunigt, weil die  redundante Auswertung der Argumente nicht mehr
stattfindet.
Zus"atzlich beg"unstigt dieses die 
Integration von Funktionsparametern, da weniger Referenzen auf den Parameter
bestehen. Auf \mbox{diese} Weise kann zum Beispiel
eine {\sf while}-Schleife mit ihren Block-Argumenten
vollst"andig vereinigt werden, und das Anlegen der Bl"ocke f"ur die
Bedingung {\sf cond}
und den Schleifen\-rumpf {\sf statement} entf"allt.
\end{itemize}



\begin{figure}[tbph]
\begin{center}
\leavevmode
\epsfxsize=11cm
\epsffile{stacks.eps}
\end{center}
\caption{Kellerrahmen vor und nach einem Sprung an eine Fortsetzung mit 
         mehreren Argumenten}
\label{adjp}
\end{figure}


Gem"a\3 dieser "Uberlegungen wird der TML-Optimierer um einen
Mechanismus erweitert, der
durch folgende Transformation 
endrekursive Funktionen in nichtrekursive Funktionen "uberf"uhrt, in
der die urspr"ungliche Funktion als rekursive Fortsetzung auftritt:

\begin{xple}
\begin{tabular}{lll}

                     &    &\k{proc}(\pvec{V} ce cc)\\
                     &    &  (Y \k{proc}(c recurse)\\
                     &    &  (c\\
\k{proc}(\pvec{v} ce cc) & &     \k{cont}()\\
   ( $\ldots$ )  &  $\;\;\longrightarrow$       &       (recurse \pvec{V})\\
                     &    &     \k{cont}(\pvec{v})\\
                     &    &       ( $\ldots ) [\mbox{\sf recurse}/\mbox{\sf tailrecursive call}]$ \\
\end{tabular}       
\end{xple}

Nach der Transformation sind in dem Rumpf der urspr"unglichen Funktion
alle rekursiven Funktionsaufrufe durch Aufrufe an die eingef"uhrte
Fortsetzung {\sf recurse} substituiert. Aufrufe an Fortsetzungen werden
bei der Codegenerierung (siehe
Kapitel \ref{CodeGen}) in Spr"unge "ubersetzt. Als Beispiel f"ur diese
Transformation soll die Optimierung einer endrekursiven
Fakult"atfunktion dienen:


\begin{xple}
\begin{tabular}{llll}
                                      &&\k{proc}(N ACC e c\_1) &\\
                                      &&  (Y \k{proc}(c\_2 recurse) &; (1)\\
                                      &&  (c\_2                     &\\
                                      &&    \k{cont}()              &\\
                                      &&     (recurse N ACC)        &; (2)\\
\k{proc}(n acc e c\_1)                &&    \k{cont}(n acc)                     &; (3)\\
   (== n 1                            &$\longrightarrow$&     (== n 1  &\\
      \k{cont}()                      &&       \k{cont}() &\\
       (c\_1 acc)                     &&        (c\_1 acc) &\\        
      \k{cont}()                      &&       \k{cont}() &\\
       (- n 1 e \k{cont}(t\_1)        &&        (- n 1 e \k{cont}(t\_1) &\\
       (* n acc e \k{cont}(t\_2)      &&        (* n acc e \k{cont}(t\_2) &\\
       (fact t\_1 t\_2 e c\_1))))     &&        (recurse t\_1 t\_2))))))     &; (4) \\
\end{tabular}
\end{xple}

An diesem TML-Code sind  folgende Punkte erkennbar:

\begin{enumerate}
\item
Aus einer ehemals rekursiven Funktion wurde eine nicht-rekursive Funktion.
Das Y-Primitiv im Rumpf definiert lediglich eine rekursive Fortsetzung 
{\sf recurse},
die in eine Sprungmarke "ubersetzt wird.
\item
An den ersten Aufruf der  Fakult"atfortsetzung werden  zwei
Argumente "ubergeben: die eingef"uhrten Variablen {\sf N}
und {\sf ACC}.
\item 
Die  rekursive Fortsetzung besitzt zwei Parameter: {\sf n} und {\sf acc}.
\item
In der Kellermaschine TVM erfordert der Aufruf der Fortsetzung
die in Abbildung \ref{adjp}
angedeutete Anpassung des Kellerrahmens, um Zwischenergebnisse
vom Keller zu entfernen und die aktuellen Parameter {\sf t\_1} und {\sf
t\_2} zu erhalten.
\end{enumerate}


Die n"achste Transformation eliminiert die Parameter aus der
Fortsetzung, die an jeder Aufrufstelle unver"andert weitergegeben
werden:

\begin{xple}
\begin{tabular}{lll}
\k{proc}(\pvec{V} ce cc)   &                      &\k{proc}(\pvec{V} ce cc)  \\
 (Y \k{proc}(c recurse)    &                      & (Y \k{proc}(c recurse)\\
 (c                        &                      & (c\\
     \k{cont}()            &                      &     \k{cont}()\\
       (recurse \pvec{V})  &   $\longrightarrow$  &       (recurse $V_{1} \ldots V_{i-1} V_{i+1} \ldots V_{n}$)\\
     \k{cont}(\pvec{v})    &                      &     \k{cont}($v_{1} \ldots v_{i-1} v_{i+1} \ldots v_{n}$)\\
       ( $\ldots $         &                      &       ( $\ldots $ \\
           (recurse $\ldots v_{i} \ldots $)) &    &           (recurse $\ldots \not\!v_{i} \ldots$ )) $[V_{i}/v_{i}]$ \\

	   
\end{tabular}       
\end{xple}


Die Anwendung der beiden Transformationen auf die {\sf while}-Schleife
liefert folgendes Ergebnis:

%                                                                   \k{proc}(self COND STATEMENT c\_1) 
\begin{xple}
\begin{tabular}{llll}
                                              && \k{proc}(self COND STATEMENT c\_1)&\\ 
                                              && (Y \k{proc}(c\_2 recurse)              &\\
                                              &&  (c\_2                                 &\\
                                              &&   recurse                                &; (1)\\
\k{proc}(self cond statement c\_1)            &&   \k{cont}()                        &\\
  (cond \k{cont}(t\_1)                        &&      (COND  \k{cont}(t\_1)            &; (2)\\
  (classTest t\_1 ``True'' ``False''&$\longrightarrow$&      (== t\_1 true false &; (3)\\
      \k{cont}()                              &&        \k{cont}()                           &\\
         (statement \k{cont}(t\_2)            &&           (STATEMENT \k{cont}(t\_2)&\\
         (Int:while self cond statement c\_1))&&           (recurse))     &; (4)\\
      \k{cont}()                              &&        \k{cont}()         &\\
         (c\_1 ok))))                         &&           (c\_1 ok))))) &\\
\end{tabular}	
\end{xple}


Zu den Punkten einige Erl"auterungen:
\begin{enumerate}
\item
Die Fortsetzung des Y-Primitivs dient als Einstieg in die Schleife, die an
dieser Stelle angesprungen wird.
\item
Die Parameter an die "ursprungliche rekursive Funktion konnten aus
der rekursiven Fortsetzung herausgezogen werden, da diese an jedem
Aufruf unver"andert weitergegeben werden. In dem Rumpf stehen statt
der urspr"unglichen Parameter die substituierten, neu eingef"uhrten Parameter {\sf COND}
und {\sf STATEMENT}.
\item
Das  {\sf classTest}-Primitiv mit den Klassen {\sf True} und
{\sf False} wurde mit Hilfe einer Meta-Evaluationsfunktion
(Faltfunktion) durch das TML-Konditional ersetzt.
\item
Der Sprung erfolgt in diesem Beispiel ohne Parameter.
\end{enumerate}

Der TML-Graph dieser transformierten {\sf while}-Schleife entspricht
dem handcodierten TML-Baum, der in dem TL-"Ubersetzer f"ur das
Schl"usselwort {\sf while} erzeugt wird.  Die Optimierungs\-mechanismen
dieser Arbeit haben  eine rein objektorientiert definierte Methode
in Code "uberf"uhrt, der "aquivalent zu einer herk"ommlichen Kontrollstruktur
einer imperativen \mbox{Sprache} ist. Endrekursiv formulierte
Iteratoren wie {\sf some}, {\sf every} und auch {\sf for} lassen sich
auf die \mbox{gleiche} Weise optimieren. Weitere Reduktions-
und Expansionsl"aufe k"onnen Kontrollstrukturen vollst"andig
an ihren Aufruforten integrieren. Als Beispiel soll
folgendes Vorkommnis einer {\sf while}-Schleife in der Methode
{\sf printOn} der Klasse {\sf Int} betrachtet werden:

\begin{xple}
\begin{tabbing}
$\ldots$\\
  while(\{divisor $<=$ value\}, \{divisor := divisor * 10\}), \\
$\ldots$
\end{tabbing}	      
\end{xple}

Die Klassenanalyse transformiert den TML-Code dieses
TooL-Fragments in einen TML-Baum, in dem die
Botschaft {\sf while} an den Empf"anger
{\sf self} durch einen Funktionsaufruf {\sf Int:while} ersetzt ist:

\begin{xple}
\begin{tabular}{l}
$\ldots$ \\
  (Int:while self\\
    \k{proc}(c\_1)\\
      ([$\:$] divisor 0 \k{cont}(t\_2)\\
      ([$\:$] value 0 \k{cont}(t\_3)\\
      (send ``$<=$'' t\_2 t\_3 \k{cont}(t\_4)\\
      (c\_1 t\_4))))\\
    \k{proc}(c\_5)\\
      ([$\:$] divisor 0 \k{cont}(t\_6)\\
      (send ``*'' t\_6 10 \k{cont}(t\_7)\\
      ([$\:$]:= divisor 0 t\_7 \k{cont}()\\
      (c\_2 t\_7))))\\
    \k{cont}(t\_8)\\
     $\ldots$
\end{tabular}	      
\end{xple}

Durch Beta-Reduktion kann die {\sf while}-Methode optimiert und
an der Aufrufstelle integriert werden. Das Ergebnis ist wie folgt:


\begin{xple}
\begin{tabular}{l}
$\ldots$ \\
  (Y \k{proc}(c\_10 recurse)\\
  (c\_10\\
   recurse\\
   \k{cont}()\\
     ([$\:$] divisor 0 \k{cont}(t\_2)\\
     ([$\:$] value 0 \k{cont}(t\_3)\\
     (send ``$<=$'' t\_2 t\_3 \k{cont}(t\_4)\\
     (== t\_4 true false\\
       \k{cont}()\\
         ([$\:$] divisor 0 \k{cont}(t\_6)\\
         (send ``*'' t\_6 10 \k{cont}(t\_7)\\
         ([$\:$]:= divisor 0 t\_7 recurse)))\\
       \k{cont}()\\
         (c\_11 ok)))))))\\
      $\ldots$
\end{tabular}	      
\end{xple}




Die in diesem Kapitel vorgestellten Optimierungstechniken
eliminieren die Nachteile der \mbox{reinen} Objektorientierung,
insbesondere f"ur Kontrollstrukturen.
Die Umformung von End\-rekursionen in Spr"unge stellt eine Notwendigkeit 
f"ur jeden ernsthaften Einsatz von TooL-Programmen dar, und
erzeugt auf allgemeine Weise ohne hartverdrahtete Information
(etwa "uber die Selektoren {\sf for} und {\sf while}) aus relativ
ineffizienten Methoden schnelle und platz\-sparende iterative Schleifen.
Ohne die Verwendung
der markierten Typen w"are die Optimierung der Kontrollstrukturen
wesentlich komplizierter unter Verwendung von Klassenabsch"atzung ausgefallen.

 

Mit Hilfe der Profilinformation kann ein
 Methodencache aufgebaut werden, in dem \mbox{speziell} auf die Empf"angerklasse
 optimierte Methodenversionen eingetragen werden. In einzelnen
 F"allen sind diese Versionen zus"atzlich noch auf die
 h"aufigsten Klassen der ersten beiden Argumente eingestellt, und somit
 wurde die Technik der empf"angerspezifischen "Ubersetzung  auf
 eine empf"anger- und argumentspezifische "Ubersetzung  erweitert.

Die rein objektorientierte Natur der Sprache bleibt erhalten.
Auch von den Basisklassen {\sf Int, Char, Bool, Fun} und
{\sf String} k"onnen Subklassen abgeleitet werden. Der
Methodencache wird zusammen mit der Profiltabelle
gel"oscht, wenn Klassen redefiniert oder neue
Klassen in den Klassengraphen eingef"ugt werden.
Damit spiegeln "Anderungen im Klassensystem sich ohne Einschr"ankungen
im Laufzeitverhalten wider.

