# Introduction

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

Dans ce didacticiel, vous allez commencer à utiliser un outil important d'organisation du code.

Le temps de lecture de ce didacticiel est estimé à 20 minutes si les structures de contrôle ont été abordées.

# Mise en place

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

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/index, BLN/test
    		0 -> &index
    	:label loop1
    		:int.cmp @(array/@&index) < 0 -> &test
    		:goto skip1 :when @&test TRUE
    		:shift (array/@&index)
    		:goto end1
    	:label skip1
    		:shift -1 (array/@&index)
    	:label end1
    		:shift &index
    		:goto loop1 :when @&index IN array
    		0 -> &index
    	:label loop2
    		:int.cmp @(array/@&index) < 0 -> &test
    		:goto skip2 :when @&test TRUE
    		:shift (array/@&index)
    		:goto end2
    	:label skip2
    		:shift -1 (array/@&index)
    	:label end2
    		:shift &index
    		:goto loop2 :when @&index IN array
    		0 -> &index
    	:label loop3
    		:com.message @&index ": " @(array/@&index)
    		:shift &index
    		:goto loop3 :when @&index IN array
    	END
    	MEMORY array
    END

# Fonction simple

Avant de modifier l'application, prenez le temps de lancer l'application avec quelques valeurs :

    ./fonctions.svm -i -5 -i -4 -i -3 -i -2 -i -1 -i 0 -i 1 -i 2 -i 3 -i 4 -i 5
    0: -7
    1: -6
    2: -5
    3: -4
    4: -3
    5: 2
    6: 3
    7: 4
    8: 5
    9: 6
    10: 7

L'application semble "éloigner" les valeurs de la valeur 0, en retranchant 2 aux valeurs négatives et en ajoutant 2 aux autres.

Cependant, pour les besoins de ce didacticiel, cette application réalise cette opération de manière absolument pas optimale : le code de calcul est même écrit deux fois.

## Création

Tant que l'application reste petite, écrire tout le code d'un bloc peut paraître encore raisonnable. Cela change complètement lorsque l'application grossit.

Enlevez du code la seconde boucle de calcul, puis déplacez la première boucle de calcul vers la fin du code. Enfin, appliquez quelques changements pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/index, BLN/test
    		0 -> &index
    	:label loop3
    		:com.message @&index ": " @(array/@&index)
    		:shift &index
    		:goto loop3 :when @&index IN array
    		:shutdown

    	:label away
    		0 -> &index
    	:label away_loop
    		:int.cmp @(array/@&index) < 0 -> &test
    		:goto away_skip :when @&test TRUE
    		:shift (array/@&index)
    		:goto away_end
    	:label away_skip
    		:shift -1 (array/@&index)
    	:label away_end
    		:shift &index
    		:goto away_loop :when @&index IN array
    		:return
    	END
    	MEMORY array
    END

Lancez l'application avec les mêmes valeurs :

    ./fonctions.svm -i -5 -i -4 -i -3 -i -2 -i -1 -i 0 -i 1 -i 2 -i 3 -i 4 -i 5
    0: -5
    1: -4
    2: -3
    3: -2
    4: -1
    5: 0
    6: 1
    7: 2
    8: 3
    9: 4
    10: 5

Oups ! Cela ne fonctionne plus. Et pour le moment, c'est complètement normal car le code déplacé se situe après une instruction `:shutdown`.

Avant de modifier plus le code, vous pouvez remarquer que le code déplacé se situe entre deux instructions précises : une instruction `:label` au début et une instruction `:return` à la fin.

Une fonction est une portion de code :

- débutant par une instruction `:label etiquette` où l'étiquette est appelée "nom de la fonction",
- comportant au moins une instruction `:return` atteignable depuis l'instruction `:label` marquant le début de la fonction. Cette instruction marque la fin de la fonction.

## Appel

Modifiez le code une nouvelle fois pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/index, BLN/test
    		:debug BREAK
    		:call away &0*0
    		:call away &0*0
    		0 -> &index
    	:label loop3
    		:com.message @&index ": " @(array/@&index)
    		:shift &index
    		:goto loop3 :when @&index IN array
    		:shutdown

    	:label away
    		0 -> &index
    	:label away_loop
    		:int.cmp @(array/@&index) < 0 -> &test
    		:goto away_skip :when @&test TRUE
    		:shift (array/@&index)
    		:goto away_end
    	:label away_skip
    		:shift -1 (array/@&index)
    	:label away_end
    		:shift &index
    		:goto away_loop :when @&index IN array
    		:return
    	END
    	MEMORY array
    END

