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 créer et manipuler des séquenceurs 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 sequenceurs.svm_plugin en utilisant ce code :

PLUGIN sequencer

DEFINE

Définition d'un séquenceur

Structure

Un séquenceur doit être capable de gérer plusieurs noyaux, en règle générale : pour cela, une structure de données servant à la gestion des noyaux peut être très utile. Le générateur d'extension offre des facilités pour définir une telle structure. Modifiez le code de l'extension :

PLUGIN sequencer

DEFINE

SEQUENCER sequencer.instance
%{
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}

Comme pour les types extensions, le contenu de la structure du séquenceur suit la déclaration de l'objet séquenceur. Ici, la struct C/C++ générée a pour type le nom formé de la chaine fixe "sequencer_" suivie du nom de l'entrée du séquenceur (ce qui donne ici le type sequencer_instance).

Construction et destruction

Pour construire et détruire cette structure, deux fonctions doivent être définies. Modifiez le code de l'extension :

PLUGIN sequencer

DEFINE

SEQUENCER sequencer.instance
%{
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}
create default:
%{
	object->_main = nullptr;
	object->_interrupted = false;
	object->_success = nullptr;
	object->_failure = nullptr;
%}
delete default:
%{
	if(object->_main) VARIABLE_LOCAL(object->_main);
	if(object->_success) VARIABLE_LOCAL(object->_success);
	if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}

Les options default permettent de générer du code de construction et destruction classique.

Politique de séquencement

La prochaine fonction à définir contient la politique de séquencement, et par conséquent, est la fonction centrale du séquenceur. Modifiez le code de l'extension :

PLUGIN sequencer

DEFINE

SEQUENCER sequencer.instance
%{
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}
create default:
%{
	object->_main = nullptr;
	object->_interrupted = false;
	object->_success = nullptr;
	object->_failure = nullptr;
%}
delete default:
%{
	if(object->_main) VARIABLE_LOCAL(object->_main);
	if(object->_success) VARIABLE_LOCAL(object->_success);
	if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
	if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
	if(not object->_interrupted)
	{
		if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
	}
	else
	{
		if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
	}
	return nullptr;
%}

L'option object permet de générer du code de conversion en C/C++ pour avoir un accès direct à l'objet du séquenceur dans la variable magique object.

Le fonctionnement de cette fonction est plutôt simple : lorsqu'elle est invoquée, elle doit simplement retourner le prochain noyau à exécuter. Lorsque plus aucun noyau ne doit être exécuté, la fonction doit simplement renvoyer un pointeur C/C++ nul. Cela met alors fin à l'exécution du processus contenant le séquenceur.

Il est fortement conseillé d'écrire une fonction de séquencement qui soit non bloquante, sous peine de bloquer l'exécution du code dans la machine virtuelle.

Attachement et détachement

Pour qu'un noyau soit pris en charge par un séquenceur, il est obligatoire de l'attacher à ce séquenceur. De même, lorsqu'un noyau arrive en fin de vie ou sur requête explicite, le noyau peut être détaché pour être soustrait au séquenceur. Modifiez le code de l'extension :

PLUGIN sequencer

DEFINE

SEQUENCER sequencer.instance
%{
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}
create default:
%{
	object->_main = nullptr;
	object->_interrupted = false;
	object->_success = nullptr;
	object->_failure = nullptr;
%}
delete default:
%{
	if(object->_main) VARIABLE_LOCAL(object->_main);
	if(object->_success) VARIABLE_LOCAL(object->_success);
	if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
	if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
	if(not object->_interrupted)
	{
		if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
	}
	else
	{
		if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
	}
	return nullptr;
%}
attach object:
%{
	if(argc==0)
	{
		if(object->_main) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_main = kernel;
		return TRUE;
	}
	SVM_Boolean b = ARGV_VALUE(0,boolean);
	if(b)
	{
		if(object->_success) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_success = kernel;
	}
	else
	{
		if(object->_failure) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_failure = kernel;
	}
	return TRUE;
%}
detach object:
%{
	if(kernel==object->_main)
	{
		VARIABLE_LOCAL(kernel); object->_main = nullptr;
		object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
		return TRUE;
	}
	if(kernel==object->_success)
	{
		VARIABLE_LOCAL(kernel); object->_success = nullptr;
		return TRUE;
	}
	if(kernel==object->_failure)
	{
		VARIABLE_LOCAL(kernel); object->_failure = nullptr;
		return TRUE;
	}
	return FALSE;
%}

