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 apprendre à introspecter et configurer les extensions depuis les extensions.

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 extensions.svm_plugin en utilisant ce code :

PLUGIN plugin

DEFINE

Introspection

Affichage

La première manière d'introspecter les extensions est de demander la représentation textuelle des objets des extensions présents sur la machine virtuelle.

Modifiez le code de l'extension :

PLUGIN plugin

DEFINE

INSTRUCTION plugin.print -> STR
%{
	SVM_String s = ::svm_plugin_print(svm);
	return NEW_VALUE(string,s);
%}

Générez et compilez cette extenion, puis créez une petite application de test pour appeller cette instruction et en afficher le résultat dans le terminal.

Présence d'objets

Il est également possible de déterminer la présence d'objets de manière plus fine.

Requêtes simples

Commençons par les objets dont le seul nom suffit pour tester s'ils sont accessibles dans la machine virtuelle.

Modifiez le code de l'extension :

PLUGIN plugin

DEFINE

INSTRUCTION plugin.type PEP -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Boolean b = ::svm_plugin_has_type(svm,n);
	return NEW_VALUE(boolean,b);
%}

INSTRUCTION plugin.interruption PEP -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Boolean b = ::svm_plugin_has_interruption(svm,n);
	return NEW_VALUE(boolean,b);
%}

INSTRUCTION plugin.sequencer PEP -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Boolean b = ::svm_plugin_has_sequencer(svm,n);
	return NEW_VALUE(boolean,b);
%}

INSTRUCTION plugin.scheduler PEP -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Boolean b = ::svm_plugin_has_scheduler(svm,n);
	return NEW_VALUE(boolean,b);
%}

INSTRUCTION plugin.structure PEP -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Boolean b = ::svm_plugin_has_structure(svm,n);
	return NEW_VALUE(boolean,b);
%}

Générez et compilez cette extenion, puis créez une petite application de test pour appeller ces instructions.

Ces fonctions d'interface programmatique sont très utiles pour tester une dépendance vers une autre extension de manière plus souple qu'avec la section USE des extensions : en effet, ici, la machine peut quand même démarrer et la dépendance est testée plus tard dans l'exécution de l'application. De même, cela permet de tester des dépendances dynamiquement, comme présenté dans l'exemple ci-dessus.

Requêtes élaborées

Vous pouvez remarquer qu'il manque deux objets dans les requêtes simples : les instructions et les fonctions. En réalité, savoir qu'une instruction ou une fonction existe n'est pas utile en soit. Il est bien plus intéressant de vérifier si un appel à une instruction ou à une fonction est possible.

Modifiez le code de l'extension :

PLUGIN plugin

DEFINE

INSTRUCTION plugin.instruction PEP VALUE * -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Parameter r = ::svm_parameter_value_new(svm,::svm_value_boolean_new(svm,TRUE));
	SVM_Boolean b = ::svm_plugin_has_instruction(svm,n,argc-1,argv+1,r);
	return NEW_VALUE(boolean,b);
%}

INSTRUCTION plugin.function PEP -> BLN
%{
	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Parameter *p = ::svm_parameter_array_new(svm,3);
	p[0] = ::svm_parameter_value_new(svm,::svm_value_integer_new_null(svm));
	p[1] = ::svm_parameter_marker_new__raw(svm,"<");
	p[2] = ::svm_parameter_value_new(svm,::svm_value_string_new_null(svm));
	SVM_Parameter r = ::svm_parameter_value_new(svm,::svm_value_boolean_new(svm,TRUE));
	SVM_Boolean b = ::svm_plugin_has_function(svm,n,3,p,r);
	return NEW_VALUE(boolean,b);
%}

Générez et compilez cette extenion, puis créez une petite application de test pour appeller la première instruction avec ce code :

#!/usr/bin/env svm
LOG
PLUGIN "svmcom.so"
LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so"

PROCESS "plugin"
	CODE "main" INLINE
		:memory BLN*5/b
		:plugin.instruction plugin.instruction p.e -> (b/0)
		:plugin.instruction plugin.void 3 -> (b/1)
		:plugin.instruction plugin.instruction "a" -> (b/2)
		:plugin.instruction plugin.instruction p.e 1 2 3 -> (b/3)
		:plugin.instruction com.message 1 2 3 -> (b/4)
		:com.message @(b/0) " " @(b/1) " " @(b/2) " " @(b/3) " " @(b/4)
	END
END

Vous pouvez lancer cette application :

./plugin.svm
TRUE FALSE FALSE TRUE FALSE

