09

Ebola : premiers essais et retour à la case départ

avr
1 Comment » |  Posted by Etienne SOBOLE |  Category:ARM, Assembleur, Code, Ebola, Projets

Ca fait un petit bout de temps maintenant que j’ai testé mon idée de créer un langage.
J’avais juste pas eu le temps d’y revenir…

Finalement, après mures réflexions, mon langage est passé à la casse au profit d’un éditeur Assembleur.

Ebola, devait être un langage permettant d’écrire du code assembleur un peu plus lisible. J’ai donc fait quelques tests, et sur ce point, le résultat est plutôt concluant.
Par contre la nécessité d’utiliser une commande (gea) pour convertir mon code Ebola en code assembleur gcc me semble un peu trop contraignant et compliqué…
D’où l’idée de faire directement la conversion au moment de l’enregistrement du fichier. On ne peut plus alors parler de langage mais plutôt d’outil, un peu comme le serai dreamweaver pour le web.

Dans la foulée j’en ai profité pour remettre une couche sur la syntaxe afin de l’enrichir un peu, maintenant que j’y vois plus clair.

Voila où j’en suis de ma réflexion sur la syntaxe.

Les directives

.trace		1
.output		gcc
.optimze		0
.strict		1
.stack		1

Il y a 5 directives possibles pour le moment

  • trace : permet d’activer ou non le fonctionnement des appels “call”. Par défaut trace vaut 0.
  • output : permet de définir le format de sortie. Dans un premier temps (et sans doute pour longtemps) seul gcc sera géré. Par défaut output vaut gcc.
  • optimize : j’envisage un jour d’optimiser le code produit en réordonnancant les instrutions pour limiter les bulles dans les pipelines. Par défaut optimize vaut 0.
  • strict: interdit l’usage de l’opcode B (branch). Cela implique que toute les boucles de la fonction doivent utilliser les structures fournies par Ebola. Par défaut strict vaut 1.
  • stack: indique si Ebola est le seul a pouvoir utiliser la pile. Mettre 0 dans stack revient à interdire les fonctions récursives car les variable locales seront stockée dans le segment data. Dabs ce cas, le registre sp (r13) peut éventuellement être sauvegardé dans la but d’être utilisé par la fonction. A l’inverse mettre 1 dans stack indique que seul Ebola peut gérer la pile. L’utilisation de sp (r13) est interdit puisque sp sera utilisé pour accéder aux variables locales de la fonction. r12 quant à lui est disponible et peut éventuellement être utilisé. Par défaut stack vaut 1.

Les structures

struct pixel_rgb
   .byte red
   .byte green
   .byte blue
end struct

Les structures permettent principalement de définir des offset relatif a une adresse. pour accéder a un attribut d’une structure on utilise l’opérateur . (point)

	mov			r0, pixel_rgb.red				@ read the value of the offset red of the structure pixel_red.
	ldr			r0, [r7, pixel_rgb.red]		@ load the value of r7 + pixel_rgb.red

Les variables

Ebola gère 3 type de variables.
Les constantes auxquelles on ne peut accéder qu’en lecture.
Les variables statiques dont la portée est le fichier. Ces variables conservent leur valeurs entre chaque appel.
Les variables locales dont la portée est la fonction. Elle sont réinitialisées à chaque appel de la fonction et permettent la récursivité (si la directive .stack vaut 1).

const

const
	.byte			foo, 0
end const
	...
	mov		r4, foo								@ return the value of foo

Les variables contantes sont déclarées en amont de la fonction. Elle doivent forcément être initialisée.
On peut y accèder en assembleur directement via leur nom en utilisant l’opcode mov.
Par contre comme cela reste des variables stockées en mémoire, elles ne peuvent pas être utilisées directement avec les autres opcodes et doivent donc être chargées dans des registres avant d’être utilisées.

	adr		r0, "Hello Ebola !!!"
	adr		r1, "Hello Ebola !!!"

L’opération d’affectation d’une chaîne de caractères créé aussi une variable de type const.
De ce fait, il n’est pas possible d’écrire dans l’adresse mémoire contenu dans r0 après l’affectation.
Les chaînes statiques étant des constantes, r0 et r1 (dans le code ci-dessus) contiennent la même valeur (le même pointeur).

data

data
	.byte			foo
	.pixel_rgb	mon_objet
end data
	...
	adr		r4, data								@ return a pointer of data
	mov		r5, r4.foo							@ read the value of foo (fast syntaxe)
	ldr		r5, [r4, data.foo]				@ read the value of foo
	adr		r6, data.foo						@ return a pointer of foo

Les données contenu dans la structure data sont enregistrées dans le segment .data
Les variables data sont des variables dites statiques qui conservent leur valeurs entre deux appels de la fonction.
Typiquement, en assembleur elle sont utilisées pour y stocker des données temporairement.
Aucune initialisation n’est possible puisque la valeur des données est inconnue lorsque l’on entre dans la fonction.

stack

	stack		foo, 0
	stack		foo2
	...
	mov		r4, foo

Les variable locales, telle qu’on les connait en C sont initialisé dans le corps de la fonction grâce à l’instruction “stack”.
Si la directive .stack vaut 1 (ou n’est pas définie), alors ces données seront mise sur la pile.
Si la directive .stack vaut 0, alors ces données seront alors enregistrée dans le segment data.

