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 la manipulation des valeurs mémoire de la machine virtuelle.

Le temps de lecture de ce didacticiel est estimé à 25 minutes.

Mise en place

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
%{
%}

Variables Simple Virtual Machine

Types

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 :

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.

Portée et destruction

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.

Opérations

Création

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.

Tests

Types

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.

Nullité

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.

Constance

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.

Comparaison

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 :

Valeur

Récupération

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.

Modification

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.

Affichage

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.

Copie

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.

Déplacement

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.)

Conclusion

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.