Lancez une nouvelle fois l'application avec les mêmes valeurs :

    ./fonctions.svm -i -5 -i -4 -i -3 -i -2 -i -1 -i 0 -i 1 -i 2 -i 3 -i 4 -i 5
    0: -7
    1: -6
    2: -5
    3: -4
    4: -3
    5: 2
    6: 3
    7: 4
    8: 5
    9: 6
    10: 7

Cela fonctionne à nouveau ! Et cette fois avec une amélioration de taille : le code de calcul n'est écrit qu'une fois dans l'application. Cela permet, à long terme, de simplifier la maintenance de l'application en en écrivant chaque partie qu'une fois.

L'aspect réutilisation du code des fonctions est permis par l'emploi de l'instruction `:call nom_de_fonction ...`. Pour le moment, ignorons le pointeur indiqué après le nom de la fonction.

- Un appel de fonction commence avec l'instruction `:call nom_de_fonction pointeur`. Une fois cette instruction exécutée, les instructions de la fonction sont exécutées en commençant par l'instruction suivant `:label nom_de_fonction`.
- Un appel de fonction se termine lorsqu'une instruction `:return` est exécutée dans la fonction. L'exécution reprend après l'instruction `:call` qui a démarré l'appel de fonction.

Les instructions `:call` et `:return` peuvent être postfixées avec une condition comme l'instruction `:goto`.

Observez le comportement de la machine virtuelle lors de l'exécution d'une fonction en lançant l'application en mode débugueur :

    ./fonctions.svm -d 8080 -i -1 -i 1

Functions
Processor - K main - P application
State:
Next instruction: <main:1/10>
Current instruction: <main:1/9>
Code
Current memory: &0*0
Allocated memory:
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
State:
Next instruction: <main:1/3>
Current instruction: <main:1/2>
Code
Current memory: &0*0
Allocated memory: &2*2
Defined aliases: index test
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Global interruptions:
Waiting interruptions:
Code main - K main - P application

    :memory INT/index , BLN/test
    :debug BREAK
    :call away &0*0
    :call away &0*0
    0 -> &index
    :label loop3
    :com.message @&index ": " @(array/@&index)
    :shift &index
    :goto loop3 :when @&index IN array
    :shutdown
    :label away
    0 -> &index
    :label away_loop
    :int.cmp @(array/@&index) < 0 -> &test
    :goto away_skip :when @&test TRUE
    :shift (array/@&index)
    :goto away_end
    :label away_skip
    :shift -1 (array/@&index)
    :label away_end
    :shift &index
    :goto away_loop :when @&index IN array
    :return

Auto-scroll to
Current
with
above
Display
=Code main - K main - P application

    :memory INT/index , BLN/test
    :debug BREAK
    :call away &0*0
    :call away &0*0
    0 -> &index
    :label loop3
    :com.message @&index ": " @(array/@&index)
    :shift &index
    :goto loop3 :when @&index IN array
    :shutdown
    :label away
    0 -> &index
    :label away_loop
    :int.cmp @(array/@&index) < 0 -> &test
    :goto away_skip :when @&test TRUE
    :shift (array/@&index)
    :goto away_end
    :label away_skip
    :shift -1 (array/@&index)
    :label away_end
    :shift &index
    :goto away_loop :when @&index IN array
    :return

Auto-scroll to
Current
with
above
Display

Ici, le programme a été avancé jusqu'à ce que la première instruction `:call` ait été exécutée.

Le processeur est donc sur le point d'exécuter la première instruction de la fonction "away". Vous pouvez remarquer que la partie "State" se trouve dupliquée :

- l'instruction `:call` réalise cette duplication pour conserver l'état d'exécution du code qui a appelé la fonction,
- cet état sauvegardé sera restauré par l'instruction `:return`, pour reprendre l'exécution du code après l'instruction `:call`.

Suivez pas à pas l'exécution du code dans le débugueur pour observer ce mécanisme. N'hésitez pas à relancer plusieurs fois l'application avec le débugueur activé jusqu'à comprendre comment les instructions d'appel de fonction et de retour de fonction permettent de déplacer temporairement le flot d'instructions.

# Paramètres

Il manque une dernière touche à apporter au code pour que notre première fonction puisse être complète : actuellement, elle ne peut modifier les valeurs que du tableau "array". Il pourrait être souhaitable qu'elle puisse transformer les valeurs de n'importe quel tableau avec un code unique !

