Ce didacticiel est destiné aux utilisateurs maîtrisant la programmation en C ou C++, ainsi que l'architecture de la Simple Virtual Machine.
Dans ce didacticiel, vous allez découvrir la manipulation des valeurs mémoire de la machine virtuelle.
Le temps de lecture de ce didacticiel est estimé à 25 minutes.
Pour commencer, créez le canevas de l'extension dans le fichier valeurs.svm_plugin en utilisant ce code :
PLUGIN value
DEFINE
INSTRUCTION value.example VALUE
%{
%}
Modifiez le code de l'instruction :
PLUGIN value
DEFINE
INSTRUCTION value.example VALUE
%{
SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
%}
Ici, le prototype indique que le premier paramètre est une valeur, sans préciser son type : le code mentionne ce fait en utilisant le type SVM_Value
.
Dans le cas où le type du paramètre est connu, la variable Simple Virtual Machine peut utiliser un type parmi :
SVM_Value_Integer
SVM_Value_String
SVM_Value_Boolean
SVM_Value_Pointer
SVM_Value_Library
SVM_Value_Symbol
SVM_Value_PluginEntryPoint
SVM_Value_Interruption
SVM_Value_Plugin
(ce type est en réalité la classe de toutes les valeurs extensions)SVM_Value_Automatic
Cela permet d'indiquer le type de la valeur dans le code C ou C++, mais n'altère en rien le fonctionnement de l'extension, le type réel de la valeur n'étant pas déduit du type de la variable Simple Virtual Machine.
La portée des valeurs est exactement la même que les variables Simple Virtual Machine : les fonctions svm_variable_scope_set_global
, svm_variable_scope_set_local
, svm_variable_delete
, svm_variable_scope_set_shared
et svm_variable_scope_is_local
peuvent être employées sur les valeurs.
Les valeurs sont des variables Simple Virtual Machine qui contiennent tout ce qui peut être écrit dans une mémoire de la machine virtuelle.
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.example BLN -> INT ?
%{
SVM_Boolean b = ARGV_VALUE(0,boolean);
SVM_Value_Integer n = ::svm_value_integer_new_null(svm);
SVM_Value_Integer i = ::svm_value_integer_new(svm,17);
return b?i:n;
%}
Cette instruction renvoie ici soit un entier avec une valeur, soit un entier nul. Pour vous en convaincre, vous pouvez utiliser cette petite application après avoir généré et compilé l'extension :
#!/usr/bin/env svm
LOG
DEBUG "values"
LOCAL PLUGIN "svmpluginvalue/libsvmvalue.so"
PLUGIN "svmrun.so"
PROCESS "values"
CODE "main" INLINE
:memory INT*2/i
[ 0 , 0 ] -> i
:value.example TRUE -> (i/0)
:value.example FALSE -> (i/1)
:run.coredump STDOUT
END
END
Pour chaque type, une fonction de l'interface programmatique permet de créér une valeur nulle de ce type : cette fonction utilise le canevas svm_value_new_null_<type>
.
Pour chaque type sauf le type automatique, une fonction de l'interface programmatique permet de créér une valeur de ce type : cette fonction utilise le canevas svm_value_new_<type>
ou des variantes de ce canevas.
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.example [ INT STR BLN ] -> BLN
%{
SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
if(::svm_value_type_is_integer(svm,v))
{
return NEW_VALUE(boolean,TRUE);
}
else if(::svm_value_type_is_string(svm,v))
{
return NEW_VALUE(boolean,FALSE);
}
else //if(::svm_value_type_is_boolean(svm,v))
{
return v;
}
%}
Comme cette instruction peut avoir plusieurs types possibles pour son premier paramètre, il peut être nécessaire de vérifier le type de la valeur avant de pouvoir manipuler la valeur.
Notez qu'il est aussi possible de récupérer le type d'une valeur avec la fonction svm_value_type_get_internal
, ainsi que la fonction svm_value_type_get_external
lorsque la valeur a un type défini dans une extension.
Modifiez le code de l'extension :
PLUGIN value
code:
%{
SVM_Value _v;
%}
initialisation:
%{
_v = ::svm_value_automatic_new_null(svm);
::svm_variable_scope_set_global(svm,_v);
%}
finalisation:
%{
::svm_variable_scope_set_local(svm,_v);
%}
DEFINE
INSTRUCTION value.set VALUE
%{
SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
::svm_variable_scope_set_local(svm,_v);
_v = v;
::svm_variable_scope_set_global(svm,_v);
%}
INSTRUCTION value.get -> VALUE
%{
return _v;
%}
INSTRUCTION value.test -> BLN
%{
return NEW_VALUE(boolean,::svm_value_state_is_null(svm,_v));
%}
Vous pouvez générer puis compiler l'extension, puis la tester avec cette application :
#!/usr/bin/env svm
LOG
DEBUG "values"
LOCAL PLUGIN "svmpluginvalue/libsvmvalue.so"
PLUGIN "svmrun.so"
PROCESS "values"
CODE "main" INLINE
:memory BLN*2/b
:value.test -> (b/0)
:value.set 17
:value.test -> (b/1)
:run.coredump STDOUT
END
END
Ici, la fonction svm_value_state_is_null
permet de savoir si la variable valeur contient effectivement une valeur.
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.const VALUE -> BLN
%{
SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
return NEW_VALUE(boolean,::svm_value_state_is_constant(svm,v));
%}
Après génération et compilation de l'extension, exécutez cette application :
#!/usr/bin/env svm
LOG
DEBUG "values"
LOCAL PLUGIN "svmpluginvalue/libsvmvalue.so"
PLUGIN "svmrun.so"
PROCESS "values"
CODE "main" INLINE
:memory BLN*2/b
:value.const FALSE -> (b/0)
:value.const @(b/0) -> (b/1)
:run.coredump STDOUT
END
END
Ici, l'instruction renvoie un booléen vrai lorsque la valeur passée en paramètre est une valeur présente dans le code de l'application, et faux sinon.
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.compare VALUE [ = <> < <= > => ] VALUE -> BLN
%{
SVM_Value l = ::svm_parameter_value_get(svm,argv[0]);
auto o = ARGV_MARKER(1);
SVM_Value r = ::svm_parameter_value_get(svm,argv[2]);
SVM_Value_Comparison c = ::svm_value_compare(svm,l,r);
if(c.weak)
::svm_machine_trace__raw(svm,"Comparison is weak");
if(o=="=")
return NEW_VALUE(boolean,c.equal);
if(o=="<>")
return NEW_VALUE(boolean,c.different);
if(not c.order)
ERROR_INTERNAL(FAILURE,"Invalid operation");
if(not c.total)
ERROR_INTERNAL(FAILURE,"Order is partial");
if(o=="<")
return NEW_VALUE(boolean,c.inferior);
if(o=="<=")
return NEW_VALUE(boolean,c.inferior_or_equal);
if(o==">")
return NEW_VALUE(boolean,c.superior);
if(o=="=>")
return NEW_VALUE(boolean,c.superior_or_equal);
%}
Après génération et compilation de l'extension, exécutez cette application :
#!/usr/bin/env svm
LOG
DEBUG "values"
LOCAL PLUGIN "svmpluginvalue/libsvmvalue.so"
PLUGIN "svmreal.so"
PLUGIN "svmrun.so"
PROCESS "values"
CODE "main" INLINE
:memory BLN*10/b
:interruption FAILURE ignore
:symbol a
:value.compare 1 = 1 -> (b/0)
:value.compare 1 = 2 -> (b/1)
:value.compare 1 < 2 -> (b/2)
:value.compare 1 = CONST real.number "1" -> (b/3)
:value.compare CONST real.number "1" < CONST real.number "2" -> (b/4)
:value.compare &3*5 <> &4*2 -> (b/5)
:value.compare $"a" = $"a" -> (b/6)
:value.compare $"a" = $"b" -> (b/7)
:value.compare $"a" < $"b" -> (b/8)
:value.compare TRUE <= FALSE -> (b/9)
:symbol b
:run.coredump STDOUT
:shutdown
:label ignore
:return
END
END
Le résultat de cette application montre que la fonction svm_value_compare
:
svm_value_type_get_internal
et svm_value_type_get_external
, où de tester si la valeur est d'un type précis avec la fonction svm_value_is_<type>
.svm_value_state_is_null
.svm_value_state_is_constant
.svm_value_compare
qui supporte l'équivalence, l'ordre total et l'ordre partiel. La comparaison peut être forte ou faible selon l'implémentation.Pour manipuler une valeur en C ou C++, il faut avant tout convertir les variables valeur en types C ou C++. Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.in INT PTR -> BLN
%{
SVM_Value_Integer v = ::svm_parameter_value_get(svm,argv[0]);
auto i = ::svm_value_integer_get(svm,v);
SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[1]);
SVM_Address a = ::svm_value_pointer_get_address(svm,p);
SVM_Size s = ::svm_value_pointer_get_size(svm,p);
return ::svm_value_boolean_new__raw(svm,((i>=a) and (i<(a+s))));
%}
Ce sont les fonctions svm_value_get_<type>
et leurs variantes qui permettent de récupérer les valeurs en C ou C++ pour les exploiter.
Notez ici que pour une valeur générique comme un entier, la récupération de la valeur se fait dans un type du C ou C++. Pour une valeur spécifique à la machine comme un pointeur, la récupération de la valeur se fait dans des types de l'interface programmatique.
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.increment MUTABLE INT
%{
SVM_Value_Integer v = ::svm_parameter_value_get(svm,argv[0]);
auto i = ::svm_value_integer_get(svm,v);
++i;
::svm_value_integer_set(svm,v,i);
%}
Ici, la fonction à l'honneur est la fonction svm_value_set_<type>
, qui permet de remplacer la valeur directement dans la variable.
Notez ici la présence du mot-clef MUTABLE
dans le prototype de l'instruction : il évite les appels de l'instruction avec des valeurs constantes, ce qui ferait échouer la fonction svm_value_integer_set
dans ce contexte.
svm_value_get_<type>
et leurs variantes permettent d'exploiter des valeurs en C ou C++.svm_value_set_<type>
et leurs variantes permettent de remplacer le contenu d'une valeur non constante.Modifiez le code de l'extension :
PLUGIN value
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION value.display VALUE
%{
SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
SVM_String s = ::svm_value_print(svm,v);
std::cout << "[Value=" << RAW_STRING(s) << "]" << std::endl;
%}
Générez et compilez l'extension, puis utilisez l'instruction dans une petite application de test de votre crû.
La fonction svm_value_print
permet de récupérer la forme textuelle d'une valeur, telle qu'elle apparaît dans le débugueur.
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.shift INT PTR -> PTR
%{
auto i = ARGV_VALUE(0,integer);
SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[1]);
SVM_Size s = ::svm_value_pointer_get_size(svm,p);
SVM_Value c = ::svm_value_copy(svm,p);
::svm_value_pointer_set_addresssize__raw(svm,c,i,s);
return c;
%}
Ici, l'usage de la fonction svm_value_copy
est intéressant, car il permet à cette instruction de fonctionner sur un pointeur constant passé en argument.
La fonction svm_value_copy
permet d'obtenir une copie d'une valeur. La copie est une nouvelle instance de la valeur. Cette fonction échoue avec une interruption si la valeur est d'un type extension dont la fonction de copie n'est pas définie.
Lorsqu'une valeur est écrite en mémoire, elle est copiée par défaut. Cela peut être gênant lorsque la valeur ne peut pas être copiée. Dans ce cas, il est possible de marquer une valeur comme étant déplaçable en mémoire : cela signifie que la valeur sera placée une fois en mémoire directement, sans aucune copie. Une seconde écriture en mémoire forcera une copie.
C'est notamment le cas par défaut pour les valeurs de retour des instructions, ce qui permet à une instruction de retourner une valeur de type extension sans fonction de copie créée par l'instruction d'être placée en mémoire !
Modifiez le code de l'extension :
PLUGIN value
DEFINE
INSTRUCTION value.move INT PTR -> PTR
%{
auto i = ARGV_VALUE(0,integer);
SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[1]);
SVM_Size s = ::svm_value_pointer_get_size(svm,p);
SVM_Value c = ::svm_value_copy(svm,p);
::svm_value_state_set_movable(svm,c);
::svm_value_pointer_set_addresssize__raw(svm,c,i,s);
return c;
%}
Ici, la valeur copiée est explicitement marquée comme déplaçable. (Ce qui aurait été fait de toute manière au retour de l'instruction.)
svm_value_state_set_movable
.Vous venez de voir comment manipuler les variables Simple Virtual Machine qui contiennent des valeurs mémoire.
La gestion des valeurs de la machine virtuelle est essentielle pour l'écriture des instructions extension. En particulier car ces valeurs font partie de l'interface entre le code de l'instruction et le code de la machine virtuelle.
Connaître l'existence et le fonctionnement des différentes fonctions de l'interface programmatique dédiées aux valeurs est important pour une gestion raisonnable des valeurs de la machine virtuelle.
Vous avez maintenant en main tous les concepts vous permettant d'écrire vos premières instructions extension, celles permettant de réaliser des calculs sans altérer la machine virtuelle.