# Introduction

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

Dans ce didacticiel, vous allez créer vos propres adresses en mémoire.

Le temps de lecture de ce didacticiel est estimé à 15 minutes si les fonctions et lectures/écritures en mémoire ont été abordées.

# Mise en place

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

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    	END
    END

# Allocation mémoire

## Etat initial de la mémoire

Lancez l'application en mode débugueur, puis ouvrez la fenêtre de mémoire :

Memory
Memory - K main - P application

Address | Type | Value
--- | --- | ---

Alias | Pointer
--- | ---

Address:
Display
Aliases
P
Focus
Back
Clear

Il est facile de constater que la mémoire est vide par défaut ! En effet, aucune adresse n'est définie.

Pour utiliser la mémoire, il faut donc créer des adresses mémoire.

## Bloc mémoire

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory INT, STR, BLN, PTR
    	END
    END

Puis lancez l'application dans le débugueur, et ouvrez la fenêtre mémoire. Après l'exécution de l'instruction `:memory`, la fenêtre doit contenir :

Memory
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | INT |
&1 | STR |
&2 | BLN |
&3 | PTR |

Alias | Pointer
--- | ---

Address:
Display
Aliases
P
Focus
Back
Clear

L'instruction vient de créer quatre adresses mémoire. Maintenant, il est possible d'utiliser ces adresses nouvellement définies pour écrire puis lire des données.

Détaillons le résultat cette instruction :

1. l'instruction alloue toujours un bloc de mémoire : les quatre adresses sont contigües dans la mémoire,
2. l'instruction utilise une description du bloc faite des types à créer : les types des adresses correspondent à ceux indiqués dans l'instruction,
3. l'instruction détermine seule où allouer le bloc : il n'y a aucun moyen fiable d'assurer que les adresses sont allouées à partir d'une adresse donnée.

Les adresses créées portent un type. Celui-ci permettra de garantir la nature de la valeur enregistrée à une adresse donnée. Ici, le bloc comporte quatre types de base de la machine :

- `INT` : l'adresse correspondante peut contenir un nombre entier,
- `STR` : l'adresse correspondante peut contenir une chaine de caractères (souvent utilisée comme zone de mémoire à taille variable),
- `BLN` : l'adresse correspondante peut contenir un booléen,
- `PTR` : l'adresse correspondante peut contenir un pointeur (une adresse mémoire peut donc contenir une valeur qui désigne une autre zone mémoire).

Maintenant, modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory ((INT*3, STR)*2, BLN)*2, PTR*4
    	END
    END

La description du bloc peut être améliorée en ajoutant des multiplicateurs, et des parenthèses peuvent être utilisées pour créer des groupes. Ces groupes peuvent également recevoir un multiplicateur.

Lancez l'application en mode débugueur, et constatez l'état de la mémoire après l'instruction d'allocation mémoire :

Memory
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | INT |
&1 | INT |
&2 | INT |
&3 | STR |
&4 | INT |
&5 | INT |
&6 | INT |
&7 | STR |
&8 | BLN |
&9 | INT |
&10 | INT |
&11 | INT |
&12 | STR |
&13 | INT |
&14 | INT |
&15 | INT |
&16 | STR |
&17 | BLN |
&18 | PTR |
&19 | PTR |
&20 | PTR |
&21 | PTR |

Alias | Pointer
--- | ---

Address:
Display
Aliases
P
Focus
Back
Clear

## Pointeur

Il peut être complexe de deviner à quelle adresse l'instruction va créer le bloc mémoire. Aussi, la zone de mémoire créée est constituée d'adresses contigües et donc facile à désigner avec un pointeur. En réalité, l'instruction retourne bien un pointeur sur la nouvelle zone mémoire, et ce pointeur peut être écrit en mémoire.

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory PTR
    		:memory ((INT*3, STR)*2, BLN)*2, PTR*4 -> &0
    	END
    END

Lancez de nouveau l'application en mode débugueur :

Memory
Memory - K main - P application