Interprétons alors le résultat :

  1. Le premier booléen est vrai, car l'instruction plugin.instruction accepte un point d'entrée d'extension et retourne un booléen,
  2. Le second est faux, car l'instruction plugin.void n'existe pas,
  3. Le troisième booléen est faux, car l'instruction plugin.instruction n'accepte pas une chaine de caractères comme paramètre,
  4. Le quatrième booléen est vrai, car l'instruction plugin.instruction accepte un point d'entrée d'extension suivi d'un nombre arbitraire de valeurs, et retourne un booléen,
  5. Le dernier booléen est faux, car l'instruction com.message accepte n'importe quel nombre de valeurs, mais ne retourne pas un booléen.

La fonction svm_plugin_has_function réalise le même test sur les fonctions.

Configuration

En plus des objets destinés à l'application et à la machine virtuelle, les extensions permettent de définir des objets servant à la configuration des extensions depuis l'application. Ces objets sont accessibles depuis toutes les fonctions de l'extension, y compris ses fonctions d'initialisation, de démarrage et de finalisation.

Options

Modifiez le code de l'extension :

PLUGIN plugin

DEFINE

OPTION plugin.flag -f BLN

OPTION plugin.integer -i INT

OPTION plugin.string -s STR

INSTRUCTION plugin.option PEP -> VALUE ?
%{
	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
	if(not ::svm_plugin_has_option(svm,o))
	{
		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown option.",SOFTWARE);
	}
	return ::svm_plugin_get_option(svm,o);
%}

Avant de générer l'extension, constatez que :

Générez et compilez l'extension. Puis, créez une petite application avec ce code :

#!/usr/bin/env svm
LOG
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so"

PROCESS "plugin"
	CODE "main" INLINE
		:memory AUTO*4/o
		:plugin.option plugin.flag -> (o/0)
		:plugin.option plugin.integer -> (o/1)
		:plugin.option plugin.string -> (o/2)
		:plugin.option plugin.unknown -> (o/3)
		:run.coredump STDOUT
	END
END

Lancez cette application :

./plugin.svm
Kernel:
State: R, transmit_interruption

Processor:
 State:
   Next instruction: <main:1/6> (Current one: <main:1/5>)
   Current memory  : &0*0
   Allocated memory: &0*4
   Aliases         : o
   Flags           :
   Cascaded flags  :
   Local interruption handlers:
   Cascaded local interruption handlers:
 Saved states:
 Global interruption handlers:
 Waiting interruptions:
 
Code at <main:1/5>:
     :memory AUTO*4/o   # <0> @(main, line 1)
     :plugin.option plugin.flag -> (o/0)        # <1> @(main, line 2)
     :plugin.option plugin.integer -> (o/1)     # <2> @(main, line 3)
     :plugin.option plugin.string -> (o/2)      # <3> @(main, line 4)
     :plugin.option plugin.unknown -> (o/3)     # <4> @(main, line 5)
====== HERE ======
     :run.coredump STDOUT       # <5> @(main, line 6) SYSTEM
==================
 Labels:
 Symbols:


Memory:
  &0: BLN FALSE
  &1: INT
  &2: STR
  &3: IRQ FAILURE
 Aliases:
  o -> &0*4
 Free space:

Modifiez le code de l'application pour ajouter les options d'extension :

#!/usr/bin/env svm
LOG
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so" -f -i 17 -s "value"

PROCESS "plugin"
	CODE "main" INLINE
		:memory AUTO*4/o
		:plugin.option plugin.flag -> (o/0)
		:plugin.option plugin.integer -> (o/1)
		:plugin.option plugin.string -> (o/2)
		:plugin.option plugin.unknown -> (o/3)
		:run.coredump STDOUT
	END
END

Puis relancez l'application :

./plugin.svm
Kernel:
State: R, transmit_interruption

Processor:
 State:
   Next instruction: <main:1/6> (Current one: <main:1/5>)
   Current memory  : &0*0
   Allocated memory: &0*4
   Aliases         : o
   Flags           :
   Cascaded flags  :
   Local interruption handlers:
   Cascaded local interruption handlers:
 Saved states:
 Global interruption handlers:
 Waiting interruptions:
 
Code at <main:1/5>:
     :memory AUTO*4/o   # <0> @(main, line 1)
     :plugin.option plugin.flag -> (o/0)        # <1> @(main, line 2)
     :plugin.option plugin.integer -> (o/1)     # <2> @(main, line 3)
     :plugin.option plugin.string -> (o/2)      # <3> @(main, line 4)
     :plugin.option plugin.unknown -> (o/3)     # <4> @(main, line 5)
====== HERE ======
     :run.coredump STDOUT       # <5> @(main, line 6) SYSTEM
==================
 Labels:
 Symbols:


Memory:
  &0: BLN TRUE
  &1: INT 17
  &2: STR "value"
  &3: IRQ FAILURE
 Aliases:
  o -> &0*4
 Free space:

Vous pouvez noter également que les valeurs sont visibles depuis le débugueur :