La fonction d'attachement a deux possibilités :

La fonction de détachement a également deux possibilités :

Affichage

La dernière fonction à créer sert à indiquer l'état du séquenceur. Modifiez le code de l'extension :

PLUGIN sequencer

DEFINE

SEQUENCER sequencer.instance
%{
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}
create default:
%{
	object->_main = nullptr;
	object->_interrupted = false;
	object->_success = nullptr;
	object->_failure = nullptr;
%}
delete default:
%{
	if(object->_main) VARIABLE_LOCAL(object->_main);
	if(object->_success) VARIABLE_LOCAL(object->_success);
	if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
	if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
	if(not object->_interrupted)
	{
		if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
	}
	else
	{
		if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
	}
	return nullptr;
%}
attach object:
%{
	if(argc==0)
	{
		if(object->_main) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_main = kernel;
		return TRUE;
	}
	SVM_Boolean b = ARGV_VALUE(0,boolean);
	if(b)
	{
		if(object->_success) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_success = kernel;
	}
	else
	{
		if(object->_failure) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_failure = kernel;
	}
	return TRUE;
%}
detach object:
%{
	if(kernel==object->_main)
	{
		VARIABLE_LOCAL(kernel); object->_main = nullptr;
		object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
		return TRUE;
	}
	if(kernel==object->_success)
	{
		VARIABLE_LOCAL(kernel); object->_success = nullptr;
		return TRUE;
	}
	if(kernel==object->_failure)
	{
		VARIABLE_LOCAL(kernel); object->_failure = nullptr;
		return TRUE;
	}
	return FALSE;
%}
print object:
%{
	std::ostringstream oss;
	oss << "Main kernel: ";
	if(object->_main)
	{
		SVM_String m = ::svm_kernel_print(svm,object->_main);
		oss << RAW_STRING(m);
	}
	oss << std::endl << "Main kernel " << (object->_interrupted?"":"not ") << "interrupted"
	    << std::endl << "Success kernel: ";
	if(object->_success)
	{
		SVM_String s = ::svm_kernel_print(svm,object->_success);
		oss << RAW_STRING(s);
	}
	oss << std::endl << "Failure kernel: ";
	if(object->_failure)
	{
		SVM_String f = ::svm_kernel_print(svm,object->_failure);
		oss << RAW_STRING(f);
	}
	oss << std::endl;
	return NEW_STRING(oss.str());
%}

Vous pouvez générer et compiler l'extension. Cependant, plutôt que d'utiliser directement l'extension, il est conseillé de regarder le code produit par le générateur d'extension.

Cette fonction doit simplement produire une chaine de caractères de type SVM_String contenant de préférence au moins la liste des noyaux attachés au séquenceur avec leurs états.

Utilisation

Utilisation basique

Pour pouvoir utiliser pleinement ce séquenceur, il faut ajouter une petite instruction pour attacher des noyaux supplémentaires :

PLUGIN sequencer

DEFINE

