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 manipuler le processeur depuis une extension.

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

PLUGIN cpu

DEFINE

Registres

Pointeur d'instruction

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.current -> SYM
%{
	return ::svm_processor_get_currentinstruction(svm,CURRENT(kernel));
%}

INSTRUCTION cpu.next -> SYM
%{
	return ::svm_processor_get_nextinstruction(svm,CURRENT(kernel));
%}

Générez puis compilez l'extension. Ensuite, écrivez une petite application affichant le contenur des deux registres pointeur d'instruction.

Notez que la modification du registre d'instruction courante est impossible. La modification du pointeur de prochaine instruction existe, et s'appelle un saut. Nous aborderons les sauts juste après la gestion de l'autre registre.

Pointeur de mémoire

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.get_p -> PTR
%{
	return ::svm_processor_get_currentpointer(svm,CURRENT(kernel));
%}

INSTRUCTION cpu.set_p PTR
%{
	SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[0]);
	::svm_processor_set_currentpointer(svm,CURRENT(kernel),p);
%}

Générez puis compilez l'extension. Ensuite, écrivez une petite application modifiant le pointeur mémoire et l'utilisant pour référencer un tableau. Comparez également le retour de l'instruction :cpu.get_p avec P.

Exécution de code

Sauts

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.local INT
%{
	auto a = ARGV_VALUE(0,integer);
	::svm_processor_jump_local(svm,CURRENT(kernel),a);
%}

INSTRUCTION cpu.global ( SYM | LIB INT )
%{
	SVM_Value_Symbol s = nullptr;
	if(argc==1)
	{
		s = ::svm_parameter_value_get(svm,argv[0]);
	}
	else
	{
		SVM_Value_Library l = ::svm_parameter_value_get(svm,argv[0]);
		SVM_Address a = ARGV_VALUE(1,integer);
		s = ::svm_value_symbol_new(svm,l,a);
	}
	::svm_processor_jump_global(svm,CURRENT(kernel),s);
%}

Générez puis compilez l'extension. Ensuite, écrivez une petite application utilisant les deux instructions de l'extension pour écrire des petits algorithmes tel des parcours de tableaux sans utiliser d'étiquette ou de symbole.

Fonctions

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.call ( INT | LIB INT ) PTR
%{
	if(argc==2)
	{
		SVM_Address a = ARGV_VALUE(0,integer);
		SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[1]);
		::svm_processor_call_local(svm,CURRENT(kernel),a,p);
	}
	else
	{
		SVM_Value_Library l = ::svm_parameter_value_get(svm,argv[0]);
		SVM_Address a = ARGV_VALUE(1,integer);
		SVM_Value_Symbol s = ::svm_value_symbol_new(svm,l,a);
		SVM_Value_Pointer p = ::svm_parameter_value_get(svm,argv[2]);
		::svm_processor_call_global(svm,CURRENT(kernel),s,p);
	}
%}

INSTRUCTION cpu.return
%{
	::svm_processor_return(svm,CURRENT(kernel));
%}

Générez puis compilez l'extension. Ensuite, écrivez une petite application utilisant les deux instructions de l'extension pour écrire un calcul de la suite de Fibonacci récursif sans aucune étiquette ou symbole.

Arrêt

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.shutdown
%{
	::svm_processor_current_shutdown(svm);
%}

Vous pouvez générer et compiler l'extension pour tester cette fonction.

Notez cependant que cette fonction de l'interface programmatique ne peut s'appliquer que sur le processeur courant, alors que les autres fonctions ont la capacité de modifier un autre processeur que le processeur courant !

Pause

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.pause INT INT ?
%{
	SVM_Value_Integer s = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Value_Integer ms = nullptr;
	if(argc==2)
	{
		ms = ::svm_parameter_value_get(svm,argv[1]);
	}
	else
	{
		ms = ::svm_value_integer_new(svm,0);
	}
	::svm_processor_current_sleep(svm,s,ms,TRUE);
%}

Générez puis compilez l'extension. Ensuite, écrivez une petite application pour tester cette instruction.

Dans la documentation de cette fonction d'interface, il est précisé de n'utiliser que cette fonction pour réaliser un temps d'attente dans du code d'extension. Toute autre fonction de l'interface du système d'exploitation est à proscrire.

Interruptions

Gestionnaires d'interruption

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.irq_local SYM
%{
	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Value_Interruption *li = ::svm_machine_list_interruption(svm);
	while(*li)
	{
		::svm_processor_interruptionhandler_set_local_global(svm,CURRENT(kernel),*li,s,FALSE);
		++li;
	}
%}

