But : écrire un compilateur en Fopix vers une JVM.
Déroulé des cours :
- JVM
- Interpréteur Fopix
- Compilation directe
- Transformations
- Soutenance en mi-mars (dernier cours)
Projet en Scala.
hexedit
pour voir le code générer.
javap -c -v File.class
pour voir le code générer d’une façon lisible.
xmachin_n
où n est compris entre 0 et 5 (c’est une optimisation).
Quand on utilise des références, on remplace le xmachin
par amachin
CAN : http://www.classfileanalyzer.javaseiten.de/
Executer le code du projet
Dans SBT (recommandé)
$ sbt
# Ensuite on compile
$ compile
# Ensuite on lance le fichier
$ run exemples/test.fx
Lancer directement le test
Prend + de temps
$ sbt "run exemples/test.fx"
Traduction directe
Fopix \ra Javix (\subseteq JVM)
Arguments
On va faire la passation d’arguments à la mano, sans utiliser les arguments spécifiques de la JVM.
Par exemple :
Avant de faire le saut vers le corps de la fonction f, il faut sauvegarder les variables utilisées dans la pile.
Après le retour de la fonction :
- restaurer les variables précédemment sauvegardées
Attention au sommet de la pile : mettre le résultat de la fonction.
Il faudra utiliser DISPATCH
: tableswitch permettant de sauter à une étiquette.
Exemple
def min(x, y) = if x < y then x else y
val a = 1
val b = min(a, 3)
val _ = print_int(b + a)
Code :
iconst_1 # a = 1
istore 0 # v0 = 1
# Appel à min
iload 0
sipush 1000
iload 0 #
iconst_3 # v0 = a
istore 1 # v1 = 3
istore 0 #
goto min_body
min_body:
iload 0
iload 1
if_cmpge ifelse
iload 0 # Positif
goto ifexit
ifelse:
iload 1 # Négatif
ifexit:
swap
goto dispatch
ret_min: # Retour de la fonction retour, on doit renvoyer la valeur
swap
istore 0
# End call min
istore 1
iload 1
iload 0
iaobl
"print_int"
return
Rappel :
graph TD Fopix --> ANF ANF --> Kontix Kontix --> Javix
Kontix
f(x_{1}, \dots, x_{n}) \underset{\text{Kontix}}{\implies} f(K, \text{Env}, x_{1,} \dots, x_{n})
main Kontix -> début du programme Javix
- v0 \leftarrow Kontinuation (l’adresse
int
associée à la kontinuation) : typeint
\RA pas besoin de boxé - v1 \leftarrow Environnment (tableau / référence au tableau) :
E[0], E[1], E[2]...
Au début
- v0 \leftarrow kontinuation initiale (certaine adresse/étiquette)
addr_init
(= 1000)etiquette_init
(= “dispatch”_ret: return
pour terminer le programme) - v1 \leftarrow
env.initial
(tableau vide)
Compilation
En général
\RA Permet d’estimer une taille précise de la taille de la pile
Le If
Le if
basique
Comme dans Javix, rien de spécial, 2 étiquettes if_else
et if_exit
.
Le if(comp, TailExpr, TailExpr)
Si la condition est vraie, alors on continue avec l’expression récursive terminale, donc on ne revient jamais en arrière
Si la condition est fausse, on a une autre expression récursive terminale, alors on ne revient pas en arrière.
Donc vu qu’on ne revient jamais en arrière, pas besoin d’étiquette if_exit
.
Exemple : if(b) call(f) else call(g)
.
if((comp_op, be1, be2), te1, te2)
compil(be1)
compil(be2)
ificmp_invop label_if_else
compil(te1)
label_if_else:
compile(te2)
Let/Blet : Let/BLet(id, e1, e2)
- Création d’une nouvelle variable
Fresh()
Compiler(e1)
; Générer une variable associée à l'id
; Mettre dans cette variable le résultat
Compiler(e2)
Appel de fonction : Call(be, [a0, ..., an]
be
: basic expression
[a_{0}, \dots, a_{n}] un ensemble de variables
- Pas besoin de sauvegarder/restauration des variables, car on ne revient jamais en arrière.
- Pas d’adresse de retour non plus pour la même raison
Appel direct be = Fun f
Exemple
f(1, 3) : Call(Fun f, [Num(1), Num(3)]
; Il faut calculer les arguments a de 0 à n.
; Puis on AStore
AStore(n+2)
; ...
AStore(2)
Goto f_body ; étiquette qui traduit le K de la fonction f
Appel indirect be
n’est pas un Fun
, c’est une expression + compliquée
; Calcul de be
; Il faut calculer les arguments a de 0 à n.
; Puis on AStore
IStore(n+2)
; ...
AStore(2)
Goto dispatch
Appel à Kontinuation : Call(K, [be])
= Ret(be)
be
est l’argument de la kontinuation.
ALoad(0) ; v0 = Kontinuation courante
Calcul(be)
AStore 2 ; résultat de be
Goto dispatch
Push : PushCont(cont, ids, e)
let E = [K, E, eds] in
let K = cont in e
Push taille len(ids) + 2 ; 2 : 1 pour k et 1 pour e
ANewArray ; création du tableau
; Maintenant on écrit dedans les nouvelles valeurs
E[0] <- v0
E[1] <- v1
E[2] <- les autres variables "ids"
AStore 1 ; on met l'adresse du tableau E sur la pile
; Il faut laisser au sommet de la pile l'adresse de la kontinuation correspondante
Push 'adresse'
IStore 0
Def : DefCont(cont, [x, y, z], res, expr)
def kont(e, res) =
let [K, E, x, y, z] = e in
expr
label_kont:
; extraire l'environnement e
; v1 = (kont, env, variables)
v0 <- v1[0] ; on change de Kontinuation
v3 <- v1[2] ; x
v4 <- v1[3] ; y
v5 <- v1[4] ; z
v1 <- v1[1] ; env, qu'il faut faire à la fin pour pas perdre les variables, car on écrit dans v1
Trampoline
Fonctionne pour n’importe quelle fonction
Exemple : mutuellement récursif
// On implémente le trampoline une fois pour toute
sealed trait State[A]
case class Stop[A](resultat: A) extends State[A]
case class Next[A](thunk: () => State[A]) extends State[A]
def trampoline[A](init_state: State[A]): A = {
var state: State[A] = init_state
while (state.isInstanceOf[Next[A]]) {
state = state.asInstanceOf[Next[A]].thunk()
}
state.asInstanceOf[Stop[A]].result
}
// Exemple avec de la récursivité mutuelle
/* Dans tout les cas, les fontions even et odd terminent directement,
* soit avec un Stop, soit avec un Next. */
def even(n: Int): State[Boolean] = {
if n == 0 {
Stop(true)
} else {
Next(() => odd(n - 1)) // notre thunk
}
}
def odd(n: Int): State[Boolean] = {
if n == 0 {
Stop(false)
} else {
Next(() => even(n - 1)) // notre thunk
}
}
trampoline(even(200)) // => Renvois Stop(true)
// Un even + transparent vis-à-vis de son fonctionnement
def even_rec(n: Int): Boolean = trampoline(even(n)).isInstanceOf(Stop[Boolean].result)
En OCaml
type 'a state =
| Stop of 'a
| Next of (unit -> 'a state)
let rec trampoline = function
| Next thunk -> trampoline (thunk ())
| Stop x -> x
;;
(*define some functions which use them *)
let rec fact_norec n acc =
if n == 0 then Stop acc else Next (fun () -> fact_norec (n - 1) (n * acc))
;;
let rec even n = if n = 0 then Stop true else Next (fun () -> odd (n - 1))
and odd n = if n = 0 then Stop false else Next (fun () -> even (n - 1))
Examen de 2024
Notes de cours autorisées 💃💃
On voit en détailtreelist.fx
pour coder le typeSome
.
On peut coder les expressions en assignant chaque expression (Var
, Num
, Sum
, Prod
) à une valeur (0, 1, 2, 3) et ensuite les traduire en Fopix.
Ret
/PushCont
à préciser
Réponse de l’exo 4 : Oui, mais l’approche est moins générale que la CPS donc le niveau d’optimisation est plus basse (CPS est meilleure).
Exceptions
Probablement à l’examen cette année
Arrêter l’exécution du programme et faire quelque chose de différent, comme les kontinuations.
\RA simuler les exceptions avec les kontinuations
- Une fonction termine soit avec
K(resultat)
soit avecKe(mon_exception)
try ... catch
\RAPushCont(ke)
Cf. les notes de cours.
“Devoir”
Correction de TailRec.md
la semaine prochaine.