Introduction

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

Dans ce didacticiel, vous allez transmettre de la mémoire allouée dans une fonction à sa fonction appellante.

Le temps de lecture de ce didacticiel est estimé à 15 minutes si l'allocation et la libération mémoire ont été abordées.

Mise en place

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

#!/usr/bin/env svm
DESCRIPTION
Global memory example
END
LOG
DEBUG "Global memory" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:shutdown
	# sequence function: PTR (internal), INT (input) size, PTR (output) sequence
	:label sequence
		:memory INT -> &P
		:memory INT*@(P/1) -> (P/2)
		0 -> &@&P
	:label sequence_loop
		@&@&P -> (@(P/2)/@&@&P)
		:shift &@&P
		:goto sequence_loop :when @&@&P IN @(P/2)
		:return
	END
END

Allocation de mémoire globale

Un cas particulier

Avant de modifier le code, prenez quelques minutes pour comprendre ce que fait la fonction "sequence" :

  1. elle alloue un tableau d'entiers de la taille du second paramètre,
  2. elle remplit ce tableau avec les entiers de zéro à la taille moins un.

Modifiez le code pour insérer un appel à cette fonction :

#!/usr/bin/env svm
DESCRIPTION
Global memory example
END
LOG
DEBUG "Global memory" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:memory (PTR, INT, PTR)/parameters
		5 -> (parameters/1)
		:call sequence parameters
		:shutdown
	# sequence function: PTR (internal), INT (input) size, PTR (output) sequence
	:label sequence
		:memory INT -> &P
		:memory INT*@(P/1) -> (P/2)
		0 -> &@&P
	:label sequence_loop
		@&@&P -> (@(P/2)/@&@&P)
		:shift &@&P
		:goto sequence_loop :when @&@&P IN @(P/2)
		:return
	END
END

Lancez l'exécution en mode débugueur, et faites exécuter le code jusqu'à l'instruction :shutdown. La mémoire se présente ainsi :

Global memory
Memory - K main - P application
AddressTypeValue
&0PTR&3*1
&1INT5
&2PTR&4*5
AliasPointer
parameters&0*3
Address:
Display
Aliases
P
Focus
Back
Clear

La mémoire créée par la fonction a bien été libérée, ce qui est normalement attendu. Mais ici, le résultat de la fonction est perdu !

Allocation globale

On aimerait ici avoir une allocation mémoire qui ne soit pas détruite par l'instruction :return.

Modifiez le code de la fonction ainsi :

#!/usr/bin/env svm
DESCRIPTION
Global memory example
END
LOG
DEBUG "Global memory" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:memory (PTR, INT, PTR)/parameters
		5 -> (parameters/1)
		:call sequence parameters
		:shutdown
	# sequence function: PTR (internal), INT (input) size, PTR (output) sequence
	:label sequence
		:memory INT -> &P
		:memory GLOBAL INT*@(P/1) -> (P/2)
		0 -> &@&P
	:label sequence_loop
		@&@&P -> (@(P/2)/@&@&P)
		:shift &@&P
		:goto sequence_loop :when @&@&P IN @(P/2)
		:return
	END
END

Puis relancez l'application en mode débugueur, et exécutez la jusqu'à l'instruction :shutdown. La mémoire se présente maintenant ainsi :

Global memory
Memory - K main - P application
AddressTypeValue
&0PTR&3*1
&1INT5
&2PTR&4*5
&4INT0
&5INT1
&6INT2
&7INT3
&8INT4
AliasPointer
parameters&0*3
Address:
Display
Aliases
P
Focus
Back
Clear

Cette fois, le résultat de la fonction a persisté après le retour de la fonction.

Il est possible d'allouer de la mémoire globale depuis une fonction, en ajoutant le mot-clef GLOBAL à l'instruction :memory.

Le bloc mémoire ainsi alloué ne sera pas libéré à la fin de la fonction.

Libération de mémoire globale

Une fuite mémoire

Ajoutez deux nouvelles fonctions au code, puis transformez le code principal pour obtenir :

