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 :

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.

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 :

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
AddressTypeValue
&0INT-6
&1INT-5
&2INT-4
&3INT-3
&4INT-2
&5INT1
&6INT2
&7INT3
&8INT4
&9INT5
&10INT6
&11INT0
&12BLNFALSE
AliasPointer
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 :

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é.

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.