L'ASSEMBLEUR FORTH

La rapidité d'exécution des définitions compilées en FORTH n'est plus à démontrer. En principe, les primitives FORTH permettent de résoudre tous les problèmes rencontrés par le programmeur. Cependant, diverses situations peuvent justifier le recours à l'exécution de définitions directement par le micro-processeur:

- pour accélérer la vitesse d'exécution d'un programme par optimisation des routines sensibles. Exemple: routines d'affichage, acquisition de données...

- pour gérer des interfaces nouveaux non prévus par le constructeur ou dans la liste des interruptions du système d'exploitation. Exemple: carte FAX, carte de conversion de signaux A/N-N/A (analogique numérique)...

TURBO-Forth intègre un assembleur simpifié mais puissant, permettant la définition de mots dont la zone paramétrique contient du code directement exécutable par le micro-processeur. L'assemblage est exécuté en une passe et utilise une syntaxe postfixée.

A. Définition des primitives utilisateur.

Le mot CODE crée un nouvel en-tête dans le dictionnaire. Le champ du code (cfa) pointe sur la zone paramétrique (pfa) de la définition à assembler. La fin de la séquence d'assemblage est marquée par le mot END-CODE.

A la différence d'une définition FORTH compilée par le mot de définition : (deux- points) et terminée par le mot ; (point-virgule), o- FORTH passe en état de compilation, le système reste en état d'interprétation entre CODE et END-CODE.

Dans les exemples illustrant ce chapitre, les mnémoniques d'assemblage et les noms des registres seront dactylographiés en caractères minuscules pour les distinguer des mots FORTH et des macro-instructions. Cette présentation n'est nullement obligatoire. Si l'assemblage des exemples ne fonctionne pas correctement, vérifier l'état de la variable CAPS qui doit être à l'état ON en appuyant sur la touche de fonction F7.

Le mot à définir est suivi du code assembleur dont les mnémoniques sont définis dans le vocabulaire ASSEMBLER:

ASSEMBLER WORDS FORTH

affiche le contenu du vocabulaire ASSEMBLER.

Les mnémoniques d'assemblage sont utilisés avec une syntaxe postfixée:

- opérateur
- registre(s) - opérateur
- opérande - préfixe d'opérande - registre - opérateur
- opérande - préfixe de mode d'adressage - registre - opérateur

L'opérande peut être une valeur numérique, une variable, une constante ou un label.

Le mode d'adressage est implicitement en mode inhérent. Pour forcer l'assemblage dans un mode d'adressage particulier on utilisera un des mots suivants:

#   force l'assemblage d'un opérande en adressage immédiat,
#)   force l'assemblage d'un opérande en adressage direct.

L'exécution d'une définition, exécutable en langage machine et assemblée sous FORTH, s'achève par l'un des mots suivants:

NEXT redonne le contrôle à l'interpréteur FORTH sans déposer de paramètre sur la pile de données, (ben alors, qu'est-ce qui fait ? Il sert lors des mises de valeurs dans les variables ( pas besoins de passer par la pile)
1PUSH empile le contenu du registre AX (micro-processeur) et redonne le contrôle à l'interpréteur FORTH,
2PUSH empile le contenu des registres DX et AX (micro-processeur) et redonne le contrôle à l'interpréteur FORTH.

Presque toutes les primitives du vocabulaire FORTH écrites en code machine ont été définies sur ce modèle. Exemple, voici comment a été défini le mot DUP dans vocabulaire FORTH:

Le mot de définition CODE crée l'en-tête de définition DUP. Le commentaire de pile entre parenthèse est facultatif. L'opération exécutée par DUP consiste à récupérer le paramètre situé sur la pile de données et à l'affecter au registre accumulateur AX du micro-processeur, puis à remettre deux fois sur la pile le contenu de ce registre.

Le mot NEXT sert à redonner le contrôle à l'interpréteur FORTH. Ce mot est une macro-instruction chargée d'assembler un saut vers une adresse absolue. En remplaçant NEXT par 1PUSH, la vraie définition de DUP est:

En cas d'omission de la macro-instruction NEXT, 1PUSH ou 2PUSH, la routine plantera le système lors de son exécution.

Soit à définir une variante de DUP, nommée TRIPL, qui triplera une donnée préalablement empilée:

Pour exécuter ensuite un mot dont la définition a été assemblée par CODE.. END-CODE, il suffit de l'exécuter ou le compiler comme n'importe quel mot déjà pré-défini:

6 TRIPL . . . affiche 6 6 6

Variante pour la définition de TRIPL:

L'assembleur FORTH laisse une plus grande liberté d'écriture et de présentation qu'un assembleur conventionnel. Rien n'empêche l'écriture de TRIPL sous la forme suivante:

CODE TRIPL ax pop ax push ax dx mov 2PUSH END-CODE

Ceci est quand même à éviter. Par tradition, les programmeurs FORTH séparent l'opérateur et l'opérandes de l'opérateur suivant par au moins trois caractères d'espacement. Les commentaires ne sont généralement pas superflus si l'on tient à pouvoir reprendre des définitions pour modification ultérieure.

B.Passage de paramètre par la pile.

FORTH permet de passer aisément des paramètres, via la pile de données, entre définition compilée en FORTH et définition assemblée. Seuls les registres AX, BX, CX et DX peuvent recevoir par dépilage les données qui ont été préalablement déposées sur la pile de données. Ces registres ont chacun un usage spécifique:

AX accumulateur; utilisé pour les opérations de multiplication, division, entrée/sortie de mots,
BX adressage;
CX calculs, compteur,
DX extension d'accumulateur AX; entrée/sortie indirecte de mots.

Ce sont les instructions POP et PUSH qui sont respectivement chargées du dépilage et de l'empilage des paramètres entre la pile et les registres du micro-processeur.

Pour affecter des valeurs préalablement déposées sur la pile aux autres registres, il faut d'abord dépiler ces valeurs vers le registre accumulateur AX. Exemple: pour modifier le contenu du registre BP, procéder comme suit:

ax pop ax bp mov

Pour le renvoi de paramètre vers la pile de données, il faut empiler le contenu de l'un des registres AX, BX, CX ou DX. Pour empiler le contenu d'un registre différent de AX, BX, CX ou DX, il faut faire passer leur valeur par le registre accumulateur AX. Exemple: soit à récupérer le contenu du registre DS (Data Segment), procéder comme suit:

ds ax mov ax push

On retrouve cette partie de code dans la définition du mot DSEGMENT défini dans le dictionnaire FORTH:

Le dictionnaire FORTH offre une vaste palette d'exemples de définitions assemblées illustrant le passage de paramètres par la pile de données.

C. Passage de paramètre par adresse.

Le passage de paramètres par la pile, bien que simple et efficace, ne permet pas de mémoriser des valeurs utilisées pendant le déroulement d'un programme et dont peuvent dépendre d'autres routines. Un programme ne peut être concevable sans registre mémoire. En FORTH, une adresse est réservée et nommée en utilisant l'un des mots de définition CONSTANT, 2CONSTANT, VARIABLE ou 2VARIABLE ou toute structure de données définie par l'utilisateur.

Le passage de paramètre entre un registre du micro-processeur et une adresse mémoire 8 ou 16 bits est sélectionnée en utilisant le préfixe de sélection d'adressage direct #). Exemple, soit à incrémenter le score d'un joueur au cours d'une partie:

- en FORTH

- en assembleur:

Autre exemple, soit à récupérer les contenus de deux adresses et à renvoyer leur somme dans une troisième adresse:

-en FORTH:

10 ADR1 !
12 ADR2 !
ADR3=ADR1+ADR2 ADR3 @ .   affiche 22

-en assembleur:

10 ADR1 !
12 ADR2 !
ADR3=ADR1+ADR2 ADR3 @ .   affiche 22

D. Passage de paramètre en adressage immédiat.

Pour initialiser un des registres AX, BX, CX ou DX avec une valeur constante, il faut utiliser le préfixe de sélection d'adressage immédiat #:

10 # ax mov

initialise le registre AX avec la valeur litérale 10 en adressage immédiat.

La valeur litérale peut également provenir d'une constante ou désigner une adresse. Voici une variante de la définition SCORE+1! utilisant l'affectation du registre BX en adressage immédiat:

Seuls les registres AX, BX, CX et DX peuvent recevoir une valeur litérale en adressage immédiat. Pour affecter un registre différent, il faut passer la valeur litérale par le registre accumulateur AX:

HEX 40 # ax mov ax ds mov DECIMAL

La valeur litérale peut être exprimée dans n'importe quelle base numérique, sous réserve d'avoir été préalablement sélectionnée:

La valeur litérale peut aussi provenir d'une constante définie en FORTH, extraite d'une variable, résulter d'un calcul ou d'un paramètre renvoyé par l'exécution d'un mot FORTH:

E. Passage de paramètre en adressage indirect.

Le passage de paramètre peut être effectué en calculant l'adresse à partir d'une base et d'un déplacement. Les préfixes d'opérandes encadrés par les symboles [ et ] doivent être précédés d'une valeur litérale 8 ou 16 bits:

0 [bx] ax mov