#!/usr/bin/env svm
DESCRIPTION
Global memory example
END
LOG
DEBUG "Global memory" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:memory (PTR, INT, INT)/parameters
		5 -> (parameters/1)
		:call nth_sum_of_squares parameters
		:com.message @(parameters/2)
		:shutdown
	# sequence function: PTR (internal), INT (input) size, PTR (output) sequence
	:label sequence
		:memory INT -> &P
		:memory GLOBAL INT*@(P/1) -> (P/2)
		0 -> &@&P
	:label sequence_loop
		@&@&P -> (@(P/2)/@&@&P)
		:shift &@&P
		:goto sequence_loop :when @&@&P IN @(P/2)
		:return
	# sum of squares function: PTR (internal), PTR (input) sequence, INT (output) sum of squares
	:label sum_of_squares
		:memory INT, INT -> &P
		0 -> &@&P
		0 -> (P/2)
	:label sum_of_squares_loop
		:int.mul @(@(P/1)/@&@&P) @(@(P/1)/@&@&P) -> (@&P/1)
		:shift @(@&P/1) (P/2)
		:shift &@&P
		:goto sum_of_squares_loop :when @&@&P IN @(P/1)
		:return
	# nth sum of squares: PTR, INT (input) nb, INT (output) sum of squares
	:label nth_sum_of_squares
		:memory PTR, PTR, INT, PTR -> &P
		@(P/1) -> (@&P/2)
		:call sequence (@&P/1)*3
		@(@&P/3) -> (@&P/1)
		:call sum_of_squares &@&P*3
		@(@&P/2) -> (P/2)
		:return
	END
END

Lancez l'exécution en mode débugueur, et faites exécuter le code jusqu'à l'instruction :shutdown. La mémoire se présente comme ceci :

Global memory
Memory - K main - P application
AddressTypeValue
&0PTR&3*4
&1INT5
&2INT30
&8INT0
&9INT1
&10INT2
&11INT3
&12INT4
AliasPointer
parameters&0*3
Address:
Display
Aliases
P
Focus
Back
Clear

L'application répond bien, et elle ne génère pas d'erreur. Cependant, un des pires ennemis du développeur se cache dans cette application : la fuite mémoire.

En effet, la mémoire globale allouée par la fonction "sequence" :

Lorsque la fonction est appellée une fois, une fuite mémoire reste peu gênante. Cela n'est pas du tout la même histoire quand la fonction est appellée des milliers de fois, et peut conduire au crash de l'application à long terme.

Rendre la mémoire locale

Modifiez le code dans la fonction "nth_sum_of_squares" :

#!/usr/bin/env svm
DESCRIPTION
Global memory example
END
LOG
DEBUG "Global memory" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:memory (PTR, INT, INT)/parameters
		5 -> (parameters/1)
		:call nth_sum_of_squares parameters
		:com.message @(parameters/2)
		:shutdown
	# sequence function: PTR (internal), INT (input) size, PTR (output) sequence
	:label sequence
		:memory INT -> &P
		:memory GLOBAL INT*@(P/1) -> (P/2)
		0 -> &@&P
	:label sequence_loop
		@&@&P -> (@(P/2)/@&@&P)
		:shift &@&P
		:goto sequence_loop :when @&@&P IN @(P/2)
		:return
	# sum of squares function: PTR (internal), PTR (input) sequence, INT (output) sum of squares
	:label sum_of_squares
		:memory INT, INT -> &P
		0 -> &@&P
		0 -> (P/2)
	:label sum_of_squares_loop
		:int.mul @(@(P/1)/@&@&P) @(@(P/1)/@&@&P) -> (@&P/1)
		:shift @(@&P/1) (P/2)
		:shift &@&P
		:goto sum_of_squares_loop :when @&@&P IN @(P/1)
		:return
	# nth sum of squares: PTR, INT (input) nb, INT (output) sum of squares
	:label nth_sum_of_squares
		:memory PTR, PTR, INT, PTR -> &P
		@(P/1) -> (@&P/2)
		:call sequence (@&P/1)*3
		:local @(@&P/3)
		@(@&P/3) -> (@&P/1)
		:call sum_of_squares &@&P*3
		@(@&P/2) -> (P/2)
		:return
	END
END

Lancez l'exécution en mode débugueur, et faites exécuter le code jusqu'à l'instruction :shutdown. La mémoire est maintenant correcte :

