``Attack of the mutant Wimp''

Drôle de titre, pour l'acte 2 de l'initiation à un langage réputé difficile... Alors que l'on sait que le Wimp ne s'aborde, quel que soit le langage, que sur le tard, après avoir maîtrisé les bases dudit langage...

Hmm... Mais je n'aime pas traîner trop longtemps sur des choses trop simples: ça donne l'impression de piétiner, et je n'aime pas piétiner. Vous non plus, j'imagine, puisque vous avez choisi de vivre avec un ordinateur de la race RISC.

donc, nous allons aborder l'étude du Wimp. En assembleur. Rien que ça. Sacré saut, n'est-ce pas?


Transition: de l'addition et de !extASM

Une petite partie de transition nous permettra de faire rapidement connaissance avec la syntaxe exigée par !extASM, le shareware pour lequel j'ai finalement opté pour programmer en assembleur.

Pour cela, je vais simplement, et vous de même, taper un listing qui fera exactement ce faisait celui de l'article précédent (je vous renvoie donc à cet article pour les explications):


;Premiere addition en assembleur sur ARM

#smile
#set LMAX=1                     ;1 chiffre max.

#entry                          ;Point d'entree
        MOV R0,#1               ;R0=1
        MOV R1,#1               ;R1=1
        
        ADD R0,R0,R1            ;R0=R0+R1
        ADR R1,RESULT           ;Adresse de Result
        MOV R2,#LMAX            ;nb max. chiffres
        SWI OS_BinaryToDecimal
        
        ADR R0,MOUT             ;Adresse message
        SWI OS_Write0
        
        MOV R0,R1               ;Adresse de Result
        MOV R1,R2               ;R1=R2 (nb car.)
        SWI OS_WriteN

        SWI OS_Exit
       
.MOUT   DCB "Resultat: ",0      ;A afficher
.RESULT DBB LMAX                ;Res. Lmax octets

#end                            ;Fin d'assemblage

Je tape donc ce listing, j'appelle le fichier, par exemple ``PremAdd'', et je drague son icone sur celui de extASM, et, à moins que j'en aie décidé autrement dans les options, un fichier ``Object'' est créé dans le dossier d'origine, que je peux exécuter en double-cliquant dessus. Tout va bien.

Un coup d'oeil aux deux listings met immédiatement en lumière les principales différences entre les deux assembleurs (l'ancien et le nouveau):

Je vous renvoie au fichier d'aide de ExtASM, lisible avec StrongHelp, pour les autres finesses, mais constatez déjà avec moi que le source fait beaucoup moins "préhistoire" sous cette forme...

Ah, j'allais oublier: la directive "#smile" au début du programme ordonne à l'assembleur de m'afficher un smiley si la compilation s'est déroulée sans erreur... C'est plus utile que ça en a l'air.


Mon premier programme Wimp

Non, ce n'est pas un rêve: dans quelques minutes, nous aurons réalisé notre première application Wimp en assembleur ARM!

Que faire, en premier lieu?

Du calme, je vous sens tout excités! Nous n'allons pas trop en faire au début, tout de même!

En fait, nous allons nous contenter d'assez peu, comme nos cousins qui apprennent le Wimp en BASIC ou en C, au début. Mais rassurez-vous: nous accélèrerons très vite (enfin, cela dépendra de vos capacités d'assimilation, et de vos remarques, que j'espère voir arriver nombreuses au bureau de l'ARMada!).

Je passe sur les grandes introductions à la gloire du multi-tâche, qu'à présent tous les systêmes permettent plus ou moins, même si, à part Unix (encore que de façon moins complète), la communication entre applications est plus ou moins disponible... Pour ça, n'importe quel bouquin d'initiation au Wimp fera l'affaire.

Par contre, nous allons faire ensemble les exemples de base. Le premier de ceux-ci sera évidemment un machin qui affichera "Bonjour à tous!" avant de nous quitter...

Je tape un listing monstrueux...