INSTRUCTION cpu.irq_cascade SYM
%{
	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Value_Interruption *li = ::svm_machine_list_interruption(svm);
	while(*li)
	{
		::svm_processor_interruptionhandler_set_local_global(svm,CURRENT(kernel),*li,s,TRUE);
		++li;
	}
%}

INSTRUCTION cpu.irq_global SYM
%{
	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Value_Interruption *li = ::svm_machine_list_interruption(svm);
	while(*li)
	{
		::svm_processor_interruptionhandler_set_global_global(svm,CURRENT(kernel),*li,s);
		++li;
	}
%}

Générez puis compilez l'extension. Ensuite, écrivez une petite application pour tester ces instructions. Vous pouvez également améliorer ce code pour accepter des gestionnaires d'interruptions locaux.

Interruptions en attente

Le processeur possède la liste des interruptions reçues durant l'exécution d'une instruction. Grâce à l'interface programmatique de la machine virtuelle, il est possible de récupérer, de modifier cette liste d'interruption et même de bloquer le traitement des interruptions.

Lecture et modification des interruptions en attente

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.keep_hw_irq
%{
	SVM_Value_Interruption *li = ::svm_processor_list_interruption(svm,CURRENT(kernel));
	::svm_processor_clear_interruption(svm,CURRENT(kernel));
	while(*li)
	{
		if(::svm_value_interruption_get_kind(svm,*li)==HARDWARE)
		{
			::svm_processor_add_interruption(svm,CURRENT(kernel),*li);
		}
		++li;
	}
%}

Générez puis compilez l'extension.

En l'état, cette instruction n'est pas très utile, car il est très rare que le processeur reçoive plusieurs interruptions durant l'exécution d'une instruction.

Blocage du traitement des interruptions

Modifiez le code de l'extension pour ajouter deux instructions :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.keep_hw_irq
%{
	SVM_Value_Interruption *li = ::svm_processor_list_interruption(svm,CURRENT(kernel));
	::svm_processor_clear_interruption(svm,CURRENT(kernel));
	while(*li)
	{
		if(::svm_value_interruption_get_kind(svm,*li)==HARDWARE)
		{
			::svm_processor_add_interruption(svm,CURRENT(kernel),*li);
		}
		++li;
	}
%}

SYSTEM INSTRUCTION cpu.hold
%{
	::svm_processor_hold_interruption(svm,CURRENT(kernel));
%}

SYSTEM INSTRUCTION cpu.release
%{
	::svm_processor_release_interruption(svm,CURRENT(kernel));
%}

Crééz un fichier exécutable avec ce code :

#!/usr/bin/env svm
LOG
DEBUG "processor"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
PROCESS "cpu"
	CODE "main" INLINE
		:interruption GLOBAL FAILURE f
		:interruption GLOBAL DEVICE d
		:interruption GLOBAL TERMINATE t
		:debug BREAK
		:run.trace 1
		:run.interrupt FAILURE
		:run.trace 2
		:cpu.hold
		:run.trace 3
		:run.interrupt FAILURE
		:run.trace 4
		:run.interrupt DEVICE
		:run.trace 5
		:run.interrupt TERMINATE
		:run.trace 6
		:cpu.release
		:run.trace 7
		:shutdown
	:label f
		:run.trace "FAILURE"
		:return
	:label d
		:run.trace "DEVICE"
		:return
	:label t
		:run.trace "TERMINATE"
		:return
	END
END

Lancez cette application :

./processor.svm
### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
1

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
FAILURE

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
2

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
3

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
4

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
5

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
6

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
TERMINATE

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
DEVICE

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
FAILURE

### Simple Virtual Machine 1234 : PROCESS cpu : INSTRUCTION run.trace | 2023-01-01 00:00:00 GMT ######################################################
7

La trace obtenue suggère bien que les interruptions ont été retenues par le processeur entre les instructions :cpu.hold et :cpu.release.

Ajoutez dans cette application l'instruction :cpu.keep_hw_irq juste avant :cpu.release puis lancez l'application à nouveau en mode débugueur. Lors de l'exécution, pendant que les interruptions sont bloquées, envoyez des signaux SIGTERM à la machine virtuelle :

kill -15 $(pidof svm)

Cette fois, les interruptions qui ont été levées par les instructions :run.interrupt sont carrément ignorées, mais pas celles générées par les signaux SIGTERM.

Erreurs

