# Introduction

Ce didacticiel est destiné aux nouveaux utilisateurs de la Simple Virtual Machine.

Dans ce didacticiel, vous allez mettre en place des fonctions récursives.

Le temps de lecture de ce didacticiel est estimé à 15 minutes si les fonctions ont été abordées.

# Mise en place

Pour commencer, créez le canevas de l'application dans le fichier exécutable recursion.svm en utilisant ce code :

    #!/usr/bin/env svm
    DESCRIPTION
    Recursion example
    END
    LOG
    DEBUG "Recursion" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    	END
    	MEMORY nb
    END

# Récursion simple

Modifiez le code pour ajouter une fonction récursive et son appel :

    #!/usr/bin/env svm
    DESCRIPTION
    Recursion example
    END
    LOG
    DEBUG "Recursion" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory (PTR, INT, INT)/parameters
    		[ , @&nb , ] -> parameters
    		:call f parameters
    		:com.message @(parameters/2)
    		:shutdown
    		# Function f: PTR, INT (input), INT (output)
    	:label f
    		:memory PTR, INT, INT, BLN -> &P
    		:int.cmp @(P/1) < 2 -> (@&P/3)
    		:goto f_end :when @(@&P/3) TRUE
    		@(P/1) -> (@&P/1)
    		:shift -1 (@&P/1)
    		:call f &@&P*3
    		:int.mul @(@&P/2) @(P/1) -> (P/2)
    		:return
    	:label f_end
    		1 -> (P/2)
    		:return
    	END
    	MEMORY nb
    END

Avant de lancer l'application, observons la fonction :

- la fonction est constituée de deux branches de code,
- une de ces deux branches contient un appel de fonction vers elle-même en utilisant des paramètres créés localement.

Une telle fonction est dite "récursive".

Lancez l'application avec quelques valeurs :

    ./recursion.svm 0
    1
    ./recursion.svm 1
    1
    ./recursion.svm 2
    2
    ./recursion.svm 3
    6
    ./recursion.svm 4
    24
    ./recursion.svm 5
    120

Il s'agit de la célèbre fonction factorielle : elle renvoie le produit des nombres de 1 à la valeur d'entrée.

Lancez l'application avec la valeur 5 dans le débugueur et observez le déroulement de l'exécution instruction par instruction. Lorsque la fonction passe dans la branche sans appel récursif, la mémoire et le processeur ressemblent à :

Recursion
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | INT | 5
&1 | PTR | &4*4
&2 | INT | 5
&3 | INT |
&4 | PTR | &8*4
&5 | INT | 4
&6 | INT |
&7 | BLN | FALSE
&8 | PTR | &12*4
&9 | INT | 3
&10 | INT |
&11 | BLN | FALSE
&12 | PTR | &16*4
&13 | INT | 2
&14 | INT |
&15 | BLN | FALSE
&16 | PTR | &20*4
&17 | INT | 1
&18 | INT |
&19 | BLN | FALSE
&20 | PTR |
&21 | INT |
&22 | INT |
&23 | BLN | TRUE

Alias | Pointer
--- | ---
nb | &0*1
parameters | &1*3

Address:
Display
Aliases
P
Focus
Back
Clear
Processor - K main - P application
State:
Next instruction: <main:1/15>
Current instruction: <main:1/14>
Code
Current memory: &16*3
Allocated memory: &20*4
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
State:
Next instruction: <main:1/12>
Current instruction: <main:1/11>
Code
Current memory: &12*3
Allocated memory: &16*4
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
State:
Next instruction: <main:1/12>
Current instruction: <main:1/11>
Code
Current memory: &8*3
Allocated memory: &12*4
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
State:
Next instruction: <main:1/12>
Current instruction: <main:1/11>
Code
Current memory: &4*3
Allocated memory: &8*4
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
State:
Next instruction: <main:1/12>
Current instruction: <main:1/11>
Code
Current memory: &1*3
Allocated memory: &4*4
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
State:
Next instruction: <main:1/4>
Current instruction: <main:1/3>
Code
Current memory: &0*0
Allocated memory: &1*3
Defined aliases: parameters
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Global interruptions:
Waiting interruptions:
Code main - K main - P application

    :debug BREAK
    :memory ( PTR , INT , INT )/parameters
    [  , @&nb ,  ] -> parameters
    :call f parameters
    :com.message @(parameters/2)
    :shutdown
    :label f
    :memory PTR , INT , INT , BLN -> &P
    :int.cmp @(P/1) < 2 -> (@&P/3)
    :goto f_end :when @(@&P/3) TRUE
    @(P/1) -> (@&P/1)
    :shift -1 (@&P/1)
    :call f &@&P*3
    :int.mul @(@&P/2) @(P/1) -> (P/2)
    :return
    :label f_end
    1 -> (P/2)
    :return