Address | Type | Value
--- | --- | ---
&0 | PTR | &1*22
&1 | INT |
&2 | INT |
&3 | INT |
&4 | STR |
&5 | INT |
&6 | INT |
&7 | INT |
&8 | STR |
&9 | BLN |
&10 | INT |
&11 | INT |
&12 | INT |
&13 | STR |
&14 | INT |
&15 | INT |
&16 | INT |
&17 | STR |
&18 | BLN |
&19 | PTR |
&20 | PTR |
&21 | PTR |
&22 | PTR |

Alias | Pointer
--- | ---

Address:
Display
Aliases
P
Focus
Back
Clear

La première allocation a créé un premier bloc contenant seulement un pointeur. Comme la mémoire est vide, ce bloc est placé à l'adresse `&0`.

Ensuite, la seconde allocation crée le même bloc que précédemment mais cette fois, le pointeur qui désigne la zone créée est placé à l'adresse `&0` : les différentes adresses de cette nouvelle zone peuvent être accédées à travers l'expression `(@&0/indice)`.

L'utilisation du pointeur retourné devient obligatoire lorsque la taille d'au moins un bloc ne peut pas être connue depuis le code :

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    ARGUMENT INT size
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory PTR, PTR
    		:memory INT*@&size -> &1
    		:memory STR*2 -> &2
    		"text" -> (@&2/0)
    	END
    	MEMORY size
    END

Lancez l'application ainsi transformée (avec ici 5 comme argument) dans le débugueur :

Memory
Memory - K main - P application

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

Alias | Pointer
--- | ---
size | &0*1

Address:
Display
Aliases
P
Focus
Back
Clear

## Alias

L'instruction d'allocation mémoire permet une dernière facilité pour localiser où se trouvent les adresses créées : elle peut définir des alias sur des portions du bloc.

Modifiez le code pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    ARGUMENT INT size
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory PTR/zone
    		:memory INT*@&size/array, (STR, BLN)/sub1, PTR, (STR, INT)*2/sub2 -> &zone
    	END
    	MEMORY size
    END

Lancez l'application dans le débugueur (ici, avec l'argument 3) :

Memory
Memory - K main - P application

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

Alias | Pointer
--- | ---
size | &0*1
zone | &1*1
array | &2*3
sub1 | &5*2
sub2 | &8*4

Address:
Display
Aliases
P
Focus
Back
Clear

La première allocation a créé l'alias "zone", et la seconde les alias "array", "sub1" et "sub2". Notez qu'il y a une adresse sans alias dans le bloc de la seconde allocation.

Ce mécanisme est très utile pour regrouper en une seule allocation mémoire plusieurs groupes d'adresses ayant des fonctions différentes.

- L'allocation mémoire est réalisée par l'instruction `:memory description_de_bloc_memoire`.
- La description du bloc est donnée par la liste des types à associer aux adresses créées. Cette liste peut être construite :
  - par une suite de types (séparés par des virgules `,`),
  - avec des regroupements (entre parenthèses `(` et `)`),
  - et des répétitions sur les types ou les regroupements (postfixés par une multiplication `*` et le nombre de répétitions).
- L'instruction détermine seule à quelle adresse commencera le bloc dans la mémoire, et retourne le pointeur designant la zone allouée.
- L'instruction peut associer en même temps des alias à des portions de la zone mémoire créée (sous-bloc postfixé par une division `/` et l'alias).

# Libération mémoire

Allouer de la mémoire est assez naturel lors de l'écriture d'une application. L'opération inverse, appellée libération mémoire, l'est beaucoup moins. Pourtant, il est crucial pour une application de contrôler avec précision les ressources mémoire qu'elle utilise.

Modifiez le code de l'application pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Memory example
    END
    LOG
    DEBUG "Memory" STYLE "default"
    PLUGIN "svmcom.so"
    ARGUMENT INT size
    PROCESS "application"
    	CODE "main" INLINE
    		:debug BREAK
    		:memory (PTR, INT)/parameters
    		@&size -> (parameters/1)
    		:call function parameters
    		:com.message @(parameters/1)
    		:shutdown

    	:label function
    		:memory INT -> &P
    		@(P/1) -> &@&P
    		:shift @&@&P (P/1)
    		:shift @&@&P (P/1)
    		:return
    	END
    	MEMORY size
    END

Lancez l'application en mode débugueur, et exécutez l'application pas à pas en observant à la fois le code et la mémoire :

Memory
Memory - K main - P application

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