Les fonctions de l'interface programmatique vu précédemment permettent d'ajouter une interruption au processeur, mais n'arrêtent pas l'exécution de l'instruction courante.

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.error IRQ
%{
	SVM_Value_Interruption i = ::svm_parameter_value_get(svm,argv[0]);
	::svm_machine_trace__raw(svm,"1");
	::svm_processor_current_raise_error(svm,i);
	::svm_machine_trace__raw(svm,"2");
%}

Modifiez le code de l'application pour appeller l'instruction :cpu.error, et constater en lançant l'application que la trace numéro 2 de l'instruction n'est pas exécutée !

Notez que des variantes de la fonction svm_processor_current_raise_error est utilisée dans les macros ERROR_INTERNAL et ERROR_EXTERNAL définies par le générateur d'extension.

Drapeaux

Modification

Modifiez le code de l'extension :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.flag [ 'SET' 'RESET' ] STR:flag BLN:cascade
%{
	auto a = ARGV_KEYWORD(0);
	SVM_Value f = ::svm_parameter_value_get(svm,argv[1]);
	auto b = ARGV_VALUE(2,boolean);
	if(a=="SET")
	{
		::svm_processor_set_flag(svm,CURRENT(kernel),f,b);
	}
	else
	{
		::svm_processor_reset_flag(svm,CURRENT(kernel),f,b);
	}
%}

Générez et compilez l'extension, comme habituellement. Puis, crééz une petite application qui vous permet de tester cette instruction : vous constaterez qu'elle permet de retirer un drapeau, ce qui n'est pas possible dans le jeu d'instructions de base de la machine.

Test

Modifiez le code de l'extension pour ajouter une instruction :

PLUGIN cpu

DEFINE

INSTRUCTION cpu.flag [ 'SET' 'RESET' ] STR:flag BLN:cascade
%{
	auto a = ARGV_KEYWORD(0);
	SVM_Value f = ::svm_parameter_value_get(svm,argv[1]);
	auto b = ARGV_VALUE(2,boolean);
	if(a=="SET")
	{
		::svm_processor_set_flag(svm,CURRENT(kernel),f,b);
	}
	else
	{
		::svm_processor_reset_flag(svm,CURRENT(kernel),f,b);
	}
%}

INSTRUCTION cpu.test STR -> BLN
%{
	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
	return NEW_VALUE(boolean,::svm_processor_has_flag(svm,CURRENT(kernel)));
%}

Après regénération et recompilation de l'extension, modifiez votre application de test pour utiliser cette instruction. Notez que la détection est insensible au fait que le drapeau soit cascadé ou local.

Manipulations de la pile de retour

Recherche d'états

L'interface programmatique permet de rechercher les niveaux de pile de retour contenant un certain drapeau. Modifiez le code de l'extension :

PLUGIN cpu

includes:
%{
#include <iostream>
%}

DEFINE

INSTRUCTION cpu.find STR
%{
	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
	unsigned long int l = 0;
	SVM_Size s = ::svm_processor_returnstack_get_size(svm,CURRENT(kernel));
	for(;;)
	{
		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),f,l);
		if(l==s) break;
		std::cout << "Found at level " << l << std::endl;
		++l;
	}
%}

Après regénération et recompilation de l'extension, modifiez votre application de test avec ce code :

#!/usr/bin/env svm
LOG
DEBUG "processor"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
PROCESS "cpu"
	CODE "main" INLINE
		:flag "a"
		:call f1 P
		:shutdown
	:label f1
		:flag "a"
		:call f2 P
		:return
	:label f2
		:call f3 P
		:return
	:label f3
		:call f4 P
		:return
	:label f4
		:flag "a"
		:call f5 P
		:return
	:label f5
		:flag "a"
		:cpu.find "a"
		:debug BREAK
		:return
	END
END

Lancez l'application :

./processor.svm
Found at level 0
Found at level 3
Found at level 4

Vous pouvez remarquer :

Echange

Avec l'interface programmatique, il est possible d'échanger l'état courant avec un état sauvegardé dans la pile de retour.

Modifiez le code de l'extension pour ajouter une instruction :

PLUGIN cpu

includes:
%{
#include <iostream>
%}

DEFINE

INSTRUCTION cpu.find STR
%{
	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
	unsigned long int l = 0;
	SVM_Size s = ::svm_processor_returnstack_get_size(svm,CURRENT(kernel));
	for(;;)
	{
		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),f,l);
		if(l==s) break;
		std::cout << "Found at level " << l << std::endl;
		++l;
	}
%}

INSTRUCTION cpu.swap [ INT STR ]
%{
	SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Size l;
	if(::svm_value_type_is_integer(svm,v))
	{
		l = ::svm_value_integer_get(svm,v);
	}
	else
	{
		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),v,0);
	}
	::svm_processor_returnstack_swap_level(svm,CURRENT(kernel),l);