SEQUENCER sequencer.instance
%{
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}
create default:
%{
	object->_main = nullptr;
	object->_interrupted = false;
	object->_success = nullptr;
	object->_failure = nullptr;
%}
delete default:
%{
	if(object->_main) VARIABLE_LOCAL(object->_main);
	if(object->_success) VARIABLE_LOCAL(object->_success);
	if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
	if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
	if(not object->_interrupted)
	{
		if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
	}
	else
	{
		if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
	}
	return nullptr;
%}
attach object:
%{
	if(argc==0)
	{
		if(object->_main) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_main = kernel;
		return TRUE;
	}
	SVM_Boolean b = ARGV_VALUE(0,boolean);
	if(b)
	{
		if(object->_success) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_success = kernel;
	}
	else
	{
		if(object->_failure) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_failure = kernel;
	}
	return TRUE;
%}
detach object:
%{
	if(kernel==object->_main)
	{
		VARIABLE_LOCAL(kernel); object->_main = nullptr;
		object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
		return TRUE;
	}
	if(kernel==object->_success)
	{
		VARIABLE_LOCAL(kernel); object->_success = nullptr;
		return TRUE;
	}
	if(kernel==object->_failure)
	{
		VARIABLE_LOCAL(kernel); object->_failure = nullptr;
		return TRUE;
	}
	return FALSE;
%}
print object:
%{
	std::ostringstream oss;
	oss << "Main kernel: ";
	if(object->_main)
	{
		SVM_String m = ::svm_kernel_print(svm,object->_main);
		oss << RAW_STRING(m);
	}
	oss << std::endl << "Main kernel " << (object->_interrupted?"":"not ") << "interrupted"
	    << std::endl << "Success kernel: ";
	if(object->_success)
	{
		SVM_String s = ::svm_kernel_print(svm,object->_success);
		oss << RAW_STRING(s);
	}
	oss << std::endl << "Failure kernel: ";
	if(object->_failure)
	{
		SVM_String f = ::svm_kernel_print(svm,object->_failure);
		oss << RAW_STRING(f);
	}
	oss << std::endl;
	return NEW_STRING(oss.str());
%}

INSTRUCTION sequencer.attach BLN [ LIB SYM ]
%{
	SVM_Value_Boolean b = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Value c = ::svm_parameter_value_get(svm,argv[1]);
	SVM_Kernel k = nullptr;
	if(::svm_value_type_is_library(svm,c))
	{
		SVM_Code code = ::svm_value_library_get_code(svm,c);
		k = ::svm_kernel_new_code(svm,FALSE,FALSE,FALSE,nullptr,code);
		for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
		{
			::svm_processor_interruptionhandler_set_local_local(svm,k,*irq,0,FALSE);
		}
	}
	else
	{
		k = ::svm_kernel_new_symbol(svm,FALSE,FALSE,FALSE,nullptr,c);
		for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
		{
			::svm_processor_interruptionhandler_set_local_global(svm,k,*irq,c,FALSE);
		}
	}
	SVM_Parameter *params = ::svm_parameter_array_new(svm,1);
	params[0] = ::svm_parameter_value_new(svm,b);
	if(not ::svm_process_kernel_attach(svm,CURRENT(process),k,1,params))
	{
		ERROR_INTERNAL(FAILURE,"Unable to attach kernel");
	}
%}

Générez et compilez l'extension.

Créez un fichier exécutable pour y placer un exemple d'application permettant d'illustrer le fonctionnement de ce séquenceur :

#!/usr/bin/env svm
LOG SERVER "localhost" "8081" QUIET
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginsequencer/libsvmsequencer.so"
OPTION -i FLAG irq
PROCESS "test"
	CODE "main" INLINE
		:memory LIB*2/l
		:library "success" ":com.message \"success\"\n" -> (l/0)
		:library "failure" ":com.message \"failure\"\n" -> (l/1)
		:sequencer.attach TRUE @(l/0)
		:sequencer.attach FALSE @(l/1)
		:debug BREAK
		:com.message "main"
		:shutdown :unless @&irq TRUE
		:run.interrupt FAILURE
	END
	MEMORY irq
	SEQUENCER sequencer.instance
END

Vous pouvez appeler cette application :

./sequencer.svm
main
success
./sequencer.svm -i
main
failure

Pour faire simple, ce séquenceur réalise un try ... catch à un niveau élévé dans l'architecture de la machine virtuelle.

Différentes politiques de séquencement

