# 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

Address | Type | Value
--- | --- | ---
&0 | PTR | &3*1
&1 | INT | 5
&2 | PTR | &4*5

Alias | Pointer
--- | ---
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

Address | Type | Value
--- | --- | ---
&0 | PTR | &3*1
&1 | INT | 5
&2 | PTR | &4*5
&4 | INT | 0
&5 | INT | 1
&6 | INT | 2
&7 | INT | 3
&8 | INT | 4

Alias | Pointer
--- | ---
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

Address | Type | Value
--- | --- | ---
&0 | PTR | &3*4
&1 | INT | 5
&2 | INT | 30
&8 | INT | 0
&9 | INT | 1
&10 | INT | 2
&11 | INT | 3
&12 | INT | 4

Alias | Pointer
--- | ---
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" :

- persiste en mémoire après le retour de la fonction "nth_sum_of_squares", alors qu'elle n'est plus nécessaire,
- aucun pointeur ne permet de retrouver cette mémoire afin de la libérer.

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

Address | Type | Value
--- | --- | ---
&0 | PTR | &3*4
&1 | INT | 5
&2 | INT | 30

Alias | Pointer
--- | ---
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

Address | Type | Value
--- | --- | ---
&0 | PTR | &4*4
&1 | INT | 3
&2 | INT | 4
&3 | INT | 18

Alias | Pointer
--- | ---
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.

- La mémoire globale peut être rendue locale à une fonction avec l'instruction `:local pointeur`. La mémoire est alors libérée à la fin de la fonction contenant cette instruction : l'instruction `:local` peut être exécutée juste après la sous-fonction qui a alloué la mémoire globale (et c'est même recommandé !).
- Si la mémoire globale a été allouée en plusieurs fois, l'instruction `:local CASCADE pointeur` permet de rendre locale toute la mémoire accessible depuis le pointeur. La mémoire accessible est obtenue en suivant tous les pointeurs à partir du pointeur donné en argument de l'instruction.

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