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.
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
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.
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 :
:label etiquette
où l'étiquette est appelée "nom de la fonction",:return
atteignable depuis l'instruction :label
marquant le début de la fonction. Cette instruction marque la fin de la fonction.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.
: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
.: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
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 :
:call
réalise cette duplication pour conserver l'état d'exécution du code qui a appelé la fonction,: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.
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.
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.
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" :
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 :
Il est permis d'appeler une fonction depuis une autre fonction.
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.
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é.
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.
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.