désigne une adresse pointée par le contenu du registre BX avec un déplacement nul par rapport à cette adresse. Exemple, on souhaite élever 2 à une puissance quelconque pour un exposant situé dans l'intervalle [0..7]; on définit:

-en FORTH:

-en assembleur:

Exécution de POIDS:

0 POIDS .   affiche 1
3 POIDS .   affiche 8

L'exploitation des données transitant par la pile de données ou stockées dans des variables FORTH est donc une opération aisée. On préférera la définition de plusieurs sous-routines de petite taille en assembleur, faciles à mettre au point, à la conception d'une seule routine très longue. Une connaissance approfondie du jeu d'instructions du processeur 8088 ou 8086 est requise.

F. Les labels et sous-programmes.

Dans tout programme il est nécessaire de prendre des décisions pour poursuivre l'exécution vers les parties de code adéquates. Contrairement à un assembleur conventionnel, FORTH ne gère que les références de branchement vers des parties de code déjà définies. Exemple, soit à mettre à jour une variable MAX-SCORE désignant le plus haut score atteint par un joueur si son score dépasse la valeur de cette variable:

-en FORTH:

en assembleur:

En exécution:

10 SCORE !
20 MAX-SCORE !
TEST-HIGHTER MAX-SCORE @ .   affiche 20
30 SCORE !
TEST-HIGHTER MAX-SCORE @ .   affiche 30

Le branchement conditionnel ou inconditionnel vers une adresse absolue ou relative doit être exprimée sous forme litérale ou un label. Le mot de définition LABEL crée un en-tête dans le dictionnaire, suivi du code assembleur.

Si le code défini par LABEL est appelé par une instruction de branchement, il doit s'achever par un autre branchement ou par NEXT END-CODE. Si ce code est appelé en tant que sous-programme, il doit s'achever par l'instruction d'assemblage RET:

Dans cette version de TEST-HIGHTER, les parties de code assemblées par LABEL sont exécutées en tant que sous-programmes. Elles sont terminées par l'instruction RET. Les valeurs des sauts conditionnels ont été estimées et vérifiées à l'aide du désassembleur (voir le chapitre AIDE ET MISE AU POINT).

IMPORTANT: FORTH et le processeur partagent la même pile. A l'appel d'un sous- programme, il est déconseillé d'altérer le contenu du sommet de la pile de données. Dans le cas d'un passage de paramètre par la pile de données, il faut sauvegarder l'adresse de retour:

dx pop   \ adresse de retour dans DX
ax pop   \ récupération paramètre
...reste du programme...
ax push   \ renvoi du résultat, le cas échéant
dx push   \ restauration de l'adresse de retour
ret   \ retour de sous-programme vers la procédure d'appel

G. Les structures de contrôle.

Dans le précédent exemple illustrant l'appel des sous-programme, définition de TEST- HIGHTER, il a été nécessaire d'estimer les valeurs des sauts conditionnels vers l'avant. Cette méthode comporte de nombreux inconvénients dont le risque d'erreur dans l'estimation peut être fatal pour la bonne exécution du programme.

FORTH permet de structurer les branchements en avant sans avoir recours à une estimation ou à des labels:

Les mots IF, ELSE et THEN utilisés ici sont des macro-instructions définies dans le vocabulaire ASSEMBLER. Leur fonctionnement est différent de leurs synonymes définis dans le vocabulaire FORTH.

Le mot IF est toujours précédé par une macro-instruction de test choisie parmi celles définies dans le vocabulaire ASSEMBLER:

macro signification
OV test de débordement; OVerlay
U>= supérieur ou égal en valeur absolue
U< strictement inférieur en valeur absolue
0<> non nul
0= nul
U> strictement supérieur en valeur absolue
U<= strictement inférieur ou égal en valeur absolue
0>= supérieur ou égal à zéro; positif ou nul
0< inférieur à zéro; négatif
>= supérieur ou égal
< inférieur
> supérieur
<= inférieur ou égal

La macro-instruction de test doit impérativement être placée:

-avant IF pour une structure du type IF..THEN, IF.. ELSE.. THEN,
-avant UNTIL pour une structure de type BEGIN.. UNTIL,
-avant WHILE pour une structure de type BEGIN.. WHILE.. REPEAT.

La macro-instruction de test est remplacée par une instruction de branchement relatif lors de l'assemblage du programme. Grfce aux structures de contrôle, on évite le recours aux labels dans la majorité des cas. Les branchements sont codés sur un octet, c'est à dire dans l'intervalle [-128..+127], à l'exception des branchements assemblés par JMP et JCXZ. Une structure de contrôle incluant trop d'instructions risque de provoquer une erreur lors de l'assemblage.

Equivalences entre les macro-instructions de test et codes de branchements relatifs définis dans le vocabulaire FORTH:

Code Macro-instruction Synonyme assembleur
JMP ( branchement long)  
JO    
JNO OV  
JB U>= JNAE JC
JAE U< JNB JNC
JE 0<> JZ
JNE 0= JNZ
JBE U> JNA
JA U<= JNBE
JS 0>=  
JNS 0<  
JPE   JP
JPO   JNP
JL >= JNGE
JGE < JNL
JLE > JNG
JG <= JNLE
JCXZ ( branchement long)  

Les synonymes ne sont pas définis dans l'assembleur FORTH. S'ils sont nécessaires à la lisibilité du programme source, ils peuvent facilement être créés par l'utilisateur:

H. Accès aux interruptions système.

TURBO-Forth est un interpréteur-compilateur fort peu gourmand en mémoire. Avec son assembleur, ses ressources de mise au point, son décompilateur, il n'occupe guère plus de 26 kilo-octets de mémoire vive. Cette compacité est essentiellement d-e aux appels successifs des fonctions d'interruptions du DOS et du BIOS. Sans ces interruptions, TURBO-Forth serait beaucoup plus volumineux.

TURBO-Forth est équipé de primitives faisant appels aux fonctions d'interruptions DOS et BIOS les plus courantes. Si l'utilisateur veut accéder à des fonctions spécifiques non implémentées, il doit définir de nouvelles primitives. Exemple, écrire une version assembleur de REPLICATE affichant les caractères exclusivement à l'écran:


10 65 REPLIQUE   affiche AAAAAAAAAA

La définition du mot REPLIQUE fait appel à la fonction numéro 6 de l'interruption 21 (en hexadécimal). Chaque fonction d'interruption fait appel à un nombre variable de paramètres et renvoie un nombre variable de paramètres. Le nombre de paramètres peut être nul en entrée et en sortie.

Dans l'exemple ci-avant, on utilise la structure BEGIN.. UNTIL pour effectuer un branchement arrière. Une instruction spécifique à l'assembleur du micro- processeur 8088/86, JCXZ effectue un test de nullité directement sur le contenu du registre CX. On peut réécrire la définition de REPLIQUE sous la forme suivante:

La macro-instruction LOOP, de même que ses homologues LOOPE et LOOPNE est utilisée en principe avec la macro-instruction DO pour marquer une boucle répétitive.

Dans la définition modifiée de REPLIQUE, on remplace DO par HERE pour marquer le début de la boucle et ne pas initialiser le registre CX avec une valeur constante prédéfinie.

I. Conversion de programmes assembleur.

Le programmeur débutant est amené à s'inspirer de programmes assembleur existant pour les utiliser dans ses programmes FORTH. Il n'est pas question de s'attaquer à la conversion de programmes de taille conséquente, mais de reprendre des parties de programme en les adaptant à la syntaxe particulière de l'assembleur FORTH.

Le petit programme ci-après, écrit en assembleur préfixé, modifie le numéro de la page vidéo active:

MOV AH,5 ; sélectionne page vidéo
MOV AL,1 ; vers page 1
INT 10   ; fonction assumée par le BIOS
RET      ; retour au DOS

Pour transformer l'assembleur préfixé en assembleur postfixé utilisable par le langage FORTH, il suffit de réécrire, en lisant chaque ligne de droite à gauche, les lignes du programme initial:

MOV AH,5   devient en FORTH   5 ah mov

cependant, il faut marquer explicitement le mode d'adressage pour l'assembleur FORTH; on rajoutera le préfixe d'opérande # dans ce cas:

L'instruction RET doit être remplacée par NEXT si la routine n'est pas appelé en tant que sous-programme par une instruction CALL depuis une autre routine. On peut définir un mot PAGE1 pour changer la page vidéo active:

Pour revenir à la page vidéo initiale, il faut définir un mot PAGE0 qui annulera l'effet de PAGE1:

La carte mémoire vidéo de tout affichage 80x25 sur moniteur monochrome ou couleur (sélectionnée par 2 MODE) est partagée en quatre page vidéo distinctes numérotées de 0 à 3:

Pour sélectionner n'importe quelle page vidéo comprise entre 0 et 3 en faisant appel à une seule définition, il faut modifier PAGE0. On définit le mot PAGE:

La position du curseur n'est mémorisée que pour la bascule entre deux pages. La sélection de plus de deux pages perturbe la mémorisation de la position du curseur.

Le mot DARK efface le contenu de toutes les pages vidéo. La sélection des pages vidéo est possible également dans d'autres modes vidéo, mais produit parfois des effets surprenants selon les cartes vidéo et les systèmes.

 

 

-- hautdepage -- page d'accueil --