Le séquenceur présenté ici est relativement simple. En particulier, il ne peut accepter qu'au maximum trois noyaux.

D'autres séquenceurs peuvent avoir une politique de séquencement qui autorisent autant de noyaux que nécessaires, comme le séquenceur run.stack qui utilise une pile (LIFO) de noyaux pour enchainer des noyaux comme des appels de fonction, ou comme le séquenceur inline.sequencer qui utilise une liste de noyaux pour réaliser une séquence de noyaux avec un objectif commun.

Evidemment, dans l'absolu, n'importe quelle politique de séquencement peut être écrite pour satisfaire aux besoins particuliers d'une application.

Altération d'un séquenceur

Il peut être parfois utile de modifier la structure d'un séquenceur en dehors des appels au séquenceur, via une instruction. Modifiez le code de l'extension :

PLUGIN sequencer

includes:
%{
#include <mutex>
%}

DEFINE

SEQUENCER sequencer.instance
%{
	mutable std::mutex _mutex;
	SVM_Kernel _main;
	bool _interrupted;
	SVM_Kernel _success;
	SVM_Kernel _failure;
%}
create default:
%{
	std::lock_guard<std::mutex> protection(object->_mutex);
	object->_main = nullptr;
	object->_interrupted = false;
	object->_success = nullptr;
	object->_failure = nullptr;
%}
delete default:
%{
	std::lock_guard<std::mutex> protection(object->_mutex);
	if(object->_main) VARIABLE_LOCAL(object->_main);
	if(object->_success) VARIABLE_LOCAL(object->_success);
	if(object->_failure) VARIABLE_LOCAL(object->_failure);
%}
current object:
%{
	std::lock_guard<std::mutex> protection(object->_mutex);
	if(object->_main and ::svm_kernel_is_runnable(svm,object->_main)) return object->_main;
	if(not object->_interrupted)
	{
		if(object->_success and ::svm_kernel_is_runnable(svm,object->_success)) return object->_success;
	}
	else
	{
		if(object->_failure and ::svm_kernel_is_runnable(svm,object->_failure)) return object->_failure;
	}
	return nullptr;
%}
attach object:
%{
	std::lock_guard<std::mutex> protection(object->_mutex);
	if(argc==0)
	{
		if(object->_main) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_main = kernel;
		return TRUE;
	}
	SVM_Boolean b = ARGV_VALUE(0,boolean);
	if(b)
	{
		if(object->_success) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_success = kernel;
	}
	else
	{
		if(object->_failure) return FALSE;
		VARIABLE_GLOBAL(kernel); object->_failure = kernel;
	}
	return TRUE;
%}
detach object:
%{
	std::lock_guard<std::mutex> protection(object->_mutex);
	if(kernel==object->_main)
	{
		VARIABLE_LOCAL(kernel); object->_main = nullptr;
		object->_interrupted =::svm_kernel_get_state(svm,kernel)==INTERRUPTED;
		return TRUE;
	}
	if(kernel==object->_success)
	{
		VARIABLE_LOCAL(kernel); object->_success = nullptr;
		return TRUE;
	}
	if(kernel==object->_failure)
	{
		VARIABLE_LOCAL(kernel); object->_failure = nullptr;
		return TRUE;
	}
	return FALSE;
%}
print object:
%{
	std::lock_guard<std::mutex> protection(object->_mutex);
	std::ostringstream oss;
	oss << "Main kernel: ";
	if(object->_main)
	{
		SVM_String m = ::svm_kernel_print(svm,object->_main);
		oss << RAW_STRING(m);
	}
	oss << std::endl << "Main kernel " << (object->_interrupted?"":"not ") << "interrupted"
	    << std::endl << "Success kernel: ";
	if(object->_success)
	{
		SVM_String s = ::svm_kernel_print(svm,object->_success);
		oss << RAW_STRING(s);
	}
	oss << std::endl << "Failure kernel: ";
	if(object->_failure)
	{
		SVM_String f = ::svm_kernel_print(svm,object->_failure);
		oss << RAW_STRING(f);
	}
	oss << std::endl;
	return NEW_STRING(oss.str());
%}