plugin
Plugins
Definitions
INSTRUCTION plugin.option PEP -> VALUE ?
SYSTEM INSTRUCTION run.coredump [ 'STDOUT' 'STDERR' 'TRACE' ] ? -> STR
INSTRUCTION run.interrupt IRQ
INSTRUCTION run.interruption [ 'GLOBAL' 'CASCADE' ] ? [ STR SYM ] : target ?
INSTRUCTION run.loop MUTABLE INT : index ( { INT : step } ) ? [ < > ] INT : limit STR : label
WAITING INSTRUCTION run.parallel_call SYM : function PTR : parameters STR : name ( 'PM' = BLN : protected_mode | 'SEQ' = [ 'DEFAULT' PEP ] : sequencer | 'SCHED' = [ 'DEFAULT' PEP ] : scheduler | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory | 'PARAMS' = [ 'COPY' 'MOVE' 'SHARE' ] ) *
INSTRUCTION run.parallel_limit [ 'NONE' INT ]
INSTRUCTION run.protected_call SYM : function PTR : parameters ( 'NOIRQ' | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory ) *
INSTRUCTION run.retry INT ? : number_instructions
WAITING INSTRUCTION run.sleep [ 'HARD' 'SOFT' ] INT : seconds
INSTRUCTION run.suspend
SYSTEM INSTRUCTION run.trace 'LOCATION' ? VALUE +
SCHEDULER run.parallel
SCHEDULER run.rrpreempt
SEQUENCER run.stack
OPTION plugin.flag -f BLN (TRUE)
OPTION plugin.integer -i INT (17)
OPTION plugin.string -s STR ("value")
in
Filter

Arguments

En plus des options, les extensions peuvent définir des arguments obligatoires, ainsi que des arguments optionnels, présents en extra sur la ligne de commande de l'extension.

Modifiez le code de l'extension :

PLUGIN plugin

DEFINE

OPTION plugin.flag -f BLN

OPTION plugin.integer -i INT

OPTION plugin.string -s STR

INSTRUCTION plugin.option PEP -> VALUE ?
%{
	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
	if(not ::svm_plugin_has_option(svm,o))
	{
		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown option.",SOFTWARE);
	}
	return ::svm_plugin_get_option(svm,o);
%}

ARGUMENT plugin.integer INT

ARGUMENT plugin.string STR

ARGUMENTS plugin.extra

INSTRUCTION plugin.argument PEP -> VALUE
%{
	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
	if(not ::svm_plugin_has_argument(svm,o))
	{
		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown argument.",SOFTWARE);
	}
	return ::svm_plugin_get_argument(svm,o);
%}

INSTRUCTION plugin.arguments PEP -> [ PTR IRQ ]
%{
	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
	if(not ::svm_plugin_has_arguments(svm,o))
	{
		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown extra arguments.",SOFTWARE);
	}
	SVM_Value *t = ::svm_plugin_get_arguments(svm,o);
	SVM_Size s=0;
	for(SVM_Value *it=t ; *it ; ++it) { ++s; }
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal__raw(svm,z,AUTOMATIC,s);
	SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z);
	::svm_memory_write_pointer(svm,CURRENT(kernel),p,t);
	return p;
%}

De la même manière que pour les options, l'extension définit ici deux arguments obligatoire avec leur type associé, et les arguments en extra qui eux n'ont pas de type associé.

Générez et compilez l'extension. Puis, modifiez le code de l'application :

#!/usr/bin/env svm
LOG
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so" -f -i 17 -s "value" -- 3 "other" 1 2 3 "four" 5

PROCESS "plugin"
	CODE "main" INLINE
		:memory AUTO*4/o, AUTO*5/a
		:plugin.option plugin.flag -> (o/0)
		:plugin.option plugin.integer -> (o/1)
		:plugin.option plugin.string -> (o/2)
		:plugin.option plugin.unknown -> (o/3)
		:plugin.argument plugin.integer -> (a/0)
		:plugin.argument plugin.string -> (a/1)
		:plugin.argument plugin.unknown -> (a/2)
		:plugin.arguments plugin.extra -> (a/3)
		:plugin.arguments plugin.unknown -> (a/4)
		:run.coredump STDOUT
	END
END

Cette fois, l'exécution donne :

./plugin.svm
Kernel:
State: R, transmit_interruption

Processor:
 State:
   Next instruction: <main:1/11> (Current one: <main:1/10>)
   Current memory  : &0*0
   Allocated memory: &0*14
   Aliases         : a o
   Flags           :
   Cascaded flags  :
   Local interruption handlers:
   Cascaded local interruption handlers:
 Saved states:
 Global interruption handlers:
 Waiting interruptions:
 
