Effiziente Virtuelle Maschinen fr funktionale Programmiersprachen Xavier Leroy

  • Slides: 108
Download presentation
Effiziente Virtuelle Maschinen für funktionale Programmiersprachen Xavier Leroy, The ZINC experiment: an economical implementation

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 n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

Motivation n Wie können Programme in funktionalen Sprachen effizient und plattformunabhängig ausgeführt werden ?

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

Eigenschaften funktionaler Sprachen n Abstraktion ¨ ¨ ¨ n Applikation ¨ ¨ n Curried

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

N-ary functions n n-ary functions sind Funktionen mit mehr als einem Argument. Bei der

N-ary functions n 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 n als unary functions mit Argumenten-Tupel fun f (a, b) = a+b n oder als curried functions val f = fn a => fn b => a+b

Vergleich von Unary / curried functions n Nachteil bei Argumenten-Tupel: ¨ n Allokation des

Vergleich von Unary / curried functions n Nachteil bei Argumenten-Tupel: ¨ n 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] ¨ n Partielle Applikation ist bei Argumenten-Tupel nicht möglich. => Curried functions sollten effizient implementiert werden! => N-ary functions als curried functions übersetzbar.

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

Die abstrakte Maschine n Die abstrakte Maschine besteht aus folgenden Komponenten: Der Akkumulator enthält

Die abstrakte Maschine n 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. ¨ n 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 n Bei der naiven Auswertung wird jeweils ein Parameter angewandt. n M

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

Auswertungsbeispiel Akkumulator n (fn a => fn b => a + x) 3 x

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

Auswertungsbeispiel Akkumulator n (fn a => fn b => a + x) 3 n

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

Auswertungsbeispiel Akkumulator 3 n (fn a => fn b => a + x) n

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

Auswertungsbeispiel Akkumulator n fn a => fn b => a + x n Die

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

Auswertungsbeispiel Akkumulator n fn b => a + x n Bei einer weiteren Abstraktion

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

Auswertungsbeispiel Akkumulator Closure B n Die neue Closure ersetzt die unterbrochene Auswertung auf dem

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

Auswertungsbeispiel Akkumulator n x n Das nächste Argument wird in den Akkumulator geladen. Closure

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

Auswertungsbeispiel Akkumulator 7 n … und auf den Stack gelegt. Die unterbrochene Auswertung wird

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

Auswertungsbeispiel Akkumulator n fn b => a + x n Die Abstraktion wird ausgeführt,

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

Auswertungsbeispiel Akkumulator n a + x n „a“ wird aus der Umgebung in den

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

Auswertungsbeispiel Akkumulator 3 n + x n … und auf den Stack gelegt. Umgebung

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

Auswertungsbeispiel Akkumulator n + x n „x“ wird aus der Umgebung in den Akkumulator

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

Auswertungsbeispiel Akkumulator 7 n + n Der oberste Wert auf dem Stack wird zum

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

Auswertungsbeispiel Akkumulator 10 n Der Wert im Akkumulator ist das Ergebnis. Umgebung x: 7

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

Analyse der Auswertung n Die Auswertung von Mehrfach-Applikationen erfolgt schrittweise: n Bei Left-To-Right Evaluation:

Analyse der Auswertung n Die Auswertung von Mehrfach-Applikationen erfolgt schrittweise: n 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) ¨ n 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 n Es werden für k Argumente mindestens k - 1 Closures

Probleme dieser Auswertung n Es werden für k Argumente mindestens k - 1 Closures verwendet. n 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. ¨ n Wie kann man diese Closures meiden ?

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

„Echte“ Mehrfachapplikation - Vorteile n Alle Argumente könnten vor der Applikation auf den Stack

„Echte“ Mehrfachapplikation - Vorteile n Alle Argumente könnten vor der Applikation auf den Stack gelegt werden. Die Auswertung muss nicht nach jeder Teilapplikation unterbrochen werden. n 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) ¨ n Die Applikation aller Argumente erzeugt keine unnötigen Closures.

Probleme der Mehrfachapplikation n Mehrere Einzelapplikationen sollten die gleiche Auswertungsreihenfolge haben wie eine Mehrfachapplikation.

Probleme der Mehrfachapplikation n Mehrere Einzelapplikationen sollten die gleiche Auswertungsreihenfolge haben wie eine Mehrfachapplikation. [Gilt nicht für „Zwischenergebnisse“ !] n 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) ¨ n 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 n exception Abs exception Right val f = fn x =>

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