Auto-scroll to
Current
with
above
Display

Lorsque les appels récursifs se terminent, vous pouvez observer les différentes valeurs de la factorielle dans la mémoire.

Une fonction est dite récursive lorsqu'elle s'appelle elle-même. La récursion se termine lorsque la fonction passe par une branche de code qui ne l'appelle plus.

Les règles habituelles des fonctions s'appliquent toutes aux fonctions récursives.

# Récursion interrompue

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Recursion example
    END
    LOG
    DEBUG "Recursion" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory (PTR, INT, INT)/parameters
    		[ , @&nb , ] -> parameters
    		:flag "main"
    		:interruption CASCADE NUMERIC stop
    		:call f parameters
    		:goto error :unless (parameters/2) INITIALISED
    		:com.message @(parameters/2)
    		:shutdown
    	:label error
    		:com.message "Error"
    		:shutdown
    		# Function f: PTR, INT (input), INT (output)
    	:label f
    		:memory PTR, INT, INT, INT, BLN -> &P
    		:int.cmp @(P/1) = 0 -> (@&P/4)
    		:goto f_end :when @(@&P/4) TRUE
    		:int.mod @(P/1) 2 -> (@&P/3)
    		:int.cmp @(@&P/3) = 0 -> (@&P/4)
    		:goto f_alter :when @(@&P/4) TRUE
    		:int.sub @(P/1) 1 -> (@&P/1)
    		:call f &@&P*3
    		:int.div 100 @(@&P/2) -> (P/2)
    		:return
    	:label f_alter
    		:int.div @(P/1) 4 -> (@&P/1)
    		:call f &@&P*3
    		:int.add @(P/1) @(@&P/2) -> (P/2)
    		:return
    	:label f_end
    		0 -> (P/2)
    		:return
    		# Interruption handler
    	:label stop
    		:return 0 :unless "main" RAISED
    	END
    	MEMORY nb
    END

Puis lancez l'application avec quelques valeurs, en mode débugueur à votre convenance :

    ./recursion.svm 0
    0
    ./recursion.svm 2
    2
    ./recursion.svm 3
    50
    ./recursion.svm 8
    10
    ./recursion.svm 10
    12

Dans tous ces cas, la fonction se déroule correctement, et les appels récursifs aboutissent pour donner le résultat.

Maintenant, lancez l'application avec ces autres valeurs :

    ./recursion.svm 1
    Error
    ./recursion.svm 4
    Error
    ./recursion.svm 6
    Error
    ./recursion.svm 17
    Error
    ./recursion.svm 70
    Error

Cette fois, pour chacune de ces valeurs, un des appels récursif tombe sur une division par 0, ce qui génère une interruption `NUMERIC`.

Pour rattrapper cette interruption, le code appellant la fonction :

1. Lève un drapeau "main" sur son niveau de pile de retour de fonction,
2. Place un gestionnaire d'interruption cascadé sur l'interruption `NUMERIC`.

Et la fonction associée au gestionnaire d'interruption contient une seule instruction qui permet de ressortir de tous les appels récursifs (jusqu'à détecter le drapeau "main") en une seule fois en libérant la mémoire.

Lancez l'application en mode débugueur avec la valeur 17, puis placez un point d'arrêt éphémère sur l'instruction `:return 0 :unless "main" RAISED` et lancez l'exécution du code :

