07

Ebola – L’assembleur évolué.

mar
No Comments |  Posted by Etienne SOBOLE |  Category:Ebola, Projets

J’ai attaqué il y a quelques semaines la décompression JPEG en assembleur.
Et là, il a bien fallu se rendre compte que l’assembleur, c’est pas très dur, mais c’est difficilement lisible (surtout re-lisible en fait).

Certes les commentaires sont là pour aider, mais finalement cela s’avère généralement insuffisant.
Il a donc fallu trouver un juste milieu entre un langage un poil plus évolué et la performance de l’assembleur.
J’ai donc décidé de créer mon propre “langage”!

Alors par langage il faut comprendre un truc extrêmement simpliste dont le seul but est de rendre un source un peu plus lisible sans nuire (trop) aux performances.
J’ai décidé d’appeler mon langage Ebola. Parce que çà va être une tuerie ;)
Il s’agit plus d’un ensemble de “macro” que d’un vrai langage. Un ensemble de modification mineure de l’assembleur…

Petit exemple

Bon! Le mieux pour comprendre le langage est encore de vous proposer un petit exemple qui regroupe la majeure partie des améliorations, puis de vous les expliquer.

Voilà a quoi ressemble un code source Ebola.
Fichier: convert_pixel.ea

	.debug 1
struct pixel
	.byte red
	.byte green
	.byte blue
end struct

function swap_red_green
	use			r0, r1, r2, r8, r9
@ r0 = nb pixel
@ r1 = pixel buffer

	call			printf, "Start"

	for			r0, #0, r2, #1
	ldrb			r8, [r1, pixel.red]
	ldrb			r9, [r1, pixel.green]
	strb			r8, [r1, pixel.green]
	strb			r9, [r1, pixel.red]
	add			r1, pixel.size
	endfor

	call			printf, "End"
	return

Ce code est equivalent à la version assembleur gcc suivante.
Fichier: convert_pixel.s

	.global	swap_red_green
	.type	swap_red_green, %function
	.align	4
swap_red_green:
	push			{r8, r9}

	push			{r0-r4}
	movw			r0, #:lower16:.str1
	movt			r0, #:upper16:.str1
	bl				printf
	pop			{r0-r4}

	mov			r2, #0
	cmp			r2, r0
	bge			swap_red_green_for1_exit
swap_red_green_for1_start:
	ldrb			r8, [r1]
	ldrb			r9, [r1, #1]
	strb			r8, [r1, #1]
	strb			r9, [r1]
	add			r1, r1, #3
	adds			r2, r2, #1
	blt			swap_red_green_for1_start
swap_red_green_for1_exit:

	push			{r0-r4}
	movw			r0, #:lower16:.str2
	movt			r0, #:upper16:.str2
	bl				printf
	pop			{r0-r4}

	mov			pc, lr

.data
	.align	4
.str1:
	.ascii	"Start\000"
.str2:
	.ascii	"End\000"

Alors cela ne semble pas très compliqué ni très évolué d’ailleurs.
Voici les évolutions qu’Ebola apporte à l’assembleur.

Les structures

Ebola permet la définition de structures

struct pixel
	.byte red
	.byte green
	.byte blue
end struct

Ces structures, contrairement au C ou C++ ne permettent pas de définir des espace mémoires, mais de définir des offsets
pixel.red vaut donc 0
pixel.green vaut 1
pixel.blue vaut 2
pixel.size vaut 3 (à savoir la taille de la structure)

ces variables peuvent être utilisée dans le code Ebola

	ldr			r0, [r1], pixel.size

Les structures peuvent être enregistrée dans un fichier à part (.eah)
Dans ce cas on peut inclure les fichier grâce à la directive .include

	.include "pixel.eah"

Les fonctions

Le mot clé function permet de définir une fonction. La fonction se termine par return.
l’opcode use permet de spécifier quelques sont les registres qu’il convient de sauvegarder. use réalise à la fois la sauvegarde mais également la restauration lors du return.

function my_function
   use         r0, r1, r2, r8, r9			@ register used by the function
   													@ do the push (from r5 to r14)
	...

   return											@ do the pop and the mov pc, lr

debug et call

J’ai ajouté un opcode call[cc] qui permet d’appeler une fonction et s’occupant de sauvegarder tous les registres et de réaffecter aux registres d’appel les bonne valeurs.
Parce que call sera une vrai usine à gaz, il n’est utilisé que si la directive .debug est mise à 1. Sinon call est tout simplement ignoré.
call est conditionnel et peut utiliser toutes les conditions habituelles de l’ARM (eq, lt, le, ne, …).

	.debug 1											@ indicate that compilation will used debug mode
														@ in this case call opcode will work
	...
	call			print_f, "my value is %d", r5
	...

for et endfor

l’opcode for[cc] permet de définir une boucle entre deux bornes (voir un peu plus).
for[cc] prend 4 paramètres :

  • Rd : le registre de boucle
  • la valeur de départ
  • Rn : le registre de comparaison
  • l’incrément

la boucle for d’Ebola ne permet qu’un test sur un seul registre. Il n’est pas possible de de faire un test sur deux conditions, ni d’utiliser une valeur mémoire.

Si l’on a besoin de sortir de la boucle avant la fin de celle-ci on peut utiliser l’opcode conditionnel stop[cc]

	for			r0, #0, r2, #5
	add			r4, r4, #1
	cmp			r4, 100
	stopge
	endfor

for[cc] peut être suivi par le mode de comparaison. Par défaut for compare la différence [ne], c’est à dire que tant que le registre Rd est différent du registres Rn la boucle s’exécute.

Il est possible de définir une condition différente:
forlt : tant que Rd < Rn
forle : tant que Rd <= Rn
forgt : tant que Rd > Rn
forge : tant que Rd >= Rn

while et endwhile

Un while et un for sont relativement similaire. Donc inutile de détailler trop la boucle while, elle fonctionne comme un for.
La seule différence notoire est que la condition est obligatoire.

	whilene		r0, r2
	add			r4, r4, #1
	cmp			r4, 100
	stopge
	add			r0, r0, #1
	endwhile

Définition des chaînes statiques

Le truc le plus pénible avec les chaînes de caractère en assembleur est de devoir aller les déclarer dans la section data.
Avec Ebola, il n’est plus nécessaire de définir des chaines de caractères dans la section data. Elles sont déclarées automatiquement. elles peuvent être utilisées avec les opcodes MOV, LDR, ADR et CALL.

adr r0, "Hello Ebola !!!"
mov r0, "Hello Ebola !!!"
ldr r0, "Hello Ebola !!!"
call printf, "Hello Ebola !!!"

Les chaînes sont toujours terminée par le caractère null.

Remarques

Pour l’instant je ne me suis pas décidé à implémenter le IF … ELSE … ENDIF.
Il offre certes un niveau de visibilité très élevé, mais sa conversion en instruction assembleur à généralement un coût en terme de performances.

D’une manière général, mon super langage n’est jamais qu’un brouillon. et puisqu’il ne va sans doute n’y avoir qu’un seul utilisateur de ce langage, j’aurai toujours la possibilité de l’améliorer :)

Bon ben il ne me reste plus qu’à écrire une commande qui converti le code source Ebola en assembleur capable de mapper les numéro de lignes des erreurs de compilation retournées par gcc en numéro de ligne Ebola !

Répondre

Human control : 6 + 3 =