Alias | Pointer
--- | ---
size | &0*1
parameters | &1*2

Address:
Display
Aliases
P
Focus
Back
Clear
Code main - K main - P application

    :debug BREAK
    :memory ( PTR , INT )/parameters
    @&size -> (parameters/1)
    :call function parameters
    :com.message @(parameters/1)
    :shutdown
    :label function
    :memory INT -> &P
    @(P/1) -> &@&P
    :shift @&@&P (P/1)
    :shift @&@&P (P/1)
    :return

Auto-scroll to
Current
with
above
Display

L'instruction `:return` nous intéresse particulièrement ici : en plus de sortir de la fonction, elle va supprimer toute la mémoire allouée dans la fonction et cela peut se constater aisément dans le débugueur.

La mémoire allouée en dehors de toute fonction sera quant à elle libérée automatiquement à la fin de l'exécution de l'application.

La libération de la mémoire est automatique :

- En dehors d'une fonction, la mémoire est libérée à la fin de l'exécution de l'application. La mémoire ainsi allouée est dite globale.
- La mémoire allouée durant l'exécution d'une fonction est libérée à la fin de la fonction par l'instruction `:return`. La mémoire ainsi allouée est dite locale à la fonction.

Notez également que les fonctions prennent une nouvelle responsabilité ici, et que le code exécuté dans une fonction peut accéder à plus de mémoire que son appellant : les fonctions font office de contexte pour l'exécution du code.

# Fonctions utilisant de la mémoire

Le mécanisme présenté ci-dessus permet aux fonctions d'utiliser de la mémoire pour leur propre fonctionnement. Cependant, pour que la fonction puisse être appellée depuis n'importe quel endroit du code, il faut respecter quelques principes pour l'accès à la mémoire locale de la fonction :

- l'utilisation des alias est à éviter : si la fonction est appellée dans un contexte où l'alias existe déjà, l'appel de la fonction échouera lors de la création de la mémoire locale,
- il faut réserver un pointeur parmi les paramètres de la fonction pour indiquer où se situe la mémoire locale à la fonction,
- il est préférable d'allouer la mémoire locale en une seule instruction `:memory`.

Lorsque le premier paramètre est réservé à la mémoire locale, l'accès à la mémoire devient systématique :

- pour les paramètres, via `(P/numéro_de_paramètre)` en partant de 1 pour les numéros de paramètres,
- pour la mémoire locale, via `(@&P/numéro_de_variable)` en partant de 0 pour les numéros de variable locale,
- pour la mémoire globale, via les alias.

Le code utilisé pour illustrer la libération mémoire implémente ce mécanisme. Observez la construction de la fonction et son appel.

Ces recommandations peuvent être ignorées ou partiellement suivies selon l'utilisation voulue de la fonction.

Une fonction appellable depuis n'importe où doit :

- Réserver un pointeur dans ses paramètres pour accéder à la mémoire locale à la fonction (Par exemple, le premier paramètre).
- Allouer la mémoire locale sans définir d'alias, et la rendre accessible depuis le pointeur `P` au travers du paramètre réservé pour la mémoire locale.
- Utiliser les alias uniquement pour accéder à la mémoire globale définie à tout moment dans l'exécution.

Le canevas de définition d'une fonction s'écrit donc :

    :label nom_fonction
    	:memory mémoire_locale -> &P
    	corps_fonction
    	:return

Le canevas d'appel de fonction (l'alias peut être remplacé par un pointeur sans alias si nécessaire) s'écrit donc :

    	:memory (PTR, paramètres)/alias
    	[ , initialisation_paramètres ] -> alias
    	:call nom_fonction alias

# Conclusion

Vous venez de voir les mécanismes de base de la gestion de la mémoire.

Le couple d'opérations allocation et libération a été conçu pour être le plus simple et le plus agréable possible : l'allocation ne demande qu'une description fonctionnelle de la mémoire requise et la libération est automatique, évitant le célèbre écueil des fuites mémoire.

Une gestion bien maîtrisée des ressources mémoires est un des piliers fondamentaux de la stabilité des applications qui doivent s'exécuter pendant longtemps (de l'ordre de l'année, par exemple), et une libération automatique et systématique de la mémoire est une aide précieuse pour atteindre une telle maîtrise.