Effiziente Virtuelle Maschinen für funktionale Programmiersprachen Xavier Leroy, The ZINC experiment: an economical implementation of the ML language.

Slides:



Advertisements
Ähnliche Präsentationen
C Sharp (C#) Martin Saternus Senior Student Partner
Advertisements

Algorithmentheorie 08 – Dynamische Programmierung (1)
Programmierung 1 - Repetitorium
CPI Der einzelne Befehl braucht immer noch 5 Zyklen (stimmt nicht ganz, einige brauchen weniger!) Was verbessert wird, ist der Durchsatz = #Befehle /
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Programmierung 1 - Repetitorium
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
10. Grundlagen imperativer Programmiersprachen
der Universität Oldenburg
der Universität Oldenburg
Kapitel 4 Syntaktische Analyse: LR Parsing.
Parser generieren Yet Another Compiler – Compiler YACC.
Java: Objektorientierte Programmierung
Java: Dynamische Datentypen
Sortierverfahren Richard Göbel.
Java: Grundlagen der Sprache
Java: Grundlagen der Objektorientierung
Objekte werden als Adressen (Referenzen) übergeben. Dies führt manchmal zu unerwarteten Ergebnissen...
Polymorphie (Vielgestaltigkeit)
Polymorphie (Vielgestaltigkeit)
Dynamischer Speicher. In einer Funktion wird z.B. mit der Deklaration int i; Speicher auf dem sogenannten Stack reserviert. Wenn die Funktion verlassen.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (03 – Verschiedene Algorithmen für dasselbe Problem) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Tobias Lauer.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (03 – Verschiedene Algorithmen für dasselbe Problem) Prof. Dr. Th. Ottmann.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (03 – Verschiedene Algorithmen für dasselbe Problem) Prof. Dr. Th. Ottmann.
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Kapitel 7 Claudio Moraga, Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
Praxis-Repetitorium JAVA zusätzliche, ergänzende Lehrveranstaltung
Zusammenfassung Vorwoche
PKJ 2005/1 Stefan Dissmann Zusammenfassung Vorwoche Methoden sind mit einem Namen versehene Programmabschnitte besitzen Rückgabetyp, Namen, Parameterliste.
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
DVG Ablaufsteuerung
DVG Klassen und Objekte
Stacks Referat im Fach Basisinformationstechnologien von Venelina Koleva.
Visualisierung funktionaler Programme
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
Javakurs FSS 2012 Lehrstuhl Stuckenschmidt
Mark & Sweep Seminar Softwareentwicklung: Garbage Collection Eva Schartner.
Java Garbage Collection Angelika Kusel, Überblick Was ist Garbage Collection? Vor- und Nachteile von GC GC-Algorithmen/Verfahren Java Garbage.
Effiziente Algorithmen
Effiziente Algorithmen Hartmut Klauck Universität Frankfurt SS
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Information und Kommunikation Hartmut Klauck Universität Frankfurt SS
Polynome und schnelle Fourier-Transformation
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Einführung in die Programmiersprache C 4
Vom Umgang mit Daten. public void myProgram() { int[] saeulenWerte = new int[world.getSizeX()]; for (int i = 0; i < saeulenWerte.length; i++) { saeulenWerte[i]
Institut für Wirtschaftsinformatik – Software Engineering, JKU Linz 1 Algorithmen und Datenstrukturen SS 2005 Mag.Th. Hilpold u. Dr. A.Stritzinger Institut.
Algorithmen und Datenstrukturen Übungsmodul 8
Die Idee hinter Copying Garbage Collection (1) Aufteilung des Heaps in zwei Teile: To-Space und From-Space Nutzung eines Teiles durch das Programm Ist.
Agenda für heute, 12. Mai, 2005 ProzedurenProzeduren Funktionsprozeduren Prozedurparameter Lokale und globale Variablen Datentypen: Ordinaltypen.
Programmieren in C Grundlagen C 2
PHP: Operatoren und Kontrollstrukturen
Polymorphie (Vielgestaltigkeit). Wenn eine Methode, wie z.B. print für verschiedene Programmteile steht (und z.B. einmal Objekte verschiedener Klassen.
1 Eine rationale Dekonstruktion von Landin’s SECD-Maschine Betreuerin: Prof. Dr. Rita Loogen Bearbeiter: Dong Liang.
Einführung in Java PING e.V. Weiterbildung Andreas Rossbacher 24. März 2005.
Funktionen. Aufgabe : Eingabe zweier Zahlen ---> Minimum bestimmen Dann nochmals Eingabe zweier Zahlen ---> Minimum bestimmen.
Pointer. Grundsätzliches: Im Arbeitsspeicher werden Daten gespeichert. Um auf die Daten eindeutig zugreifen zu können, werden diesen Daten Adressen zugeordnet.
Funktionen, Felder und Parameter- übergabe. Funktionsaufruf mit Feld als Parameter: Parameter = Name des Feldes.
Dr. Wolfram Amme, Semantik funktionaler Programme, Informatik II, FSU Jena, SS Semantik funktionaler Programme.
Tutorium Software-Engineering SS14 Florian Manghofer.
Funktionen (Zweck und Eigenschaften) Funktionen sind Unterprogramme, die einen bestimmten Zweck erfüllen Sie zerlegen Probleme in kleine, abgeschlossene.
Dr. Wolfram Amme, Automatische Speicherverwaltung, Informatik II, FSU Jena, SS Automatische Speicherverwaltung.
Implementieren von Klassen
 Präsentation transkript:

Effiziente Virtuelle Maschinen für funktionale Programmiersprachen Xavier Leroy, The ZINC experiment: an economical implementation of the ML language. Technical report 117, INRIA, 1990

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Motivation Wie können Programme in funktionalen Sprachen effizient und plattformunabhängig ausgeführt werden ? native Code Compiler hohe Ausführungsgeschwindigkeit Programmdarstellung ist plattformabhängig Compiler müssen für jede Ziel-Plattform neu entwickelt werden Virtuelle Maschine VM selbst ist plattformabhängig, aber leicht portierbar Bytecode ist plattformunabhängig Overhead der VM kann minimiert werden

Eigenschaften funktionaler Sprachen Abstraktion Curried functions: val f = fn a => fn b => a+b Unary functions: val f = fn (a,b) => a+b N-ary functions ? Applikation mit Unterversorgung von Argumenten mit Überversorgung von Argumenten Schleifen werde durch rekursive Funktionen dargestellt.

N-ary functions n-ary functions sind Funktionen mit mehr als einem Argument. Bei der Ausführung werden diese zurückübersetzt in einfachere Funktionen. Es gibt dazu zwei Möglichkeiten fun f (a b) = a+b als unary functions mit Argumenten-Tupel fun f (a,b) = a+b oder als curried functions val f = fn a => fn b => a+b

Vergleich von Unary / curried functions Nachteil bei Argumenten-Tupel: Allokation des Argumenten-Tupel auf dem Heap bei jedem Aufruf Vorteil von curried functions: Curried functions sind bei partieller Anwendung verwendbar: val add = fn a => fn b => a+b map (add 5) [1,2,3] Partielle Applikation ist bei Argumenten-Tupel nicht möglich. => Curried functions sollten effizient implementiert werden! => N-ary functions als curried functions übersetzbar.

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Die abstrakte Maschine Die abstrakte Maschine besteht aus folgenden Komponenten: Der Akkumulator enthält Zwischenergebnisse Der Code-Zeiger zeigt auf die nächste auszuführende Instruktion. Die Umgebung verwaltet alle aktuellen Bezeichnerbindungen. Auf dem Stack werden Aufrufparameter, Ergebnis-Werte und Closures von unterbrochenen Auswertungen abgelegt. Die Semantik der Maschine wird bestimmt durch: Das Instruction Set der Maschine. Die Operationale Semantik bestimmt wie diese Instruktionen in Abhängigkeit vom aktuellen Inhalt von Umgebung und Stack ausgewertet werden.

Naive Auswertung Bei der naiven Auswertung wird jeweils ein Parameter angewandt. M N 1 N 2 ==> (M N 1 ) N 2 Über- und Unterversorgung betrachten wir später!

Auswertungsbeispiel (fn a => fn b => a + x) 3 x Um die erste Applikation auszuführen, wird der Folgeausdruck als Closure auf den Stack gelegt. Stack Umgebung x: 7 Akkumulator

Auswertungsbeispiel (fn a => fn b => a + x) 3 Der Parameter wird in den Akkumulator geladen. Stack Closure A Umgebung x: 7 Closure A Code X Umgebung x: 7 Akkumulator

Auswertungsbeispiel (fn a => fn b => a + x) … und auf den Stack gelegt. Stack Closure A Umgebung x: 7 Closure A Code X Umgebung x: 7 Akkumulator 3

Auswertungsbeispiel fn a => fn b => a + x Die Abstraktion wird ausgeführt, indem das Argument von Stack genommen wird und als a in die Umgebung eingefügt. Stack 3 Closure A Umgebung x: 7 Closure A Code X Umgebung x: 7 Akkumulator

Auswertungsbeispiel fn b => a + x Bei einer weiteren Abstraktion ist die Auswertung zunächst abgebrochen. Das Zwischenergebnis wird in einer Closure in den Akkumulator gepackt. Stack Closure A Umgebung x: 7 a: 3 Closure A Code X Umgebung x: 7 Akkumulator

Auswertungsbeispiel Die neue Closure ersetzt die unterbrochene Auswertung auf dem Stack. Diese wird fortgesetzt. Stack Closure A Code X Umgebung x: 7 Closure B Code fn b => a + x Umgebung x: 7 a: 3 Akkumulator Closure B

Auswertungsbeispiel x Das nächste Argument wird in den Akkumulator geladen. Stack Closure B Umgebung x: 7 Closure B Code fn b => a + x Umgebung x: 7 a: 3 Akkumulator

Auswertungsbeispiel … und auf den Stack gelegt. Die unterbrochene Auswertung wird fortgesetzt. Stack Closure B Umgebung x: 7 Closure B Code fn b => a + x Umgebung x: 7 a: 3 Akkumulator 7

Auswertungsbeispiel fn b => a + x Die Abstraktion wird ausgeführt, indem das Argument von Stack genommen wird und als b in die Umgebung eingefügt. Stack 7 Umgebung x: 7 a: 3 Akkumulator

Auswertungsbeispiel a + x a wird aus der Umgebung in den Akkumulator geladen. Stack Umgebung x: 7 a: 3 b: 7 Akkumulator

Auswertungsbeispiel + x … und auf den Stack gelegt. Stack Umgebung x: 7 a: 3 b: 7 Akkumulator 3

Auswertungsbeispiel + x x wird aus der Umgebung in den Akkumulator geladen. Stack 3 Umgebung x: 7 a: 3 b: 7 Akkumulator

Auswertungsbeispiel + Der oberste Wert auf dem Stack wird zum Akkumulator addiert. Stack 3 Umgebung x: 7 a: 3 b: 7 Akkumulator 7

Auswertungsbeispiel Der Wert im Akkumulator ist das Ergebnis. Stack Umgebung x: 7 a: 3 b: 7 Akkumulator 10

Analyse der Auswertung Die Auswertung von Mehrfach-Applikationen erfolgt schrittweise: Bei Left-To-Right Evaluation: ((((M) (N 1 )) (N 2 )) (N 3 )) Reihenfole: M, N 1, (M N 1 )=a, N 2, (a N 2 )=b, N 3, (b N 3 ) Bei der Auswertung jeder Applikation [außer der letzten] entsteht eine Closure für das bisher erzeugte Zwischenergebnis. n-Argumente => n-1 Closures.

Probleme dieser Auswertung Es werden für k Argumente mindestens k - 1 Closures verwendet. Closures werden auf dem Heap angelegt. Allokationen sind zeitintensiv Die Closures werden [teilweise] nur einmal verwendet. Der Speicherbedarf steigt. Die Garbage Collection wird häufiger verwendet. Wie kann man diese Closures meiden ?

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Echte Mehrfachapplikation - Vorteile Alle Argumente könnten vor der Applikation auf den Stack gelegt werden. Die Auswertung muss nicht nach jeder Teilapplikation unterbrochen werden. Die Reihenfolge bei 3-fach-Applikation M N 1 N 2 N 3 ist: bei Einzelapplikationen: M, N 1, (M N 1 )=a, N 2, (a N 2 )=b, N 3, (b N 3 ) bei Mehrfachapplikation: M, N 1, N 2, N 3, (M N 1 N 2 N 3 ) Die Applikation aller Argumente erzeugt keine unnötigen Closures.

Probleme der Mehrfachapplikation Mehrere Einzelapplikationen sollten die gleiche Auswertungsreihenfolge haben wie eine Mehrfachapplikation. [Gilt nicht für Zwischenergebnisse !] Problem: Left-To-Right Evaluation Order M N 1 N 2 => M, N 1, N 2, (M N 1 N 2 ) (M N 1 ) N 2 => [M, N 1, (M N 1 )=a], N 2, (a N 2 ) Lösung: Right-To-Left Evaluation Order M N 1 N 2 => N 2, N 1, (M N 1 N 2 ) (M N 1 ) N 2 => N 2, [N 1, (M N 1 )=a], (a N 2 )

Beispiel für Inkonsistenz exception Abs exception Right val f = fn x => (raise Abs; fn y => y) Problem: Left-To-Right Evaluation Order f 1 (raise Right) => raise Right ( f 1 ) (raise Right) => raise Abs Lösung: Right-To-Left Evaluation Order f 1 (raise Right) => raise Right ( f 1 ) (raise Right) => raise Right

Auswertungsbeispiel (fn a => fn b => a + x) 3 x Zuerst wird das rechte Argument in den Akkumulator geladen. Stack Umgebung x: 7 Akkumulator

Auswertungsbeispiel (fn a => fn b => a + x) 3 … und auf den Stack gelegt. Stack Umgebung x: 7 Akkumulator 7

Auswertungsbeispiel (fn a => fn b => a + x) 3 Dann wird das nächste Argument in den Akkumulator geladen. Stack 7 Umgebung x: 7 Akkumulator

Auswertungsbeispiel fn a => fn b => a + x … und auf den Stack gelegt. Stack 7 Umgebung x: 7 Akkumulator 3

Auswertungsbeispiel fn a => fn b => a + x Die Abstraktion wird ausgeführt, indem das Argument von Stack genommen wird und als a in die Umgebung eingefügt. Stack 3 7 Umgebung x: 7 Akkumulator

Auswertungsbeispiel fn b => a + x Die Abstraktion wird ausgeführt, indem das Argument von Stack genommen wird und als b in die Umgebung eingefügt. Stack 7 Umgebung x: 7 a: 3 Akkumulator

Auswertungsbeispiel a + x a wird aus der Umgebung in den Akkumulator geladen. Stack Umgebung x: 7 a: 3 b: 7 Akkumulator

Auswertungsbeispiel + x … und auf den Stack gelegt. Stack Umgebung x: 7 a: 3 b: 7 Akkumulator 3

Auswertungsbeispiel + x x wird aus der Umgebung in den Akkumulator geladen. Stack 3 Umgebung x: 7 a: 3 b: 7 Akkumulator

Auswertungsbeispiel + Der oberste Wert auf dem Stack wird zum Akkumulator addiert. Stack 3 Umgebung x: 7 a: 3 b: 7 Akkumulator 7

Auswertungsbeispiel Der Wert im Akkumulator ist das Ergebnis. Stack Umgebung x: 7 a: 3 b: 7 Akkumulator 10

Analyse der Auswertung Bei dieser Auswertung wurden KEINE Closures erzeugt.

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Darstellung von Funktionen Funktionen werden durch λ-Abstraktionen mit de-Bruijn-Darstellung ersetzt: val f = fn a => fn b => a + b val f = λ. λ. + val f = fn a => fn b => fn x => a + b val f = λ. λ. λ. + Die Bezeichner von neu gebundenen Abstraktionen entfallen. Außerdem reduzieren sich Umgebungen von Funktionen Bezeichner -> Wert auf einfachere Werte-Listen.

Darstellung von Funktionen Funktionen werden als Werte in Form von Closures dargestellt. Eine Closure besteht aus einer Umgebung und einer Code-Pointer. Beispiel (ML-Notation): val f = fn a => fn b => fn c => a + b f : {}, o val g = f 5 3 g : {a:5, b:3}, o

Darstellung von Funktionen Funktionen werden als Werte in Form von Closures dargestellt. Eine Closure besteht aus einer Umgebung und einer Code-Pointer. Beispiel (de-Bruijn-Notation): val f = λ. λ. λ. + f : [], o val g = f 5 3 g : [3, 5], o

Das Instruction Set Access(n) - liest das n. Element aus der Umgebung in den Akkumulator. Reduce(c) - führt c aus und legt den Wert des Akkumulators auf den Stack. Return - Beendet ein Auswertung eines Ausdrucks Grab - nimmt das oberste Element [das nächste Argument] vom Stack und fügt sie als neues erstes Element in die Umgebung ein. ConstInt(i) - legt die Integer-Konstante i in den Akkumulator. AddInt - Addiert das oberste Element des Stacks zum Akkumulator.

Die Übersetzungsfunktion [ ] [ M N ]--> Reduce( [ N ]; Return ); [ M ] [ ]--> Access(n) [ λ. N ]--> Grab; [ N ] [ i ]--> ConstInt(i) [ N 1 + N 2 ]--> Reduce( [ N 2 ]; Return ); [ N 1 ]; AddInt Jedes Programm endet mit Return.

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 [ ( λ. λ. + ) ( ( λ. ) 1 ) 2 ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( [ 2 ]; Return ); [ ( λ. λ. + ) ( ( λ. ) 1 ) ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); [ ( λ. λ. + ) ( ( λ. ) 1 ) ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( [ ( λ. ) 1 ]; Return ); [ λ. λ. + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( [ 1 ]; Return ); [ λ. ]; Return ); [ λ. λ. + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); [ λ. ]; Return ); [ λ. λ. + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; [ ]; Return ); [ λ. λ. + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; Access(0); Return ); [ λ. λ. + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; Access(0); Return ); Grab; [ λ. + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; Access(0); Return ); Grab; Grab; [ + ]; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; Access(0); Return ); Grab; Grab; Reduce( [ ]; Return ); [ ]; AddInt; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; Access(0); Return ); Grab; Grab; Reduce( Access(0); Return ); [ ]; AddInt; Return

Übersetzungsbeispiel ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 ( λ. λ. + ) ( ( λ. ) 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Reduce( ConstInt(1); Return ); Grab; Access(0); Return ); Grab; Grab; Reduce( Access(0); Return ); Access(1); AddInt; Return

Das Instruction Set Access(n) - liest das n. Element aus der Umgebung in den Akkumulator. Reduce(c) - führt c aus und legt den Wert des Akkumulators auf den Stack. Return - Beendet ein Auswertung eines Ausdrucks Grab - nimmt das oberste Element [das nächste Argument] vom Stack und fügt sie als neues erstes Element in die Umgebung ein. ConstInt(i) - legt die Integer-Konstante i in den Akkumulator. AddInt - Addiert das oberste Element des Stacks zum Akkumulator.

Operationale Semantik CodeAkkuEnv.StackCodeAkkuEnvStack Access(k); ca[v 0..v n ]scvkvk s Reduce(c); caescae :: s Returnae :: sc0c0 ae0e0 a :: s Return(c 0, e 0 )ev :: sc0c0 (c 0, e 0 )e0e0 v :: s Grab; caev :: scav :: es Grab; cae :: sc0c0 ae0e0 (Grab; c, e) :: s ConstInt(i); caescies AddInt; caev :: sca + ves SubInt; caev :: sca – ves

Verbleibende Closures Closures können nur von Grab und Reduce erzeugt werden. Grab erzeugt eine Closure genau dann, wenn keine weiteren Argumente vorhanden sind. (Normale Closure) => Das Ergebnis ist eine Abstraktion, die Closure ist notwendig. Reduce erzeugt Closures, um die Auswertung zu unterbrechen, bis ein Argument reduziert ist. (Markierte Closure) Diese Closures können nicht in die Umgebung eingefügt werden. => Sie können auf dem Stack alloziiert werden. => Sie belasten den Heap / die Garbage Collection nicht. Wie funktioniert Unter- / Überversorgung ?

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Unterversorgung Unterversorgung bedeutet, dass eine Curried-Function weniger Argumente bekommt, als sie bekommen könnte. ( fn a => fn b => 1 ) 2 ( λ. λ. 1 ) 2 Reduce( ConstInt(2); Return ); Grab; Grab; Access(1); Return

Unterversorgung Reduce( ConstInt(2); Return ); Grab; Grab; Access(1); Return Stack Umgebung Akkumulator

Unterversorgung ConstInt(2); Return Stack Umgebung Akkumulator Closure A Code Grab;... Return Umgebung

Unterversorgung Return Stack Umgebung Akkumulator 2 Closure A Code Grab;... Return Umgebung

Unterversorgung Grab; Grab; Access(1); Return Stack 2 Umgebung Akkumulator

Unterversorgung Grab; Access(1); Return Stack Umgebung 2 Akkumulator

Operationale Semantik CodeAkkuEnv.StackCodeAkkuEnvStack Access(k); ca[v 0..v n ]scvkvk s Reduce(c); caescae :: s Returnae :: sc0c0 ae0e0 a :: s Return(c 0, e 0 )ev :: sc0c0 (c 0, e 0 )e0e0 v :: s Grab; caev :: scav :: es Grab; cae :: sc0c0 ae0e0 (Grab; c, e) :: s ConstInt(i); caescies AddInt; caev :: sca + ves SubInt; caev :: sca – ves

Unterversorgung Stack (Closure B) Umgebung Akkumulator Closure B Code Grab; Access(1); Return Umgebung 2

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Überversorgung Überversorgung bedeutet, dass eine Curried-Function mehr Argumente bekommt, als zu erwarten wäre. Daher muss das Ergebnis der Anwendung der erwarteten Argumente eine Abstraktion sein. (Typ-Korrektheit) ( fn a => a ) ( fn b => 1 ) 2 ( λ. ) ( λ. 1 ) 2 Reduce( ConstInt(2); Return ); Reduce( Grab; ConstInt(1); Return ); Grab; Access(0); Return

Überversorgung Reduce( ConstInt(2); Return ); Reduce( Grab; ConstInt(1); Return ); Grab; Access(0); Return Stack Umgebung Akkumulator

Überversorgung ConstInt(2); Return Stack Umgebung Akkumulator Closure A Code Reduce...; Return Umgebung

Überversorgung Return Stack Umgebung Akkumulator 2 Closure A Code Reduce... Return Umgebung

Überversorgung Reduce( Grab; ConstInt(1); Return ); Grab; Access(0); Return Stack 2 Umgebung Akkumulator

Überversorgung Grab; ConstInt(1); Return Stack 2 Umgebung Akkumulator Closure B Code Grab;...; Return Umgebung

Operationale Semantik CodeAkkuEnv.StackCodeAkkuEnvStack Access(k); ca[v 0..v n ]scvkvk s Reduce(c); caescae :: s Returnae :: sc0c0 ae0e0 a :: s Return(c 0, e 0 )ev :: sc0c0 (c 0, e 0 )e0e0 v :: s Grab; caev :: scav :: es Grab; cae :: sc0c0 ae0e0 (Grab; c, e) :: s ConstInt(i); caescies AddInt; caev :: sca + ves SubInt; caev :: sca – ves

Überversorgung Grab; Access(0); Return Stack (Closure C) 2 Umgebung Akkumulator Closure C Code Grab;...; Return Umgebung

Überversorgung Access(0); Return Stack 2 Umgebung (Closure C) Akkumulator Closure C Code Grab;...; Return Umgebung

Überversorgung Return Stack 2 Umgebung (Closure C) Akkumulator (Closure C) Closure C Code Grab;...; Return Umgebung

Operationale Semantik CodeAkkuEnv.StackCodeAkkuEnvStack Access(k); ca[v 0..v n ]scvkvk s Reduce(c); caescae :: s Returnae :: sc0c0 ae0e0 a :: s Return(c 0, e 0 )ev :: sc0c0 (c 0, e 0 )e0e0 v :: s Grab; caev :: scav :: es Grab; cae :: sc0c0 ae0e0 (Grab; c, e) :: s ConstInt(i); caescies AddInt; caev :: sca + ves SubInt; caev :: sca – ves

Überversorgung Grab; ConstInt(1); Return Stack 2 Umgebung Akkumulator

Überversorgung ConstInt(1); Return Stack Umgebung 2 Akkumulator

Überversorgung Return Stack Umgebung 2 Akkumulator 1

Überversorgung Stack 1 Umgebung Akkumulator

Weitere Optimierungen Durch einen Cache (in Registern der Virtuellen Maschine) wird der Zugriff auf die aktuellsten Elemente der Umgebung beschleunigt. Es gibt spezialisierte Instruktionen für häufige Operationen. (z.B.: Let, Konstante Konstruktoren, Endrekursion …) Durch eine globale Variablenliste wird die Umgebung stark verkleinert. Trennung von Argumenten-Stack und Return-Stack

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Darstellung von Werten Alle Werte sind 32-bit Worte. unboxed, also direkt, werden folgende Typen abgelegt: 31-bit vorzeichenbehaftete Integer werden als 32-bit Integer mit gesetztem Bit 0 dargestellt. Zeiger in den Heap (oder in den Code auf Konstanten) werden direkt abgelegt. (Ihre Binärdarstellung endet auf 00.) Alle anderen Datentypen werden im Heap angelegt und es wird nur der Zeiger auf diesen Speicherblock abgelegt. boxed 31-bit Integer bit Pointer ? Real

Heap-Aufbau Der Speicher besteht aus 32-bit Worten, die in Blöcke aufgeteilt werden. Jeder Block hat einen Header, mit einer Beschreibung seines Inhalts, seiner Größe und 2 Bits für die Garbage Collection. Die Garbage Collection kann selbständig zwischen unstrukturierten Daten und Listen von Werten unterscheiden. Es gibt im Code keine Zeiger in den Heap. Es gibt in der globalen Variablenliste Zeiger in den Heap.

Speicherblöcke im Heap Im ersten Word eines Blocks steht: n – die Größe des Blocks (22 bit) GC – Daten für den Garbage Collector (2 bit) tag – Typinformation (8 bit) Das Tag-Feld bestimmt die Interpretation des Inhalts tag=255: Unstrukturierte Blöcke tag=254: Closure tag<254: Konkreter Datentyp nGCtag field field n

Unstrukturierte Blöcke Unstrukturierte Blöcke enthalten keine Werte. tag = 255 Beispiel: Strings Strings werden nullterminiert abgelegt Sie werden aufgefüllt, so dass Länge = 4 * Block-Größe – letztes Byte. Beispiel: var x = Abstract_Maschine 5GC255 Abst ract _Mas chin e\0\0\3

Closure Closures sind stukturierte Blöcke, d.h. sie enthalten Werte. tag = 254 Der Datenblock besteht aus dem Codezeiger und der zugehörigen Umgebung Im Beispiel: k Elemente in der Umgebung k+1GC254 value 1 value 2 … value k

Konkrete Datentypen Instanzen sind strukturierte Blöcke, d.h. sie enthalten Werte. tag < 254 Beispiel: datatype t = I of int * int | S of string Varianten werden beginnend mit 0 durchnummeriert. Die Variantennummer wird als tag verwendet. 2GC0 value 1 value 2 1GC1 pointer 1

Konkrete Datentypen (2) Sind mehr als 254 Varianten vorhanden, so muss eine alternative Darstellung verwendet werden. Die Variantennummer wird als Integer im ersten Daten-Feld abgelegt. Beispiel: datatype t = C0 of int |... | C254 of int * int 2GC0 0 value 1 3GC0 254 value 1 value 2

Beispiel datatype t = A of int | B of int * t * string * (int->int) val x = B(3, A 2, test, (fn a => fn b => a+b) 3) 4GC1 3 o o o test \0\0\0\4 1GC o 3

Vorteile der Blockdarstellung Der Garbage Collector kann erkennen, ob es sich um reine Daten oder eine Liste von Werten handelt, die Zeiger enthalten können. Zur Erinnerung: Zeiger können von Integern an den beiden abschließenden 00 unterschieden werden.

Überblick Motivation Eigenschaften funktionaler Sprahen N-ary / Unary / curried functions Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation Probleme Analyse der Auswertung Darstellung von Funktionen Das Instruction Set Die Übersetzungsfunktion Operationale Semantik Unterversorgung Überversorgung Optimierungen Darstellung von Werten Das reale Instruction Set Benchmarks

Das reale Instruction Set Constants and literals Function handling Environment handling Building and destructing blocks Integers Floating-point numbers Strings Predicates Branches and conditional branches Miscellaneous

Constants and literals Constbyte(int 8 ), Constshort(int 16 ), Constlog(int 32 ) Lädt eine Konstante. Atom(tag), Atom0, …, Atom9 Lädt einen Pointer auf einen konstanten Block mit Tag tag. GetGlobal(int 16 ), SetGlobal(int 16 ) Lädt eine globale Variable oder speichert diese.

Function handling Push, Pushmark Legt den Akkumulator bzw. eine Markierung auf den Stack Apply, AppTerm Führt die Closure im Akkumulator aus. (Normal / endrekursiv) Return Beendet die Auswertung eines Ausdrucks Grab Fügt ein Argument vom Stack in die Umgebung ein. Cur(ofs) Erstellt eine Closure für Codepointer ofs im Akkumulator

Integers and floating-point numbers SuccInt, PredInt, NegInt, AddInt, SubInt, MulInt, DivInt, ModInt, AndInt, OrInt, XorInt, ShiftLeftInt, ShiftRightInt Berechnet die jeweilige Operation mit dem Akkumulator und ggf. dem obersten Element des Stacks. FloatOfInt, IntOfFloat Konvertiert Integer in Fließkommazahlen oder umgekehrt. Floatop(AddFloat), Floatop(SubFloat), Floatop(MulFloat), Floatop(DivFloat) Berechnet die jeweiligen Operationen

Branches and conditional branches Branchifeqtag(tag, ofs), Branchifneqtag(tag, ofs) Springt relativ um ofs, falls der Akkumulator auf Tag tag zeigt. Switch(ofs 0,..., ofs k ) Springt relativ um ofs tag falls der Akkumulator auf Tag tag zeigt. BranchifEq(ofs) Springt relativ um ofs, falls der Akkumulator und der oberste Element des Stacks pointer-gleich sind. BranchifEqual(ofs) Springt relativ um ofs, falls der Akkumulator und der oberste Element des Stacks struktuell gleich sind. … viele weitere

Benchmarks fun fib n = if n<2 then 1 else fib(n-1) + fib(n-2) fun tak x y z = if x>y then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y) else z fun sum [] = 0 | sum (a::ar) = a + sum ar fun interval n = if n = 0 then nil else n :: interval(n-1) fun double f x = f (f x) val quad = double double val oct = quad quad fun succ n = n+1 fib 26 tak sum (interval 10000) double oct (fn x => x+1) 1 map (quad quad succ) (intervall 1000)

Benchmarks (2)

Quellen Xavier Leroy, The ZINC experiment: an economical implementation of the ML language. Technical report 117, INRIA, 1990