En quelque sorte, il faudrait pouvoir préciser le tableau au moment de l'appel de la fonction.

## Spécification

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/index, BLN/test
    		:debug BREAK
    		:call away array
    		:call away array
    		0 -> &index
    	:label loop3
    		:com.message @&index ": " @(array/@&index)
    		:shift &index
    		:goto loop3 :when @&index IN array
    		:shutdown

    	:label away
    		0 -> &index
    	:label away_loop
    		:int.cmp @(array/@&index) < 0 -> &test
    		:goto away_skip :when @&test TRUE
    		:shift (array/@&index)
    		:goto away_end
    	:label away_skip
    		:shift -1 (array/@&index)
    	:label away_end
    		:shift &index
    		:goto away_loop :when @&index IN array
    		:return
    	END
    	MEMORY array
    END

Cette modification de code réalise ce que l'on souhaiterait faire ! Cependant, la fonction elle-même n'a pas été modifiée et ne tient pas compte de ce changement.

## Accès

Modifiez à nouveau le code :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/index, BLN/test
    		:debug BREAK
    		:call away array
    		:call away array
    		0 -> &index
    	:label loop3
    		:com.message @&index ": " @(array/@&index)
    		:shift &index
    		:goto loop3 :when @&index IN array
    		:shutdown

    	:label away
    		0 -> &index
    	:label away_loop
    		:int.cmp @(P/@&index) < 0 -> &test
    		:goto away_skip :when @&test TRUE
    		:shift (P/@&index)
    		:goto away_end
    	:label away_skip
    		:shift -1 (P/@&index)
    	:label away_end
    		:shift &index
    		:goto away_loop :when @&index IN P
    		:return
    	END
    	MEMORY array
    END

Lancez l'application comme précédemment :

    ./fonctions.svm -i -5 -i -4 -i -3 -i -2 -i -1 -i 0 -i 1 -i 2 -i 3 -i 4 -i 5
    0: -7
    1: -6
    2: -5
    3: -4
    4: -3
    5: 2
    6: 3
    7: 4
    8: 5
    9: 6
    10: 7

Pour vous convaincre que le tableau modifié est bien celui écrit dans l'instruction `:call`, modifiez le second appel de fonction :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/index, BLN/test
    		:debug BREAK
    		:call away array
    		:call away &3*5
    		0 -> &index
    	:label loop3
    		:com.message @&index ": " @(array/@&index)
    		:shift &index
    		:goto loop3 :when @&index IN array
    		:shutdown

    	:label away
    		0 -> &index
    	:label away_loop
    		:int.cmp @(P/@&index) < 0 -> &test
    		:goto away_skip :when @&test TRUE
    		:shift (P/@&index)
    		:goto away_end
    	:label away_skip
    		:shift -1 (P/@&index)
    	:label away_end
    		:shift &index
    		:goto away_loop :when @&index IN P
    		:return
    	END
    	MEMORY array
    END

Et relancez une dernière fois l'application :

    ./fonctions.svm -i -5 -i -4 -i -3 -i -2 -i -1 -i 0 -i 1 -i 2 -i 3 -i 4 -i 5
    0: -6
    1: -5
    2: -4
    3: -4
    4: -3
    5: 2
    6: 3
    7: 4
    8: 4
    9: 5
    10: 6

Cette vue du débugueur montre dans la mémoire quelles seront les valeurs modifiées lors du second appel à la fonction "away" :

Functions
Processor - K main - P application
State:
Next instruction: <main:1/11>
Current instruction: <main:1/10>
Code
Current memory: &3*5
Allocated memory:
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
State:
Next instruction: <main:1/4>
Current instruction: <main:1/3>
Code
Current memory: &0*0
Allocated memory: &11*2
Defined aliases: index test
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Global interruptions:
Waiting interruptions:
Code main - K main - P application

    :memory INT/index , BLN/test
    :debug BREAK
    :call away array
    :call away &3*5
    0 -> &index
    :label loop3
    :com.message @&index ": " @(array/@&index)
    :shift &index
    :goto loop3 :when @&index IN array
    :shutdown
    :label away
    0 -> &index
    :label away_loop
    :int.cmp @(P/@&index) < 0 -> &test
    :goto away_skip :when @&test TRUE
    :shift (P/@&index)
    :goto away_end
    :label away_skip
    :shift -1 (P/@&index)
    :label away_end
    :shift &index
    :goto away_loop :when @&index IN P
    :return