Auswertungsbeispiel Akkumulator n (fn a => fn b => a + x) 3 x

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

Auswertungsbeispiel Akkumulator 7 n (fn a => fn b => a + x) 3

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

Auswertungsbeispiel Akkumulator n (fn a => fn b => a + x) 3 n

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

Auswertungsbeispiel Akkumulator 3 n fn a => fn b => a + x n

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

Auswertungsbeispiel Akkumulator n fn a => fn b => a + x n Die

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

Auswertungsbeispiel Akkumulator n fn b => a + x n Die Abstraktion wird ausgeführt,

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

Auswertungsbeispiel Akkumulator n a + x n „a“ wird aus der Umgebung in den

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

Auswertungsbeispiel Akkumulator 3 n + x n … und auf den Stack gelegt. Umgebung

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

Auswertungsbeispiel Akkumulator n + x n „x“ wird aus der Umgebung in den Akkumulator

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

Auswertungsbeispiel Akkumulator 7 n + n Der oberste Wert auf dem Stack wird zum

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

Auswertungsbeispiel Akkumulator 10 n Der Wert im Akkumulator ist das Ergebnis. Umgebung x: 7

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

Analyse der Auswertung n Bei dieser Auswertung wurden KEINE Closures erzeugt.

Analyse der Auswertung n Bei dieser Auswertung wurden KEINE Closures erzeugt.

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

Darstellung von Funktionen werden durch λ-Abstraktionen mit de-Bruijn-Darstellung ersetzt: val f = fn a

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

Darstellung von Funktionen werden als Werte in Form von Closures dargestellt. Eine Closure besteht

Darstellung von Funktionen werden als Werte in Form von Closures dargestellt. Eine Closure besteht aus einer Umgebung und einer Code-Pointer. n Beispiel (ML-Notation): n ¨ 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 werden als Werte in Form von Closures dargestellt. Eine Closure besteht

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

Das Instruction Set n Access(n) - liest das n. Element aus der Umgebung in

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

Die Übersetzungsfunktion [ ] n [ [ [ n Jedes Programm endet mit Return.

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

Übersetzungsbeispiel n ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 n ( λ. λ. <1> + <0> ) ( ( λ. <0> ) 1 ) 2 n Reduce( Const. Int(2); Return ); Reduce( Const. Int(1); Return ); Grab; [ <0> ]; Return ); [ λ. λ. <1> + <0> ]; Return

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

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

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

Übersetzungsbeispiel n ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 n ( λ. λ. <1> + <0> ) ( ( λ. <0> ) 1 ) 2 n Reduce( Const. Int(2); Return ); Reduce( Const. Int(1); Return ); Grab; Access(0); Return ); Grab; Reduce( [ <0> ]; Return ); [ <1> ]; Add. Int; Return

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

Übersetzungsbeispiel n ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 n ( λ. λ. <1> + <0> ) ( ( λ. <0> ) 1 ) 2 n Reduce( Const. Int(2); Return ); Reduce( Const. Int(1); Return ); Grab; Access(0); Return ); Grab; Reduce( Access(0); Return ); [ <1> ]; Add. Int; Return

Übersetzungsbeispiel n ( fn a => fn b => a + b ) (

Übersetzungsbeispiel n ( fn a => fn b => a + b ) ( ( fn x => x ) 1 ) 2 n ( λ. λ. <1> + <0> ) ( ( λ. <0> ) 1 ) 2 n Reduce( Const. Int(2); Return ); Reduce( Const. Int(1); Return ); Grab; Access(0); Return ); Grab; Reduce( Access(0); Return ); Access(1); Add. Int; Return

Das Instruction Set n Access(n) - liest das n. Element aus der Umgebung in

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

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v 0. . vn] s c vk [v 0. . vn] s Reduce(c‘); c a e s c‘ a e <c, e> : : s Return a e <c 0, e 0> : : s c 0 a e 0 a : : s Return (c 0, e 0) e v : : s c 0 (c 0, e 0) e 0 v : : s Grab; c a e v : : s c a v : : e s Grab; c a e <c 0, e 0> : : s c 0 a e 0 (Grab; c, e) : : s Const. Int(i); c a e s c i e s Add. Int; c a e v : : s c a+v e s Sub. Int; c a e v : : s c a–v e s

Verbleibende Closures n n Closures können nur von Grab und Reduce erzeugt werden. ¨