Code at <main:1/10>:
     :memory AUTO*4/o , AUTO*5/a        # <0> @(main, line 1)
     :plugin.option plugin.flag -> (o/0)        # <1> @(main, line 2)
     :plugin.option plugin.integer -> (o/1)     # <2> @(main, line 3)
     :plugin.option plugin.string -> (o/2)      # <3> @(main, line 4)
     :plugin.option plugin.unknown -> (o/3)     # <4> @(main, line 5)
     :plugin.argument plugin.integer -> (a/0)   # <5> @(main, line 6)
     :plugin.argument plugin.string -> (a/1)    # <6> @(main, line 7)
     :plugin.argument plugin.unknown -> (a/2)   # <7> @(main, line 8)
     :plugin.arguments plugin.extra -> (a/3)    # <8> @(main, line 9)
     :plugin.arguments plugin.unknown -> (a/4)  # <9> @(main, line 10)
====== HERE ======
     :run.coredump STDOUT       # <10> @(main, line 11) SYSTEM
==================
 Labels:
 Symbols:


Memory:
  &0: BLN TRUE
  &1: INT 17
  &2: STR "value"
  &3: IRQ FAILURE
  &4: INT 3
  &5: STR "other"
  &6: IRQ FAILURE
  &7: PTR &9*5
  &8: IRQ FAILURE
  &9: INT 1
  &10: INT 2
  &11: INT 3
  &12: STR "four"
  &13: INT 5
 Aliases:
  a -> &4*5
  o -> &0*4
 Free space:

Notez que si moins de deux arguments sont présents sur la ligne de commande de l'extension, la machine refuse de démarrer.

Ici, vous pouvez retrouver la valeur des deux premiers arguments aux adresses &4 et &4. Les valeurs des arguments en extra sont quant à eux écrits en mémoire et accessible via le pointeur &9*5.

Tout comme les options, les valeurs d'arguments sont visibles dans le débugueur avec la position de l'argument sur la ligne de commande :

plugin
Plugins
Definitions
INSTRUCTION plugin.argument PEP -> VALUE
INSTRUCTION plugin.arguments PEP -> [ PTR IRQ ]
INSTRUCTION plugin.option PEP -> VALUE ?
SYSTEM INSTRUCTION run.coredump [ 'STDOUT' 'STDERR' 'TRACE' ] ? -> STR
INSTRUCTION run.interrupt IRQ
INSTRUCTION run.interruption [ 'GLOBAL' 'CASCADE' ] ? [ STR SYM ] : target ?
INSTRUCTION run.loop MUTABLE INT : index ( { INT : step } ) ? [ < > ] INT : limit STR : label
WAITING INSTRUCTION run.parallel_call SYM : function PTR : parameters STR : name ( 'PM' = BLN : protected_mode | 'SEQ' = [ 'DEFAULT' PEP ] : sequencer | 'SCHED' = [ 'DEFAULT' PEP ] : scheduler | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory | 'PARAMS' = [ 'COPY' 'MOVE' 'SHARE' ] ) *
INSTRUCTION run.parallel_limit [ 'NONE' INT ]
INSTRUCTION run.protected_call SYM : function PTR : parameters ( 'NOIRQ' | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory ) *
INSTRUCTION run.retry INT ? : number_instructions
WAITING INSTRUCTION run.sleep [ 'HARD' 'SOFT' ] INT : seconds
INSTRUCTION run.suspend
SYSTEM INSTRUCTION run.trace 'LOCATION' ? VALUE +
SCHEDULER run.parallel
SCHEDULER run.rrpreempt
SEQUENCER run.stack
OPTION plugin.flag -f BLN (TRUE)
OPTION plugin.integer -i INT (17)
OPTION plugin.string -s STR ("value")
ARGUMENT plugin.integer INT (@0 3)
ARGUMENT plugin.string STR (@1 "other")
ARGUMENTS plugin.extra ([ 1 2 3 "four" 5 ])
in
Filter

Conclusion

Vous venez de voir comment introspecter et configurer les extensions depuis une extension.

L'introspection d'extension permet de manière générale d'assouplir la gestion des dépendances entre extensions, ainsi que de réaliser des dépendances dynamiques.

L'introspection d'extension est souvent utile lorsqu'une extension réalise un polymorphisme dynamique inter-extension, car il permet de réagir à l'absence d'une fonction de manière moins brutale que ce qui a pu être décrit dans un didacticiel précédent.

La possibilité de rendre les extensions configurables au travers d'options et d'arguments permet aux applications de les utiliser avec une plus grande souplesse. En particulier, une option pour empêcher une extension de créer des processus au démarrage peut être souhaitable, ainsi que de pouvoir changer certaines valeurs utilisées par les extensions dans leur fonctionnement pour éviter les collisions de comportement entre extensions.