l’instruction stack reserve donc la place nécessaire à la valeur puis initialise (éventuellement) la mémoire allouée avec la valeur.
Les variables locales de type stack initialisée peuvent potentiellement être assez gourmande en temps CPU. Il faut donc être bien sur que vous en avez besoin avant de les utiliser.

La lecture d’une variable locale se fait de la même façon que l’affectation d’une variable de type const.
en cas de conflit, c’est la variable locale qui est lue.

Les fonctions

function swap_red_green
   use         r0, r1, r2, r8, r9
   ...
   return

Les fonctions sont déclarées via le mot clé function. Elles se terminent par l’instruction return.
L’instruction use permet de définir quels sont les registres qui vont être utilisés par la fonction.
Si la fonction ne dispose pas d’instruction use alors elle considérera qu’aucun registre ne doit être sauvegardé.
L’instruction use est très importante. Elle permet :

  • de ne sauvegarder que les registres nécessaires
  • de restaurer correctement les registres avant de ressortir de la fonction
  • d’indiquer à l’optimiseur de code (le jour où il existera) quels sont les registres dont il dispose pour optimiser le code

Les boucles for[cc] … end for

	for[cc]		r4, #0, r2, #1
	...
	end for

la boucle for prend 4 paramètres

  • le registre d’itération
  • la valeur ou le registre de départ
  • la valeur ou le registre d’arrivée
  • l’incrément

[cc] est le code conditionnel de bouclage par défaut il vaut lt

L’équivalent assembleur de la boucle for (avant optimisation) est le suivant:

	mov			r4, #0
	cmp			r4, r2
	bge			exit_for
for:
	...
	add			r4, r4, #1
	cmp			r4, r2
	blt			for:

Rem: Si le registre d’itération n’est pas utilisé à l’intérieur de la boucle, l’optimiseur devrait être capable de remplacer une boucle for par une boucle while plus rapide.
Rem: Si le registre d’itération n’est pas utilisé à l’intérieur de la boucle et le nombre d’itération (registre d’arrivée – registre de départ) est constant, l’optimseur devrait être capable de remplacer une boucle for par une boucle loop dont la performance est optimale.

Les boules while[cc] … end while

	whilene		r4, #10
	...
	add			r4, r4, #1
	end while

La boucle while (comme dans tous les langage) n’est qu’une version simplifiée de la boucle for.
l’initialisation des variables est gérée par le développeur avant l’entrée dans le while.
L’incrément est également géré par le développeur à l’intérieur de la boucle.

L’équivalent assembleur de la boucle for (avant optimisation) est le suivant:

	cmp			r4, #10
	beq			exit_while
while:
	...
	add			r4, r4, #1
	cmp			r4, #10
	bne			while
exit_while:

Les boucles loop … do[cc] again

	mov			r4, #10
	loop
	...
	subs			r4, r4, #1
	dogt			again

Les boucles loop … do[cc] again sont une représentation exacte d’une boucle classique en assembleur.
Leur principal intérêt est qu’elles ne génère aucun code additionnel caché !

L’équivalent assembleur de la boucle loop (avant optimisation) est le suivant:

	mov			r4, #10
loop:
	...
	subs			r4, r4, #1
	bgt			loop

Du fait de leur similitude avec l’assembleur, évidement ces boucles sont les plus efficaces possibles.

Les instruction if[cc] … else … endif

	ifeq			r0, #5
	...
	elsene		r1, r7
	...
	elseeq
	...
	else
	...
	endif

l’instruction if[cc] — else[cc] … else … end if remplace aussi bien le if du C que le switch.
l’instruction else peut être conditionnelle et correspond alors a un else if.
S’il n’y a pas d’opérande à comparer alors la comparaison porte sur les opérandes précédentes. On économise alors l’instruction de comparaison.

L’équivalent assembleur de la boucle loop (avant optimisation) est le suivant:

	cmp         r0, #5
	bne         else1
	...
	b           endif
else1:
	cmp         r1, r7
	beq         else2
	...
	b           endif
else2:
	bne			else
	...
	b           endif
else:
	...
endif:

Sortir des boucles

	exit
	...
	cmp			r5, #1
	exiteq
	...
	exiteq			r5, #1

A tout moment il est possible de sortir d’une boucle en utilisant l’instruction exit[cc].
En cas de sortie conditionnelle, L’opération de test (ou de comparaison) peut soit être spécifiée et donc réalisé par Ebola, soit réalisée par le développeur.

Conclusion

La syntaxe Ebola n’est pas prévu pour simplifier grandement le code assembleur. Par contre elle structure le programme et le rend plus lisible.
Elle simplifie aussi grandement le portage d’un source C (par exemple).
Enfin Ebola devrait permettre dans le même outil d’éditer un code assembleur et de compter les cycles du programme, ce qui devrait permettre un gain de temps énorme puisque l’optimisation pourra se faire en même temps que le développement.

La version 0.6 du compteur de cycles (que j’ai presque fini) implémente un nouveau moteur qui permet le calcul en temps réel (disons extrêmement court) du nombre de cycle d’un programme.

Enfin tout est là dans Ebola, pour que dans un avenir proche je me livre à un nouveau passe-temps : l’optimisation automatique d’un code assembleur. J’aurai alors l’occasion de revenir sur les choix que j’ai fait sur telle ou telle spécification d’Ebola.

One Response to “Ebola : premiers essais et retour à la case départ”

  1. [...] This post is a translation of « Ebola : premiers essais et retour à la case départ » [...]

Répondre

Human control : 4 + 6 =