Notes de la séance 6 de TransProg M2
====================================

Conversion CPS : introduction

Une continuation est un argument fonctionnel qu’une fonction reçoit comme paramètre, et qu’elle va lancer sur son résultat au lieu de faire un simple return sur ce résultat (ou équivalent selon les langages).

La CPS (continuation passing style), ou en français “style par passage de continuation”, correspond à l’usage systématique de continuations tout au long d’un programme.

La mise en forme CPS (ou conversion CPS) est la transformation de programme qui prend un programme quelconque et en fait une version CPS. L’intérêt de cette transformation est d’obtenir un programme ne faisant plus que des appels de fonctions terminaux.
En particulier, les fonctions récursives seront toutes récursives terminales (tailrec) après la mise en CPS, ce qui nous permettra une compilation optimisée.

Voir CpsDemo.scala.

Les nouveaux langages intermédiaires : Anfix et Kontix

Anfix

Anfix correspond à une forme dite ANF chez d’autres auteurs (pour Administrative Normal Form). L’idée est de forcer l’usage de variables locales entre toutes opérations, par exemple un f(x+1) sera transformé en let y = x+1 in f(y).
Cette étape préparatoire simplifiera le passage à Kontix et à ses formes CPS.

Voir AnfixAST.scala.

Kontix

Kontix correspond à du code mis en CPS (on y fait donc des “Kontinuations”).

Voir KontixAST.scala.

Les deux chemins de compilation

              directe
Fopix ------------------>  Javix
  \                        /
   \ ajout let            /  compil optimisée (pas d'adresse de retour)
    \                    /
      Anfix ----------> Kontix
              cps

Un premier exemple complet : fact

En Fopix:

def fact(x) = if x == 0 then 1 else x * fact(x-1)
val _ = print_int(fact(10))
val _ = print_string("\n")

En Anfix:

def fact(x) =
 if x == 0
 then 1
 else 
   let y = 
     (let x' = x-1 in fact(x'))
   in
   x * y
val _ = let r = fact(10) in print_int(r)
val _ = print_string("\n")

Ou bien (si on réordonne un peu les let, pas obligatoire
ni forcément souhaitable) :

def fact(x) =
 if x == 0
 then 1
 else 
   let x' = x-1 in
   let y = fact(x') in
   x * y
val _ = let r = fact(10) in print_int(r)
val _ = print_string("\n")

En Kontix

Etape 1 : la traduction de fact

Grosso modo : un Def de Anfix devient un DefFun de Kontix (mais le corps de la fonction est à ajuster pour en faire un TailExpr)

DefFun(fact,[x],
  If((==,Var(x),Num(0)),
     Ret(Num(1)),
     /* Un let sans appels de fonction à gauche du let,
        peut devenir un Let de Kontix */
     Let(x',Op(-,Var x, Num 1),
          /* Un let avec un appel de fn à gauche ici fact(x') : */
          PushCont(kont1,[x],
            Call(fact,[x'])))))

DefKont(kont1,[x],y,
  Ret(Op(*,Var(x),Var(y))

NB: La règle d’or lors de la traduction de Anfix vers Kontix:

CPS(let x = expr1 in expr2)
=
PushCont(kontN,[...vars à sauvegarder...], CPS(expr1))

avec un DefKont en plus à accumuler quelque-part:

DefKont(kontN,[...vars sauvergardés ...], x, CPS(expr2))

où kontN : un nouveau nom de continuation
et [... vars à sauvegarder ... ] sont les variables actuelles utilisées dans expr2 (à part x).

Remarque : on peut dans un premier temps traduire systématiquement tous les let en continuations (mais ça fait certains sauts pour rien). Ici cela donnerait:

DefFun(fact,[x],
  If((==,Var(x),Num(1)),
     Ret(Num(1))
     PushCont(kont0,[x],
      Ret (Op(-,Var(x),Num(1)))))
     
DefKont(kont0,[x],x',
  PushCont(kont1,[x],
    Call(fact,[x'])))

DefKont(kont1,[x],y,
  Ret(Op(*,Var(x),Var(y))

Dernière chose : le “main”, ou comment s’occuper des Val

En Anfix de départ:

val _ = let r = fact(10) in print_int(r)
val _ = print_string("\n")

En une seule expression (de syntaxe Anfix) :

let _ = 
  let r = fact(10) in print_int(r)
in
let _ = print_string("\n")
in
0 /* pour simuler un unit */

Passage en Kontix

/* expression qui servira de main:TailExpr */
  PushCont(kont2,[],
   PushCont(kont3,[],
    Call(fact,[Num 10]))))

/* Continuations générées au passage */
DefKont(kont2,[],_,
Let(_,Prim(print_string,[Str("\n")]),
    Ret(Num(0)))
    
DefKont(kont3,[],r,
 Ret(Prim(print_int,[Var(r)])))

Second exemple complet : Fibonacci

En Fopix

def fib(x) = if x <= 1 then 1
else fib(x-1) + fib(x-2)

Les lignes Val sont omises ici (même traitement que pour fact ci-dessus).

En Anfix

def fib(x) = if x <= 1 then 1
else
  let r1 = (let y = x-1 in fib(y)) in
  let r2 = (let z = x-2 in fib(z)) in
  r1+r2

En Kontix

DefFun(fib,[x],
 If( (<=,Var(x),Num(1)),
     Ret(Num(1)),
     PushCont(kont1,[x],
      /* à traduire ici : (let y = x-1 in fib(y)) */
      Let(y,Op(-,Var(x),Num(1)),
      Call(fib,[y]))))
      
DefKont(kont1,[x],r1,
   /* à traduire ici :
      let r2 = (let z = x-2 in fib(z)) in
      r1+r2 */
   PushCont(kont2,[r1],
    /* à traduire ici : 
       (let z = x-2 in fib(z)) */
    Let(z,Op(-,Var(x),Num(2)),
      Call(fib,[z]))))
  
DefKont(kont2,[r1],r2,
  Ret(Op(+,Var r1,Var r2))