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.
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
Avant de modifier le code, prenez quelques minutes pour comprendre ce que fait la fonction "sequence" :
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 :
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 !
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 :
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.
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 :
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.
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 :
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.
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" :
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.
: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é !).: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.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.