Auto-scroll to
Current
with
above
Display
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | INT | -6
&1 | INT | -5
&2 | INT | -4
&3 | INT | -3
&4 | INT | -2
&5 | INT | 1
&6 | INT | 2
&7 | INT | 3
&8 | INT | 4
&9 | INT | 5
&10 | INT | 6
&11 | INT | 0
&12 | BLN | FALSE

Alias | Pointer
--- | ---
array | &0*11
index | &11*1
test | &12*1

Address:
Display
Aliases
P
Focus
Back
Clear

Pour obtenir ce résultat, vous pouvez cliquer sur le bouton "P" en bas de la fenêtre mémoire. Notez également que ce pointeur n'est pas enregistré dans la mémoire, mais dans le processeur.

L'instruction `:call nom_de_fonction pointeur` accepte comme deuxième argument un pointeur. Le mot-clef `P` dans la fonction est égal à ce pointeur.

Le mot-clef `P` désigne dans la mémoire une zone très particulière pour le code de la fonction. Cette zone contient le tableau des paramètres de la fonction et sert de zone d'échange de données entre l'appelant de la fonction et la fonction elle-même :

- les adresses contenant les données d'entrée de la fonction doivent être écrites par l'appelant avant l'appel de la fonction,
- les adresses contenant les données de sortie de la fonction doivent être lues par l'appelant après le retour de la fonction,
- les adresses peuvent avoir les deux rôles ci-dessus en même temps, et parfois aucun des deux.

# Appels imbriqués

Il est permis d'appeler une fonction depuis une autre fonction.

## Cas nominal

Modifiez le code afin d'obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:com.message 1
    		:call function1 P
    		:com.message 5
    		:shutdown

    	:label function1
    		:com.message 2
    		:call function2 P
    		:com.message 4
    		:return

    	:label function2
    		:com.message 3
    		:return
    	END
    	MEMORY array
    END

Lancez l'application sans option :

    ./fonctions.svm
    1
    2
    3
    4
    5

Les valeurs produites par l'application marquent le flot d'instructions suivi. L'appel de la fonction "function2" est réalisé pendant l'exécution de la fonction "function1", mise en attente pendant l'appel de la fonction "function2".

Ajoutez une instruction point d'arrêt au début du code, puis lancez l'application en mode débugueur pour suivre l'état du processeur durant toute l'exécution du code.

## Retour anticipé

Dans certaines circonstances, une fonction peut vouloir court-circuiter la reprise d'une ou plusieurs fonctions appelantes.

Ajoutez un 2 à l'instruction `:return` de la fonction "function2" :

    #!/usr/bin/env svm
    DESCRIPTION
    Functions example
    END
    LOG
    DEBUG "Functions" STYLE "default"
    PLUGIN "svmint.so"
    PLUGIN "svmcom.so"
    OPTION -i MULTIPLE INT array
    PROCESS "application"
    	CODE "main" INLINE
    		:com.message 1
    		:call function1 P
    		:com.message 5
    		:shutdown

    	:label function1
    		:com.message 2
    		:call function2 P
    		:com.message 4
    		:return

    	:label function2
    		:com.message 3
    		:return 2
    	END
    	MEMORY array
    END

Lancez maintenant l'application sans option :

    ./fonctions.svm
    1
    2
    3
    5

La valeur 4 a disparue, car la partie de la fonction "function1" après l'appel à la fonction "function2" a été court-circuité.

- Les appels de fonctions peuvent être imbriqués : dans le corps d'une fonction, il est possible d'appeler une ou plusieurs autres fonctions.
- Les retours de fonctions se font dans le sens inverse des appels : par défaut, une fonction appelante verra son exécution reprendre après le retour d'une fonction appelée.

Il est possible de sortir de plusieurs fonctions d'un seul coup avec l'instruction `:return nombre_retours` et terminer prématurément l'exécution de fonctions appelantes.

# Conclusion

Vous venez de voir comment utiliser les fonctions pour organiser le code en morceaux logiques et réutilisables.

Ce concept de fonction, plutôt rustique en apparence, est en réalité central dans l'architecture du processeur de la machine virtuelle. La complexité qu'il induit sur le processeur est largement compensée par les bénéfices qu'il apporte au développeur dans l'écriture et la maintenance de ses applications.

Les fonctions peuvent utiliser de la mémoire pour réaliser leur traitement. Cet aspect sera abordé dans un prochain didacticiel.