Recursion
Processor - K main - P application
State:
Next instruction: <main:1/28>
Current instruction: <main:1/27>
Code
Current memory: &14*3
Allocated memory:
Defined aliases:
Local interruptions:
Cascaded local interruptions: NUMERIC => <main:1/27>
Code
Flags:
Cascaded flags:
Return stack:
State:
Next instruction: <main:1/20>
Current instruction: <main:1/19>
Code
Current memory: &14*3
Allocated memory: &19*5
Defined aliases:
Local interruptions:
Cascaded local interruptions: NUMERIC => <main:1/27>
Code
Flags:
Cascaded flags:
State:
Next instruction: <main:1/23>
Current instruction: <main:1/22>
Code
Current memory: &9*3
Allocated memory: &14*5
Defined aliases:
Local interruptions:
Cascaded local interruptions: NUMERIC => <main:1/27>
Code
Flags:
Cascaded flags:
State:
Next instruction: <main:1/23>
Current instruction: <main:1/22>
Code
Current memory: &4*3
Allocated memory: &9*5
Defined aliases:
Local interruptions:
Cascaded local interruptions: NUMERIC => <main:1/27>
Code
Flags:
Cascaded flags:
State:
Next instruction: <main:1/19>
Current instruction: <main:1/18>
Code
Current memory: &1*3
Allocated memory: &4*5
Defined aliases:
Local interruptions:
Cascaded local interruptions: NUMERIC => <main:1/27>
Code
Flags:
Cascaded flags:
State:
Next instruction: <main:1/6>
Current instruction: <main:1/5>
Code
Current memory: &0*0
Allocated memory: &1*3
Defined aliases: parameters
Local interruptions:
Cascaded local interruptions: NUMERIC => <main:1/27>
Code
Flags: main
Cascaded flags:
Global interruptions:
Waiting interruptions:
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | INT | 17
&1 | PTR | &4*5
&2 | INT | 17
&3 | INT |
&4 | PTR | &9*5
&5 | INT | 16
&6 | INT |
&7 | INT | 1
&8 | BLN | FALSE
&9 | PTR | &14*5
&10 | INT | 4
&11 | INT |
&12 | INT | 0
&13 | BLN | TRUE
&14 | PTR | &19*5
&15 | INT | 1
&16 | INT |
&17 | INT | 0
&18 | BLN | TRUE
&19 | PTR | &24*5
&20 | INT | 0
&21 | INT | 0
&22 | INT | 1
&23 | BLN | FALSE

Alias | Pointer
--- | ---
nb | &0*1
parameters | &1*3

Address:
Display
Aliases
P
Focus
Back
Clear
Code main - K main - P application

    :debug BREAK
    :memory ( PTR , INT , INT )/parameters
    [  , @&nb ,  ] -> parameters
    :flag "main"
    :interruption CASCADE NUMERIC stop
    :call f parameters
    :goto error :unless (parameters/2) INITIALISED
    :com.message @(parameters/2)
    :shutdown
    :label error
    :com.message "Error"
    :shutdown
    :label f
    :memory PTR , INT , INT , INT , BLN -> &P
    :int.cmp @(P/1) = 0 -> (@&P/4)
    :goto f_end :when @(@&P/4) TRUE
    :int.mod @(P/1) 2 -> (@&P/3)
    :int.cmp @(@&P/3) = 0 -> (@&P/4)
    :goto f_alter :when @(@&P/4) TRUE
    :int.sub @(P/1) 1 -> (@&P/1)
    :call f &@&P*3
    :int.div 100 @(@&P/2) -> (P/2)
    :return
    :label f_alter
    :int.div @(P/1) 4 -> (@&P/1)
    :call f &@&P*3
    :int.add @(P/1) @(@&P/2) -> (P/2)
    :return
    :label f_end
    0 -> (P/2)
    :return
    :label stop
    :return 0 :unless "main" RAISED

Auto-scroll to
Current
with
above
Display

Avant de poursuivre l'exécution du code, notez :

1. sur quel niveau de la pile de retour se trouve le drapeau "main",
2. le nombre d'états dans la pile de retour du processeur,
3. le nombre d'adresses définies dans la mémoire.

Puis exécutez uniquement l'instruction `:return 0 :unless "main" RAISED` et constatez le nouvel état : en une seule instruction, tous les appels récursifs sont immédiatement terminés, et la mémoire allouée par chaque appel est libérée.

L'instruction `:return 0` associée à une condition permet d'interrompre proprement l'exécution d'une fonction récursive.

Les drapeaux peuvent être utilisés pour simplifier la condition de sortie de la fonction récursive.

Lorsqu'il s'agit d'une sortie anticipée suite à une interruption, ce mécanisme peut être mis en place indépendamment de la fonction récursive elle-même et peut varier d'un appel de la fonction à l'autre.

# Conclusion

Vous venez de voir comment écrire des fonctions récursives dans une application.

Les fonctions récursives sont parfois un moyen élégant pour écrire certains calculs, mais comportent souvent une gestion d'erreur relativement complexe.

Avec l'argument 0 de l'instruction de retour de fonction et une condition, une fonction récursive peut être aisément terminée sans crainte de fuite mémoire.