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 comment définir vos propres instructions.
Le temps de lecture de ce didacticiel est estimé à 20 minutes.
Pour commencer, créez le canevas de l'extension dans le fichier instructions.svm_plugin en utilisant ce code :
PLUGIN instruction
DEFINE
Modifiez le fichier d'extension pour ajouter une première instruction :
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.simple
%{
std::cout << "Simple instruction called" << std::endl;
%}
Une instruction s'écrit simplement :
Générez le code C++ de l'extension, ouvrez le fichier "src/plugin.cpp" dans le dossier de l'extension, puis cherchez la fonction C++ qui correspond à l'instruction :
SVM_Value instruction_simple(const void *svm, SVM_Size argc, SVM_Parameter argv[])
SVM_Value
, ce qui indique qu'une instruction peut retourner une valeur.svm
. C'est ce paramètre qu'il faut transmettre à toutes les fonctions de l'interface programmatique.argc
et argv
que nous verrons juste après.Vous pouvez également compiler l'extension, puis écrire une application minimaliste qui appelle l'instruction :
./instructions.svm
Simple instruction called
%{
et %}
.svm
, argc
et argv
.Les variables magiques argc
et argv
permettent d'accéder aux arguments de l'instruction, un peu comme les arguments de ligne de commande avec la fonction "main" du C/C++ :
SVM_Parameter
, car certains arguments de l'instruction peuvent être des marqueurs ou des mots-clefs.Modifiez le fichier d'extension pour remplacer l'instruction simple par une autre :
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.parameters ( INT STR ) +
%{
long long int s = 0;
for(SVM_Size i = 0 ; i<argc ; i+=2)
{
SVM_Value_Integer vi = ::svm_parameter_value_get(svm,argv[i]);
SVM_Value_String vs = ::svm_parameter_value_get(svm,argv[i+1]);
long long int ri = ::svm_value_integer_get(svm,vi);
SVM_String rs = ::svm_value_string_get(svm,vs);
s += ri;
std::cout << std::string(rs.string,rs.size) << std::endl;
}
std::cout << "Sum: " << s << std::endl;
%}
Puis créez le fichier exécutable d'application dans le fichier instructions.svm :
#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmplugininstruction/libsvminstruction.so"
PROCESS "instructions"
CODE "main" INLINE
:instruction.parameters 1 "a" 2 "b" # argc = 4
:instruction.parameters 1 "a" 2 "b" 3 "c" 4 "d" 5 "e" # argc = 10
END
END
L'exécution de l'application donne :
./instructions.svm
a
b
Sum: 3
a
b
c
d
e
Sum: 15
Ici, les paramètres sont lus deux à deux :
svm_parameter_value_get
sur l'élément de argv
pour le transformer en valeur.Modifiez le fichier d'extension pour ajouter une instruction :
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.parameters ( INT STR ) +
%{
long long int s = 0;
for(SVM_Size i = 0 ; i<argc ; i+=2)
{
SVM_Value_Integer vi = ::svm_parameter_value_get(svm,argv[i]);
SVM_Value_String vs = ::svm_parameter_value_get(svm,argv[i+1]);
long long int ri = ::svm_value_integer_get(svm,vi);
SVM_String rs = ::svm_value_string_get(svm,vs);
s += ri;
std::cout << std::string(rs.string,rs.size) << std::endl;
}
std::cout << "Sum: " << s << std::endl;
%}
INSTRUCTION instruction.markers MARKER *
%{
for(SVM_Size i = 0 ; i<argc ; ++i)
{
SVM_String m = ::svm_parameter_marker_get(svm,argv[i]);
std::cout << std::string(m.string,m.size) << std::endl;
}
%}
Générez et compilez l'extension, puis modifiez le fichier d'application :
#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmplugininstruction/libsvminstruction.so"
PROCESS "instructions"
CODE "main" INLINE
:instruction.markers { < , > , <= , => , = , <> }
END
END
L'exécution donne maintenant :
./instructions.svm
{
<
,
>
,
<=
,
=>
,
=
,
<>
}
L'instruction affiche chaque marqueur placé en argument. Les marqueurs sont obtenus grâce à la fonction interface svm_parameter_marker_get
qui renvoient une chaine de caractères contenant le marqueur.
De la même manière, les mots-clefs peuvent être récupérés avec la fonction interface svm_parameter_keyword_get
:
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.parameters ( INT STR ) +
%{
long long int s = 0;
for(SVM_Size i = 0 ; i<argc ; i+=2)
{
SVM_Value_Integer vi = ::svm_parameter_value_get(svm,argv[i]);
SVM_Value_String vs = ::svm_parameter_value_get(svm,argv[i+1]);
long long int ri = ::svm_value_integer_get(svm,vi);
SVM_String rs = ::svm_value_string_get(svm,vs);
s += ri;
std::cout << std::string(rs.string,rs.size) << std::endl;
}
std::cout << "Sum: " << s << std::endl;
%}
INSTRUCTION instruction.markers MARKER *
%{
for(SVM_Size i = 0 ; i<argc ; ++i)
{
SVM_String m = ::svm_parameter_marker_get(svm,argv[i]);
std::cout << std::string(m.string,m.size) << std::endl;
}
%}
INSTRUCTION instruction.keywords KEYWORD *
%{
for(SVM_Size i = 0 ; i<argc ; ++i)
{
SVM_String k = ::svm_parameter_keyword_get(svm,argv[i]);
std::cout << std::string(k.string,k.size) << std::endl;
}
%}
Les mots-clefs sont également passés au code C/C++ sous forme de chaine de caractères.
Après génération et compilation de l'extension, vous pouvez modifier le code de l'application pour appeller cette instruction :
#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmplugininstruction/libsvminstruction.so"
PROCESS "instructions"
CODE "main" INLINE
:instruction.keywords A B C D E
END
END
Sans surprise, l'exécution donne :
./instructions.svm
A
B
C
D
E
Lorsque la nature de l'argument ne peut être garanti par l'expression rationnelle de l'instruction, un appel à svm_parameter_value_get
, svm_parameter_marker_get
ou svm_parameter_keyword_get
peut être fatal à l'exécution de l'instruction. Heureusement, l'interface permet de détecter la nature des paramètres.
Modifiez le fichier d'extension pour retirer les deux dernières instructions, et ajoutez une autre instruction :
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.parameters ( INT STR ) +
%{
long long int s = 0;
for(SVM_Size i = 0 ; i<argc ; i+=2)
{
SVM_Value_Integer vi = ::svm_parameter_value_get(svm,argv[i]);
SVM_Value_String vs = ::svm_parameter_value_get(svm,argv[i+1]);
long long int ri = ::svm_value_integer_get(svm,vi);
SVM_String rs = ::svm_value_string_get(svm,vs);
s += ri;
std::cout << std::string(rs.string,rs.size) << std::endl;
}
std::cout << "Sum: " << s << std::endl;
%}
INSTRUCTION instruction.detect . *
%{
for(SVM_Size i = 0 ; i<argc ; ++i)
{
if(::svm_parameter_type_is_value(svm,argv[i]))
{
std::cout << i << ": value" << std::endl;
continue;
}
if(::svm_parameter_type_is_marker(svm,argv[i]))
{
std::cout << i << ": marker" << std::endl;
continue;
}
if(::svm_parameter_type_is_keyword(svm,argv[i]))
{
std::cout << i << ": keyword" << std::endl;
continue;
}
ERROR_INTERNAL(FAILURE,"Unknown parameter type");
}
%}
Générez et compilez à nouveau l'extension, puis modifiez le code de l'application :
#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmplugininstruction/libsvminstruction.so"
PROCESS "instructions"
CODE "main" INLINE
:instruction.detect 1 < "a" IS "weird"
END
END
L'exécution donne :
./instructions.svm
0: value
1: marker
2: value
3: keyword
4: value
L'accès aux arguments d'une instruction est relativement fréquent dans les instructions. L'interface programmatique offre des fonctions qui permettent de réaliser toutes les opérations sur les arguments, mais au prix d'une certaine verbosité.
Pour cela, le générateur d'extension définit des macros C/C++ facilitant l'accès aux paramètres dans les cas les plus courants :
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.parameters ( INT STR ) +
%{
long long int s = 0;
for(SVM_Size i = 0 ; i<argc ; i+=2)
{
SVM_Value_Integer vi = ::svm_parameter_value_get(svm,argv[i]);
SVM_Value_String vs = ::svm_parameter_value_get(svm,argv[i+1]);
long long int ri = ::svm_value_integer_get(svm,vi);
SVM_String rs = ::svm_value_string_get(svm,vs);
s += ri;
std::cout << std::string(rs.string,rs.size) << std::endl;
}
std::cout << "Sum: " << s << std::endl;
%}
INSTRUCTION instruction.macros INT MARKER STR KEYWORD BLN
%{
long long int i = ARGV_VALUE(0,integer);
std::string m = ARGV_MARKER(1);
SVM_String s = ARGV_VALUE(2,string);
std::string k = ARGV_KEYWORD(3);
SVM_Boolean b = ARGV_VALUE(4,boolean);
std::cout << i << " " << m << " " << std::string(s.string,s.size) << " " << k << " " << (b?"TRUE":"FALSE") << std::endl;
%}
Après avoir généré et compilé l'extension, modifiez le code de l'application :
#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmplugininstruction/libsvminstruction.so"
PROCESS "instructions"
CODE "main" INLINE
:instruction.macros 1 < "a" IS FALSE
END
END
Puis exécutez l'application :
./instructions.svm
1 < a IS FALSE
Ici, les macros permettent :
argc
(nombre d'arguments) et argv
(tableau d'arguments).svm_parameter_value_get
, svm_parameter_marker_get
et svm_parameter_keyword_get
permettent d'accéder aux arguments.svm_parameter_type_is_value
, svm_parameter_type_is_marker
et svm_parameter_type_is_keyword
permettent de connaître la nature d'un argument.Les instructions peuvent renvoyer des valeurs. Pour cela, le prototype de l'instruction doit indiquer une valeur de retour. Dans ce cas, le code doit également retourner une valeur :
PLUGIN instruction
includes:
%{
#include <iostream>
%}
DEFINE
INSTRUCTION instruction.parameters ( INT STR ) + -> INT
%{
long long int s = 0;
for(SVM_Size i = 0 ; i<argc ; i+=2)
{
auto ri = ARGV_VALUE(i,integer);
auto rs = ARGV_VALUE(i+1,string);
s += ri;
std::cout << RAW_STRING(rs) << std::endl;
}
return NEW_VALUE(integer,s);
%}
Générez et compilez à nouveau l'extension, puis modifiez le code de l'application :
#!/usr/bin/env svm
LOG
PLUGIN "svmcom.so"
LOCAL PLUGIN "svmplugininstruction/libsvminstruction.so"
PROCESS "instructions"
CODE "main" INLINE
:memory INT/i
:instruction.parameters 1 "a" 2 "b" 3 "c" -> &i
:com.message "[" @&i "]"
END
END
Cette fois, l'exécution donne :
./instructions.svm
a
b
c
[6]
Cette fois, la dernière ligne construit une valeur de type entier et retourne cette valeur à la machine.
Notez que lorsqu'une instruction ne retourne pas de valeur, la fonction C/C++ associée doit toujours renvoyer un SVM_Value
. Par convention, il faut retourner un pointeur nul (au sens C/C++) dans un tel cas. La macro RETURN
permet de garder un code cohérent tout en renvoyant ce pointeur nul.
RETURN
pour sortir de l'instruction.Une instruction d'extension peut être classée comme système (son exécution devient alors interdite dans un noyau protégé) simplement en ajoutant le drapeau SYSTEM
devant le mot INSTRUCTION
. Certaines fonctions de l'interface programmatique vérifient qu'elles sont exécutées dans une instruction système.
Modifiez le code de l'extension pour ajouter le drapeau à l'instruction, et exécutez le code en mode débugueur. Dans le débugueur, le drapeau apparaît dans la définition de l'instruction.
Une instruction d'extension peut être notée comme pouvant attendre un événement extérieur simplement en ajoutant WAITING
devant le mot INSTRUCTION
. Notez que certaines fonctions de l'interface programmatique peuvent vérifier que l'instruction porte bien le drapeau d'attente, car elles réalisent une telle attente.
Modifiez le code de l'extension pour ajouter le drapeau à l'instruction, et exécutez le code en mode débugueur. Dans le débugueur, le drapeau apparaît dans la définition de l'instruction.
SYSTEM
au début de sa définition.WAITING
au début de sa définition.Vous venez de voir comment créer vos propres instructions pour la Simple Virtual Machine.
L'écriture d'instructions personnalisées est le cœur de l'utilisation de la machine virtuelle pour des applications conséquentes.
De même, la majorité des fonctionnalités fournies avec la machine sont des instructions d'extension, et il est très confortable de pouvoir les utiliser sans avoir à les réimplémenter.
Une application conséquente écrite avec la Simple Virtual Machine devrait contenir au moins une extension dédiée à l'application avec des instructions spécifiques pour le fonctionnement de cette application.
Enfin, grâce à cette capacité à introduire rapidement des instructions fait de la machine virtuelle un orchestrateur de fonctions écrites en C/C++, au même titre qu'un shell orchestre des processus.