Verbleibende Closures n n 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 n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

Unterversorgung n Unterversorgung bedeutet, dass eine Curried-Function weniger Argumente bekommt, als sie bekommen könnte.

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

Unterversorgung Akkumulator n Reduce( Const. Int(2); Return ); Grab; Access(1); Return Umgebung Stack

Unterversorgung Akkumulator n Reduce( Const. Int(2); Return ); Grab; Access(1); Return Umgebung Stack

Unterversorgung Akkumulator n Const. Int(2); Return Closure A Umgebung Stack Code Grab; . .

Unterversorgung Akkumulator n Const. Int(2); Return Closure A Umgebung Stack Code Grab; . . . Return Umgebung <Closure A>

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

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

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

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

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

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

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v 0. . vn] s c vk [v 0. . vn] s Reduce(c‘); c a e s c‘ a e <c, e> : : s Return a e <c 0, e 0> : : s c 0 a e 0 a : : s Return (c 0, e 0) e v : : s c 0 (c 0, e 0) e 0 v : : s Grab; c a e v : : s c a v : : e s Grab; c a e <c 0, e 0> : : s c 0 a e 0 (Grab; c, e) : : s Const. Int(i); c a e s c i e s Add. Int; c a e v : : s c a+v e s Sub. Int; c a e v : : s c a–v e s

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

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

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

Überversorgung n Überversorgung bedeutet, dass eine Curried-Function mehr Argumente bekommt, als zu erwarten wäre.

Überversorgung n Ü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) n ( fn a => a ) ( fn b => 1 ) 2 n ( λ. <0> ) ( λ. 1 ) 2 n Reduce( Const. Int(2); Return ); Reduce( Grab; Const. Int(1); Return ); Grab; Access(0); Return

Überversorgung Akkumulator n Reduce( Const. Int(2); Return ); Reduce( Grab; Const. Int(1); Return );

Überversorgung Akkumulator n Reduce( Const. Int(2); Return ); Reduce( Grab; Const. Int(1); Return ); Grab; Access(0); Return Umgebung Stack

Überversorgung Akkumulator n Const. Int(2); Return Closure A Umgebung Stack Code Reduce. . .

Überversorgung Akkumulator n Const. Int(2); Return Closure A Umgebung Stack Code Reduce. . . ; Return Umgebung <Closure A>

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

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

Überversorgung Akkumulator n Reduce( Grab; Const. Int(1); Return ); Grab; Access(0); Return Umgebung Stack

Überversorgung Akkumulator n Reduce( Grab; Const. Int(1); Return ); Grab; Access(0); Return Umgebung Stack 2

Überversorgung Akkumulator n Grab; Const. Int(1); Return Closure B Umgebung Stack Code Grab; .

Überversorgung Akkumulator n Grab; Const. Int(1); Return Closure B Umgebung Stack Code Grab; . . . ; Return Umgebung <Closure B> 2

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v 0. . vn] s c vk [v 0. . vn] s Reduce(c‘); c a e s c‘ a e <c, e> : : s Return a e <c 0, e 0> : : s c 0 a e 0 a : : s Return (c 0, e 0) e v : : s c 0 (c 0, e 0) e 0 v : : s Grab; c a e v : : s c a v : : e s Grab; c a e <c 0, e 0> : : s c 0 a e 0 (Grab; c, e) : : s Const. Int(i); c a e s c i e s Add. Int; c a e v : : s c a+v e s Sub. Int; c a e v : : s c a–v e s

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

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

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

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

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

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

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v

Operationale Semantik Code Akku Env. Stack Code Akku Env Stack Access(k); c a [v 0. . vn] s c vk [v 0. . vn] s Reduce(c‘); c a e s c‘ a e <c, e> : : s Return a e <c 0, e 0> : : s c 0 a e 0 a : : s Return (c 0, e 0) e v : : s c 0 (c 0, e 0) e 0 v : : s Grab; c a e v : : s c a v : : e s Grab; c a e <c 0, e 0> : : s c 0 a e 0 (Grab; c, e) : : s Const. Int(i); c a e s c i e s Add. Int; c a e v : : s c a+v e s Sub. Int; c a e v : : s c a–v e s

Überversorgung Akkumulator n Grab; Const. Int(1); Return Umgebung Stack 2

Überversorgung Akkumulator n Grab; Const. Int(1); Return Umgebung Stack 2

Überversorgung Akkumulator n Const. Int(1); Return Umgebung 2 Stack

