108 lines
5.6 KiB
TeX
108 lines
5.6 KiB
TeX
\section{Bytecodegenerierung}
|
|
Die Bytecodegenerierung ist letztendlich eine zweistufige Transformation:
|
|
|
|
\vspace{20px}
|
|
\texttt{Getypter AST -> [ClassFile] -> [[Word8]]}
|
|
\vspace{20px}
|
|
|
|
Vom AST, der bereits den Typcheck durchlaufen hat, wird zunächst eine Abbildung in die einzelnen ClassFiles vorgenommen.
|
|
Diese ClassFiles werden anschließend in deren Byte-Repräsentation serialisiert.
|
|
|
|
\subsection{Codegenerierung}
|
|
Für die erste der beiden Transformationen (\texttt{Getypter AST -> [ClassFile]}) werden die Konzepte der ``Builder'' und ``Assembler'' eingeführt.
|
|
Sie sind wie folgt definiert:
|
|
|
|
\vspace{20px}
|
|
\begin{lstlisting}[language=haskell]
|
|
type ClassFileBuilder a = a -> ClassFile -> ClassFile
|
|
type Assembler a = ([ConstantInfo], [Operation], [String]) -> a
|
|
-> ([ConstantInfo], [Operation], [String])
|
|
\end{lstlisting}
|
|
\vspace{20px}
|
|
|
|
Die Idee hinter beiden ist, dass sie jeweils zwei Inputs haben, wobei der Rückgabewert immer den gleichen Typ hat wie einer der Inputs.
|
|
Das erlaubt es, eine Faltung durchzuführen. Ein ClassFileBuilder z.B bekommt als ersten Parameter den AST,
|
|
und als zweiten Parameter (und Rückgabewert) eine ClassFile. Soll nun eine Klasse gebaut werden,
|
|
wird der ClassFileBuilder mit dem AST und einer leeren ClassFile aufgerufen.
|
|
Der Zustand dieser anfangs leeren ClassFile wird durch alle folgenden Builder/Assembler durchgeschleift, was es erlaubt,
|
|
nach und nach kleinere Transformationen auf sie anzuwenden. Der Nutzer ruft beispielsweise die Funktion \texttt{classBuilder} auf.
|
|
Diese wendet nach und nach folgende Transformationen an:
|
|
|
|
\vspace{20px}
|
|
\begin{enumerate}
|
|
\item Allen Konstruktoren werden Initialisierer aller Felder hinzugefügt
|
|
\item Für jedes Feld der Klasse wird ein Eintrag im Konstantenpool \& der Classfile erstellt
|
|
\item Für jede Methode wird ein Eintrag im Konstantenpool \& der Classfile erstellt
|
|
\item Allen Methoden wird der zugehörige Bytecode erstellt und zugewiesen
|
|
\item Allen Konstruktoren wird der zugehörige Bytecode erstellt und zugewiesen
|
|
\end{enumerate}
|
|
\vspace{20px}
|
|
|
|
Die Unterteilung von Deklaration der Methoden/Konstruktoren und Bytecodeerzeugung ist deswegen notwendig,
|
|
weil der Code einer Methode auch eine andere, erst nachher deklarierte Methode aufrufen kann.
|
|
Nach dem Hinzufügen der Deklarationen sind alle Methoden/Konstruktoren der Klasse bekannt.
|
|
Wie oben beschrieben wird auch hier der Zustand über alle Faltungen mitgenommen.
|
|
Jeder Schritt hat Zugriff auf alle Daten, die aus dem vorherigen Schritt bleiben. Sukzessive wird eine korrekte ClassFile aufgebaut.
|
|
Besonders interessant sind hierbei die beiden letzten Schritte. Dort wird das Verhalten jeder einzelnen Methode/Konstruktor in Bytecode übersetzt.
|
|
In diesem Schritt werden zusätzlich zu den \texttt{Buildern} noch die \texttt{Assembler} verwendet (Definition siehe oben.).
|
|
Die Assembler funktionieren ähnlich wie die Builder, arbeiten allerdings nicht auf einer ClassFile, sondern auf dem Inhalt einer Methode;
|
|
Sie verarbeiten jeweils ein Tupel der Form:
|
|
|
|
\vspace{20px}
|
|
\texttt{([ConstantInfo], [Operation], [String])}
|
|
\vspace{20px}
|
|
|
|
Dieses repräsentiert:
|
|
|
|
\vspace{20px}
|
|
\texttt{(Konstantenpool, Bytecode, Lokale Variablen)}
|
|
\vspace{20px}
|
|
|
|
In der Praxis werden meist nur Bytecode und Konstanten hinzugefügt. Prinzipiell können Assembler auch Code/Konstanten entfernen oder modifizieren.
|
|
Als Beispiel dient hier der Assembler \texttt{assembleExpression}:
|
|
|
|
\vspace{20px}
|
|
\begin{lstlisting}[language=haskell]
|
|
assembleExpression (constants, ops, lvars) (TypedExpression _ NullLiteral)
|
|
= (constants, ops ++ [Opaconst_null], lvars)
|
|
\end{lstlisting}
|
|
\vspace{20px}
|
|
|
|
Hier werden die Konstanten und lokalen Variablen des Inputs nicht berührt, dem Bytecode wird lediglich die Operation \texttt{aconst\_null} hinzugefügt.
|
|
Damit ist das Verhalten des gematchten Inputs - eines Nullliterals - abgebildet.
|
|
Die Assembler rufen sich teilweise rekursiv selbst auf, da ja auch der AST verschachteltes Verhalten abbilden kann.
|
|
Der Startpunkt für die Assembly einer Methode ist der Builder \texttt{methodAssembler}. Dieser entspricht Schritt 3 in der obigen Übersicht.
|
|
|
|
\subsection{Serialisierung}
|
|
Damit Bytecode generiert werden kann, braucht es Strukturen, die die Daten halten, die letztendlich serialisiert werden.
|
|
Die JVM erwartet den kompilierten Code in handliche Pakete verpackt.
|
|
Die Struktur dieser Pakete ist \href{https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html}{hier dokumentiert}.
|
|
Jede Struktur, die in dieser übergreifenden Class File vorkommt, haben wir in Haskell abgebildet.
|
|
Es gibt z.B die Struktur "ClassFile", die wiederum weitere Strukturen wie z.B Informationen über Felder oder Methoden der Klasse beinhaltet.
|
|
Alle diese Strukturen implementieren folgende TypeClass:
|
|
|
|
\vspace{20px}
|
|
\begin{lstlisting}[language=haskell]
|
|
class Serializable a where
|
|
serialize :: a -> [Word8]
|
|
\end{lstlisting}
|
|
\vspace{20px}
|
|
|
|
Hier ist ein Beispiel anhand der Serialisierung der einzelnen Operationen:
|
|
|
|
\vspace{20px}
|
|
\begin{lstlisting}[language=haskell]
|
|
instance Serializable Operation where
|
|
serialize Opiadd = [0x60]
|
|
serialize Opisub = [0x64]
|
|
serialize Opimul = [0x68]
|
|
...
|
|
serialize (Opgetfield index) = 0xB4 : unpackWord16 index
|
|
\end{lstlisting}
|
|
\vspace{20px}
|
|
|
|
Die Struktur ClassFile ruft für deren Kinder rekursiv diese \texttt{serialize} Funktion auf und konkateniert die Ergebnisse.
|
|
Am Ende bleibt eine flache Word8-Liste übrig, die Serialisierung ist damit abgeschlossen.
|
|
Da der Typecheck sicherstellt, dass alle referenzierten Methoden/Felder gültig sind,
|
|
kann die Übersetzung der einzelnen Klassen voneinander unabhängig geschehen.
|