INSTRUCTION sequencer.attach BLN [ LIB SYM ]
%{
	SVM_Value_Boolean b = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Value c = ::svm_parameter_value_get(svm,argv[1]);
	SVM_Kernel k = nullptr;
	if(::svm_value_type_is_library(svm,c))
	{
		SVM_Code code = ::svm_value_library_get_code(svm,c);
		k = ::svm_kernel_new_code(svm,FALSE,FALSE,FALSE,nullptr,code);
		for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
		{
			::svm_processor_interruptionhandler_set_local_local(svm,k,*irq,0,FALSE);
		}
	}
	else
	{
		k = ::svm_kernel_new_symbol(svm,FALSE,FALSE,FALSE,nullptr,c);
		for(SVM_Value_Interruption *irq = ::svm_machine_list_interruption(svm) ; *irq ; ++irq)
		{
			::svm_processor_interruptionhandler_set_local_global(svm,k,*irq,c,FALSE);
		}
	}
	SVM_Parameter *params = ::svm_parameter_array_new(svm,1);
	params[0] = ::svm_parameter_value_new(svm,b);
	if(not ::svm_process_kernel_attach(svm,CURRENT(process),k,1,params))
	{
		ERROR_INTERNAL(FAILURE,"Unable to attach kernel");
	}
%}

INSTRUCTION sequencer.ignore BLN
%{
	SVM_Boolean b = ARGV_VALUE(0,boolean);
	SVM_Value_PluginEntryPoint sn = ::svm_process_get_sequencer(svm,CURRENT(process));
	if(not sn)
	{
		ERROR_INTERNAL(FAILURE,"Default sequencer attached to current process. sequencer.instance required.");
	}
	SVM_String snp = ::svm_value_pluginentrypoint_get_plugin(svm,sn);
	SVM_String sne = ::svm_value_pluginentrypoint_get_entry(svm,sn);
	if(not ((RAW_STRING(snp)=="sequencer") and (RAW_STRING(sne)=="instance")))
	{
		ERROR_INTERNAL(FAILURE,"Invalid plugin sequencer attacher to current process. sequencer.instance required.");
	}
	auto s = reinterpret_cast<sequencer_instance*>(::svm_process_sequencer_get_internal(svm,CURRENT(process)));
	std::lock_guard<std::mutex> protection(s->_mutex);
	if(b)
	{
		VARIABLE_LOCAL(s->_failure); s->_failure = nullptr;
	else
	{
		VARIABLE_LOCAL(s->_success); s->_success = nullptr;
	}
%}

Vous pouvez remarquer :

Après regénération et recompilation de l'extension, ajoutez un appel à cette nouvelle instruction avec TRUE en paramètre dans l'application précédente, et constatez le changement de comportement :

#!/usr/bin/env svm
LOG SERVER "localhost" "8081" QUIET
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginsequencer/libsvmsequencer.so"
OPTION -i FLAG irq
PROCESS "test"
	CODE "main" INLINE
		:memory LIB*2/l
		:library "success" ":com.message \"success\"\n" -> (l/0)
		:library "failure" ":com.message \"failure\"\n" -> (l/1)
		:sequencer.attach TRUE @(l/0)
		:sequencer.attach FALSE @(l/1)
		:debug BREAK
		:com.message "main"
		:sequencer.ignore TRUE
		:shutdown :unless @&irq TRUE
		:run.interrupt FAILURE
	END
	MEMORY irq
	SEQUENCER sequencer.instance
END

Conclusion

Vous venez de voir comment créer et manipuler les séquenceurs depuis une extension.

Dans la majorité des processus, le séquenceur par défaut (ne supportant qu'un noyau) est suffisant. Cependant, changer de séquenceur est parfois requis pour le fonctionnement de certaines instructions.

Quand tous les séquenceurs déjà existants se révèlent insuffisants, il est possible d'en créer un grâce aux extensions.