Überversorgung Akkumulator n Const. Int(1); Return Umgebung 2 Stack

Überversorgung Akkumulator 1 n Return Umgebung 2 Stack

Überversorgung Akkumulator 1 n Return Umgebung 2 Stack

Überversorgung Akkumulator Umgebung Stack 1

Überversorgung Akkumulator Umgebung Stack 1

Weitere Optimierungen n Durch einen Cache (in Registern der Virtuellen Maschine) wird der Zugriff

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

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

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

Darstellung von Werten 31 -bit Integer . . 1 32 -bit Pointer . .

Darstellung von Werten 31 -bit Integer . . 1 32 -bit Pointer . . 00 ? Real . . 10 n Alle Werte sind 32 -bit Worte. n 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. ) ¨ n Alle anderen Datentypen werden im Heap angelegt und es wird nur der Zeiger auf diesen Speicherblock abgelegt. „boxed“

Heap-Aufbau n Der Speicher besteht aus 32 -bit Worten, die in Blöcke aufgeteilt werden.

Heap-Aufbau n 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. n Die Garbage Collection kann selbständig zwischen unstrukturierten Daten und Listen von Werten unterscheiden. n Es gibt im Code keine Zeiger in den Heap. Es gibt in der globalen Variablenliste Zeiger in den Heap. n

Speicherblöcke im Heap n n Im ersten Word eines Blocks steht: n – die

Speicherblöcke im Heap n n 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) n GC tag field 1. . . fieldn n n Das Tag-Feld bestimmt die Interpretation des Inhalts tag=255: Unstrukturierte Blöcke tag=254: Closure tag<254: Konkreter Datentyp

Unstrukturierte Blöcke n Unstrukturierte Blöcke enthalten keine Werte. tag = 255 n Beispiel: Strings

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

Closure n n Closures sind stukturierte Blöcke, d. h. sie enthalten Werte. tag =

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

Konkrete Datentypen n n Instanzen sind strukturierte Blöcke, d. h. sie enthalten Werte. tag

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

Konkrete Datentypen (2) n Sind mehr als 254 Varianten vorhanden, so muss eine alternative

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

Beispiel n n datatype t = A of int | B of int *

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

Vorteile der Blockdarstellung n Der Garbage Collector kann erkennen, ob es sich um reine

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

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive

Überblick n n Motivation Eigenschaften funktionaler Sprahen ¨ n n Die abstrakte Maschine Naive Auswertung Analyse der Auswertung Echte Mehrfachapplikation ¨ n N-ary / Unary / curried functions Probleme Analyse der Auswertung n n n n n 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 n n n n n Constants and literals Function handling

Das reale Instruction Set n n n n n 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 n Constbyte(int 8), Constshort(int 16), Constlog(int 32) Lädt eine Konstante. n

Constants and literals n Constbyte(int 8), Constshort(int 16), Constlog(int 32) Lädt eine Konstante. n Atom(tag), Atom 0, …, Atom 9 Lädt einen Pointer auf einen konstanten Block mit Tag tag. n Get. Global(int 16), Set. Global(int 16) Lädt eine globale Variable oder speichert diese.

Function handling n Push, Pushmark Legt den Akkumulator bzw. eine Markierung auf den Stack

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

Integers and floating-point numbers n Succ. Int, Pred. Int, Neg. Int, Add. Int, Sub.

Integers and floating-point numbers n Succ. Int, Pred. Int, Neg. Int, Add. Int, Sub. Int, Mul. Int, Div. Int, Mod. Int, And. Int, Or. Int, Xor. Int, Shift. Left. Int, Shift. Right. Int Berechnet die jeweilige Operation mit dem Akkumulator und ggf. dem obersten Element des Stacks. n Float. Of. Int, Int. Of. Float Konvertiert Integer in Fließkommazahlen oder umgekehrt. n Floatop(Add. Float), Floatop(Sub. Float), Floatop(Mul. Float), Floatop(Div. Float) Berechnet die jeweiligen Operationen

Branches and conditional branches n Branchifeqtag(tag, ofs), Branchifneqtag(tag, ofs) Springt relativ um ofs, falls

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

Benchmarks n fun fib n = if n<2 then 1 else fib(n-1) + fib(n-2)

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

Benchmarks (2)

Benchmarks (2)

Quellen n Xavier Leroy, The ZINC experiment: an economical implementation of the ML language.

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