NEXT
Nous y voilà ! Après avoir mis en place un framework de tests dans l'article précédent, il est temps d'implémenter le mot NEXT.
Un petit rappel du premier article de Moving Forth : NEXT est le mot qui permet de faire avancer l'exécution d'une séquence de pointeurs vers des mots Forth. Son pseudo-code (en modèle Indirect Threaded Code) est le suivant :
(IP) -> W récupère la mémoire pointée par IP dans le registre "W"
-> W contient maintenant l'adresse du Code Field
IP+2 -> IP avance IP, le compteur de programme
(W) -> X récupère la mémoire pointée par W dans le registre "X"
-> X contient maintenant l'adresse du code d'exécution
JP (X) saute à l'adresse présente dans le registre X
Implémentation
En 6502, le code est un peu long, car l'adressage indirect nécessite de passer par des registres en page zéro. Et les manipuler demande plusieurs instructions. Je ne suis pas très versé en 6502, on verra s'il y a moyen de faire mieux plus tard.
Dans certains Forth, NEXT est tellement court qu'il se pose la question d'en faire une macro. Ici, la réponse est assez simple : c'est non. Le code est assez long, et donc NEXT sera appelé avec un JMP à son adresse.
Très bien, j'ai donc NEXT. Il me faut aussi une liste de mots à appeler. Le but étant d'implémenter l'écriture d'une valeur en mémoire, je vais créer un pseudo-mot TEST-WORD. Puis comme il faut faire cela en boucle, un second mot qui réinitialisera l'IP (Instruction Pointer) au début de la séquence, que je vais appeler RESET-CODE.
Cela donne quelque chose comme ceci :
loop_words:
.word test_word_cfa
.word reset_code_cfa
Les pointeurs sont vers les CFA (Code Field Address) des mots. Pour rappel, le CFA est l'adresse où se trouve le code machine qui sera en charge d'exécuter le mot suivant (oui, c'est bien du Indirect Threaded Code, c'est indirect).
Par exemple, pour TEST-WORD, le CFA est l'adresse de la première instruction assembleur qui compose le mot :
test_word_cfa:
.word test_word_pfa
test_word_pfa:
lda #$42
sta $7FF
jmp next
NEXT va lire le CFA, puis sauter à l'adresse indiquée, qui est test_word_pfa. Et donc exécuter le code qui place la valeur $42 à l'adresse $07FF, avant de redonner la main à NEXT.
J'ai dit plus haut que c'était un pseudo-mot. En effet, le CFA et le PFA ne sont qu'une partie de la définition d'un mot complet en Forth. Mais comme cette partie n'a pas été abordée pour le moment, et qu'elle n'est pas nécessaire pour faire fonctionner NEXT, je laisse ceci de côté.
Reste à bootstrapper l'IP pour qu'il pointe sur loop_words au démarrage du programme. J'ajoute donc un petit code d'initialisation qui... sera aussi le code du mot RESET-CODE :
boot_forth:
; Set the Instruction Pointer to the start of temporary Forth code
reset_code:
lda #<loop_words
sta REG_IP
lda #>loop_words
sta REG_IP + 1
; Jump to the Forth NEXT loop
jmp next
Plus tard, il faudra aussi initialiser d'autres registres dans ce code. Mais pour le moment, ça suffit.
J'enlève l'ancien code d'écriture en mémoire de la boucle principale, je remplace par un appel à boot_forth, et voilà ! Les tests passent... Mais le code d'affichage n'affiche plus le HELLO FAMICOM du squelette. C'est normal puisque la boucle principale Forth n'appelle pas le code de synchro et d'affichage.
La boucle complète
J'ai tous les éléments pour remettre en place la boucle principale. Il suffit de créer deux mots (ou plutôt pseudo-mots) qui feront juste un appel aux deux routines présentes initialement dans la boucle principale.
read_joy_safe_word_cfa:
.word read_joy_safe_word_pfa
read_joy_safe_word_pfa:
jsr read_joy_safe
jmp next
post_logic_word_cfa:
.word post_logic_word_pfa
post_logic_word_pfa:
jsr post_logic
jmp next
Et de modifier la liste des mots à appeler dans loop_words :
loop_words:
.word read_joy_safe_word_cfa
.word test_word_cfa
.word post_logic_word_cfa
.word reset_code_cfa
Et l'affichage remarche. Avec beaucoup plus d'indirections et donc plus lent, c'est vrai. Je verrai plus tard comment regrouper les appels. Ou comment modifier les sous-routines pour les implémenter directement comme des mots.
En tout cas pour le moment, j'ai retrouvé le comportement initial. Parfait.
DOCOL et ;S
Dans la partie 2 de cette série, j'avais listé les mots DOCOL et ;S comme prioritaires à implémenter. En effet, ces mots permettent d'appeler des mots définis par d'autres mots. C'est exactement ce qui est fait avec loop_words. Sauf que cela n'est pas un vrai mot. Il n'a même pas de CFA. Cette liste est démarrée manuellement par le code d'initialisation, que je rappelle en boucle.
Mais cette boucle est aussi un petit hack avec le RESET-CODE. Je ne peux pas l'utiliser proprement pour démontrer DOCOL et ;S. Je vais donc créer un autre pseudo-mot
main_loop_cfa:
.word docol
main_loop_pfa:
.word read_joy_safe_word_cfa
.word test_word_cfa
.word post_logic_word_cfa
.word do_semi_cfa
docol étant le code machine de DOCOL et do_semi_cfa le pointeur sur le CFA de ;S.
La boucle « hack » se retrouve donc remplacée par :
loop_words:
.word main_loop_cfa
.word reset_code
Pseudo-code pour les deux mots
Le pseudo-code de DOCOL est le suivant :
PUSH IP pousse IP sur la pile de retour
W+2 -> IP puisque W pointait sur le Code Field,
W+2 est l'adresse du Parameter Field.
Le Parameter Field contient la liste des
adresses des mots à exécuter. IP pointe
donc sur cette liste.
JUMP NEXT saute à NEXT pour continuer l'exécution
Le pseudo-code de ;S est le suivant :
POP IP récupère l'adresse de retour depuis
la pile de retour et la place dans IP
JUMP NEXT saute à NEXT pour continuer l'exécution
Et ça fonctionne
À nouveau, les tests passent et l'affichage fonctionne. Les trois premiers mots Forth sont donc implémentés : NEXT, DOCOL et ;S.
Moving Forth, article 3
Jetons à présent un œil sur le troisième article de la série Moving Forth. Son titre est « Demystifying DOES> », mais il en profite surtout pour décortiquer le fonctionnement des CFA et PFA.
On passe sur l'introduction, qui revient sur des erreurs dans les parties précédentes pour arriver à l'explication du Code Field.
Le Code Field, c'est le cœur du fonctionnement d'un mot en Forth. C'est un indicateur de la nature du mot, de la façon dont il va se comporter, être exécuté. Le Code Field indique quoi faire avec les paramètres du mot, les paramètres étant situés dans le Parameter Field, qui suit le Code Field.
Ainsi, on peut définir différents fonctionnements :
- un mot qui exécute les paramètres comme du code machine,
- un mot qui traite les paramètres comme des référence à d'autres mots,
- un mot qui traite les paramètres comme un emplacement mémoire réservé pour une variable,
- etc...
En quelque sorte, le Code Field représente une routine avec un paramètre implicite : le Parameter Field.
L'article fait un parallèle avec la programmation objet, où le Code Field serait la méthode unique d'une classe, et le Parameter Field l'instance de cette classe pour chaque mot utilisant ce Code Field. Je ne suis pas certain que ce parallèle aide beaucoup... mais pourquoi pas.
Code Field et Indirect Threaded Code
Dans le modèle ITC (Indirect Threaded Code), le Code Field est une adresse qui pointe vers un code machine chargé d'interpréter le contenu du Parameter Field. On a vu deux cas pour le moment :
- un Code Field qui pointe vers
DOCOLentraîne l'appel successif de tous les mots dont les listés dans lePFA. Ces mots sont eux-mêmes représentés par leurCFA. - un Code Field qui pointe vers directement sur l'adresse du Parameter Field. Dans ce cas, le code machine est directement dans le
PFA.
Il existe différents Code Field standards en Forth, mais rien n'empêche l'utilisateur d'en définir de nouveaux. Et c'est exactement ce que fait le mot DOES> mentionné par le titre. Cependant, son fonctionnement nécessitera probablement un article à lui seul. Retenez juste que DOES> permet de définir une nouvelle classe de mots partageant un même Code Field personnalisé.
L'article mentionne aussi les mots CREATE ou encore ;CODE, qui respectivement permettent de définir un nouveau mot et de définir un mot dont le code est écrit en assembleur. Là encore, ce sont des sujets pour plus tard lorsqu'il sera question de définir des mots par programmation.
Pour le moment, je n'ai toujours que des pseudo-mots, et ils sont tous définis directement dans le code assembleur.
L'article est plus long car il aborde ces sujets dans les différents modèles d'exécution de Forth, ainsi que les mots CONSTANT et VARIABLE, qui sont des mots de constructions avec un Code Field particulier, que nous verrons plus tard.
La suite
J'ai à présent les premiers pseudo-mots pour mon Forth, mais il m'en manque un pour créer la boucle en entier. En effet, pour le moment je réinitialise l'interpréteur pour relancer la boucle. Ce n'est pas une vraie boucle Forth. Je pense que ma prochaine étape sera donc de créer le mot BRANCH, qui modifie le registre IP.