Global memory
Memory - K main - P application
AddressTypeValue
&0PTR&3*4
&1INT5
&2INT30
AliasPointer
parameters&0*3
Address:
Display
Aliases
P
Focus
Back
Clear

L'ajout de l'instruction :local a résolu le problème, en indiquant à la machine virtuelle que la mémoire globale créée par la fonction "sequence" est de la mémoire locale à la fonction "nth_sum_of_squares".

Cette zone mémoire peut donc être utilisée jusqu'au retour de la fonction "nth_sum_of_squares" où elle sera libérée.

Vous pouvez rejouer l'application avec le débugueur pour constater à quel moment la mémoire est libérée.

Allocations globales complexes

Parfois, la mémoire qui doit être rendue locale peut provenir de plusieurs allocations mémoire. Quand ces allocations sont liées entre elles par des pointeurs, il est possible de rendre tous les blocs locaux en une seule instruction :local en ajoutant le mot-clef CASCADE.

Supprimez tout le code de l'application, et remplacez le pour obtenir :

#!/usr/bin/env svm
DESCRIPTION
Global memory example
END
LOG
DEBUG "Global memory" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:memory (PTR, INT, INT, INT)/parameters
		3 -> (parameters/1)
		4 -> (parameters/2)
		:call sum_table parameters
		:com.message @(parameters/3)
		:shutdown
		# function create table PTR (internal), INT (input) lines, INT (input) columns, PTR (output) table
	:label create_table
		:memory INT, INT -> &P
		:memory GLOBAL PTR*@(P/1) -> (P/3)
		0 -> &@&P
	:label create_table_loop
		:memory GLOBAL INT*@(P/2) -> (@(P/3)/@&@&P)
		0 -> (@&P/1)
	:label create_table_loop_loop
		:int.mul @&@&P @(@&P/1) -> (@(@(P/3)/@&@&P)/@(@&P/1))
		:shift (@&P/1)
		:goto create_table_loop_loop :when @(@&P/1) IN @(@(P/3)/@&@&P)
		:shift &@&P
		:goto create_table_loop :when @&@&P IN @(P/3)
		:return
		# function sum on table PTR (internal), INT (input) lines, INT (input) columns, INT (output) sum
	:label sum_table
		:memory PTR, INT, INT, PTR -> &P
		@(P/1) -> (@&P/1)
		@(P/2) -> (@&P/2)
		:call create_table @&P
		:local CASCADE @(@&P/3)
		0 -> (P/3)
		0 -> (@&P/1)
	:label sum_table_loop
		:int.add @(@(@&P/3)/@(@&P/1)) -> (@&P/2)
		:shift @(@&P/2) (P/3)
		:shift (@&P/1)
		:goto sum_table_loop :when @(@&P/1) IN @(@&P/3)
		:return
	END
END

Lancez ce code en mode débugueur, et constatez l'état de la mémoire après le retour de la fonction "sum_table" :

Global memory
Memory - K main - P application
AddressTypeValue
&0PTR&4*4
&1INT3
&2INT4
&3INT18
AliasPointer
parameters&0*4
Address:
Display
Aliases
P
Focus
Back
Clear

Dans ce code, l'allocation de mémoire globale est faite en plusieurs fois et grâce au mot-clef CASCADE, tous ces blocs deviennent locaux en une instruction. Pour y parvenir, la machine virtuelle lit tous les pointeurs présents dans le bloc, et ajoute les zones mémoires pointées à la mémoire locale.

En extra, vous pouvez ajouter des points d'arrêt :debug EXPLAIN pour voir le calcul des différentes adresses mémoires de ce code.

Conclusion

Vous venez de voir comment gérer la mémoire globale dans les fonctions, essentielle pour outrepasser le mécanisme habituel de gestion mémoire.

L'apparition de fuites mémoire dûes à l'usage de mémoire globale est un des pires fléaux pour les applications, et c'est pour cette raison que l'allocation de mémoire globale n'est pas faite par défaut.

L'utilisation de l'instruction rendant de la mémoire locale à une fonction permet d'éviter ces fuites mémoires. Cette instruction a été conçue pour pouvoir être exécutée au plus tôt, associant l'appel de sous-fonction créant la mémoire globale et l'instruction programmant la libération de cette même mémoire.