Bon, allez, tapez-moi ça (j'insiste! Pas de copier-coller, retapez-le, sinon vous ne le lirez pas, ou trop peu!):


;Premier programme Wimp en assembleur
;(C)1996 A. VIDOVIC pour l'ARMada

#smile

#set lenpile=1024		;ceci devrait suffire pour une pile (en mots)

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#entry
	ADR R0,pile
	STR R13,[R0]

	MOV R13,R0		;on crée une pile DB (ED)
	
;-------Initialisation du Wimp

	MOV R0,#200		;derniere version connue de Wimp*100
	MOV R1,#&4B534154	;"TASK"
	ADR R2,nom_prog		;short description for Task Mgr
	SWI Wimp_Initialise
	ADR R0,task_handle
	STR R1,[R0]		;sauvegarder le task_handle	

;-------Polling loop

.je_polle

	MOV R0,#0		;masque (Ici tout retourner)
	ADR R1,user_buf
	SWI Wimp_Poll		;=>R0: reason code...
	
	CMP R0,#0		;pas de raison particulière
	BEQ affichage		;dans ce cas afficher le message	

	B je_polle		;sinon continuer à attendre

;-------Affichage du message

.affichage
	ADR R0,user_buf
	MOV R1,#255
	STR R1,[R0],#4		;premier mot de user_buf: code d'erreur

	ADR R1,message		;on va recopier le message dans user_buf
.copier_mes
	LDRB R2,[R1],#1		;prendre le prochain caractère
	STRB R2,[R0],#1		;le stocker dans user_buf
	CMP R2,#0		;dernier octet (le nul) copié?
	BNE copier_mes		;non: continuer à copier
	
	ADR R0,user_buf		;on va faire afficher le message	
	MOV R1,#1		;code "Ok"
	ADR R2,nom_prog		;message provenant de notre programme
	SWI Wimp_ReportError	;affiché par la sortie "Erreurs" du Wimp

;-------Fermeture su slot Wimp et sortie du programme

	ADR R0,task_handle	;récupérer le task handle
	LDR R0,[R0]
	MOV R1,#&4B534154	;"TASK"
	SWI Wimp_CloseDown	

	ADR R0,pile
	LDR R13,[R0]		;on restaure l'ancienne pile

	MOV R0,#0
	SWI OS_Exit

;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	DBD lenpile,0		;espace pour la pile programme
.pile	DBD 1,0			;sauvegarde ancien pointeur de pile


.task_handle			;espace pour contenir le handle
	DBD 1,0

.user_buf			;un bloc de 256 octets (nuls) de long
	DBB 256,0

.nom_prog			;nom à donner au Wimp, au début
	DCB "MyFirst",0

.message			;le message (thaïlandais)
	DCB "Bonjour à tous!",0

#end

Une fois ce source tapé, sauvez-le dans un fichier appelé, mettons "Wimp1", et compilez-le. Si vous n'avez pas fait de faute de frappe, le smiley vous sourit bêtement, et vous pouvez alors démarrer (Oups! Le mot malheureux!) votre exécutable, qui alors dêclenchera l'ouverture d'une fenêtre impérative, contenant un panneau "Danger particulier", intitulé "Message from Myfirst", affichant "Bonjour à tous", et vous offrant d'appuyer sur le bouton "OK"...

Amusez-vous une trentaine de fois avec, comme si c'était la première fois de votre vie que vous voyiez une boîte de message d'erreur...


Comprenons ensemble ce que nous avons tapé...

Ouuu... Mé foué... V'la-t-y pas qu'j'ons tapé beaucoup de choses nouvelles, non? Bon, ben... 'Va falloir relire avec attention...

Mais nous n'allons pas tout relire. Seulement ce qui est nouveau. Toutefois, j'insiste: Si ce n'était pas suffisant, si vous avez la moindre hésitation, si quelque chose ne vous semble pas parfaitement clair, il faut alors que vous en fassiez la remarque au bureau de l'ARMada, qui me transmettra, ce qui me permettra d'apporter tous les éclaircissements nécessaires dans le prochain numéro!

La pile

Vous avez remarqué le "#set lenpile=1024" au début du programme? Il s'agit de la définition d'une constante numérique (cf explications dans les précédents articles)... Je vais m'en servir plus loin, vers la fin:
	DBD lenpile,0		;espace pour la pile programme
.pile	DBD 1,0			;sauvegarde ancien pointeur de pile
La directive DBD x,0 permet de réserver, dans le corps du programme exécutable, une zone de x mots de long, initialisés à la valeur 0. Vous pourriez lire cette directive "Define Block DoubleWord x, value 0" (définir un bloc de mots de longueur x, de valeur 0).

Remarque: DoubleWord, c'est un mot de 32bits, car historiquement les mots faisaient 16bits, mais de nos jours, c'est 32bits.

Le label ".pile" sert à stocker l'ancienne adresse de la pile système, qui est normalement dans R13, et sert de point de départ pour la nouvelle, que je définis uniquement pour ce programme, ce qui est conseillé, car c'est toujours plus propre d'avoir sa propre pile.

La pile, c'est une zone où seront empilées des valeurs utilisées entre autres par le système... Comme nous ne nous en servons pas ici, nous y reviendrons ultérieurement. Mais vous avez noté quele registre R13 a donc une utilisation spéciale, et que vous ne pourrez pas l'utiliser sans précautions dans vos programmes!

L'accès à une zone de mémoire

Qu'est-ce que nous allons vite! Nous venons aussi, dans la foulée, de voir comment on arrive à accéder à la mémoire! Par exemple au début:
	ADR R0,task_handle
	STR R1,[R0]		;sauvegarder le task_handle	
La pseudo-instruction "ADR" permet de mettre dans le registre la valeur de l'adresse d'un label. Nous en avons parlé dans l'acte 1. Ce qui est nouveau, c'est le "STR" qui suit. Vous pouvez traduire "STR" par "Store Register", ou "Ranger le Registre". Ici, le contenu du registre R1 est recopié en mémoire, à l'adresse spécifiée dans le registre R0.

Relisez bien la phrase qui précède jusqu'à ce que vous ayez compris. C'est hyper-important.

Un peu plus loin, vous pouvez lire:

	ADR R0,task_handle	;récupérer le task handle
	LDR R0,[R0]
Ici, c'est l'opération inverse: "LDR", que vous pouvez traduire par "Load Register", ou "Charger le Registre". Il s'agit ici de lire en mémoire, à l'adresse spécifiée dans R0, un mot, et de le stocker dans... Et bien, comme vous le voyez, ici c'est dans R0, ce qui effacera l'ancienne valeur de ce registre, du coup.

En résumé, vous avez vu que rien de ce qui est dans la mémoire n'a été à un moment ou à un autre dans le microprocesseur, et nous avons vu les deux instructions:

Il y a aussi les versions "8 bits" de ces instructions: A noter: LDRB Rx,[Ry] lira seulement 8bits en mémoire et les recopiera dans les huits bits de poids faible du registre Rx, mais tous les autres bits de ce registre seront mis à 0!

Les comparaisons

Nous avons souvent, dans un programme, à comparer les variables à des valeurs, de façon à décider de la suite des évênements. La comparaison se retrouve ici par exemple à la ligne:
	CMP R0,#0		;pas de raison particulière
C'est l'instruction "CMP", comme "Compare", qui permet cette opération. Ici, on compare le contenu du registre de 32bits R0 à la valeur 0. Mais ce n'est pas la comparaison elle-même qui décide de ce qui va suivre: elle ne fait que préparer le processeur à réagir quand il rencontrera un code de condition...

Les branchements

Fichtre! A présent les branchements! Remarquez dans le listing ces lignes:
.je_polle

	(...)

	BEQ affichage		;dans ce cas afficher le message	

	B je_polle		;sinon continuer à attendre

.affichage

	(...)
Il y a des labels, à certains endroits, et des instructions de branchement. En fait, il n'y a qu'une seule instruction, pour brancher simplement:
	B label
C'est "B" comme "Brancher". C'est l'équivalent d'un GOTO en Basic ou en C (nous verrons l'équivalent du GOSUB plus tard).

Mais il y a des formes conditionnelles de "B", qu'on écrit simplement en ajoutant un code de condition de deux lettres "xx" après le "B". Les codes possibles sont:

	EQ =		MI 		HI		GT >
	NE <>		PL 		LS		LE <=
	CS		VS		GE >=		AL
	CC		VC		LT <		NV
A côté de certains d'entre eux, j'ai écrit des symboles qui donnent une idée de leur signification. Pour d'autres, cela nécessiterait de s'étendre un peu plus. Nous verrons cela ultérieurement.

Donc, nous sommes en mesure de comprendre la signification des deux lignes:

	CMP R0,#0		;pas de raison particulière
	BEQ affichage		;dans ce cas afficher le message	
Ici, donc, nous comparons la valeur de R0 avec 0. Si elle est égale, nous branchons l'exécution à partir du label ".affichage"... Autrement dit: si R0 contient une valeur nulle, nous allons à ".affichage"... Autrement dit: "IF R0=0 THEN GOTO affichage"

Les tableaux de données

Non!... il y a même des tableaux dans ce programme?... Et oui! Je vous rappelle qu'un tableau, c'est une zone de mémoire qui contient tout un ensemble de cellules de mémoire, de différentes tailles, que l'on doit traiter les unes à la suite des autres...

Par exemple: la pile, dont nous parlions plus haut... Mais nous y reviendrons plus tard. Ou encore: une chaîne de caractères, qui est un tableau de caractères, que l'on doit lire un par un. Ou encore, le tableau de données dont on transmet l'adresse au Wimp pour certaines opérations.

Pour accéder aux cellules de mémoire d'un tableau, nous pourrions faire une série d'opérations qui consisterait, à chaque fois, à mettre l'adresse de la cellule dans un registre avec "ADR", puis à accéder à la cellule... Mais il y a plus simple...

Il y a bien des moyens, mais ici, nous n'en verrons qu'un, qui s'appelle la "Post-incrémentation"... Pfff!.... Qu'est-ce que c'est que cette bête-là?

Jetez un coup d'oeil aux lignes du programme suivantes:

	ADR R0,user_buf

	(...)

	STR R1,[R0],#4		;premier mot de user_buf: code d'erreur
Ici, nous mettons d'abord l'adresse de la zone "user_buf" dans R0. Ensuite, nous écrivons le nombre contenu dans le registre R1 à l'adresse pointée par R0, mais ce n'est pas fini: le processeur, une fois cette écriture en mémoire effectuée, ajoutera 4 au contenu de R0, c'est-à-dire, il fera pointer R0 4 octets plus loin (4 octets, c'est la longueur d'un mot de 32 bits)!

Pourquoi? Parceque j'ai l'intention de continuer à écrire dans cette zone de mémoire, dans ce tableau que j'ai appelé "user_buf", après ce nombre, et sans écrire par dessus.

Post-incrémentation: J'ai incrémenté (augmenté, quoi!) après l'opération.

N'oubliez pas: post-incrémentation: virgule et incrément après le crochet fermé!

Les boucles

Fabuleux! Nous aurions pu passer 6 actes rien que sur ce programme! A présent, nous voyons les boucles!

En fait, cette notion tient compte de tout ce que vous avez déjà vu. C'est bon signe, nous allons pouvoir commencer à travailler sans, sans cesse, avoir l'impression de débuter...

Revenons sur ces quelques lignes:

	ADR R0,user_buf

	(...)

	ADR R1,message		;on va recopier le message dans user_buf
.copier_mes
	LDRB R2,[R1],#1		;prendre le prochain caractère
	STRB R2,[R0],#1		;le stocker dans user_buf
	CMP R2,#0		;dernier octet (le nul) copié?
	BNE copier_mes		;non: continuer à copier

	(...)
C'est un peu long, mais vous connaissez déjà la plupart de ces instructions. Reste maintenant à comprendre leur effet groupé.

Tout d'abord, nous chargeons R0 avec une adresse (de destination), et R1 avec une adresse (de départ). Ensuite, à partir du label, nous lisons l'octet pointé par R1 dans R2, puis le stockons à l'adresse pointée par R0.

Vous avez bien sûr noté qu'après les opérations de lecture, et d'écriture, les registres R1 et R0 pointent sur l'octet suivant en mémoire, grâce à la post-incrémentation de 1.

Ensuite, nous comparons la valeur de l'octet que nous venons de recopier avec la valeur 0. Si R2 ne contient pas de 0, nous revenons au label et recommençons l'opération. Sinon, l'exécution se poursuit plus loin.

En clair: on recopie, octet par octet, la zone pointée par R1 dans celle pointée par R0, jusqu'à ce que l'octet copié soit nul.

Nous venons de réaliser une copie de chaîne de caractères "null-terminated": vous avez remarqué, dans le listing, que la chaîne "Bonjour à tous!" se termine par un octet nul:

.message			;le message (thaïlandais)
	DCB "Bonjour à tous!",0
La directive "DCB" permet de définir une constante de valeur "octets": ici, les octets que nous incluons suivent, soit compris entre des guillemets, soit séparés par des virgules, soit les deux. Traduisez par "Define Constant Byte". (A propos, il y a aussi la directive "DBB", pour "Define Bloc Byte")

Voilà. C'était une des nombreuses sortes de boucles possibles. Ici, la boucle "UNTIL" (en anglais: "Jusqu'à").

Les fonctions du Wimp utilisées

Quand même... Il vaudrait mieux les regrouper, celles-là: Ces deux-là ne sont pas difficiles à comprendre... Passons à la suite. Nous reviendrons plus en détail sur le Reason code, au fur et à mesure que nous approfondirons notre connaissance du Wimp... Pour cette fonction, les valeurs des différents bits dans les masques sont très variées, je vous renvoie à une documentation sur le Wimp pour approfondir (par exemple, StrongHelp).


On s'en est plutôt bien tirés, non?

Bravo. Si vous avez tout suivi sans trop de problèmes, c'est bien. Si vous avez eu quelques difficultés, n'hésitez surtout pas à en faire part au bureau, et dans le prochain numéro je ferai une petite mise au point pour tout le monde...

En tous les cas, si cette façon plutôt "Bulldozer" d'avancer en assembleur vous convient, alors nous allons progresser très vite et nous pourrons plus vite que prévu sortir des programmes merveilleux de rapidité et de compacité! Et pourquoi pas des mégadémos programmées par tous les ARMadistes?

Bon, sur ce, bon courage, et à la prochaine!

Retour au sommaire