Introduction

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.

Mise en place

Pour commencer, créez le canevas de l'extension dans le fichier instructions.svm_plugin en utilisant ce code :

PLUGIN instruction

DEFINE

Prototype

Objet instruction

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 :

  1. en écrivant le prototype de l'instruction tel qu'il apparaît dans le débugueur dans la fenêtre des extensions,
  2. suivi d'un bloc de code en C/C++ réalisant la fonctionnalité.

Variables magiques

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[])

Vous pouvez également compiler l'extension, puis écrire une application minimaliste qui appelle l'instruction :

./instructions.svm
Simple instruction called

Paramètres

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++ :

Valeurs

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 :

Marqueurs

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.

Mots-clef

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

Détection de paramètre

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

Macros

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 :

Valeur de retour

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.

Drapeaux

Système

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.

Attente

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.

Conclusion

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.