%}

Regénérez et compilez l'extension. Ensuite, modifiez le code de l'application avec ce code :

#!/usr/bin/env svm
LOG
DEBUG "processor"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
PROCESS "cpu"
	CODE "main" INLINE
		:flag "a"
		:call f1 P
		:run.trace "main"
		:shutdown
	:label f1
		:flag "a"
		:call f2 P
		:run.trace "f1"
		:return
	:label f2
		:call f3 P
		:run.trace "f2"
		:return
	:label f3
		:call f4 P
		:run.trace "f3"
		:return
	:label f4
		:flag "a"
		:call f5 P
		:run.trace "f4"
		:return
	:label f5
		:flag "a"
		:cpu.find "a"
		:debug BREAK
		:cpu.swap 2
		:run.trace "f5"
		:return
	END
END

L'exécution du code montre que les retours de fonction ne se passent plus comme d'habitude. En effet, la fin de l'exécution de la fonction f5 (la fonction courante) est échangée avec la fonction f2 ! Une exécution dans le débugueur permet de bien mettre en évidence dans quel ordre les instructions sont exécutées.

Déplacement

Enfin, il est possible de déplacer une portion de la pile de retour.

Modifiez le code de l'extension pour ajouter une instruction :

PLUGIN cpu

includes:
%{
#include <iostream>
%}

DEFINE

INSTRUCTION cpu.find STR
%{
	SVM_Value f = ::svm_parameter_value_get(svm,argv[0]);
	unsigned long int l = 0;
	SVM_Size s = ::svm_processor_returnstack_get_size(svm,CURRENT(kernel));
	for(;;)
	{
		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),f,l);
		if(l==s) break;
		std::cout << "Found at level " << l << std::endl;
		++l;
	}
%}

INSTRUCTION cpu.swap [ INT STR ]
%{
	SVM_Value v = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Size l;
	if(::svm_value_type_is_integer(svm,v))
	{
		l = ::svm_value_integer_get(svm,v);
	}
	else
	{
		l = ::svm_processor_returnstack_find_flag(svm,CURRENT(kernel),v,0);
	}
	::svm_processor_returnstack_swap_level(svm,CURRENT(kernel),l);
%}

INSTRUCTION cpu.move INT:start INT:size INT:target
%{
	auto s = ARGV_VALUE(0,integer);
	auto ss = ARGV_VALUE(1,integer);
	auto t = ARGV_VALUE(2,integer);
	::svm_processor_returnstack_move_level(svm,CURRENT(kernel),s,s+ss,t);
%}

Regénérez et compilez l'extension. Ensuite, modifiez le code de l'application avec ce code :

#!/usr/bin/env svm
LOG
DEBUG "processor"
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmplugincpu/libsvmcpu.so"
PROCESS "cpu"
	CODE "main" INLINE
		:flag "a"
		:call f1 P
		:run.trace "main"
		:shutdown
	:label f1
		:flag "a"
		:call f2 P
		:run.trace "f1"
		:return
	:label f2
		:call f3 P
		:run.trace "f2"
		:return
	:label f3
		:call f4 P
		:run.trace "f3"
		:return
	:label f4
		:flag "a"
		:call f5 P
		:run.trace "f4"
		:return
	:label f5
		:flag "a"
		:cpu.find "a"
		:debug BREAK
		:cpu.move 1 2 0
		:run.trace "f5"
		:return
	END
END

L'exécution du code montre que cette fois, la fonction f5 termine bien en premier. En revanche, les fonctions f3 et f2 se terminent dans cet ordre (ce qui est normal) avant la fonction f4 ! L'instruction :cpu.move a littéralement déplacé une portion de la pile de retour.

Conclusion

Vous venez de voir comment manipuler les processeurs de la machine virtuelle depuis les extensions.

L'interface permet une interaction étendue avec les processeurs de la machine, pour créér des sauts ou appels de fonction enrichis. Cela peut même donner des résultats parfois surprenant, surtout lorsque deux opérations de saut ou d'appel de fonction ou de retour de fonction sont appellés dans la même instruction.

La manipulation de la pile de retour peut aussi hautement changer l'ordre d'exécution, surtout lorsque plusieurs opérations sont utilisées : c'est comme cela que peuvent être implémentées les coroutines dans la machine virtuelle !

Enfin, la gestion des interruptions peut être affinée par l'intermédiaire de l'interface programmatique, notamment avec la capacité à retarder le traitement des interruptions.